实时Notion Todoist双向同步模板
高级
这是一个Sales, Engineering, Design, HR, Product, Marketing领域的自动化工作流,包含 246 个节点。主要使用 If, Set, Code, Form, Html 等节点。 使用Redis的Notion Todoist实时双向同步
前置要求
- •Google 账号和 Gmail API 凭证
- •Redis 服务器连接信息
- •Notion API Key
- •HTTP Webhook 端点(n8n 会自动生成)
- •可能需要目标 API 的认证凭证
使用的节点 (246)
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "k9abwUyVzl7OCsAl",
"meta": {
"instanceId": "fb8bc2e315f7f03c97140b30aa454a27bc7883a19000fa1da6e6b571bf56ad6d"
},
"name": "实时 Notion Todoist 双向同步模板",
"tags": [
{
"id": "RKga6I6NviNI12bx",
"name": "template",
"createdAt": "2024-09-19T19:09:21.997Z",
"updatedAt": "2024-09-19T19:09:21.997Z"
}
],
"nodes": [
{
"id": "5e0488a3-270d-46b3-8b4b-f4ee459a3016",
"name": "获取项目",
"type": "n8n-nodes-base.httpRequest",
"position": [
-700,
260
],
"parameters": {
"url": "https://api.todoist.com/rest/v2/projects",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 4.2
},
{
"id": "e52b0a6d-4697-4c42-91c9-6c786752877c",
"name": "获取部分",
"type": "n8n-nodes-base.httpRequest",
"position": [
180,
260
],
"parameters": {
"url": "https://api.todoist.com/rest/v2/sections",
"options": {},
"sendQuery": true,
"authentication": "predefinedCredentialType",
"queryParameters": {
"parameters": [
{
"name": "project_id",
"value": "={{ $json.project_id }}"
}
]
},
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 4.2
},
{
"id": "8af0c04f-a46e-4fb8-ab5a-09952a3c1360",
"name": "获取 Notion 数据库",
"type": "n8n-nodes-base.notion",
"position": [
-1580,
260
],
"parameters": {
"resource": "database",
"operation": "getAll"
},
"credentials": {
"notionApi": {
"id": "ObmaBA0dJss3JJPv",
"name": "Notion (Test)"
}
},
"typeVersion": 2.2
},
{
"id": "09e43e00-6df6-4844-bda3-f5e8be0aed3a",
"name": "准备下拉菜单",
"type": "n8n-nodes-base.code",
"position": [
-1360,
260
],
"parameters": {
"jsCode": "let dropDownValues = [];\n\nfor (const item of $input.all()) {\n if (item.json.name == \"Inbox\") continue;\n dropDownValues.push({\"option\": item.json.name});\n}\n\nreturn { \"options\": JSON.stringify(dropDownValues) };"
},
"typeVersion": 2
},
{
"id": "426872eb-228a-4b1c-b992-cf17893d29d2",
"name": "准备下拉菜单1",
"type": "n8n-nodes-base.code",
"position": [
-480,
260
],
"parameters": {
"jsCode": "let dropDownValues = [];\n\nfor (const item of $input.all()) {\n if (item.json.name == \"Inbox\") continue;\n dropDownValues.push({\"option\": item.json.name});\n}\n\nreturn { \"options\": JSON.stringify(dropDownValues) };"
},
"typeVersion": 2
},
{
"id": "bebc6443-6187-4b97-82ff-45b6ce75cd7e",
"name": "生成配置",
"type": "n8n-nodes-base.code",
"position": [
400,
260
],
"parameters": {
"jsCode": "let sections = [];\nfor (const item of $('Get sections').all()) {\n sections.push({ id: item.json.id, name: item.json.name }); \n}\n\nreturn { json: \n{\n \"database_id\": $('Get Notion Database ID').first().json.database_id,\n \"project_id\": $('Get Todoist Project ID').first().json.project_id,\n \"sections\": sections\n} \n};"
},
"typeVersion": 2
},
{
"id": "52692c9c-a490-4c2c-8df4-93870bf52ded",
"name": "选择 Notion 数据库",
"type": "n8n-nodes-base.form",
"position": [
-1140,
260
],
"webhookId": "971affb1-c55c-4025-9b4b-743c8f3fcfcf",
"parameters": {
"options": {
"buttonLabel": "Continue"
},
"defineForm": "json",
"jsonOutput": "=[\n {\n \"fieldLabel\": \"Select Notion Database\",\n \"fieldType\": \"dropdown\",\n \"requiredField\": true,\n \"fieldOptions\": {\n \"values\": {{ $json.options }}\n }\n }\n]"
},
"typeVersion": 1
},
{
"id": "7db73660-3f99-42b6-843d-ee4adf758441",
"name": "选择 Todoist 项目",
"type": "n8n-nodes-base.form",
"position": [
-260,
260
],
"webhookId": "971affb1-c55c-4025-9b4b-743c8f3fcfcf",
"parameters": {
"options": {
"buttonLabel": "Continue"
},
"defineForm": "json",
"jsonOutput": "=[\n {\n \"fieldLabel\": \"Select Todoist Project\",\n \"fieldType\": \"dropdown\",\n \"requiredField\": true,\n \"fieldOptions\": {\n \"values\": {{ $json.options }}\n }\n }\n]"
},
"typeVersion": 1
},
{
"id": "3fe51a88-765f-469e-9846-191506b0a894",
"name": "验证安全令牌",
"type": "n8n-nodes-base.if",
"position": [
-580,
980
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "286fafb0-a200-4963-a19f-e63162c484b4",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('OAuth redirect').first().json.query.state }}",
"rightValue": "={{ $json.state }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "7a040b65-5cc3-44d6-8bf6-c6bb7dfe5128",
"name": "生成安全令牌",
"type": "n8n-nodes-base.crypto",
"position": [
-800,
620
],
"parameters": {
"action": "generate",
"dataPropertyName": "state"
},
"typeVersion": 1
},
{
"id": "7033d868-2d83-45bb-a48b-f4fd6eb14e0d",
"name": "存储变量",
"type": "n8n-nodes-base.code",
"position": [
-580,
620
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const staticData = $getWorkflowStaticData('global');\nstaticData.clientID = $('Todoist Webhook Setup Helper').first().json['Client ID'];\nstaticData.clientSecret = $('Todoist Webhook Setup Helper').first().json['Client secret'];\nstaticData.state = $json.state;\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "d538d40a-599b-4255-a5e0-83d9b72f671e",
"name": "获取变量",
"type": "n8n-nodes-base.code",
"position": [
-800,
980
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const staticData = $getWorkflowStaticData('global');\n$input.item.json.clientID = staticData.clientID;\n$input.item.json.clientSecret = staticData.clientSecret;\n$input.item.json.state = staticData.state;\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "7c895d16-e522-4b63-bee6-0cea3e3177dd",
"name": "重定向到认证页面",
"type": "n8n-nodes-base.form",
"position": [
-360,
620
],
"webhookId": "41b4bc95-2938-4a53-a051-7a3079001329",
"parameters": {
"operation": "completion",
"redirectUrl": "=https://todoist.com/oauth/authorize?client_id={{ $json['Client ID'] }}&scope=task:add,data:read_write,data:delete&state={{ $json.state }}",
"respondWith": "redirect"
},
"typeVersion": 1
},
{
"id": "b72c6f9f-f821-4859-b75d-192541ce0d34",
"name": "OAuth 重定向",
"type": "n8n-nodes-base.webhook",
"position": [
-1020,
980
],
"webhookId": "7aee8b09-29e3-4e12-9b66-c6e8ab080bf7",
"parameters": {
"path": "7aee8b09-29e3-4e12-9b66-c6e8ab080bf7",
"options": {},
"httpMethod": [
"GET"
],
"responseMode": "responseNode",
"multipleMethods": true
},
"typeVersion": 2
},
{
"id": "d0adcb66-4817-44f9-ad7e-320cff86aefe",
"name": "交换令牌",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"position": [
-360,
880
],
"parameters": {
"url": "https://todoist.com/oauth/access_token",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "form-urlencoded",
"bodyParameters": {
"parameters": [
{
"name": "client_id",
"value": "={{ $json.clientID }}"
},
{
"name": "client_secret",
"value": "={{ $json.clientSecret }}"
},
{
"name": "code",
"value": "={{ $('OAuth redirect').first().json.query.code }}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "4fda8614-308c-4d66-ac7d-c2491ab5a86a",
"name": "响应成功",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
-140,
780
],
"parameters": {
"options": {},
"respondWith": "text",
"responseBody": "=Developer App activated successfully. The window can be closed now."
},
"typeVersion": 1.1
},
{
"id": "347fb99f-861f-483a-bc39-7341fe41439f",
"name": "响应错误",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
-140,
980
],
"parameters": {
"options": {},
"respondWith": "text",
"responseBody": "Something went wrong."
},
"typeVersion": 1.1
},
{
"id": "754727b6-1122-46b2-a956-79ed277c8b32",
"name": "获取 Notion 数据库 ID",
"type": "n8n-nodes-base.code",
"position": [
-920,
260
],
"parameters": {
"jsCode": "let database_id = null;\n\nfor (const item of $('Get Notion Databases').all()) {\n if (item.json.name == $('Choose Notion Database').first().json['Select Notion Database']) {\n database_id = item.json.id;\n }\n}\n\nreturn { json: { database_id: database_id } };"
},
"typeVersion": 2
},
{
"id": "565f4144-549e-4adc-b51b-2d87767c3dc3",
"name": "获取 Todoist 项目 ID",
"type": "n8n-nodes-base.code",
"position": [
-40,
260
],
"parameters": {
"jsCode": "let project_id = null;\n\nfor (const item of $('Get projects').all()) {\n if (item.json.name == $('Choose Todoist Project').first().json['Select Todoist Project']) {\n project_id = item.json.id;\n }\n}\n\nreturn { json: { project_id: project_id } };"
},
"typeVersion": 2
},
{
"id": "6f79cd9e-493a-46b2-a3fb-853852a299be",
"name": "Notion-Todoist 同步设置助手",
"type": "n8n-nodes-base.formTrigger",
"position": [
-1800,
260
],
"webhookId": "7edf515f-bd69-4875-a5d8-eca8b4679896",
"parameters": {
"options": {
"buttonLabel": "Begin"
},
"formTitle": "Notion-Todoist Sync Setup Helper",
"formFields": {
"values": [
{
"fieldLabel": " ",
"placeholder": "Click \"Begin\" to continue"
}
]
},
"formDescription": "This tool gathers all necessary information and builds a JSON config which is needed as globals in the sync workflows."
},
"typeVersion": 2.2
},
{
"id": "684e5eea-8148-4ddf-8972-dab890083ce2",
"name": "Todoist Webhook 设置助手",
"type": "n8n-nodes-base.formTrigger",
"position": [
-1020,
620
],
"webhookId": "feb96ecb-a7c0-41d3-9368-2299cdce492e",
"parameters": {
"options": {},
"formTitle": "Todoist Webhook Setup Helper",
"formFields": {
"values": [
{
"fieldLabel": "Client ID",
"requiredField": true
},
{
"fieldLabel": "Client secret",
"requiredField": true
}
]
},
"responseMode": "lastNode",
"formDescription": "This tool helps activating the Todoist Webhook for a registered Developer App by connecting to the App vie OAuth."
},
"typeVersion": 2.2
},
{
"id": "05f938b9-f2b3-4535-8fea-5c378bbf7944",
"name": "返回配置 JSON",
"type": "n8n-nodes-base.form",
"position": [
620,
260
],
"webhookId": "739f32d1-356d-429d-8698-a166485cc36f",
"parameters": {
"options": {},
"operation": "completion",
"completionTitle": "Copy this to the Globals Nodes",
"completionMessage": "={{ $json.toJsonString() }}"
},
"typeVersion": 1
},
{
"id": "e5101689-8b9d-4896-aad6-b39ea2ec654d",
"name": "Todoist",
"type": "n8n-nodes-base.todoist",
"position": [
2980,
600
],
"parameters": {
"filters": {
"projectId": "={{ $('Globals').first().json.project_id }}"
},
"operation": "getAll",
"returnAll": true
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 2.1,
"waitBetweenTries": 5000
},
{
"id": "c588831b-99c1-4c4b-9d33-4cb14687d896",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"position": [
2980,
400
],
"parameters": {
"simple": false,
"options": {},
"resource": "databasePage",
"operation": "getAll",
"returnAll": true,
"databaseId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Globals').first().json.database_id }}"
},
"filterJson": "={\n \"and\": [\n {\n \"and\": [\n {\n \"property\": \"Status\",\n \"status\": {\n \"does_not_equal\": \"Done\"\n }\n },\n {\n \"property\": \"Status\",\n \"status\": {\n \"does_not_equal\": \"Obsolete\"\n }\n }\n ]\n },\n {\n \"or\": [\n {\n \"property\": \"Focus\",\n \"checkbox\": {\n \"equals\": true\n }\n },\n {\n \"property\": \"Due\",\n \"date\": {\n \"is_not_empty\": true\n }\n }\n ]\n }\n ]\n}",
"filterType": "json"
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.2,
"waitBetweenTries": 5000
},
{
"id": "72c05668-ed66-4a17-abd1-d848890d23d2",
"name": "比较数据集",
"type": "n8n-nodes-base.compareDatasets",
"position": [
3420,
500
],
"parameters": {
"options": {},
"resolve": "mix",
"mergeByFields": {
"values": [
{
"field1": "id",
"field2": "id"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "4fe3e94e-cd61-49e3-a37c-1a4a45ae990d",
"name": "在 Notion 中存在/已完成",
"type": "n8n-nodes-base.if",
"position": [
3880,
1220
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "455d4c70-c774-44d4-b3ba-78754073c2db",
"operator": {
"type": "object",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "e6917957-0511-489e-aef1-268d54d1ae1b",
"name": "获取 Todoist 任务",
"type": "n8n-nodes-base.todoist",
"onError": "continueErrorOutput",
"maxTries": 2,
"position": [
4540,
-280
],
"parameters": {
"taskId": "={{ $json.id }}",
"operation": "get"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 2.1,
"waitBetweenTries": 2000
},
{
"id": "7ee99bf2-7316-4106-820b-71fb7dc62a69",
"name": "如果 Todoist ID 存在",
"type": "n8n-nodes-base.if",
"position": [
4100,
-180
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "71e9af47-a275-4dd4-a984-0b7ba2cb8733",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.id }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "c54b47a5-8c6e-4656-841b-12e64d0dc3af",
"name": "存储 Todoist ID",
"type": "n8n-nodes-base.notion",
"position": [
5200,
-180
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Notion').item.json.id }}"
},
"options": {},
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "Todoist ID|rich_text",
"textContent": "={{ $json.id }}"
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.2,
"waitBetweenTries": 5000
},
{
"id": "29ccfeb0-f3c1-4141-a7ba-211ae6195e63",
"name": "在 Todoist 中标记为未完成",
"type": "n8n-nodes-base.todoist",
"position": [
4980,
-480
],
"parameters": {
"taskId": "={{ $json.id }}",
"operation": "reopen"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 2.1,
"waitBetweenTries": 5000
},
{
"id": "7322844b-0f2b-4f33-9aa6-117845bf90e5",
"name": "在 Todoist 中标记为已完成",
"type": "n8n-nodes-base.todoist",
"position": [
4980,
1120
],
"parameters": {
"taskId": "={{ $('Todoist').item.json.id }}",
"operation": "close"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 2.1
},
{
"id": "44d117ae-f3a1-47ae-8e6d-c668e721add7",
"name": "在 Todoist 中删除任务",
"type": "n8n-nodes-base.todoist",
"position": [
4980,
1320
],
"parameters": {
"taskId": "={{ $('Todoist').item.json.id }}",
"operation": "delete"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 2.1
},
{
"id": "57176531-6186-40d4-9306-65eea976583f",
"name": "遍历项目",
"type": "n8n-nodes-base.splitInBatches",
"position": [
3660,
1320
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "25bc1156-cfa2-4633-a635-f7bb2f4f25f2",
"name": "获取 Notion 任务",
"type": "n8n-nodes-base.notion",
"position": [
3880,
1420
],
"parameters": {
"limit": 1,
"simple": false,
"filters": {
"conditions": [
{
"key": "Todoist ID|rich_text",
"condition": "equals",
"richTextValue": "={{ $('Todoist').item.json.id }}"
}
]
},
"options": {},
"resource": "databasePage",
"operation": "getAll",
"databaseId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Globals').first().json.database_id }}"
},
"filterType": "manual"
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.2,
"alwaysOutputData": true,
"waitBetweenTries": 5000
},
{
"id": "01ec4ae8-22df-45ef-8593-4f16e3571c2e",
"name": "在 Todoist 中更新任务",
"type": "n8n-nodes-base.httpRequest",
"position": [
3880,
520
],
"parameters": {
"url": "=https://api.todoist.com/rest/v2/tasks/{{ $json.id }}",
"method": "POST",
"options": {},
"jsonQuery": "={{ $json.toJsonString() }}",
"sendQuery": true,
"specifyQuery": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 4.2,
"waitBetweenTries": 5000
},
{
"id": "4961331b-7cab-4c5e-b700-cb35e9258426",
"name": "选择 Todoist 字段",
"type": "n8n-nodes-base.set",
"position": [
3200,
600
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "18fbd156-06db-4991-bee7-14d8bde6bbf5",
"name": "id",
"type": "string",
"value": "={{ $json.id }}"
},
{
"id": "a0c01dca-6cfa-4461-8c93-fe3f2191d624",
"name": "content",
"type": "string",
"value": "={{ $json.content }}"
},
{
"id": "a9bae78e-1a70-4b8e-be0c-ce74e546b0de",
"name": "priority",
"type": "string",
"value": "={{ $json.priority }}"
},
{
"id": "6ed4364e-6f62-464d-a841-e520b5bf3d96",
"name": "due_datetime",
"type": "string",
"value": "={{ $if($json.due.datetime !== undefined, $json.due.datetime.toDateTime().toISO(), $json.due.date) }}"
},
{
"id": "23035cde-aea0-459d-9886-06cd7f20dd61",
"name": "description",
"type": "string",
"value": "={{ $json.description }}"
},
{
"id": "6795fa58-4d52-4e88-8a1c-10e574ed2c3d",
"name": "section_id",
"type": "string",
"value": "={{ $json.section_id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "283370f2-93ff-4272-82bf-ad45c54e47f7",
"name": "将 Notion 映射到 Todoist",
"type": "n8n-nodes-base.code",
"position": [
3200,
400
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const globals = $('Globals').first().json;\nconst properties = $json.properties;\n\nlet output = {};\n\noutput.id = properties['Todoist ID'].rich_text.length > 0 ? \n properties['Todoist ID'].rich_text[0].text.content : \n null;\n\noutput.content = properties['Name'].title.length > 0 ? properties['Name'].title[0].text.content : '[empty]';\n\noutput.description = \"[↗ Open in Notion](\" + $json.url + \")\"\n\n// Map priority\nif (properties['Priority'].select === null) {\n output.priority = \"1\"; // P4\n} else {\n output.priority = {\n \"do first\": \"4\", // P1\n \"urgent\": \"3\", // P2\n \"important\": \"2\" // P3\n }[properties['Priority'].select.name] ?? \"1\"; // P4\n}\n\n// Map section\nconst statusName = properties['Status'].status.name;\noutput.section_id = null;\nif (!['Done', 'Obsolete'].includes(statusName)) {\n const sectionMap = Object.fromEntries(\n globals.sections.map(section => [section.name, section.id])\n );\n if (!sectionMap.hasOwnProperty(statusName)) {\n throw new Error(\"No Todoist section found for status '\" + statusName + \"'\");\n }\n output.section_id = sectionMap[statusName];\n}\n\n// Set UTC if time is set\noutput.due_datetime = null;\nif (properties['Due'].date !== null) {\n output.due_datetime = properties.Due.date.start;\n if (properties.Due.date.start.length > 10) {\n output.due_datetime = new Date(properties.Due.date.start).toISOString();\n }\n}\n\nreturn { json: output };"
},
"typeVersion": 2
},
{
"id": "ca33c90f-3204-45e1-9d66-20e7e740a4d7",
"name": "在关闭前在 Todoist 中更新任务",
"type": "n8n-nodes-base.httpRequest",
"position": [
4540,
1120
],
"parameters": {
"url": "=https://api.todoist.com/rest/v2/tasks/{{ $json.id }}",
"method": "POST",
"options": {},
"jsonQuery": "={{ $json.toJsonString() }}",
"sendQuery": true,
"specifyQuery": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 4.2,
"waitBetweenTries": 5000
},
{
"id": "dd79e458-8c60-4de8-bdde-ec0009b2d4dc",
"name": "将 Notion 映射到 Todoist1",
"type": "n8n-nodes-base.code",
"position": [
4100,
1120
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const globals = $('Globals').first().json;\nconst properties = $json.properties;\n\nlet output = {};\n\noutput.id = properties['Todoist ID'].rich_text.length > 0 ? \n properties['Todoist ID'].rich_text[0].text.content : \n null;\n\noutput.content = properties['Name'].title.length > 0 ? properties['Name'].title[0].text.content : '[empty]';\n\noutput.description = \"[↗ Open in Notion](\" + $json.url + \")\"\n\n// Map priority\nif (properties['Priority'].select === null) {\n output.priority = \"1\"; // P4\n} else {\n output.priority = {\n \"do first\": \"4\", // P1\n \"urgent\": \"3\", // P2\n \"important\": \"2\" // P3\n }[properties['Priority'].select.name] ?? \"1\"; // P4\n}\n\n// Map section\nconst statusName = properties['Status'].status.name;\noutput.section_id = null;\nif (!['Done', 'Obsolete'].includes(statusName)) {\n const sectionMap = Object.fromEntries(\n globals.sections.map(section => [section.name, section.id])\n );\n if (!sectionMap.hasOwnProperty(statusName)) {\n throw new Error(\"No Todoist section found for status '\" + statusName + \"'\");\n }\n output.section_id = sectionMap[statusName];\n}\n\n// Set UTC if time is set\noutput.due_datetime = null;\nif (properties['Due'].date !== null) {\n output.due_datetime = properties.Due.date.start;\n if (properties.Due.date.start.length > 10) {\n output.due_datetime = new Date(properties.Due.date.start).toISOString();\n }\n}\n\nreturn { json: output };"
},
"typeVersion": 2
},
{
"id": "7dec20dc-c1a9-4fd7-85b0-c3425a73ff66",
"name": "处理空日期",
"type": "n8n-nodes-base.code",
"position": [
3660,
520
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// API requires due_string instead of due_datetime when date/time is removed\nif ($json.due_datetime === null) {\n delete $json.due_datetime;\n $input.item.json.due_string = \"no date\";\n}\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "b1bf65ad-b9b1-49ba-a892-678966c520a3",
"name": "处理空日期1",
"type": "n8n-nodes-base.code",
"position": [
4320,
1120
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// API requires due_string instead of due_datetime when date/time is removed\nif ($json.due_datetime === null) {\n delete $json.due_datetime;\n $input.item.json.due_string = \"no date\";\n}\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "8dbd2213-4a7f-44ef-9276-67672c6bb1ec",
"name": "处理空日期2",
"type": "n8n-nodes-base.code",
"position": [
3660,
-180
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// API requires due_string instead of due_datetime when date/time is removed\nif ($json.due_datetime === null) {\n delete $json.due_datetime;\n $input.item.json.due_string = \"no date\";\n}\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "4a538bb6-80f6-4920-b96a-0756e0dc5449",
"name": "在 Todoist 中创建任务",
"type": "n8n-nodes-base.httpRequest",
"position": [
4980,
-280
],
"parameters": {
"url": "=https://api.todoist.com/rest/v2/tasks",
"method": "POST",
"options": {},
"jsonQuery": "={{ $('If Todoist ID exists').item.json.toJsonString() }}",
"sendQuery": true,
"specifyQuery": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 4.2,
"waitBetweenTries": 5000
},
{
"id": "afdad4ec-e8bc-48c0-b80d-e1f4066871c2",
"name": "添加项目 ID",
"type": "n8n-nodes-base.set",
"position": [
3880,
-180
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "38aef493-8125-46fd-82bc-93c6d7d6f66e",
"name": "project_id",
"type": "string",
"value": "={{ $('Globals').first().json.project_id }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "fb2f5730-5a69-4bed-983f-9597da7a0065",
"name": "锁定 Todoist ID",
"type": "n8n-nodes-base.redis",
"position": [
5200,
-380
],
"parameters": {
"key": "=lock_{{ $('Compare Datasets').item.json.id }}",
"ttl": 15,
"value": "={{ $now.toISO() }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "8f4e1ffd-a46c-4161-b6ed-89c0e78c47b1",
"name": "生成 UUID",
"type": "n8n-nodes-base.crypto",
"position": [
6080,
-80
],
"parameters": {
"action": "generate",
"dataPropertyName": "uuid"
},
"typeVersion": 1
},
{
"id": "28b4e10e-a9cb-40c0-9a2c-d45303f4b64e",
"name": "更新部分(同步 API)",
"type": "n8n-nodes-base.httpRequest",
"position": [
6740,
-80
],
"parameters": {
"url": "https://api.todoist.com/sync/v9/sync",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "form-urlencoded",
"authentication": "predefinedCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "commands",
"value": "={{ $json.commands.toJsonString() }}"
}
]
},
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 4.2,
"waitBetweenTries": 5000
},
{
"id": "82cabb00-43bf-404a-9b3b-0a95bee0ba26",
"name": "状态已更改",
"type": "n8n-nodes-base.filter",
"position": [
4100,
520
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "or",
"conditions": [
{
"id": "e3225b20-c6b6-479b-a0b5-dc9725197521",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist').item.json.section_id }}",
"rightValue": "={{ $json.section_id }}"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "d4242f35-f49f-4b3e-a99d-dacc90ea912a",
"name": "聚合",
"type": "n8n-nodes-base.aggregate",
"position": [
6520,
-80
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "commands"
},
"typeVersion": 1
},
{
"id": "4f6d649b-94c7-4826-950c-1f1632037d56",
"name": "准备批处理项目",
"type": "n8n-nodes-base.set",
"position": [
6300,
-80
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={\n \"type\": \"item_move\",\n \"uuid\": \"{{ $json.uuid }}\",\n \"args\": {\n \"id\": \"{{ $('Merge').item.json.item.id }}\",\n \"section_id\": \"{{ $json.item.section_id }}\"\n }\n }\n"
},
"typeVersion": 3.4
},
{
"id": "4d5993d2-13af-47e3-b17a-9c19db7d0489",
"name": "遍历项目1",
"type": "n8n-nodes-base.splitInBatches",
"position": [
5860,
-180
],
"parameters": {
"options": {},
"batchSize": 100
},
"typeVersion": 3
},
{
"id": "5235aab4-70f2-4d21-b9d3-a11ab817dd13",
"name": "状态为已完成",
"type": "n8n-nodes-base.if",
"position": [
4760,
1220
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a4a40d08-7929-46da-8369-c6d33b14e4f3",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Exists/Completed in Notion').item.json.properties['Status'].status.name }}",
"rightValue": "Done"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "608efb15-5e9b-49c2-96ea-ad35445c82bd",
"name": "锁定 Todoist ID1",
"type": "n8n-nodes-base.redis",
"position": [
4760,
1020
],
"parameters": {
"key": "=lock_{{ $('Compare Datasets').item.json.id }}",
"ttl": 15,
"value": "={{ $now.toISO() }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "2d571e77-6162-4f5c-a147-99878d5b559a",
"name": "锁定 Todoist ID2",
"type": "n8n-nodes-base.redis",
"position": [
4100,
320
],
"parameters": {
"key": "=lock_{{ $('Compare Datasets').item.json.id }}",
"ttl": 15,
"value": "={{ $now.toISO() }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "cee5f777-4763-490e-a89f-8ba715cfd5b8",
"name": "在 Todoist 中更新任务",
"type": "n8n-nodes-base.httpRequest",
"position": [
5200,
-580
],
"parameters": {
"url": "=https://api.todoist.com/rest/v2/tasks/{{ $('If Todoist ID exists').item.json.id }}",
"method": "POST",
"options": {},
"jsonQuery": "={{ $('If Todoist ID exists').item.json.toJsonString() }}",
"sendQuery": true,
"specifyQuery": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 4.2,
"waitBetweenTries": 5000
},
{
"id": "0b2c57af-c3b6-4bed-8e4c-ec2f84576a47",
"name": "获取映射项",
"type": "n8n-nodes-base.set",
"position": [
5420,
-580
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "7419f1e6-2978-4e4d-9147-61dcb693260b",
"name": "item",
"type": "object",
"value": "={{ $('Map Notion to Todoist').item.json }}"
},
{
"id": "7684427f-3267-4092-a5d3-0d13fe4e2ebb",
"name": "action",
"type": "string",
"value": "reopened"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "e6d335b2-e848-4802-9809-15ea9eb3fd29",
"name": "获取映射项1",
"type": "n8n-nodes-base.set",
"position": [
5000,
520
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "7679df9c-ccdb-425c-ad60-c8bd85379e87",
"name": "item",
"type": "object",
"value": "={{ $('Map Notion to Todoist').item.json }}"
},
{
"id": "d6bc106f-ed01-49be-9c27-13429684d573",
"name": "action",
"type": "string",
"value": "updated_status"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "ce49a060-0a2b-491f-b6f5-efa49b54e8d3",
"name": "计划触发器",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
2760,
500
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 6
}
]
}
},
"typeVersion": 1.2
},
{
"id": "4402861c-7b7e-47c4-9e5c-91589262c556",
"name": "准备摘要数据",
"type": "n8n-nodes-base.set",
"position": [
4100,
720
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "2d823132-c41f-4685-9443-8ee143809ccb",
"name": "item",
"type": "object",
"value": "={{ $('Map Notion to Todoist').item.json }}"
},
{
"id": "45b155c2-4254-45b3-bdd4-127ee356f8d4",
"name": "action",
"type": "string",
"value": "updated"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "3a26cfc0-3e4e-4e14-80ef-2ad00236ae38",
"name": "准备摘要数据1",
"type": "n8n-nodes-base.set",
"position": [
5200,
1120
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "9614bc29-d8fa-4531-a8fc-5974f58e0f7a",
"name": "item",
"type": "object",
"value": "={{ $('Todoist').item.json }}"
},
{
"id": "6c0ffc23-a595-49ce-82b9-b02c3f9766d8",
"name": "action",
"type": "string",
"value": "completed"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a3e4e83d-6789-4c55-9699-41da5eeac9e4",
"name": "合并",
"type": "n8n-nodes-base.merge",
"position": [
5640,
-180
],
"parameters": {},
"typeVersion": 3
},
{
"id": "9b93f9ff-0c11-49eb-8425-7d3dc71eaa0b",
"name": "准备摘要数据2",
"type": "n8n-nodes-base.set",
"position": [
5200,
1320
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "833becab-363d-48f1-8911-12abd34f16a0",
"name": "item",
"type": "object",
"value": "={{ $('Todoist').item.json }}"
},
{
"id": "306997a6-442e-44cc-ace1-c9bfe83ef28e",
"name": "action",
"type": "string",
"value": "deleted"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "f6ea7bc7-093b-47ea-8bb0-48a487566218",
"name": "过滤掉状态更改",
"type": "n8n-nodes-base.filter",
"position": [
6100,
720
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "db22023d-a3ff-4891-8c14-5316be7e5334",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $json.action }}",
"rightValue": "updated_status"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "63990a2a-7380-4799-9037-bd98ed779ddd",
"name": "将 Todoist 映射到 Notion",
"type": "n8n-nodes-base.code",
"position": [
6320,
720
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const globals = $('Globals').first().json;\nlet eventData = $json.item;\n\nlet output = {};\n\noutput.name = eventData.content\n\n// Map priority\noutput.priority = {\n \"4\": \"do first\", // P1\n \"3\": \"urgent\", // P2\n \"2\": \"important\" // P3\n}[eventData.priority] ?? \"\"; // P4\n\n// Map section\nconst sectionId = eventData.section_id;\nconst sectionMap = Object.fromEntries(\n globals.sections.map(section => [section.id, section.name])\n);\n\noutput.status = \"Backlog\";\nif (sectionId !== null) {\n if (!sectionMap.hasOwnProperty(sectionId)) {\n //throw new Error(\"No status name found for section ID '\" + sectionId + \"'\");\n }\n output.status = sectionMap[sectionId];\n}\n\n// Since there is no Done section in Todoist, override status if completed\nif (eventData.is_completed) {\n output.status = \"Done\";\n}\n\nif ($json.action == \"deleted\") {\n output.status = \"Obsolete\";\n}\n\n// Format due date\noutput.due = eventData.due ? eventData.due.date : eventData.due_datetime || \"\"\n\nreturn { json: output };"
},
"typeVersion": 2
},
{
"id": "a5f3a1ed-1a97-441e-afbc-604b5011928e",
"name": "映射摘要字段",
"type": "n8n-nodes-base.set",
"position": [
6540,
720
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "e0fcdd39-1bd8-44e7-b84f-0b6ba834fb46",
"name": "name",
"type": "string",
"value": "=<a href=\"{{ $('Merge summary').item.json.item.description.extractUrl().replaceAll(')', '') }}\">{{ $json.name }}</a>"
},
{
"id": "f3bdfee8-1671-4e80-b8da-febe6f463756",
"name": "priority",
"type": "string",
"value": "={{ $json.priority }}"
},
{
"id": "060bfe55-0dfa-406b-99cc-ad8592a209dc",
"name": "status",
"type": "string",
"value": "={{ $json.status }}"
},
{
"id": "7b0ee321-0631-4cde-b1a7-b3547aff3561",
"name": "due",
"type": "string",
"value": "={{ $if(!$json.due, '', $if($json.due.length > 10, $json.due.toDateTime().toLocaleString({ dateStyle: 'medium', timeStyle: 'short' }), $json.due.toDateTime().toLocaleString({ dateStyle: 'medium' }))) }}"
},
{
"id": "11184572-1126-4b21-aa72-1540969200f3",
"name": "action",
"type": "string",
"value": "={{ $('Merge summary').item.json.action }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "50a844e8-5c5c-43e6-b774-993af828cb11",
"name": "转换为 HTML 表格",
"type": "n8n-nodes-base.html",
"position": [
6760,
720
],
"parameters": {
"options": {
"capitalize": true
},
"operation": "convertToHtmlTable"
},
"typeVersion": 1.2
},
{
"id": "53e56e25-4931-4a1a-b70f-1eda8520c649",
"name": "生成邮件正文",
"type": "n8n-nodes-base.html",
"position": [
6980,
720
],
"parameters": {
"html": "<!DOCTYPE html>\n<html>\n<head>\n <style>\n body {\n font-family: Arial, sans-serif;\n line-height: 1.6;\n color: #333;\n margin: 0;\n padding: 20px;\n }\n .container {\n max-width: 800px;\n margin: 0 auto;\n background-color: #ffffff;\n padding: 20px;\n border-radius: 5px;\n box-shadow: 0 2px 5px rgba(0,0,0,0.1);\n }\n h1 {\n color: #2c3e50;\n text-align: center;\n margin-bottom: 30px;\n padding-bottom: 10px;\n border-bottom: 2px solid #3498db;\n }\n table {\n width: 100%;\n border-collapse: collapse;\n margin-top: 20px;\n }\n th {\n background-color: #3498db;\n color: white;\n padding: 12px;\n text-align: left;\n }\n td {\n padding: 10px;\n border-bottom: 1px solid #ddd;\n }\n tr:nth-child(even) {\n background-color: #f9f9f9;\n }\n tr:hover {\n background-color: #f5f5f5;\n }\n a {\n color: #333;\n text-decoration: underline;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <h1>Notion-Todoist Sync Report</h1>\n <p>The following changes have been applied to Todoist:</p>\n {{ $json.table }}\n </div>\n</body>\n</html>\n"
},
"typeVersion": 1.2
},
{
"id": "aa0507f8-e5b4-45b1-89bb-6ced3dd9b27e",
"name": "获取映射项2",
"type": "n8n-nodes-base.set",
"position": [
5420,
-180
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "7679df9c-ccdb-425c-ad60-c8bd85379e87",
"name": "item",
"type": "object",
"value": "={{ $('Map Notion to Todoist').item.json }}"
},
{
"id": "d6bc106f-ed01-49be-9c27-13429684d573",
"name": "action",
"type": "string",
"value": "created"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "60826717-078e-4509-9dbd-8b3896078a9c",
"name": "Gmail",
"type": "n8n-nodes-base.gmail",
"position": [
7200,
720
],
"webhookId": "5af8a60e-a295-40c1-8f03-0a920ba1de17",
"parameters": {
"message": "={{ $json.html }}",
"options": {
"appendAttribution": false
},
"subject": "Notion-Todoist Sync Report"
},
"credentials": {
"gmailOAuth2": {
"id": "TWM2BLjDHQtGAFQn",
"name": "Gmail (octionicsolutions)"
}
},
"typeVersion": 2.1
},
{
"id": "29ea508e-eba9-40a3-9840-d70d4ec51ddb",
"name": "合并摘要",
"type": "n8n-nodes-base.merge",
"position": [
5880,
680
],
"parameters": {
"numberInputs": 5
},
"typeVersion": 3
},
{
"id": "9773c5d4-a910-4b26-858d-2abf30c9602f",
"name": "便签8",
"type": "n8n-nodes-base.stickyNote",
"position": [
3140,
260
],
"parameters": {
"color": 7,
"width": 220,
"height": 300,
"content": "## 保持同步"
},
"typeVersion": 1
},
{
"id": "e4224f99-4063-49a2-a5cd-c2515b117bc5",
"name": "便签 9",
"type": "n8n-nodes-base.stickyNote",
"position": [
4040,
980
],
"parameters": {
"color": 7,
"width": 220,
"height": 300,
"content": "## 保持同步"
},
"typeVersion": 1
},
{
"id": "3d5662ad-81a4-4a02-abe3-38d59a08bf9f",
"name": "便签10",
"type": "n8n-nodes-base.stickyNote",
"position": [
2920,
20
],
"parameters": {
"width": 220,
"height": 300,
"content": "## 设置全局变量"
},
"typeVersion": 1
},
{
"id": "e78779d9-32f9-467f-ae2c-ad0cec69f506",
"name": "全局变量",
"type": "n8n-nodes-base.set",
"position": [
2980,
160
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "{\n \"database_id\": \"165beaa8f4448063be17d105a9898961\",\n \"project_id\": \"2327925123\",\n \"sections\": [\n {\"id\": \"177308158\", \"name\": \"Backlog\"},\n {\"id\": \"175778849\", \"name\": \"In progress\"},\n {\"id\": \"175778857\", \"name\": \"On hold\"}\n ]\n}"
},
"executeOnce": false,
"typeVersion": 3.4
},
{
"id": "f01855a1-e9bf-469a-ad68-bef8d0286e3a",
"name": "便签14",
"type": "n8n-nodes-base.stickyNote",
"position": [
5800,
-260
],
"parameters": {
"color": 7,
"width": 1100,
"height": 380,
"content": "在完全同步模式下使用同步 API 更新 Todoist 中每个任务的部分。如果需要,使用批处理并分成多个批次,因为在 15 分钟内有 100 次完全同步的请求限制。"
},
"typeVersion": 1
},
{
"id": "2ec59ee5-5ffb-4b57-9a2d-0f7d10a580ba",
"name": "便签15",
"type": "n8n-nodes-base.stickyNote",
"position": [
2640,
-740
],
"parameters": {
"color": 5,
"width": 4760,
"height": 2420,
"content": "# 从 Notion 到 Todoist 的单向同步"
},
"typeVersion": 1
},
{
"id": "a6f87f39-6885-4fff-99f0-1f29f0db25b7",
"name": "便签16",
"type": "n8n-nodes-base.stickyNote",
"position": [
2920,
320
],
"parameters": {
"color": 7,
"width": 220,
"height": 440,
"content": "从 Notion 和 Todoist 获取打开/未完成的任务"
},
"typeVersion": 1
},
{
"id": "fcffe10e-4ab3-4172-8be3-d060a4070922",
"name": "便签17",
"type": "n8n-nodes-base.stickyNote",
"position": [
6040,
660
],
"parameters": {
"color": 7,
"width": 1100,
"height": 220,
"content": "生成包含此工作流所做所有更改的电子邮件报告,因为 Todoist 中的更改正在被覆盖。"
},
"typeVersion": 1
},
{
"id": "af70e38e-eca7-4eba-9125-69a8ff222ac0",
"name": "执行工作流触发器",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"disabled": true,
"position": [
2800,
3540
],
"parameters": {},
"typeVersion": 1
},
{
"id": "8e09225d-5d7b-468e-a8f6-c0836469799a",
"name": "存在差异1",
"type": "n8n-nodes-base.filter",
"position": [
6980,
2720
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "db8871d6-7f3f-468e-9a6e-3975c9f003f5",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Todoist to Notion1').item.json.name }}",
"rightValue": "={{ $json.name }}"
},
{
"id": "df493beb-52a8-47f7-90c6-fb5786b1deed",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Todoist to Notion1').item.json.priority }}",
"rightValue": "={{ $json.property_priority }}"
},
{
"id": "8942df51-6aa3-46d6-91f3-c6949bfd2361",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Todoist to Notion1').item.json.status }}",
"rightValue": "={{ $json.property_status }}"
},
{
"id": "5484e1f8-4fbc-4c63-829a-4b45b9090702",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $if($('Map Todoist to Notion1').item.json.due !== null, $('Map Todoist to Notion1').item.json.due.toDateTime().toISO(), null) }}",
"rightValue": "={{ $if($json.property_due !== null, $json.property_due.start.toDateTime().toISO(), null) }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "d30a9839-6ff4-49d3-8413-4c4786c47a0a",
"name": "需要完成更改1",
"type": "n8n-nodes-base.filter",
"position": [
6980,
3100
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b9e134c4-66a7-4a83-8372-282c19fdc98e",
"operator": {
"type": "boolean",
"operation": "notEquals"
},
"leftValue": "={{ $('Get Todoist Task1').item.json.is_completed }}",
"rightValue": "={{ $if($json.property_status == \"Done\", true, false) }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "38edc95d-ae92-46bb-9cc5-338588d720af",
"name": "在 Notion 中创建任务",
"type": "n8n-nodes-base.notion",
"position": [
7420,
2220
],
"parameters": {
"title": "={{ $('Map Todoist to Notion1').item.json.name }}",
"options": {},
"resource": "databasePage",
"databaseId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Globals1').first().json.database_id }}"
},
"propertiesUi": {
"propertyValues": [
{
"key": "Todoist ID|rich_text",
"textContent": "={{ $('Map Todoist to Notion1').item.json.todoist_id }}"
},
{
"key": "Priority|select",
"selectValue": "={{ $('Map Todoist to Notion1').item.json.priority }}"
},
{
"key": "Status|status",
"statusValue": "={{ $('Map Todoist to Notion1').item.json.status }}"
},
{
"key": "Due|date",
"date": "={{ $('Map Todoist to Notion1').item.json.due || '' }}",
"includeTime": "={{ $('Map Todoist to Notion1').item.json.due !== null && $('Map Todoist to Notion1').item.json.due.length > 10 }}"
},
{
"key": "Focus|checkbox",
"checkboxValue": true
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.2,
"waitBetweenTries": 3000
},
{
"id": "fd60280f-aef7-43f6-835c-0086adad3952",
"name": "在 Notion 中更新任务",
"type": "n8n-nodes-base.notion",
"position": [
7640,
2720
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {},
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "Name|title",
"title": "={{ $('Map Todoist to Notion1').item.json.name }}"
},
{
"key": "Priority|select",
"selectValue": "={{ $('Map Todoist to Notion1').item.json.priority }}"
},
{
"key": "Status|status",
"statusValue": "={{ $('Map Todoist to Notion1').item.json.status }}"
},
{
"key": "Due|date",
"date": "={{ $ifEmpty($('Map Todoist to Notion1').item.json.due, \"\") }}",
"includeTime": "={{ $('Map Todoist to Notion1').item.json.due !== null && $('Map Todoist to Notion1').item.json.due.length > 10 }}"
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.2,
"waitBetweenTries": 3000
},
{
"id": "5aecc1ce-f83b-43a2-b2a3-44b00b59f26b",
"name": "在 Notion 中标记为已完成",
"type": "n8n-nodes-base.notion",
"position": [
7640,
3380
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {},
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "Status|status",
"statusValue": "Done"
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.2,
"waitBetweenTries": 3000
},
{
"id": "fb2c809e-50ad-4627-ad7e-4919cf30b845",
"name": "在 Notion 中标记为已废弃",
"type": "n8n-nodes-base.notion",
"position": [
7640,
3700
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {},
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "Status|status",
"statusValue": "Obsolete"
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.2,
"waitBetweenTries": 3000
},
{
"id": "6cb3caa1-45f5-4675-ae06-5c15163ccac1",
"name": "在 Notion 中标记为进行中",
"type": "n8n-nodes-base.notion",
"position": [
7640,
3180
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {},
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "Status|status",
"statusValue": "In progress"
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.2,
"waitBetweenTries": 3000
},
{
"id": "7aa800dd-620a-40b3-86a3-9fc07aeb51fb",
"name": "仅当未锁定时继续",
"type": "n8n-nodes-base.filter",
"position": [
3860,
2900
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6b3359cb-c8fc-4663-b2a9-e367ad4fcba5",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.item }}",
"rightValue": "={{ null }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "ddf2093b-3528-4141-9694-0bc164851919",
"name": "锁定 Notion ID",
"type": "n8n-nodes-base.redis",
"position": [
8300,
3040
],
"parameters": {
"key": "=lock_{{ $json.id }}",
"ttl": 15,
"value": "={{ $now.toISO() }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "05aac18c-e591-430e-9bc9-93a581cb0e89",
"name": "检查 Todoist ID 是否被锁定",
"type": "n8n-nodes-base.redis",
"position": [
3640,
2900
],
"parameters": {
"key": "=lock_{{ $json.body.event_data.id }}",
"keyType": "string",
"options": {},
"operation": "get",
"propertyName": "item"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "4df2d7a9-ca8e-4b64-a797-1cf9ec4786cc",
"name": "在 Todoist 中更新描述",
"type": "n8n-nodes-base.todoist",
"position": [
7860,
2220
],
"parameters": {
"taskId": "={{ $('Map Todoist to Notion1').item.json.todoist_id }}",
"operation": "update",
"updateFields": {
"description": "={{ \"[↗ Open in Notion](\" + $('Create task in Notion').item.json.url + \")\" }}"
}
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 2.1
},
{
"id": "a2b07adf-6e01-4bbc-a852-8b59fb20046f",
"name": "Todoist 中的描述已更改",
"type": "n8n-nodes-base.filter",
"position": [
6980,
2920
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "or",
"conditions": [
{
"id": "db8871d6-7f3f-468e-9a6e-3975c9f003f5",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Get Todoist Task1').item.json.description }}",
"rightValue": "={{ \"[↗ Open in Notion](\" + $json.url + \")\" }}"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "5fe846b7-9869-4d1f-a6f0-6cd14e3127d4",
"name": "在 Todoist 中更新描述1",
"type": "n8n-nodes-base.todoist",
"position": [
7200,
2920
],
"parameters": {
"taskId": "={{ $('Map Todoist to Notion1').item.json.todoist_id }}",
"operation": "update",
"updateFields": {
"description": "={{ \"[↗ Open in Notion](\" + $json.url + \")\" }}"
}
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 2.1
},
{
"id": "879b47ad-5d32-4a40-ad61-690762e202b0",
"name": "生成 UUID1",
"type": "n8n-nodes-base.crypto",
"position": [
7420,
2440
],
"parameters": {
"action": "generate",
"dataPropertyName": "uuid"
},
"typeVersion": 1
},
{
"id": "106f7e35-0cc2-4054-817c-6c3d50407e88",
"name": "更新部分(同步 API)1",
"type": "n8n-nodes-base.httpRequest",
"position": [
7640,
2440
],
"parameters": {
"url": "https://api.todoist.com/sync/v9/sync",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "form-urlencoded",
"authentication": "predefinedCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "commands",
"value": "=[ { \"type\": \"item_move\", \"uuid\": \"{{ $json.uuid }}\", \"args\": { \"id\": \"{{ $('Map Todoist to Notion1').item.json.todoist_id }}\", \"section_id\": \"{{ $json.section_id }}\" } }]"
}
]
},
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 4.2
},
{
"id": "20d2cdc2-92ca-497d-a606-445ecf52e908",
"name": "获取积压部分 ID",
"type": "n8n-nodes-base.code",
"position": [
7200,
2440
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const globals = $('Globals1').first().json;\n\nlet output = {};\n\nconst statusName = $('Map Todoist to Notion1').item.json.status;\nconst sectionMap = Object.fromEntries(\n globals.sections.map(section => [section.name, section.id])\n);\nif (!sectionMap.hasOwnProperty(statusName)) {\n throw new Error(\"No Todoist section found for status '\" + statusName + \"'\");\n}\noutput.section_id = sectionMap[statusName];\n\nreturn { json: output };"
},
"typeVersion": 2
},
{
"id": "72819157-59db-4a4e-ad13-1bf73c583be7",
"name": "未找到 Notion 任务",
"type": "n8n-nodes-base.if",
"position": [
6280,
2760
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "27ce7ba1-43e5-4534-a759-1bafb979c190",
"operator": {
"type": "object",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "3b5d3da8-e8b6-46d0-8706-88a9d1a5e95a",
"name": "检查创建标志是否存在1",
"type": "n8n-nodes-base.redis",
"position": [
6540,
2440
],
"parameters": {
"key": "=creating_for_{{ $('Todoist trigger reference').item.json.body.event_data.id }}",
"keyType": "string",
"options": {},
"operation": "get",
"propertyName": "item"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "1dc24cdd-63e7-4307-a945-6cc07babd535",
"name": "仅当标志不存在时继续1",
"type": "n8n-nodes-base.filter",
"position": [
6760,
2440
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6b3359cb-c8fc-4663-b2a9-e367ad4fcba5",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.item }}",
"rightValue": "={{ null }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "2e64fc80-428b-4f14-a7a1-db158bc58b06",
"name": "设置创建标志1",
"type": "n8n-nodes-base.redis",
"position": [
6980,
2440
],
"parameters": {
"key": "=creating_for_{{ $('Todoist trigger reference').item.json.body.event_data.id }}",
"ttl": 10,
"value": "={{ $now.toISO() }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "4a2cb838-40d6-4e16-bb41-c04548d3394c",
"name": "既非重点也非计划1",
"type": "n8n-nodes-base.filter",
"position": [
7420,
3020
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "95c47aa4-249c-4235-89c2-44c16c5b65bb",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.property_focus }}",
"rightValue": ""
},
{
"id": "a611c1d2-d01a-4f5a-8ad8-cfaa65b11202",
"operator": {
"type": "object",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.property_due }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "56df5752-6beb-4ebe-8b6c-1b5752633db3",
"name": "在 Notion 中标记为重点",
"type": "n8n-nodes-base.notion",
"position": [
7640,
2920
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {},
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "Focus|checkbox",
"checkboxValue": true
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.2,
"waitBetweenTries": 3000
},
{
"id": "e031cb0d-41bf-4273-a198-5485cdaa3758",
"name": "Todoist 触发器引用",
"type": "n8n-nodes-base.noOp",
"position": [
3200,
2900
],
"parameters": {},
"typeVersion": 1
},
{
"id": "539bac50-20c8-4212-aaa5-a99b7f2c3cdb",
"name": "获取 Notion 任务2",
"type": "n8n-nodes-base.notion",
"position": [
6760,
3700
],
"parameters": {
"limit": 1,
"filters": {
"conditions": [
{
"key": "Todoist ID|rich_text",
"condition": "equals",
"richTextValue": "={{ $('Todoist trigger reference').item.json.body.event_data.id }}"
}
]
},
"options": {},
"resource": "databasePage",
"operation": "getAll",
"databaseId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Globals1').first().json.database_id }}"
},
"filterType": "manual"
},
"credentials": {
"notionApi": {
"id": "ObmaBA0dJss3JJPv",
"name": "Notion (Test)"
}
},
"typeVersion": 2.2,
"alwaysOutputData": true
},
{
"id": "1585da72-5578-456b-a092-21e66b033350",
"name": "找到 Notion 任务1",
"type": "n8n-nodes-base.filter",
"position": [
6980,
3700
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d644079f-b140-42b3-85c5-f1283a6d21d8",
"operator": {
"type": "object",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "c5df6b20-a7ed-4e57-9814-091f76ee677b",
"name": "按事件切换",
"type": "n8n-nodes-base.switch",
"position": [
6760,
2920
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "item:updated",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "4ffd3f5d-dc24-47cb-a4df-1a6f6a6dc2b9",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Todoist trigger reference').item.json.body.event_name }}",
"rightValue": "item:updated"
}
]
},
"renameOutput": true
},
{
"outputKey": "item:completed",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d5290195-6595-4c91-af86-6df724bb0980",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Todoist trigger reference').item.json.body.event_name }}",
"rightValue": "item:completed"
}
]
},
"renameOutput": true
},
{
"outputKey": "item:uncompleted",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "e047ade5-ce68-4bc8-b799-7130bd0eb917",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Todoist trigger reference').item.json.body.event_name }}",
"rightValue": "item:uncompleted"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "74859244-46a3-45a2-8e90-923c6bdc0d40",
"name": "如果事件不是 :deleted",
"type": "n8n-nodes-base.if",
"position": [
4080,
2900
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "4fb65ecf-dbb9-4bd1-a5a8-614441aa2963",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Todoist trigger reference').item.json.body.event_name }}",
"rightValue": "item:deleted"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "f3404606-51e8-4b53-b332-20f9cf26a969",
"name": "执行数据",
"type": "n8n-nodes-base.executionData",
"position": [
3420,
2900
],
"parameters": {
"dataToSave": {
"values": [
{
"key": "database_id",
"value": "={{ $('Globals1').first().json.database_id }}"
},
{
"key": "project_id",
"value": "={{ $('Globals1').first().json.project_id }}"
},
{
"key": "todoist_id",
"value": "={{ $json.body.event_data.id }}"
},
{
"key": "source",
"value": "Todoist"
}
]
}
},
"typeVersion": 1
},
{
"id": "643b67db-43e2-47fb-8f7f-1c5ad7b780fc",
"name": "执行数据1",
"type": "n8n-nodes-base.executionData",
"position": [
5840,
2760
],
"parameters": {
"dataToSave": {
"values": [
{
"key": "task_name",
"value": "={{ $json.name }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "ad4ff5a0-ab39-48f9-82fd-9f920b8160d5",
"name": "执行数据2",
"type": "n8n-nodes-base.executionData",
"position": [
7640,
2220
],
"parameters": {
"dataToSave": {
"values": [
{
"key": "notion_id",
"value": "={{ $json.id }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "ee82b160-35ce-4943-b8f7-6297382f0656",
"name": "执行数据3",
"type": "n8n-nodes-base.executionData",
"position": [
6540,
2920
],
"parameters": {
"dataToSave": {
"values": [
{
"key": "notion_id",
"value": "={{ $json.id }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "591f2447-1a81-436c-b073-838cb3011f51",
"name": "执行数据4",
"type": "n8n-nodes-base.executionData",
"position": [
7200,
3700
],
"parameters": {
"dataToSave": {
"values": [
{
"key": "notion_id",
"value": "={{ $json.id }}"
},
{
"key": "task_name",
"value": "={{ $json.name }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "558f1a95-56ad-43c0-b924-b8a6ad50cf36",
"name": "全局变量1",
"type": "n8n-nodes-base.set",
"position": [
3420,
2640
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "{\n \"database_id\": \"165beaa8-f444-8063-be17-d105a9898961\",\n \"project_id\": \"2327925123\",\n \"sections\": [\n {\"id\": \"177308158\", \"name\": \"Backlog\"},\n {\"id\": \"175778849\", \"name\": \"In progress\"},\n {\"id\": \"175778857\", \"name\": \"On hold\"}\n ]\n}"
},
"executeOnce": true,
"typeVersion": 3.4
},
{
"id": "3f39ff17-8700-475e-9ae7-ef3dbef299af",
"name": "便签12",
"type": "n8n-nodes-base.stickyNote",
"position": [
3360,
2500
],
"parameters": {
"width": 220,
"height": 300,
"content": "## 设置全局变量"
},
"typeVersion": 1
},
{
"id": "44393b98-739a-4478-a485-67b8bb59e901",
"name": "将 Todoist 映射到 Notion1",
"type": "n8n-nodes-base.code",
"position": [
5620,
2760
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const globals = $('Globals1').first().json;\nlet eventData = $json;\n\nlet output = {};\n\noutput.todoist_id = eventData.id;\n\noutput.name = eventData.content\n\n// Map priority\noutput.priority = {\n \"4\": \"do first\", // P1\n \"3\": \"urgent\", // P2\n \"2\": \"important\" // P3\n}[eventData.priority] ?? \"\"; // P4\n\n// Map section\nconst sectionId = eventData.section_id;\nconst sectionMap = Object.fromEntries(\n globals.sections.map(section => [section.id, section.name])\n);\n\noutput.status = \"Backlog\";\nif (sectionId !== null) {\n if (!sectionMap.hasOwnProperty(sectionId)) {\n throw new Error(\"No status name found for section ID '\" + sectionId + \"'\");\n }\n output.status = sectionMap[sectionId];\n}\n\n// Since there is no Done section in Todoist, override status if completed\nif (eventData.is_completed) {\n output.status = \"Done\";\n}\n\n// Map date and time\noutput.due = null;\nif (eventData.due !== null) {\n output.due = eventData.due.date\n if (eventData.due.datetime !== undefined) {\n output.due = eventData.due.datetime\n }\n}\n\nreturn { json: output };"
},
"typeVersion": 2
},
{
"id": "9c9bf784-1318-4891-a6d8-3303aa09bd82",
"name": "获取 Todoist 任务1",
"type": "n8n-nodes-base.todoist",
"onError": "continueErrorOutput",
"maxTries": 3,
"position": [
4740,
2760
],
"parameters": {
"taskId": "={{ $('Todoist trigger reference').item.json.body.event_data.id }}",
"operation": "get"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 2.1,
"waitBetweenTries": 3000
},
{
"id": "0d64962d-079a-4991-9442-8cf1f18b4b4f",
"name": "获取 Notion 任务1",
"type": "n8n-nodes-base.notion",
"position": [
6060,
2760
],
"parameters": {
"limit": 1,
"filters": {
"conditions": [
{
"key": "Todoist ID|rich_text",
"condition": "equals",
"richTextValue": "={{ $('Map Todoist to Notion1').item.json.todoist_id }}"
}
]
},
"options": {},
"resource": "databasePage",
"operation": "getAll",
"databaseId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Globals1').first().json.database_id }}"
},
"filterType": "manual"
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"typeVersion": 2.2,
"alwaysOutputData": true
},
{
"id": "0380f6c3-a3e8-4dba-8e45-f98a53dfa60f",
"name": "便签23",
"type": "n8n-nodes-base.stickyNote",
"position": [
5780,
2660
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "存储更多之前不可用的信息"
},
"typeVersion": 1
},
{
"id": "a36cf7d4-d467-428b-b6cc-2ff7c9ccd1ad",
"name": "便签 26",
"type": "n8n-nodes-base.stickyNote",
"position": [
7360,
2140
],
"parameters": {
"color": 7,
"width": 220,
"height": 240,
"content": "设置重点标志,以便在 Notion 中更新后它仍保留在 Todoist 中"
},
"typeVersion": 1
},
{
"id": "79a16ed2-2cc9-4dc0-9efd-996ec3409b73",
"name": "便签 27",
"type": "n8n-nodes-base.stickyNote",
"position": [
6460,
1880
],
"parameters": {
"color": 7,
"width": 1800,
"height": 760,
"content": "**创建 Notion 页面**(如果不存在)"
},
"typeVersion": 1
},
{
"id": "124820ca-86d1-4b99-8437-2fcdc399cef7",
"name": "便签 28",
"type": "n8n-nodes-base.stickyNote",
"position": [
6460,
2660
],
"parameters": {
"color": 7,
"width": 1340,
"height": 900,
"content": "**更新 Notion 页面**(如果需要更改)"
},
"typeVersion": 1
},
{
"id": "597e44ec-8c2c-4e2e-9d8c-e9fd8495dcd3",
"name": "便签 29",
"type": "n8n-nodes-base.stickyNote",
"position": [
3580,
2800
],
"parameters": {
"color": 7,
"width": 440,
"height": 280,
"content": "检查 Redis 中的缓存标志,以防止覆盖和无限循环。"
},
"typeVersion": 1
},
{
"id": "88302c02-82d5-4e5d-9716-49e01d4d1b12",
"name": "便签31",
"type": "n8n-nodes-base.stickyNote",
"position": [
6000,
2660
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "检索 Notion 页面,以便进行比较"
},
"typeVersion": 1
},
{
"id": "31f3c572-4f38-4133-85ff-699ab6359d0e",
"name": "便签 32",
"type": "n8n-nodes-base.stickyNote",
"position": [
6480,
2820
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "存储更多之前不可用的信息"
},
"typeVersion": 1
},
{
"id": "f2f6d6b4-3c6e-46ef-906b-7293d16a0ce2",
"name": "便签33",
"type": "n8n-nodes-base.stickyNote",
"position": [
3360,
2800
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "在突出显示的执行数据中存储相关信息,以便调试"
},
"typeVersion": 1
},
{
"id": "3529a085-c1dc-4467-927f-cebbbc7ec128",
"name": "便签35",
"type": "n8n-nodes-base.stickyNote",
"position": [
2640,
1780
],
"parameters": {
"color": 5,
"width": 5880,
"height": 2160,
"content": "## 基于触发器的从 Todoist 到 Notion 的同步(双向同步的一部分)"
},
"typeVersion": 1
},
{
"id": "7bd78d2f-8452-48d0-8b85-c2ec9023336f",
"name": "便签36",
"type": "n8n-nodes-base.stickyNote",
"position": [
3140,
2800
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "以下节点引用此节点,因此可以轻松替换触发器"
},
"typeVersion": 1
},
{
"id": "e87ad29c-5eb8-4abb-86a2-d2dd937ab81d",
"name": "便签37",
"type": "n8n-nodes-base.stickyNote",
"position": [
6480,
2380
],
"parameters": {
"color": 7,
"width": 660,
"height": 240,
"content": "通过在 Redis 中存储包含 Todoist ID 的特殊标志来防止在 Notion 中创建重复项"
},
"typeVersion": 1
},
{
"id": "e496f9f0-28be-4ae2-8a69-47805e858724",
"name": "需要完成更改",
"type": "n8n-nodes-base.filter",
"position": [
1220,
2560
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b9e134c4-66a7-4a83-8372-282c19fdc98e",
"operator": {
"type": "boolean",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.is_completed }}",
"rightValue": "={{ $json.is_completed }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "6f0f1698-638a-49ec-af54-8a11ddfb4a3a",
"name": "是否已完成?",
"type": "n8n-nodes-base.if",
"position": [
1440,
2560
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a8595a9c-fc03-4bb1-b1fc-ecde223d19aa",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.is_completed }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "dab457b1-f419-4916-bc93-35029e8a8136",
"name": "存在差异",
"type": "n8n-nodes-base.filter",
"position": [
1220,
2260
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "or",
"conditions": [
{
"id": "3a400a40-a25b-4e88-81ba-1aa9ada9d3aa",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.content }}",
"rightValue": "={{ $json.content }}"
},
{
"id": "8a649292-a21e-4d3f-9e3c-0047b21a7487",
"operator": {
"type": "number",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.priority }}",
"rightValue": "={{ $json.priority }}"
},
{
"id": "65f02b94-18de-4883-b5b1-f58fe5459828",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.due_datetime || null }}",
"rightValue": "={{ $json.due === null ? null : $json.due.date}}"
},
{
"id": "e3225b20-c6b6-479b-a0b5-dc9725197521",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.section_id }}",
"rightValue": "={{ $json.section_id }}"
},
{
"id": "0b24cd35-b8b0-4d21-943c-fad905c4542a",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.description }}",
"rightValue": "={{ $json.description }}"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "57489d22-0081-48f5-883d-d646cde35908",
"name": "仅当未锁定时继续1",
"type": "n8n-nodes-base.filter",
"position": [
-1680,
3040
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6b3359cb-c8fc-4663-b2a9-e367ad4fcba5",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.item }}",
"rightValue": "={{ null }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "6bbb506e-407b-4357-935e-692cdccac165",
"name": "检查 Notion ID 是否被锁定",
"type": "n8n-nodes-base.redis",
"position": [
-1900,
3040
],
"parameters": {
"key": "=lock_{{ $json.body.data.id }}",
"keyType": "string",
"options": {},
"operation": "get",
"propertyName": "item"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "abd91f47-f21c-4805-ae8a-d52a6b576304",
"name": "获取 Notion 任务",
"type": "n8n-nodes-base.notion",
"maxTries": 2,
"position": [
-1460,
3040
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Notion trigger reference').item.json.body.data.id }}"
},
"simple": false,
"resource": "databasePage",
"operation": "get"
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.2,
"waitBetweenTries": 3000
},
{
"id": "62a7503b-d605-484b-95c0-864ea698f31b",
"name": "检查创建标志是否存在",
"type": "n8n-nodes-base.redis",
"position": [
1440,
3400
],
"parameters": {
"key": "=creating_for_{{ $('Notion trigger reference').item.json.body.data.id }}",
"keyType": "string",
"options": {},
"operation": "get",
"propertyName": "item"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "e7981df5-7d8e-4f2d-b25f-9b74b177c95e",
"name": "仅当标志不存在时继续",
"type": "n8n-nodes-base.filter",
"position": [
1660,
3400
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6b3359cb-c8fc-4663-b2a9-e367ad4fcba5",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.item }}",
"rightValue": "={{ null }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "22869aac-c36b-4aad-ab3b-00971cfe7c4e",
"name": "设置创建标志",
"type": "n8n-nodes-base.redis",
"position": [
1880,
3400
],
"parameters": {
"key": "=creating_for_{{ $('Notion trigger reference').item.json.body.data.id }}",
"ttl": 10,
"value": "={{ $now.toISO() }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "86e4a245-d8e3-4a2a-adab-57f908fadaca",
"name": "Todoist ID 是否存在?",
"type": "n8n-nodes-base.if",
"position": [
-360,
3240
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "8b074088-25e0-4975-9356-bd2d9c17c0d3",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.id }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "0f49f736-eeec-4159-bd12-056ef4270fb5",
"name": "获取 todoist ID",
"type": "n8n-nodes-base.set",
"position": [
1880,
2560
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "be85c68d-e9a8-4a88-ae12-54ec9cc685e5",
"name": "id",
"type": "string",
"value": "={{ $('Get Todoist Task2').item.json.id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "0f1eea07-0750-46a8-801b-76254461437f",
"name": "Todoist ID 存在",
"type": "n8n-nodes-base.filter",
"position": [
1220,
2000
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "d20ece89-96d3-4324-894f-860711138952",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.id }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "5fe0975a-9b10-4046-8037-54e20ebd8804",
"name": "获取 todoist ID1",
"type": "n8n-nodes-base.set",
"position": [
1880,
2000
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "be85c68d-e9a8-4a88-ae12-54ec9cc685e5",
"name": "id",
"type": "string",
"value": "={{ $('Todoist ID exists').item.json.id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "7025479a-b14e-471e-bc7d-211241b37d8f",
"name": "状态已更改但不为空",
"type": "n8n-nodes-base.filter",
"position": [
1220,
2900
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "e3225b20-c6b6-479b-a0b5-dc9725197521",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.section_id }}",
"rightValue": "={{ $json.section_id }}"
},
{
"id": "2ea2a63b-9968-4f95-af48-fc71fa210ffd",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.section_id }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "e8b84dd0-9f5e-4670-a2ee-50e75448b611",
"name": "状态是否为已废弃?",
"type": "n8n-nodes-base.if",
"position": [
-800,
3040
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "9118fe29-c592-430e-8877-57cb81e6bf75",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Get Notion task').item.json.properties['Status'].status.name }}",
"rightValue": "Obsolete"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "aba681f0-a079-442f-9538-fb81abe8a9b7",
"name": "状态不是已完成",
"type": "n8n-nodes-base.filter",
"position": [
1220,
3240
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "e3225b20-c6b6-479b-a0b5-dc9725197521",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Get Notion task').item.json.properties['Status'].status.name }}",
"rightValue": "Done"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "8262fbbd-3fc4-478c-8ffc-3d8d6ece743c",
"name": "既非重点也非计划",
"type": "n8n-nodes-base.if",
"position": [
-580,
3140
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "990a9ac6-c358-4aae-8c4e-4c49f9830ef5",
"operator": {
"type": "object",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $('Get Notion task').item.json.properties.Due.date }}",
"rightValue": ""
},
{
"id": "93c8c7eb-1979-4eaf-90a6-be659290cded",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $('Get Notion task').item.json.properties.Focus.checkbox }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "d0ab88eb-deed-4a2f-85f9-7aa381659c55",
"name": "Notion 触发器引用",
"type": "n8n-nodes-base.noOp",
"position": [
-2340,
3040
],
"parameters": {},
"typeVersion": 1
},
{
"id": "1895a6d2-71dc-4f58-a746-9b59c71bedcb",
"name": "Notion Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-3220,
3040
],
"webhookId": "7e1a4d8a-9cdc-4991-817c-1429ccfbece2",
"parameters": {
"path": "7e1a4d8a-9cdc-4991-817c-1429ccfbece2",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2
},
{
"id": "35178847-629c-4672-858a-9b7de7344ef7",
"name": "存储 Todoist ID1",
"type": "n8n-nodes-base.notion",
"position": [
2320,
3500
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Notion trigger reference').item.json.body.data.id }}"
},
"options": {},
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "Todoist ID|rich_text",
"textContent": "={{ $json.id }}"
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.2,
"waitBetweenTries": 3000
},
{
"id": "1310034f-b035-43d0-9448-122bf0018e63",
"name": "在 Todoist 中标记为已完成1",
"type": "n8n-nodes-base.todoist",
"position": [
1660,
2460
],
"parameters": {
"taskId": "={{ $('Get Todoist Task2').item.json.id }}",
"operation": "close"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 2.1,
"waitBetweenTries": 3000
},
{
"id": "baa2d8ad-3eee-46ec-bd20-cea4615f5cec",
"name": "在 Todoist 中更新任务1",
"type": "n8n-nodes-base.httpRequest",
"position": [
1660,
2260
],
"parameters": {
"url": "=https://api.todoist.com/rest/v2/tasks/{{ $json.id }}",
"method": "POST",
"options": {},
"jsonQuery": "={{ $('Map Notion to Todoist2').item.json.toJsonString() }}",
"sendQuery": true,
"specifyQuery": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 4.2,
"waitBetweenTries": 3000
},
{
"id": "ad9d9069-a09b-41d0-a4f5-e83ac28f04cf",
"name": "全局变量2",
"type": "n8n-nodes-base.set",
"position": [
-2120,
2780
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "{\n \"database_id\": \"165beaa8-f444-8063-be17-d105a9898961\",\n \"project_id\": \"2327925123\",\n \"sections\": [\n {\"id\": \"177308158\", \"name\": \"Backlog\"},\n {\"id\": \"175778849\", \"name\": \"In progress\"},\n {\"id\": \"175778857\", \"name\": \"On hold\"}\n ]\n}"
},
"executeOnce": true,
"typeVersion": 3.4
},
{
"id": "165db83b-47fd-4aa6-9d04-c7ce07e8f8bf",
"name": "在 Todoist 中标记为未完成1",
"type": "n8n-nodes-base.todoist",
"position": [
1660,
2660
],
"parameters": {
"taskId": "={{ $json.id }}",
"operation": "reopen"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 2.1,
"waitBetweenTries": 3000
},
{
"id": "a5bca570-f213-4d40-ac5f-7f4bbd532432",
"name": "生成 UUID2",
"type": "n8n-nodes-base.crypto",
"position": [
1440,
2900
],
"parameters": {
"action": "generate",
"dataPropertyName": "uuid"
},
"typeVersion": 1
},
{
"id": "00cea352-9026-433b-b351-28720e0b6a40",
"name": "更新部分(同步 API)2",
"type": "n8n-nodes-base.httpRequest",
"position": [
1660,
2900
],
"parameters": {
"url": "https://api.todoist.com/sync/v9/sync",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "form-urlencoded",
"authentication": "predefinedCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "commands",
"value": "=[ { \"type\": \"item_move\", \"uuid\": \"{{ $json.uuid }}\", \"args\": { \"id\": \"{{ $('Get Todoist Task2').item.json.id }}\", \"section_id\": \"{{ $('Map Notion to Todoist2').item.json.section_id }}\" } }]"
}
]
},
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 4.2
},
{
"id": "1cd9bf69-0e3c-4ace-8e79-894fea1c0045",
"name": "锁定 Todoist ID4",
"type": "n8n-nodes-base.redis",
"position": [
2320,
2580
],
"parameters": {
"key": "=lock_{{ $json.id }}",
"ttl": 15,
"value": "={{ $now.toISO() }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "dc21db22-41a0-4a3b-aeba-dc3c81fdf251",
"name": "在 Todoist 中删除任务2",
"type": "n8n-nodes-base.todoist",
"onError": "continueRegularOutput",
"position": [
1660,
2000
],
"parameters": {
"taskId": "={{ $json.id }}",
"operation": "delete"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 2.1,
"waitBetweenTries": 3000
},
{
"id": "e90c2ef5-61f9-4e16-879f-14c9d7974f88",
"name": "将 Notion 映射到 Todoist2",
"type": "n8n-nodes-base.code",
"position": [
-1240,
3040
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const globals = $('Globals2').first().json;\nconst properties = $('Get Notion task').item.json.properties;\n\nlet output = {};\n\noutput.project_id = globals.project_id;\n\noutput.id = properties['Todoist ID'].rich_text.length > 0 ? \n properties['Todoist ID'].rich_text[0].text.content : \n null;\n\noutput.content = properties['Name'].title.length > 0 ? properties['Name'].title[0].text.content : '[empty]';\n\noutput.description = \"[↗ Open in Notion](\" + $('Get Notion task').item.json.url + \")\"\n\n// Map priority\nif (properties['Priority'].select === null) {\n output.priority = \"1\"; // P4\n} else {\n output.priority = {\n \"do first\": \"4\", // P1\n \"urgent\": \"3\", // P2\n \"important\": \"2\" // P3\n }[properties['Priority'].select.name] ?? \"1\"; // P4\n}\n\n// Map section\nconst statusName = properties['Status'].status.name;\noutput.section_id = null;\nif (!['Done', 'Obsolete'].includes(statusName)) {\n const sectionMap = Object.fromEntries(\n globals.sections.map(section => [section.name, section.id])\n );\n if (!sectionMap.hasOwnProperty(statusName)) {\n throw new Error(\"No Todoist section found for status '\" + statusName + \"'\");\n }\n output.section_id = sectionMap[statusName];\n}\n\n// Map completed by status \"Done\"\noutput.is_completed = false;\nif (properties['Status'].status.name == \"Done\") {\n output.is_completed = true;\n}\n\n// Set UTC if time is set\nif (properties['Due'].date !== null) {\n output.due_datetime = properties.Due.date.start\n if (properties.Due.date.start.length > 10) {\n output.due_datetime = new Date(properties.Due.date.start).toISOString();\n }\n} else {\n // API requires due_string instead of due_datetime when date/time is removed\n output.due_string = \"no date\";\n}\n\nreturn { json: output };"
},
"typeVersion": 2
},
{
"id": "e5e12b26-6c45-4c63-9c7d-05ae88a457aa",
"name": "获取 Todoist 任务2",
"type": "n8n-nodes-base.todoist",
"onError": "continueErrorOutput",
"position": [
300,
3140
],
"parameters": {
"taskId": "={{ $json.id }}",
"operation": "get"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 2.1
},
{
"id": "05441894-9904-44b3-94f7-bb600bcbe7ba",
"name": "在 Todoist 中创建任务1",
"type": "n8n-nodes-base.httpRequest",
"position": [
2100,
3400
],
"parameters": {
"url": "=https://api.todoist.com/rest/v2/tasks",
"method": "POST",
"options": {},
"jsonQuery": "={{ $('Map Notion to Todoist2').item.json.toJsonString() }}",
"sendQuery": true,
"specifyQuery": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 4.2,
"waitBetweenTries": 3000
},
{
"id": "354b6df0-b51e-476c-bb54-baaa1a6ca788",
"name": "执行数据5",
"type": "n8n-nodes-base.executionData",
"position": [
-2120,
3040
],
"parameters": {
"dataToSave": {
"values": [
{
"key": "database_id",
"value": "={{ $('Globals2').first().json.database_id }}"
},
{
"key": "project_id",
"value": "={{ $('Globals2').first().json.project_id }}"
},
{
"key": "notion_id",
"value": "={{ $json.body.data.id }}"
},
{
"key": "source",
"value": "Notion"
}
]
}
},
"typeVersion": 1
},
{
"id": "04fe1427-30ba-45ae-904a-05c7b4c2269f",
"name": "执行数据6",
"type": "n8n-nodes-base.executionData",
"position": [
-1020,
3040
],
"parameters": {
"dataToSave": {
"values": [
{
"key": "todoist_id",
"value": "={{ $json.id }}"
},
{
"key": "task_name",
"value": "={{ $json.content }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "2b8262d7-298b-4a70-a420-50d8d066cb40",
"name": "便签22",
"type": "n8n-nodes-base.stickyNote",
"position": [
1380,
2840
],
"parameters": {
"color": 7,
"width": 440,
"height": 220,
"content": "在完全同步模式下使用同步 API 在 Todoist 中更新部分(**15 分钟内最多 100 个请求!**)"
},
"typeVersion": 1
},
{
"id": "10571edb-ed79-43d3-bcb9-a64273c3eeb7",
"name": "便签24",
"type": "n8n-nodes-base.stickyNote",
"position": [
1140,
1880
],
"parameters": {
"color": 7,
"width": 900,
"height": 300,
"content": "**删除任务**(如果不再符合筛选条件)"
},
"typeVersion": 1
},
{
"id": "85124819-6f06-44e8-a5dd-0c31c1aec5e3",
"name": "便签 25",
"type": "n8n-nodes-base.stickyNote",
"position": [
1140,
2200
],
"parameters": {
"color": 7,
"width": 900,
"height": 880,
"content": "**更新任务**(如果需要更改)"
},
"typeVersion": 1
},
{
"id": "6a9d1e64-39d1-4361-aa60-2368ba36a2cd",
"name": "便签38",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1520,
2940
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Notion 会尝试再次发送 webhook。为确保数据是最新的,必须再次检索有效负载。"
},
"typeVersion": 1
},
{
"id": "4787a4de-f5d2-4983-a658-916c497ce683",
"name": "便签39",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2180,
2640
],
"parameters": {
"width": 220,
"height": 300,
"content": "## 设置全局变量"
},
"typeVersion": 1
},
{
"id": "5afdea5e-e6ed-463a-99bb-35bda66c25e6",
"name": "便签41",
"type": "n8n-nodes-base.stickyNote",
"position": [
1600,
1920
],
"parameters": {
"color": 7,
"width": 220,
"height": 240,
"content": "出错时继续,因此无需事先检索任务"
},
"typeVersion": 1
},
{
"id": "6353737c-ea2b-4907-94d1-d5b4c743ca29",
"name": "便签42",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2400,
2940
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "以下节点引用此节点,因此可以轻松替换触发器"
},
"typeVersion": 1
},
{
"id": "caa80f7d-1c1a-4bbf-a92d-4df2a65e6e2f",
"name": "便签43",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2180,
2940
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "在突出显示的执行数据中存储相关信息,以便调试"
},
"typeVersion": 1
},
{
"id": "58332f57-5bee-4559-922b-f2ec025af90d",
"name": "便签44",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1080,
2940
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "存储更多之前不可用的信息"
},
"typeVersion": 1
},
{
"id": "785526ba-39e6-42aa-83f0-731a81e821ec",
"name": "便签45",
"type": "n8n-nodes-base.stickyNote",
"position": [
-860,
2940
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "与其在 Notion 中删除页面,更安全的做法是将它们标记为已废弃。"
},
"typeVersion": 1
},
{
"id": "933c2fd0-c378-4984-b34a-6a1f89d61299",
"name": "便签46",
"type": "n8n-nodes-base.stickyNote",
"position": [
1380,
3320
],
"parameters": {
"color": 7,
"width": 660,
"height": 260,
"content": "在极少数情况下,Notion 在创建新任务时会在短时间内触发多个 webhook。这可以防止在 Todoist 中创建重复项。"
},
"typeVersion": 1
},
{
"id": "14b97b13-0689-481b-8a21-61940e742b2a",
"name": "便签47",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1300,
2940
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "映射更高级,并且还包括边缘情况的回退值"
},
"typeVersion": 1
},
{
"id": "ce6ed70d-4fa0-4291-9ac8-13cd23e899cc",
"name": "便签48",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1960,
2940
],
"parameters": {
"color": 7,
"width": 440,
"height": 280,
"content": "检查 Redis 中的缓存标志,以防止覆盖和无限循环。"
},
"typeVersion": 1
},
{
"id": "21378455-34e5-42bf-857d-543a7a972c33",
"name": "便利贴5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3340,
1780
],
"parameters": {
"color": 5,
"width": 5880,
"height": 1960,
"content": "# 基于触发器的从 Notion 到 Todoist 的同步(双向同步的一部分)"
},
"typeVersion": 1
},
{
"id": "0688c57e-9de6-43b5-8b75-b214760a8445",
"name": "便签20",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3280,
2820
],
"parameters": {
"width": 220,
"height": 400,
"content": "## 配置 Webhook"
},
"typeVersion": 1
},
{
"id": "d3a90492-d62f-4b01-bf2b-1b82b74c04cc",
"name": "便签 7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-640,
3040
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "出于效率原因,仅同步重点和计划的任务"
},
"typeVersion": 1
},
{
"id": "d0b03cb0-8c69-4a6a-854a-afdfc690219f",
"name": "便签19",
"type": "n8n-nodes-base.stickyNote",
"position": [
-420,
3120
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Notion 和 Todoist 任务之间的映射存储在 Notion 中"
},
"typeVersion": 1
},
{
"id": "c34e228b-eee5-43f4-9876-8ffcd3f64488",
"name": "便签11",
"type": "n8n-nodes-base.stickyNote",
"position": [
2260,
2460
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Todoist ID 会在 Redis 中缓存 15 秒,因此不会触发另一个差异同步。"
},
"typeVersion": 1
},
{
"id": "c78c9264-683f-4ee5-9cd2-086627ce4fd1",
"name": "便签13",
"type": "n8n-nodes-base.stickyNote",
"position": [
1140,
3100
],
"parameters": {
"color": 7,
"width": 1340,
"height": 580,
"content": "**创建任务**(如果它不存在(不再存在))"
},
"typeVersion": 1
},
{
"id": "7940c709-6107-4efa-83dc-639b88286a84",
"name": "便签 6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1160,
3160
],
"parameters": {
"color": 7,
"width": 220,
"height": 240,
"content": "边缘情况:任务已从已废弃移动到已完成"
},
"typeVersion": 1
},
{
"id": "2d6b7423-0818-4a2d-b672-0d09f7eaab59",
"name": "便签49",
"type": "n8n-nodes-base.stickyNote",
"position": [
2700,
3180
],
"parameters": {
"color": 7,
"width": 1020,
"height": 700,
"content": "## Todoist Webhook 路由器示例(可选)"
},
"typeVersion": 1
},
{
"id": "b1b38cc8-3dda-43de-9fd3-8341b5fcd798",
"name": "便签51",
"type": "n8n-nodes-base.stickyNote",
"position": [
4020,
2800
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "使用 Todoist 的事件类型是实际识别任务是否被删除的唯一选项"
},
"typeVersion": 1
},
{
"id": "5eb3caf9-ae18-47ce-80c1-52b1029e627d",
"name": "便签52",
"type": "n8n-nodes-base.stickyNote",
"position": [
5560,
2660
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "映射更高级,并且还包括边缘情况的回退值"
},
"typeVersion": 1
},
{
"id": "d8935c51-34a4-4d69-890e-23a7194ba5ea",
"name": "便签53",
"type": "n8n-nodes-base.stickyNote",
"position": [
6220,
2660
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "前一个节点总是返回输出数据,因为它使用过滤器来检索匹配项"
},
"typeVersion": 1
},
{
"id": "d7f254d6-026a-48bb-b3db-1104181db443",
"name": "便签54",
"type": "n8n-nodes-base.stickyNote",
"position": [
7140,
2380
],
"parameters": {
"color": 7,
"width": 220,
"height": 240,
"content": "反向映射部分/状态"
},
"typeVersion": 1
},
{
"id": "68536590-3bd2-443a-804a-e433910b0ccc",
"name": "便签55",
"type": "n8n-nodes-base.stickyNote",
"position": [
7360,
2380
],
"parameters": {
"color": 7,
"width": 440,
"height": 240,
"content": "在完全同步模式下使用同步 API 在 Todoist 中更新部分(**15 分钟内最多 100 个请求!**)"
},
"typeVersion": 1
},
{
"id": "18013962-c950-4976-8639-b00b3c2c43ed",
"name": "便签56",
"type": "n8n-nodes-base.stickyNote",
"position": [
7580,
2140
],
"parameters": {
"color": 7,
"width": 220,
"height": 240,
"content": "存储更多之前不可用的信息"
},
"typeVersion": 1
},
{
"id": "02b2cddc-565e-46b1-aaec-b3fc3db0611c",
"name": "便签57",
"type": "n8n-nodes-base.stickyNote",
"position": [
8240,
2920
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Notion ID 会在 Redis 中缓存 15 秒,因此不会触发另一个差异同步。"
},
"typeVersion": 1
},
{
"id": "9d1db117-f41a-4529-a2e8-503d65cff0ad",
"name": "便签58",
"type": "n8n-nodes-base.stickyNote",
"position": [
6460,
3580
],
"parameters": {
"color": 7,
"width": 1340,
"height": 320,
"content": "**删除 Notion 页面**(如果 Todoist 任务已被删除)"
},
"typeVersion": 1
},
{
"id": "9de565e0-3edb-4ea0-bace-8c17b10d5aac",
"name": "便签59",
"type": "n8n-nodes-base.stickyNote",
"position": [
7140,
3620
],
"parameters": {
"color": 7,
"width": 220,
"height": 260,
"content": "存储更多之前不可用的信息"
},
"typeVersion": 1
},
{
"id": "b8aeecf7-28fb-433e-b045-147e6080cc30",
"name": "Todoist Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
2760,
2900
],
"webhookId": "81107d16-d839-4507-a627-f607f1d53d4e",
"parameters": {
"path": "47b150f3-12a3-4fe9-97cc-5d139daa3b00",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2
},
{
"id": "d48d3e29-d1b1-4751-adad-692cddaf49e7",
"name": "便签60",
"type": "n8n-nodes-base.stickyNote",
"position": [
3600,
-640
],
"parameters": {
"color": 7,
"width": 1980,
"height": 860,
"content": "处理**仅在 Notion 中找到的任务**。这可能仍然意味着任务存在于 Todoist 中,但由于已完成而不符合筛选条件。在这种情况下,将检索 Todoist 任务。如果存在,Todoist 任务将被重新打开,否则将被创建。"
},
"typeVersion": 1
},
{
"id": "bfbf04c7-8e27-4afd-998c-1720eb7f05bd",
"name": "便签61",
"type": "n8n-nodes-base.stickyNote",
"position": [
3600,
240
],
"parameters": {
"color": 7,
"width": 1760,
"height": 640,
"content": "处理显示**两个系统之间存在差异**的任务。如果存在差异,Todoist 任务将被 Notion 的所有属性覆盖。此外,状态将在后续步骤中相应更新。"
},
"typeVersion": 1
},
{
"id": "8edde5d8-649a-42a3-bf81-83c36ac1ff03",
"name": "便签62",
"type": "n8n-nodes-base.stickyNote",
"position": [
3600,
900
],
"parameters": {
"color": 7,
"width": 1760,
"height": 720,
"content": "处理**仅在 Todoist 中找到的任务**。这可能仍然意味着任务存在于 Notion 中,但由于已完成而不符合筛选条件。在这种情况下,将检索 Notion 页面。如果由于被删除而不存在,Todoist 任务也将被删除。"
},
"typeVersion": 1
},
{
"id": "284199c4-40ba-4590-9eb8-1af4dedfedf4",
"name": "便签63",
"type": "n8n-nodes-base.stickyNote",
"position": [
2700,
360
],
"parameters": {
"width": 220,
"height": 280,
"content": "## 设置计划"
},
"typeVersion": 1
},
{
"id": "2cad2f03-b24d-4146-883d-78bdd11ac127",
"name": "便签64",
"type": "n8n-nodes-base.stickyNote",
"position": [
3140,
560
],
"parameters": {
"color": 7,
"width": 220,
"height": 200,
"content": "准备比较数据"
},
"typeVersion": 1
},
{
"id": "453dd0b4-1163-46dd-849b-39179f8ddb7c",
"name": "Sticky Note65",
"type": "n8n-nodes-base.stickyNote",
"position": [
7140,
560
],
"parameters": {
"width": 220,
"height": 320,
"content": "## 设置邮件地址"
},
"typeVersion": 1
},
{
"id": "f77d8d57-0a5c-4b33-be07-9806c55330c3",
"name": "执行工作流",
"type": "n8n-nodes-base.executeWorkflow",
"disabled": true,
"position": [
3520,
3440
],
"parameters": {
"options": {},
"workflowId": {
"__rl": true,
"mode": "id",
"value": ""
}
},
"typeVersion": 1.1
},
{
"id": "4bd15a48-39d1-41c3-8b15-7de6abf3d35d",
"name": "提取 ID",
"type": "n8n-nodes-base.set",
"position": [
-2560,
2940
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "7f0c61dc-87ae-4fbe-9e40-d9d27a0d61a8",
"name": "body.data.id",
"type": "string",
"value": "={{ $json.data.id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "6e61887e-8022-44ee-b9ab-94058b08a07b",
"name": "分离 Notion 更改",
"type": "n8n-nodes-base.splitOut",
"position": [
-2780,
2940
],
"parameters": {
"options": {},
"fieldToSplitOut": "body"
},
"typeVersion": 1
},
{
"id": "5ed59854-c972-42bd-9b41-8e6c1d616c8f",
"name": "正文是数组吗?",
"type": "n8n-nodes-base.if",
"position": [
-3000,
3040
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "0479a702-9ec0-4203-bcaa-e4bdbd872f4e",
"operator": {
"type": "object",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $json.body[0] }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "a6003e6f-84a7-4c5e-99d7-47b31dee10d1",
"name": "便签66",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3060,
2880
],
"parameters": {
"color": 7,
"width": 660,
"height": 340,
"content": "如果使用 Notion Webhook Emulator 的数据而不是常规的 Notion Webhook,数据结构不同,并且可以包含多个项目。此部分使工作流与两种方法兼容。"
},
"typeVersion": 1
},
{
"id": "07740ae3-3472-4125-bae9-d1c54e9584f1",
"name": "按项目切换",
"type": "n8n-nodes-base.switch",
"position": [
2980,
2900
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "[project name]",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.event_data.project_id }}",
"rightValue": "2346249328"
}
]
},
"renameOutput": true
}
]
},
"options": {},
"looseTypeValidation": true
},
"typeVersion": 3.2
},
{
"id": "a76190bf-f467-4bbe-82f3-581c2ab27e2a",
"name": "便签21",
"type": "n8n-nodes-base.stickyNote",
"position": [
2700,
2680
],
"parameters": {
"width": 220,
"height": 400,
"content": "## 在 Todoist 应用中注册 Webhook"
},
"typeVersion": 1
},
{
"id": "689992a3-c8bc-4d2e-856b-2c512afdb2da",
"name": "便签67",
"type": "n8n-nodes-base.stickyNote",
"position": [
2920,
2680
],
"parameters": {
"width": 220,
"height": 400,
"content": "## 设置项目 ID 和名称"
},
"typeVersion": 1
},
{
"id": "f2243125-18c3-4484-9a06-85c638b1895f",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"disabled": true,
"position": [
3080,
3540
],
"webhookId": "1d758ef5-a9b9-4313-b81e-1c7dfe230a33",
"parameters": {
"path": "1d758ef5-a9b9-4313-b81e-1c7dfe230a33",
"options": {}
},
"typeVersion": 2
},
{
"id": "f875a8c7-2223-4296-bd55-8f42789d11b7",
"name": "分支",
"type": "n8n-nodes-base.switch",
"disabled": true,
"position": [
3300,
3540
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "project1",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.event_data.project_id }}",
"rightValue": ""
}
]
},
"renameOutput": true
},
{
"outputKey": "project2",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "759db81d-4d1a-463f-9773-1db73b6a7829",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.event_data.project_id }}",
"rightValue": ""
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "e603a21c-238f-45a9-8d7d-d8ccabe25217",
"name": "执行工作流1",
"type": "n8n-nodes-base.executeWorkflow",
"disabled": true,
"position": [
3520,
3640
],
"parameters": {
"options": {},
"workflowId": {
"__rl": true,
"mode": "id",
"value": ""
}
},
"typeVersion": 1.1
},
{
"id": "91fe0191-0c57-404b-95e8-c3257426ccde",
"name": "便签50",
"type": "n8n-nodes-base.stickyNote",
"position": [
2740,
3440
],
"parameters": {
"width": 220,
"height": 280,
"content": "### 替换触发器"
},
"typeVersion": 1
},
{
"id": "98bbef00-86b5-46b2-8242-45f8eec47de4",
"name": "便签68",
"type": "n8n-nodes-base.stickyNote",
"position": [
3020,
3420
],
"parameters": {
"width": 220,
"height": 300,
"content": "### 注册 Webhook"
},
"typeVersion": 1
},
{
"id": "650d3da2-4dee-44ab-afcd-680fb0a0d74a",
"name": "便签69",
"type": "n8n-nodes-base.stickyNote",
"position": [
3240,
3380
],
"parameters": {
"width": 220,
"height": 340,
"content": "### 设置项目 ID 和名称"
},
"typeVersion": 1
},
{
"id": "b9c01e3c-bb9c-4bfd-aa22-b1b4a7824e42",
"name": "便签 70",
"type": "n8n-nodes-base.stickyNote",
"position": [
3460,
3320
],
"parameters": {
"width": 220,
"height": 500,
"content": "### 选择子工作流"
},
"typeVersion": 1
},
{
"id": "007599a2-8c60-423c-80a8-7599ba709db8",
"name": "便签72",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1920,
-580
],
"parameters": {
"width": 980,
"height": 560,
"content": "# 0. 从这里开始"
},
"typeVersion": 1
},
{
"id": "a30814bb-205c-488b-aa0b-bf315f3e32b2",
"name": "便签2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1920,
0
],
"parameters": {
"width": 2780,
"height": 460,
"content": "# 1. 为全局变量节点生成配置 JSON"
},
"typeVersion": 1
},
{
"id": "1f273075-0648-44b1-a8b7-7e226671293a",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1920,
480
],
"parameters": {
"width": 800,
"height": 680,
"content": "# 2. 设置 Todoist Webhook"
},
"typeVersion": 1
},
{
"id": "4915c92e-aca2-409a-909f-7b2efa440bed",
"name": "便签3",
"type": "n8n-nodes-base.stickyNote",
"position": [
60,
480
],
"parameters": {
"width": 800,
"height": 680,
"content": "# 4. 设置 Notion Webhook"
},
"typeVersion": 1
},
{
"id": "de16a551-7864-4758-9da0-8f25856ca33d",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1100,
480
],
"parameters": {
"width": 1140,
"height": 680,
"content": "# 3. Activate Todoist Webhook\nWebhooks are a bit different in Todoist then usual. A Developer App needs to be created and once be connected to via OAuth, to activate the webhook functionality.\nThis flow only works when using the **production URL** of it's form trigger."
},
"typeVersion": 1
},
{
"id": "921e8fea-c860-4943-b9f8-9dee42a36688",
"name": "Due date empty",
"type": "n8n-nodes-base.filter",
"position": [
7420,
2820
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "9e948541-8c3b-4b99-99ab-0966319500ac",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $if($('Map Todoist to Notion1').item.json.due !== null, $('Map Todoist to Notion1').item.json.due.toDateTime().toISO(), null) }}",
"rightValue": "null"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "6ae7b8f4-32b5-4a08-9061-07399fc87631",
"name": "Has not been completed?",
"type": "n8n-nodes-base.if",
"position": [
7200,
3100
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a8595a9c-fc03-4bb1-b1fc-ecde223d19aa",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $('Get Todoist Task1').item.json.is_completed }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "dae00353-4b24-4ab1-a2c3-86ffd6860ae3",
"name": "End here",
"type": "n8n-nodes-base.noOp",
"position": [
5180,
2860
],
"parameters": {},
"typeVersion": 1
},
{
"id": "11685bda-e714-4183-87b5-47dd1dd45123",
"name": "Retry limit reached",
"type": "n8n-nodes-base.stopAndError",
"position": [
4320,
20
],
"parameters": {
"errorMessage": "Retry limit reached"
},
"typeVersion": 1
},
{
"id": "05386c19-1529-48e6-b158-1eaca54ac19f",
"name": "Set tries",
"type": "n8n-nodes-base.set",
"position": [
4320,
-280
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "cd93a7f6-4c06-4e8a-8d0d-e812c5ec4bc5",
"name": "tries",
"type": "number",
"value": "={{ $json.tries || 0 }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "21d1ab05-c6fe-44c8-831c-095d24e7251f",
"name": "Update tries",
"type": "n8n-nodes-base.set",
"position": [
5200,
20
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "df3c9b29-afa6-4e08-868d-5b7e8202eefa",
"name": "tries",
"type": "number",
"value": "={{ $('Set tries').item.json.tries + 1 }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "c67ba192-2207-47a3-a3c5-91d059bc4e91",
"name": "等待",
"type": "n8n-nodes-base.wait",
"position": [
4980,
20
],
"webhookId": "9d3b561f-4afd-478c-8f6e-60641d4f1d0b",
"parameters": {},
"typeVersion": 1.1
},
{
"id": "ca20fd60-2b56-4590-ae22-a43c6dd05973",
"name": "Catch known error",
"type": "n8n-nodes-base.if",
"position": [
4760,
-180
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6a379b06-0b04-4ae4-9bf9-394bd40744b7",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.error }}",
"rightValue": "could not be found"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "feb6c1ec-2696-49df-94ed-e257361b92a0",
"name": "If tries left",
"type": "n8n-nodes-base.if",
"position": [
4100,
20
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b18f784a-4386-4ced-a9e1-ce5a21ad036e",
"operator": {
"type": "number",
"operation": "lt"
},
"leftValue": "={{ $json.tries }}",
"rightValue": 3
}
]
}
},
"typeVersion": 2.2
},
{
"id": "a7f467fb-4ef2-4251-aa0d-47b069fe1cd1",
"name": "便签18",
"type": "n8n-nodes-base.stickyNote",
"position": [
-200,
3060
],
"parameters": {
"color": 7,
"width": 1320,
"height": 480,
"content": "Retrieve Todoist task, which can be completed or deleted at this point.\nUsing advanced retry logic, since the \"Get many tasks\" endpoint does not return completed tasks."
},
"typeVersion": 1
},
{
"id": "6b1cc5de-e44e-4a8a-ba28-b05273abba18",
"name": "Retry limit reached1",
"type": "n8n-nodes-base.stopAndError",
"position": [
80,
3340
],
"parameters": {
"errorMessage": "Retry limit reached"
},
"typeVersion": 1
},
{
"id": "5e38207a-bf7c-4d0d-9e19-ba387a5c68a9",
"name": "Set tries1",
"type": "n8n-nodes-base.set",
"position": [
80,
3140
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "cd93a7f6-4c06-4e8a-8d0d-e812c5ec4bc5",
"name": "tries",
"type": "number",
"value": "={{ $json.tries || 0 }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "1ba45fd2-c7e3-40de-a341-27a342892765",
"name": "Update tries1",
"type": "n8n-nodes-base.set",
"position": [
960,
3340
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "df3c9b29-afa6-4e08-868d-5b7e8202eefa",
"name": "tries",
"type": "number",
"value": "={{ $('Set tries1').item.json.tries + 1 }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a9696b6e-f134-46ba-9b25-862ae73df812",
"name": "等待1",
"type": "n8n-nodes-base.wait",
"position": [
740,
3340
],
"webhookId": "9d3b561f-4afd-478c-8f6e-60641d4f1d0b",
"parameters": {},
"typeVersion": 1.1
},
{
"id": "84e88ff7-bf50-4911-8f30-7027d68abe2a",
"name": "Catch known error1",
"type": "n8n-nodes-base.if",
"position": [
520,
3240
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6a379b06-0b04-4ae4-9bf9-394bd40744b7",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.error }}",
"rightValue": "could not be found"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "0482d2b4-4327-4a71-b473-9d12e07e24a7",
"name": "If tries left1",
"type": "n8n-nodes-base.if",
"position": [
-140,
3340
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b18f784a-4386-4ced-a9e1-ce5a21ad036e",
"operator": {
"type": "number",
"operation": "lt"
},
"leftValue": "={{ $json.tries }}",
"rightValue": 3
}
]
}
},
"typeVersion": 2.2
},
{
"id": "92ff51e9-b9c7-47b4-a0ed-a5a0fddd7ef7",
"name": "便签4",
"type": "n8n-nodes-base.stickyNote",
"position": [
4240,
2660
],
"parameters": {
"color": 7,
"width": 1320,
"height": 600,
"content": "Todoist webhooks can arrive late. To ensure the data is up to date, the payload must be retrieved again. Triggers on deletion can overlap, thus try 3 times, then just stop.\nUsing advanced retry logic, since the \"Get many tasks\" endpoint does not return completed tasks.\nNo more retries needed, if Task does not exist."
},
"typeVersion": 1
},
{
"id": "a611b837-4c77-4092-a0ee-db5ed7eee17c",
"name": "Set tries2",
"type": "n8n-nodes-base.set",
"position": [
4520,
2760
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "cd93a7f6-4c06-4e8a-8d0d-e812c5ec4bc5",
"name": "tries",
"type": "number",
"value": "={{ $json.tries || 0 }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "ac1787c5-a25e-4916-a537-7e9a0854fdc9",
"name": "Update tries2",
"type": "n8n-nodes-base.set",
"position": [
5400,
3060
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "df3c9b29-afa6-4e08-868d-5b7e8202eefa",
"name": "tries",
"type": "number",
"value": "={{ $('Set tries2').item.json.tries + 1 }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "614e1fa0-aac4-425f-87e0-85ba084319cb",
"name": "等待2",
"type": "n8n-nodes-base.wait",
"position": [
5180,
3060
],
"webhookId": "9d3b561f-4afd-478c-8f6e-60641d4f1d0b",
"parameters": {},
"typeVersion": 1.1
},
{
"id": "5c205ac1-0c4c-4441-b49f-6e0fd12ead4a",
"name": "Catch known error2",
"type": "n8n-nodes-base.if",
"position": [
4960,
2860
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6a379b06-0b04-4ae4-9bf9-394bd40744b7",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.error }}",
"rightValue": "could not be found"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "e00655f6-fe5d-4d16-ac8f-303687aa17a9",
"name": "If tries left2",
"type": "n8n-nodes-base.if",
"position": [
4300,
3060
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b18f784a-4386-4ced-a9e1-ce5a21ad036e",
"operator": {
"type": "number",
"operation": "lt"
},
"leftValue": "={{ $json.tries }}",
"rightValue": 3
}
]
}
},
"typeVersion": 2.2
},
{
"id": "a78d5103-9c36-4660-8823-6dbc99a90b3b",
"name": "Retry limit reached2",
"type": "n8n-nodes-base.stopAndError",
"position": [
4520,
3060
],
"parameters": {
"errorMessage": "Retry limit reached"
},
"typeVersion": 1
},
{
"id": "a760a6a7-f93c-4674-a21e-d10867c523de",
"name": "便签 30",
"type": "n8n-nodes-base.stickyNote",
"position": [
7800,
2140
],
"parameters": {
"color": 7,
"width": 220,
"height": 240,
"content": "Overwrite description with Notion URL"
},
"typeVersion": 1
},
{
"id": "5a7190f7-78e7-4362-8129-72e461abb099",
"name": "Turn Markdown into Notion Blocks",
"type": "n8n-nodes-base.code",
"position": [
7640,
1960
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Get the markdown from the input\nconst markdown = $('Get Todoist Task').item.json.description || '';\n\n// Split the markdown into lines and remove empty lines\nconst lines = markdown.split('\\n').filter(line => line.trim());\n\n// Initialize result array\nconst result = [];\n\n// Helper function to split long text into chunks\nfunction splitIntoChunks(text, maxLength) {\n const chunks = [];\n let remaining = text;\n \n while (remaining.length > maxLength) {\n // Find the last space before maxLength\n let splitIndex = remaining.lastIndexOf(' ', maxLength);\n // If no space found, force split at maxLength\n if (splitIndex === -1) splitIndex = maxLength;\n \n chunks.push(remaining.substring(0, splitIndex).trim());\n remaining = remaining.substring(splitIndex).trim();\n }\n \n if (remaining.length > 0) {\n chunks.push(remaining);\n }\n \n return chunks;\n}\n\nfor (const line of lines) {\n const trimmedLine = line.trim();\n \n // Skip if empty line\n if (!trimmedLine) continue;\n \n // Check for different types of markdown elements\n if (trimmedLine.startsWith('# ')) {\n result.push({\n type: 'heading_1',\n content: trimmedLine.substring(2).trim()\n });\n }\n else if (trimmedLine.startsWith('## ')) {\n result.push({\n type: 'heading_2',\n content: trimmedLine.substring(3).trim()\n });\n }\n else if (trimmedLine.startsWith('### ')) {\n result.push({\n type: 'heading_3',\n content: trimmedLine.substring(4).trim()\n });\n }\n else if (trimmedLine.match(/^\\d+\\./)) {\n result.push({\n type: 'numbered_list_item',\n content: trimmedLine.replace(/^\\d+\\.\\s*/, '').trim()\n });\n }\n else if (trimmedLine.startsWith('- ')) {\n result.push({\n type: 'bulleted_list_item',\n content: trimmedLine.substring(2).trim()\n });\n }\n else {\n // For paragraphs, check if they need to be split\n if (trimmedLine.length > 2000) {\n const chunks = splitIntoChunks(trimmedLine, 2000);\n for (const chunk of chunks) {\n result.push({\n type: 'paragraph',\n content: chunk\n });\n }\n } else {\n result.push({\n type: 'paragraph',\n content: trimmedLine\n });\n }\n }\n}\n\n// Return the result in the format n8n expects\nreturn { blocks: result };\n"
},
"typeVersion": 2
},
{
"id": "edb96637-26f5-49e7-ad33-a9be019f874c",
"name": "Handle each block separately",
"type": "n8n-nodes-base.splitOut",
"position": [
7860,
1960
],
"parameters": {
"options": {},
"fieldToSplitOut": "blocks"
},
"typeVersion": 1
},
{
"id": "8c4722b7-87ae-42e3-a97e-16f4bb112b74",
"name": "Append Notion Block",
"type": "n8n-nodes-base.notion",
"position": [
8080,
1960
],
"parameters": {
"blockId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Create task in Notion').item.json.id }}"
},
"blockUi": {
"blockValues": [
{
"type": "={{ $json.type }}",
"textContent": "={{ $json.content }}"
}
]
},
"resource": "block"
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"typeVersion": 2.2
},
{
"id": "8ba90a10-f79d-4bac-af19-f61e7bbad979",
"name": "便签34",
"type": "n8n-nodes-base.stickyNote",
"position": [
7580,
1900
],
"parameters": {
"color": 7,
"width": 660,
"height": 240,
"content": "Copy task description initially to Notion, before it get's replaced in Todoist by the Notion URL"
},
"typeVersion": 1
},
{
"id": "888f3904-cd9a-4b7b-ab7e-af2eda62aa68",
"name": "Sticky Note40",
"type": "n8n-nodes-base.stickyNote",
"position": [
60,
1180
],
"parameters": {
"width": 800,
"height": 380,
"content": "# 5. Finishing Touches\n\n### Cleanup\nNow technically everything is ready, but it is missing better usability and maintainability. So start by moving (cut & paste) the 3 big flows (outlined in blue) to separate n8n workflows. You can name them as follows:\n- Notion-Todoist Full Sync (<workspace-name>)\n- Notion-Todoist Diff Sync (<workspace-name>)\n- Todoist-Notion Diff Sync (<workspace-name>)\n \nMake sure to **activate every workflow** which requires it.\nAlso save this workflow to avoid duplicates.\n\n### First run\n- Execute the **Notion-Todoist Full Sync** workflow manually first\n- Now that the initial sync is done and you can start using the realtime sync"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "a3febb48-a786-4168-8e4f-61821e15858e",
"connections": {
"Wait": {
"main": [
[
{
"node": "Update tries",
"type": "main",
"index": 0
}
]
]
},
"Merge": {
"main": [
[
{
"node": "Loop Over Items1",
"type": "main",
"index": 0
},
{
"node": "Merge summary",
"type": "main",
"index": 0
}
]
]
},
"Wait1": {
"main": [
[
{
"node": "Update tries1",
"type": "main",
"index": 0
}
]
]
},
"Wait2": {
"main": [
[
{
"node": "Update tries2",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "Map Notion to Todoist",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "Execute Workflow",
"type": "main",
"index": 0
}
],
[
{
"node": "Execute Workflow1",
"type": "main",
"index": 0
}
]
]
},
"Todoist": {
"main": [
[
{
"node": "Pick Todoist Fields",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Aggregate": {
"main": [
[
{
"node": "Update section (Sync API)",
"type": "main",
"index": 0
}
]
]
},
"Set tries": {
"main": [
[
{
"node": "Get Todoist Task",
"type": "main",
"index": 0
}
]
]
},
"Set tries1": {
"main": [
[
{
"node": "Get Todoist Task2",
"type": "main",
"index": 0
}
]
]
},
"Set tries2": {
"main": [
[
{
"node": "Get Todoist Task1",
"type": "main",
"index": 0
}
]
]
},
"Extract IDs": {
"main": [
[
{
"node": "Notion trigger reference",
"type": "main",
"index": 0
}
]
]
},
"Get projects": {
"main": [
[
{
"node": "Prep Dropdown1",
"type": "main",
"index": 0
}
]
]
},
"Get sections": {
"main": [
[
{
"node": "Generate config",
"type": "main",
"index": 0
}
]
]
},
"Update tries": {
"main": [
[
{
"node": "If tries left",
"type": "main",
"index": 0
}
]
]
},
"Generate UUID": {
"main": [
[
{
"node": "Prepare Batching Items",
"type": "main",
"index": 0
}
]
]
},
"Get variables": {
"main": [
[
{
"node": "Verify security token",
"type": "main",
"index": 0
}
]
]
},
"If tries left": {
"main": [
[
{
"node": "Set tries",
"type": "main",
"index": 0
}
],
[
{
"node": "Retry limit reached",
"type": "main",
"index": 0
}
]
]
},
"Merge summary": {
"main": [
[
{
"node": "Filter out status changes",
"type": "main",
"index": 0
}
]
]
},
"Prep Dropdown": {
"main": [
[
{
"node": "Choose Notion Database",
"type": "main",
"index": 0
}
]
]
},
"Update tries1": {
"main": [
[
{
"node": "If tries left1",
"type": "main",
"index": 0
}
]
]
},
"Update tries2": {
"main": [
[
{
"node": "If tries left2",
"type": "main",
"index": 0
}
]
]
},
"Add project ID": {
"main": [
[
{
"node": "If Todoist ID exists",
"type": "main",
"index": 0
}
]
]
},
"Body is array?": {
"main": [
[
{
"node": "Split out Notion changes",
"type": "main",
"index": 0
}
],
[
{
"node": "Notion trigger reference",
"type": "main",
"index": 0
}
]
]
},
"Due date empty": {
"main": [
[
{
"node": "Mark as Focussed in Notion",
"type": "main",
"index": 0
}
]
]
},
"Execution Data": {
"main": [
[
{
"node": "Check if Todoist ID is locked",
"type": "main",
"index": 0
}
]
]
},
"Generate UUID1": {
"main": [
[
{
"node": "Update section (Sync API)1",
"type": "main",
"index": 0
}
]
]
},
"Generate UUID2": {
"main": [
[
{
"node": "Update section (Sync API)2",
"type": "main",
"index": 0
}
]
]
},
"Get todoist ID": {
"main": [
[
{
"node": "Lock Todoist ID4",
"type": "main",
"index": 0
}
]
]
},
"If tries left1": {
"main": [
[
{
"node": "Set tries1",
"type": "main",
"index": 0
}
],
[
{
"node": "Retry limit reached1",
"type": "main",
"index": 0
}
]
]
},
"If tries left2": {
"main": [
[
{
"node": "Set tries2",
"type": "main",
"index": 0
}
],
[
{
"node": "Retry limit reached2",
"type": "main",
"index": 0
}
]
]
},
"Notion Webhook": {
"main": [
[
{
"node": "Body is array?",
"type": "main",
"index": 0
}
]
]
},
"OAuth redirect": {
"main": [
[
{
"node": "Get variables",
"type": "main",
"index": 0
}
]
]
},
"Prep Dropdown1": {
"main": [
[
{
"node": "Choose Todoist Project",
"type": "main",
"index": 0
}
]
]
},
"Status changed": {
"main": [
[
{
"node": "Get mapped item1",
"type": "main",
"index": 0
}
]
]
},
"Status is Done": {
"main": [
[
{
"node": "Mark as Completed in Todoist",
"type": "main",
"index": 0
}
],
[
{
"node": "Delete Task in Todoist",
"type": "main",
"index": 0
}
]
]
},
"Exchange Tokens": {
"main": [
[
{
"node": "Respond with success",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond with error",
"type": "main",
"index": 0
}
]
]
},
"Execution Data1": {
"main": [
[
{
"node": "Get Notion Task1",
"type": "main",
"index": 0
}
]
]
},
"Execution Data2": {
"main": [
[
{
"node": "Update Description in Todoist",
"type": "main",
"index": 0
},
{
"node": "Lock Notion ID",
"type": "main",
"index": 0
}
]
]
},
"Execution Data3": {
"main": [
[
{
"node": "Switch by Event",
"type": "main",
"index": 0
}
]
]
},
"Execution Data4": {
"main": [
[
{
"node": "Mark as Obsolete in Notion",
"type": "main",
"index": 0
}
]
]
},
"Execution Data5": {
"main": [
[
{
"node": "Check if Notion ID is locked",
"type": "main",
"index": 0
}
]
]
},
"Execution Data6": {
"main": [
[
{
"node": "Status is Obsolete?",
"type": "main",
"index": 0
}
]
]
},
"Generate config": {
"main": [
[
{
"node": "Return config JSON",
"type": "main",
"index": 0
}
]
]
},
"Get Notion Task": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Get Notion task": {
"main": [
[
{
"node": "Map Notion to Todoist2",
"type": "main",
"index": 0
}
]
]
},
"Get mapped item": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Get todoist ID1": {
"main": [
[
{
"node": "Lock Todoist ID4",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[
{
"node": "Exists/Completed in Notion",
"type": "main",
"index": 0
}
],
[
{
"node": "Get Notion Task",
"type": "main",
"index": 0
}
]
]
},
"Store variables": {
"main": [
[
{
"node": "Redirect to Auth Page",
"type": "main",
"index": 0
}
]
]
},
"Switch by Event": {
"main": [
[
{
"node": "Differences exist1",
"type": "main",
"index": 0
},
{
"node": "Description has changed in Todoist",
"type": "main",
"index": 0
}
],
[
{
"node": "Requires completion change1",
"type": "main",
"index": 0
}
],
[
{
"node": "Requires completion change1",
"type": "main",
"index": 0
}
]
]
},
"Todoist Webhook": {
"main": [
[
{
"node": "Switch by project",
"type": "main",
"index": 0
}
]
]
},
"Compare Datasets": {
"main": [
[
{
"node": "Handle empty dates2",
"type": "main",
"index": 0
}
],
[],
[
{
"node": "Handle empty dates",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Get Notion Task1": {
"main": [
[
{
"node": "Notion Task not found",
"type": "main",
"index": 0
}
]
]
},
"Get Notion Task2": {
"main": [
[
{
"node": "Notion Task found1",
"type": "main",
"index": 0
}
]
]
},
"Get Todoist Task": {
"main": [
[
{
"node": "Mark as Incomplete in Todoist",
"type": "main",
"index": 0
}
],
[
{
"node": "Catch known error",
"type": "main",
"index": 0
}
]
]
},
"Get mapped item1": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Get mapped item2": {
"main": [
[
{
"node": "Merge summary",
"type": "main",
"index": 1
}
]
]
},
"Loop Over Items1": {
"main": [
[],
[
{
"node": "Generate UUID",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
},
{
"node": "Todoist",
"type": "main",
"index": 0
},
{
"node": "Globals",
"type": "main",
"index": 0
}
]
]
},
"Store Todoist ID": {
"main": [
[
{
"node": "Get mapped item2",
"type": "main",
"index": 0
}
]
]
},
"Catch known error": {
"main": [
[
{
"node": "Create task in Todoist",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait",
"type": "main",
"index": 0
}
]
]
},
"Differences exist": {
"main": [
[
{
"node": "Update task in Todoist1",
"type": "main",
"index": 0
}
]
]
},
"Get Todoist Task1": {
"main": [
[
{
"node": "Map Todoist to Notion1",
"type": "main",
"index": 0
}
],
[
{
"node": "Catch known error2",
"type": "main",
"index": 0
}
]
]
},
"Get Todoist Task2": {
"main": [
[
{
"node": "Differences exist",
"type": "main",
"index": 0
},
{
"node": "Requires completion change",
"type": "main",
"index": 0
},
{
"node": "Status changed but not empty",
"type": "main",
"index": 0
}
],
[
{
"node": "Catch known error1",
"type": "main",
"index": 0
}
]
]
},
"Set creating flag": {
"main": [
[
{
"node": "Create task in Todoist1",
"type": "main",
"index": 0
}
]
]
},
"Switch by project": {
"main": [
[
{
"node": "Todoist trigger reference",
"type": "main",
"index": 0
}
]
]
},
"Todoist ID exists": {
"main": [
[
{
"node": "Delete Task in Todoist2",
"type": "main",
"index": 0
}
]
]
},
"Catch known error1": {
"main": [
[
{
"node": "Status is not Done",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait1",
"type": "main",
"index": 0
}
]
]
},
"Catch known error2": {
"main": [
[
{
"node": "End here",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait2",
"type": "main",
"index": 0
}
]
]
},
"Differences exist1": {
"main": [
[
{
"node": "Update task in Notion",
"type": "main",
"index": 0
},
{
"node": "Due date empty",
"type": "main",
"index": 0
}
]
]
},
"Handle empty dates": {
"main": [
[
{
"node": "Update Task in Todoist",
"type": "main",
"index": 0
}
]
]
},
"Map summary fields": {
"main": [
[
{
"node": "Convert to HTML Table",
"type": "main",
"index": 0
}
]
]
},
"Notion Task found1": {
"main": [
[
{
"node": "Execution Data4",
"type": "main",
"index": 0
}
]
]
},
"Set creating flag1": {
"main": [
[
{
"node": "Get Backlog Section ID",
"type": "main",
"index": 0
}
]
]
},
"Status is not Done": {
"main": [
[
{
"node": "Check if creating flag exists",
"type": "main",
"index": 0
}
]
]
},
"Todoist ID exists?": {
"main": [
[
{
"node": "Set tries1",
"type": "main",
"index": 0
}
],
[
{
"node": "Check if creating flag exists",
"type": "main",
"index": 0
}
]
]
},
"Append Notion Block": {
"main": [
[
{
"node": "Lock Notion ID",
"type": "main",
"index": 0
}
]
]
},
"Generate email body": {
"main": [
[
{
"node": "Gmail",
"type": "main",
"index": 0
}
]
]
},
"Handle empty dates1": {
"main": [
[
{
"node": "Update task in Todoist before closing",
"type": "main",
"index": 0
}
]
]
},
"Handle empty dates2": {
"main": [
[
{
"node": "Add project ID",
"type": "main",
"index": 0
}
]
]
},
"Has been completed?": {
"main": [
[
{
"node": "Mark as Completed in Todoist1",
"type": "main",
"index": 0
}
],
[
{
"node": "Mark as Incomplete in Todoist1",
"type": "main",
"index": 0
}
]
]
},
"Pick Todoist Fields": {
"main": [
[
{
"node": "Compare Datasets",
"type": "main",
"index": 1
}
]
]
},
"Status is Obsolete?": {
"main": [
[
{
"node": "Todoist ID exists",
"type": "main",
"index": 0
}
],
[
{
"node": "Neither focussed nor planned",
"type": "main",
"index": 0
}
]
]
},
"Get Notion Databases": {
"main": [
[
{
"node": "Prep Dropdown",
"type": "main",
"index": 0
}
]
]
},
"If Todoist ID exists": {
"main": [
[
{
"node": "Set tries",
"type": "main",
"index": 0
}
],
[
{
"node": "Create task in Todoist",
"type": "main",
"index": 0
}
]
]
},
"Prepare summary data": {
"main": [
[
{
"node": "Merge summary",
"type": "main",
"index": 2
}
]
]
},
"Convert to HTML Table": {
"main": [
[
{
"node": "Generate email body",
"type": "main",
"index": 0
}
]
]
},
"Create task in Notion": {
"main": [
[
{
"node": "Execution Data2",
"type": "main",
"index": 0
},
{
"node": "Turn Markdown into Notion Blocks",
"type": "main",
"index": 0
}
]
]
},
"Map Notion to Todoist": {
"main": [
[
{
"node": "Compare Datasets",
"type": "main",
"index": 0
}
]
]
},
"Map Todoist to Notion": {
"main": [
[
{
"node": "Map summary fields",
"type": "main",
"index": 0
}
]
]
},
"Notion Task not found": {
"main": [
[
{
"node": "Check if creating flag exists1",
"type": "main",
"index": 0
}
],
[
{
"node": "Execution Data3",
"type": "main",
"index": 0
}
]
]
},
"Prepare summary data1": {
"main": [
[
{
"node": "Merge summary",
"type": "main",
"index": 3
}
]
]
},
"Prepare summary data2": {
"main": [
[
{
"node": "Merge summary",
"type": "main",
"index": 4
}
]
]
},
"Update task in Notion": {
"main": [
[
{
"node": "Lock Notion ID",
"type": "main",
"index": 0
}
]
]
},
"Verify security token": {
"main": [
[
{
"node": "Exchange Tokens",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond with error",
"type": "main",
"index": 0
}
]
]
},
"Choose Notion Database": {
"main": [
[
{
"node": "Get Notion Database ID",
"type": "main",
"index": 0
}
]
]
},
"Choose Todoist Project": {
"main": [
[
{
"node": "Get Todoist Project ID",
"type": "main",
"index": 0
}
]
]
},
"Create task in Todoist": {
"main": [
[
{
"node": "Store Todoist ID",
"type": "main",
"index": 0
},
{
"node": "Lock Todoist ID",
"type": "main",
"index": 0
}
]
]
},
"Delete Task in Todoist": {
"main": [
[
{
"node": "Prepare summary data2",
"type": "main",
"index": 0
}
]
]
},
"Get Backlog Section ID": {
"main": [
[
{
"node": "Generate UUID1",
"type": "main",
"index": 0
},
{
"node": "Create task in Notion",
"type": "main",
"index": 0
}
]
]
},
"Get Notion Database ID": {
"main": [
[
{
"node": "Get projects",
"type": "main",
"index": 0
}
]
]
},
"Get Todoist Project ID": {
"main": [
[
{
"node": "Get sections",
"type": "main",
"index": 0
}
]
]
},
"Map Notion to Todoist1": {
"main": [
[
{
"node": "Handle empty dates1",
"type": "main",
"index": 0
}
]
]
},
"Map Notion to Todoist2": {
"main": [
[
{
"node": "Execution Data6",
"type": "main",
"index": 0
}
]
]
},
"Map Todoist to Notion1": {
"main": [
[
{
"node": "Execution Data1",
"type": "main",
"index": 0
}
]
]
},
"Mark as Done in Notion": {
"main": [
[
{
"node": "Lock Notion ID",
"type": "main",
"index": 0
}
]
]
},
"Prepare Batching Items": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
]
]
},
"Update Task in Todoist": {
"main": [
[
{
"node": "Status changed",
"type": "main",
"index": 0
},
{
"node": "Lock Todoist ID2",
"type": "main",
"index": 0
},
{
"node": "Prepare summary data",
"type": "main",
"index": 0
}
]
]
},
"Update task in Todoist": {
"main": [
[
{
"node": "Get mapped item",
"type": "main",
"index": 0
}
]
]
},
"Create task in Todoist1": {
"main": [
[
{
"node": "Store Todoist ID1",
"type": "main",
"index": 0
},
{
"node": "Lock Todoist ID4",
"type": "main",
"index": 0
}
]
]
},
"Delete Task in Todoist2": {
"main": [
[
{
"node": "Get todoist ID1",
"type": "main",
"index": 0
}
]
]
},
"Generate security token": {
"main": [
[
{
"node": "Store variables",
"type": "main",
"index": 0
}
]
]
},
"Has not been completed?": {
"main": [
[
{
"node": "Mark as In Progress in Notion",
"type": "main",
"index": 0
},
{
"node": "Neither focussed nor planned1",
"type": "main",
"index": 0
}
],
[
{
"node": "Mark as Done in Notion",
"type": "main",
"index": 0
}
]
]
},
"Update task in Todoist1": {
"main": [
[
{
"node": "Get todoist ID",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow Trigger": {
"main": [
[]
]
},
"If event is not :deleted": {
"main": [
[
{
"node": "Set tries2",
"type": "main",
"index": 0
}
],
[
{
"node": "Get Notion Task2",
"type": "main",
"index": 0
}
]
]
},
"Notion trigger reference": {
"main": [
[
{
"node": "Globals2",
"type": "main",
"index": 0
},
{
"node": "Execution Data5",
"type": "main",
"index": 0
}
]
]
},
"Split out Notion changes": {
"main": [
[
{
"node": "Extract IDs",
"type": "main",
"index": 0
}
]
]
},
"Filter out status changes": {
"main": [
[
{
"node": "Map Todoist to Notion",
"type": "main",
"index": 0
}
]
]
},
"Todoist trigger reference": {
"main": [
[
{
"node": "Execution Data",
"type": "main",
"index": 0
},
{
"node": "Globals1",
"type": "main",
"index": 0
}
]
]
},
"Update section (Sync API)": {
"main": [
[
{
"node": "Loop Over Items1",
"type": "main",
"index": 0
}
]
]
},
"Exists/Completed in Notion": {
"main": [
[
{
"node": "Map Notion to Todoist1",
"type": "main",
"index": 0
}
],
[
{
"node": "Delete Task in Todoist",
"type": "main",
"index": 0
}
]
]
},
"Mark as Focussed in Notion": {
"main": [
[
{
"node": "Lock Notion ID",
"type": "main",
"index": 0
}
]
]
},
"Mark as Obsolete in Notion": {
"main": [
[
{
"node": "Lock Notion ID",
"type": "main",
"index": 0
}
]
]
},
"Requires completion change": {
"main": [
[
{
"node": "Has been completed?",
"type": "main",
"index": 0
}
]
]
},
"Update section (Sync API)2": {
"main": [
[
{
"node": "Get todoist ID",
"type": "main",
"index": 0
}
]
]
},
"Only continue if not locked": {
"main": [
[
{
"node": "If event is not :deleted",
"type": "main",
"index": 0
}
]
]
},
"Requires completion change1": {
"main": [
[
{
"node": "Has not been completed?",
"type": "main",
"index": 0
}
]
]
},
"Check if Notion ID is locked": {
"main": [
[
{
"node": "Only continue if not locked1",
"type": "main",
"index": 0
}
]
]
},
"Handle each block separately": {
"main": [
[
{
"node": "Append Notion Block",
"type": "main",
"index": 0
}
]
]
},
"Mark as Completed in Todoist": {
"main": [
[
{
"node": "Prepare summary data1",
"type": "main",
"index": 0
}
]
]
},
"Neither focussed nor planned": {
"main": [
[
{
"node": "Todoist ID exists",
"type": "main",
"index": 0
}
],
[
{
"node": "Todoist ID exists?",
"type": "main",
"index": 0
}
]
]
},
"Only continue if not locked1": {
"main": [
[
{
"node": "Get Notion task",
"type": "main",
"index": 0
}
]
]
},
"Status changed but not empty": {
"main": [
[
{
"node": "Generate UUID2",
"type": "main",
"index": 0
}
]
]
},
"Todoist Webhook Setup Helper": {
"main": [
[
{
"node": "Generate security token",
"type": "main",
"index": 0
}
]
]
},
"Check if Todoist ID is locked": {
"main": [
[
{
"node": "Only continue if not locked",
"type": "main",
"index": 0
}
]
]
},
"Check if creating flag exists": {
"main": [
[
{
"node": "Only continue if flag does not exist",
"type": "main",
"index": 0
}
]
]
},
"Mark as Completed in Todoist1": {
"main": [
[
{
"node": "Get todoist ID",
"type": "main",
"index": 0
}
]
]
},
"Mark as In Progress in Notion": {
"main": [
[
{
"node": "Lock Notion ID",
"type": "main",
"index": 0
}
]
]
},
"Mark as Incomplete in Todoist": {
"main": [
[
{
"node": "Update task in Todoist",
"type": "main",
"index": 0
},
{
"node": "Lock Todoist ID",
"type": "main",
"index": 0
}
]
]
},
"Neither focussed nor planned1": {
"main": [
[
{
"node": "Mark as Focussed in Notion",
"type": "main",
"index": 0
}
]
]
},
"Check if creating flag exists1": {
"main": [
[
{
"node": "Only continue if flag does not exist1",
"type": "main",
"index": 0
}
]
]
},
"Mark as Incomplete in Todoist1": {
"main": [
[
{
"node": "Get todoist ID",
"type": "main",
"index": 0
}
]
]
},
"Notion-Todoist Sync Setup Helper": {
"main": [
[
{
"node": "Get Notion Databases",
"type": "main",
"index": 0
}
]
]
},
"Turn Markdown into Notion Blocks": {
"main": [
[
{
"node": "Handle each block separately",
"type": "main",
"index": 0
}
]
]
},
"Description has changed in Todoist": {
"main": [
[
{
"node": "Update Description in Todoist1",
"type": "main",
"index": 0
}
]
]
},
"Only continue if flag does not exist": {
"main": [
[
{
"node": "Set creating flag",
"type": "main",
"index": 0
}
]
]
},
"Only continue if flag does not exist1": {
"main": [
[
{
"node": "Set creating flag1",
"type": "main",
"index": 0
}
]
]
},
"Update task in Todoist before closing": {
"main": [
[
{
"node": "Status is Done",
"type": "main",
"index": 0
},
{
"node": "Lock Todoist ID1",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 销售, 工程, 设计, 人力资源, 产品, 营销
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
在可视化参考库中探索n8n节点
在可视化参考库中探索n8n节点
If
Ftp
Set
+93
113 节点I versus AI
其他
GitHub 同步仪表板 - V2
具有提交历史和回滚功能的 GitHub 工作流版本控制仪表板
If
N8n
Set
+20
94 节点Eduard
开发运维
使用 n8n、Apify 和 OpenAI o3 自托管 AI 深度研究代理
使用 n8n、Apify 和 OpenAI o3 自托管 AI 深度研究代理
If
Set
Code
+20
87 节点Jimleuk
人工智能
[模板] AI宠物店 v8
🐶 AI宠物店助手 - 集成GPT-4o、Google日历和WhatsApp/Instagram/Facebook
If
N8n
Set
+38
244 节点Amanda Benks
销售
(Duc)深度研究市场模板
集成PerplexityAI研究和OpenAI内容的多层级WordPress博客生成器
If
Set
Xml
+28
132 节点Daniel Ng
人工智能
API架构提取器
API架构提取器
If
Set
Code
+22
88 节点Polina Medvedieva
工程
工作流信息
难度等级
高级
节点数量246
分类6
节点类型29
作者
Mario
@octionicWorkflow Optimization Expert | Software Architect. Use my link to book an initial consultation for custom built workflows using n8n.
外部链接
在 n8n.io 查看 →
分享此工作流