From 9e364c9df3f05c27138dde33487bffbc697a9d73 Mon Sep 17 00:00:00 2001 From: Wouter Horlings Date: Mon, 18 Nov 2019 17:22:20 +0100 Subject: [PATCH] Added classes for Todoistapi, simple main(), and package requirements --- myTodoist.py | 248 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 42 ++++++++ taskcheck.py | 19 ++++ 3 files changed, 309 insertions(+) create mode 100644 myTodoist.py create mode 100644 requirements.txt create mode 100644 taskcheck.py diff --git a/myTodoist.py b/myTodoist.py new file mode 100644 index 0000000..7ca66ab --- /dev/null +++ b/myTodoist.py @@ -0,0 +1,248 @@ +from todoist.api import TodoistAPI +import sys +from datetime import date, time, datetime, timedelta +import gi +gi.require_version('Gio', '2.0') +from gi.repository import Gio + + +##@brief Simple function to parse string from api to a datetimeformat. +# @param datastr String with date +# @return datetime object with date as specified in string. +def makedatetime(datestr): + if datestr == None: + return None + return datetime.strptime(datestr,"%Y-%m-%d") + +##@brief Simple function to parse iso formated string from api to a datetimeformat. +# @param datastr String with date and time in iso format +# @return datetime object with date as specified in string. +def fromisoformat(datestr): + if datestr == None: + return None + return datetime.strptime(datestr,"%Y-%m-%dT%H:%M:%SZ") + +class TodoProject: + ''' Common class for api calls''' + + def __init__(self,project=None): + fh_api = open("apikey","r") + self.apikey = fh_api.read() + self.api = TodoistAPI(self.apikey) + self.sync() + if project != None: + if isinstance(project,int): + self.project= project + else: + for project in self.api["projects"]: + if(project["name"] == project): + self.project = project["id"] + else: + self.project == None + self.items = TodoItemList(None,self.api) + + def sync(self): + return self.api.sync() + + def checktaskstoday(self,tasklimit): + if len(self.items.itemsForDay()) < tasklimit: + self.sendnotification("TASKS","Not enough Tasks Scheduled for today!") + + def checkTasksWithoutNotes(self,date=date.today()): + if len(self.items.itemsClosedWithoutNotes())>0: + self.sendnotification("TASKS","There are tasks without a log.") + + def checkTasksOverDue(self): + _overdueItems = len(self.items.itemsOverDue()) + if _overdueItems==1: + self.sendnotification("TASKS","There is one task over due!") + elif _overdueItems>1: + self.sendnotification("TASKS","There are " + str(_overdueItems) + " tasks over due!") + + def sendnotification(self,title,body): + Application = Gio.Application.new("todoist.tasks", Gio.ApplicationFlags.FLAGS_NONE) + Application.register() + Notification = Gio.Notification.new(title) + Notification.set_body(body) + Priority = Gio.NotificationPriority(2) + Notification.set_priority(Priority) + Icon = Gio.ThemedIcon.new("flag") + Notification.set_icon(Icon) + Application.send_notification(None, Notification) + +## +# @class TodoItemList +# @brief Contains a list of items +# Can be called like a list of items but has some aditional methods. +class TodoItemList: + + ## + # @brief Constructor + # @param project can be the "name" or "id" of a project. If None the constructor will load all items from all projects + # @param api object form TodoistAPI + def __init__(self,project,api): + self.api = api + self.api.sync() + self.items = [] + self.items_by_id = {} + for item in self.api['items']: + todoItem = TodoItem(item,self.api) + if project==None or todoItem.project_id == project: + self.items_by_id[str(todoItem.id)] = todoItem + self.items.append(todoItem) + # Also load all notes and append them to their corresponding item. + for note in self.api['notes']: + todoNote = TodoNote(note,self.api) + self.items_by_id.get(str(todoNote.item_id)).addNote(todoNote) + + + ##@brief magic method definition to so this object can be used as a list. + # @param key index + def __getitem__(self,key): + return self.items[key] + + ##@brief magic method definition to so this object can be used as a list. + def __len__(self): + return len(self.items) + + ##@brief magic method definition to so this object can be used as a list. + def __iter__(self): + return iter(self.items) + + ##@brief magic method definition to so this object can be used as a list. + def __reversed__(self): + return reversed(self.items) + + ##@brief get all items that are due for specified day. + # @_date date-object to specify the search day. Defaults to today. + # @return list of items that have a duedate as specified in _date. + def itemsForDay(self,_date=date.today()): + _items = [] + for item in self.items: + if item.dueOnDate(_date): + _items.append(item) + return _items + + ##@brief get all items that closed but do not have notes attached to them + # @return list of closed items that do not have notes attached. + def itemsClosedWithoutNotes(self): + _items = [] + for item in self.items: + if item.closedWithoutNotes(): + _items.append(item) + return _items + + ##@brief get all items that are still open but passed there deadline + # @return list of open items that passed their deadline + def itemsOverDue(self): + _items = [] + for item in self.items: + if item.isOverDue(): + _items.append(item) + return _items + +##@class TodoItem +# @brief Information of an Item/Task +class TodoItem: + ##@brief Constructor of TodoItem-object + # @param item dict that is given by the TodoistAPI + # @param dict that is given via the TodoistAPI + # @param api TodoistAPI object + def __init__(self,item,api): + self.api = api + self.id = item["id"] + self.user_id = item["user_id"] + self.project_id = item["project_id"] + self.content = item["content"] + self.priority = item["priority"] + self.parent_id = item["parent_id"] + self.child_order = item["child_order"] + self.section_id = item["section_id"] + self.day_order = item["day_order"] + self.collapsed = item["collapsed"] + self.labels = item["labels"] + self.assigned_by_uid = item["assigned_by_uid"] + self.responsible_uid = item["responsible_uid"] + self.checked = item["checked"]==1 + self.in_history = item["in_history"] + self.is_deleted = item["is_deleted"] + self.sync_id = item["sync_id"] + self.date_added = fromisoformat(item["date_added"]) + self.date_completed = fromisoformat(item["date_completed"]) + #add also empty list for nodes + self.notes = [] + self._due = item["due"] + #check if due is set. + if self._due != None: + self.due_date = makedatetime(self._due["date"]) + self.due_timezone = self._due["timezone"] + self.due_string = self._due["string"] + self.due_lang = self._due["lang"] + self.due_is_recurring = self._due["is_recurring"] + else: + self.due_date = None + self.due_timezone = None + self.due_string = None + self.due_lang = None + self.due_is_recurring = None + + ##@brief Check wether a duedate is set + # @return True if duedate is set + def hasDue(self): + return self._due != None + + ##@brief Check wether the item has passed its deadline + # @return True is overDue + def isOverDue(self): + if self.hasDue() and not self.checked: + return self.due_date.date() < date.today() + else: + return False + + ##@brief Check wether the item has has any notes attached to it + # @return True if notes are attached + def hasNotes(self): + return (len(self.notes)>0) + + ##@brief add a note to this item. + # @param note object that should be appended to this item + def addNote(self,note): + self.notes.append(note) + + ##@brief Check if this task is closed but does not have any notes + # @return True is closed without notes attached + def closedWithoutNotes(self): + return self.checked and not self.hasNotes() + + ##@brief Check if this item is almost due(today) but has not notes yet + # @return True if it has no notes but has be finished today + def almostDueWithoutNotes(self): + return self.dueOnDate() and not self.hasNotes() + + ##@brief Check if the item is due on specific day + # @param _date date(time) object for which day to check. Defaults to today. + # @return False if not due on specified date + def dueOnDate(self,_date = date.today()): + if self.hasDue(): + if isinstance(_date,datetime): + _date = date.date() + return self.due_date.date() == _date + else: + return False +##@class TodoNote +# @brief Simple implementation for note-objects +class TodoNote: + ##@brief constructor of class + # @param note dict with note-information from api + # @param api TodoistAPI-object + def __init__(self,note,api): + self.id = note["id"] + self.posted_uid = note["posted_uid"] + self.project_id = note["project_id"] + self.item_id = note["item_id"] + self.content = note["content"] + self.file_attachment = note["file_attachment"] + self.uids_to_notify = note["uids_to_notify"] + self.is_deleted = note["is_deleted"] + self.posted = note["posted"] + self.reactions = note["reactions"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ff6549f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,42 @@ +appdirs==1.4.3 +asn1crypto==1.2.0 +astroid==2.3.3 +certifi==2019.9.11 +cffi==1.13.1 +chardet==3.0.4 +configobj==5.0.6 +cryptography==2.8 +cupshelpers==1.0 +gestures==0.2.2 +idna==2.8 +isort==4.3.21 +lazy-object-proxy==1.4.3 +mccabe==0.6.1 +meson==0.52.0 +mutagen==1.41.0 +mygpoclient==1.8 +onboard==1.4.1 +packaging==19.2 +pyasn1==0.4.7 +pycairo==1.18.2 +pycparser==2.19 +pycups==1.9.74 +PyGObject==3.34.0 +pylint==2.4.4 +pyOpenSSL==19.0.0 +pyparsing==2.4.2 +pyserial==3.4 +pysmbc==1.0.15.8 +PySocks==1.7.1 +pytz==2019.3 +PyYAML==5.1.2 +requests==2.22.0 +ruamel.yaml==0.16.5 +ruamel.yaml.clib==0.2.0 +six==1.13.0 +todoist-python==8.1.1 +typed-ast==1.4.0 +urllib3==1.25.7 +wrapt==1.11.2 +youtube-dl==2019.11.5 +zope.interface==4.7.1 diff --git a/taskcheck.py b/taskcheck.py new file mode 100644 index 0000000..a1c6a21 --- /dev/null +++ b/taskcheck.py @@ -0,0 +1,19 @@ +import sys + +from myTodoist import TodoProject,TodoItem,TodoNote,TodoItemList + + + +def main(): +# if(len(sys.argv)!=2): +# return + todoist = TodoProject("Master Assignment") +# getattr(todoist, sys.argv[1])() + todoist.checktaskstoday(3) + todoist.checkTasksWithoutNotes() + todoist.checkTasksOverDue() + + + +#print(todoist.getproject()) +main()