25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

252 lines
9.4KB

  1. from todoist.api import TodoistAPI
  2. import sys
  3. from datetime import date, time, datetime, timedelta
  4. import gi
  5. gi.require_version('Gio', '2.0')
  6. from gi.repository import Gio
  7. import re
  8. ##@brief Simple function to parse iso formated string from api to a datetimeformat.
  9. # @param datastr String with date and time in iso format
  10. # @return datetime object with date as specified in string.
  11. def fromisoformat(datestr):
  12. if datestr == None:
  13. return None
  14. # Matches on YYYY-MM-DD, does not check if date is in range. Ex: 2019-11-08
  15. if (re.match(r'^\d{4}-\d{2}-\d{2}$',datestr, 0) != None):
  16. return datetime.strptime(datestr,"%Y-%m-%d")
  17. # Matches on YYYY-MM-DDTHH:MM:SSZ, does not check if date is in range. Ex: 2019-11-08T15:45:02Z
  18. if (re.match(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$',datestr, 0) != None):
  19. return datetime.strptime(datestr,"%Y-%m-%dT%H:%M:%SZ")
  20. # Matches on YYYY-MM-DDTHH:MM:SS, does not check if date is in range. Ex: 2019-11-08T15:45:02
  21. if (re.match(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$',datestr, 0) != None):
  22. return datetime.strptime(datestr,"%Y-%m-%dT%H:%M:%S")
  23. else:
  24. return None
  25. class TodoProject:
  26. ''' Common class for api calls'''
  27. def __init__(self,project=None):
  28. fh_api = open("apikey","r")
  29. self.apikey = fh_api.read()
  30. self.api = TodoistAPI(self.apikey)
  31. self.sync()
  32. if project != None:
  33. if isinstance(project,int):
  34. self.project= project
  35. else:
  36. for project in self.api["projects"]:
  37. if(project["name"] == project):
  38. self.project = project["id"]
  39. else:
  40. self.project == None
  41. self.items = TodoItemList(None,self.api)
  42. def sync(self):
  43. return self.api.sync()
  44. def checktaskstoday(self,tasklimit):
  45. if len(self.items.itemsForDay()) < tasklimit:
  46. self.sendnotification("TASKS","Not enough Tasks Scheduled for today!")
  47. def checkTasksWithoutNotes(self,date=date.today()):
  48. if len(self.items.itemsClosedWithoutNotes())>0:
  49. self.sendnotification("TASKS","There are tasks without a log.")
  50. def checkTasksOverDue(self):
  51. _overdueItems = len(self.items.itemsOverDue())
  52. if _overdueItems==1:
  53. self.sendnotification("TASKS","There is one task over due!")
  54. elif _overdueItems>1:
  55. self.sendnotification("TASKS","There are " + str(_overdueItems) + " tasks over due!")
  56. def sendnotification(self,title,body):
  57. Application = Gio.Application.new("todoist.tasks", Gio.ApplicationFlags.FLAGS_NONE)
  58. Application.register()
  59. Notification = Gio.Notification.new(title)
  60. Notification.set_body(body)
  61. Priority = Gio.NotificationPriority(2)
  62. Notification.set_priority(Priority)
  63. Icon = Gio.ThemedIcon.new("flag")
  64. Notification.set_icon(Icon)
  65. Application.send_notification(None, Notification)
  66. ##
  67. # @class TodoItemList
  68. # @brief Contains a list of items
  69. # Can be called like a list of items but has some aditional methods.
  70. class TodoItemList:
  71. ##
  72. # @brief Constructor
  73. # @param project can be the "name" or "id" of a project. If None the constructor will load all items from all projects
  74. # @param api object form TodoistAPI
  75. def __init__(self,project,api):
  76. self.api = api
  77. self.api.sync()
  78. self.items = []
  79. self.items_by_id = {}
  80. for item in self.api['items']:
  81. todoItem = TodoItem(item,self.api)
  82. if project==None or todoItem.project_id == project:
  83. self.items_by_id[str(todoItem.id)] = todoItem
  84. self.items.append(todoItem)
  85. # Also load all notes and append them to their corresponding item.
  86. for note in self.api['notes']:
  87. todoNote = TodoNote(note,self.api)
  88. self.items_by_id.get(str(todoNote.item_id)).addNote(todoNote)
  89. ##@brief magic method definition to so this object can be used as a list.
  90. # @param key index
  91. def __getitem__(self,key):
  92. return self.items[key]
  93. ##@brief magic method definition to so this object can be used as a list.
  94. def __len__(self):
  95. return len(self.items)
  96. ##@brief magic method definition to so this object can be used as a list.
  97. def __iter__(self):
  98. return iter(self.items)
  99. ##@brief magic method definition to so this object can be used as a list.
  100. def __reversed__(self):
  101. return reversed(self.items)
  102. ##@brief get all items that are due for specified day.
  103. # @_date date-object to specify the search day. Defaults to today.
  104. # @return list of items that have a duedate as specified in _date.
  105. def itemsForDay(self,_date=date.today()):
  106. _items = []
  107. for item in self.items:
  108. if item.dueOnDate(_date):
  109. _items.append(item)
  110. return _items
  111. ##@brief get all items that closed but do not have notes attached to them
  112. # @return list of closed items that do not have notes attached.
  113. def itemsClosedWithoutNotes(self):
  114. _items = []
  115. for item in self.items:
  116. if item.closedWithoutNotes():
  117. _items.append(item)
  118. return _items
  119. ##@brief get all items that are still open but passed there deadline
  120. # @return list of open items that passed their deadline
  121. def itemsOverDue(self):
  122. _items = []
  123. for item in self.items:
  124. if item.isOverDue():
  125. _items.append(item)
  126. return _items
  127. ##@class TodoItem
  128. # @brief Information of an Item/Task
  129. class TodoItem:
  130. ##@brief Constructor of TodoItem-object
  131. # @param item dict that is given by the TodoistAPI
  132. # @param dict that is given via the TodoistAPI
  133. # @param api TodoistAPI object
  134. def __init__(self,item,api):
  135. self.api = api
  136. self.id = item["id"]
  137. self.user_id = item["user_id"]
  138. self.project_id = item["project_id"]
  139. self.content = item["content"]
  140. self.priority = item["priority"]
  141. self.parent_id = item["parent_id"]
  142. self.child_order = item["child_order"]
  143. self.section_id = item["section_id"]
  144. self.day_order = item["day_order"]
  145. self.collapsed = item["collapsed"]
  146. self.labels = item["labels"]
  147. self.assigned_by_uid = item["assigned_by_uid"]
  148. self.responsible_uid = item["responsible_uid"]
  149. self.checked = item["checked"]==1
  150. self.in_history = item["in_history"]
  151. self.is_deleted = item["is_deleted"]
  152. self.sync_id = item["sync_id"]
  153. self.date_added = fromisoformat(item["date_added"])
  154. self.date_completed = fromisoformat(item["date_completed"])
  155. #add also empty list for nodes
  156. self.notes = []
  157. self._due = item["due"]
  158. #check if due is set.
  159. if self._due != None:
  160. self.due_date = fromisoformat(self._due["date"])
  161. self.due_timezone = self._due["timezone"]
  162. self.due_string = self._due["string"]
  163. self.due_lang = self._due["lang"]
  164. self.due_is_recurring = self._due["is_recurring"]
  165. else:
  166. self.due_date = None
  167. self.due_timezone = None
  168. self.due_string = None
  169. self.due_lang = None
  170. self.due_is_recurring = None
  171. ##@brief Check wether a duedate is set
  172. # @return True if duedate is set
  173. def hasDue(self):
  174. return self._due != None
  175. ##@brief Check wether the item has passed its deadline
  176. # @return True is overDue
  177. def isOverDue(self):
  178. if self.hasDue() and not self.checked:
  179. return self.due_date.date() < date.today()
  180. else:
  181. return False
  182. ##@brief Check wether the item has has any notes attached to it
  183. # @return True if notes are attached
  184. def hasNotes(self):
  185. return (len(self.notes)>0)
  186. ##@brief add a note to this item.
  187. # @param note object that should be appended to this item
  188. def addNote(self,note):
  189. self.notes.append(note)
  190. ##@brief Check if this task is closed but does not have any notes
  191. # @return True is closed without notes attached
  192. def closedWithoutNotes(self):
  193. return self.checked and not self.hasNotes()
  194. ##@brief Check if this item is almost due(today) but has not notes yet
  195. # @return True if it has no notes but has be finished today
  196. def almostDueWithoutNotes(self):
  197. return self.dueOnDate() and not self.hasNotes()
  198. ##@brief Check if the item is due on specific day
  199. # @param _date date(time) object for which day to check. Defaults to today.
  200. # @return False if not due on specified date
  201. def dueOnDate(self,_date = date.today()):
  202. if self.hasDue():
  203. if isinstance(_date,datetime):
  204. _date = date.date()
  205. return self.due_date.date() == _date
  206. else:
  207. return False
  208. ##@class TodoNote
  209. # @brief Simple implementation for note-objects
  210. class TodoNote:
  211. ##@brief constructor of class
  212. # @param note dict with note-information from api
  213. # @param api TodoistAPI-object
  214. def __init__(self,note,api):
  215. self.id = note["id"]
  216. self.posted_uid = note["posted_uid"]
  217. self.project_id = note["project_id"]
  218. self.item_id = note["item_id"]
  219. self.content = note["content"]
  220. self.file_attachment = note["file_attachment"]
  221. self.uids_to_notify = note["uids_to_notify"]
  222. self.is_deleted = note["is_deleted"]
  223. self.posted = note["posted"]
  224. self.reactions = note["reactions"]