25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

277 satır
11KB

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