snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-15 15:05:30 +05:00
commit 64898b2c79
8999 changed files with 1757358 additions and 0 deletions

View File

View File

@@ -0,0 +1,82 @@
# Zulip Trello integration
Get Zulip notifications from your Trello boards!
!!! tip ""
[Zapier](./zapier) is usually a simpler way to integrate Trello
with Zulip.
{start_tabs}
1. {!create-an-incoming-webhook.md!}
1. {!generate-webhook-url-basic.md!}
1. **Log in to Trello**, and collect the following:
* **Board ID**: Go to your Trello board. The URL should look like
`https://trello.com/b/<BOARD_ID>/<BOARD_NAME>`. Note down the
`<BOARD_ID>`.
* **API Key**: Go to <https://trello.com/1/appkey/generate>. Note down
the key listed under **Developer API Keys**.
* **User Token**: Under **Developer API Keys**, click on the **Token**
link, and click **Allow**. Note down the token generated.
1. You're now going to need to run a Trello configuration script from a
computer (any computer) connected to the internet. It won't make any
changes to the computer.
Make sure you have a working copy of Python. If you're running
macOS or Linux, you very likely already do. If you're running
Windows you may or may not. If you don't have Python, follow the
installation instructions [here][1].
!!! tip ""
You do not need the latest version of Python; anything 2.7 or
higher will do.
1. Download [zulip-trello.py][2].
!!! tip ""
<kbd>Ctrl</kbd> + <kbd>S</kbd> or <kbd>Cmd</kbd> + <kbd>S</kbd>
on that page should work in most browsers.
1. Run the `zulip-trello` script in a terminal, after replacing the all
caps arguments with the values collected above and the generated URL
above.
```
python zulip_trello.py --trello-board-name TRELLO_BOARD_NAME \
--trello-board-id TRELLO_BOARD_ID \
--trello-api-key TRELLO_API_KEY \
--trello-token TRELLO_TOKEN \
--zulip-webhook-url "GENERATED_WEBHOOK_URL"
```
!!! warn ""
**Note**: Make sure that you wrap the webhook URL generated above
in quotes when supplying it on the command-line, as shown above.
The `zulip_trello.py` script only needs to be run once, and can be run
on any computer with python.
1. You can delete `zulip_trello.py` from your computer if you'd like.
{end_tabs}
{!congrats.md!}
![](/static/images/integrations/trello/001.png)
### Related documentation
{!webhooks-url-specification.md!}
[1]: https://realpython.com/installing-python/
[2]: https://raw.githubusercontent.com/zulip/python-zulip-api/main/zulip/integrations/trello/zulip_trello.py

View File

@@ -0,0 +1,75 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"5735940584b4870a92c9d2db",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"card":{
"shortLink":"xPKXoSTQ",
"idShort":3,
"name":"New card",
"id":"53582e2da0fac5676a714280"
},
"attachment":{
"url":"http://url.com",
"name":"attachment_name",
"id":"5735940584b4870a92c9d2da"
}
},
"type":"addAttachmentToCard",
"date":"2016-05-13T08:44:53.897Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,74 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"573597f5d3adc4af52e39f57",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"checklist":{
"name":"Checklist",
"id":"573597f5d3adc4af52e39f56"
},
"card":{
"shortLink":"xPKXoSTQ",
"idShort":3,
"name":"New card",
"id":"53582e2da0fac5676a714280"
}
},
"type":"addChecklistToCard",
"date":"2016-05-13T09:01:41.124Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,75 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"5735bd3a54bf8d93c35149bb",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"list":{
"name":"Basics",
"id":"53582e2da0fac5676a714279"
},
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"card":{
"shortLink":"xPKXoSTQ",
"idShort":3,
"name":"New card",
"id":"53582e2da0fac5676a714280"
},
"text":"New comment"
},
"type":"commentCard",
"date":"2016-05-13T11:40:42.612Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,96 @@
{
"model": {
"id": "55351ade1ed595cfa1bddf81",
"name": "Welcome Board",
"desc": "",
"descData": null,
"closed": false,
"idOrganization": null,
"pinned": false,
"url": "https://trello.com/b/BDlkcF4E/welcome-board",
"shortUrl": "https://trello.com/b/BDlkcF4E",
"prefs": {
"permissionLevel": "private",
"voting": "disabled",
"comments": "members",
"invitations": "members",
"selfJoin": true,
"cardCovers": true,
"calendarFeedEnabled": false,
"background": "blue",
"backgroundImage": null,
"backgroundImageScaled": null,
"backgroundTile": false,
"backgroundBrightness": "dark",
"backgroundColor": "#0079BF",
"canBePublic": true,
"canBeOrg": true,
"canBePrivate": true,
"canInvite": true
},
"labelNames": {
"green": "",
"yellow": "",
"orange": "",
"red": "",
"purple": "",
"blue": "",
"sky": "",
"lime": "",
"pink": "",
"black": ""
}
},
"action": {
"id": "595940d7ba919afcb0db1e8f",
"idMemberCreator": "55351ade1ed595cfa1bddf80",
"data": {
"list": {
"name": "List",
"id": "595940919403b0cd052718c6"
},
"board": {
"shortLink": "BDlkcF4E",
"name": "Welcome Board",
"id": "55351ade1ed595cfa1bddf81"
},
"card": {
"shortLink": "P2r0z66z",
"idShort": 8,
"name": "New Card",
"id": "595940ccdd7b429f714ac9f3",
"desc": "New Description"
},
"old": {
"desc": ""
}
},
"type": "updateCard",
"date": "2017-07-02T18:52:07.432Z",
"memberCreator": {
"id": "55351ade1ed595cfa1bddf80",
"avatarHash": "bcf3f3d2c214f269da2a483abc82524d",
"fullName": "Marco Matarazzo",
"initials": "MM",
"username": "marcomatarazzo1"
},
"display": {
"translationKey": "action_changed_description_of_card",
"entities": {
"card": {
"type": "card",
"desc": "New Description",
"id": "595940ccdd7b429f714ac9f3",
"shortLink": "P2r0z66z",
"text": "New Card"
},
"memberCreator": {
"type": "member",
"id": "55351ade1ed595cfa1bddf80",
"username": "marcomatarazzo1",
"text": "Marco Matarazzo"
}
}
}
}
}

View File

@@ -0,0 +1,77 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"572a3733549f0fa0e7398918",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"label":{
"color":"green",
"name":"",
"id":"54642de974d650d56763bc32"
},
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"card":{
"shortLink":"r33ylX2Z",
"idShort":2,
"name":"Card name",
"id":"53582e2da0fac5676a71427f"
},
"text":"text value",
"value":"green"
},
"type":"addLabelToCard",
"date":"2016-05-04T17:53:55.770Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,73 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"5735a316ba78942251d84438",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"idMemberAdded":"4e6a7fad05d98b02ba00845c",
"memberType":"normal"
},
"type":"addMemberToBoard",
"date":"2016-05-13T09:49:10.519Z",
"member":{
"id":"4e6a7fad05d98b02ba00845c",
"avatarHash":"a6cc37f6849928acb91064cf65e61cbc",
"fullName":"Trello",
"initials":"T",
"username":"trello"
},
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,78 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"572b6d4635b843ed3faa7dc0",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"card":{
"shortLink":"9BduUcVQ",
"idShort":8,
"name":"Card name",
"id":"53582e2da0fac5676a71428b"
},
"idMember":"53582e2da0fac5676a714276"
},
"type":"addMemberToCard",
"date":"2016-05-05T15:56:54.348Z",
"member":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
},
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,68 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"5735b78f3fbdc04509aff9f2",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"list":{
"name":"New list",
"id":"5735b78f3fbdc04509aff9f1"
}
},
"type":"createList",
"date":"2016-05-13T11:16:31.028Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,78 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"572b784d97893f947c179ccb",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"list":{
"name":"Intermediate",
"id":"53582e2da0fac5676a71427a"
},
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"card":{
"shortLink":"9BduUcVQ",
"idShort":8,
"name":"Card name",
"id":"53582e2da0fac5676a71428b",
"closed":true
},
"old":{
"closed":false
}
},
"type":"updateCard",
"date":"2016-05-05T16:43:57.557Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,141 @@
{
"model":{
"id":"59780df0f985476615f5ea6a",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iR0fe7ne/welcome-board",
"shortUrl":"https://trello.com/b/iR0fe7ne",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":true,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"59b7d0e1443cddf631c53a93",
"backgroundImage":"https://trello-backgrounds.s3.amazonaws.com/SharedBackground/2560x1707/f875a4638e0662f49d91e5295f7f76a9/photo-1505150643203-7a35720d76c6",
"backgroundImageScaled":[
{
"width":140,
"height":100,
"url":"https://trello-backgrounds.s3.amazonaws.com/SharedBackground/140x100/d9a6c1e0ccec47a25505569cafa30340/photo-1505150643203-7a35720d76c6.jpg"
},
{
"width":256,
"height":192,
"url":"https://trello-backgrounds.s3.amazonaws.com/SharedBackground/256x192/23826573fbf5d32c5e9aeebe662526ce/photo-1505150643203-7a35720d76c6.jpg"
},
{
"width":480,
"height":480,
"url":"https://trello-backgrounds.s3.amazonaws.com/SharedBackground/480x480/1f48d9f160296facf8b306749b9c7e3c/photo-1505150643203-7a35720d76c6.jpg"
},
{
"width":960,
"height":960,
"url":"https://trello-backgrounds.s3.amazonaws.com/SharedBackground/960x960/91264ea69a09a3de35cab60f2e3dbe31/photo-1505150643203-7a35720d76c6.jpg"
},
{
"width":1024,
"height":1024,
"url":"https://trello-backgrounds.s3.amazonaws.com/SharedBackground/1024x1024/8d0342dfb519de1c40e63475ce87f9dd/photo-1505150643203-7a35720d76c6.jpg"
},
{
"width":2048,
"height":2048,
"url":"https://trello-backgrounds.s3.amazonaws.com/SharedBackground/2048x2048/696ae241e26150b8c460d0294d4c1928/photo-1505150643203-7a35720d76c6.jpg"
},
{
"width":1280,
"height":1280,
"url":"https://trello-backgrounds.s3.amazonaws.com/SharedBackground/1280x1280/3b7b38da5e996ad302a75bf3c5d127fe/photo-1505150643203-7a35720d76c6.jpg"
},
{
"width":1920,
"height":1920,
"url":"https://trello-backgrounds.s3.amazonaws.com/SharedBackground/1920x1920/e1a2b009d9a68fadf870ff35fd659a15/photo-1505150643203-7a35720d76c6.jpg"
},
{
"width":2560,
"height":1600,
"url":"https://trello-backgrounds.s3.amazonaws.com/SharedBackground/2560x1600/e2e1fd2f18d6165f0cc3e625533f1cd1/photo-1505150643203-7a35720d76c6.jpg"
},
{
"width":2560,
"height":1707,
"url":"https://trello-backgrounds.s3.amazonaws.com/SharedBackground/2560x1707/f875a4638e0662f49d91e5295f7f76a9/photo-1505150643203-7a35720d76c6"
}
],
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundBottomColor":"#0b0504",
"backgroundTopColor":"#396b84",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"59bb22be8b214c452bed7c65",
"idMemberCreator":"59780df0f985476615f5ea66",
"data":{
"board":{
"shortLink":"iR0fe7ne",
"name":"Welcome Board",
"id":"59780df0f985476615f5ea6a",
"prefs":{
"background":"59b7d0e1443cddf631c53a93"
}
},
"old":{
"prefs":{
"background":"lime"
}
}
},
"type":"updateBoard",
"date":"2017-09-15T00:45:50.508Z",
"memberCreator":{
"id":"59780df0f985476615f5ea66",
"avatarHash":null,
"fullName":"Eeshan Garg",
"initials":"EG",
"username":"eeshangarg"
},
"display":{
"translationKey":"action_changed_board_background",
"entities":{
"board":{
"type":"board",
"id":"59780df0f985476615f5ea6a",
"text":"Welcome Board",
"shortLink":"iR0fe7ne"
},
"memberCreator":{
"type":"member",
"id":"59780df0f985476615f5ea66",
"username":"eeshangarg",
"text":"Eeshan Garg"
}
}
}
}
}

View File

@@ -0,0 +1,71 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{ }
},
"action":{
"id":"572a340644ed9c7d8b6f9a30",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"listAfter":{
"name":"Intermediate",
"id":"53582e2da0fac5676a71427a"
},
"listBefore":{
"name":"Basics",
"id":"53582e2da0fac5676a714279"
},
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"card":{
"shortLink":"r33ylX2Z",
"idShort":2,
"name":"This is a card.",
"id":"53582e2da0fac5676a71427f",
"idList":"53582e2da0fac5676a71427a"
},
"old":{
"idList":"53582e2da0fac5676a714279"
}
},
"type":"updateCard",
"date":"2016-05-04T17:40:22.254Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,96 @@
{
"model": {
"id": "55351ade1ed595cfa1bddf81",
"name": "Welcome Board",
"desc": "",
"descData": null,
"closed": false,
"idOrganization": null,
"pinned": false,
"url": "https://trello.com/b/BDlkcF4E/welcome-board",
"shortUrl": "https://trello.com/b/BDlkcF4E",
"prefs": {
"permissionLevel": "private",
"voting": "disabled",
"comments": "members",
"invitations": "members",
"selfJoin": true,
"cardCovers": true,
"calendarFeedEnabled": false,
"background": "blue",
"backgroundImage": null,
"backgroundImageScaled": null,
"backgroundTile": false,
"backgroundBrightness": "dark",
"backgroundColor": "#0079BF",
"canBePublic": true,
"canBeOrg": true,
"canBePrivate": true,
"canInvite": true
},
"labelNames": {
"green": "",
"yellow": "",
"orange": "",
"red": "",
"purple": "",
"blue": "",
"sky": "",
"lime": "",
"pink": "",
"black": ""
}
},
"action": {
"id": "595940dd9907aab7445c3faa",
"idMemberCreator": "55351ade1ed595cfa1bddf80",
"data": {
"list": {
"name": "List",
"id": "595940919403b0cd052718c6"
},
"board": {
"shortLink": "BDlkcF4E",
"name": "Welcome Board",
"id": "55351ade1ed595cfa1bddf81"
},
"card": {
"shortLink": "P2r0z66z",
"idShort": 8,
"name": "New Card",
"id": "595940ccdd7b429f714ac9f3",
"desc": "Changed Description"
},
"old": {
"desc": "New Description"
}
},
"type": "updateCard",
"date": "2017-07-02T18:52:13.184Z",
"memberCreator": {
"id": "55351ade1ed595cfa1bddf80",
"avatarHash": "bcf3f3d2c214f269da2a483abc82524d",
"fullName": "Marco Matarazzo",
"initials": "MM",
"username": "marcomatarazzo1"
},
"display": {
"translationKey": "action_changed_description_of_card",
"entities": {
"card": {
"type": "card",
"desc": "Changed Description",
"id": "595940ccdd7b429f714ac9f3",
"shortLink": "P2r0z66z",
"text": "New Card"
},
"memberCreator": {
"type": "member",
"id": "55351ade1ed595cfa1bddf80",
"username": "marcomatarazzo1",
"text": "Marco Matarazzo"
}
}
}
}
}

View File

@@ -0,0 +1,78 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"572b71d86efad95cda50c1ff",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"list":{
"name":"Intermediate",
"id":"53582e2da0fac5676a71427a"
},
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"old":{
"due":"2016-05-11T10:00:00.000Z"
},
"card":{
"shortLink":"9BduUcVQ",
"idShort":8,
"name":"Card name",
"id":"53582e2da0fac5676a71428b",
"due":"2016-05-24T10:00:00.000Z"
}
},
"type":"updateCard",
"date":"2016-05-05T16:16:24.684Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,115 @@
{
"model":{
"id":"597958d15c95392f2830e9f5",
"name":"Zulip",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/l9NqJcCd/zulip",
"shortUrl":"https://trello.com/b/l9NqJcCd",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"cardAging":"regular",
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"backgroundBottomColor":"#0079BF",
"backgroundTopColor":"#0079BF",
"canBePublic":true,
"canBeEnterprise":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"5c6f1c2149a22f1567ea1bc1",
"idMemberCreator":"59780df0f985476615f5ea66",
"data":{
"checkItem":{
"state":"complete",
"name":"Tomatoes",
"id":"5c6f1a64af711461f2534921"
},
"checklist":{
"name":"Checklist",
"id":"5c6f1a58d61c527c91892ba1"
},
"card":{
"shortLink":"R2thJK3P",
"idShort":3,
"name":"Something something",
"id":"5abd0cc638506013c1ebb33f"
},
"board":{
"shortLink":"l9NqJcCd",
"name":"Zulip",
"id":"597958d15c95392f2830e9f5"
}
},
"type":"updateCheckItemStateOnCard",
"date":"2019-02-21T21:46:09.784Z",
"limits":{
},
"display":{
"translationKey":"action_completed_checkitem",
"entities":{
"checkitem":{
"type":"checkItem",
"nameHtml":"Tomatoes",
"id":"5c6f1a64af711461f2534921",
"state":"complete",
"text":"Tomatoes"
},
"card":{
"type":"card",
"id":"5abd0cc638506013c1ebb33f",
"shortLink":"R2thJK3P",
"text":"Something something"
},
"memberCreator":{
"type":"member",
"id":"59780df0f985476615f5ea66",
"username":"eeshangarg",
"text":"Eeshan Garg"
}
}
},
"memberCreator":{
"id":"59780df0f985476615f5ea66",
"avatarHash":null,
"avatarUrl":null,
"fullName":"Eeshan Garg",
"idMemberReferrer":null,
"initials":"EG",
"nonPublic":{
},
"username":"eeshangarg"
}
}
}

View File

@@ -0,0 +1,74 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"572b84076b9f7ff48a8c6639",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"list":{
"name":"Intermediate",
"id":"53582e2da0fac5676a71427a"
},
"card":{
"shortLink":"5qrgGdD5",
"idShort":20,
"name":"New card",
"id":"572b84076b9f7ff48a8c6638"
}
},
"type":"createCard",
"date":"2016-05-05T17:33:59.341Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,97 @@
{
"model":{
"id":"597958d15c95392f2830e9f5",
"name":"Zulip",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/l9NqJcCd/zulip",
"shortUrl":"https://trello.com/b/l9NqJcCd",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"cardAging":"regular",
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"59795b110565448e01ecb996",
"idMemberCreator":"59780df0f985476615f5ea66",
"data":{
"list":{
"name":"To Do",
"id":"597958d15c95392f2830e9f6"
},
"board":{
"shortLink":"l9NqJcCd",
"name":"Zulip",
"id":"597958d15c95392f2830e9f5"
},
"card":{
"shortLink":"R9VBNGce",
"idShort":1,
"name":"Work on Zulip!",
"id":"597958df4120dc412438c3c4",
"pos":196607
},
"old":{
"pos":65535
}
},
"type":"updateCard",
"date":"2017-07-27T03:16:33.556Z",
"memberCreator":{
"id":"59780df0f985476615f5ea66",
"avatarHash":null,
"fullName":"Eeshan Garg",
"initials":"EG",
"username":"eeshangarg"
},
"display":{
"translationKey":"action_moved_card_higher",
"entities":{
"card":{
"type":"card",
"pos":196607,
"id":"597958df4120dc412438c3c4",
"shortLink":"R9VBNGce",
"text":"Work on Zulip!"
},
"memberCreator":{
"type":"member",
"id":"59780df0f985476615f5ea66",
"username":"eeshangarg",
"text":"Eeshan Garg"
}
}
}
}
}

View File

@@ -0,0 +1,96 @@
{
"model": {
"id": "55351ade1ed595cfa1bddf81",
"name": "Welcome Board",
"desc": "",
"descData": null,
"closed": false,
"idOrganization": null,
"pinned": false,
"url": "https://trello.com/b/BDlkcF4E/welcome-board",
"shortUrl": "https://trello.com/b/BDlkcF4E",
"prefs": {
"permissionLevel": "private",
"voting": "disabled",
"comments": "members",
"invitations": "members",
"selfJoin": true,
"cardCovers": true,
"calendarFeedEnabled": false,
"background": "blue",
"backgroundImage": null,
"backgroundImageScaled": null,
"backgroundTile": false,
"backgroundBrightness": "dark",
"backgroundColor": "#0079BF",
"canBePublic": true,
"canBeOrg": true,
"canBePrivate": true,
"canInvite": true
},
"labelNames": {
"green": "",
"yellow": "",
"orange": "",
"red": "",
"purple": "",
"blue": "",
"sky": "",
"lime": "",
"pink": "",
"black": ""
}
},
"action": {
"id": "595940e3153c06dd27fc27a6",
"idMemberCreator": "55351ade1ed595cfa1bddf80",
"data": {
"list": {
"name": "List",
"id": "595940919403b0cd052718c6"
},
"board": {
"shortLink": "BDlkcF4E",
"name": "Welcome Board",
"id": "55351ade1ed595cfa1bddf81"
},
"card": {
"shortLink": "P2r0z66z",
"idShort": 8,
"name": "New Card",
"id": "595940ccdd7b429f714ac9f3",
"desc": ""
},
"old": {
"desc": "Changed Description"
}
},
"type": "updateCard",
"date": "2017-07-02T18:52:19.020Z",
"memberCreator": {
"id": "55351ade1ed595cfa1bddf80",
"avatarHash": "bcf3f3d2c214f269da2a483abc82524d",
"fullName": "Marco Matarazzo",
"initials": "MM",
"username": "marcomatarazzo1"
},
"display": {
"translationKey": "action_changed_description_of_card",
"entities": {
"card": {
"type": "card",
"desc": "",
"id": "595940ccdd7b429f714ac9f3",
"shortLink": "P2r0z66z",
"text": "New Card"
},
"memberCreator": {
"type": "member",
"id": "55351ade1ed595cfa1bddf80",
"username": "marcomatarazzo1",
"text": "Marco Matarazzo"
}
}
}
}
}

View File

@@ -0,0 +1,78 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"572b721625d61ac1a5592497",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"list":{
"name":"Intermediate",
"id":"53582e2da0fac5676a71427a"
},
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"old":{
"due":"2016-05-24T10:00:00.000Z"
},
"card":{
"shortLink":"9BduUcVQ",
"idShort":8,
"name":"Card name",
"id":"53582e2da0fac5676a71428b",
"due":null
}
},
"type":"updateCard",
"date":"2016-05-05T16:17:26.908Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,77 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"572b903acadd8176647a1c8d",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"label":{
"color":"green",
"name":"",
"id":"54642de974d650d56763bc32"
},
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"card":{
"shortLink":"r33ylX2Z",
"idShort":2,
"name":"New Card",
"id":"53582e2da0fac5676a71427f"
},
"text":"text value",
"value":"green"
},
"type":"removeLabelFromCard",
"date":"2016-05-05T18:26:02.307Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,73 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"57359c57e337352c531efaa8",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"deactivated":false,
"idMember":"4e6a7fad05d98b02ba00845c"
},
"type":"removeMemberFromBoard",
"date":"2016-05-13T09:20:23.166Z",
"member":{
"id":"4e6a7fad05d98b02ba00845c",
"avatarHash":"a6cc37f6849928acb91064cf65e61cbc",
"fullName":"Trello",
"initials":"T",
"username":"trello"
},
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,79 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"572b6d154f004c5cccc0d8f1",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"card":{
"shortLink":"9BduUcVQ",
"idShort":8,
"name":"Card name",
"id":"53582e2da0fac5676a71428b"
},
"deactivated":false,
"idMember":"4e6a7fad05d98b02ba00845c"
},
"type":"removeMemberFromCard",
"date":"2016-05-05T15:56:05.991Z",
"member":{
"id":"4e6a7fad05d98b02ba00845c",
"avatarHash":"a6cc37f6849928acb91064cf65e61cbc",
"fullName":"Trello",
"initials":"T",
"username":"trello"
},
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,67 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"New name",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/new-name",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"5735c14e8cf95868089df00a",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"board":{
"shortLink":"iqXXzYEj",
"id":"53582e2da0fac5676a714277",
"name":"New name"
},
"old":{
"name":"Welcome Board"
}
},
"type":"updateBoard",
"date":"2016-05-13T11:58:06.045Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,77 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"572a36f33c7ab8948bab8064",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"list":{
"name":"Intermediate",
"id":"53582e2da0fac5676a71427a"
},
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"card":{
"shortLink":"r33ylX2Z",
"idShort":2,
"id":"53582e2da0fac5676a71427f",
"name":"New name"
},
"old":{
"name":"Old name"
}
},
"type":"updateCard",
"date":"2016-05-04T17:52:51.098Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,78 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"572b7a8b7ef0703562559b3a",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"list":{
"name":"Intermediate",
"id":"53582e2da0fac5676a71427a"
},
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"card":{
"shortLink":"9BduUcVQ",
"idShort":8,
"name":"Card name",
"id":"53582e2da0fac5676a71428b",
"closed":false
},
"old":{
"closed":true
}
},
"type":"updateCard",
"date":"2016-05-05T16:53:31.423Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,78 @@
{
"model":{
"id":"53582e2da0fac5676a714277",
"name":"Welcome Board",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/iqXXzYEj/welcome-board",
"shortUrl":"https://trello.com/b/iqXXzYEj",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"canBePublic":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"572b701647e1a8d7834b186a",
"idMemberCreator":"53582e2da0fac5676a714276",
"data":{
"list":{
"name":"Intermediate",
"id":"53582e2da0fac5676a71427a"
},
"board":{
"shortLink":"iqXXzYEj",
"name":"Welcome Board",
"id":"53582e2da0fac5676a714277"
},
"old":{
"due":null
},
"card":{
"shortLink":"9BduUcVQ",
"idShort":8,
"name":"Card name",
"id":"53582e2da0fac5676a71428b",
"due":"2016-05-11T10:00:00.000Z"
}
},
"type":"updateCard",
"date":"2016-05-05T16:08:54.559Z",
"memberCreator":{
"id":"53582e2da0fac5676a714276",
"avatarHash":null,
"fullName":"TomaszKolek",
"initials":"T",
"username":"tomaszkolek"
}
}
}

View File

@@ -0,0 +1,115 @@
{
"model":{
"id":"597958d15c95392f2830e9f5",
"name":"Zulip",
"desc":"",
"descData":null,
"closed":false,
"idOrganization":null,
"pinned":false,
"url":"https://trello.com/b/l9NqJcCd/zulip",
"shortUrl":"https://trello.com/b/l9NqJcCd",
"prefs":{
"permissionLevel":"private",
"voting":"disabled",
"comments":"members",
"invitations":"members",
"selfJoin":false,
"cardCovers":true,
"cardAging":"regular",
"calendarFeedEnabled":false,
"background":"blue",
"backgroundImage":null,
"backgroundImageScaled":null,
"backgroundTile":false,
"backgroundBrightness":"dark",
"backgroundColor":"#0079BF",
"backgroundBottomColor":"#0079BF",
"backgroundTopColor":"#0079BF",
"canBePublic":true,
"canBeEnterprise":true,
"canBeOrg":true,
"canBePrivate":true,
"canInvite":true
},
"labelNames":{
"green":"",
"yellow":"",
"orange":"",
"red":"",
"purple":"",
"blue":"",
"sky":"",
"lime":"",
"pink":"",
"black":""
}
},
"action":{
"id":"5c6f28a04e959901fe61a6de",
"idMemberCreator":"59780df0f985476615f5ea66",
"data":{
"checkItem":{
"state":"incomplete",
"name":"Tomatoes",
"id":"5c6f1a64af711461f2534921"
},
"checklist":{
"name":"Checklist",
"id":"5c6f1a58d61c527c91892ba1"
},
"card":{
"shortLink":"R2thJK3P",
"idShort":3,
"name":"Something something",
"id":"5abd0cc638506013c1ebb33f"
},
"board":{
"shortLink":"l9NqJcCd",
"name":"Zulip",
"id":"597958d15c95392f2830e9f5"
}
},
"type":"updateCheckItemStateOnCard",
"date":"2019-02-21T22:39:28.225Z",
"limits":{
},
"display":{
"translationKey":"action_marked_checkitem_incomplete",
"entities":{
"checkitem":{
"type":"checkItem",
"nameHtml":"Tomatoes",
"id":"5c6f1a64af711461f2534921",
"state":"incomplete",
"text":"Tomatoes"
},
"card":{
"type":"card",
"id":"5abd0cc638506013c1ebb33f",
"shortLink":"R2thJK3P",
"text":"Something something"
},
"memberCreator":{
"type":"member",
"id":"59780df0f985476615f5ea66",
"username":"eeshangarg",
"text":"Eeshan Garg"
}
}
},
"memberCreator":{
"id":"59780df0f985476615f5ea66",
"avatarHash":null,
"avatarUrl":null,
"fullName":"Eeshan Garg",
"idMemberReferrer":null,
"initials":"EG",
"nonPublic":{
},
"username":"eeshangarg"
}
}
}

View File

@@ -0,0 +1,178 @@
from unittest.mock import patch
import orjson
from zerver.lib.test_classes import WebhookTestCase
class TrelloHookTests(WebhookTestCase):
def test_trello_confirmation_request(self) -> None:
response = self.client_head(self.build_webhook_url())
self.assertEqual(response.status_code, 200, response)
def test_trello_webhook_when_card_was_moved_to_another_list(self) -> None:
expected_message = "TomaszKolek moved [This is a card.](https://trello.com/c/r33ylX2Z) from Basics to Intermediate."
self.check_webhook("changing_cards_list", "Welcome Board", expected_message)
def test_trello_webhook_when_card_was_renamed(self) -> None:
expected_message = 'TomaszKolek renamed the card from "Old name" to [New name](https://trello.com/c/r33ylX2Z).'
self.check_webhook("renaming_card", "Welcome Board", expected_message)
def test_trello_webhook_when_label_was_added_to_card(self) -> None:
expected_message = 'TomaszKolek added a green label with "text value" to [Card name](https://trello.com/c/r33ylX2Z).'
self.check_webhook("adding_label_to_card", "Welcome Board", expected_message)
def test_trello_webhook_when_label_was_removing_from_card(self) -> None:
expected_message = 'TomaszKolek removed a green label with "text value" from [New Card](https://trello.com/c/r33ylX2Z).'
self.check_webhook("removing_label_from_card", "Welcome Board", expected_message)
def test_trello_webhook_when_member_was_added_to_card(self) -> None:
expected_message = (
"TomaszKolek added TomaszKolek to [Card name](https://trello.com/c/9BduUcVQ)."
)
self.check_webhook("adding_member_to_card", "Welcome Board", expected_message)
def test_trello_webhook_when_member_was_removed_from_card(self) -> None:
expected_message = (
"TomaszKolek removed Trello from [Card name](https://trello.com/c/9BduUcVQ)."
)
self.check_webhook("removing_member_from_card", "Welcome Board", expected_message)
def test_trello_webhook_when_due_date_was_set(self) -> None:
expected_message = "TomaszKolek set due date for [Card name](https://trello.com/c/9BduUcVQ) to <time:2016-05-11T10:00:00+00:00>."
self.check_webhook("setting_due_date_to_card", "Welcome Board", expected_message)
def test_trello_webhook_when_due_date_was_changed(self) -> None:
expected_message = "TomaszKolek changed due date for [Card name](https://trello.com/c/9BduUcVQ) from <time:2016-05-11T10:00:00+00:00> to <time:2016-05-24T10:00:00+00:00>."
self.check_webhook("changing_due_date_on_card", "Welcome Board", expected_message)
def test_trello_webhook_when_due_date_was_removed(self) -> None:
expected_message = (
"TomaszKolek removed the due date from [Card name](https://trello.com/c/9BduUcVQ)."
)
self.check_webhook("removing_due_date_from_card", "Welcome Board", expected_message)
def test_trello_webhook_when_card_was_archived(self) -> None:
expected_message = "TomaszKolek archived [Card name](https://trello.com/c/9BduUcVQ)."
self.check_webhook("archiving_card", "Welcome Board", expected_message)
def test_trello_webhook_when_card_was_reopened(self) -> None:
expected_message = "TomaszKolek reopened [Card name](https://trello.com/c/9BduUcVQ)."
self.check_webhook("reopening_card", "Welcome Board", expected_message)
def test_trello_webhook_when_card_was_created(self) -> None:
expected_message = "TomaszKolek created [New card](https://trello.com/c/5qrgGdD5)."
self.check_webhook("creating_card", "Welcome Board", expected_message)
def test_trello_webhook_when_attachment_was_added_to_card(self) -> None:
expected_message = "TomaszKolek added [attachment_name](http://url.com) to [New card](https://trello.com/c/xPKXoSTQ)."
self.check_webhook("adding_attachment_to_card", "Welcome Board", expected_message)
def test_trello_webhook_when_checklist_was_added_to_card(self) -> None:
expected_message = "TomaszKolek added the Checklist checklist to [New card](https://trello.com/c/xPKXoSTQ)."
self.check_webhook("adding_checklist_to_card", "Welcome Board", expected_message)
def test_trello_webhook_when_check_item_is_checked(self) -> None:
expected_message = "Eeshan Garg checked **Tomatoes** in **Checklist** ([Something something](https://trello.com/c/R2thJK3P))."
self.check_webhook("check_item_on_card_checklist", "Zulip", expected_message)
def test_trello_webhook_when_check_item_is_unchecked(self) -> None:
expected_message = "Eeshan Garg unchecked **Tomatoes** in **Checklist** ([Something something](https://trello.com/c/R2thJK3P))."
self.check_webhook("uncheck_item_on_card_checklist", "Zulip", expected_message)
def test_trello_webhook_when_member_was_removed_from_board(self) -> None:
expected_message = (
"TomaszKolek removed Trello from [Welcome Board](https://trello.com/b/iqXXzYEj)."
)
self.check_webhook("removing_member_from_board", "Welcome Board", expected_message)
def test_trello_webhook_when_member_was_added_to_board(self) -> None:
expected_message = (
"TomaszKolek added Trello to [Welcome Board](https://trello.com/b/iqXXzYEj)."
)
self.check_webhook("adding_member_to_board", "Welcome Board", expected_message)
def test_trello_webhook_when_list_was_added_to_board(self) -> None:
expected_message = (
"TomaszKolek added New list list to [Welcome Board](https://trello.com/b/iqXXzYEj)."
)
self.check_webhook("adding_new_list_to_board", "Welcome Board", expected_message)
def test_trello_webhook_when_comment_was_added_to_card(self) -> None:
expected_message = "TomaszKolek commented on [New card](https://trello.com/c/xPKXoSTQ):\n~~~ quote\nNew comment\n~~~"
self.check_webhook("adding_comment_to_card", "Welcome Board", expected_message)
def test_trello_webhook_when_board_was_renamed(self) -> None:
expected_message = "TomaszKolek renamed the board from Welcome Board to [New name](https://trello.com/b/iqXXzYEj)."
self.check_webhook("renaming_board", "New name", expected_message)
def verify_post_is_ignored(self, payload: str) -> None:
with patch("zerver.webhooks.trello.view.check_send_webhook_message") as m:
result = self.client_post(self.url, payload, content_type="application/json")
self.assertFalse(m.called)
self.assert_json_success(result)
def test_trello_webhook_when_card_is_moved_within_single_list_ignore(self) -> None:
payload = self.get_body("moving_card_within_single_list")
self.verify_post_is_ignored(payload)
def test_trello_webhook_when_board_background_is_changed_ignore(self) -> None:
payload = self.get_body("change_board_background_image")
self.verify_post_is_ignored(payload)
def test_ignored_card_actions(self) -> None:
"""
Certain card-related actions are now ignored solely based on the
action type, and we don't need to do any other parsing to ignore
them as invalid.
"""
actions = [
"copyCard",
"createCheckItem",
"updateCheckItem",
"updateList",
]
for action in actions:
data = dict(
model="whatever",
action=dict(
type=action,
),
)
payload = orjson.dumps(data).decode()
self.verify_post_is_ignored(payload)
def test_ignoring_card_updates(self) -> None:
fields = [
"cover",
"dueComplete",
"idAttachmentCover",
"pos",
]
for field in fields:
card: dict[str, object] = {}
old = {}
old[field] = "should-be-ignored"
data = dict(
model="whatever",
action=dict(
type="updateCard",
data=dict(card=card, old=old),
),
)
payload = orjson.dumps(data).decode()
self.verify_post_is_ignored(payload)
def test_trello_webhook_when_description_was_added_to_card(self) -> None:
expected_message = "Marco Matarazzo set description for [New Card](https://trello.com/c/P2r0z66z) to:\n~~~ quote\nNew Description\n~~~"
self.check_webhook("adding_description_to_card", "Welcome Board", expected_message)
def test_trello_webhook_when_description_was_removed_from_card(self) -> None:
expected_message = (
"Marco Matarazzo removed description from [New Card](https://trello.com/c/P2r0z66z)."
)
self.check_webhook("removing_description_from_card", "Welcome Board", expected_message)
def test_trello_webhook_when_description_was_changed_on_card(self) -> None:
expected_message = "Marco Matarazzo changed description for [New Card](https://trello.com/c/P2r0z66z) from\n~~~ quote\nNew Description\n~~~\nto\n~~~ quote\nChanged Description\n~~~"
self.check_webhook("changing_description_on_card", "Welcome Board", expected_message)

View File

@@ -0,0 +1,44 @@
# Webhooks for external integrations.
from django.http import HttpRequest, HttpResponse
from zerver.decorator import return_success_on_head_request, webhook_view
from zerver.lib.exceptions import UnsupportedWebhookEventTypeError
from zerver.lib.response import json_success
from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
from zerver.lib.validator import WildValue, check_string
from zerver.lib.webhooks.common import check_send_webhook_message
from zerver.models import UserProfile
from .board_actions import SUPPORTED_BOARD_ACTIONS, process_board_action
from .card_actions import IGNORED_CARD_ACTIONS, SUPPORTED_CARD_ACTIONS, process_card_action
@webhook_view("Trello")
@return_success_on_head_request
@typed_endpoint
def api_trello_webhook(
request: HttpRequest,
user_profile: UserProfile,
*,
payload: JsonBodyPayload[WildValue],
) -> HttpResponse:
action_type = payload["action"]["type"].tame(check_string)
message = get_topic_and_body(payload, action_type)
if message is None:
return json_success(request)
else:
topic, body = message
check_send_webhook_message(request, user_profile, topic, body)
return json_success(request)
def get_topic_and_body(payload: WildValue, action_type: str) -> tuple[str, str] | None:
if action_type in SUPPORTED_CARD_ACTIONS:
return process_card_action(payload, action_type)
if action_type in IGNORED_CARD_ACTIONS:
return None
if action_type in SUPPORTED_BOARD_ACTIONS:
return process_board_action(payload, action_type)
raise UnsupportedWebhookEventTypeError(action_type)

View File

@@ -0,0 +1,118 @@
from collections.abc import Mapping
from zerver.lib.exceptions import UnsupportedWebhookEventTypeError
from zerver.lib.validator import WildValue, check_string
SUPPORTED_BOARD_ACTIONS = [
"removeMemberFromBoard",
"addMemberToBoard",
"createList",
"updateBoard",
]
REMOVE_MEMBER = "removeMemberFromBoard"
ADD_MEMBER = "addMemberToBoard"
CREATE_LIST = "createList"
CHANGE_NAME = "changeName"
TRELLO_BOARD_URL_TEMPLATE = "[{board_name}]({board_url})"
ACTIONS_TO_MESSAGE_MAPPER = {
REMOVE_MEMBER: "removed {member_name} from {board_url_template}.",
ADD_MEMBER: "added {member_name} to {board_url_template}.",
CREATE_LIST: "added {list_name} list to {board_url_template}.",
CHANGE_NAME: "renamed the board from {old_name} to {board_url_template}.",
}
def process_board_action(payload: WildValue, action_type: str | None) -> tuple[str, str] | None:
action_type = get_proper_action(payload, action_type)
if action_type is not None:
return get_topic(payload), get_body(payload, action_type)
return None
def get_proper_action(payload: WildValue, action_type: str | None) -> str | None:
if action_type == "updateBoard":
data = get_action_data(payload)
# we don't support events for when a board's background
# is changed
if "background" in data["old"].get("prefs", {}):
return None
elif data["old"]["name"].tame(check_string):
return CHANGE_NAME
raise UnsupportedWebhookEventTypeError(action_type)
return action_type
def get_topic(payload: WildValue) -> str:
return get_action_data(payload)["board"]["name"].tame(check_string)
def get_body(payload: WildValue, action_type: str) -> str:
message_body = ACTIONS_TO_FILL_BODY_MAPPER[action_type](payload, action_type)
creator = payload["action"]["memberCreator"]["fullName"].tame(check_string)
return f"{creator} {message_body}"
def get_managed_member_body(payload: WildValue, action_type: str) -> str:
data = {
"member_name": payload["action"]["member"]["fullName"].tame(check_string),
}
return fill_appropriate_message_content(payload, action_type, data)
def get_create_list_body(payload: WildValue, action_type: str) -> str:
data = {
"list_name": get_action_data(payload)["list"]["name"].tame(check_string),
}
return fill_appropriate_message_content(payload, action_type, data)
def get_change_name_body(payload: WildValue, action_type: str) -> str:
data = {
"old_name": get_action_data(payload)["old"]["name"].tame(check_string),
}
return fill_appropriate_message_content(payload, action_type, data)
def fill_appropriate_message_content(
payload: WildValue, action_type: str, data: Mapping[str, str] = {}
) -> str:
data = dict(data)
if "board_url_template" not in data:
data["board_url_template"] = get_filled_board_url_template(payload)
message_body = get_message_body(action_type)
return message_body.format(**data)
def get_filled_board_url_template(payload: WildValue) -> str:
return TRELLO_BOARD_URL_TEMPLATE.format(
board_name=get_board_name(payload), board_url=get_board_url(payload)
)
def get_board_name(payload: WildValue) -> str:
return get_action_data(payload)["board"]["name"].tame(check_string)
def get_board_url(payload: WildValue) -> str:
return "https://trello.com/b/{}".format(
get_action_data(payload)["board"]["shortLink"].tame(check_string)
)
def get_message_body(action_type: str) -> str:
return ACTIONS_TO_MESSAGE_MAPPER[action_type]
def get_action_data(payload: WildValue) -> WildValue:
return payload["action"]["data"]
ACTIONS_TO_FILL_BODY_MAPPER = {
REMOVE_MEMBER: get_managed_member_body,
ADD_MEMBER: get_managed_member_body,
CREATE_LIST: get_create_list_body,
CHANGE_NAME: get_change_name_body,
}

View File

@@ -0,0 +1,300 @@
from collections.abc import Mapping
from datetime import datetime
from zerver.lib.exceptions import UnsupportedWebhookEventTypeError
from zerver.lib.timestamp import datetime_to_global_time
from zerver.lib.validator import WildValue, check_bool, check_none_or, check_string
SUPPORTED_CARD_ACTIONS = [
"updateCard",
"createCard",
"addLabelToCard",
"removeLabelFromCard",
"addMemberToCard",
"removeMemberFromCard",
"addAttachmentToCard",
"addChecklistToCard",
"commentCard",
"updateCheckItemStateOnCard",
]
IGNORED_CARD_ACTIONS = [
"copyCard",
"createCheckItem",
"updateCheckItem",
"updateList",
]
CREATE = "createCard"
CHANGE_LIST = "changeList"
CHANGE_NAME = "changeName"
SET_DESC = "setDesc"
CHANGE_DESC = "changeDesc"
REMOVE_DESC = "removeDesc"
ARCHIVE = "archiveCard"
REOPEN = "reopenCard"
SET_DUE_DATE = "setDueDate"
CHANGE_DUE_DATE = "changeDueDate"
REMOVE_DUE_DATE = "removeDueDate"
ADD_LABEL = "addLabelToCard"
REMOVE_LABEL = "removeLabelFromCard"
ADD_MEMBER = "addMemberToCard"
REMOVE_MEMBER = "removeMemberFromCard"
ADD_ATTACHMENT = "addAttachmentToCard"
ADD_CHECKLIST = "addChecklistToCard"
COMMENT = "commentCard"
UPDATE_CHECK_ITEM_STATE = "updateCheckItemStateOnCard"
TRELLO_CARD_URL_TEMPLATE = "[{card_name}]({card_url})"
ACTIONS_TO_MESSAGE_MAPPER = {
CREATE: "created {card_url_template}.",
CHANGE_LIST: "moved {card_url_template} from {old_list} to {new_list}.",
CHANGE_NAME: 'renamed the card from "{old_name}" to {card_url_template}.',
SET_DESC: "set description for {card_url_template} to:\n~~~ quote\n{desc}\n~~~\n",
CHANGE_DESC: (
"changed description for {card_url_template} from\n"
"~~~ quote\n{old_desc}\n~~~\nto\n~~~ quote\n{desc}\n~~~\n"
),
REMOVE_DESC: "removed description from {card_url_template}.",
ARCHIVE: "archived {card_url_template}.",
REOPEN: "reopened {card_url_template}.",
SET_DUE_DATE: "set due date for {card_url_template} to {due_date}.",
CHANGE_DUE_DATE: "changed due date for {card_url_template} from {old_due_date} to {due_date}.",
REMOVE_DUE_DATE: "removed the due date from {card_url_template}.",
ADD_LABEL: 'added a {color} label with "{text}" to {card_url_template}.',
REMOVE_LABEL: 'removed a {color} label with "{text}" from {card_url_template}.',
ADD_MEMBER: "added {member_name} to {card_url_template}.",
REMOVE_MEMBER: "removed {member_name} from {card_url_template}.",
ADD_ATTACHMENT: "added [{attachment_name}]({attachment_url}) to {card_url_template}.",
ADD_CHECKLIST: "added the {checklist_name} checklist to {card_url_template}.",
COMMENT: "commented on {card_url_template}:\n~~~ quote\n{text}\n~~~\n",
UPDATE_CHECK_ITEM_STATE: "{action} **{item_name}** in **{checklist_name}** ({card_url_template}).",
}
def prettify_date(date_string: str) -> str:
dt = datetime.fromisoformat(date_string)
return datetime_to_global_time(dt)
def process_card_action(payload: WildValue, action_type: str) -> tuple[str, str] | None:
proper_action = get_proper_action(payload, action_type)
if proper_action is not None:
return get_topic(payload), get_body(payload, proper_action)
return None
def get_proper_action(payload: WildValue, action_type: str) -> str | None:
if action_type == "updateCard":
data = get_action_data(payload)
old_data = data["old"]
card_data = data["card"]
if data.get("listBefore"):
return CHANGE_LIST
if old_data.get("name").tame(check_none_or(check_string)):
return CHANGE_NAME
if old_data.get("desc").tame(check_none_or(check_string)) == "":
return SET_DESC
if old_data.get("desc").tame(check_none_or(check_string)):
if card_data.get("desc").tame(check_none_or(check_string)) == "":
return REMOVE_DESC
else:
return CHANGE_DESC
if old_data.get("due", "").tame(check_none_or(check_string)) is None:
return SET_DUE_DATE
if old_data.get("due").tame(check_none_or(check_string)):
if card_data.get("due", "").tame(check_none_or(check_string)) is None:
return REMOVE_DUE_DATE
else:
return CHANGE_DUE_DATE
if old_data.get("closed").tame(check_none_or(check_bool)) is False and card_data.get(
"closed", False
).tame(check_bool):
return ARCHIVE
if (
old_data.get("closed").tame(check_none_or(check_bool))
and card_data.get("closed").tame(check_none_or(check_bool)) is False
):
return REOPEN
# We don't support events for when a card is moved up or down
# within a single list (pos), or when the cover changes (cover).
# We also don't know if "dueComplete" is just a new name for "due".
ignored_fields = [
"cover",
"dueComplete",
"idAttachmentCover",
"pos",
]
for field in ignored_fields:
if field in old_data:
return None
raise UnsupportedWebhookEventTypeError(action_type)
return action_type
def get_topic(payload: WildValue) -> str:
return get_action_data(payload)["board"]["name"].tame(check_string)
def get_body(payload: WildValue, action_type: str) -> str:
message_body = ACTIONS_TO_FILL_BODY_MAPPER[action_type](payload, action_type)
creator = payload["action"]["memberCreator"].get("fullName").tame(check_none_or(check_string))
return f"{creator} {message_body}"
def get_added_checklist_body(payload: WildValue, action_type: str) -> str:
data = {
"checklist_name": get_action_data(payload)["checklist"]["name"].tame(check_string),
}
return fill_appropriate_message_content(payload, action_type, data)
def get_update_check_item_body(payload: WildValue, action_type: str) -> str:
action = get_action_data(payload)
state = action["checkItem"]["state"].tame(check_string)
data = {
"action": "checked" if state == "complete" else "unchecked",
"checklist_name": action["checklist"]["name"].tame(check_string),
"item_name": action["checkItem"]["name"].tame(check_string),
}
return fill_appropriate_message_content(payload, action_type, data)
def get_added_attachment_body(payload: WildValue, action_type: str) -> str:
data = {
"attachment_url": get_action_data(payload)["attachment"]["url"].tame(check_string),
"attachment_name": get_action_data(payload)["attachment"]["name"].tame(check_string),
}
return fill_appropriate_message_content(payload, action_type, data)
def get_updated_card_body(payload: WildValue, action_type: str) -> str:
data = {
"card_name": get_card_name(payload),
"old_list": get_action_data(payload)["listBefore"]["name"].tame(check_string),
"new_list": get_action_data(payload)["listAfter"]["name"].tame(check_string),
}
return fill_appropriate_message_content(payload, action_type, data)
def get_renamed_card_body(payload: WildValue, action_type: str) -> str:
data = {
"old_name": get_action_data(payload)["old"]["name"].tame(check_string),
"new_name": get_action_data(payload)["old"]["name"].tame(check_string),
}
return fill_appropriate_message_content(payload, action_type, data)
def get_added_label_body(payload: WildValue, action_type: str) -> str:
data = {
"color": get_action_data(payload)["value"].tame(check_string),
"text": get_action_data(payload)["text"].tame(check_string),
}
return fill_appropriate_message_content(payload, action_type, data)
def get_managed_member_body(payload: WildValue, action_type: str) -> str:
data = {
"member_name": payload["action"]["member"]["fullName"].tame(check_string),
}
return fill_appropriate_message_content(payload, action_type, data)
def get_comment_body(payload: WildValue, action_type: str) -> str:
data = {
"text": get_action_data(payload)["text"].tame(check_string),
}
return fill_appropriate_message_content(payload, action_type, data)
def get_managed_due_date_body(payload: WildValue, action_type: str) -> str:
data = {
"due_date": prettify_date(get_action_data(payload)["card"]["due"].tame(check_string)),
}
return fill_appropriate_message_content(payload, action_type, data)
def get_changed_due_date_body(payload: WildValue, action_type: str) -> str:
data = {
"due_date": prettify_date(get_action_data(payload)["card"]["due"].tame(check_string)),
"old_due_date": prettify_date(get_action_data(payload)["old"]["due"].tame(check_string)),
}
return fill_appropriate_message_content(payload, action_type, data)
def get_managed_desc_body(payload: WildValue, action_type: str) -> str:
data = {
"desc": get_action_data(payload)["card"]["desc"].tame(check_string),
}
return fill_appropriate_message_content(payload, action_type, data)
def get_changed_desc_body(payload: WildValue, action_type: str) -> str:
data = {
"desc": get_action_data(payload)["card"]["desc"].tame(check_string),
"old_desc": get_action_data(payload)["old"]["desc"].tame(check_string),
}
return fill_appropriate_message_content(payload, action_type, data)
def get_body_by_action_type_without_data(payload: WildValue, action_type: str) -> str:
return fill_appropriate_message_content(payload, action_type)
def fill_appropriate_message_content(
payload: WildValue, action_type: str, data: Mapping[str, str] = {}
) -> str:
data = dict(data)
if "card_url_template" not in data:
data["card_url_template"] = get_filled_card_url_template(payload)
message_body = get_message_body(action_type)
return message_body.format(**data)
def get_filled_card_url_template(payload: WildValue) -> str:
return TRELLO_CARD_URL_TEMPLATE.format(
card_name=get_card_name(payload), card_url=get_card_url(payload)
)
def get_card_url(payload: WildValue) -> str:
return "https://trello.com/c/{}".format(
get_action_data(payload)["card"]["shortLink"].tame(check_string)
)
def get_message_body(action_type: str) -> str:
return ACTIONS_TO_MESSAGE_MAPPER[action_type]
def get_card_name(payload: WildValue) -> str:
return get_action_data(payload)["card"]["name"].tame(check_string)
def get_action_data(payload: WildValue) -> WildValue:
return payload["action"]["data"]
ACTIONS_TO_FILL_BODY_MAPPER = {
CREATE: get_body_by_action_type_without_data,
CHANGE_LIST: get_updated_card_body,
CHANGE_NAME: get_renamed_card_body,
SET_DESC: get_managed_desc_body,
CHANGE_DESC: get_changed_desc_body,
REMOVE_DESC: get_body_by_action_type_without_data,
ARCHIVE: get_body_by_action_type_without_data,
REOPEN: get_body_by_action_type_without_data,
SET_DUE_DATE: get_managed_due_date_body,
CHANGE_DUE_DATE: get_changed_due_date_body,
REMOVE_DUE_DATE: get_body_by_action_type_without_data,
ADD_LABEL: get_added_label_body,
REMOVE_LABEL: get_added_label_body,
ADD_MEMBER: get_managed_member_body,
REMOVE_MEMBER: get_managed_member_body,
ADD_ATTACHMENT: get_added_attachment_body,
ADD_CHECKLIST: get_added_checklist_body,
COMMENT: get_comment_body,
UPDATE_CHECK_ITEM_STATE: get_update_check_item_body,
}