forked from platypush/platypush
[trello] (Almost) complete plugin rewrite.
- Merged `trello` plugin and backend into a single plugin. - Removed legacy `Response` objects, replaced with data classes and schemas. - Fixed the Websocket connection flow to reflect the new authentication protocol. Closes: #307
This commit is contained in:
parent
39b4483401
commit
c919cf0cd8
11 changed files with 1076 additions and 591 deletions
|
@ -39,7 +39,6 @@ Backends
|
||||||
platypush/backend/stt.picovoice.speech.rst
|
platypush/backend/stt.picovoice.speech.rst
|
||||||
platypush/backend/tcp.rst
|
platypush/backend/tcp.rst
|
||||||
platypush/backend/todoist.rst
|
platypush/backend/todoist.rst
|
||||||
platypush/backend/trello.rst
|
|
||||||
platypush/backend/weather.buienradar.rst
|
platypush/backend/weather.buienradar.rst
|
||||||
platypush/backend/weather.darksky.rst
|
platypush/backend/weather.darksky.rst
|
||||||
platypush/backend/weather.openweathermap.rst
|
platypush/backend/weather.openweathermap.rst
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
``trello``
|
|
||||||
============================
|
|
||||||
|
|
||||||
.. automodule:: platypush.backend.trello
|
|
||||||
:members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
``trello``
|
|
||||||
=====================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.message.response.trello
|
|
||||||
:members:
|
|
|
@ -19,5 +19,4 @@ Responses
|
||||||
platypush/responses/tensorflow.rst
|
platypush/responses/tensorflow.rst
|
||||||
platypush/responses/todoist.rst
|
platypush/responses/todoist.rst
|
||||||
platypush/responses/translate.rst
|
platypush/responses/translate.rst
|
||||||
platypush/responses/trello.rst
|
|
||||||
platypush/responses/weather.buienradar.rst
|
platypush/responses/weather.buienradar.rst
|
||||||
|
|
|
@ -1,217 +0,0 @@
|
||||||
import json
|
|
||||||
|
|
||||||
from threading import Event
|
|
||||||
from typing import List
|
|
||||||
from websocket import WebSocketApp
|
|
||||||
|
|
||||||
from platypush.backend import Backend
|
|
||||||
from platypush.context import get_plugin
|
|
||||||
from platypush.message.event.trello import (
|
|
||||||
MoveCardEvent,
|
|
||||||
NewCardEvent,
|
|
||||||
ArchivedCardEvent,
|
|
||||||
UnarchivedCardEvent,
|
|
||||||
)
|
|
||||||
|
|
||||||
from platypush.plugins.trello import TrelloPlugin
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloBackend(Backend):
|
|
||||||
"""
|
|
||||||
This backend listens for events on a remote Trello account through websocket interface..
|
|
||||||
Note that the Trello websocket interface
|
|
||||||
`is not officially supported <https://community.developer.atlassian.com/t/websocket-interface/34586/4>`_
|
|
||||||
and it requires a different token from the one you use for the Trello API (and for the Trello plugin).
|
|
||||||
To get the websocket token:
|
|
||||||
|
|
||||||
1. Open https://trello.com in your browser.
|
|
||||||
2. Open the developer tools (F12), go to the Network tab, select 'Websocket' or 'WS' in the filter bar
|
|
||||||
and refresh the page.
|
|
||||||
3. You should see an entry in the format ``wss://trello.com/1/Session/socket?token=<token>``.
|
|
||||||
4. Copy the ``<token>`` in the configuration of this backend.
|
|
||||||
|
|
||||||
Requires:
|
|
||||||
|
|
||||||
* The :class:`platypush.plugins.trello.TrelloPlugin` configured.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
_websocket_url_base = 'wss://trello.com/1/Session/socket?token={token}'
|
|
||||||
|
|
||||||
def __init__(self, boards: List[str], token: str, **kwargs):
|
|
||||||
"""
|
|
||||||
:param boards: List of boards to subscribe, by ID or name.
|
|
||||||
:param token: Trello web client API token.
|
|
||||||
"""
|
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self._plugin: TrelloPlugin = get_plugin('trello')
|
|
||||||
self.token = token
|
|
||||||
self._req_id = 0
|
|
||||||
self._boards_by_id = {}
|
|
||||||
self._boards_by_name = {}
|
|
||||||
|
|
||||||
for b in boards:
|
|
||||||
b = self._plugin.get_board(b).board
|
|
||||||
# noinspection PyUnresolvedReferences
|
|
||||||
self._boards_by_id[b.id] = b
|
|
||||||
# noinspection PyUnresolvedReferences
|
|
||||||
self._boards_by_name[b.name] = b
|
|
||||||
|
|
||||||
self.url = self._websocket_url_base.format(token=self.token)
|
|
||||||
self._connected = Event()
|
|
||||||
|
|
||||||
self._items = {}
|
|
||||||
self._event_handled = False
|
|
||||||
|
|
||||||
def _initialize_connection(self, ws: WebSocketApp):
|
|
||||||
for board_id in self._boards_by_id.keys():
|
|
||||||
self._send(
|
|
||||||
ws,
|
|
||||||
{
|
|
||||||
'type': 'subscribe',
|
|
||||||
'modelType': 'Board',
|
|
||||||
'idModel': board_id,
|
|
||||||
'tags': ['clientActions', 'updates'],
|
|
||||||
'invitationTokens': [],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
self.logger.info('Trello boards subscribed')
|
|
||||||
|
|
||||||
def _on_msg(self):
|
|
||||||
def hndl(*args):
|
|
||||||
if len(args) < 2:
|
|
||||||
self.logger.warning(
|
|
||||||
'Missing websocket argument - make sure that you are using '
|
|
||||||
'a version of websocket-client < 0.53.0 or >= 0.58.0'
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
ws, msg = args[:2]
|
|
||||||
if not msg:
|
|
||||||
# Reply back with an empty message when the server sends an empty message
|
|
||||||
ws.send('')
|
|
||||||
return
|
|
||||||
|
|
||||||
# noinspection PyBroadException
|
|
||||||
try:
|
|
||||||
msg = json.loads(msg)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.warning(
|
|
||||||
'Received invalid JSON message from Trello: {}: {}'.format(msg, e)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if 'error' in msg:
|
|
||||||
self.logger.warning('Trello error: {}'.format(msg['error']))
|
|
||||||
return
|
|
||||||
|
|
||||||
if msg.get('reqid') == 0:
|
|
||||||
self.logger.debug('Ping response received, subscribing boards')
|
|
||||||
self._initialize_connection(ws)
|
|
||||||
return
|
|
||||||
|
|
||||||
notify = msg.get('notify')
|
|
||||||
if not notify:
|
|
||||||
return
|
|
||||||
|
|
||||||
if notify['event'] != 'updateModels' or notify['typeName'] != 'Action':
|
|
||||||
return
|
|
||||||
|
|
||||||
for delta in notify['deltas']:
|
|
||||||
args = {
|
|
||||||
'card_id': delta['data']['card']['id'],
|
|
||||||
'card_name': delta['data']['card']['name'],
|
|
||||||
'list_id': (
|
|
||||||
delta['data'].get('list') or delta['data'].get('listAfter', {})
|
|
||||||
).get('id'),
|
|
||||||
'list_name': (
|
|
||||||
delta['data'].get('list') or delta['data'].get('listAfter', {})
|
|
||||||
).get('name'),
|
|
||||||
'board_id': delta['data']['board']['id'],
|
|
||||||
'board_name': delta['data']['board']['name'],
|
|
||||||
'closed': delta.get('closed'),
|
|
||||||
'member_id': delta['memberCreator']['id'],
|
|
||||||
'member_username': delta['memberCreator']['username'],
|
|
||||||
'member_fullname': delta['memberCreator']['fullName'],
|
|
||||||
'date': delta['date'],
|
|
||||||
}
|
|
||||||
|
|
||||||
if delta.get('type') == 'createCard':
|
|
||||||
self.bus.post(NewCardEvent(**args))
|
|
||||||
elif delta.get('type') == 'updateCard':
|
|
||||||
if 'listBefore' in delta['data']:
|
|
||||||
args.update(
|
|
||||||
{
|
|
||||||
'old_list_id': delta['data']['listBefore']['id'],
|
|
||||||
'old_list_name': delta['data']['listBefore']['name'],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
self.bus.post(MoveCardEvent(**args))
|
|
||||||
elif 'closed' in delta['data'].get('old', {}):
|
|
||||||
cls = (
|
|
||||||
UnarchivedCardEvent
|
|
||||||
if delta['data']['old']['closed']
|
|
||||||
else ArchivedCardEvent
|
|
||||||
)
|
|
||||||
self.bus.post(cls(**args))
|
|
||||||
|
|
||||||
return hndl
|
|
||||||
|
|
||||||
def _on_error(self):
|
|
||||||
def hndl(*args):
|
|
||||||
error = args[1] if len(args) > 1 else args[0]
|
|
||||||
self.logger.warning('Trello websocket error: {}'.format(error))
|
|
||||||
|
|
||||||
return hndl
|
|
||||||
|
|
||||||
def _on_close(self):
|
|
||||||
def hndl(*_):
|
|
||||||
self.logger.warning('Trello websocket connection closed')
|
|
||||||
self._connected.clear()
|
|
||||||
self._req_id = 0
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
self._connect()
|
|
||||||
self._connected.wait(timeout=20)
|
|
||||||
break
|
|
||||||
except TimeoutError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
return hndl
|
|
||||||
|
|
||||||
def _on_open(self):
|
|
||||||
def hndl(*args):
|
|
||||||
ws = args[0] if args else None
|
|
||||||
self._connected.set()
|
|
||||||
if ws:
|
|
||||||
self._send(ws, {'type': 'ping'})
|
|
||||||
self.logger.info('Trello websocket connected')
|
|
||||||
|
|
||||||
return hndl
|
|
||||||
|
|
||||||
def _send(self, ws: WebSocketApp, msg: dict):
|
|
||||||
msg['reqid'] = self._req_id
|
|
||||||
ws.send(json.dumps(msg))
|
|
||||||
self._req_id += 1
|
|
||||||
|
|
||||||
def _connect(self) -> WebSocketApp:
|
|
||||||
return WebSocketApp(
|
|
||||||
self.url,
|
|
||||||
on_open=self._on_open(),
|
|
||||||
on_message=self._on_msg(),
|
|
||||||
on_error=self._on_error(),
|
|
||||||
on_close=self._on_close(),
|
|
||||||
)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
super().run()
|
|
||||||
self.logger.info('Started Todoist backend')
|
|
||||||
ws = self._connect()
|
|
||||||
ws.run_forever()
|
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
|
|
@ -1,10 +0,0 @@
|
||||||
manifest:
|
|
||||||
events:
|
|
||||||
platypush.message.event.trello.ArchivedCardEvent: when a card is archived/closed.
|
|
||||||
platypush.message.event.trello.MoveCardEvent: when a card is moved.
|
|
||||||
platypush.message.event.trello.UnarchivedCardEvent: when a card is un-archived/opened.
|
|
||||||
platypush.message.event.trello.NewCardEvent: when a card is created.
|
|
||||||
install:
|
|
||||||
pip: []
|
|
||||||
package: platypush.backend.trello
|
|
||||||
type: backend
|
|
|
@ -1,186 +0,0 @@
|
||||||
import datetime
|
|
||||||
|
|
||||||
from typing import Optional, List, Union
|
|
||||||
|
|
||||||
from platypush.message import Mapping
|
|
||||||
from platypush.message.response import Response
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloResponse(Response):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloList(Mapping):
|
|
||||||
def __init__(self,
|
|
||||||
id: str,
|
|
||||||
name: str,
|
|
||||||
closed: bool,
|
|
||||||
subscribed: bool,
|
|
||||||
*args, **kwargs):
|
|
||||||
super().__init__(id=id, name=name, closed=closed, subscribed=subscribed, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloBoard(Mapping):
|
|
||||||
def __init__(self,
|
|
||||||
id: str,
|
|
||||||
name: str,
|
|
||||||
url: str,
|
|
||||||
closed: bool,
|
|
||||||
lists: Optional[List[TrelloList]] = None,
|
|
||||||
description: Optional[str] = None,
|
|
||||||
date_last_activity: Optional[datetime.datetime] = None,
|
|
||||||
*args, **kwargs):
|
|
||||||
super().__init__(id=id, name=name, url=url, closed=closed, description=description, lists=lists,
|
|
||||||
date_last_activity=date_last_activity, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloBoardResponse(TrelloResponse):
|
|
||||||
def __init__(self, board: TrelloBoard, **kwargs):
|
|
||||||
super().__init__(output=board, **kwargs)
|
|
||||||
self.board = board
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloBoardsResponse(TrelloResponse):
|
|
||||||
def __init__(self, boards: List[TrelloBoard], **kwargs):
|
|
||||||
super().__init__(output=boards, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloListsResponse(TrelloResponse):
|
|
||||||
def __init__(self, lists: List[TrelloList], **kwargs):
|
|
||||||
super().__init__(output=lists, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloLabel(Mapping):
|
|
||||||
def __init__(self,
|
|
||||||
id: str,
|
|
||||||
name: str,
|
|
||||||
color: Optional[str] = None,
|
|
||||||
*args, **kwargs):
|
|
||||||
super().__init__(id=id, name=name, color=color, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloUser(Mapping):
|
|
||||||
def __init__(self,
|
|
||||||
id: str,
|
|
||||||
username: str,
|
|
||||||
fullname: str,
|
|
||||||
initials: Optional[str] = None,
|
|
||||||
avatar_url: Optional[str] = None,
|
|
||||||
*args, **kwargs):
|
|
||||||
super().__init__(id=id, username=username, fullname=fullname, initials=initials,
|
|
||||||
avatar_url=avatar_url, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloComment(Mapping):
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
def __init__(self,
|
|
||||||
id: str,
|
|
||||||
text: str,
|
|
||||||
type: str,
|
|
||||||
creator: TrelloUser,
|
|
||||||
date: Union[str, datetime.datetime],
|
|
||||||
*args, **kwargs):
|
|
||||||
super().__init__(id=id, text=text, type=type, creator=creator, date=date, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloCard(Mapping):
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
def __init__(self,
|
|
||||||
id: str,
|
|
||||||
name: str,
|
|
||||||
url: str,
|
|
||||||
closed: bool,
|
|
||||||
board: TrelloBoard,
|
|
||||||
is_due_complete: bool,
|
|
||||||
list: Optional[TrelloList] = None,
|
|
||||||
comments: Optional[List[TrelloComment]] = None,
|
|
||||||
labels: Optional[List[TrelloLabel]] = None,
|
|
||||||
description: Optional[str] = None,
|
|
||||||
due_date: Optional[Union[datetime.datetime, str]] = None,
|
|
||||||
latest_card_move_date: Optional[Union[datetime.datetime, str]] = None,
|
|
||||||
date_last_activity: Optional[Union[datetime.datetime, str]] = None,
|
|
||||||
*args, **kwargs):
|
|
||||||
super().__init__(id=id, name=name, url=url, closed=closed, board=board, is_due_complete=is_due_complete,
|
|
||||||
description=description, date_last_activity=date_last_activity, due_date=due_date, list=list,
|
|
||||||
comments=comments, labels=labels, latest_card_move_date=latest_card_move_date, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloCardResponse(TrelloResponse):
|
|
||||||
def __init__(self, card: TrelloCard, **kwargs):
|
|
||||||
super().__init__(output=card, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloCardsResponse(TrelloResponse):
|
|
||||||
def __init__(self, cards: List[TrelloCard], **kwargs):
|
|
||||||
super().__init__(output=cards, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloPreview(Mapping):
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
def __init__(self,
|
|
||||||
id: str,
|
|
||||||
scaled: bool,
|
|
||||||
url: str,
|
|
||||||
bytes: int,
|
|
||||||
height: int,
|
|
||||||
width: int,
|
|
||||||
*args, **kwargs):
|
|
||||||
super().__init__(id=id, scaled=scaled, url=url, bytes=bytes, height=height, width=width, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloAttachment(Mapping):
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
def __init__(self,
|
|
||||||
id: str,
|
|
||||||
bytes: int,
|
|
||||||
date: str,
|
|
||||||
edge_color: str,
|
|
||||||
id_member: str,
|
|
||||||
is_upload: bool,
|
|
||||||
name: str,
|
|
||||||
previews: List[TrelloPreview],
|
|
||||||
url: str,
|
|
||||||
mime_type: Optional[str] = None,
|
|
||||||
*args, **kwargs):
|
|
||||||
super().__init__(id=id, bytes=bytes, date=date, edge_color=edge_color, id_member=id_member, is_upload=is_upload,
|
|
||||||
name=name, previews=previews, url=url, mime_type=mime_type, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloChecklistItem(Mapping):
|
|
||||||
def __init__(self,
|
|
||||||
id: str,
|
|
||||||
name: str,
|
|
||||||
checked: bool,
|
|
||||||
*args, **kwargs):
|
|
||||||
super().__init__(id=id, name=name, checked=checked, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloChecklist(Mapping):
|
|
||||||
def __init__(self,
|
|
||||||
id: str,
|
|
||||||
name: str,
|
|
||||||
checklist_items: List[TrelloChecklistItem],
|
|
||||||
*args, **kwargs):
|
|
||||||
super().__init__(id=id, name=name, checklist_items=checklist_items, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloMember(Mapping):
|
|
||||||
def __init__(self,
|
|
||||||
id: str,
|
|
||||||
full_name: str,
|
|
||||||
bio: Optional[str],
|
|
||||||
url: Optional[str],
|
|
||||||
username: Optional[str],
|
|
||||||
initials: Optional[str],
|
|
||||||
member_type: Optional[str] = None,
|
|
||||||
*args, **kwargs):
|
|
||||||
super().__init__(id=id, full_name=full_name, bio=bio, url=url, username=username, initials=initials,
|
|
||||||
member_type=member_type, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TrelloMembersResponse(Mapping):
|
|
||||||
def __init__(self, members: List[TrelloMember], **kwargs):
|
|
||||||
super().__init__(output=members, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
|
|
@ -1,20 +1,30 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
|
from threading import Event
|
||||||
from typing import Optional, Dict, List, Union
|
from typing import Optional, Dict, List, Union
|
||||||
|
|
||||||
# noinspection PyPackageRequirements
|
|
||||||
import trello
|
import trello
|
||||||
|
from trello.board import Board, List as List_ # type: ignore
|
||||||
|
from trello.exceptions import ResourceUnavailable # type: ignore
|
||||||
|
from websocket import WebSocketApp
|
||||||
|
|
||||||
# noinspection PyPackageRequirements
|
from platypush.context import get_bus
|
||||||
from trello.board import Board, List as List_
|
from platypush.message.event.trello import (
|
||||||
|
MoveCardEvent,
|
||||||
|
NewCardEvent,
|
||||||
|
ArchivedCardEvent,
|
||||||
|
UnarchivedCardEvent,
|
||||||
|
)
|
||||||
|
from platypush.plugins import RunnablePlugin, action
|
||||||
|
from platypush.schemas.trello import (
|
||||||
|
TrelloBoardSchema,
|
||||||
|
TrelloCardSchema,
|
||||||
|
TrelloListSchema,
|
||||||
|
TrelloMemberSchema,
|
||||||
|
)
|
||||||
|
|
||||||
# noinspection PyPackageRequirements
|
from ._model import (
|
||||||
from trello.exceptions import ResourceUnavailable
|
|
||||||
|
|
||||||
from platypush.message.response.trello import (
|
|
||||||
TrelloBoard,
|
TrelloBoard,
|
||||||
TrelloBoardsResponse,
|
|
||||||
TrelloCardsResponse,
|
|
||||||
TrelloCard,
|
TrelloCard,
|
||||||
TrelloAttachment,
|
TrelloAttachment,
|
||||||
TrelloPreview,
|
TrelloPreview,
|
||||||
|
@ -24,50 +34,84 @@ from platypush.message.response.trello import (
|
||||||
TrelloComment,
|
TrelloComment,
|
||||||
TrelloLabel,
|
TrelloLabel,
|
||||||
TrelloList,
|
TrelloList,
|
||||||
TrelloBoardResponse,
|
|
||||||
TrelloListsResponse,
|
|
||||||
TrelloMembersResponse,
|
|
||||||
TrelloMember,
|
TrelloMember,
|
||||||
TrelloCardResponse,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from platypush.plugins import Plugin, action
|
|
||||||
|
|
||||||
|
class TrelloPlugin(RunnablePlugin):
|
||||||
class TrelloPlugin(Plugin):
|
|
||||||
"""
|
"""
|
||||||
Trello integration.
|
Trello integration.
|
||||||
|
|
||||||
You'll need a Trello API key. You can get it `here <https://trello.com/app-key>`.
|
You'll need a Trello API key. You can get it `here <https://trello.com/app-key>`.
|
||||||
You'll also need an auth token if you want to view/change private resources. You can generate a permanent token
|
|
||||||
linked to your account on
|
You'll also need an auth token if you want to view/change private
|
||||||
https://trello.com/1/connect?key=<KEY>&name=platypush&response_type=token&expiration=never&scope=read,write
|
resources. You can generate a permanent token linked to your account on
|
||||||
|
``https://trello.com/1/connect?key=<KEY>&name=platypush&response_type=token&expiration=never&scope=read,write``.
|
||||||
|
|
||||||
|
Also, polling of events requires you to follow a separate procedure to
|
||||||
|
retrieve the Websocket tokens, since Trello uses a different (and
|
||||||
|
undocumented) authorization mechanism:
|
||||||
|
|
||||||
|
1. Open https://trello.com in your browser.
|
||||||
|
2. Open the developer tools (F12), go to the Network tab, select
|
||||||
|
'Websocket' or 'WS' in the filter bar and refresh the page.
|
||||||
|
3. You should see an entry in the format
|
||||||
|
``wss://trello.com/1/Session/socket?clientVersion=...&x-b3-traceid=...&x-b3-spanid=...``.
|
||||||
|
4. Copy the ``x-b3-traceid`` and ``x-b3-spanid`` values into the
|
||||||
|
configuration of this plugin.
|
||||||
|
5. Go to the Cookies tab
|
||||||
|
6. Copy the value of the ``cloud.session.token`` cookie.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_websocket_url_base = 'wss://trello.com/1/Session/socket?clientVersion=build-194674'
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
api_key: str,
|
api_key: str,
|
||||||
api_secret: Optional[str] = None,
|
api_secret: Optional[str] = None,
|
||||||
token: Optional[str] = None,
|
token: Optional[str] = None,
|
||||||
**kwargs
|
cloud_session_token: Optional[str] = None,
|
||||||
|
boards: Optional[List[str]] = None,
|
||||||
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
:param api_key: Trello API key. You can get it `here <https://trello.com/app-key>`.
|
:param api_key: Trello API key. You can get it `here <https://trello.com/app-key>`.
|
||||||
:param api_secret: Trello API secret. You can get it `here <https://trello.com/app-key>`.
|
:param api_secret: Trello API secret. You can get it `here <https://trello.com/app-key>`.
|
||||||
:param token: Trello token. It is required if you want to access or modify private resources. You can get
|
:param token: Trello token. It is required if you want to access or modify private resources. You can get
|
||||||
a permanent token on
|
a permanent token on
|
||||||
https://trello.com/1/connect?key=<KEY>&name=platypush&response_type=token&expiration=never&scope=read,write
|
``https://trello.com/1/connect?key=<KEY>&name=platypush&response_type=token&expiration=never&scope=read,write``.
|
||||||
|
:param cloud_session_token: Cloud session token. It is required
|
||||||
|
if you want to monitor your boards for changes. See
|
||||||
|
:class:`platypush.plugins.trello.TrelloPlugin` for the procedure to
|
||||||
|
retrieve it.
|
||||||
|
:param boards: List of boards to subscribe, by ID or name. If
|
||||||
|
specified, then the plugin will listen for changes only on these boards.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.api_secret = api_secret
|
self.api_secret = api_secret
|
||||||
self.token = token
|
self.token = token
|
||||||
|
self.cloud_session_token = cloud_session_token
|
||||||
self._client = None
|
self._client = None
|
||||||
|
self._req_id = 0
|
||||||
|
self._boards_by_id = {}
|
||||||
|
self._boards_by_name = {}
|
||||||
|
self._monitored_boards = boards
|
||||||
|
self.url = None
|
||||||
|
|
||||||
def _get_client(self) -> trello.TrelloClient:
|
if token:
|
||||||
|
self.url = self._websocket_url_base
|
||||||
|
|
||||||
|
self._connected = Event()
|
||||||
|
self._items = {}
|
||||||
|
self._event_handled = False
|
||||||
|
self._ws: Optional[WebSocketApp] = None
|
||||||
|
|
||||||
|
def _get_client(self) -> trello.TrelloClient: # type: ignore
|
||||||
if not self._client:
|
if not self._client:
|
||||||
self._client = trello.TrelloClient(
|
self._client = trello.TrelloClient( # type: ignore
|
||||||
api_key=self.api_key,
|
api_key=self.api_key,
|
||||||
api_secret=self.api_secret,
|
api_secret=self.api_secret,
|
||||||
token=self.token,
|
token=self.token,
|
||||||
|
@ -81,21 +125,25 @@ class TrelloPlugin(Plugin):
|
||||||
return client.get_board(board)
|
return client.get_board(board)
|
||||||
except ResourceUnavailable:
|
except ResourceUnavailable:
|
||||||
boards = [b for b in client.list_boards() if b.name == board]
|
boards = [b for b in client.list_boards() if b.name == board]
|
||||||
assert boards, 'No such board: {}'.format(board)
|
assert boards, f'No such board: {board}'
|
||||||
return boards[0]
|
return boards[0]
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
def _get_boards(
|
||||||
|
self, all: bool = False # pylint: disable=redefined-builtin
|
||||||
|
) -> List[Board]:
|
||||||
|
client = self._get_client()
|
||||||
|
return client.list_boards(board_filter='all' if all else 'open')
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def get_boards(self, all: bool = False) -> TrelloBoardsResponse:
|
def get_boards(self, all: bool = False): # pylint: disable=redefined-builtin
|
||||||
"""
|
"""
|
||||||
Get the list of boards.
|
Get the list of boards.
|
||||||
|
|
||||||
:param all: If True, return all the boards included those that have been closed/archived/deleted. Otherwise,
|
:param all: If True, return all the boards included those that have
|
||||||
only return open/active boards (default: False).
|
been closed/archived/deleted. Otherwise, only return open/active
|
||||||
|
boards (default: False).
|
||||||
"""
|
"""
|
||||||
client = self._get_client()
|
return TrelloBoardSchema().dump(
|
||||||
|
|
||||||
return TrelloBoardsResponse(
|
|
||||||
[
|
[
|
||||||
TrelloBoard(
|
TrelloBoard(
|
||||||
id=b.id,
|
id=b.id,
|
||||||
|
@ -105,27 +153,28 @@ class TrelloPlugin(Plugin):
|
||||||
date_last_activity=b.date_last_activity,
|
date_last_activity=b.date_last_activity,
|
||||||
closed=b.closed,
|
closed=b.closed,
|
||||||
)
|
)
|
||||||
for b in client.list_boards(board_filter='all' if all else 'open')
|
for b in self._get_boards(all=all)
|
||||||
]
|
],
|
||||||
|
many=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def get_board(self, board: str) -> TrelloBoardResponse:
|
def get_board(self, board: str):
|
||||||
"""
|
"""
|
||||||
Get the info about a board.
|
Get the info about a board.
|
||||||
|
|
||||||
:param board: Board ID or name
|
:param board: Board ID or name
|
||||||
"""
|
"""
|
||||||
|
|
||||||
board = self._get_board(board)
|
b = self._get_board(board)
|
||||||
return TrelloBoardResponse(
|
return TrelloBoardSchema().dump(
|
||||||
TrelloBoard(
|
TrelloBoard(
|
||||||
id=board.id,
|
id=b.id,
|
||||||
name=board.name,
|
name=b.name,
|
||||||
url=board.url,
|
url=b.url,
|
||||||
closed=board.closed,
|
closed=b.closed,
|
||||||
description=board.description,
|
description=b.description,
|
||||||
date_last_activity=board.date_last_activity,
|
date_last_activity=b.date_last_activity,
|
||||||
lists=[
|
lists=[
|
||||||
TrelloList(
|
TrelloList(
|
||||||
id=ll.id,
|
id=ll.id,
|
||||||
|
@ -133,7 +182,7 @@ class TrelloPlugin(Plugin):
|
||||||
closed=ll.closed,
|
closed=ll.closed,
|
||||||
subscribed=ll.subscribed,
|
subscribed=ll.subscribed,
|
||||||
)
|
)
|
||||||
for ll in board.list_lists()
|
for ll in b.list_lists()
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -145,8 +194,7 @@ class TrelloPlugin(Plugin):
|
||||||
|
|
||||||
:param board: Board ID or name
|
:param board: Board ID or name
|
||||||
"""
|
"""
|
||||||
board = self._get_board(board)
|
self._get_board(board).open()
|
||||||
board.open()
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def close_board(self, board: str):
|
def close_board(self, board: str):
|
||||||
|
@ -155,8 +203,7 @@ class TrelloPlugin(Plugin):
|
||||||
|
|
||||||
:param board: Board ID or name
|
:param board: Board ID or name
|
||||||
"""
|
"""
|
||||||
board = self._get_board(board)
|
self._get_board(board).close()
|
||||||
board.close()
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_board_name(self, board: str, name: str):
|
def set_board_name(self, board: str, name: str):
|
||||||
|
@ -166,8 +213,7 @@ class TrelloPlugin(Plugin):
|
||||||
:param board: Board ID or name.
|
:param board: Board ID or name.
|
||||||
:param name: New name.
|
:param name: New name.
|
||||||
"""
|
"""
|
||||||
board = self._get_board(board)
|
self._get_board(board).set_name(name)
|
||||||
board.set_name(name)
|
|
||||||
return self.get_board(name)
|
return self.get_board(name)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -178,8 +224,7 @@ class TrelloPlugin(Plugin):
|
||||||
:param board: Board ID or name.
|
:param board: Board ID or name.
|
||||||
:param description: New description.
|
:param description: New description.
|
||||||
"""
|
"""
|
||||||
board = self._get_board(board)
|
self._get_board(board).set_description(description)
|
||||||
board.set_description(description)
|
|
||||||
return self.get_board(description)
|
return self.get_board(description)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -191,8 +236,7 @@ class TrelloPlugin(Plugin):
|
||||||
:param name: Label name
|
:param name: Label name
|
||||||
:param color: Optional HTML color
|
:param color: Optional HTML color
|
||||||
"""
|
"""
|
||||||
board = self._get_board(board)
|
self._get_board(board).add_label(name=name, color=color)
|
||||||
board.add_label(name=name, color=color)
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def delete_label(self, board: str, label: str):
|
def delete_label(self, board: str, label: str):
|
||||||
|
@ -203,16 +247,15 @@ class TrelloPlugin(Plugin):
|
||||||
:param label: Label ID or name
|
:param label: Label ID or name
|
||||||
"""
|
"""
|
||||||
|
|
||||||
board = self._get_board(board)
|
b = self._get_board(board)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
board.delete_label(label)
|
b.delete_label(label)
|
||||||
except ResourceUnavailable:
|
except ResourceUnavailable:
|
||||||
labels = [ll for ll in board.get_labels() if ll.name == label]
|
label_ = next(iter(ll for ll in b.get_labels() if ll.name == label), None)
|
||||||
|
assert label_, f'No such label: {label}'
|
||||||
assert labels, 'No such label: {}'.format(label)
|
label = label_.id
|
||||||
label = labels[0].id
|
b.delete_label(label)
|
||||||
board.delete_label(label)
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def add_member(self, board: str, member_id: str, member_type: str = 'normal'):
|
def add_member(self, board: str, member_id: str, member_type: str = 'normal'):
|
||||||
|
@ -223,8 +266,7 @@ class TrelloPlugin(Plugin):
|
||||||
:param member_id: Member ID to add.
|
:param member_id: Member ID to add.
|
||||||
:param member_type: Member type - can be 'normal' or 'admin' (default: 'normal').
|
:param member_type: Member type - can be 'normal' or 'admin' (default: 'normal').
|
||||||
"""
|
"""
|
||||||
board = self._get_board(board)
|
self._get_board(board).add_member(member_id, member_type=member_type)
|
||||||
board.add_member(member_id, member_type=member_type)
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def remove_member(self, board: str, member_id: str):
|
def remove_member(self, board: str, member_id: str):
|
||||||
|
@ -234,20 +276,17 @@ class TrelloPlugin(Plugin):
|
||||||
:param board: Board ID or name.
|
:param board: Board ID or name.
|
||||||
:param member_id: Member ID to remove.
|
:param member_id: Member ID to remove.
|
||||||
"""
|
"""
|
||||||
board = self._get_board(board)
|
self._get_board(board).remove_member(member_id)
|
||||||
board.remove_member(member_id)
|
|
||||||
|
|
||||||
def _get_members(
|
def _get_members(self, board: str, only_admin: bool = False):
|
||||||
self, board: str, only_admin: bool = False
|
b = self._get_board(board)
|
||||||
) -> TrelloMembersResponse:
|
members = b.admin_members() if only_admin else b.get_members()
|
||||||
board = self._get_board(board)
|
|
||||||
members = board.admin_members() if only_admin else board.get_members()
|
|
||||||
|
|
||||||
return TrelloMembersResponse(
|
return TrelloMemberSchema().dump(
|
||||||
[
|
[
|
||||||
TrelloMember(
|
TrelloMember(
|
||||||
id=m.id,
|
id=m.id,
|
||||||
full_name=m.full_name,
|
fullname=m.full_name,
|
||||||
bio=m.bio,
|
bio=m.bio,
|
||||||
url=m.url,
|
url=m.url,
|
||||||
username=m.username,
|
username=m.username,
|
||||||
|
@ -255,11 +294,12 @@ class TrelloPlugin(Plugin):
|
||||||
member_type=getattr(m, 'member_type', None),
|
member_type=getattr(m, 'member_type', None),
|
||||||
)
|
)
|
||||||
for m in members
|
for m in members
|
||||||
]
|
],
|
||||||
|
many=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def get_members(self, board: str) -> TrelloMembersResponse:
|
def get_members(self, board: str):
|
||||||
"""
|
"""
|
||||||
Get the list of all the members of a board.
|
Get the list of all the members of a board.
|
||||||
:param board: Board ID or name.
|
:param board: Board ID or name.
|
||||||
|
@ -267,16 +307,17 @@ class TrelloPlugin(Plugin):
|
||||||
return self._get_members(board, only_admin=False)
|
return self._get_members(board, only_admin=False)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def get_admin_members(self, board: str) -> TrelloMembersResponse:
|
def get_admin_members(self, board: str):
|
||||||
"""
|
"""
|
||||||
Get the list of the admin members of a board.
|
Get the list of the admin members of a board.
|
||||||
:param board: Board ID or name.
|
:param board: Board ID or name.
|
||||||
"""
|
"""
|
||||||
return self._get_members(board, only_admin=True)
|
return self._get_members(board, only_admin=True)
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
@action
|
@action
|
||||||
def get_lists(self, board: str, all: bool = False) -> TrelloListsResponse:
|
def get_lists(
|
||||||
|
self, board: str, all: bool = False # pylint: disable=redefined-builtin
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Get the list of lists on a board.
|
Get the list of lists on a board.
|
||||||
|
|
||||||
|
@ -284,15 +325,14 @@ class TrelloPlugin(Plugin):
|
||||||
:param all: If True, return all the lists, included those that have been closed/archived/deleted. Otherwise,
|
:param all: If True, return all the lists, included those that have been closed/archived/deleted. Otherwise,
|
||||||
only return open/active lists (default: False).
|
only return open/active lists (default: False).
|
||||||
"""
|
"""
|
||||||
|
return TrelloListSchema().dump(
|
||||||
board = self._get_board(board)
|
|
||||||
return TrelloListsResponse(
|
|
||||||
[
|
[
|
||||||
TrelloList(
|
TrelloList(
|
||||||
id=ll.id, name=ll.name, closed=ll.closed, subscribed=ll.subscribed
|
id=ll.id, name=ll.name, closed=ll.closed, subscribed=ll.subscribed
|
||||||
)
|
)
|
||||||
for ll in board.list_lists('all' if all else 'open')
|
for ll in self._get_board(board).list_lists('all' if all else 'open')
|
||||||
]
|
],
|
||||||
|
many=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -304,24 +344,24 @@ class TrelloPlugin(Plugin):
|
||||||
:param name: List name
|
:param name: List name
|
||||||
:param pos: Optional position (default: last)
|
:param pos: Optional position (default: last)
|
||||||
"""
|
"""
|
||||||
|
self._get_board(board).add_list(name=name, pos=pos)
|
||||||
|
|
||||||
board = self._get_board(board)
|
def _get_list(
|
||||||
board.add_list(name=name, pos=pos)
|
self, board: str, list: str # pylint: disable=redefined-builtin
|
||||||
|
) -> List_:
|
||||||
# noinspection PyShadowingBuiltins
|
b = self._get_board(board)
|
||||||
def _get_list(self, board: str, list: str) -> List_:
|
|
||||||
board = self._get_board(board)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return board.get_list(list)
|
return b.get_list(list)
|
||||||
except ResourceUnavailable:
|
except ResourceUnavailable:
|
||||||
lists = [ll for ll in board.list_lists() if ll.name == list]
|
lists = [ll for ll in b.list_lists() if ll.name == list]
|
||||||
assert lists, 'No such list: {}'.format(list)
|
assert lists, f'No such list: {list}'
|
||||||
return lists[0]
|
return lists[0]
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
@action
|
@action
|
||||||
def set_list_name(self, board: str, list: str, name: str):
|
def set_list_name(
|
||||||
|
self, board: str, list: str, name: str # pylint: disable=redefined-builtin
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Change the name of a board list.
|
Change the name of a board list.
|
||||||
|
|
||||||
|
@ -329,44 +369,43 @@ class TrelloPlugin(Plugin):
|
||||||
:param list: List ID or name
|
:param list: List ID or name
|
||||||
:param name: New name
|
:param name: New name
|
||||||
"""
|
"""
|
||||||
list = self._get_list(board, list)
|
self._get_list(board, list).set_name(name)
|
||||||
list.set_name(name)
|
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
@action
|
@action
|
||||||
def list_subscribe(self, board: str, list: str):
|
def list_subscribe(
|
||||||
|
self, board: str, list: str # pylint: disable=redefined-builtin
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Subscribe to a list.
|
Subscribe to a list.
|
||||||
|
|
||||||
:param board: Board ID or name
|
:param board: Board ID or name
|
||||||
:param list: List ID or name
|
:param list: List ID or name
|
||||||
"""
|
"""
|
||||||
list = self._get_list(board, list)
|
self._get_list(board, list).subscribe()
|
||||||
list.subscribe()
|
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
@action
|
@action
|
||||||
def list_unsubscribe(self, board: str, list: str):
|
def list_unsubscribe(
|
||||||
|
self, board: str, list: str # pylint: disable=redefined-builtin
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Unsubscribe from a list.
|
Unsubscribe from a list.
|
||||||
|
|
||||||
:param board: Board ID or name
|
:param board: Board ID or name
|
||||||
:param list: List ID or name
|
:param list: List ID or name
|
||||||
"""
|
"""
|
||||||
list = self._get_list(board, list)
|
self._get_list(board, list).unsubscribe()
|
||||||
list.unsubscribe()
|
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
@action
|
@action
|
||||||
def archive_all_cards(self, board: str, list: str):
|
def archive_all_cards(
|
||||||
|
self, board: str, list: str # pylint: disable=redefined-builtin
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Archive all the cards on a list.
|
Archive all the cards on a list.
|
||||||
|
|
||||||
:param board: Board ID or name
|
:param board: Board ID or name
|
||||||
:param list: List ID or name
|
:param list: List ID or name
|
||||||
"""
|
"""
|
||||||
list = self._get_list(board, list)
|
self._get_list(board, list).archive_all_cards()
|
||||||
list.archive_all_cards()
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def move_all_cards(self, board: str, src: str, dest: str):
|
def move_all_cards(self, board: str, src: str, dest: str):
|
||||||
|
@ -377,37 +416,34 @@ class TrelloPlugin(Plugin):
|
||||||
:param src: Source list
|
:param src: Source list
|
||||||
:param dest: Target list
|
:param dest: Target list
|
||||||
"""
|
"""
|
||||||
src = self._get_list(board, src)
|
src_list = self._get_list(board, src)
|
||||||
dest = self._get_list(board, dest)
|
dest_list = self._get_list(board, dest)
|
||||||
src.move_all_cards(dest.id)
|
src_list.move_all_cards(dest_list.id)
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
@action
|
@action
|
||||||
def open_list(self, board: str, list: str):
|
def open_list(self, board: str, list: str): # pylint: disable=redefined-builtin
|
||||||
"""
|
"""
|
||||||
Open/un-archive a list.
|
Open/un-archive a list.
|
||||||
|
|
||||||
:param board: Board ID or name
|
:param board: Board ID or name
|
||||||
:param list: List ID or name
|
:param list: List ID or name
|
||||||
"""
|
"""
|
||||||
list = self._get_list(board, list)
|
self._get_list(board, list).open()
|
||||||
list.open()
|
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
@action
|
@action
|
||||||
def close_list(self, board: str, list: str):
|
def close_list(self, board: str, list: str): # pylint: disable=redefined-builtin
|
||||||
"""
|
"""
|
||||||
Close/archive a list.
|
Close/archive a list.
|
||||||
|
|
||||||
:param board: Board ID or name
|
:param board: Board ID or name
|
||||||
:param list: List ID or name
|
:param list: List ID or name
|
||||||
"""
|
"""
|
||||||
list = self._get_list(board, list)
|
self._get_list(board, list).close()
|
||||||
list.close()
|
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
@action
|
@action
|
||||||
def move_list(self, board: str, list: str, position: int):
|
def move_list(
|
||||||
|
self, board: str, list: str, position: int # pylint: disable=redefined-builtin
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Move a list to another position.
|
Move a list to another position.
|
||||||
|
|
||||||
|
@ -415,15 +451,13 @@ class TrelloPlugin(Plugin):
|
||||||
:param list: List ID or name
|
:param list: List ID or name
|
||||||
:param position: New position index
|
:param position: New position index
|
||||||
"""
|
"""
|
||||||
list = self._get_list(board, list)
|
self._get_list(board, list).move(position)
|
||||||
list.move(position)
|
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
@action
|
@action
|
||||||
def add_card(
|
def add_card(
|
||||||
self,
|
self,
|
||||||
board: str,
|
board: str,
|
||||||
list: str,
|
list: str, # pylint: disable=redefined-builtin
|
||||||
name: str,
|
name: str,
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
position: Optional[int] = None,
|
position: Optional[int] = None,
|
||||||
|
@ -431,7 +465,7 @@ class TrelloPlugin(Plugin):
|
||||||
due: Optional[Union[str, datetime.datetime]] = None,
|
due: Optional[Union[str, datetime.datetime]] = None,
|
||||||
source: Optional[str] = None,
|
source: Optional[str] = None,
|
||||||
assign: Optional[List[str]] = None,
|
assign: Optional[List[str]] = None,
|
||||||
) -> TrelloCardResponse:
|
):
|
||||||
"""
|
"""
|
||||||
Add a card to a list.
|
Add a card to a list.
|
||||||
|
|
||||||
|
@ -445,16 +479,16 @@ class TrelloPlugin(Plugin):
|
||||||
:param source: Card ID to clone from
|
:param source: Card ID to clone from
|
||||||
:param assign: List of assignee member IDs
|
:param assign: List of assignee member IDs
|
||||||
"""
|
"""
|
||||||
list = self._get_list(board, list)
|
list_ = self._get_list(board, list)
|
||||||
|
|
||||||
if labels:
|
if labels:
|
||||||
labels = [
|
labels = [
|
||||||
ll
|
ll
|
||||||
for ll in list.board.get_labels()
|
for ll in list_.board.get_labels()
|
||||||
if ll.id in labels or ll.name in labels
|
if ll.id in labels or ll.name in labels
|
||||||
]
|
]
|
||||||
|
|
||||||
card = list.add_card(
|
card = list_.add_card(
|
||||||
name=name,
|
name=name,
|
||||||
desc=description,
|
desc=description,
|
||||||
labels=labels,
|
labels=labels,
|
||||||
|
@ -464,19 +498,19 @@ class TrelloPlugin(Plugin):
|
||||||
assign=assign,
|
assign=assign,
|
||||||
)
|
)
|
||||||
|
|
||||||
return TrelloCardResponse(
|
return TrelloCardSchema().dump(
|
||||||
TrelloCard(
|
TrelloCard(
|
||||||
id=card.id,
|
id=card.id,
|
||||||
name=card.name,
|
name=card.name,
|
||||||
url=card.url,
|
url=card.url,
|
||||||
closed=card.closed,
|
closed=card.closed,
|
||||||
board=TrelloBoard(
|
board=TrelloBoard(
|
||||||
id=list.board.id,
|
id=list_.board.id,
|
||||||
name=list.board.name,
|
name=list_.board.name,
|
||||||
url=list.board.url,
|
url=list_.board.url,
|
||||||
closed=list.board.closed,
|
closed=list_.board.closed,
|
||||||
description=list.board.description,
|
description=list_.board.description,
|
||||||
date_last_activity=list.board.date_last_activity,
|
date_last_activity=list_.board.date_last_activity,
|
||||||
),
|
),
|
||||||
is_due_complete=card.is_due_complete,
|
is_due_complete=card.is_due_complete,
|
||||||
list=None,
|
list=None,
|
||||||
|
@ -558,7 +592,7 @@ class TrelloPlugin(Plugin):
|
||||||
|
|
||||||
labels = [ll for ll in card.board.get_labels() if ll.name == label]
|
labels = [ll for ll in card.board.get_labels() if ll.name == label]
|
||||||
|
|
||||||
assert labels, 'No such label: {}'.format(label)
|
assert labels, f'No such label: {label}'
|
||||||
label = labels[0]
|
label = labels[0]
|
||||||
card.add_label(label)
|
card.add_label(label)
|
||||||
|
|
||||||
|
@ -575,7 +609,7 @@ class TrelloPlugin(Plugin):
|
||||||
|
|
||||||
labels = [ll for ll in card.board.get_labels() if ll.name == label]
|
labels = [ll for ll in card.board.get_labels() if ll.name == label]
|
||||||
|
|
||||||
assert labels, 'No such label: {}'.format(label)
|
assert labels, f'No such label: {label}'
|
||||||
label = labels[0]
|
label = labels[0]
|
||||||
card.remove_label(label)
|
card.remove_label(label)
|
||||||
|
|
||||||
|
@ -637,9 +671,13 @@ class TrelloPlugin(Plugin):
|
||||||
card = client.get_card(card_id)
|
card = client.get_card(card_id)
|
||||||
card.remove_attachment(attachment_id)
|
card.remove_attachment(attachment_id)
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
@action
|
@action
|
||||||
def change_card_board(self, card_id: str, board: str, list: str = None):
|
def change_card_board(
|
||||||
|
self,
|
||||||
|
card_id: str,
|
||||||
|
board: str,
|
||||||
|
list: Optional[str] = None, # pylint: disable=redefined-builtin
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Move a card to a new board.
|
Move a card to a new board.
|
||||||
|
|
||||||
|
@ -649,17 +687,18 @@ class TrelloPlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
client = self._get_client()
|
client = self._get_client()
|
||||||
card = client.get_card(card_id)
|
card = client.get_card(card_id)
|
||||||
board = self._get_board(board)
|
board_id = self._get_board(board).id
|
||||||
|
|
||||||
list_id = None
|
list_id = None
|
||||||
if list:
|
if list:
|
||||||
list_id = self._get_list(board.id, list).id
|
list_id = self._get_list(board_id, list).id
|
||||||
|
|
||||||
card.change_board(board.id, list_id)
|
card.change_board(board_id, list_id)
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
@action
|
@action
|
||||||
def change_card_list(self, card_id: str, list: str):
|
def change_card_list(
|
||||||
|
self, card_id: str, list: str # pylint: disable=redefined-builtin
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Move a card to a new list.
|
Move a card to a new list.
|
||||||
|
|
||||||
|
@ -668,8 +707,7 @@ class TrelloPlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
client = self._get_client()
|
client = self._get_client()
|
||||||
card = client.get_card(card_id)
|
card = client.get_card(card_id)
|
||||||
list = self._get_list(card.board.id, list)
|
card.change_list(self._get_list(card.board.id, list).id)
|
||||||
card.change_list(list.id)
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def change_card_pos(self, card_id: str, position: int):
|
def change_card_pos(self, card_id: str, position: int):
|
||||||
|
@ -827,11 +865,13 @@ class TrelloPlugin(Plugin):
|
||||||
card = client.get_card(card_id)
|
card = client.get_card(card_id)
|
||||||
card.subscribe()
|
card.subscribe()
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
@action
|
@action
|
||||||
def get_cards(
|
def get_cards(
|
||||||
self, board: str, list: Optional[str] = None, all: bool = False
|
self,
|
||||||
) -> TrelloCardsResponse:
|
board: str,
|
||||||
|
list: Optional[str] = None, # pylint: disable=redefined-builtin
|
||||||
|
all: bool = False, # pylint: disable=redefined-builtin
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Get the list of cards on a board.
|
Get the list of cards on a board.
|
||||||
|
|
||||||
|
@ -842,12 +882,12 @@ class TrelloPlugin(Plugin):
|
||||||
only return open/active cards (default: False).
|
only return open/active cards (default: False).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
board = self._get_board(board)
|
b = self._get_board(board)
|
||||||
lists: Dict[str, TrelloList] = {
|
lists: Dict[str, TrelloList] = {
|
||||||
ll.id: TrelloList(
|
ll.id: TrelloList(
|
||||||
id=ll.id, name=ll.name, closed=ll.closed, subscribed=ll.subscribed
|
id=ll.id, name=ll.name, closed=ll.closed, subscribed=ll.subscribed
|
||||||
)
|
)
|
||||||
for ll in board.list_lists()
|
for ll in b.list_lists()
|
||||||
}
|
}
|
||||||
|
|
||||||
list_id = None
|
list_id = None
|
||||||
|
@ -856,14 +896,16 @@ class TrelloPlugin(Plugin):
|
||||||
if list in lists:
|
if list in lists:
|
||||||
list_id = list
|
list_id = list
|
||||||
else:
|
else:
|
||||||
# noinspection PyUnresolvedReferences
|
ll = next(
|
||||||
ll = [l1 for l1 in lists.values() if l1.name == list]
|
iter(
|
||||||
assert ll, 'No such list ID/name: {}'.format(list)
|
l1 for l1 in lists.values() if l1.name == list # type: ignore
|
||||||
# noinspection PyUnresolvedReferences
|
),
|
||||||
list_id = ll[0].id
|
None,
|
||||||
|
)
|
||||||
|
assert ll, f'No such list ID/name: {list}'
|
||||||
|
list_id = ll.id # type: ignore
|
||||||
|
|
||||||
# noinspection PyUnresolvedReferences
|
return TrelloCardSchema().dump(
|
||||||
return TrelloCardsResponse(
|
|
||||||
[
|
[
|
||||||
TrelloCard(
|
TrelloCard(
|
||||||
id=c.id,
|
id=c.id,
|
||||||
|
@ -882,10 +924,11 @@ class TrelloPlugin(Plugin):
|
||||||
attachments=[
|
attachments=[
|
||||||
TrelloAttachment(
|
TrelloAttachment(
|
||||||
id=a.get('id'),
|
id=a.get('id'),
|
||||||
bytes=a.get('bytes'),
|
url=a.get('url'),
|
||||||
|
size=a.get('bytes'),
|
||||||
date=a.get('date'),
|
date=a.get('date'),
|
||||||
edge_color=a.get('edgeColor'),
|
edge_color=a.get('edgeColor'),
|
||||||
id_member=a.get('idMember'),
|
member_id=a.get('idMember'),
|
||||||
is_upload=a.get('isUpload'),
|
is_upload=a.get('isUpload'),
|
||||||
name=a.get('name'),
|
name=a.get('name'),
|
||||||
previews=[
|
previews=[
|
||||||
|
@ -893,13 +936,12 @@ class TrelloPlugin(Plugin):
|
||||||
id=p.get('id'),
|
id=p.get('id'),
|
||||||
scaled=p.get('scaled'),
|
scaled=p.get('scaled'),
|
||||||
url=p.get('url'),
|
url=p.get('url'),
|
||||||
bytes=p.get('bytes'),
|
size=p.get('bytes'),
|
||||||
height=p.get('height'),
|
height=p.get('height'),
|
||||||
width=p.get('width'),
|
width=p.get('width'),
|
||||||
)
|
)
|
||||||
for p in a.get('previews', [])
|
for p in a.get('previews', [])
|
||||||
],
|
],
|
||||||
url=a.get('url'),
|
|
||||||
mime_type=a.get('mimeType'),
|
mime_type=a.get('mimeType'),
|
||||||
)
|
)
|
||||||
for a in c.attachments
|
for a in c.attachments
|
||||||
|
@ -908,7 +950,7 @@ class TrelloPlugin(Plugin):
|
||||||
TrelloChecklist(
|
TrelloChecklist(
|
||||||
id=ch.id,
|
id=ch.id,
|
||||||
name=ch.name,
|
name=ch.name,
|
||||||
checklist_items=[
|
items=[
|
||||||
TrelloChecklistItem(
|
TrelloChecklistItem(
|
||||||
id=i.get('id'),
|
id=i.get('id'),
|
||||||
name=i.get('name'),
|
name=i.get('name'),
|
||||||
|
@ -945,10 +987,181 @@ class TrelloPlugin(Plugin):
|
||||||
latest_card_move_date=c.latestCardMove_date,
|
latest_card_move_date=c.latestCardMove_date,
|
||||||
date_last_activity=c.date_last_activity,
|
date_last_activity=c.date_last_activity,
|
||||||
)
|
)
|
||||||
for c in board.all_cards()
|
for c in b.all_cards()
|
||||||
if ((all or not c.closed) and (not list or c.list_id == list_id))
|
if ((all or not c.closed) and (not list or c.list_id == list_id))
|
||||||
]
|
],
|
||||||
|
many=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _initialize_connection(self, ws: WebSocketApp):
|
||||||
|
boards = [
|
||||||
|
b
|
||||||
|
for b in (self._get_boards() or [])
|
||||||
|
if not self._monitored_boards
|
||||||
|
or b.id in self._monitored_boards
|
||||||
|
or b.name in self._monitored_boards
|
||||||
|
]
|
||||||
|
|
||||||
|
for b in boards:
|
||||||
|
self._boards_by_id[b.id] = b
|
||||||
|
self._boards_by_name[b.name] = b
|
||||||
|
|
||||||
|
for board_id in self._boards_by_id.keys():
|
||||||
|
self._send(
|
||||||
|
ws,
|
||||||
|
{
|
||||||
|
'type': 'subscribe',
|
||||||
|
'modelType': 'Board',
|
||||||
|
'idModel': board_id,
|
||||||
|
'tags': ['clientActions', 'updates'],
|
||||||
|
'invitationTokens': [],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.logger.info('Trello boards subscribed')
|
||||||
|
|
||||||
|
def _on_msg(self, *args): # pylint: disable=too-many-return-statements
|
||||||
|
if len(args) < 2:
|
||||||
|
self.logger.warning(
|
||||||
|
'Missing websocket argument - make sure that you are using '
|
||||||
|
'a version of websocket-client < 0.53.0 or >= 0.58.0'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
ws, msg = args[:2]
|
||||||
|
if not msg:
|
||||||
|
# Reply back with an empty message when the server sends an empty message
|
||||||
|
ws.send('')
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
msg = json.loads(msg)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'Received invalid JSON message from Trello: %s: %s', msg, e
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if 'error' in msg:
|
||||||
|
self.logger.warning('Trello error: %s', msg['error'])
|
||||||
|
return
|
||||||
|
|
||||||
|
if msg.get('reqid') == 0:
|
||||||
|
self.logger.debug('Ping response received, subscribing boards')
|
||||||
|
self._initialize_connection(ws)
|
||||||
|
return
|
||||||
|
|
||||||
|
notify = msg.get('notify')
|
||||||
|
if not notify:
|
||||||
|
return
|
||||||
|
|
||||||
|
if notify['event'] != 'updateModels' or notify['typeName'] != 'Action':
|
||||||
|
return
|
||||||
|
|
||||||
|
for delta in notify['deltas']:
|
||||||
|
args = {
|
||||||
|
'card_id': delta['data']['card']['id'],
|
||||||
|
'card_name': delta['data']['card']['name'],
|
||||||
|
'list_id': (
|
||||||
|
delta['data'].get('list') or delta['data'].get('listAfter', {})
|
||||||
|
).get('id'),
|
||||||
|
'list_name': (
|
||||||
|
delta['data'].get('list') or delta['data'].get('listAfter', {})
|
||||||
|
).get('name'),
|
||||||
|
'board_id': delta['data']['board']['id'],
|
||||||
|
'board_name': delta['data']['board']['name'],
|
||||||
|
'closed': delta.get('closed'),
|
||||||
|
'member_id': delta['memberCreator']['id'],
|
||||||
|
'member_username': delta['memberCreator']['username'],
|
||||||
|
'member_fullname': delta['memberCreator']['fullName'],
|
||||||
|
'date': delta['date'],
|
||||||
|
}
|
||||||
|
|
||||||
|
if delta.get('type') == 'createCard':
|
||||||
|
get_bus().post(NewCardEvent(**args))
|
||||||
|
elif delta.get('type') == 'updateCard':
|
||||||
|
if 'listBefore' in delta['data']:
|
||||||
|
args.update(
|
||||||
|
{
|
||||||
|
'old_list_id': delta['data']['listBefore']['id'],
|
||||||
|
'old_list_name': delta['data']['listBefore']['name'],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
get_bus().post(MoveCardEvent(**args))
|
||||||
|
elif 'closed' in delta['data'].get('old', {}):
|
||||||
|
cls = (
|
||||||
|
UnarchivedCardEvent
|
||||||
|
if delta['data']['old']['closed']
|
||||||
|
else ArchivedCardEvent
|
||||||
|
)
|
||||||
|
get_bus().post(cls(**args))
|
||||||
|
|
||||||
|
def _on_error(self, *args):
|
||||||
|
error = args[1] if len(args) > 1 else args[0]
|
||||||
|
self.logger.warning('Trello websocket error: %s', error)
|
||||||
|
|
||||||
|
def _on_close(self, *_):
|
||||||
|
self.logger.warning('Trello websocket connection closed')
|
||||||
|
self._connected.clear()
|
||||||
|
self._req_id = 0
|
||||||
|
|
||||||
|
while not self.should_stop():
|
||||||
|
try:
|
||||||
|
self._connect(reconnect=True)
|
||||||
|
self._connected.wait(timeout=10)
|
||||||
|
break
|
||||||
|
except TimeoutError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def _on_open(self, *args):
|
||||||
|
ws = args[0] if args else None
|
||||||
|
self._connected.set()
|
||||||
|
if ws:
|
||||||
|
self._send(ws, {'type': 'ping'})
|
||||||
|
self.logger.info('Trello websocket connected')
|
||||||
|
|
||||||
|
def _send(self, ws: WebSocketApp, msg: dict):
|
||||||
|
msg['reqid'] = self._req_id
|
||||||
|
ws.send(json.dumps(msg))
|
||||||
|
self._req_id += 1
|
||||||
|
|
||||||
|
def _connect(self, reconnect: bool = False) -> WebSocketApp:
|
||||||
|
assert self.url, 'Trello websocket URL not set'
|
||||||
|
if reconnect:
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
if not self._ws:
|
||||||
|
self._ws = WebSocketApp(
|
||||||
|
self.url,
|
||||||
|
header={
|
||||||
|
'Cookie': (
|
||||||
|
f'token={self.token}; '
|
||||||
|
f'cloud.session.token={self.cloud_session_token}'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
on_open=self._on_open,
|
||||||
|
on_message=self._on_msg,
|
||||||
|
on_error=self._on_error,
|
||||||
|
on_close=self._on_close,
|
||||||
|
)
|
||||||
|
|
||||||
|
return self._ws
|
||||||
|
|
||||||
|
def main(self):
|
||||||
|
if not self.url:
|
||||||
|
self.logger.info(
|
||||||
|
"token/cloud_session_token not set: your Trello boards won't be monitored for changes"
|
||||||
|
)
|
||||||
|
self.wait_stop()
|
||||||
|
else:
|
||||||
|
ws = self._connect()
|
||||||
|
ws.run_forever()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if self._ws:
|
||||||
|
self._ws.close()
|
||||||
|
self._ws = None
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
163
platypush/plugins/trello/_model.py
Normal file
163
platypush/plugins/trello/_model.py
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from typing import Optional, List, Union
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrelloList:
|
||||||
|
"""
|
||||||
|
Represents a Trello list.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
closed: bool
|
||||||
|
subscribed: bool
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrelloBoard:
|
||||||
|
"""
|
||||||
|
Represents a Trello board.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
url: str
|
||||||
|
closed: bool
|
||||||
|
lists: Optional[List[TrelloList]] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
date_last_activity: Optional[datetime.datetime] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrelloLabel:
|
||||||
|
"""
|
||||||
|
Represents a Trello label.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
color: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrelloUser:
|
||||||
|
"""
|
||||||
|
Represents a Trello user.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
username: str
|
||||||
|
fullname: str
|
||||||
|
initials: Optional[str] = None
|
||||||
|
avatar_url: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrelloComment:
|
||||||
|
"""
|
||||||
|
Represents a Trello comment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
text: str
|
||||||
|
type: str
|
||||||
|
creator: TrelloUser
|
||||||
|
date: Union[str, datetime.datetime]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrelloPreview:
|
||||||
|
"""
|
||||||
|
Represents a Trello preview.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
url: str
|
||||||
|
scaled: bool
|
||||||
|
size: int
|
||||||
|
height: int
|
||||||
|
width: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrelloAttachment:
|
||||||
|
"""
|
||||||
|
Represents a Trello attachment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
size: int
|
||||||
|
date: str
|
||||||
|
edge_color: str
|
||||||
|
member_id: str
|
||||||
|
is_upload: bool
|
||||||
|
name: str
|
||||||
|
previews: List[TrelloPreview]
|
||||||
|
url: str
|
||||||
|
mime_type: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrelloChecklistItem:
|
||||||
|
"""
|
||||||
|
Represents a Trello checklist item.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
checked: bool
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrelloChecklist:
|
||||||
|
"""
|
||||||
|
Represents a Trello checklist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
items: List[TrelloChecklistItem]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrelloCard:
|
||||||
|
"""
|
||||||
|
Represents a Trello card.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
url: str
|
||||||
|
closed: bool
|
||||||
|
board: TrelloBoard
|
||||||
|
is_due_complete: bool
|
||||||
|
description: Optional[str] = None
|
||||||
|
list: Optional[TrelloList] = None
|
||||||
|
comments: Optional[List[TrelloComment]] = None
|
||||||
|
labels: Optional[List[TrelloLabel]] = None
|
||||||
|
attachments: Optional[List[TrelloAttachment]] = None
|
||||||
|
checklists: Optional[List[TrelloChecklist]] = None
|
||||||
|
due_date: Optional[Union[datetime.datetime, str]] = None
|
||||||
|
latest_card_move_date: Optional[Union[datetime.datetime, str]] = None
|
||||||
|
date_last_activity: Optional[Union[datetime.datetime, str]] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrelloMember:
|
||||||
|
"""
|
||||||
|
Represents a Trello member.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
fullname: str
|
||||||
|
bio: Optional[str]
|
||||||
|
url: Optional[str]
|
||||||
|
username: Optional[str]
|
||||||
|
initials: Optional[str]
|
||||||
|
member_type: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
# vim:sw=4:ts=4:et:
|
|
@ -1,5 +1,9 @@
|
||||||
manifest:
|
manifest:
|
||||||
events: {}
|
events:
|
||||||
|
- platypush.message.event.trello.ArchivedCardEvent
|
||||||
|
- platypush.message.event.trello.MoveCardEvent
|
||||||
|
- platypush.message.event.trello.UnarchivedCardEvent
|
||||||
|
- platypush.message.event.trello.NewCardEvent
|
||||||
install:
|
install:
|
||||||
pip:
|
pip:
|
||||||
- py-trello
|
- py-trello
|
||||||
|
|
530
platypush/schemas/trello.py
Normal file
530
platypush/schemas/trello.py
Normal file
|
@ -0,0 +1,530 @@
|
||||||
|
from marshmallow import EXCLUDE, fields
|
||||||
|
from marshmallow.schema import Schema
|
||||||
|
|
||||||
|
|
||||||
|
class TrelloLabelSchema(Schema):
|
||||||
|
"""
|
||||||
|
Trello label schema.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The label's unique identifier.",
|
||||||
|
"example": "5d62808da5d6a95a3a3e4f2f",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
name = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The label's name.",
|
||||||
|
"example": "My Label",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
color = fields.String(
|
||||||
|
metadata={
|
||||||
|
"description": "The label's color.",
|
||||||
|
"example": "green",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TrelloUserSchema(Schema):
|
||||||
|
"""
|
||||||
|
Trello user schema.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The user's unique identifier.",
|
||||||
|
"example": "5d62808da5d6a95a3a3e4f2f",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
username = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The user's username.",
|
||||||
|
"example": "myusername",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
fullname = fields.String(
|
||||||
|
metadata={
|
||||||
|
"description": "The user's full name.",
|
||||||
|
"example": "My Full Name",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
initials = fields.String(
|
||||||
|
metadata={
|
||||||
|
"description": "The user's initials.",
|
||||||
|
"example": "MFN",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
avatar_url = fields.Url(
|
||||||
|
metadata={
|
||||||
|
"description": "The user's avatar URL.",
|
||||||
|
"example": "https://trello-avatars.s3.amazonaws.com/5d62808da5d6a95a3a3e4f2f/50.png",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TrelloMemberSchema(Schema):
|
||||||
|
"""
|
||||||
|
Trello member schema.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The user's unique identifier.",
|
||||||
|
"example": "5d62808da5d6a95a3a3e4f2f",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
username = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The user's username.",
|
||||||
|
"example": "myusername",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
fullname = fields.String(
|
||||||
|
metadata={
|
||||||
|
"description": "The user's full name.",
|
||||||
|
"example": "My Full Name",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
initials = fields.String(
|
||||||
|
metadata={
|
||||||
|
"description": "The user's initials.",
|
||||||
|
"example": "MFN",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
bio = fields.String(
|
||||||
|
metadata={
|
||||||
|
"description": "The user's bio.",
|
||||||
|
"example": "My bio.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
member_type = fields.String(
|
||||||
|
metadata={
|
||||||
|
"description": "The user's type.",
|
||||||
|
"example": "admin",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TrelloCommentSchema(Schema):
|
||||||
|
"""
|
||||||
|
Trello comment schema.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The comment's unique identifier.",
|
||||||
|
"example": "5d62808da5d6a95a3a3e4f2f",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
text = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The comment's text.",
|
||||||
|
"example": "My comment's text.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
type = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The comment's type.",
|
||||||
|
"example": "commentCard",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
creator = fields.Nested(TrelloUserSchema)
|
||||||
|
date = fields.DateTime(
|
||||||
|
metadata={
|
||||||
|
"description": "The comment's date.",
|
||||||
|
"example": "2019-08-25T15:32:13.000Z",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TrelloListSchema(Schema):
|
||||||
|
"""
|
||||||
|
Trello list schema.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta: # pylint: disable=too-few-public-methods
|
||||||
|
"""
|
||||||
|
Meta class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
unknown = EXCLUDE
|
||||||
|
|
||||||
|
id = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The list's unique identifier.",
|
||||||
|
"example": "5d62808da5d6a95a3a3e4f2f",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
name = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The list's name.",
|
||||||
|
"example": "My List",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
closed = fields.Boolean(
|
||||||
|
missing=False,
|
||||||
|
metadata={
|
||||||
|
"description": "Whether the list is closed.",
|
||||||
|
"example": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
subscribed = fields.Boolean(
|
||||||
|
missing=False,
|
||||||
|
metadata={
|
||||||
|
"description": "Whether the list is subscribed.",
|
||||||
|
"example": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TrelloBoardSchema(Schema):
|
||||||
|
"""
|
||||||
|
Trello board schema.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta: # pylint: disable=too-few-public-methods
|
||||||
|
"""
|
||||||
|
Meta class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
unknown = EXCLUDE
|
||||||
|
|
||||||
|
id = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The board's unique identifier.",
|
||||||
|
"example": "5d62808da5d6a95a3a3e4f2f",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
name = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The board's name.",
|
||||||
|
"example": "My Board",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
url = fields.Url(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The board's URL.",
|
||||||
|
"example": "https://trello.com/b/5d62808da5d6a95a3a3e4f2f/my-board",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
closed = fields.Boolean(
|
||||||
|
missing=False,
|
||||||
|
metadata={
|
||||||
|
"description": "Whether the board is closed.",
|
||||||
|
"example": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
lists = fields.Nested(
|
||||||
|
TrelloListSchema,
|
||||||
|
many=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The board's lists.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
date_last_activity = fields.DateTime(
|
||||||
|
metadata={
|
||||||
|
"description": "The board's last activity date.",
|
||||||
|
"example": "2019-08-25T15:52:45.000Z",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TrelloPreviewSchema(Schema):
|
||||||
|
"""
|
||||||
|
Trello attachment preview schema.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The preview's unique identifier.",
|
||||||
|
"example": "5d62808da5d6a95a3a3e4f2f",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
url = fields.Url(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The preview's URL.",
|
||||||
|
"example": "https://trello.com/c/5d62808da5d6a95a3a3e4f2f/my-attachment-txt.jpg",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
scaled = fields.Boolean(
|
||||||
|
metadata={
|
||||||
|
"description": "Whether the preview is scaled.",
|
||||||
|
"example": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
size = fields.Integer(
|
||||||
|
metadata={
|
||||||
|
"description": "The preview's size, in bytes.",
|
||||||
|
"example": 10000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
width = fields.Integer(
|
||||||
|
metadata={
|
||||||
|
"description": "The preview's width, in pixels.",
|
||||||
|
"example": 100,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
height = fields.Integer(
|
||||||
|
metadata={
|
||||||
|
"description": "The preview's height, in pixels.",
|
||||||
|
"example": 100,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TrelloChecklistItemSchema(Schema):
|
||||||
|
"""
|
||||||
|
Trello checklist item schema.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The checklist item's unique identifier.",
|
||||||
|
"example": "5d62808da5d6a95a3a3e4f2f",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
name = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The checklist item's name.",
|
||||||
|
"example": "My Checklist Item",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
checked = fields.Boolean(
|
||||||
|
metadata={
|
||||||
|
"description": "Whether the checklist item is checked.",
|
||||||
|
"example": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TrelloChecklistSchema(Schema):
|
||||||
|
"""
|
||||||
|
Trello checklist schema.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The checklist's unique identifier.",
|
||||||
|
"example": "5d62808da5d6a95a3a3e4f2f",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
name = fields.String(
|
||||||
|
metadata={
|
||||||
|
"description": "The checklist's name.",
|
||||||
|
"example": "My Checklist",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
items = fields.Nested(TrelloChecklistItemSchema, many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TrelloAttachmentSchema(Schema):
|
||||||
|
"""
|
||||||
|
Trello attachment schema.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The attachment's unique identifier.",
|
||||||
|
"example": "5d62808da5d6a95a3a3e4f2f",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
name = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The attachment's name.",
|
||||||
|
"example": "My Attachment.txt",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
url = fields.Url(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The attachment's URL.",
|
||||||
|
"example": "https://trello.com/c/5d62808da5d6a95a3a3e4f2f/my-attachment.txt",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
size = fields.Integer(
|
||||||
|
metadata={
|
||||||
|
"description": "The attachment's size, in bytes.",
|
||||||
|
"example": 1024,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
date = fields.DateTime(
|
||||||
|
metadata={
|
||||||
|
"description": "The attachment's date.",
|
||||||
|
"example": "2019-08-25T15:32:13.000Z",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
edge_color = fields.String(
|
||||||
|
metadata={
|
||||||
|
"description": "The attachment's edge color.",
|
||||||
|
"example": "#000000",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
member_id = fields.String(
|
||||||
|
metadata={
|
||||||
|
"description": "The ID of the member who created the attachment.",
|
||||||
|
"example": "5d62808da5d6a95a3a3e4f2f",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
is_upload = fields.Boolean(
|
||||||
|
metadata={
|
||||||
|
"description": "Whether the attachment is an upload.",
|
||||||
|
"example": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
mime_type = fields.String(
|
||||||
|
metadata={
|
||||||
|
"description": "The attachment's MIME type.",
|
||||||
|
"example": "text/plain",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
previews = fields.Nested(TrelloPreviewSchema, many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TrelloCardSchema(Schema):
|
||||||
|
"""
|
||||||
|
Trello card schema.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta: # pylint: disable=too-few-public-methods
|
||||||
|
"""
|
||||||
|
Meta class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
unknown = EXCLUDE
|
||||||
|
|
||||||
|
id = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The card's unique identifier.",
|
||||||
|
"example": "5d62808da5d6a95a3a3e4f2f",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
name = fields.String(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The card's name.",
|
||||||
|
"example": "My Card",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
description = fields.String(
|
||||||
|
metadata={
|
||||||
|
"description": "The card's description.",
|
||||||
|
"example": "My card's description.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
url = fields.Url(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
"description": "The card's URL.",
|
||||||
|
"example": "https://trello.com/c/5d62808da5d6a95a3a3e4f2f/my-card",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
due_date = fields.DateTime(
|
||||||
|
metadata={
|
||||||
|
"description": "The card's due date.",
|
||||||
|
"example": "2019-08-25T15:52:45.000Z",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
latest_card_move_date = fields.DateTime(
|
||||||
|
metadata={
|
||||||
|
"description": "The card's latest move date.",
|
||||||
|
"example": "2019-08-25T15:52:45.000Z",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
date_last_activity = fields.DateTime(
|
||||||
|
metadata={
|
||||||
|
"description": "The card's last activity date.",
|
||||||
|
"example": "2019-08-25T15:52:45.000Z",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
closed = fields.Boolean(
|
||||||
|
missing=False,
|
||||||
|
metadata={
|
||||||
|
"description": "Whether the card is closed.",
|
||||||
|
"example": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
is_due_complete = fields.Boolean(
|
||||||
|
metadata={
|
||||||
|
"description": "Whether the card is due complete.",
|
||||||
|
"example": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
board = fields.Nested(TrelloBoardSchema)
|
||||||
|
list = fields.Nested(TrelloListSchema)
|
||||||
|
comments = fields.Nested(TrelloCommentSchema, many=True)
|
||||||
|
labels = fields.Nested(TrelloLabelSchema, many=True)
|
||||||
|
attachments = fields.Nested(TrelloAttachmentSchema, many=True)
|
||||||
|
checklists = fields.Nested(TrelloChecklistSchema, many=True)
|
Loading…
Reference in a new issue