Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

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