|
|
|
@@ -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"] |