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 import re ##@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 # Matches on YYYY-MM-DD, does not check if date is in range. Ex: 2019-11-08 if (re.match(r'^\d{4}-\d{2}-\d{2}$',datestr, 0) != None): return datetime.strptime(datestr,"%Y-%m-%d") # Matches on YYYY-MM-DDTHH:MM:SSZ, does not check if date is in range. Ex: 2019-11-08T15:45:02Z if (re.match(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$',datestr, 0) != None): return datetime.strptime(datestr,"%Y-%m-%dT%H:%M:%SZ") # Matches on YYYY-MM-DDTHH:MM:SS, does not check if date is in range. Ex: 2019-11-08T15:45:02 if (re.match(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$',datestr, 0) != None): return datetime.strptime(datestr,"%Y-%m-%dT%H:%M:%S") else: return None 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 = fromisoformat(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"]