Added the management of completed tasks
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
TODOIST_TOKEN=fbbe7596bcb32135a2d1d986fc729cc70211a8ff
|
||||
|
||||
TUDUDI_BASE_URL=https://tududi.diruscio.org
|
||||
TUDUDI_API_KEY=tt_78d7a84e8dcebebeaf300f1557ae7bd8390bddc9f257282d013909cfc0dfd6d2
|
||||
TUDUDI_SQLITE_PATH=./tududi_db/tudodi.sqlite3
|
||||
|
||||
DRY_RUN=false
|
||||
CLEAN_FIRST=True
|
||||
USE_AREAS=False
|
||||
SKIP_COMPLETED_TASKS=True
|
||||
SKIP_ARCHIVED_PROJECTS=True
|
||||
SKIP_EMPTY_PROJECTS=True
|
||||
REQUEST_SLEEP=0.10
|
||||
|
||||
@@ -8,6 +8,8 @@ TUDUDI_API_KEY=your_tududi_api_key
|
||||
DRY_RUN=false
|
||||
CLEAN_FIRST=True
|
||||
USE_AREAS=False
|
||||
SKIP_COMPLETED_TASKS=True
|
||||
REQUEST_SLEEP=0.10
|
||||
```
|
||||
|
||||
Make sure to replace `your_todoist_api_token`, `https://your_tududi_instance_url`, and `your_tududi_api_key` with your actual credentials.
|
||||
|
||||
@@ -43,18 +43,48 @@ load_dotenv()
|
||||
# ----------------------------
|
||||
def slug_tag(name: str) -> str:
|
||||
"""
|
||||
Convert label/tag to Tududi-safe ASCII slug:
|
||||
- removes emoji/non-ascii
|
||||
- removes accents
|
||||
- keeps a-z0-9_- and collapses spaces to _
|
||||
Convert label/tag to Tududi-safe slug while preserving emojis:
|
||||
- emojis at the beginning are moved to the end
|
||||
- text is ASCII-normalized
|
||||
- result: <slug>_<emoji>
|
||||
Example:
|
||||
"💡 IDEAS" -> "ideas_💡"
|
||||
"📚 Reading list" -> "reading_list_📚"
|
||||
"""
|
||||
s = unicodedata.normalize("NFKD", name)
|
||||
|
||||
if not name:
|
||||
return "tag"
|
||||
|
||||
# 1) Extract emojis (very permissive, Unicode-aware)
|
||||
emojis = re.findall(
|
||||
r"[\U0001F300-\U0001FAFF\U00002600-\U000027BF]",
|
||||
name
|
||||
)
|
||||
|
||||
# 2) Remove emojis from text
|
||||
text = re.sub(
|
||||
r"[\U0001F300-\U0001FAFF\U00002600-\U000027BF]",
|
||||
"",
|
||||
name
|
||||
)
|
||||
|
||||
# 3) Normalize text → ASCII slug
|
||||
s = unicodedata.normalize("NFKD", text)
|
||||
s = "".join(ch for ch in s if not unicodedata.combining(ch))
|
||||
s = s.encode("ascii", "ignore").decode("ascii")
|
||||
s = s.strip().lower()
|
||||
s = re.sub(r"\s+", "_", s)
|
||||
s = re.sub(r"[^a-z0-9_-]", "", s)
|
||||
return s or "tag"
|
||||
|
||||
if not s:
|
||||
s = "tag"
|
||||
|
||||
# 4) Append emojis at the end (if any)
|
||||
if emojis:
|
||||
s = f"{s}_{''.join(emojis)}"
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def normalize_list(resp: Any) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
@@ -661,18 +691,40 @@ def main() -> None:
|
||||
tududi_key = os.getenv("TUDUDI_API_KEY")
|
||||
tududi_email = os.getenv("TUDUDI_EMAIL")
|
||||
tududi_password = os.getenv("TUDUDI_PASSWORD")
|
||||
|
||||
|
||||
|
||||
dry_run = getenv_bool("DRY_RUN", False)
|
||||
clean_first = getenv_bool("CLEAN_FIRST", False)
|
||||
use_areas = getenv_bool("USE_AREAS", False)
|
||||
request_sleep = getenv_float("REQUEST_SLEEP", 0.25)
|
||||
skip_completed_tasks = getenv_bool("SKIP_COMPLETED_TASKS", False)
|
||||
skip_archived_projects = getenv_bool("SKIP_ARCHIVED_PROJECTS", False)
|
||||
skip_empty_projects = getenv_bool("SKIP_EMPTY_PROJECTS", False)
|
||||
|
||||
print(f"[INFO] Starting migration @ {iso_now()} (dry_run={dry_run})")
|
||||
print(f"[INFO] SKIP_COMPLETED_TASKS={skip_completed_tasks}")
|
||||
print(f"[INFO] Options: "
|
||||
f"SKIP_ARCHIVED_PROJECTS={skip_archived_projects}, "
|
||||
f"SKIP_EMPTY_PROJECTS={skip_empty_projects}")
|
||||
print("[INFO] Exporting from Todoist (sync full + completed best-effort)...")
|
||||
|
||||
tdc = TodoistClient(todoist_token)
|
||||
data = tdc.export_everything()
|
||||
|
||||
# Build project -> task count map (active + completed)
|
||||
project_task_count: Dict[str, int] = {}
|
||||
|
||||
for t in data.items:
|
||||
pid = t.get("project_id") or t.get("projectId")
|
||||
if pid is not None:
|
||||
project_task_count[str(pid)] = project_task_count.get(str(pid), 0) + 1
|
||||
|
||||
for t in data.completed_tasks:
|
||||
pid = t.get("project_id") or t.get("projectId")
|
||||
if pid is not None:
|
||||
project_task_count[str(pid)] = project_task_count.get(str(pid), 0) + 1
|
||||
|
||||
print(
|
||||
f"[INFO] Todoist: projects={len(data.projects)} labels={len(data.labels)} "
|
||||
f"sections={len(data.sections)} active_items={len(data.items)} "
|
||||
@@ -754,6 +806,17 @@ def main() -> None:
|
||||
name = p.get("name")
|
||||
if not name:
|
||||
continue
|
||||
|
||||
# --- SKIP ARCHIVED PROJECTS ---
|
||||
if skip_archived_projects and p.get("is_archived"):
|
||||
print(f"[SKIP] Archived project: {name}")
|
||||
continue
|
||||
|
||||
# --- SKIP EMPTY PROJECTS ---
|
||||
if skip_empty_projects and project_task_count.get(tid, 0) == 0:
|
||||
print(f"[SKIP] Empty project: {name}")
|
||||
continue
|
||||
|
||||
|
||||
if name in proj_by_name:
|
||||
# prendi ID numerico se esiste, fallback su uid se proprio manca (ma dovrebbe esserci)
|
||||
@@ -794,12 +857,15 @@ def main() -> None:
|
||||
# Comments index
|
||||
comments_idx = build_comments_index(data.notes)
|
||||
|
||||
# Merge active + completed tasks
|
||||
# Merge active + (if requested) completed tasks
|
||||
all_tasks_src: List[Tuple[Dict[str, Any], bool]] = []
|
||||
for i in data.items:
|
||||
all_tasks_src.append((i, False))
|
||||
for c in data.completed_tasks:
|
||||
all_tasks_src.append((c, True))
|
||||
if skip_completed_tasks:
|
||||
print(f"[INFO] Skipping completed tasks from Todoist export: {len(data.completed_tasks)}")
|
||||
else:
|
||||
for c in data.completed_tasks:
|
||||
all_tasks_src.append((c, True))
|
||||
|
||||
items_like = [x for (x, _done) in all_tasks_src if "id" in x]
|
||||
items_sorted = topo_sort_items(items_like)
|
||||
@@ -878,6 +944,11 @@ def main() -> None:
|
||||
due = normalize_due(task)
|
||||
prio = map_priority(task.get("priority"))
|
||||
project_uid = get_project_uid_for_task(task)
|
||||
|
||||
# Skip task if project was skipped
|
||||
if project_uid is None and (task.get("project_id") or task.get("projectId")):
|
||||
print(f"[SKIP] Task {tid} belongs to skipped project")
|
||||
continue
|
||||
|
||||
# Tududi note: Todoist link + Todoist description
|
||||
tududi_description = build_tududi_description_from_todoist(task, name)
|
||||
|
||||
Reference in New Issue
Block a user