1. import os, json, datetime
  2. from dataclasses import dataclass
  3. from typing import List, Optional
  4. filepath = os.path.expanduser("~/Downloads/super-productivity-backup.json")
  5. with open(filepath) as f:
  6. data = json.loads(f.read())
  7. @dataclass
  8. class Task:
  9. id: str
  10. parent: Optional[str]
  11. title: str
  12. reminder: Optional[int]
  13. planned: Optional[int]
  14. tags: List[str]
  15. children: List[str]
  16. done: bool
  17. # reminder id to timestamp mapping
  18. reminders = {
  19. reminder["id"]: reminder["remindAt"] for reminder in data["reminders"]
  20. }
  21. # tag id to tag name mapping
  22. tags = {
  23. id: tag["title"] for id, tag in data["tag"]["entities"].items()
  24. }
  25. projects = {
  26. id: project["taskIds"] for id, project in data["project"]["entities"].items()
  27. }
  28. # task id to task object mapping
  29. tasks = {
  30. id: Task(
  31. id = id,
  32. parent = task["parentId"],
  33. title = task["title"],
  34. reminder = reminders[task["reminderId"]] if task["reminderId"] else None,
  35. planned = task["plannedAt"],
  36. tags = task["tagIds"],
  37. children = task["subTaskIds"],
  38. done = task["isDone"]
  39. ) for id, task in data["task"]["entities"].items()
  40. }
  41. # convert a task to org-mode according to its level
  42. def process_task(task: Task, level: int):
  43. asterisks = "*" * level
  44. tags_str = ":".join([tags[tag_id] for tag_id in task.tags])
  45. if tags_str:
  46. tags_str = f":{tags_str}:"
  47. status = "DONE" if task.done else "TODO"
  48. # print headline in a children aware way
  49. if task.children:
  50. total = len(task.children)
  51. done = sum(1 if task.done else 0 for task in (tasks[id] for id in task.children))
  52. print(f"{asterisks} {status} [{done}/{total}] {task.title} {tags_str}")
  53. else:
  54. print(f"{asterisks} {status} {task.title} {tags_str}")
  55. # print scheduled and deadline (if any)
  56. if task.planned:
  57. print(f"SCHEDULED: {timestamp_to_org(task.planned / 1000)} ", end="")
  58. if task.reminder:
  59. print(f"DEADLINE: {timestamp_to_org(task.reminder / 1000)}")
  60. print("")
  61. # now recursively handle all sub-tasks
  62. for child in task.children:
  63. process_task(tasks[child], level = level + 1)
  64. def timestamp_to_org(ts: int) -> str:
  65. return datetime.datetime.fromtimestamp(ts).strftime("<%Y-%m-%d %a %H:%M>")
  66. def main():
  67. # We start with iterating top-level tasks that have no parent
  68. for task in tasks.values():
  69. if task.parent == None:
  70. process_task(task, level = 1)
  71. # However if you prefer to start from Projects as top level headline instead,
  72. # Then comment out above part, and uncomment the below
  73. # for project, task_ids in projects.items():
  74. # print(f"* {project}\n")
  75. # for id in task_ids:
  76. # process_task(tasks[id], level = 2)
  77. main()