Added Todoist integration
This commit is contained in:
parent
7ecb27463c
commit
29789461d7
10 changed files with 799 additions and 6 deletions
|
@ -229,6 +229,7 @@ autodoc_mock_imports = ['googlesamples.assistant.grpc.audio_helpers',
|
|||
'pyaudio',
|
||||
'avs',
|
||||
'PyOBEX',
|
||||
'todoist',
|
||||
]
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
|
|
|
@ -45,6 +45,7 @@ class MusicMopidyBackend(Backend):
|
|||
self._msg_id = 0
|
||||
self._ws = None
|
||||
self._latest_status = {}
|
||||
self._connected = False
|
||||
|
||||
try:
|
||||
self._latest_status = self._get_tracklist_status()
|
||||
|
@ -202,13 +203,18 @@ class MusicMopidyBackend(Backend):
|
|||
def _on_close(self):
|
||||
def hndl():
|
||||
self._ws = None
|
||||
self._connected = False
|
||||
self.logger.warning('Mopidy websocket connection closed')
|
||||
time.sleep(5)
|
||||
self._connect()
|
||||
|
||||
while not self._connected:
|
||||
self._connect()
|
||||
time.sleep(10)
|
||||
|
||||
return hndl
|
||||
|
||||
def _on_open(self):
|
||||
def hndl(ws):
|
||||
self._connected = True
|
||||
self.logger.info('Mopidy websocket connected')
|
||||
return hndl
|
||||
|
||||
|
|
148
platypush/backend/todoist.py
Normal file
148
platypush/backend/todoist.py
Normal file
|
@ -0,0 +1,148 @@
|
|||
import json
|
||||
import time
|
||||
|
||||
from platypush.backend import Backend
|
||||
from platypush.context import get_plugin
|
||||
from platypush.message.event.todoist import NewItemEvent, RemovedItemEvent, ModifiedItemEvent, CheckedItemEvent, \
|
||||
ItemContentChangeEvent, TodoistSyncRequiredEvent
|
||||
|
||||
from platypush.plugins.todoist import TodoistPlugin
|
||||
|
||||
|
||||
class TodoistBackend(Backend):
|
||||
"""
|
||||
This backend listens for events on a remote Todoist account.
|
||||
|
||||
Requires:
|
||||
|
||||
* **todoist-python** (``pip install todoist-python``)
|
||||
* **websocket-client** (``pip install websocket-client``)
|
||||
|
||||
Triggers:
|
||||
|
||||
* :class:`platypush.message.event.todoist.NewItemEvent` when a new item is created.
|
||||
* :class:`platypush.message.event.todoist.RemovedItemEvent` when an item is removed.
|
||||
* :class:`platypush.message.event.todoist.CheckedItemEvent` when an item is checked.
|
||||
* :class:`platypush.message.event.todoist.ItemContentChangeEvent` when the content of an item is changed.
|
||||
* :class:`platypush.message.event.todoist.ModifiedItemEvent` when an item is changed and the change
|
||||
doesn't fall into the categories above.
|
||||
* :class:`platypush.message.event.todoist.TodoistSyncRequiredEvent` when an update has occurred that doesn't
|
||||
fall into the categories above and a sync is required to get up-to-date.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, api_token: str = None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._plugin: TodoistPlugin = get_plugin('todoist')
|
||||
|
||||
if not api_token:
|
||||
assert self._plugin and self._plugin.api_token, 'No api_token specified either on Todoist backend or plugin'
|
||||
self.api_token = self._plugin.api_token
|
||||
else:
|
||||
self.api_token = api_token
|
||||
|
||||
self.url = self._plugin.get_user().output['websocket_url']
|
||||
self._ws = None
|
||||
self._connected = False
|
||||
self._todoist_initialized = False
|
||||
|
||||
self._items = {}
|
||||
self._event_handled = False
|
||||
|
||||
def _on_msg(self):
|
||||
# noinspection PyUnusedLocal
|
||||
def hndl(ws, msg):
|
||||
msg = json.loads(msg)
|
||||
if msg.get('type') == 'sync_needed':
|
||||
self._refresh_all()
|
||||
|
||||
return hndl
|
||||
|
||||
def _on_error(self):
|
||||
def hndl(error):
|
||||
self.logger.warning('Todoist websocket error: {}'.format(error))
|
||||
return hndl
|
||||
|
||||
def _on_close(self):
|
||||
def hndl():
|
||||
self._ws = None
|
||||
self.logger.warning('Todoist websocket connection closed')
|
||||
self._connected = False
|
||||
|
||||
while not self._connected:
|
||||
self._connect()
|
||||
time.sleep(10)
|
||||
|
||||
return hndl
|
||||
|
||||
def _on_open(self):
|
||||
# noinspection PyUnusedLocal
|
||||
def hndl(ws):
|
||||
self._connected = True
|
||||
self.logger.info('Todoist websocket connected')
|
||||
|
||||
if not self._todoist_initialized:
|
||||
self._refresh_all()
|
||||
self._todoist_initialized = True
|
||||
|
||||
return hndl
|
||||
|
||||
def _connect(self):
|
||||
import websocket
|
||||
|
||||
if not self._ws:
|
||||
self._ws = websocket.WebSocketApp(self.url,
|
||||
on_message=self._on_msg(),
|
||||
on_error=self._on_error(),
|
||||
on_close=self._on_close())
|
||||
|
||||
def _refresh_items(self):
|
||||
new_items = {
|
||||
i['id']: i
|
||||
for i in self._plugin.get_items().output
|
||||
}
|
||||
|
||||
if self._todoist_initialized:
|
||||
for id, item in new_items.items():
|
||||
if id not in self._items.keys():
|
||||
self._event_handled = True
|
||||
self.bus.post(NewItemEvent(item))
|
||||
|
||||
for id, item in self._items.items():
|
||||
if id not in new_items.keys():
|
||||
self._event_handled = True
|
||||
self.bus.post(RemovedItemEvent(item))
|
||||
elif new_items[id] != item:
|
||||
if new_items[id]['checked'] != item['checked']:
|
||||
self._event_handled = True
|
||||
self.bus.post(CheckedItemEvent(new_items[id]))
|
||||
elif new_items[id]['is_deleted'] != item['is_deleted']:
|
||||
self._event_handled = True
|
||||
self.bus.post(RemovedItemEvent(new_items[id]))
|
||||
elif new_items[id]['content'] != item['content']:
|
||||
self._event_handled = True
|
||||
self.bus.post(ItemContentChangeEvent(new_items[id]))
|
||||
else:
|
||||
self._event_handled = True
|
||||
self.bus.post(ModifiedItemEvent(new_items[id]))
|
||||
|
||||
self._items = new_items
|
||||
|
||||
def _refresh_all(self):
|
||||
self._event_handled = False
|
||||
self._plugin.sync()
|
||||
self._refresh_items()
|
||||
|
||||
if not self._event_handled:
|
||||
self.bus.post(TodoistSyncRequiredEvent())
|
||||
|
||||
def run(self):
|
||||
super().run()
|
||||
self.logger.info('Started Todoist backend')
|
||||
|
||||
self._connect()
|
||||
self._ws.on_open = self._on_open()
|
||||
self._ws.run_forever()
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
|
@ -46,6 +46,7 @@ class Message(object):
|
|||
if isinstance(msg, bytes) or isinstance(msg, bytearray):
|
||||
msg = msg.decode('utf-8')
|
||||
if isinstance(msg, str):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
msg = json.loads(msg.strip())
|
||||
except:
|
||||
|
@ -53,7 +54,7 @@ class Message(object):
|
|||
|
||||
assert isinstance(msg, dict)
|
||||
|
||||
if not '_timestamp' in msg:
|
||||
if '_timestamp' not in msg:
|
||||
msg['_timestamp'] = time.time()
|
||||
|
||||
return msg
|
||||
|
@ -67,10 +68,66 @@ class Message(object):
|
|||
"""
|
||||
from platypush.utils import get_message_class_by_type
|
||||
|
||||
|
||||
msg = cls.parse(msg)
|
||||
msgtype = get_message_class_by_type(msg['type'])
|
||||
if msgtype != cls: return msgtype.build(msg)
|
||||
if msgtype != cls:
|
||||
return msgtype.build(msg)
|
||||
|
||||
|
||||
class Mapping(dict):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
self.__dict__[key] = item
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.__dict__[key]
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.__dict__)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__dict__)
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self.__dict__[key]
|
||||
|
||||
def clear(self):
|
||||
return self.__dict__.clear()
|
||||
|
||||
def copy(self):
|
||||
return self.__dict__.copy()
|
||||
|
||||
def has_key(self, k):
|
||||
return k in self.__dict__
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
return self.__dict__.update(*args, **kwargs)
|
||||
|
||||
def keys(self):
|
||||
return self.__dict__.keys()
|
||||
|
||||
def values(self):
|
||||
return self.__dict__.values()
|
||||
|
||||
def items(self):
|
||||
return self.__dict__.items()
|
||||
|
||||
def pop(self, *args):
|
||||
return self.__dict__.pop(*args)
|
||||
|
||||
def __cmp__(self, dict_):
|
||||
return self.__cmp__(dict_)
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self.__dict__
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.__dict__)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__dict__)
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
||||
|
|
59
platypush/message/event/todoist.py
Normal file
59
platypush/message/event/todoist.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
from platypush.message.event import Event
|
||||
|
||||
|
||||
class TodoistEvent(Event):
|
||||
pass
|
||||
|
||||
|
||||
class NewItemEvent(TodoistEvent):
|
||||
"""
|
||||
Event triggered when a new item is created.
|
||||
"""
|
||||
|
||||
def __init__(self, item, *args, **kwargs):
|
||||
super().__init__(*args, item=item, **kwargs)
|
||||
|
||||
|
||||
class RemovedItemEvent(TodoistEvent):
|
||||
"""
|
||||
Event triggered when a new item is removed.
|
||||
"""
|
||||
|
||||
def __init__(self, item, *args, **kwargs):
|
||||
super().__init__(*args, item=item, **kwargs)
|
||||
|
||||
|
||||
class ModifiedItemEvent(TodoistEvent):
|
||||
"""
|
||||
Event triggered when an item is changed.
|
||||
"""
|
||||
|
||||
def __init__(self, item, *args, **kwargs):
|
||||
super().__init__(*args, item=item, **kwargs)
|
||||
|
||||
|
||||
class CheckedItemEvent(ModifiedItemEvent):
|
||||
"""
|
||||
Event triggered when an item is checked.
|
||||
"""
|
||||
|
||||
def __init__(self, item, *args, **kwargs):
|
||||
super().__init__(*args, item=item, **kwargs)
|
||||
|
||||
|
||||
class ItemContentChangeEvent(ModifiedItemEvent):
|
||||
"""
|
||||
Event triggered when the content of an item changes.
|
||||
"""
|
||||
|
||||
def __init__(self, item, *args, **kwargs):
|
||||
super().__init__(*args, item=item, **kwargs)
|
||||
|
||||
|
||||
class TodoistSyncRequiredEvent(TodoistEvent):
|
||||
"""
|
||||
Event triggered when an event occurs that doesn't fall into the categories above.
|
||||
"""
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
|
@ -84,4 +84,5 @@ class Response(Message):
|
|||
|
||||
return json.dumps(response_dict)
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
326
platypush/message/response/todoist.py
Normal file
326
platypush/message/response/todoist.py
Normal file
|
@ -0,0 +1,326 @@
|
|||
import todoist.models
|
||||
|
||||
import datetime
|
||||
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from platypush.message import Mapping
|
||||
from platypush.message.response import Response
|
||||
|
||||
|
||||
class TodoistResponse(Response):
|
||||
pass
|
||||
|
||||
|
||||
class TodoistUserResponse(TodoistResponse):
|
||||
def __init__(self,
|
||||
auto_reminder: Optional[int] = None,
|
||||
avatar_big: Optional[str] = None,
|
||||
avatar_medium: Optional[str] = None,
|
||||
avatar_s640: Optional[str] = None,
|
||||
avatar_small: Optional[str] = None,
|
||||
business_account_id: Optional[int] = None,
|
||||
daily_goal: Optional[int] = None,
|
||||
date_format: Optional[str] = None,
|
||||
dateist_inline_disabled: Optional[bool] = None,
|
||||
dateist_lang: Optional[str] = None,
|
||||
days_off: Optional[List[int]] = None,
|
||||
default_reminder: Optional[str] = None,
|
||||
email: Optional[str] = None,
|
||||
features: Optional[Dict[str, Any]] = None,
|
||||
full_name: Optional[str] = None,
|
||||
id: Optional[int] = None,
|
||||
image_id: Optional[str] = None,
|
||||
inbox_project: Optional[int] = None,
|
||||
is_biz_admin: Optional[bool] = None,
|
||||
is_premium: Optional[bool] = None,
|
||||
join_date: Optional[datetime.datetime] = None,
|
||||
karma: Optional[float] = None,
|
||||
karma_trend: Optional[str] = None,
|
||||
lang: Optional[str] = None,
|
||||
legacy_inbox_project: Optional[int] = None,
|
||||
mobile_host: Optional[str] = None,
|
||||
mobile_number: Optional[str] = None,
|
||||
next_week: Optional[int] = None,
|
||||
premium_until: Optional[datetime.datetime] = None,
|
||||
share_limit: Optional[int] = None,
|
||||
sort_order: Optional[int] = None,
|
||||
start_day: Optional[int] = None,
|
||||
start_page: Optional[str] = None,
|
||||
theme: Optional[int] = None,
|
||||
time_format: Optional[int] = None,
|
||||
token: Optional[str] = None,
|
||||
tz_info: Optional[Dict[str, Any]] = None,
|
||||
unique_prefix: Optional[int] = None,
|
||||
websocket_url: Optional[str] = None,
|
||||
weekly_goal: Optional[int] = None,
|
||||
**kwargs):
|
||||
response = {
|
||||
'auto_reminder': auto_reminder,
|
||||
'avatar_big': avatar_big,
|
||||
'avatar_medium': avatar_medium,
|
||||
'avatar_s640': avatar_s640,
|
||||
'avatar_small': avatar_small,
|
||||
'business_account_id': business_account_id,
|
||||
'daily_goal': daily_goal,
|
||||
'date_format': date_format,
|
||||
'dateist_inline_disabled': dateist_inline_disabled,
|
||||
'dateist_lang': dateist_lang,
|
||||
'days_off': days_off,
|
||||
'default_reminder': default_reminder,
|
||||
'email': email,
|
||||
'features': features,
|
||||
'full_name': full_name,
|
||||
'id': id,
|
||||
'image_id': image_id,
|
||||
'inbox_project': inbox_project,
|
||||
'is_biz_admin': is_biz_admin,
|
||||
'is_premium': is_premium,
|
||||
'join_date': join_date,
|
||||
'karma': karma,
|
||||
'karma_trend': karma_trend,
|
||||
'lang': lang,
|
||||
'legacy_inbox_project': legacy_inbox_project,
|
||||
'mobile_host': mobile_host,
|
||||
'mobile_number': mobile_number,
|
||||
'next_week': next_week,
|
||||
'premium_until': premium_until,
|
||||
'share_limit': share_limit,
|
||||
'sort_order': sort_order,
|
||||
'start_day': start_day,
|
||||
'start_page': start_page,
|
||||
'theme': theme,
|
||||
'time_format': time_format,
|
||||
'token': token,
|
||||
'tz_info': tz_info,
|
||||
'unique_prefix': unique_prefix,
|
||||
'websocket_url': websocket_url,
|
||||
'weekly_goal': weekly_goal,
|
||||
}
|
||||
|
||||
super().__init__(output=response, **kwargs)
|
||||
|
||||
|
||||
class TodoistProject(Mapping):
|
||||
def __init__(self,
|
||||
child_order: int,
|
||||
collapsed: int,
|
||||
color: int,
|
||||
has_more_notes: bool,
|
||||
id: int,
|
||||
is_archived: bool,
|
||||
is_deleted: bool,
|
||||
is_favorite: bool,
|
||||
name: str,
|
||||
shared: bool,
|
||||
inbox_project: Optional[bool] = None,
|
||||
legacy_id: Optional[int] = None,
|
||||
parent_id: Optional[int] = None,
|
||||
*args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.child_order = child_order
|
||||
self.collapsed = collapsed
|
||||
self.color = color
|
||||
self.has_more_notes = has_more_notes
|
||||
self.id = id
|
||||
self.inbox_project = inbox_project
|
||||
self.is_archived = bool(is_archived)
|
||||
self.is_deleted = bool(is_deleted)
|
||||
self.is_favorite = bool(is_favorite)
|
||||
self.name = name
|
||||
self.shared = shared
|
||||
self.legacy_id = legacy_id
|
||||
self.parent_id = parent_id
|
||||
|
||||
|
||||
class TodoistProjectsResponse(TodoistResponse):
|
||||
def __init__(self, projects: List[TodoistProject], **kwargs):
|
||||
self.projects = [TodoistProject(**(p.data if isinstance(p, todoist.models.Project) else p)) for p in projects]
|
||||
super().__init__(output=[p.__dict__ for p in self.projects], **kwargs)
|
||||
|
||||
|
||||
class TodoistItem(Mapping):
|
||||
def __init__(self,
|
||||
content: str,
|
||||
id: int,
|
||||
checked: bool,
|
||||
priority: int,
|
||||
child_order: int,
|
||||
collapsed: bool,
|
||||
day_order: int,
|
||||
date_added: datetime.datetime,
|
||||
in_history: bool,
|
||||
is_deleted: bool,
|
||||
user_id: int,
|
||||
has_more_notes: bool = False,
|
||||
project_id: Optional[int] = None,
|
||||
parent_id: Optional[int] = None,
|
||||
responsible_uid: Optional[int] = None,
|
||||
date_completed: Optional[datetime.datetime] = None,
|
||||
assigned_by_uid: Optional[int] = None,
|
||||
due: Optional[Dict[str, Any]] = None,
|
||||
labels: Optional[List[str]] = None,
|
||||
legacy_project_id: Optional[int] = None,
|
||||
section_id: Optional[int] = None,
|
||||
sync_id: Optional[int] = None,
|
||||
*args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.content = content
|
||||
self.id = id
|
||||
self.checked = bool(checked)
|
||||
self.priority = priority
|
||||
self.child_order = child_order
|
||||
self.collapsed = bool(collapsed)
|
||||
self.day_order = day_order
|
||||
self.date_added = date_added
|
||||
self.has_more_notes = bool(has_more_notes)
|
||||
self.in_history = bool(in_history)
|
||||
self.is_deleted = bool(is_deleted)
|
||||
self.user_id = user_id
|
||||
self.project_id = project_id
|
||||
self.parent_id = parent_id
|
||||
self.responsible_uid = responsible_uid
|
||||
self.date_completed = date_completed
|
||||
self.assigned_by_uid = assigned_by_uid
|
||||
self.due = due
|
||||
self.labels = labels
|
||||
self.legacy_project_id = legacy_project_id
|
||||
self.section_id = section_id
|
||||
self.sync_id = sync_id
|
||||
|
||||
|
||||
class TodoistItemsResponse(TodoistResponse):
|
||||
def __init__(self, items: List[TodoistItem], **kwargs):
|
||||
self.items = [TodoistItem(**(i.data if isinstance(i, todoist.models.Item) else i.__dict__)) for i in items]
|
||||
super().__init__(output=[i.__dict__ for i in self.items], **kwargs)
|
||||
|
||||
|
||||
class TodoistFilter(Mapping):
|
||||
def __init__(self,
|
||||
color: [int],
|
||||
id: [int],
|
||||
is_deleted: [bool],
|
||||
is_favorite: [bool],
|
||||
item_order: [int],
|
||||
name: [str],
|
||||
query: [str],
|
||||
legacy_id: Optional[int] = None,
|
||||
*args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.color = color
|
||||
self.id = id
|
||||
self.is_deleted = is_deleted
|
||||
self.is_favorite = is_favorite
|
||||
self.item_order = item_order
|
||||
self.name = name
|
||||
self.query = query
|
||||
self.legacy_id = legacy_id
|
||||
|
||||
|
||||
class TodoistFiltersResponse(TodoistResponse):
|
||||
def __init__(self, filters: List[TodoistFilter], **kwargs):
|
||||
self.filters = [TodoistFilter(**(f.data if isinstance(f, todoist.models.Filter) else f.__dict__))
|
||||
for f in filters]
|
||||
|
||||
super().__init__(output=[f.__dict__ for f in self.filters], **kwargs)
|
||||
|
||||
|
||||
class TodoistLiveNotification(Mapping):
|
||||
def __init__(self,
|
||||
id: [int],
|
||||
is_deleted: [bool],
|
||||
created: [str],
|
||||
is_unread: [bool],
|
||||
notification_key: [str],
|
||||
notification_type: [str],
|
||||
completed_last_month: Optional[int] = None,
|
||||
karma_level: Optional[int] = None,
|
||||
promo_img: Optional[str] = None,
|
||||
completed_tasks: Optional[int] = None,
|
||||
*args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.id = id
|
||||
self.is_deleted = bool(is_deleted)
|
||||
self.completed_last_month = completed_last_month
|
||||
self.completed_tasks = completed_tasks
|
||||
self.created = created
|
||||
self.is_unread = bool(is_unread)
|
||||
self.karma_level = karma_level
|
||||
self.notification_key = notification_key
|
||||
self.notification_type = notification_type
|
||||
self.promo_img = promo_img
|
||||
|
||||
|
||||
class TodoistLiveNotificationsResponse(TodoistResponse):
|
||||
def __init__(self, notifications: List[TodoistLiveNotification], **kwargs):
|
||||
self.notifications = [TodoistLiveNotification(**(n.data if isinstance(n, todoist.models.LiveNotification)
|
||||
else n.__dict__)) for n in notifications]
|
||||
|
||||
super().__init__(output=[n.__dict__ for n in self.notifications], **kwargs)
|
||||
|
||||
|
||||
class TodoistCollaborator(Mapping):
|
||||
def __init__(self, data: Dict[str, Any], *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
for k, v in data.items():
|
||||
self.__setattr__(k, v)
|
||||
|
||||
|
||||
class TodoistCollaboratorsResponse(TodoistResponse):
|
||||
def __init__(self, collaborators: List[TodoistCollaborator], **kwargs):
|
||||
self.collaborators = [TodoistCollaborator(c.data if isinstance(c, todoist.models.Collaborator) else c.__dict__)
|
||||
for c in collaborators]
|
||||
|
||||
super().__init__(output=[c.__dict__ for c in self.collaborators], **kwargs)
|
||||
|
||||
|
||||
class TodoistNote(Mapping):
|
||||
def __init__(self, data: Dict[str, Any], *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
for k, v in data.items():
|
||||
self.__setattr__(k, v)
|
||||
|
||||
|
||||
class TodoistNotesResponse(TodoistResponse):
|
||||
def __init__(self, notes: List[TodoistCollaborator], **kwargs):
|
||||
self.notes = [TodoistCollaborator(n.data if isinstance(n, todoist.models.Note) else n.__dict__)
|
||||
for n in notes]
|
||||
|
||||
super().__init__(output=[n.__dict__ for n in self.notes], **kwargs)
|
||||
|
||||
|
||||
class TodoistProjectNote(Mapping):
|
||||
def __init__(self, data: Dict[str, Any], *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
for k, v in data.items():
|
||||
self.__setattr__(k, v)
|
||||
|
||||
|
||||
class TodoistProjectNotesResponse(TodoistResponse):
|
||||
def __init__(self, notes: List[TodoistCollaborator], **kwargs):
|
||||
self.notes = [TodoistCollaborator(n.data if isinstance(n, todoist.models.ProjectNote) else n.__dict__)
|
||||
for n in notes]
|
||||
|
||||
super().__init__(output=[n.__dict__ for n in self.notes], **kwargs)
|
||||
|
||||
|
||||
class TodoistReminder(Mapping):
|
||||
def __init__(self, data: Dict[str, Any], *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
for k, v in data.items():
|
||||
self.__setattr__(k, v)
|
||||
|
||||
|
||||
class TodoistRemindersResponse(TodoistResponse):
|
||||
def __init__(self, reminders: List[TodoistReminder], **kwargs):
|
||||
self.reminders = [TodoistReminder(n.data if isinstance(n, todoist.models.Reminder) else n.__dict__)
|
||||
for n in reminders]
|
||||
|
||||
super().__init__(output=[r.__dict__ for r in self.reminders], **kwargs)
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
190
platypush/plugins/todoist.py
Normal file
190
platypush/plugins/todoist.py
Normal file
|
@ -0,0 +1,190 @@
|
|||
import time
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import todoist
|
||||
import todoist.managers.items
|
||||
|
||||
from platypush.plugins import Plugin, action
|
||||
from platypush.message.response.todoist import TodoistUserResponse, TodoistProjectsResponse, TodoistItemsResponse, \
|
||||
TodoistFiltersResponse, TodoistLiveNotificationsResponse, TodoistCollaboratorsResponse, TodoistNotesResponse, \
|
||||
TodoistProjectNotesResponse
|
||||
|
||||
|
||||
class TodoistPlugin(Plugin):
|
||||
"""
|
||||
Todoist integration.
|
||||
|
||||
Requires:
|
||||
|
||||
* **todoist-python** (``pip install todoist-python``)
|
||||
|
||||
You'll also need a Todoist token. You can get it `here <https://todoist.com/prefs/integrations>`.
|
||||
"""
|
||||
|
||||
_sync_timeout = 60.0
|
||||
|
||||
def __init__(self, api_token: str, **kwargs):
|
||||
"""
|
||||
:param api_token: Todoist API token. You can get it `here <https://todoist.com/prefs/integrations>`.
|
||||
"""
|
||||
|
||||
super().__init__(**kwargs)
|
||||
self.api_token = api_token
|
||||
self._api = None
|
||||
self._last_sync_time = None
|
||||
|
||||
def _get_api(self) -> todoist.TodoistAPI:
|
||||
if not self._api:
|
||||
self._api = todoist.TodoistAPI(self.api_token)
|
||||
|
||||
if not self._last_sync_time or time.time() - self._last_sync_time > self._sync_timeout:
|
||||
self._api.sync()
|
||||
|
||||
return self._api
|
||||
|
||||
@action
|
||||
def get_user(self) -> TodoistUserResponse:
|
||||
"""
|
||||
Get logged user info.
|
||||
"""
|
||||
api = self._get_api()
|
||||
return TodoistUserResponse(**api.state['user'])
|
||||
|
||||
@action
|
||||
def get_projects(self) -> TodoistProjectsResponse:
|
||||
"""
|
||||
Get list of Todoist projects.
|
||||
"""
|
||||
api = self._get_api()
|
||||
return TodoistProjectsResponse(api.state['projects'])
|
||||
|
||||
@action
|
||||
def get_items(self) -> TodoistItemsResponse:
|
||||
"""
|
||||
Get list of Todoist projects.
|
||||
"""
|
||||
api = self._get_api()
|
||||
return TodoistItemsResponse(api.state['items'])
|
||||
|
||||
@action
|
||||
def get_filters(self) -> TodoistFiltersResponse:
|
||||
"""
|
||||
Get list of Todoist filters.
|
||||
"""
|
||||
api = self._get_api()
|
||||
return TodoistFiltersResponse(api.state['filters'])
|
||||
|
||||
@action
|
||||
def get_live_notifications(self) -> TodoistLiveNotificationsResponse:
|
||||
"""
|
||||
Get list of Todoist live notifications.
|
||||
"""
|
||||
api = self._get_api()
|
||||
return TodoistLiveNotificationsResponse(api.state['live_notifications'])
|
||||
|
||||
@action
|
||||
def get_collaborators(self) -> TodoistCollaboratorsResponse:
|
||||
"""
|
||||
Get list of collaborators.
|
||||
"""
|
||||
api = self._get_api()
|
||||
return TodoistCollaboratorsResponse(api.state['collaborators'])
|
||||
|
||||
@action
|
||||
def get_notes(self) -> TodoistNotesResponse:
|
||||
"""
|
||||
Get list of Todoist notes.
|
||||
"""
|
||||
api = self._get_api()
|
||||
return TodoistNotesResponse(api.state['notes'])
|
||||
|
||||
@action
|
||||
def get_project_notes(self) -> TodoistProjectNotesResponse:
|
||||
"""
|
||||
Get list of Todoist project notes.
|
||||
"""
|
||||
api = self._get_api()
|
||||
return TodoistProjectNotesResponse(api.state['project_notes'])
|
||||
|
||||
@action
|
||||
def add_item(self, content: str, project_id: Optional[int] = None, **kwargs):
|
||||
"""
|
||||
Add a new item.
|
||||
"""
|
||||
api = self._get_api()
|
||||
manager = todoist.managers.items.ItemsManager(api=api)
|
||||
item = manager.add(content, project_id=project_id, **kwargs)
|
||||
api.commit()
|
||||
return item.data
|
||||
|
||||
@action
|
||||
def delete_item(self, item_id: int):
|
||||
"""
|
||||
Delete an item by id.
|
||||
"""
|
||||
api = self._get_api()
|
||||
manager = todoist.managers.items.ItemsManager(api=api)
|
||||
manager.delete(item_id)
|
||||
api.commit()
|
||||
|
||||
@action
|
||||
def update_item(self, item_id: int, **kwargs):
|
||||
"""
|
||||
Update an item by id.
|
||||
"""
|
||||
api = self._get_api()
|
||||
manager = todoist.managers.items.ItemsManager(api=api)
|
||||
manager.update(item_id, **kwargs)
|
||||
api.commit()
|
||||
|
||||
@action
|
||||
def complete_item(self, item_id: int):
|
||||
"""
|
||||
Mark an item as done.
|
||||
"""
|
||||
api = self._get_api()
|
||||
manager = todoist.managers.items.ItemsManager(api=api)
|
||||
manager.complete(item_id)
|
||||
api.commit()
|
||||
|
||||
@action
|
||||
def uncomplete_item(self, item_id: int):
|
||||
"""
|
||||
Mark an item as not done.
|
||||
"""
|
||||
api = self._get_api()
|
||||
manager = todoist.managers.items.ItemsManager(api=api)
|
||||
manager.uncomplete(item_id)
|
||||
api.commit()
|
||||
|
||||
@action
|
||||
def archive(self, item_id: int):
|
||||
"""
|
||||
Archive an item by id.
|
||||
"""
|
||||
api = self._get_api()
|
||||
manager = todoist.managers.items.ItemsManager(api=api)
|
||||
manager.archive(item_id)
|
||||
api.commit()
|
||||
|
||||
@action
|
||||
def unarchive(self, item_id: int):
|
||||
"""
|
||||
Un-archive an item by id.
|
||||
"""
|
||||
api = self._get_api()
|
||||
manager = todoist.managers.items.ItemsManager(api=api)
|
||||
manager.unarchive(item_id)
|
||||
api.commit()
|
||||
|
||||
@action
|
||||
def sync(self):
|
||||
"""
|
||||
Sync/update info with the remote server.
|
||||
"""
|
||||
api = self._get_api()
|
||||
api.sync()
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
|
@ -188,3 +188,6 @@ croniter
|
|||
|
||||
# Support for Node-RED integration
|
||||
# nodered
|
||||
|
||||
# Support for Todoist integration
|
||||
# todoist
|
||||
|
|
2
setup.py
2
setup.py
|
@ -252,6 +252,8 @@ setup(
|
|||
'htmldoc': ['docutils'],
|
||||
# Support for Node-RED integration
|
||||
'nodered': ['pynodered'],
|
||||
# Support for Todoist integration
|
||||
'nodered': ['todoist-python'],
|
||||
},
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue