feat: add folder support for workflows (fixes #70)

This commit is contained in:
Praveen Mudalgeri
2025-08-05 09:40:47 +05:30
parent 307d530f9b
commit c4885eee92
2057 changed files with 985290 additions and 974268 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,793 @@
{
"nodes": [
{
"id": "421824c2-59a2-441b-aacc-7dadf2ec153b",
"name": "On clicking 'execute'",
"type": "n8n-nodes-base.manualTrigger",
"position": [
900,
1180
],
"parameters": {},
"typeVersion": 1
},
{
"id": "c6024a57-1957-4714-84e3-8d326c83cd89",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
420,
1560
],
"parameters": {
"color": 6,
"width": 1910.7813046051347,
"height": 731.7039821513649,
"content": "## Subworkflow"
},
"typeVersion": 1
},
{
"id": "07691901-a8d2-4891-860b-1d672361021b",
"name": "Execute Workflow Trigger",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
480,
1940
],
"parameters": {},
"typeVersion": 1
},
{
"id": "2b1dd138-7872-42ea-9882-8750ef4cf227",
"name": "n8n",
"type": "n8n-nodes-base.n8n",
"position": [
1300,
1280
],
"parameters": {
"filters": {},
"requestOptions": {}
},
"credentials": {
"n8nApi": {
"id": "t2YEgbUMXHjsykeF",
"name": "admin"
}
},
"typeVersion": 1
},
{
"id": "96c0c6a7-2a11-441d-8177-e0a18030daf9",
"name": "Return",
"type": "n8n-nodes-base.set",
"position": [
2140,
1760
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "8d513345-6484-431f-afb7-7cf045c90f4f",
"name": "Done",
"type": "boolean",
"value": true
}
]
}
},
"typeVersion": 3.3
},
{
"id": "6715d1ff-a1f0-4e1a-b96e-f680d1495047",
"name": "Get File",
"type": "n8n-nodes-base.httpRequest",
"position": [
1100,
1640
],
"parameters": {
"url": "={{ $json.download_url }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "443b18e8-c05b-444f-b323-dea0b3041939",
"name": "If file too large",
"type": "n8n-nodes-base.if",
"position": [
860,
1660
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "45ce825e-9fa6-430c-8931-9aaf22c42585",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.content }}",
"rightValue": ""
},
{
"id": "9619a55f-7fb1-4f24-b1a7-7aeb82365806",
"operator": {
"type": "string",
"operation": "notExists",
"singleValue": true
},
"leftValue": "={{ $json.error }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2
},
{
"id": "e460a2cd-f7af-4551-8ea2-84d9b9e5cb7f",
"name": "Merge Items",
"type": "n8n-nodes-base.merge",
"position": [
860,
1920
],
"parameters": {},
"typeVersion": 2
},
{
"id": "f795180a-66aa-4a86-acb0-96cf8c487db0",
"name": "isDiffOrNew",
"type": "n8n-nodes-base.code",
"position": [
1060,
1920
],
"parameters": {
"jsCode": "const orderJsonKeys = (jsonObj) => {\n const ordered = {};\n Object.keys(jsonObj).sort().forEach(key => {\n ordered[key] = jsonObj[key];\n });\n return ordered;\n}\n\n// Check if file returned with content\nif (Object.keys($input.all()[0].json).includes(\"content\")) {\n // Decode base64 content and parse JSON\n const origWorkflow = JSON.parse(Buffer.from($input.all()[0].json.content, 'base64').toString());\n const n8nWorkflow = $input.all()[1].json;\n \n // Order JSON objects\n const orderedOriginal = orderJsonKeys(origWorkflow);\n const orderedActual = orderJsonKeys(n8nWorkflow);\n\n // Determine difference\n if (JSON.stringify(orderedOriginal) === JSON.stringify(orderedActual)) {\n $input.all()[0].json.github_status = \"same\";\n } else {\n $input.all()[0].json.github_status = \"different\";\n $input.all()[0].json.n8n_data_stringy = JSON.stringify(orderedActual, null, 2);\n }\n $input.all()[0].json.content_decoded = orderedOriginal;\n// No file returned / new workflow\n} else if (Object.keys($input.all()[0].json).includes(\"data\")) {\n const origWorkflow = JSON.parse($input.all()[0].json.data);\n const n8nWorkflow = $input.all()[1].json;\n \n // Order JSON objects\n const orderedOriginal = orderJsonKeys(origWorkflow);\n const orderedActual = orderJsonKeys(n8nWorkflow);\n\n // Determine difference\n if (JSON.stringify(orderedOriginal) === JSON.stringify(orderedActual)) {\n $input.all()[0].json.github_status = \"same\";\n } else {\n $input.all()[0].json.github_status = \"different\";\n $input.all()[0].json.n8n_data_stringy = JSON.stringify(orderedActual, null, 2);\n }\n $input.all()[0].json.content_decoded = orderedOriginal;\n\n} else {\n // Order JSON object\n const n8nWorkflow = $input.all()[1].json;\n const orderedActual = orderJsonKeys(n8nWorkflow);\n \n // Proper formatting\n $input.all()[0].json.github_status = \"new\";\n $input.all()[0].json.n8n_data_stringy = JSON.stringify(orderedActual, null, 2);\n}\n\n// Return items\nreturn $input.all();\n"
},
"typeVersion": 1
},
{
"id": "30e7d6fc-327e-4693-95ce-376a3b1f145c",
"name": "Check Status",
"type": "n8n-nodes-base.switch",
"position": [
1460,
1920
],
"parameters": {
"rules": {
"rules": [
{
"value2": "same"
},
{
"output": 1,
"value2": "different"
},
{
"output": 2,
"value2": "new"
}
]
},
"value1": "={{$json.github_status}}",
"dataType": "string"
},
"typeVersion": 1
},
{
"id": "36f12309-c7fe-446f-9571-bd1005c18ed8",
"name": "Same file - Do nothing",
"type": "n8n-nodes-base.noOp",
"position": [
1680,
1760
],
"parameters": {},
"typeVersion": 1
},
{
"id": "45f0eaa7-259b-4908-b567-af2b3b5abb6d",
"name": "File is different",
"type": "n8n-nodes-base.noOp",
"position": [
1680,
1920
],
"parameters": {},
"typeVersion": 1
},
{
"id": "d16ec06b-7a3f-486e-8328-935ed3b4d565",
"name": "File is new",
"type": "n8n-nodes-base.noOp",
"position": [
1680,
2120
],
"parameters": {},
"typeVersion": 1
},
{
"id": "cdc7f306-b7d2-4de1-8e44-0bd8d49a679f",
"name": "Create new file",
"type": "n8n-nodes-base.github",
"position": [
1900,
2120
],
"parameters": {
"owner": {
"__rl": true,
"mode": "",
"value": "={{ $('Config').first().item.repo_owner }}"
},
"filePath": "={{ $('Config').first().item.repo_path }}{{ $json.subPath }}{{$('Execute Workflow Trigger').first().json.id}}.json",
"resource": "file",
"repository": {
"__rl": true,
"mode": "",
"value": "={{ $('Config').first().item.repo_name }}"
},
"fileContent": "={{$('isDiffOrNew').item.json[\"n8n_data_stringy\"]}}",
"commitMessage": "={{$('Execute Workflow Trigger').first().json.name}} ({{$json.github_status}})"
},
"typeVersion": 1
},
{
"id": "9785333a-4a86-448d-afc2-58b0aa50ea96",
"name": "Edit existing file",
"type": "n8n-nodes-base.github",
"position": [
1900,
1920
],
"parameters": {
"owner": {
"__rl": true,
"mode": "",
"value": "={{ $('Config').first().item.repo_owner }}"
},
"filePath": "={{ $('Config').first().item.repo_path }}{{ $json.subPath }}{{$('Execute Workflow Trigger').first().json.id}}.json",
"resource": "file",
"operation": "edit",
"repository": {
"__rl": true,
"mode": "",
"value": "={{ $('Config').first().item.repo_name }}"
},
"fileContent": "={{$('isDiffOrNew').item.json[\"n8n_data_stringy\"]}}",
"commitMessage": "={{$('Execute Workflow Trigger').first().json.name}} ({{$json.github_status}})"
},
"typeVersion": 1
},
{
"id": "806db72c-c9f6-461d-be1a-1e6867a25382",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
1500,
1280
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "e5c433e4-bf56-4a0a-906c-7d74f6fe7287",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
900,
1380
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 1,
"triggerAtMinute": 33
}
]
}
},
"typeVersion": 1.2
},
{
"id": "f6b566cb-0a15-4792-ba27-d6cd2a6c9453",
"name": "Create sub path",
"type": "n8n-nodes-base.set",
"position": [
1260,
1920
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "dae43d3b-56e5-4098-b602-862ebf5cd073",
"name": "subPath",
"type": "string",
"value": "={{ $('Execute Workflow Trigger').first().json.createdAt.split('-')[0] }}/{{ $('Execute Workflow Trigger').first().json.createdAt.split('-')[1] }}/"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.3
},
{
"id": "9e2412f6-df25-4c12-8faf-0200558b537c",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
420,
1100
],
"parameters": {
"color": 4,
"width": 385,
"height": 417,
"content": "## Backup to GitHub \nThis workflow will backup all instance workflows to GitHub every 24 hours.\n\nThe files are saved into folders using `YYYY/MM/` for the directory path and `ID.json` for the filename.\n\nThe Repo Owner, Repo Name and Main folder are set using the **Variables** feature but can be replaced with the `Config` node in the subworkflow. \n\nThe workflow runs calls itself to help reduce memory usage, Once the workflow has completed it will send an optional notification to Slack.\n\n### Time to Run\nTested with 1423 workflows on `1.44.1` it took under 30 minutes for the first run and under 12 minutes once the initial run is complete."
},
"typeVersion": 1
},
{
"id": "00fdb977-4f3e-49f6-81c3-bc7f9520914f",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
860,
1100
],
"parameters": {
"color": 7,
"width": 1272.6408145680155,
"height": 416.1856906618075,
"content": "## Main workflow loop"
},
"typeVersion": 1
},
{
"id": "0c00a374-566a-49c7-80de-66a991c4bf69",
"name": "Starting Message",
"type": "n8n-nodes-base.slack",
"position": [
1140,
1280
],
"webhookId": "c02eb407-5547-4aa0-9ebf-46dab67b63b6",
"parameters": {
"text": "=:information_source: Starting Workflow Backup [{{ $execution.id }}]",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "#notifications"
},
"otherOptions": {
"includeLinkToWorkflow": false
}
},
"typeVersion": 2.2
},
{
"id": "eb7d15be-7f5d-4e39-837b-06d740685af3",
"name": "Execute Workflow",
"type": "n8n-nodes-base.executeWorkflow",
"onError": "continueErrorOutput",
"position": [
1720,
1300
],
"parameters": {
"mode": "each",
"options": {},
"workflowId": "={{ $workflow.id }}"
},
"typeVersion": 1
},
{
"id": "c831a0eb-95e1-46b3-bbf8-5d5bd928ca0a",
"name": "Completed Notification",
"type": "n8n-nodes-base.slack",
"position": [
1720,
1120
],
"webhookId": "a0c6e8c8-5d71-40fa-b02b-63a7ed5726c4",
"parameters": {
"text": "=✅ Backup has completed - {{ $('n8n').all().length }} workflows have been processed.",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "#notifications"
},
"otherOptions": {}
},
"executeOnce": true,
"typeVersion": 2.2
},
{
"id": "00864cb8-c8e4-4324-be1b-7d093e1bc3bf",
"name": "Failed Flows",
"type": "n8n-nodes-base.slack",
"position": [
1920,
1320
],
"webhookId": "2a092edb-de12-490f-931b-34d70e7d7696",
"parameters": {
"text": "=:x: Failed to backup {{ $('Loop Over Items').item.json.id }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "#notifications"
},
"otherOptions": {
"includeLinkToWorkflow": false
}
},
"typeVersion": 2.2
},
{
"id": "e4d70af5-5c21-4340-8054-7ba0203f3ee1",
"name": "Get file data",
"type": "n8n-nodes-base.github",
"position": [
660,
1660
],
"parameters": {
"owner": {
"__rl": true,
"mode": "",
"value": "={{ $('Config').first().item.repo_owner }}"
},
"filePath": "={{ $('Config').first().item.repo_path }}{{ $('Execute Workflow Trigger').first().json.createdAt.split('-')[0] }}/{{ $('Execute Workflow Trigger').first().json.createdAt.split('-')[1] }}/{{$json.id}}.json",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "",
"value": "={{ $('Config').first().item.repo_name }}"
},
"asBinaryProperty": false,
"additionalParameters": {}
},
"typeVersion": 1,
"continueOnFail": true,
"alwaysOutputData": true
},
{
"id": "42ad4762-26fb-4686-9016-729e95c95324",
"name": "Config",
"type": "n8n-nodes-base.set",
"position": [
660,
1940
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "8f6d1741-772f-462a-811f-4c334185e4f0",
"name": "repo_owner",
"type": "string",
"value": "={{ $vars.repo_owner }}"
},
{
"id": "8cac215c-4fd7-422f-9fd2-6b2d1e5e0383",
"name": "repo_name",
"type": "string",
"value": "={{ $vars.repo_name }}"
},
{
"id": "eee305e9-4164-462a-86bd-80f0d58a31ae",
"name": "repo_path",
"type": "string",
"value": "={{ $vars.repo_path }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
}
],
"pinData": {},
"connections": {
"n8n": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Config": {
"main": [
[
{
"node": "Get file data",
"type": "main",
"index": 0
},
{
"node": "Merge Items",
"type": "main",
"index": 1
}
]
]
},
"Get File": {
"main": [
[
{
"node": "Merge Items",
"type": "main",
"index": 0
}
]
]
},
"File is new": {
"main": [
[
{
"node": "Create new file",
"type": "main",
"index": 0
}
]
]
},
"Merge Items": {
"main": [
[
{
"node": "isDiffOrNew",
"type": "main",
"index": 0
}
]
]
},
"isDiffOrNew": {
"main": [
[
{
"node": "Create sub path",
"type": "main",
"index": 0
}
]
]
},
"Check Status": {
"main": [
[
{
"node": "Same file - Do nothing",
"type": "main",
"index": 0
}
],
[
{
"node": "File is different",
"type": "main",
"index": 0
}
],
[
{
"node": "File is new",
"type": "main",
"index": 0
}
]
]
},
"Failed Flows": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Get file data": {
"main": [
[
{
"node": "If file too large",
"type": "main",
"index": 0
}
]
]
},
"Create new file": {
"main": [
[
{
"node": "Return",
"type": "main",
"index": 0
}
]
]
},
"Create sub path": {
"main": [
[
{
"node": "Check Status",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[
{
"node": "Completed Notification",
"type": "main",
"index": 0
}
],
[
{
"node": "Execute Workflow",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
],
[
{
"node": "Failed Flows",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Starting Message",
"type": "main",
"index": 0
}
]
]
},
"Starting Message": {
"main": [
[
{
"node": "n8n",
"type": "main",
"index": 0
}
]
]
},
"File is different": {
"main": [
[
{
"node": "Edit existing file",
"type": "main",
"index": 0
}
]
]
},
"If file too large": {
"main": [
[
{
"node": "Get File",
"type": "main",
"index": 0
}
],
[
{
"node": "Merge Items",
"type": "main",
"index": 0
}
]
]
},
"Edit existing file": {
"main": [
[
{
"node": "Return",
"type": "main",
"index": 0
}
]
]
},
"On clicking 'execute'": {
"main": [
[
{
"node": "Starting Message",
"type": "main",
"index": 0
}
]
]
},
"Same file - Do nothing": {
"main": [
[
{
"node": "Return",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow Trigger": {
"main": [
[
{
"node": "Config",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,269 @@
{
"meta": {
"instanceId": "8c8c5237b8e37b006a7adce87f4369350c58e41f3ca9de16196d3197f69eabcd"
},
"nodes": [
{
"id": "7917ccbb-ef43-4784-adb9-7347be1f1e20",
"name": "Set",
"type": "n8n-nodes-base.set",
"position": [
580,
560
],
"parameters": {
"values": {
"string": [
{
"name": "company",
"value": "={{$json[\"What *company* are you contacting us from?\"]}}"
},
{
"name": "name",
"value": "={{$json[\"Let's start with your *first and last name.*\"]}}"
},
{
"name": "email",
"value": "={{$json[\"What *email address* can we reach you at?\"]}}"
},
{
"name": "n8nFamiliar",
"value": "={{$json[\"How familiar are you with* n8n*?\"]}}"
},
{
"name": "questions",
"value": "={{$json[\"Do you have any *specific questions* about embedding n8n at this stage?\"]}}"
},
{
"name": "employees",
"value": "={{$json[\"How many employees?\"]}}"
}
]
},
"options": {},
"keepOnlySet": true
},
"typeVersion": 1
},
{
"id": "c0cc18d0-fdd1-4ef8-aabe-33bd13667c7d",
"name": "Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
540,
360
],
"parameters": {
"width": 760,
"height": 440,
"content": "## Format Typeform inputs to Pipedrive\nIn this example, we ask for the number of employees at a company. \n\nTo map this to Pipedrive, we need the unique item number coming from Pipedrive for each of these sections. This is what the function node does. \n\nIn the Pipedrive: Organization, we map this under the custom property.\n\n\n\n\n\n\n\n\n"
},
"typeVersion": 1
},
{
"id": "92646ffb-73fb-4fee-a2b4-5060c7e04b59",
"name": "Create Organization",
"type": "n8n-nodes-base.pipedrive",
"position": [
1060,
560
],
"parameters": {
"name": "={{$node[\"Map company size\"].json[\"company\"]}}",
"resource": "organization",
"additionalFields": {
"customProperties": {
"property": [
{
"name": "eb7a7fb64081a9b9100c0622c696c159330cf3d2",
"value": "={{$node[\"Map company size\"].json[\"pipedriveemployees\"]}}"
}
]
}
}
},
"credentials": {
"pipedriveApi": {
"id": "96",
"name": "Pipedrive account"
}
},
"typeVersion": 1
},
{
"id": "4c1b7376-cc1f-4974-9110-7e1481e3fdbe",
"name": "Create Person",
"type": "n8n-nodes-base.pipedrive",
"position": [
1400,
560
],
"parameters": {
"name": "={{$node[\"Map company size\"].json[\"name\"]}}",
"resource": "person",
"additionalFields": {
"email": [
"={{$node[\"On form completion\"].json[\"What *email address* can we reach you at?\"]}}"
],
"org_id": "={{$json.id}}"
}
},
"credentials": {
"pipedriveApi": {
"id": "96",
"name": "Pipedrive account"
}
},
"typeVersion": 1
},
{
"id": "5c463f99-38e0-4c2e-a34c-86fc199b9d1f",
"name": "Create Lead",
"type": "n8n-nodes-base.pipedrive",
"position": [
1600,
560
],
"parameters": {
"title": "={{$node[\"Map company size\"].json[\"company\"]}} lead",
"resource": "lead",
"organization_id": "={{$node[\"Create Organization\"].json.id}}",
"additionalFields": {
"person_id": "={{$json.id}}"
}
},
"credentials": {
"pipedriveApi": {
"id": "96",
"name": "Pipedrive account"
}
},
"typeVersion": 1
},
{
"id": "d63383ca-a71e-4384-a3fb-942c25d7fe01",
"name": "Create Note",
"type": "n8n-nodes-base.pipedrive",
"position": [
1800,
560
],
"parameters": {
"content": "=Website form submitted\n\nQuestion:\n{{$node[\"Map company size\"].json[\"questions\"]}}\n\nCompany Size:\n{{$node[\"Set\"].json[\"employees\"]}}",
"resource": "note",
"additionalFields": {
"lead_id": "={{$json.id}}"
}
},
"credentials": {
"pipedriveApi": {
"id": "96",
"name": "Pipedrive account"
}
},
"typeVersion": 1
},
{
"id": "78568df6-1c6b-493d-b186-9f9246de518a",
"name": "On form completion",
"type": "n8n-nodes-base.typeformTrigger",
"position": [
380,
560
],
"webhookId": "[UPDATE ME]",
"parameters": {
"formId": "[UPDATE ME]"
},
"credentials": {
"typeformApi": {
"id": "21",
"name": "Typeform account"
}
},
"typeVersion": 1
},
{
"id": "6bc56059-6ae7-48bd-838c-08e717bd6bd4",
"name": "Map company size",
"type": "n8n-nodes-base.code",
"position": [
820,
560
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "switch ($input.item.json.employees) {\n case '< 20':\n // small\n $input.item.json.pipedriveemployees='59' \n break;\n case '20 - 100':\n // medium\n $input.item.json.pipedriveemployees='60' \n break;\n case '101 - 500':\n // large\n $input.item.json.pipedriveemployees='73' \n break;\n case '501 - 1000':\n // xlarge\n $input.item.json.pipedriveemployees='74' \n break;\n case '1000+':\n // Enterprise\n $input.item.json.pipedriveemployees='61' \n break;\n}\nreturn $input.item;\n"
},
"typeVersion": 1
}
],
"connections": {
"Set": {
"main": [
[
{
"node": "Map company size",
"type": "main",
"index": 0
}
]
]
},
"Create Lead": {
"main": [
[
{
"node": "Create Note",
"type": "main",
"index": 0
}
]
]
},
"Create Person": {
"main": [
[
{
"node": "Create Lead",
"type": "main",
"index": 0
}
]
]
},
"Map company size": {
"main": [
[
{
"node": "Create Organization",
"type": "main",
"index": 0
}
]
]
},
"On form completion": {
"main": [
[
{
"node": "Set",
"type": "main",
"index": 0
}
]
]
},
"Create Organization": {
"main": [
[
{
"node": "Create Person",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,260 @@
{
"meta": {
"instanceId": "237600ca44303ce91fa31ee72babcdc8493f55ee2c0e8aa2b78b3b4ce6f70bd9"
},
"nodes": [
{
"id": "b220e0c7-3c34-4221-8fee-11c133a5345b",
"name": "Get ticket",
"type": "n8n-nodes-base.zendesk",
"position": [
740,
540
],
"parameters": {
"id": "={{$node[\"On new Zendesk ticket\"].json[\"body\"][\"id\"]}}",
"operation": "get"
},
"credentials": {
"zendeskApi": {
"id": "24",
"name": "[UPDATE ME]"
}
},
"typeVersion": 1
},
{
"id": "e58834a7-1a94-429f-a50c-2e27293c32a0",
"name": "IF",
"type": "n8n-nodes-base.if",
"position": [
1140,
540
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{$node[\"Determine\"].json[\"Slack Thread ID\"]}}",
"operation": "isNotEmpty"
}
]
}
},
"typeVersion": 1
},
{
"id": "c6f82ab9-54f4-4f91-a4d9-018739c6519d",
"name": "Update ticket",
"type": "n8n-nodes-base.zendesk",
"notes": "Update the Zendesk ticket by adding the Jira issue key to the \"Jira Issue Key\" field.",
"position": [
1540,
640
],
"parameters": {
"id": "={{$node[\"On new Zendesk ticket\"].json[\"body\"][\"id\"]}}",
"operation": "update",
"updateFields": {
"customFieldsUi": {
"customFieldsValues": [
{
"id": 7022397804317,
"value": "={{$node[\"Create thread\"].json[\"ts\"]}}"
}
]
}
}
},
"credentials": {
"zendeskApi": {
"id": "24",
"name": "[UPDATE ME]"
}
},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "74d93ba5-d82d-4cc4-a177-bd86dbc18534",
"name": "On new Zendesk ticket",
"type": "n8n-nodes-base.webhook",
"position": [
540,
540
],
"webhookId": "b7845b15-0a44-4be5-b513-f4f4bb8989a6",
"parameters": {
"path": "b7845b15-0a44-4be5-b513-f4f4bb8989a6",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 1
},
{
"id": "65d387cd-5c7a-4567-9a3c-9fa033f98ac9",
"name": "Create thread",
"type": "n8n-nodes-base.slack",
"position": [
1340,
640
],
"parameters": {
"text": "={{$node[\"Get ticket\"].json[\"subject\"]}}",
"channel": "={{$node[\"Configure\"].parameter[\"values\"][\"string\"][0][\"value\"]}}",
"attachments": [],
"otherOptions": {},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"id": "28",
"name": "[UPDATE ME]"
}
},
"typeVersion": 1
},
{
"id": "50f5aa84-70bc-4b08-a9cc-576fbed72636",
"name": "Create reply on existing thread",
"type": "n8n-nodes-base.slack",
"position": [
1340,
440
],
"parameters": {
"text": "={{$node[\"On new Zendesk ticket\"].json[\"body\"][\"comment\"]}}",
"channel": "={{$node[\"Configure\"].parameter[\"values\"][\"string\"][0][\"value\"]}}",
"attachments": [],
"otherOptions": {
"thread_ts": "={{$node[\"Determine\"].json[\"Slack Thread ID\"]}}"
},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"id": "28",
"name": "[UPDATE ME]"
}
},
"typeVersion": 1
},
{
"id": "6d5e8df0-4b0b-487c-81be-93359976dd90",
"name": "Configure",
"type": "n8n-nodes-base.set",
"position": [
540,
360
],
"parameters": {
"values": {
"string": [
{
"name": "Slack channel",
"value": "#zendesk-updates"
}
]
},
"options": {
"dotNotation": false
}
},
"typeVersion": 1
},
{
"id": "934b95bb-2ffa-40a4-a2ca-02cfd646dd78",
"name": "Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-20,
360
],
"parameters": {
"width": 469.4813676974197,
"height": 268.2900466166276,
"content": "## Sync Zendesk tickets to Slack threads\n### Setup\n1. Add your [Zendesk credential](https://docs.n8n.io/integrations/builtin/credentials/zendesk/) to the `Get ticket` and `Update ticket` nodes.\n2. Add your [Slack credential](https://docs.n8n.io/integrations/builtin/credentials/slack/) to `Create Thread` and `Create reply on existing thread` nodes.\n3. Open `Configure` node and change \"Slack channel\" value to your slack channel (like #zendesk-updates).\n4. Activate the workflow so it runs automatically each time a Zendesk ticket is created."
},
"typeVersion": 1
},
{
"id": "b582f7ff-7cc6-48dc-89fc-bc8bde13b06e",
"name": "Code",
"type": "n8n-nodes-base.code",
"notes": "if thread was created already in Slack",
"position": [
940,
540
],
"parameters": {
"jsCode": "/* configure here =========================================================== */\n/* Zendesk field ID which represents the \"Slack Thread ID\" field.\n*/\nconst ISSUE_KEY_FIELD_ID = 7022397804317;\n\n/* ========================================================================== */\nnew_items = [];\n\nfor (item of $items(\"Get ticket\")) {\n \n // instantiate a new variable for status\n var custom_fields = item.json[\"custom_fields\"];\n var slack_thread_id = \"\";\n for (var i = 0; i < custom_fields.length; i++) {\n if (custom_fields[i].id == ISSUE_KEY_FIELD_ID) {\n slack_thread_id = custom_fields[i].value;\n break;\n }\n }\n\n // push the new item to the new_items array\n new_items.push({\n \"Slack Thread ID\": slack_thread_id\n });\n}\n\nreturn new_items;"
},
"notesInFlow": true,
"typeVersion": 1
}
],
"connections": {
"IF": {
"main": [
[
{
"node": "Create reply on existing thread",
"type": "main",
"index": 0
}
],
[
{
"node": "Create thread",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "IF",
"type": "main",
"index": 0
}
]
]
},
"Get ticket": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Create thread": {
"main": [
[
{
"node": "Update ticket",
"type": "main",
"index": 0
}
]
]
},
"On new Zendesk ticket": {
"main": [
[
{
"node": "Get ticket",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,665 @@
{
"meta": {
"instanceId": "a2434c94d549548a685cca39cc4614698e94f527bcea84eefa363f1037ae14cd"
},
"nodes": [
{
"id": "9be821db-fbc7-4168-962f-26c8382cefbf",
"name": "If charge has customer",
"type": "n8n-nodes-base.if",
"position": [
1560,
880
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json[\"customer\"] }}",
"operation": "isNotEmpty"
}
]
}
},
"typeVersion": 1
},
{
"id": "d06bae31-6856-4941-b86c-c611fc9d3da6",
"name": "Get customer",
"type": "n8n-nodes-base.stripe",
"position": [
2160,
920
],
"parameters": {
"resource": "customer",
"customerId": "={{ $json[\"customer\"] }}"
},
"credentials": {
"stripeApi": {
"id": "22",
"name": "[UPDATE ME]"
}
},
"typeVersion": 1
},
{
"id": "4e0d87bf-084f-4958-b2d3-cf7985f8c901",
"name": "On schedule",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-400,
1400
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1
},
{
"id": "fb620c92-5e22-4a9c-9320-847442b5e955",
"name": "Remove duplicate customers",
"type": "n8n-nodes-base.itemLists",
"position": [
1880,
920
],
"parameters": {
"compare": "selectedFields",
"options": {
"removeOtherFields": true
},
"operation": "removeDuplicates",
"fieldsToCompare": {
"fields": [
{
"fieldName": "customer"
}
]
}
},
"typeVersion": 1
},
{
"id": "3ad7554d-24b3-4ee2-8136-6a151bf06c71",
"name": "Aggregate `amount_captured`",
"type": "n8n-nodes-base.itemLists",
"position": [
1880,
540
],
"parameters": {
"options": {},
"operation": "aggregateItems",
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "amount_captured"
}
]
}
},
"typeVersion": 1
},
{
"id": "c8448580-40f2-4cf6-87ba-80903555d5a5",
"name": "Aggregate totals",
"type": "n8n-nodes-base.code",
"position": [
2820,
1360
],
"parameters": {
"jsCode": "// aggregate `amounts_captured` with the customer, taking note \n// that `aggregateAmountsPerContact` is the value in cents\nconst aggregateAmountsPerContact = new Object();\nfor (const item of $input.all()) {\n if (aggregateAmountsPerContact[item.json.email] == null) {\n aggregateAmountsPerContact[item.json.email] = 0;\n }\n aggregateAmountsPerContact[item.json.email] += item.json.amount_captured;\n}\n\n// parse the data in a way that is usable in future nodes, and\n// converts amounts from cents to dollars\nconst parsed = [];\nfor (const contact of Object.keys(aggregateAmountsPerContact)) {\n parsed.push({\n email: contact,\n amount_captured: aggregateAmountsPerContact[contact] / 100\n });\n}\n\nreturn parsed;"
},
"typeVersion": 1
},
{
"id": "dedaf89e-84d1-4964-9c87-94beea4adf26",
"name": "Create or update customer",
"type": "n8n-nodes-base.hubspot",
"position": [
3140,
1360
],
"parameters": {
"email": "={{$node[\"Aggregate totals\"].json[\"email\"]}}",
"resource": "contact",
"authentication": "oAuth2",
"additionalFields": {
"customPropertiesUi": {
"customPropertiesValues": [
{
"value": "={{$node[\"Aggregate totals\"].json[\"amount_captured\"]}}",
"property": "={{$(\"Configure\").first().json[\"contactPropertyId\"]}}"
}
]
}
}
},
"credentials": {
"hubspotOAuth2Api": {
"id": "11",
"name": "[UPDATE ME]"
}
},
"notesInFlow": false,
"typeVersion": 1
},
{
"id": "4c419e90-facc-4a64-83f2-d349264338c6",
"name": "Merge data",
"type": "n8n-nodes-base.merge",
"position": [
2520,
1360
],
"parameters": {
"mode": "combine",
"options": {},
"mergeByFields": {
"values": [
{
"field1": "id",
"field2": "customer"
}
]
}
},
"typeVersion": 2
},
{
"id": "6a21495f-e567-4b0f-b584-34306bf7fa18",
"name": "Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
2460,
1160
],
"parameters": {
"width": 219.61431588546765,
"height": 378.32426823578305,
"content": "### `Merge data`\nMore specifically, we merge the Stripe data from `Get charges` and `Get customer` nodes. Only the charges with customers on them will continue."
},
"typeVersion": 1
},
{
"id": "7319c8fe-9e55-43d9-a634-3a7884268016",
"name": "Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
2760,
1160
],
"parameters": {
"width": 218.46574043407196,
"height": 379.1631729345614,
"content": "### `Aggregate totals`\nGiven the merged data, we now aggregate the amounts from charges to the customers/contacts."
},
"typeVersion": 1
},
{
"id": "c24d972b-270d-4467-9352-4ced18e377c0",
"name": "Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1780,
400
],
"parameters": {
"width": 297.57428772569784,
"height": 325.06310253513686,
"content": "### ``Aggregate `amount_captured` ``\nThis does nothing. It is an alternative way to find the totals of every charge in existence in Stripe. Potentially useful for debugging purposes."
},
"typeVersion": 1
},
{
"id": "43da8885-fac3-4cb7-9f01-c4770cd0b030",
"name": "Get all charges",
"type": "n8n-nodes-base.stripe",
"position": [
1300,
1380
],
"parameters": {
"resource": "charge",
"operation": "getAll",
"returnAll": true
},
"credentials": {
"stripeApi": {
"id": "22",
"name": "[UPDATE ME]"
}
},
"typeVersion": 1
},
{
"id": "abfe75f5-c36f-4904-a703-cb8d1d83b686",
"name": "Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-960,
1220
],
"parameters": {
"width": 504,
"height": 510.0404950205649,
"content": "## Sync Stripe charges to HubSpot contacts\nThis workflow pushes Stripe charges to HubSpot contacts. It uses the Stripe API to get all charges and the HubSpot API to update the contacts. The workflow will create a new HubSpot property to store the total amount charged. If the property already exists, it will update the property.\n\n### How it works\n1. On a schedule, the first Stripe node gets all charges. The default schedule is once a day at midnight.\n2. Once the charges are returned, the second Stripe node gets extra customer information.\n3. Once the customer information is returned, `Merge data` node will merge the customer information with the charges so that the next node `Aggregate totals` can calculate the total amount charged per contact.\n4. Once we have the total amount charged per contact, the `Create or update customer` node will create a new HubSpot property to store the total amount charged. If the property already exists, it will update the property.\n\n\n\nWorkflow written by [David Sha](https://davidsha.me)."
},
"typeVersion": 1
},
{
"id": "67e44a47-18db-48a3-a08e-c4f2afb13a30",
"name": "Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1780,
760
],
"parameters": {
"width": 298.2919335506821,
"height": 339.6783118583311,
"content": "### `Remove duplicate customers`\nEnsures that we do not poll Stripe too many times unnecessarily. If multiple charges have the same customer, we ensure that we do not ask for the same information again."
},
"typeVersion": 1
},
{
"id": "02d46492-f3ba-47fe-ba88-f2baad30fc73",
"name": "Get HubSpot field",
"type": "n8n-nodes-base.httpRequest",
"position": [
580,
1540
],
"parameters": {
"url": "=https://api.hubapi.com/crm/v3/properties/contact/{{$(\"Configure\").first().json[\"contactPropertyId\"]}}",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "hubspotOAuth2Api"
},
"credentials": {
"hubspotOAuth2Api": {
"id": "11",
"name": "[UPDATE ME]"
}
},
"typeVersion": 3,
"continueOnFail": true
},
{
"id": "827882c4-5d3f-4cc6-b876-ae575a9a1b36",
"name": "Create field in HubSpot",
"type": "n8n-nodes-base.httpRequest",
"position": [
980,
1660
],
"parameters": {
"url": "https://api.hubapi.com/crm/v3/properties/contact",
"method": "POST",
"options": {
"response": {
"response": {
"neverError": true
}
}
},
"sendBody": true,
"authentication": "predefinedCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "name",
"value": "={{$(\"Configure\").first().json[\"contactPropertyId\"]}}"
},
{
"name": "label",
"value": "={{$(\"Configure\").first().json[\"contactPropertyLabelName\"]}}"
},
{
"name": "type",
"value": "number"
},
{
"name": "fieldType",
"value": "number"
},
{
"name": "groupName",
"value": "contactinformation"
},
{
"name": "formField",
"value": "false"
},
{
"name": "description",
"value": "=The total spend determined by the charges in Stripe. This is a field required for \"{{$workflow.name}}\" n8n workflow."
}
]
},
"nodeCredentialType": "hubspotOAuth2Api"
},
"credentials": {
"hubspotOAuth2Api": {
"id": "11",
"name": "[UPDATE ME]"
}
},
"typeVersion": 3
},
{
"id": "b4092718-bf35-49b5-aefa-b9900596fcb5",
"name": "Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
500,
1480
],
"parameters": {
"width": 656.5118956254801,
"height": 367.20468504951214,
"content": "### Create HubSpot field if required\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n_These nodes create a HubSpot field if required.\nIt makes the contact field that this workflow uses \nto store the Stripe information. To disable this \nsection, in `Configure` node change `checkFields`\nto false._"
},
"typeVersion": 1
},
{
"id": "6d74e2e3-bd95-4ccb-89c0-3d6f8f1e01f9",
"name": "Configure",
"type": "n8n-nodes-base.set",
"position": [
-80,
1400
],
"parameters": {
"values": {
"string": [
{
"name": "contactPropertyId",
"value": "stripe___total_spend"
},
{
"name": "contactPropertyLabelName",
"value": "Stripe - Total Spend"
}
],
"boolean": [
{
"name": "checkFields",
"value": true
}
]
},
"options": {}
},
"typeVersion": 1
},
{
"id": "8a8262bc-0742-4529-9f10-328c338854fe",
"name": "Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-200,
1340
],
"parameters": {
"width": 338.8262165118159,
"height": 505.43603897947025,
"content": "### Configuration\n\n\n\n\n\n\n\n\n\n\n\n\nBy default, this does not need to be updated. \n\n__`contactPropertyId` (required)__: Only change if the specific HubSpot field ID has been taken.\n\n__`contactPropertyLabelName` (required)__: Change if you would like a different display name.\n\n__`checkFields` (required)__: Turn to false if you would like to optimise this workflow, provided this workflow has run once before with this configurable enabled. This will disable the section of this workflow which deals with creating a HubSpot field."
},
"typeVersion": 1
},
{
"id": "fc640a31-2050-4276-a1f7-8154f61d2729",
"name": "Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
3080,
1160
],
"parameters": {
"width": 219.86482940052417,
"height": 377.58888520886353,
"content": "### `Create or update customer`\nBy default, the only field updated is \"Stripe - Total Spend\". The contact is identified by its email."
},
"typeVersion": 1
},
{
"id": "c91295e6-0306-4f3d-adcf-923fbef1c173",
"name": "Skip field checking",
"type": "n8n-nodes-base.if",
"position": [
240,
1400
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$node[\"Configure\"].json[\"checkFields\"]}}",
"value2": "={{false}}"
}
]
}
},
"typeVersion": 1
},
{
"id": "8f8b5a15-4895-4c5a-b8ba-8592dd754aca",
"name": "Do nothing",
"type": "n8n-nodes-base.noOp",
"position": [
1880,
1240
],
"parameters": {},
"typeVersion": 1
},
{
"id": "b953e439-955c-4046-9000-32cbb3577c27",
"name": "Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
1780,
1140
],
"parameters": {
"width": 298.2919335506821,
"height": 247.94509463108915,
"content": "### `Do nothing`\nThis is useful to know what Stripe charges had no customer assigned."
},
"typeVersion": 1
},
{
"id": "ec2116e5-2a4a-4edf-a816-b15c349f23e0",
"name": "If field exists",
"type": "n8n-nodes-base.if",
"position": [
780,
1540
],
"parameters": {
"conditions": {
"number": [
{
"value1": "={{ $json[\"error\"][\"httpCode\"] }}",
"value2": "404",
"operation": "notEqual"
}
]
}
},
"typeVersion": 1
}
],
"connections": {
"Configure": {
"main": [
[
{
"node": "Skip field checking",
"type": "main",
"index": 0
}
]
]
},
"Merge data": {
"main": [
[
{
"node": "Aggregate totals",
"type": "main",
"index": 0
}
]
]
},
"On schedule": {
"main": [
[
{
"node": "Configure",
"type": "main",
"index": 0
}
]
]
},
"Get customer": {
"main": [
[
{
"node": "Merge data",
"type": "main",
"index": 0
}
]
]
},
"Get all charges": {
"main": [
[
{
"node": "If charge has customer",
"type": "main",
"index": 0
},
{
"node": "Merge data",
"type": "main",
"index": 1
}
]
]
},
"If field exists": {
"main": [
[
{
"node": "Get all charges",
"type": "main",
"index": 0
}
],
[
{
"node": "Create field in HubSpot",
"type": "main",
"index": 0
}
]
]
},
"Aggregate totals": {
"main": [
[
{
"node": "Create or update customer",
"type": "main",
"index": 0
}
]
]
},
"Get HubSpot field": {
"main": [
[
{
"node": "If field exists",
"type": "main",
"index": 0
}
]
]
},
"Skip field checking": {
"main": [
[
{
"node": "Get all charges",
"type": "main",
"index": 0
}
],
[
{
"node": "Get HubSpot field",
"type": "main",
"index": 0
}
]
]
},
"If charge has customer": {
"main": [
[
{
"node": "Remove duplicate customers",
"type": "main",
"index": 0
},
{
"node": "Aggregate `amount_captured`",
"type": "main",
"index": 0
}
],
[
{
"node": "Do nothing",
"type": "main",
"index": 0
}
]
]
},
"Create field in HubSpot": {
"main": [
[
{
"node": "Get all charges",
"type": "main",
"index": 0
}
]
]
},
"Remove duplicate customers": {
"main": [
[
{
"node": "Get customer",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,209 @@
{
"meta": {
"instanceId": "ef45cd7f45f7589c4c252d786d5d1a3233cdbfc451efa7e17688db979f2dc6ae"
},
"nodes": [
{
"id": "b83bfb2d-6d1b-4984-8fc4-6cf0a35309dc",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1380,
960
],
"parameters": {
"width": 1074,
"height": 468,
"content": "# ⚠️ When and how to use this workflow\n\nIf you previously upgraded to n8n version `0.214.3`, some of your workflows might have accidentally been re-wired in the wrong way. This affected nodes which have more than 1 output, such as `If`, `Switch`, and `Compare Datasets`.\n\nThis workflow helps you identify potentially affected workflows and nodes that you should check.\n\n**❗Please ensure to run this workflow as the instance owner❗**\n\n1. Configure the \"Get all workflows\" node to use your n8n API key. (You can find/create your API key under \"Settings > n8n API\")\n2. If you have community nodes installed that have more than 1 output, add them to the constant `MULTI_OUTPUT_NODES` in the \"Parse potentially affected workflows\" code node.\n3. Activate the workflow\n4. Visit `{YOUR_INSTANCE_URL}/webhooks/affected-workflows` from your browser\n5. The report will list potentially affected workflows/nodes.\n 1. The square brackets after the workflow name list the potentially affected nodes\n 2. Inspect each reported workflow individually (you can click on a row to open it in a new tab)\n 3. **Verify that the correct outbound connectors are used to connect subsequent nodes.**"
},
"typeVersion": 1
},
{
"id": "ba065db3-be3c-4694-afbd-c9095526adf6",
"name": "Get all workflows",
"type": "n8n-nodes-base.n8n",
"position": [
1540,
1460
],
"parameters": {
"filters": {}
},
"credentials": {
"n8nApi": {
"id": "13",
"name": "n8n account"
}
},
"typeVersion": 1
},
{
"id": "0fdd3ac4-8c11-4c90-b613-fcbe479a71f6",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
1380,
1460
],
"webhookId": "9f6c90b5-1d0a-4dca-8009-2ee39a4f8002",
"parameters": {
"path": "affected-workflows",
"options": {
"rawBody": false,
"responseHeaders": {
"entries": [
{
"name": "Content-Type",
"value": "text/html; charset=utf-8"
}
]
}
},
"responseMode": "responseNode"
},
"typeVersion": 1
},
{
"id": "88725f34-678a-4127-b163-368ab2fc7b39",
"name": "Parse potentially affected workflows",
"type": "n8n-nodes-base.code",
"position": [
1880,
1460
],
"parameters": {
"jsCode": "// Define an array of objects representing node types that have multiple outputs.\n// Each object specifies the node type and the number of outputs it has.\nconst MULTI_OUTPUT_NODES = [\n { type: 'n8n-nodes-base.compareDatasets', outputs: 4 }, \n { type: 'n8n-nodes-base.switch', outputs: 4}, \n { type: 'n8n-nodes-base.if', outputs: 2}\n]\n\n// Initialize an empty array to store the affected workflows.\nconst affectedWorkflows = [];\n\n// Loop through each item in the $input array.\nfor (const item of $input.all()) {\n // Get the workflow data from the item.\n const workflowData = item.json;\n\n const nodes = workflowData.nodes;\n const connections = workflowData.connections;\n\n // Initialize an empty array to store the potentially affected connections.\n const potentiallyAffectedNodes = [];\n\n for (const connectionName of Object.keys(connections)) {\n const connection = connections[connectionName];\n // Match connection by its name to get the node data\n const connectionNode = nodes.find(node => node.name === connectionName);\n\n // Check if the connection node is a multi-output node.\n const matchedMultiOutputNode = MULTI_OUTPUT_NODES.find(n => n.type === connectionNode.type);\n if(matchedMultiOutputNode) {\n const connectedOutputs = connection.main.filter(c => c && c.length > 0);\n\n // Check if the connection has empty outputs.\n const hasEmptyOutputs = connectedOutputs.length < matchedMultiOutputNode.outputs;\n\n // If there are no connected outputs, skip this connection, it couldn't been affected by the migration\n if(connectedOutputs.length === 0) continue;\n\n // If the connection has empty outputs, it might have been affected by the wrong connections migration\n // which filtered-out empty indexes\n if(hasEmptyOutputs) potentiallyAffectedNodes.push(connectionName);\n }\n }\n\n if(potentiallyAffectedNodes.length > 0) {\n affectedWorkflows.push(\n { \n workflowId: workflowData.id, \n workflowName: workflowData.name,\n active: workflowData.active, \n potentiallyAffectedNodes\n }\n )\n }\n}\n\nreturn {workflows: affectedWorkflows};\n"
},
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "a2324a53-da62-4386-8c86-4d85ffb228b4",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1880,
1620
],
"parameters": {
"width": 236,
"height": 194,
"content": "# 👆\n\nIn case you have community nodes installed, add them to `MULTI_OUTPUT_NODES`"
},
"typeVersion": 1
},
{
"id": "019f564b-edd4-40be-97f5-f1b1cf433005",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1540,
1620
],
"parameters": {
"width": 208,
"height": 197,
"content": "# 👆\n\nConfigure this node to use your n8n API credential"
},
"typeVersion": 1
},
{
"id": "9fa255a8-8e2d-4e3f-ad83-d56b69066e67",
"name": "Generate Report",
"type": "n8n-nodes-base.html",
"position": [
2200,
1460
],
"parameters": {
"html": "\n<!DOCTYPE html>\n\n<html>\n<head>\n <meta charset=\"UTF-8\" />\n <title>n8n workflows report</title>\n</head>\n<body>\n <div class=\"container\">\n <h1>Affected workflows:</h1>\n <ul id=\"list\"></ul>\n </div>\n</body>\n</html>\n\n<style>\n.container {\n background-color: #ffffff;\n text-align: center;\n padding: 16px;\n border-radius: 8px;\n}\n\nh1 {\n color: #ff6d5a;\n font-size: 24px;\n font-weight: bold;\n padding: 8px;\n}\n\nh2 {\n color: #909399;\n font-size: 18px;\n font-weight: bold;\n padding: 8px;\n}\n\nul {\n list-style: none;\n text-align: left;\n padding: 0;\n}\n\nli {\n margin: 8px 0;\n}\n\na {\n color: #409eff;\n text-decoration: none;\n transition: color 0.2s ease-in-out;\n}\n\na:hover {\n color: #ff9900;\n}\n</style>"
},
"typeVersion": 1
},
{
"id": "7923de27-9d69-4ad2-a6e1-dc061c9e8e8f",
"name": "Serve HTML Report",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
2360,
1460
],
"parameters": {
"options": {
"responseHeaders": {
"entries": [
{
"name": "Content-Type",
"value": "text/html; charset=utf-8"
}
]
}
},
"respondWith": "text",
"responseBody": "={{ $node[\"Generate Report\"].parameter[\"html\"] }}\n<script>\nconst { workflows } = {{ JSON.stringify($node[\"Parse potentially affected workflows\"].json) }}\n\nconst $list = document.getElementById('list');\n// Append LI element to the UL element for each item in the affectedWorkflows array\nworkflows.forEach((workflow) => {\n const $listItem = document.createElement('li');\n if(!workflow) return;\n const title = `<a \n target=\"_blank\" href=\"//${window.location.host}/workflow/${workflow.workflowId}\">ID: ${workflow.workflowId}: ${workflow.workflowName} [${workflow.potentiallyAffectedNodes.join(', ')}]</a>`\n $listItem.innerHTML = title\n $list.appendChild($listItem);\n});\n\n</script>"
},
"typeVersion": 1
},
{
"id": "fd63ade5-c7b4-43d5-9849-79bb9aa8dca3",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2360,
1620
],
"parameters": {
"width": 451,
"height": 194,
"content": "# 👆\n\nFind the generated report at `{YOUR_INSTANCE_URL}/webhooks/affected-workflows`"
},
"typeVersion": 1
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "Get all workflows",
"type": "main",
"index": 0
}
]
]
},
"Generate Report": {
"main": [
[
{
"node": "Serve HTML Report",
"type": "main",
"index": 0
}
]
]
},
"Get all workflows": {
"main": [
[
{
"node": "Parse potentially affected workflows",
"type": "main",
"index": 0
}
]
]
},
"Parse potentially affected workflows": {
"main": [
[
{
"node": "Generate Report",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,487 @@
{
"meta": {
"instanceId": "a2434c94d549548a685cca39cc4614698e94f527bcea84eefa363f1037ae14cd"
},
"nodes": [
{
"id": "deafa2e8-af41-4f11-92e0-09992f6c6970",
"name": "Read PDF",
"type": "n8n-nodes-base.readPDF",
"position": [
860,
1420
],
"parameters": {},
"typeVersion": 1
},
{
"id": "8e3ddbb1-83a1-4f79-9464-61d5a20f0427",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-760,
1300
],
"parameters": {
"width": 444.034812880766,
"height": 599.5274151436035,
"content": "## Send specific PDF attachments from Gmail to Google Drive using OpenAI\n\n_**DISCLAIMER**: You may have varying success when using this workflow so be prepared to validate the correctness of OpenAI's results._\n\nThis workflow reads PDF textual content and sends the text to OpenAI. Attachments of interest will then be uploaded to a specified Google Drive folder. For example, you may wish to send invoices received from an email to an inbox folder in Google Drive for later processing. This workflow has been designed to easily change the search term to match your needs. See the workflow for more details.\n\n### How it works\n1. Triggers off on the `On email received` node.\n2. Iterates over the attachments in the email.\n3. Uses the `OpenAI` node to filter out the attachments that do not match the search term set in the `Configure` node. You could match on various PDF files (i.e. invoice, receipt, or contract).\n4. If the PDF attachment matches the search term, the workflow uses the `Google Drive` node to upload the PDF attachment to a specific Google Drive folder.\n\n\nWorkflow written by [David Sha](https://davidsha.me)."
},
"typeVersion": 1
},
{
"id": "fb2c3697-a92f-4be1-b9a6-0326f87de70b",
"name": "Configure",
"type": "n8n-nodes-base.set",
"position": [
-20,
1520
],
"parameters": {
"values": {
"number": [
{
"name": "maxTokenSize",
"value": 4000
},
{
"name": "replyTokenSize",
"value": 50
}
],
"string": [
{
"name": "Match on",
"value": "payslip"
},
{
"name": "Google Drive folder to upload matched PDFs",
"value": "https://drive.google.com/drive/u/0/folders/1SKdHTnYoBNlnhF_QJ-Zyepy-3-WZkObo"
}
]
},
"options": {}
},
"typeVersion": 1
},
{
"id": "792c49f4-06e3-4d77-a31f-1513f70abf32",
"name": "Is PDF",
"type": "n8n-nodes-base.if",
"position": [
640,
1520
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $binary.data.fileExtension }}",
"value2": "pdf"
}
]
}
},
"typeVersion": 1
},
{
"id": "82be9111-665d-41c6-8190-2247acdb749b",
"name": "Not a PDF",
"type": "n8n-nodes-base.noOp",
"position": [
860,
1620
],
"parameters": {},
"typeVersion": 1
},
{
"id": "c2ac155f-38ee-46f2-8a24-5614e3c32ff5",
"name": "Is matched",
"type": "n8n-nodes-base.if",
"position": [
1720,
1480
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json[\"text\"] }}",
"value2": "true"
}
]
}
},
"typeVersion": 1
},
{
"id": "4a8f15b8-c153-493d-9a2a-d63d911d642d",
"name": "This is a matched PDF",
"type": "n8n-nodes-base.noOp",
"position": [
1940,
1380
],
"parameters": {},
"typeVersion": 1
},
{
"id": "89601591-5c7b-461c-859b-25c7c1f0c2e6",
"name": "This is not a matched PDF",
"type": "n8n-nodes-base.noOp",
"position": [
1940,
1580
],
"parameters": {},
"typeVersion": 1
},
{
"id": "ac517c4a-83b8-441f-b14c-c927c18f8012",
"name": "Iterate over email attachments",
"type": "n8n-nodes-base.code",
"position": [
420,
1420
],
"parameters": {
"jsCode": "// https://community.n8n.io/t/iterating-over-email-attachments/13588/3\nlet results = [];\n\nfor (const item of $input.all()) {\n for (key of Object.keys(item.binary)) {\n results.push({\n json: {},\n binary: {\n data: item.binary[key],\n }\n });\n }\n}\n\nreturn results;"
},
"typeVersion": 1
},
{
"id": "79fdf2de-42fe-4ebb-80fb-cc80dcd284f9",
"name": "OpenAI matches PDF textual content",
"type": "n8n-nodes-base.openAi",
"position": [
1300,
1340
],
"parameters": {
"prompt": "=Does this PDF file look like a {{ $(\"Configure\").first().json[\"Match on\"] }}? Return \"true\" if it is a {{ $(\"Configure\").first().json[\"Match on\"] }} and \"false\" if not. Only reply with lowercase letters \"true\" or \"false\".\n\nThis is the PDF filename:\n```\n{{ $binary.data.fileName }}\n```\n\nThis is the PDF text content:\n```\n{{ $json.text }}\n```",
"options": {
"maxTokens": "={{ $('Configure').first().json.replyTokenSize }}",
"temperature": 0.1
}
},
"credentials": {
"openAiApi": {
"id": "30",
"name": "REPLACE ME"
}
},
"typeVersion": 1,
"alwaysOutputData": false
},
{
"id": "8bdb3263-40f2-4277-8cc0-f6edef90a1cd",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
1500,
1480
],
"parameters": {
"mode": "combine",
"options": {
"clashHandling": {
"values": {
"resolveClash": "preferInput1"
}
}
},
"combinationMode": "mergeByPosition"
},
"typeVersion": 2
},
{
"id": "8e68e725-b2df-4c0c-8b17-e0cd4610714d",
"name": "Upload file to folder",
"type": "n8n-nodes-base.googleDrive",
"position": [
2160,
1380
],
"parameters": {
"name": "={{ $binary.data.fileName }}",
"options": {},
"parents": [
"={{ $('Configure').first().json[\"Google Drive folder to upload matched PDFs\"].split(\"/\").at(-1) }}"
],
"binaryData": true
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "32",
"name": "REPLACE ME"
}
},
"typeVersion": 2
},
{
"id": "bda00901-5ade-471c-b6f9-a18ef4d71589",
"name": "On email received",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
-240,
1520
],
"parameters": {
"simple": false,
"filters": {},
"options": {
"downloadAttachments": true,
"dataPropertyAttachmentsPrefixName": "attachment_"
},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"gmailOAuth2": {
"id": "31",
"name": "REPLACE ME"
}
},
"typeVersion": 1
},
{
"id": "b2ff4774-336b-47a3-af3f-ada809ed9b8a",
"name": "Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-100,
1440
],
"parameters": {
"width": 259.0890718059702,
"height": 607.9684549079709,
"content": "### Configuration\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n__`Match on`(required)__: What should OpenAI's search term be? Examples: invoice, callsheet, receipt, contract, payslip.\n__`Google Drive folder to upload matched PDFs`(required)__: Paste the link of the GDrive folder, an example has been provided but will need to change to a folder you own.\n__`maxTokenSize`(required)__: The maximum token size for the model you choose. See possible models from OpenAI [here](https://platform.openai.com/docs/models/gpt-3).\n__`replyTokenSize`(required)__: The reply's maximum token size. Default is 300. This determines how much text the AI will reply with."
},
"typeVersion": 1
},
{
"id": "beb571fe-e7a3-4f3c-862b-dc01821e5f3f",
"name": "Ignore large PDFs",
"type": "n8n-nodes-base.noOp",
"position": [
1300,
1620
],
"parameters": {},
"typeVersion": 1
},
{
"id": "f3c4f249-08a7-4e5e-8f46-e07393ac10b5",
"name": "Is text within token limit?",
"type": "n8n-nodes-base.if",
"position": [
1080,
1520
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.text.length() / 4 <= $('Configure').first().json.maxTokenSize - $('Configure').first().json.replyTokenSize }}",
"value2": true
}
]
}
},
"typeVersion": 1
},
{
"id": "93b6fb96-3e0e-4953-bd09-cf882d2dc69c",
"name": "Has attachments?",
"type": "n8n-nodes-base.if",
"position": [
200,
1520
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $('On email received').item.binary.isNotEmpty() }}",
"value2": true
}
]
}
},
"typeVersion": 1
},
{
"id": "554d415e-a965-46be-8442-35c4cb6b005c",
"name": "There are no attachments",
"type": "n8n-nodes-base.noOp",
"position": [
420,
1620
],
"parameters": {},
"typeVersion": 1
}
],
"connections": {
"Merge": {
"main": [
[
{
"node": "Is matched",
"type": "main",
"index": 0
}
]
]
},
"Is PDF": {
"main": [
[
{
"node": "Read PDF",
"type": "main",
"index": 0
}
],
[
{
"node": "Not a PDF",
"type": "main",
"index": 0
}
]
]
},
"Read PDF": {
"main": [
[
{
"node": "Is text within token limit?",
"type": "main",
"index": 0
}
]
]
},
"Configure": {
"main": [
[
{
"node": "Has attachments?",
"type": "main",
"index": 0
}
]
]
},
"Is matched": {
"main": [
[
{
"node": "This is a matched PDF",
"type": "main",
"index": 0
}
],
[
{
"node": "This is not a matched PDF",
"type": "main",
"index": 0
}
]
]
},
"Has attachments?": {
"main": [
[
{
"node": "Iterate over email attachments",
"type": "main",
"index": 0
}
],
[
{
"node": "There are no attachments",
"type": "main",
"index": 0
}
]
]
},
"On email received": {
"main": [
[
{
"node": "Configure",
"type": "main",
"index": 0
}
]
]
},
"This is a matched PDF": {
"main": [
[
{
"node": "Upload file to folder",
"type": "main",
"index": 0
}
]
]
},
"Is text within token limit?": {
"main": [
[
{
"node": "OpenAI matches PDF textual content",
"type": "main",
"index": 0
},
{
"node": "Merge",
"type": "main",
"index": 1
}
],
[
{
"node": "Ignore large PDFs",
"type": "main",
"index": 0
}
]
]
},
"Iterate over email attachments": {
"main": [
[
{
"node": "Is PDF",
"type": "main",
"index": 0
}
]
]
},
"OpenAI matches PDF textual content": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,285 @@
{
"nodes": [
{
"id": "678e86bc-2755-4c79-97d6-fa4da1ed9ff9",
"name": "Postgres Trigger",
"type": "n8n-nodes-base.postgresTrigger",
"disabled": true,
"position": [
500,
480
],
"parameters": {
"schema": {
"__rl": true,
"mode": "list",
"value": "computed",
"cachedResultName": "computed"
},
"firesOn": "UPDATE",
"tableName": {
"__rl": true,
"mode": "list",
"value": "users",
"cachedResultName": "users"
},
"additionalFields": {}
},
"credentials": {
"postgres": {
"id": "8",
"name": "Postgres Product Analytics"
}
},
"typeVersion": 1
},
{
"id": "accecdfc-283c-4119-9b23-4cf44bc5e68c",
"name": "Filter",
"type": "n8n-nodes-base.filter",
"notes": "Filter out @n8n.io emails",
"position": [
980,
540
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.email }}",
"value2": "n8n.io",
"operation": "notContains"
}
]
}
},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "d16d7ae7-0c60-48f0-97fe-c7618cab73d3",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
380
],
"parameters": {
"width": 424,
"height": 559,
"content": "## 👋 How to use this template\nThis template shows how to sync data from one service to another. In this example we're saving a new qualified lead to a Google Sheets file. Here's how you can test the template:\n\n1. Duplicate our [Google Sheets](https://docs.google.com/spreadsheets/d/1gVfyernVtgYXD-oPboxOSJYQ-HEfAguEryZ7gTtK0V8/edit?usp=sharing) file\n2. Double click the `Google Sheets` node and create a credential by signing in.\n3. Select the correct Google Sheets document and sheet.\n4. Click the `Execute Workflow` button and double click the nodes to see the input and output data\n\n### To customize it to you needs, just do the following:\n1. Enable or exchange the `Postgres trigger` with any service that fits your use case.\n2. Change the `Filter` to fit your needs\n3. Adjust the Google Sheets node as described above\n4. Disable or remove the `On clicking \"Execute Node\"` and `Code` node\n"
},
"typeVersion": 1
},
{
"id": "8bc7439e-d814-4960-8b75-fc77805f74c7",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
460,
380
],
"parameters": {
"width": 344,
"height": 562,
"content": "### 1. Trigger step listens for new events\n\n"
},
"typeVersion": 1
},
{
"id": "63b2bc4c-8e33-4432-af4b-4595b2012ce1",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
840,
460
],
"parameters": {
"width": 462,
"height": 407,
"content": "### 2. Filter and transform your data\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nIn this case, we only want to save qualified users that don't have `@n8n.io` in their email address.\n\nTo edit the filter, simply drag and drop input data into the fields or change the values directly. **Besides filters, n8n has other powerful transformation nodes like [Set](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.set), [ItemList](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.itemlists/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.itemLists), [Code](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.code) and many more.**"
},
"typeVersion": 1
},
{
"id": "448e2c49-aa75-405b-ba51-3acbce0fb758",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1340,
460
],
"parameters": {
"width": 342.52886836027733,
"height": 407.43618112665195,
"content": "### 3. Save the user in a Google Sheet\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nFor simplicity, we're saving our qualified user in a Google Sheet.\n\n**You can replace this node with any service like [Excel](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.microsoftexcel/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.microsoftExcel), [HubSpot](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.hubspot/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.hubspot), [Pipedrive](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.pipedrive/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.pipedrive), [Zendesk](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.zendesk/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.zendesk) etc.**"
},
"typeVersion": 1
},
{
"id": "c0ee182d-4c31-488b-a547-5f2d2ba8786e",
"name": "On clicking \"Execute Node\"",
"type": "n8n-nodes-base.manualTrigger",
"notes": "For testing the workflow",
"position": [
500,
680
],
"parameters": {},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "87f2a11e-f704-4c9e-ac8b-ee1f057cd347",
"name": "Code",
"type": "n8n-nodes-base.code",
"notes": "Mock Data",
"position": [
680,
680
],
"parameters": {
"jsCode": "return [\n {\n \"id\": 1,\n \"username\": \"max_mustermann\",\n \"email\": \"max_mustermann@acme.com\",\n \"company_size\": \"500-999\",\n \"role\": \"Sales\",\n \"users\": 50\n }\n]"
},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "0992077f-b6d3-47d2-94d2-c612dfbf5062",
"name": "Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"notes": "Add to \"Users to contact\"",
"position": [
1400,
540
],
"parameters": {
"columns": {
"value": {
"id": "={{ $json.id }}",
"email": "={{ $json.email }}",
"username": "={{ $json.username }}"
},
"schema": [
{
"id": "id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "id",
"defaultMatch": true,
"canBeUsedToMatch": true
},
{
"id": "username",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "username",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "email",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "contacted",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "contacted",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"id"
]
},
"options": {
"cellFormat": "USER_ENTERED"
},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1gVfyernVtgYXD-oPboxOSJYQ-HEfAguEryZ7gTtK0V8/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1gVfyernVtgYXD-oPboxOSJYQ-HEfAguEryZ7gTtK0V8",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1gVfyernVtgYXD-oPboxOSJYQ-HEfAguEryZ7gTtK0V8/edit?usp=drivesdk",
"cachedResultName": "Qualified leads to contact"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "9",
"name": "Google Sheets account"
}
},
"notesInFlow": true,
"typeVersion": 4
}
],
"connections": {
"Code": {
"main": [
[
{
"node": "Filter",
"type": "main",
"index": 0
}
]
]
},
"Filter": {
"main": [
[
{
"node": "Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"Postgres Trigger": {
"main": [
[
{
"node": "Filter",
"type": "main",
"index": 0
}
]
]
},
"On clicking \"Execute Node\"": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,256 @@
{
"nodes": [
{
"id": "764c42ae-3761-4375-9de4-69ecdaf82b10",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-20,
520
],
"parameters": {
"width": 377.1993316649719,
"height": 590.2004455566864,
"content": "## 👋 How to use this template\nThis template shows how you can take any event from any service, transform its data and send an alert to your desired app. Here's how to use it:\n\n1. Double click the `Slack` node and connect to your Slack account by creating a Credential.\n2. Change the channel name in the `Slack` node to a channel or user you have in Slack.\n2. Click the `Execute Workflow` button, then double click the nodes to see their input and output data\n\n### To customize this template to you needs:\n1. Enable or swap the `Linear trigger` with any service that fits your use case.\n2. Change the data transformation to fit your needs\n3. Adjust the Slack node or swap it with any node that fits your use case\n4. Disable or remove the `When clicking \"Execute Workflow\"` and `Code` node\n"
},
"typeVersion": 1
},
{
"id": "b35b39f5-2937-437e-b4bb-bfd4fc06b2e2",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
423.0997586567955,
520
],
"parameters": {
"width": 398.2006312053042,
"height": 600.6569416091058,
"content": "### 1. Trigger step listens for new events\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nWe added a `Linear trigger` that starts the workflow every time we have an `Issue` event int the `Product & Design` team. \n\n**You can replace this node with any trigger you wish, like [Jira](https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.jiratrigger/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.jiraTrigger), [Clickup](https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.clickuptrigger/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.clickUpTrigger), [HubSpot](https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.hubspottrigger/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.hubspotTrigger), [Google Sheets](https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.googlesheetstrigger/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.googleSheetsTrigger) etc.**"
},
"typeVersion": 1
},
{
"id": "466097b6-a830-43fb-9776-d3c7f676fc9a",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1400,
620
],
"parameters": {
"width": 317.52886836027733,
"height": 408.7361996915138,
"content": "### 3. Notify the right channel\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nLast but not least we're sending a message to the `#important-bugs` channel in Slack.\n\n**You can replace this node with any service like [Teams](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.microsoftteams/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.microsoftTeams), [Telegram](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.telegram/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.telegram), [Email](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.sendemail/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.emailSend) etc.**"
},
"typeVersion": 1
},
{
"id": "99b3eadc-f3ff-4f73-91c2-909ab17ea8ff",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
880,
620
],
"parameters": {
"width": 462,
"height": 407,
"content": "### 2. Filter and transform your data\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nWe only want to notify the team, if the event is fired on creating an urgent bug.\n\nTo edit the nodes, simply drag and drop input data into the fields or change the values directly. **Besides filters, n8n does have other powerful transformation nodes like [Set](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.set), [ItemList](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.itemlists/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.itemLists), [Code](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.code) and many more.**"
},
"typeVersion": 1
},
{
"id": "90e3e605-f497-4aaa-b0be-cb064e9b9ac9",
"name": "Linear Trigger",
"type": "n8n-nodes-base.linearTrigger",
"disabled": true,
"position": [
500,
600
],
"webhookId": "b705f01f-3262-46d4-90f2-fc9f962e6766",
"parameters": {
"teamId": "583b87b7-a8f8-436b-872c-61373503d61d",
"resources": [
"issue"
]
},
"credentials": {
"linearApi": {
"id": "15",
"name": "Linear account"
}
},
"typeVersion": 1
},
{
"id": "f956bf3b-b119-4006-b964-6fdb089ff877",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"notes": "For testing the workflow",
"position": [
500,
800
],
"parameters": {},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "2b347886-f7a8-44eb-b26a-57c436eda594",
"name": "Code",
"type": "n8n-nodes-base.code",
"notes": "Mock Data",
"position": [
680,
800
],
"parameters": {
"jsCode": "return [\n {\n \"action\": \"create\",\n \"createdAt\": \"2023-06-27T13:15:14.118Z\",\n \"data\": {\n \"id\": \"204224f8-3084-49b0-981f-3ad7f9060316\",\n \"createdAt\": \"2023-06-27T13:15:14.118Z\",\n \"updatedAt\": \"2023-06-27T13:15:14.118Z\",\n \"number\": 647,\n \"title\": \"Test event\",\n \"priority\": 3,\n \"boardOrder\": 0,\n \"sortOrder\": -48454,\n \"teamId\": \"583b87b7-a8f8-436b-872c-61373503d61d\",\n \"previousIdentifiers\": [],\n \"creatorId\": \"49ae7598-ae5d-42e6-8a03-9f6038a0d37a\",\n \"stateId\": \"49c4401a-3d9e-40f6-a904-2a5eb95e0237\",\n \"priorityLabel\": \"No priority\",\n \"subscriberIds\": [\n \"49ae7598-ae5d-42e6-8a03-9f6038a0d37a\"\n ],\n \"labelIds\": [\n \"23381844-cdf1-4547-8d42-3b369af5b4ef\"\n ],\n \"state\": {\n \"id\": \"49c4401a-3d9e-40f6-a904-2a5eb95e0237\",\n \"color\": \"#bec2c8\",\n \"name\": \"Backlog\",\n \"type\": \"backlog\"\n },\n \"team\": {\n \"id\": \"583b87b7-a8f8-436b-872c-61373503d61d\",\n \"key\": \"PD\",\n \"name\": \"Product & Design\"\n },\n \"labels\": [\n {\n \"id\": \"23381844-cdf1-4547-8d42-3b369af5b4ef\",\n \"color\": \"#4CB782\",\n \"name\": \"bug\"\n }\n ]\n },\n \"url\": \"https://linear.app/n8n/issue/PD-647/test-event\",\n \"type\": \"Issue\",\n \"organizationId\": \"1c35bbc6-9cd4-427e-8bc5-e5d370a9869f\",\n \"webhookTimestamp\": 1687871714230\n }\n]"
},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "750acf22-5fc7-40b6-8989-aa8ba1cb207b",
"name": "Filter",
"type": "n8n-nodes-base.filter",
"notes": "Keep urgent bugs only",
"position": [
960,
700
],
"parameters": {
"conditions": {
"number": [
{
"value1": "={{ $json.data.priority }}",
"value2": 3,
"operation": "largerEqual"
}
],
"string": [
{
"value1": "={{ $json.data.labels[0].name }}",
"value2": "bug"
}
]
}
},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "8ce7bb41-30f6-4d28-a5c7-ae5cb856ecc2",
"name": "Set",
"type": "n8n-nodes-base.set",
"notes": "Transform title",
"position": [
1180,
700
],
"parameters": {
"values": {
"string": [
{
"name": "title",
"value": "={{ $json.data.title.toTitleCase() }}"
},
{
"name": "url",
"value": "={{ $json.url }}"
}
]
},
"options": {},
"keepOnlySet": true
},
"notesInFlow": true,
"typeVersion": 2
},
{
"id": "b9c6f60a-5b69-4bf5-9514-9c9dc9813595",
"name": "Slack",
"type": "n8n-nodes-base.slack",
"position": [
1500,
700
],
"parameters": {
"text": "=<!channel> New urgent bug *<{{ $json.url }}|{{ $json.title }}>*",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "#important bugs"
},
"otherOptions": {}
},
"credentials": {
"slackApi": {
"id": "6",
"name": "Idea Bot"
}
},
"typeVersion": 2
}
],
"connections": {
"Set": {
"main": [
[
{
"node": "Slack",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Filter",
"type": "main",
"index": 0
}
]
]
},
"Filter": {
"main": [
[
{
"node": "Set",
"type": "main",
"index": 0
}
]
]
},
"Linear Trigger": {
"main": [
[
{
"node": "Filter",
"type": "main",
"index": 0
}
]
]
},
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,688 @@
{
"id": "pPtCy6qPfEv1qNRn",
"meta": {
"instanceId": "205b3bc06c96f2dc835b4f00e1cbf9a937a74eeb3b47c99d0c30b0586dbf85aa"
},
"name": "[1/3 - anomaly detection] [1/2 - KNN classification] Batch upload dataset to Qdrant (crops dataset)",
"tags": [
{
"id": "n3zAUYFhdqtjhcLf",
"name": "qdrant",
"createdAt": "2024-12-10T11:56:59.987Z",
"updatedAt": "2024-12-10T11:56:59.987Z"
}
],
"nodes": [
{
"id": "53831410-b4f3-4374-8bdd-c2a33cd873cb",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-640,
0
],
"parameters": {},
"typeVersion": 1
},
{
"id": "e303ccea-c0e0-4fe5-bd31-48380a0e438f",
"name": "Google Cloud Storage",
"type": "n8n-nodes-base.googleCloudStorage",
"position": [
820,
160
],
"parameters": {
"resource": "object",
"returnAll": true,
"bucketName": "n8n-qdrant-demo",
"listFilters": {
"prefix": "agricultural-crops"
},
"requestOptions": {}
},
"credentials": {
"googleCloudStorageOAuth2Api": {
"id": "fn0sr7grtfprVQvL",
"name": "Google Cloud Storage account"
}
},
"typeVersion": 1
},
{
"id": "737bdb15-61cf-48eb-96af-569eb5986ee8",
"name": "Get fields for Qdrant",
"type": "n8n-nodes-base.set",
"position": [
1080,
160
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "10d9147f-1c0c-4357-8413-3130829c2e24",
"name": "=publicLink",
"type": "string",
"value": "=https://storage.googleapis.com/{{ $json.bucket }}/{{ $json.selfLink.split('/').splice(-1) }}"
},
{
"id": "ff9e6a0b-e47a-4550-a13b-465507c75f8f",
"name": "cropName",
"type": "string",
"value": "={{ $json.id.split('/').slice(-3, -2)[0].toLowerCase()}}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "2b18ed0c-38d3-49e9-be3d-4f7b35f4d9e5",
"name": "Qdrant cluster variables",
"type": "n8n-nodes-base.set",
"position": [
-360,
0
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "58b7384d-fd0c-44aa-9f8e-0306a99be431",
"name": "qdrantCloudURL",
"type": "string",
"value": "=https://152bc6e2-832a-415c-a1aa-fb529f8baf8d.eu-central-1-0.aws.cloud.qdrant.io"
},
{
"id": "e34c4d88-b102-43cc-a09e-e0553f2da23a",
"name": "collectionName",
"type": "string",
"value": "=agricultural-crops"
},
{
"id": "33581e0a-307f-4380-9533-615791096de7",
"name": "VoyageEmbeddingsDim",
"type": "number",
"value": 1024
},
{
"id": "6e390343-2cd2-4559-aba9-82b13acb7f52",
"name": "batchSize",
"type": "number",
"value": 4
}
]
}
},
"typeVersion": 3.4
},
{
"id": "f88d290e-3311-4322-b2a5-1350fc1f8768",
"name": "Embed crop image",
"type": "n8n-nodes-base.httpRequest",
"position": [
2120,
160
],
"parameters": {
"url": "https://api.voyageai.com/v1/multimodalembeddings",
"method": "POST",
"options": {},
"jsonBody": "={{\n{\n \"inputs\": $json.batchVoyage,\n \"model\": \"voyage-multimodal-3\",\n \"input_type\": \"document\"\n}\n}}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"id": "Vb0RNVDnIHmgnZOP",
"name": "Voyage API"
}
},
"typeVersion": 4.2
},
{
"id": "250c6a8d-f545-4037-8069-c834437bbe15",
"name": "Create Qdrant Collection",
"type": "n8n-nodes-base.httpRequest",
"position": [
320,
160
],
"parameters": {
"url": "={{ $('Qdrant cluster variables').first().json.qdrantCloudURL }}/collections/{{ $('Qdrant cluster variables').first().json.collectionName }}",
"method": "PUT",
"options": {},
"jsonBody": "={{\n{\n \"vectors\": {\n \"voyage\": { \n \"size\": $('Qdrant cluster variables').first().json.VoyageEmbeddingsDim, \n \"distance\": \"Cosine\" \n } \n }\n}\n}}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "qdrantApi"
},
"credentials": {
"qdrantApi": {
"id": "it3j3hP9FICqhgX6",
"name": "QdrantApi account"
}
},
"typeVersion": 4.2
},
{
"id": "20b612ff-4794-43ef-bf45-008a16a2f30f",
"name": "Check Qdrant Collection Existence",
"type": "n8n-nodes-base.httpRequest",
"position": [
-100,
0
],
"parameters": {
"url": "={{ $json.qdrantCloudURL }}/collections/{{ $json.collectionName }}/exists",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "qdrantApi"
},
"credentials": {
"qdrantApi": {
"id": "it3j3hP9FICqhgX6",
"name": "QdrantApi account"
}
},
"typeVersion": 4.2
},
{
"id": "c067740b-5de3-452e-a614-bf14985a73a0",
"name": "Batches in the API's format",
"type": "n8n-nodes-base.set",
"position": [
1860,
160
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "f14db112-6f15-4405-aa47-8cb56bb8ae7a",
"name": "=batchVoyage",
"type": "array",
"value": "={{ $json.batch.map(item => ({ \"content\": ([{\"type\": \"image_url\", \"image_url\": item[\"publicLink\"]}])}))}}"
},
{
"id": "3885fd69-66f5-4435-86a4-b80eaa568ac1",
"name": "=batchPayloadQdrant",
"type": "array",
"value": "={{ $json.batch.map(item => ({\"crop_name\":item[\"cropName\"], \"image_path\":item[\"publicLink\"]})) }}"
},
{
"id": "8ea7a91e-af27-49cb-9a29-41dae15c4e33",
"name": "uuids",
"type": "array",
"value": "={{ $json.uuids }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "bf9a9532-db64-4c02-b91d-47e708ded4d3",
"name": "Batch Upload to Qdrant",
"type": "n8n-nodes-base.httpRequest",
"position": [
2320,
160
],
"parameters": {
"url": "={{ $('Qdrant cluster variables').first().json.qdrantCloudURL }}/collections/{{ $('Qdrant cluster variables').first().json.collectionName }}/points",
"method": "PUT",
"options": {},
"jsonBody": "={{\n{\n \"batch\": {\n \"ids\" : $('Batches in the API\\'s format').item.json.uuids,\n \"vectors\": {\"voyage\": $json.data.map(item => item[\"embedding\"]) },\n \"payloads\": $('Batches in the API\\'s format').item.json.batchPayloadQdrant\n }\n}\n}}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "qdrantApi"
},
"credentials": {
"qdrantApi": {
"id": "it3j3hP9FICqhgX6",
"name": "QdrantApi account"
}
},
"typeVersion": 4.2
},
{
"id": "3c30373f-c84c-405f-bb84-ec8b4c7419f4",
"name": "Split in batches, generate uuids for Qdrant points",
"type": "n8n-nodes-base.code",
"position": [
1600,
160
],
"parameters": {
"language": "python",
"pythonCode": "import uuid\n\ncrops = [item.json for item in _input.all()]\nbatch_size = int(_('Qdrant cluster variables').first()['json']['batchSize'])\n\ndef split_into_batches_add_uuids(array, batch_size):\n return [\n {\n \"batch\": array[i:i + batch_size],\n \"uuids\": [str(uuid.uuid4()) for j in range(len(array[i:i + batch_size]))]\n }\n for i in range(0, len(array), batch_size)\n ]\n\n# Split crops into batches\nbatched_crops = split_into_batches_add_uuids(crops, batch_size)\n\nreturn batched_crops"
},
"typeVersion": 2
},
{
"id": "2b028f8c-0a4c-4a3a-9e2b-14b1c2401c6d",
"name": "If collection exists",
"type": "n8n-nodes-base.if",
"position": [
120,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "2104b862-667c-4a34-8888-9cb81a2e10f8",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.result.exists }}",
"rightValue": "true"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "768793f6-391e-4cc9-b637-f32ee2f77156",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
500,
340
],
"parameters": {
"width": 280,
"height": 200,
"content": "In the next workflow, we're going to use Qdrant to get the number of images belonging to each crop type defined by `crop_name` (for example, *\"cucumber\"*). \nTo get this information about counts in payload fields, we need to create an index on that field to optimise the resources (it needs to be done once). That's what is happening here"
},
"typeVersion": 1
},
{
"id": "0c8896f7-8c57-4add-bc4d-03c4a774bdf1",
"name": "Payload index on crop_name",
"type": "n8n-nodes-base.httpRequest",
"position": [
500,
160
],
"parameters": {
"url": "={{ $('Qdrant cluster variables').first().json.qdrantCloudURL }}/collections/{{ $('Qdrant cluster variables').first().json.collectionName }}/index",
"method": "PUT",
"options": {},
"jsonBody": "={\n \"field_name\": \"crop_name\",\n \"field_schema\": \"keyword\"\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "qdrantApi"
},
"credentials": {
"qdrantApi": {
"id": "it3j3hP9FICqhgX6",
"name": "QdrantApi account"
}
},
"typeVersion": 4.2
},
{
"id": "342186f6-41bf-46be-9be8-a9b1ca290d55",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-360,
-360
],
"parameters": {
"height": 300,
"content": "Setting up variables\n1) Cloud URL - to connect to Qdrant Cloud (your personal cluster URL)\n2) Collection name in Qdrant\n3) Size of Voyage embeddings (needed for collection creation in Qdrant) <this one should not be changed unless the embedding model is changed>\n4) Batch size for batch embedding/batch uploading to Qdrant "
},
"typeVersion": 1
},
{
"id": "fae9248c-dbcc-4b6d-b977-0047f120a587",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-100,
-220
],
"parameters": {
"content": "In Qdrant, you can create a collection once; if you try to create it two times with the same name, you'll get an error, so I am adding here a check if a collection with this name exists already"
},
"typeVersion": 1
},
{
"id": "f7aea242-3d98-4a1c-a98a-986ac2b4928b",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
180,
340
],
"parameters": {
"height": 280,
"content": "If a collection with the name set up in variables doesn't exist yet, I create an empty one; \n\nCollection will contain [named vectors](https://qdrant.tech/documentation/concepts/vectors/#named-vectors), with a name *\"voyage\"*\nFor these named vectors, I define two parameters:\n1) Vectors size (in our case, Voyage embeddings size)\n2) Similarity metric to compare embeddings: in our case, **\"Cosine\"**.\n"
},
"typeVersion": 1
},
{
"id": "b84045c1-f66a-4543-8d42-1e76de0b6e91",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
800,
-280
],
"parameters": {
"height": 400,
"content": "Now it's time to embed & upload to Qdrant our image datasets;\nBoth of them, [crops](https://www.kaggle.com/datasets/mdwaquarazam/agricultural-crops-image-classification) and [lands](https://www.kaggle.com/datasets/apollo2506/landuse-scene-classification) were uploaded to our Google Cloud Storage bucket, and in this workflow we're fetching **the crops dataset** (for lands it will be a nearly identical workflow, up to variable names)\n(you should replace it with your image datasets)\n\nDatasets consist of **image URLs**; images are grouped by folders based on their class. For example, we have a system of subfolders like *\"tomato\"* and *\"cucumber\"* for the crops dataset with image URLs of the respective class.\n"
},
"typeVersion": 1
},
{
"id": "255dfad8-c545-4d75-bc9c-529aa50447a9",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1080,
-140
],
"parameters": {
"height": 240,
"content": "Google Storage node returns **mediaLink**, which can be used directly for downloading images; however, we just need a public image URL so that Voyage API can process it; so here we construct this public link and extract a crop name from the folder in which image was stored (for example, *\"cucumber\"*)\n"
},
"typeVersion": 1
},
{
"id": "a6acce75-cce0-4de3-bc64-37592c97359b",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1600,
-80
],
"parameters": {
"height": 180,
"content": "I regroup images into batches of `batchSize` size and, to make batch upload to Qdrant possible, generate UUIDs to use them as batch [point IDs](https://qdrant.tech/documentation/concepts/points/#point-ids) (Qdrant doesn't set up id's for the user; users have to choose them themselves)"
},
"typeVersion": 1
},
{
"id": "cab3cc83-b50c-41f4-8d51-59e04bba5556",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
1340,
-60
],
"parameters": {
"content": "Since we build anomaly detection based on the crops dataset, to test it properly, I didn't upload to Qdrant pictures of tomatoes at all; I filter them out here"
},
"typeVersion": 1
},
{
"id": "e5cdcce5-efdc-41f2-9796-656bd345f783",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
1860,
-100
],
"parameters": {
"height": 200,
"content": "Since Voyage API requires a [specific json structure](https://docs.voyageai.com/reference/multimodal-embeddings-api) for batch embeddings, as does [Qdrant's API for uploading points in batches](https://api.qdrant.tech/api-reference/points/upsert-points), I am adapting the structure of jsons\n\n[NB] - [payload = meta data in Qdrant]"
},
"typeVersion": 1
},
{
"id": "a7f15c44-3d5c-4b43-bfb2-94fe27a32071",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
2120,
20
],
"parameters": {
"width": 180,
"height": 80,
"content": "Embedding images with Voyage model (mind `input_type`)"
},
"typeVersion": 1
},
{
"id": "01b92e7e-d954-4d58-85b1-109c336546c4",
"name": "Filtering out tomato to test anomalies",
"type": "n8n-nodes-base.filter",
"position": [
1340,
160
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f7953ae2-5333-4805-abe5-abf6da645c5e",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $json.cropName }}",
"rightValue": "tomato"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "8d564817-885e-453a-a087-900b34b84d9c",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1160,
-280
],
"parameters": {
"width": 440,
"height": 460,
"content": "## Batch Uploading Dataset to Qdrant \n### This template imports dataset images from storage, creates embeddings for them in batches, and uploads them to Qdrant in batches. In this particular template, we work with [crops dataset](https://www.kaggle.com/datasets/mdwaquarazam/agricultural-crops-image-classification). However, it's analogous to [lands dataset](https://www.kaggle.com/datasets/apollo2506/landuse-scene-classification), and in general, it's adaptable to any dataset consisting of image URLs (as the following pipelines are).\n\n* First, check for an existing Qdrant collection to use; otherwise, create it here. Additionally, when creating the collection, we'll create a [payload index](https://qdrant.tech/documentation/concepts/indexing/#payload-index), which is required for a particular type of Qdrant requests we will use later.\n* Next, import all (dataset) images from Google Storage but keep only non-tomato-related ones (for anomaly detection testing).\n* Create (per batch) embeddings for all imported images using the Voyage AI multimodal embeddings API.\n* Finally, upload the resulting embeddings and image descriptors to Qdrant via batch uploading."
},
"typeVersion": 1
},
{
"id": "0233d3d0-bbdf-4d5b-a366-53cbfa4b6f9c",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
-860,
360
],
"parameters": {
"color": 4,
"width": 540,
"height": 420,
"content": "### For anomaly detection\n**1. This is the first pipeline to upload (crops) dataset to Qdrant's collection.**\n2. The second pipeline is to set up cluster (class) centres in this Qdrant collection & cluster (class) threshold scores.\n3. The third is the anomaly detection tool, which takes any image as input and uses all preparatory work done with Qdrant (crops) collection.\n\n### For KNN (k nearest neighbours) classification\n**1. This is the first pipeline to upload (lands) dataset to Qdrant's collection.**\n2. The second is the KNN classifier tool, which takes any image as input and classifies it based on queries to the Qdrant (lands) collection.\n\n### To recreate both\nYou'll have to upload [crops](https://www.kaggle.com/datasets/mdwaquarazam/agricultural-crops-image-classification) and [lands](https://www.kaggle.com/datasets/apollo2506/landuse-scene-classification) datasets from Kaggle to your own Google Storage bucket, and re-create APIs/connections to [Qdrant Cloud](https://qdrant.tech/documentation/quickstart-cloud/) (you can use **Free Tier** cluster), Voyage AI API & Google Cloud Storage\n\n**In general, pipelines are adaptable to any dataset of images**\n"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "27776c4a-3bf9-4704-9c13-345b75ffacc0",
"connections": {
"Embed crop image": {
"main": [
[
{
"node": "Batch Upload to Qdrant",
"type": "main",
"index": 0
}
]
]
},
"Google Cloud Storage": {
"main": [
[
{
"node": "Get fields for Qdrant",
"type": "main",
"index": 0
}
]
]
},
"If collection exists": {
"main": [
[
{
"node": "Google Cloud Storage",
"type": "main",
"index": 0
}
],
[
{
"node": "Create Qdrant Collection",
"type": "main",
"index": 0
}
]
]
},
"Get fields for Qdrant": {
"main": [
[
{
"node": "Filtering out tomato to test anomalies",
"type": "main",
"index": 0
}
]
]
},
"Batch Upload to Qdrant": {
"main": [
[]
]
},
"Create Qdrant Collection": {
"main": [
[
{
"node": "Payload index on crop_name",
"type": "main",
"index": 0
}
]
]
},
"Qdrant cluster variables": {
"main": [
[
{
"node": "Check Qdrant Collection Existence",
"type": "main",
"index": 0
}
]
]
},
"Payload index on crop_name": {
"main": [
[
{
"node": "Google Cloud Storage",
"type": "main",
"index": 0
}
]
]
},
"Batches in the API's format": {
"main": [
[
{
"node": "Embed crop image",
"type": "main",
"index": 0
}
]
]
},
"Check Qdrant Collection Existence": {
"main": [
[
{
"node": "If collection exists",
"type": "main",
"index": 0
}
]
]
},
"When clicking Test workflow": {
"main": [
[
{
"node": "Qdrant cluster variables",
"type": "main",
"index": 0
}
]
]
},
"Filtering out tomato to test anomalies": {
"main": [
[
{
"node": "Split in batches, generate uuids for Qdrant points",
"type": "main",
"index": 0
}
]
]
},
"Split in batches, generate uuids for Qdrant points": {
"main": [
[
{
"node": "Batches in the API's format",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,614 @@
{
"id": "1g8EAij2RwhNN70t",
"meta": {
"instanceId": "a4bfc93e975ca233ac45ed7c9227d84cf5a2329310525917adaf3312e10d5462",
"templateCredsSetupCompleted": true
},
"name": "xSend and check TTS (Text-to-speech) voice calls end email verification",
"tags": [],
"nodes": [
{
"id": "56842e20-266b-4770-b4cd-3106418caefa",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-480,
-740
],
"parameters": {
"width": 440,
"height": 180,
"content": "## STEP 1\n[Register here to ClickSend](https://clicksend.com/?u=586989) and obtain your API Key and 2 € of free credits\n\nIn the node \"Send Voice\" create a \"Basic Auth\" with the username you registered and the API Key provided as your password"
},
"typeVersion": 1
},
{
"id": "9dfff5ae-fc04-4957-a7b6-6866e8ab0854",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-480,
-320
],
"parameters": {
"width": 440,
"content": "## STEP 3\n\nSubmit the form and you will receive a call to the phone number you entered where the selected voice will tell you the content of the text you wrote."
},
"typeVersion": 1
},
{
"id": "914666e8-1dc3-4d71-abf7-408b66a4508c",
"name": "Send Voice",
"type": "n8n-nodes-base.httpRequest",
"position": [
260,
0
],
"parameters": {
"url": "https://rest.clicksend.com/v3/voice/send",
"method": "POST",
"options": {},
"jsonBody": "={\n \"messages\": [\n {\n \"source\": \"n8n\",\n \"body\": \"Your verification number is {{ $json.Code }}\",\n \"to\": \"{{ $('On form submission').item.json.To }}\",\n \"voice\": \"{{ $('On form submission').item.json.Voice }}\",\n \"lang\": \"{{ $('On form submission').item.json.Lang }}\",\n \"machine_detection\": 1\n }\n ]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": " application/json"
},
{}
]
}
},
"credentials": {
"httpBasicAuth": {
"id": "UwsDe2JxT39eWIvY",
"name": "ClickSend API"
}
},
"typeVersion": 4.2
},
{
"id": "838266ee-33aa-4380-9335-5290cad30504",
"name": "On form submission",
"type": "n8n-nodes-base.formTrigger",
"position": [
-440,
0
],
"webhookId": "194f453a-1d86-4222-bd4d-117f03005560",
"parameters": {
"options": {},
"formTitle": "Send Voice Message",
"formFields": {
"values": [
{
"fieldLabel": "To",
"placeholder": "+39xxxx",
"requiredField": true
},
{
"fieldType": "dropdown",
"fieldLabel": "Voice",
"fieldOptions": {
"values": [
{
"option": "male"
},
{
"option": "female"
}
]
},
"requiredField": true
},
{
"fieldType": "dropdown",
"fieldLabel": "Lang",
"fieldOptions": {
"values": [
{
"option": "en-us \t"
},
{
"option": "it-it"
},
{
"option": "en-au"
},
{
"option": "en-gb"
},
{
"option": "de-de"
},
{
"option": "es-es"
},
{
"option": "fr-fr"
},
{
"option": "is-is"
},
{
"option": "da-dk"
},
{
"option": "nl-nl"
},
{
"option": "pl-pl"
},
{
"option": "pt-br"
},
{
"option": "ru-ru"
}
]
},
"requiredField": true
},
{
"fieldType": "email",
"fieldLabel": "Email",
"placeholder": "Email",
"requiredField": true
},
{
"fieldLabel": "Nome ",
"placeholder": "Nome",
"requiredField": true
}
]
}
},
"typeVersion": 2.2
},
{
"id": "aab0e353-0af0-4867-9178-4195c6ed045b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-480,
-1020
],
"parameters": {
"color": 3,
"width": 440,
"height": 240,
"content": "## Send and Check TTS (Text-to-Speech) Voice Calls with Email Verification\n\nThis workflow automates the process of sending voice calls for verification purposes and combines it with email verification. It uses the ClickSend API for voice calls and integrates with SMTP for email verification. \n"
},
"typeVersion": 1
},
{
"id": "f4c3e305-be7e-43e7-a874-2767a0411624",
"name": "Send Email",
"type": "n8n-nodes-base.emailSend",
"position": [
1180,
-100
],
"webhookId": "92aa0a80-8bea-47b7-86ef-bebc90435526",
"parameters": {
"html": "=Hi {{ $('On form submission').item.json['Nome '] }},<br>\nThe email verification code is <b>{{ $json['Code Email'] }}</b>",
"options": {},
"subject": "Verify your code",
"toEmail": "={{ $('On form submission').item.json['Email'] }}",
"fromEmail": "EMAIL"
},
"credentials": {
"smtp": {
"id": "hRjP3XbDiIQqvi7x",
"name": "SMTP info@n3witalia.com"
}
},
"typeVersion": 2.1
},
{
"id": "5a3ff941-6d25-4479-bedc-c3cfa7c75e36",
"name": "Code for voice",
"type": "n8n-nodes-base.code",
"position": [
40,
0
],
"parameters": {
"jsCode": "// Loop over input items and modify the 'Code' field to add spaces between characters\nfor (const item of $input.all()) {\n const code = item.json.Code;\n\n const spacedCode = code.split('').join(' ');\n\n item.json.Code = spacedCode;\n}\n\nreturn $input.all();"
},
"typeVersion": 2
},
{
"id": "14ccfe99-8fbd-4cde-9ca3-c73e541086b3",
"name": "Set voice code",
"type": "n8n-nodes-base.set",
"position": [
-220,
0
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "89fb63af-790e-4388-9495-5f1e517ee486",
"name": "Code",
"type": "string",
"value": "12345"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "b01e3604-5ca9-45cb-8a59-4f86f33d169b",
"name": "Verify voice code",
"type": "n8n-nodes-base.form",
"position": [
480,
0
],
"webhookId": "b4356cb9-4185-4c65-b7c4-1f1e00a50ce0",
"parameters": {
"options": {},
"formFields": {
"values": [
{
"fieldLabel": "Verify",
"placeholder": "Verify",
"requiredField": true
}
]
}
},
"typeVersion": 1
},
{
"id": "9c013995-ce9d-4c65-9c19-2f1a410ada38",
"name": "Fail voice code",
"type": "n8n-nodes-base.form",
"position": [
940,
100
],
"webhookId": "330b8918-7890-485c-a4fb-b0a917c14edb",
"parameters": {
"options": {},
"operation": "completion",
"completionTitle": "Oh no!",
"completionMessage": "Sorry, the code entered is invalid. Verification has not been completed"
},
"typeVersion": 1
},
{
"id": "3abbb31d-2ad0-4c2e-8891-e65e484e2ae4",
"name": "Set email code",
"type": "n8n-nodes-base.set",
"position": [
940,
-100
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "33438b85-27f4-4264-ab88-e1d3ec8b1ae8",
"name": "Code Email",
"type": "string",
"value": "56789"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "3e453956-0056-4532-a096-a0a6de9702ae",
"name": "Verify email code",
"type": "n8n-nodes-base.form",
"position": [
1440,
-100
],
"webhookId": "db9965d4-7660-4775-a5c6-772de7927e85",
"parameters": {
"options": {},
"formFields": {
"values": [
{
"fieldLabel": "Verify email",
"placeholder": "Verify email code",
"requiredField": true
}
]
}
},
"typeVersion": 1
},
{
"id": "964528b3-f25f-4591-b5fe-6b405aaed0d2",
"name": "Is email code correct?",
"type": "n8n-nodes-base.if",
"position": [
1680,
-100
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "14ee5cfc-2a21-413d-9099-e63ce12da323",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Set email code').item.json['Code Email'] }}",
"rightValue": "={{ $json['Verify email'] }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "df8f62cc-8f45-462e-84bf-0121cbf650c7",
"name": "Is voice code correct?",
"type": "n8n-nodes-base.if",
"position": [
700,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "5aaaf956-3693-4930-b63e-dceb51857716",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{$('Set voice code').item.json.Code}}",
"rightValue": "={{ $json.Verify }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "1f7ff94c-9b29-481b-99cf-cef63714995c",
"name": "Success",
"type": "n8n-nodes-base.form",
"position": [
1920,
-200
],
"webhookId": "3dfd4429-927f-4695-9b64-87f53b52c3f6",
"parameters": {
"options": {},
"operation": "completion",
"completionTitle": "Great!",
"completionMessage": "Your mobile number and email address have been verified successfully. Thank you!"
},
"typeVersion": 1
},
{
"id": "2c6fbd06-30f9-47b8-afa0-042439ff92c6",
"name": "Fail email code",
"type": "n8n-nodes-base.form",
"position": [
1920,
0
],
"webhookId": "a26fc536-f976-4719-bb11-43111f7ec330",
"parameters": {
"options": {},
"operation": "completion",
"completionTitle": "Oh no!",
"completionMessage": "Sorry, the code entered is invalid. Verification has not been completed"
},
"typeVersion": 1
},
{
"id": "632e4253-f4d1-4255-93d8-b7c3b8571e36",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-260,
-80
],
"parameters": {
"width": 180,
"height": 240,
"content": "Set the code that will be spoken in the verification phone call"
},
"typeVersion": 1
},
{
"id": "37f3d155-cbb8-4c03-b8ae-43df4eec06d1",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
900,
-180
],
"parameters": {
"width": 180,
"height": 240,
"content": "Set the code that will be sent in the verification email"
},
"typeVersion": 1
},
{
"id": "4c3a01a0-927f-499f-8bf2-e402b77050c4",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-480,
-520
],
"parameters": {
"width": 440,
"content": "## STEP 2\n\nSet the verification code for this explanatory flow that will be set in the voice call and verification email.\n\nIn the node \"Send Email\" set the sender."
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "3e26e024-4da6-4449-bc3f-8604c837396a",
"connections": {
"Send Email": {
"main": [
[
{
"node": "Verify email code",
"type": "main",
"index": 0
}
]
]
},
"Send Voice": {
"main": [
[
{
"node": "Verify voice code",
"type": "main",
"index": 0
}
]
]
},
"Code for voice": {
"main": [
[
{
"node": "Send Voice",
"type": "main",
"index": 0
}
]
]
},
"Set email code": {
"main": [
[
{
"node": "Send Email",
"type": "main",
"index": 0
}
]
]
},
"Set voice code": {
"main": [
[
{
"node": "Code for voice",
"type": "main",
"index": 0
}
]
]
},
"Verify email code": {
"main": [
[
{
"node": "Is email code correct?",
"type": "main",
"index": 0
}
]
]
},
"Verify voice code": {
"main": [
[
{
"node": "Is voice code correct?",
"type": "main",
"index": 0
}
]
]
},
"On form submission": {
"main": [
[
{
"node": "Set voice code",
"type": "main",
"index": 0
}
]
]
},
"Is email code correct?": {
"main": [
[
{
"node": "Success",
"type": "main",
"index": 0
}
],
[
{
"node": "Fail email code",
"type": "main",
"index": 0
}
]
]
},
"Is voice code correct?": {
"main": [
[
{
"node": "Set email code",
"type": "main",
"index": 0
}
],
[
{
"node": "Fail voice code",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,194 @@
{
"meta": {
"instanceId": "257476b1ef58bf3cb6a46e65fac7ee34a53a5e1a8492d5c6e4da5f87c9b82833"
},
"nodes": [
{
"id": "f5c16b6d-b7b0-4b36-9e74-795a4f486604",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"position": [
360,
1700
],
"parameters": {},
"typeVersion": 1
},
{
"id": "0cc486d8-397f-44b1-a23b-04d0c142a48d",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
220,
1420
],
"parameters": {
"height": 259,
"content": "## Email search with Icypeas (bulk search)\n\n\nThis workflow demonstrates how to perform email searches (bulk search) using Icypeas. Visit https://icypeas.com to create your account."
},
"typeVersion": 1
},
{
"id": "b932d050-4934-4f2f-a620-79f08b97c428",
"name": "Authenticates to your Icypeas account",
"type": "n8n-nodes-base.code",
"position": [
860,
1700
],
"parameters": {
"jsCode": "const API_BASE_URL = \"https://app.icypeas.com/api\";\nconst API_PATH = \"/bulk-search\";\nconst METHOD = \"POST\";\n\n// Change here\nconst API_KEY = \"PUT_API_KEY_HERE\";\nconst API_SECRET = \"PUT_API_SECRET_HERE\";\nconst USER_ID = \"PUT_USER_ID_HERE\";\n////////////////\n\nconst genSignature = (\n url,\n method,\n secret,\n timestamp = new Date().toISOString()\n) => {\n const Crypto = require('crypto');\n const payload = `${method}${url}${timestamp}`.toLowerCase();\n const sign = Crypto.createHmac(\"sha1\", secret).update(payload).digest(\"hex\");\n\n return sign;\n};\n\nconst apiUrl = `${API_BASE_URL}${API_PATH}`;\n\nconst data = $input.all().map((x) => [x.json.firstname, x.json.lastname, x.json.company]);\n$input.first().json.data = data;\n$input.first().json.api = {\n timestamp: new Date().toISOString(),\n secret: API_SECRET,\n key: API_KEY,\n userId: USER_ID,\n url: apiUrl,\n};\n\n$input.first().json.api.signature = genSignature(apiUrl, METHOD, API_SECRET, $input.first().json.api.timestamp);\nreturn $input.first();"
},
"typeVersion": 1
},
{
"id": "35325df4-1d77-4200-9aca-a7f311f3857e",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
500,
1560
],
"parameters": {
"height": 606.4963141641612,
"content": "## Read your Google Sheet file\n\nThis node reads a Google Sheet. You need to create a sheet with :\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n**The first column** :\nHeader : lastname\n\n**The first column** :\nHeader : firstname\n\n**The first column** :\nHeader : company\n\n\nDon't forget to specify the path of your file in the node and your credentials."
},
"typeVersion": 1
},
{
"id": "ca04cf0b-59b6-4836-902f-2e93b6cbc3f5",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
741.0092314499475,
1458.51011235955
],
"parameters": {
"width": 392.0593078758952,
"height": 1203.3290499048028,
"content": "## Authenticates to your Icypeas account\n\nThis code node utilizes your API key, API secret, and User ID to establish a connection with your Icypeas account.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nOpen this node and insert your API Key, API secret, and User ID within the quotation marks. You can locate these credentials on your Icypeas profile at https://app.icypeas.com/bo/profile. Here is the extract of what you have to change :\n\nconst API_KEY = \"**PUT_API_KEY_HERE**\";\nconst API_SECRET = \"**PUT_API_SECRET_HERE**\";\nconst USER_ID = \"**PUT_USER_ID_HERE**\";\n\nDo not change any other line of the code.\n\nIf you are a self-hosted user, follow these steps to activate the crypto module :\n\n1.Access your n8n instance:\nLog in to your n8n instance using your web browser by navigating to the URL of your instance, for example: http://your-n8n-instance.com.\n\n2.Go to Settings:\nIn the top-right corner, click on your username, then select \"Settings.\"\n\n3.Select General Settings:\nIn the left menu, click on \"General.\"\n\n4.Enable the Crypto module:\nScroll down to the \"Additional Node Packages\" section. You will see an option called \"crypto\" with a checkbox next to it. Check this box to enable the Crypto module.\n\n5.Save the changes:\nAt the bottom of the page, click \"Save\" to apply the changes.\n\nOnce you've followed these steps, the Crypto module should be activated for your self-hosted n8n instance. Make sure to save your changes and optionally restart your n8n instance for the changes to take effect.\n\n\n\n\n\n"
},
"typeVersion": 1
},
{
"id": "69e3246b-f490-43e7-94ae-566eb4faf6b9",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1133,
1460
],
"parameters": {
"width": 328.8456933308303,
"height": 869.114109302513,
"content": "## Performs email searches (bulk).\n\n\nThis node executes an HTTP request (POST) to search for the email addresses.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n### You need to create credentials in the HTTP Request node :\n\n➔ In the Credential for Header Auth, click on - Create new Credential -.\n➔ In the Name section, write “Authorization”\n➔ In the Value section, select expression (located just above the field on the right when you hover on top of it) and write {{ $json.api.key + ':' + $json.api.signature }} .\n➔ Then click on “Save” to save the changes.\n\n### To retrieve the results :\n\nAfter some time, the results, which are downloadable, will be available in the Icypeas application in this section : https://app.icypeas.com/bo/bulksearch?task=email-search, and you will receive the search results via email from no-reply@icypeas.com, providing you with the results of your search.\n\n\n\n\n"
},
"typeVersion": 1
},
{
"id": "56abf128-57b3-4038-a262-38b09b3e3faf",
"name": "Reads lastname,firstname and company from your sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
580,
1700
],
"parameters": {
"sheetName": {
"__rl": true,
"mode": "list",
"value": ""
},
"documentId": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"typeVersion": 4.1
},
{
"id": "f256a8e7-c8c6-4177-810e-f7af4961db05",
"name": "Run bulk search (email-search)",
"type": "n8n-nodes-base.httpRequest",
"position": [
1200,
1700
],
"parameters": {
"url": "={{ $json.api.url }}",
"method": "POST",
"options": {},
"sendBody": true,
"sendHeaders": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "task",
"value": "=email-search"
},
{
"name": "name",
"value": "Test"
},
{
"name": "user",
"value": "={{ $json.api.userId }}"
},
{
"name": "data",
"value": "={{ $json.data }}"
}
]
},
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "X-ROCK-TIMESTAMP",
"value": "={{ $json.api.timestamp }}"
}
]
}
},
"typeVersion": 4.1
}
],
"pinData": {},
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Reads lastname,firstname and company from your sheet",
"type": "main",
"index": 0
}
]
]
},
"Authenticates to your Icypeas account": {
"main": [
[
{
"node": "Run bulk search (email-search)",
"type": "main",
"index": 0
}
]
]
},
"Reads lastname,firstname and company from your sheet": {
"main": [
[
{
"node": "Authenticates to your Icypeas account",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,194 @@
{
"meta": {
"instanceId": "257476b1ef58bf3cb6a46e65fac7ee34a53a5e1a8492d5c6e4da5f87c9b82833"
},
"nodes": [
{
"id": "bfbd4299-0c8d-4368-b156-c76602ca068c",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"position": [
640,
1700
],
"parameters": {},
"typeVersion": 1
},
{
"id": "40cf87be-d9fc-434b-9099-0151968d2a0b",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
500,
1420
],
"parameters": {
"height": 259,
"content": "## Domain scan with Icypeas (bulk search)\n\n\nThis workflow demonstrates how to perform domain scans (bulk search) using Icypeas. Visit https://icypeas.com to create your account."
},
"typeVersion": 1
},
{
"id": "c646dddb-bcd4-4ac8-b08f-e61ec16c99c5",
"name": "Authenticates to your Icypeas account",
"type": "n8n-nodes-base.code",
"position": [
1140,
1700
],
"parameters": {
"jsCode": "const API_BASE_URL = \"https://app.icypeas.com/api\";\nconst API_PATH = \"/bulk-search\";\nconst METHOD = \"POST\";\n\n// Change here\nconst API_KEY = \"PUT_API_KEY_HERE\";\nconst API_SECRET = \"PUT_API_SECRET_HERE\";\nconst USER_ID = \"PUT_USER_ID_HERE\";\n////////////////\n\nconst genSignature = (\n url,\n method,\n secret,\n timestamp = new Date().toISOString()\n) => {\n const Crypto = require('crypto');\n const payload = `${method}${url}${timestamp}`.toLowerCase();\n const sign = Crypto.createHmac(\"sha1\", secret).update(payload).digest(\"hex\");\n\n return sign;\n};\n\nconst apiUrl = `${API_BASE_URL}${API_PATH}`;\n\nconst data = $input.all().map((x) => [ x.json.company]);\n$input.first().json.data = data;\n$input.first().json.api = {\n timestamp: new Date().toISOString(),\n secret: API_SECRET,\n key: API_KEY,\n userId: USER_ID,\n url: apiUrl,\n};\n\n$input.first().json.api.signature = genSignature(apiUrl, METHOD, API_SECRET, $input.first().json.api.timestamp);\nreturn $input.first();"
},
"typeVersion": 1
},
{
"id": "f0fcf039-2508-429e-8b9a-4ec1ab929d97",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
780,
1548.9314213779933
],
"parameters": {
"height": 523.2083276562503,
"content": "## Read your Google sheet file\n\nThis node reads a Google Sheet. You need to create a sheet with :\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n**The first column** :\nHeader : company\n\n\n\n\nDon't forget to specify the path of your file in the node and your credentials."
},
"typeVersion": 1
},
{
"id": "1d0d1805-f664-44d3-83be-9ea26d43526c",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1021.0092314499475,
1458.51011235955
],
"parameters": {
"width": 392.0593078758952,
"height": 1203.3290499048028,
"content": "## Authenticates to your Icypeas account\n\nThis code node utilizes your API key, API secret, and User ID to establish a connection with your Icypeas account.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nOpen this node and insert your API Key, API secret, and User ID within the quotation marks. You can locate these credentials on your Icypeas profile at https://app.icypeas.com/bo/profile. Here is the extract of what you have to change :\n\nconst API_KEY = \"**PUT_API_KEY_HERE**\";\nconst API_SECRET = \"**PUT_API_SECRET_HERE**\";\nconst USER_ID = \"**PUT_USER_ID_HERE**\";\n\nDo not change any other line of the code.\n\nIf you are a self-hosted user, follow these steps to activate the crypto module :\n\n1.Access your n8n instance:\nLog in to your n8n instance using your web browser by navigating to the URL of your instance, for example: http://your-n8n-instance.com.\n\n2.Go to Settings:\nIn the top-right corner, click on your username, then select \"Settings.\"\n\n3.Select General Settings:\nIn the left menu, click on \"General.\"\n\n4.Enable the Crypto module:\nScroll down to the \"Additional Node Packages\" section. You will see an option called \"crypto\" with a checkbox next to it. Check this box to enable the Crypto module.\n\n5.Save the changes:\nAt the bottom of the page, click \"Save\" to apply the changes.\n\nOnce you've followed these steps, the Crypto module should be activated for your self-hosted n8n instance. Make sure to save your changes and optionally restart your n8n instance for the changes to take effect.\n\n\n\n\n\n"
},
"typeVersion": 1
},
{
"id": "999fda2a-50ba-4641-8842-7d62587e0ad5",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1413,
1460
],
"parameters": {
"width": 328.8456933308303,
"height": 869.114109302513,
"content": "## Performs domain scans (bulk).\n\n\nThis node executes an HTTP request (POST) to scan the domains/companies.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n### You need to create credentials in the HTTP Request node :\n\n➔ In the Credential for Header Auth, click on - Create new Credential -.\n➔ In the Name section, write “Authorization”\n➔ In the Value section, select expression (located just above the field on the right when you hover on top of it) and write {{ $json.api.key + ':' + $json.api.signature }} .\n➔ Then click on “Save” to save the changes.\n\n### To retrieve the results :\n\nAfter some time, the results, which are downloadable, will be available in the Icypeas application in this section : https://app.icypeas.com/bo/bulksearch?task=domain-search, and you will receive the scan results via email from no-reply@icypeas.com, providing you with the results of your scans.\n\n\n\n\n"
},
"typeVersion": 1
},
{
"id": "0f5382ae-cd84-47a7-9818-ad252c9d62c3",
"name": "Reads lastname,firstname and company from your sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
840,
1700
],
"parameters": {
"sheetName": {
"__rl": true,
"mode": "list",
"value": ""
},
"documentId": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"typeVersion": 4.1
},
{
"id": "ce00b713-6ddc-4625-a9cc-e9badc2022d4",
"name": "Run bulk search (domain-search)",
"type": "n8n-nodes-base.httpRequest",
"position": [
1480,
1700
],
"parameters": {
"url": "={{ $json.api.url }}",
"method": "POST",
"options": {},
"sendBody": true,
"sendHeaders": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "task",
"value": "=domain-search"
},
{
"name": "name",
"value": "dernierT"
},
{
"name": "user",
"value": "={{ $json.api.userId }}"
},
{
"name": "data",
"value": "={{ $json.data }}"
}
]
},
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "X-ROCK-TIMESTAMP",
"value": "={{ $json.api.timestamp }}"
}
]
}
},
"typeVersion": 4.1
}
],
"pinData": {},
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Reads lastname,firstname and company from your sheet",
"type": "main",
"index": 0
}
]
]
},
"Authenticates to your Icypeas account": {
"main": [
[
{
"node": "Run bulk search (domain-search)",
"type": "main",
"index": 0
}
]
]
},
"Reads lastname,firstname and company from your sheet": {
"main": [
[
{
"node": "Authenticates to your Icypeas account",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,194 @@
{
"meta": {
"instanceId": "257476b1ef58bf3cb6a46e65fac7ee34a53a5e1a8492d5c6e4da5f87c9b82833"
},
"nodes": [
{
"id": "8e31498a-d004-4d55-8952-b07e4e49f75f",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"position": [
800,
1320
],
"parameters": {},
"typeVersion": 1
},
{
"id": "56e1351c-804d-41d4-9651-d2ca2020c4ce",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
660,
1020
],
"parameters": {
"height": 292.0581548177272,
"content": "## Perform Batch Processing of Email verifications with Icypeas \n\n\nThis workflow demonstrates how to perform email verifications (bulk search) using Icypeas. Visit https://icypeas.com to create your account."
},
"typeVersion": 1
},
{
"id": "0bd19032-2894-4e0e-b66f-00718bd389a7",
"name": "Authenticates to your Icypeas account",
"type": "n8n-nodes-base.code",
"position": [
1300,
1320
],
"parameters": {
"jsCode": "const API_BASE_URL = \"https://app.icypeas.com/api\";\nconst API_PATH = \"/bulk-search\";\nconst METHOD = \"POST\";\n\n// Change here\nconst API_KEY = \"PUT_API_KEY_HERE\";\nconst API_SECRET = \"PUT_API_SECRET_HERE\";\nconst USER_ID = \"PUT_USER_ID_HERE\";\n////////////////\n\nconst genSignature = (\n url,\n method,\n secret,\n timestamp = new Date().toISOString()\n) => {\n const Crypto = require('crypto');\n const payload = `${method}${url}${timestamp}`.toLowerCase();\n const sign = Crypto.createHmac(\"sha1\", secret).update(payload).digest(\"hex\");\n\n return sign;\n};\n\nconst apiUrl = `${API_BASE_URL}${API_PATH}`;\n\nconst data = $input.all().map((x) => [ x.json.email]);\n$input.first().json.data = data;\n$input.first().json.api = {\n timestamp: new Date().toISOString(),\n secret: API_SECRET,\n key: API_KEY,\n userId: USER_ID,\n url: apiUrl,\n};\n\n$input.first().json.api.signature = genSignature(apiUrl, METHOD, API_SECRET, $input.first().json.api.timestamp);\nreturn $input.first();"
},
"typeVersion": 1
},
{
"id": "df9bc762-c680-447f-a4f3-eba1ba13cb3d",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
940,
1168.9314213779933
],
"parameters": {
"height": 523.2083276562503,
"content": "## Read your Google sheet file\n\nThis node reads a Google Sheet. You need to create a sheet with :\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n**The first column** :\nHeader : email\n\n\n\n\nDon't forget to specify the path of your file in the node and your credentials."
},
"typeVersion": 1
},
{
"id": "c542f720-7c21-4161-a643-4e67983ad090",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1181.009231449947,
1078.51011235955
],
"parameters": {
"width": 392.0593078758952,
"height": 1203.3290499048028,
"content": "## Authenticates to your Icypeas account\n\nThis code node utilizes your API key, API secret, and User ID to establish a connection with your Icypeas account.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nOpen this node and insert your API Key, API secret, and User ID within the quotation marks. You can locate these credentials on your Icypeas profile at https://app.icypeas.com/bo/profile. Here is the extract of what you have to change :\n\nconst API_KEY = \"**PUT_API_KEY_HERE**\";\nconst API_SECRET = \"**PUT_API_SECRET_HERE**\";\nconst USER_ID = \"**PUT_USER_ID_HERE**\";\n\nDo not change any other line of the code.\n\nIf you are a self-hosted user, follow these steps to activate the crypto module :\n\n1.Access your n8n instance:\nLog in to your n8n instance using your web browser by navigating to the URL of your instance, for example: http://your-n8n-instance.com.\n\n2.Go to Settings:\nIn the top-right corner, click on your username, then select \"Settings.\"\n\n3.Select General Settings:\nIn the left menu, click on \"General.\"\n\n4.Enable the Crypto module:\nScroll down to the \"Additional Node Packages\" section. You will see an option called \"crypto\" with a checkbox next to it. Check this box to enable the Crypto module.\n\n5.Save the changes:\nAt the bottom of the page, click \"Save\" to apply the changes.\n\nOnce you've followed these steps, the Crypto module should be activated for your self-hosted n8n instance. Make sure to save your changes and optionally restart your n8n instance for the changes to take effect.\n\n\n\n\n\n"
},
"typeVersion": 1
},
{
"id": "26602f88-789e-4f9e-8df0-2f7f498f242c",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1573,
1080
],
"parameters": {
"width": 328.8456933308303,
"height": 869.114109302513,
"content": "## Performs email verifications (bulk).\n\n\nThis node executes an HTTP request (POST) to verify the emails.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n### You need to create credentials in the HTTP Request node :\n\n➔ In the Credential for Header Auth, click on - Create new Credential -.\n➔ In the Name section, write “Authorization”\n➔ In the Value section, select expression (located just above the field on the right when you hover on top of it) and write {{ $json.api.key + ':' + $json.api.signature }} .\n➔ Then click on “Save” to save the changes.\n\n### To retrieve the results :\n\nAfter some time, the results, which are downloadable, will be available in the Icypeas application in this section : https://app.icypeas.com/bo/bulksearch?task=email-verification, and you will receive the verification results via email from no-reply@icypeas.com, providing you with the results of your email verifications.\n\n\n\n\n"
},
"typeVersion": 1
},
{
"id": "96128999-d7e1-44cd-b9d3-7550e4333414",
"name": "Reads lastname,firstname and company from your sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
1000,
1320
],
"parameters": {
"sheetName": {
"__rl": true,
"mode": "list",
"value": ""
},
"documentId": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"typeVersion": 4.1
},
{
"id": "bc548060-6e09-493b-9e74-fc7ef6a9b88f",
"name": "Run bulk search (email-verif)",
"type": "n8n-nodes-base.httpRequest",
"position": [
1640,
1320
],
"parameters": {
"url": "={{ $json.api.url }}",
"method": "POST",
"options": {},
"sendBody": true,
"sendHeaders": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "task",
"value": "=email-verification"
},
{
"name": "name",
"value": "dernierTsfg"
},
{
"name": "user",
"value": "={{ $json.api.userId }}"
},
{
"name": "data",
"value": "={{ $json.data }}"
}
]
},
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "X-ROCK-TIMESTAMP",
"value": "={{ $json.api.timestamp }}"
}
]
}
},
"typeVersion": 4.1
}
],
"pinData": {},
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Reads lastname,firstname and company from your sheet",
"type": "main",
"index": 0
}
]
]
},
"Authenticates to your Icypeas account": {
"main": [
[
{
"node": "Run bulk search (email-verif)",
"type": "main",
"index": 0
}
]
]
},
"Reads lastname,firstname and company from your sheet": {
"main": [
[
{
"node": "Authenticates to your Icypeas account",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,492 @@
{
"meta": {
"instanceId": "cb484ba7b742928a2048bf8829668bed5b5ad9787579adea888f05980292a4a7"
},
"nodes": [
{
"id": "bd34c2fb-9892-408e-be1f-a25f6f9970ad",
"name": "Add your competitors here",
"type": "n8n-nodes-base.code",
"position": [
1260,
800
],
"parameters": {
"jsCode": "return [\n {\"competitor\":\"zendesk\"},\n {\"competitor\":\"intercom\"},\n {\"competitor\":\"dixa\"}\n]"
},
"typeVersion": 2
},
{
"id": "ec726fe0-e85f-47b3-8cd9-05b94fc5f8ab",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1400,
600
],
"parameters": {
"color": 7,
"width": 235.65210573476693,
"height": 396.04301075268825,
"content": "Add your API key here\n\n1. Sign up here\nhttps://app.scrapingbee.com/\n\n2. Get your API key\n\n3. Paste it the node"
},
"typeVersion": 1
},
{
"id": "fd7b88e5-ef30-488e-803e-aec43334c41b",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
460,
460
],
"parameters": {
"width": 465,
"height": 342.8125,
"content": "# Read me\nThis workflow monitor G2 reviews URLS. \n\nWhen a new review is published, it will: \n- trigger a Slack notification \n- record the review in Google Sheets\n\n\nTo install it, you'll need access to Slack, Google Sheets and ScrapingBee\n\n### Full guide here: https://lempire.notion.site/Scrape-G2-reviews-with-n8n-3f46e280e8f24a68b3797f98d2fba433?pvs=4"
},
"typeVersion": 1
},
{
"id": "925c9ce9-1691-47bd-b184-5532cfa85da5",
"name": "Execute workflow every day",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
980,
560
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 8
}
]
}
},
"typeVersion": 1.1
},
{
"id": "2dc9997d-fd94-4beb-b5be-8ec16b70f060",
"name": "Get G2 data with ScrapingBee",
"type": "n8n-nodes-base.httpRequest",
"position": [
1460,
800
],
"parameters": {
"url": "https://app.scrapingbee.com/api/v1",
"options": {
"batching": {
"batch": {
"batchSize": 3
}
}
},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "api_key",
"value": "YOUR_API_KEY"
},
{
"name": "url",
"value": "=https://www.g2.com/products/{{ $json.competitor }}/reviews?utf8=%E2%9C%93&order=most_recent "
},
{
"name": "premium_proxy",
"value": "true"
},
{
"name": "country_code",
"value": "us"
},
{
"name": "stealth_proxy",
"value": "true"
}
]
}
},
"typeVersion": 4.1
},
{
"id": "b7472e8d-5abb-489b-bf32-5d36e7bce5cc",
"name": "Get review section HTML",
"type": "n8n-nodes-base.html",
"position": [
1680,
800
],
"parameters": {
"options": {},
"operation": "extractHtmlContent",
"extractionValues": {
"values": [
{
"key": "divs",
"cssSelector": "div.paper.paper--white.paper--box.mb-2.position-relative.border-bottom",
"returnArray": true,
"returnValue": "html"
}
]
}
},
"typeVersion": 1
},
{
"id": "9ad1fb30-c388-4ad9-a299-9fb508b01a57",
"name": "Iterate on reviews",
"type": "n8n-nodes-base.itemLists",
"position": [
1840,
800
],
"parameters": {
"options": {},
"fieldToSplitOut": "divs"
},
"typeVersion": 3
},
{
"id": "cb25b05d-2543-4d42-9c7e-2db5f534db2a",
"name": "Extract structured data",
"type": "n8n-nodes-base.html",
"position": [
2020,
800
],
"parameters": {
"options": {},
"operation": "extractHtmlContent",
"dataPropertyName": "divs",
"extractionValues": {
"values": [
{
"key": "date",
"cssSelector": "div.d-f.mb-1"
},
{
"key": "reviewHtml",
"cssSelector": "div[itemprop=reviewBody]",
"returnValue": "html"
},
{
"key": "user_profile",
"attribute": "href",
"cssSelector": "a.td-n",
"returnValue": "attribute"
},
{
"key": "rating",
"attribute": "content",
"cssSelector": "meta[itemprop=ratingValue]",
"returnValue": "attribute"
},
{
"key": "reviewUrl",
"attribute": "href",
"cssSelector": "a.pjax",
"returnValue": "attribute"
}
]
}
},
"typeVersion": 1
},
{
"id": "4b2d088c-afc8-4bd9-80e1-0ef78fe94597",
"name": "Convert Review HTML to Markdown",
"type": "n8n-nodes-base.markdown",
"position": [
2200,
800
],
"parameters": {
"html": "={{ $json.reviewHtml }}",
"options": {},
"destinationKey": "review"
},
"typeVersion": 1
},
{
"id": "0c03c9a2-0ee8-4700-bf9d-f07b01fd9590",
"name": "Get all past reviews",
"type": "n8n-nodes-base.googleSheets",
"position": [
1260,
460
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Khbjjt_Dw0LdggwEE6sj300McXelmSR1ttoG8UNojyY/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/1Khbjjt_Dw0LdggwEE6sj300McXelmSR1ttoG8UNojyY/edit#gid=0"
}
},
"typeVersion": 4
},
{
"id": "27d41c8f-694b-49bf-9ea7-24964e00b9b4",
"name": "Continue only if review is new",
"type": "n8n-nodes-base.merge",
"position": [
2420,
480
],
"parameters": {
"mode": "combine",
"options": {},
"joinMode": "keepNonMatches",
"mergeByFields": {
"values": [
{
"field1": "reviewUrl",
"field2": "reviewUrl"
}
]
},
"outputDataFrom": "input2"
},
"typeVersion": 2.1
},
{
"id": "f4574136-c4ab-44ce-bf06-17b3c487867c",
"name": "Send new review to Slack",
"type": "n8n-nodes-base.slack",
"position": [
2760,
480
],
"parameters": {
"text": "=🚨 New review in G2\n\nRating: {{ $json[\"rating\"] }}\n<{{ $json[\"user_profile\"]}}|See user in G2>\n<{{$json[\"reviewUrl\"]}}|See review in G2>\n\nReview Content:\n{{ $json.review }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "g2_reviews"
},
"otherOptions": {
"botProfile": {
"imageValues": {
"icon_url": "https://upload.wikimedia.org/wikipedia/en/thumb/3/38/G2_Crowd_logo.svg/640px-G2_Crowd_logo.svg.png",
"profilePhotoType": "image"
}
},
"includeLinkToWorkflow": false
}
},
"typeVersion": 2.1
},
{
"id": "09076f69-32a4-4ddf-a662-10c0c0e35e7f",
"name": "Add new review to Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
2760,
700
],
"parameters": {
"columns": {
"value": {
"date": "={{ $json.date }}",
"rating": "={{ $json.rating }}",
"review": "={{ $json.review }}",
"reviewUrl": "={{ $json.reviewUrl }}",
"user_profile": "={{ $json.user_profile }}"
},
"schema": [
{
"id": "date",
"type": "string",
"display": true,
"required": false,
"displayName": "date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "rating",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "rating",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "review",
"type": "string",
"display": true,
"required": false,
"displayName": "review",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "user_profile",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "user_profile",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "reviewUrl",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "reviewUrl",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"reviewUrl"
]
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Khbjjt_Dw0LdggwEE6sj300McXelmSR1ttoG8UNojyY/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/1Khbjjt_Dw0LdggwEE6sj300McXelmSR1ttoG8UNojyY/edit#gid=0"
}
},
"typeVersion": 4
}
],
"pinData": {},
"connections": {
"Iterate on reviews": {
"main": [
[
{
"node": "Extract structured data",
"type": "main",
"index": 0
}
]
]
},
"Get all past reviews": {
"main": [
[
{
"node": "Continue only if review is new",
"type": "main",
"index": 0
}
]
]
},
"Extract structured data": {
"main": [
[
{
"node": "Convert Review HTML to Markdown",
"type": "main",
"index": 0
}
]
]
},
"Get review section HTML": {
"main": [
[
{
"node": "Iterate on reviews",
"type": "main",
"index": 0
}
]
]
},
"Add your competitors here": {
"main": [
[
{
"node": "Get G2 data with ScrapingBee",
"type": "main",
"index": 0
}
]
]
},
"Execute workflow every day": {
"main": [
[
{
"node": "Get all past reviews",
"type": "main",
"index": 0
},
{
"node": "Add your competitors here",
"type": "main",
"index": 0
}
]
]
},
"Get G2 data with ScrapingBee": {
"main": [
[
{
"node": "Get review section HTML",
"type": "main",
"index": 0
}
]
]
},
"Continue only if review is new": {
"main": [
[
{
"node": "Add new review to Google Sheets",
"type": "main",
"index": 0
},
{
"node": "Send new review to Slack",
"type": "main",
"index": 0
}
]
]
},
"Convert Review HTML to Markdown": {
"main": [
[
{
"node": "Continue only if review is new",
"type": "main",
"index": 1
}
]
]
}
}
}

View File

@@ -0,0 +1,343 @@
{
"meta": {
"instanceId": "dbd43d88d26a9e30d8aadc002c9e77f1400c683dd34efe3778d43d27250dde50"
},
"nodes": [
{
"id": "174f80b5-6c84-47b3-a906-eeb4fc5207b8",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-840,
620
],
"webhookId": "5dc2467c-0b39-43e9-bdbd-399231f69c4e",
"parameters": {
"path": "5dc2467c-0b39-43e9-bdbd-399231f69c4e",
"options": {},
"httpMethod": "POST",
"responseCode": null
},
"typeVersion": 1
},
{
"id": "e03fc5ca-9446-44b7-9c0a-44c8696ec06a",
"name": "Code",
"type": "n8n-nodes-base.code",
"position": [
-540,
620
],
"parameters": {
"jsCode": "\nconst available = items[0].json.body.available;\nconst inventory_item = items[0].json.body.inventory_item_id;\nconst lowInventory = available > 0 && available < 4;\nconst outOfStock = available === 0;\n\nreturn [\n {\n json: {\n available: available,\n inventory_tem: inventory_item,\n low_inventory: lowInventory,\n out_of_stock: outOfStock,\n },\n },\n];"
},
"typeVersion": 1
},
{
"id": "2e8b6898-87aa-4e27-80df-647f022e7810",
"name": "Low Inventory",
"type": "n8n-nodes-base.if",
"position": [
-180,
500
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.low_inventory }}",
"value2": "={{ true }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "02c33a4d-e806-4447-a754-5d2027ebfc2b",
"name": "Out of stock",
"type": "n8n-nodes-base.if",
"position": [
-180,
780
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.out_of_stock }}",
"value2": "={{ true }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "ce6a4937-ce78-486e-adcb-a0d11a856cd9",
"name": "HTTP Request",
"type": "n8n-nodes-base.httpRequest",
"position": [
560,
400
],
"parameters": {
"body": "={\n \"embeds\": [\n {\n \"title\": \"{{ $json.data.inventoryItem.variant.product.title }}\",\n \"description\": \"This product is running out of stock!\",\n \"color\": 16776960,\n \"fields\": [\n {\n \"name\": \"Remaining Inventory\",\n \"value\": \"{{ $json.data.inventoryItem.variant.inventoryQuantity }}\",\n \"inline\": false\n },\n {\n \"name\": \"Product Variant\",\n \"value\": \"{{ $json.data.inventoryItem.variant.title }}\",\n \"inline\": true\n }\n ],\n \"image\": {\n \"url\": \"{{ $json.data.inventoryItem.variant.product.images.edges[0].node.originalSrc }}\"\n },\n \"footer\": {\n \"text\": \"Alert from inventory management system\"\n }\n }\n ]\n}",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "raw",
"authentication": "predefinedCredentialType",
"rawContentType": "application/json",
"nodeCredentialType": "discordBotApi"
},
"credentials": {
"discordBotApi": {
"id": "opA36m6ZPvLM8V3I",
"name": "Discord Bot account"
}
},
"typeVersion": 4.1
},
{
"id": "4a571564-03a1-44de-a06d-b5142911d6f4",
"name": "HTTP Request1",
"type": "n8n-nodes-base.httpRequest",
"position": [
560,
860
],
"parameters": {
"body": "={\n \"embeds\": [\n {\n \"title\": \"{{ $json.data.inventoryItem.variant.product.title }}\",\n \"description\": \"This product is sold out!\",\n \"color\": 16711680,\n \"fields\": [\n {\n \"name\": \"Remaining Inventory\",\n \"value\": \"{{ $json.data.inventoryItem.variant.inventoryQuantity }}\",\n \"inline\": false\n },\n {\n \"name\": \"Product Variant\",\n \"value\": \"{{ $json.data.inventoryItem.variant.title }}\",\n \"inline\": true\n }\n ],\n \"image\": {\n \"url\": \"{{ $json.data.inventoryItem.variant.product.images.edges[0].node.originalSrc }}\"\n },\n \"footer\": {\n \"text\": \"Alert from inventory management system\"\n }\n }\n ]\n}",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "raw",
"authentication": "predefinedCredentialType",
"rawContentType": "application/json",
"nodeCredentialType": "discordBotApi"
},
"credentials": {
"discordBotApi": {
"id": "opA36m6ZPvLM8V3I",
"name": "Discord Bot account"
}
},
"typeVersion": 4.1
},
{
"id": "703b259c-e655-41e2-abb0-9ad80d2224a5",
"name": "GraphQL1- shopify",
"type": "n8n-nodes-base.graphql",
"position": [
180,
400
],
"parameters": {
"query": "={\n inventoryItem(id: \"gid://shopify/InventoryItem/{{ $json.inventory_tem }}\") {\n id\n variant {\n id\n title\n inventoryQuantity # This line adds the inventory quantity field\n product {\n id\n title\n images(first: 1) {\n edges {\n node {\n originalSrc\n }\n }\n }\n }\n }\n }\n}",
"endpoint": "https://store.myshopify.com/admin/api/2023-10/graphql.json",
"authentication": "headerAuth"
},
"typeVersion": 1
},
{
"id": "eb4c0d15-85b8-42cf-9c0d-d53e3e787cf9",
"name": "GraphQL - shopify",
"type": "n8n-nodes-base.graphql",
"position": [
200,
860
],
"parameters": {
"query": "={\n inventoryItem(id: \"gid://shopify/InventoryItem/{{ $json.inventory_tem }}\") {\n id\n variant {\n id\n title\n inventoryQuantity # This line adds the inventory quantity field\n product {\n id\n title\n images(first: 1) {\n edges {\n node {\n originalSrc\n }\n }\n }\n }\n }\n }\n}",
"endpoint": "https://store.myshopify.com/admin/api/2023-10/graphql.json",
"authentication": "headerAuth"
},
"typeVersion": 1
},
{
"id": "b06a4e50-f640-48a3-92e1-f41584a2e89b",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1160,
600
],
"parameters": {
"color": 7,
"width": 253.05487804878055,
"height": 376,
"content": "### Webhook Node (Shopify Listener)\nSetup Requirement: First, add the \"Inventory Level Update\" event in Shopify\n\nPurpose: Listens for inventory updates from Shopify\n\nSetup: Configured in Shopify settings; linked to n8n URL\n\nAction: Triggers workflow on inventory level changes\n\nNote: Ensure correct URL setup in Shopify for accurate triggers"
},
"typeVersion": 1
},
{
"id": "a4e7c588-56f2-4d4f-8531-8969f0667b79",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-600,
780
],
"parameters": {
"color": 7,
"width": 246.67682926829286,
"height": 318,
"content": "### Function Node (Inventory Check)\n\nPurpose: Processes inventory data from Shopify.\nAction: Extracts available inventory and item ID\n\nLogic: Determines if inventory is low (<4 items) or out of stock (0 items)\n\nNote: Adjust thresholds as needed for different stock levels"
},
"typeVersion": 1
},
{
"id": "3e25dfbf-38b3-4206-891f-194f175db418",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-240,
400
],
"parameters": {
"color": 7,
"width": 185,
"height": 80,
"content": "Checks if low_inventory is true (almost out of stock)"
},
"typeVersion": 1
},
{
"id": "2527ba84-ba49-4a08-a9d4-cb8af9b9723d",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-220,
920
],
"parameters": {
"color": 7,
"width": 180,
"height": 80,
"content": "Checks if out_of_stock is true (no stock left)"
},
"typeVersion": 1
},
{
"id": "a879f649-abd0-4b72-86de-deac6b6b4dc6",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
120,
560
],
"parameters": {
"color": 7,
"width": 272,
"height": 258.34634146341466,
"content": "### Shopify graphql\n\nRetrieves product variant, title, inventory quantity, and image.\nUses Shopify's GraphQL API for detailed data retrieval.\n\nEndpoint to be customized: Replace store.myshopify.com in https://store.myshopify.com/admin/api/2023-10/graphql.json with your actual Shopify store's myshopify URL."
},
"typeVersion": 1
},
{
"id": "5b7fa7ff-61e3-44c3-9bd3-2ac1c058df8c",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
520,
580
],
"parameters": {
"color": 7,
"width": 214,
"height": 145,
"content": "Discord1: Configured to send messages to Channel A\n\nDiscord2: Configured to send messages to Channel B."
},
"typeVersion": 1
},
{
"id": "809838f1-70ee-46ab-9cf4-2a8cb4fe35a2",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1160,
260
],
"parameters": {
"width": 361.2353658536575,
"height": 305.7548780487801,
"content": "## Low Stock & Sold Out Watcher for Shopify\nThis n8n workflow automates the process of monitoring inventory levels for Shopify products, ensuring timely updates and efficient stock management. \n\nIt is designed to alert users when inventory levels are low or out of stock, integrating with Shopify's webhook system and providing notifications through Discord (can be changed to any messaging platform) with product images and details.\n"
},
"typeVersion": 1
}
],
"connections": {
"Code": {
"main": [
[
{
"node": "Low Inventory",
"type": "main",
"index": 0
},
{
"node": "Out of stock",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Out of stock": {
"main": [
[
{
"node": "GraphQL - shopify",
"type": "main",
"index": 0
}
]
]
},
"Low Inventory": {
"main": [
[
{
"node": "GraphQL1- shopify",
"type": "main",
"index": 0
}
]
]
},
"GraphQL1- shopify": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"GraphQL - shopify": {
"main": [
[
{
"node": "HTTP Request1",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,342 @@
{
"meta": {
"instanceId": "0bd9e607aabfd58640f9f5a370e768a7755e93315179f5bcc6d1f8f114b3567a"
},
"nodes": [
{
"id": "97b36168-7fa8-4a97-a6cc-c42496918c4c",
"name": "Search Person in CRM",
"type": "n8n-nodes-base.pipedrive",
"position": [
-880,
400
],
"parameters": {
"term": "={{ $json.from.value[0].address }}",
"limit": 1,
"resource": "person",
"operation": "search",
"additionalFields": {
"includeFields": ""
}
},
"credentials": {
"pipedriveApi": {
"id": "MdJQDtRDHnpwuVYP",
"name": "Pipedrive LinkedUp"
}
},
"typeVersion": 1
},
{
"id": "2a17582b-9375-4a01-87d9-a50f573b83db",
"name": "In campaign?",
"type": "n8n-nodes-base.if",
"position": [
-420,
400
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.in_campaign }}",
"value2": "True"
}
]
}
},
"typeVersion": 1
},
{
"id": "2a8d509f-8ac2-4f45-a905-f34552833381",
"name": "Get person from CRM",
"type": "n8n-nodes-base.pipedrive",
"position": [
-640,
400
],
"parameters": {
"personId": "={{ $json.id }}",
"resource": "person",
"operation": "get",
"resolveProperties": true
},
"credentials": {
"pipedriveApi": {
"id": "MdJQDtRDHnpwuVYP",
"name": "Pipedrive LinkedUp"
}
},
"typeVersion": 1
},
{
"id": "b9c6f3d3-1a6d-4144-8e77-3a3c6e5282d8",
"name": "Is interested?",
"type": "n8n-nodes-base.openAi",
"position": [
-180,
380
],
"parameters": {
"model": "gpt-4",
"prompt": {
"messages": [
{
"content": "=You are the best sales development representative in the world. You send cold email messages daily to CEOs and founders of companies. You do this to persuade them to make contact. This could be a phone call or a video meeting. \n\nYour task is to assess whether someone is interested in meeting up or calling sometime. You do this by attentively evaluating their response.\n\nThis is the email:\n{{ $('Get email').item.json.text }}\n\nThe response format should be:\n{\"interested\": [yes/no],\n\"reason\": reason\n}\n\nJSON:"
}
]
},
"options": {},
"resource": "chat"
},
"credentials": {
"openAiApi": {
"id": "qPBzqgpCRxncJ90K",
"name": "OpenAi account 2"
}
},
"typeVersion": 1
},
{
"id": "f1eb438d-f002-4082-8481-51565df13f5c",
"name": "Get email",
"type": "n8n-nodes-base.set",
"position": [
-1100,
400
],
"parameters": {
"fields": {
"values": [
{
"name": "email",
"stringValue": "={{ $json.text }}"
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "78461c36-ba54-4f0f-a38e-183bfafa576c",
"name": "Create deal in CRM",
"type": "n8n-nodes-base.pipedrive",
"position": [
460,
360
],
"parameters": {
"title": "={{ $('Get person from CRM').item.json.Name }} Deal",
"additionalFields": {}
},
"credentials": {
"pipedriveApi": {
"id": "MdJQDtRDHnpwuVYP",
"name": "Pipedrive LinkedUp"
}
},
"typeVersion": 1
},
{
"id": "efe07661-9afc-4184-b558-e1f547b6721f",
"name": "IF interested",
"type": "n8n-nodes-base.if",
"position": [
240,
380
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.interested }}",
"value2": "yes"
}
]
}
},
"typeVersion": 1
},
{
"id": "7c2b7b59-9d68-4d8c-9b9f-a36ea47526c9",
"name": "Get response",
"type": "n8n-nodes-base.code",
"position": [
20,
380
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "let interested = JSON.parse($json[\"message\"][\"content\"]).interested\nlet reason = JSON.parse($json[\"message\"][\"content\"]).reason\n\nreturn {json:{\n interested: interested,\n reason: reason\n}}"
},
"typeVersion": 1
},
{
"id": "53f51f8c-5995-4bcd-a038-3018834942e6",
"name": "Email box 1",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
-1300,
400
],
"parameters": {
"simple": false,
"filters": {
"labelIds": []
},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"typeVersion": 1
},
{
"id": "bb1254ec-676a-4edc-bf4a-a1c66bac78bb",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1880,
360
],
"parameters": {
"width": 452.37174177689576,
"height": 462.1804790107177,
"content": "## About the workflow\nThe workflow reads every reply that is received from a cold email campaign and qualifies if the lead is interested in a meeting. If the lead is interested, a deal is made in pipedrive. You can add as many email inboxes as you need!\n\n## Setup:\n- Add credentials to the Gmail, OpenAI and Pipedrive Nodes.\n- Add a in_campaign field in Pipedrive for persons. In Pipedrive click on your credentials at the top right, go to company settings > Data fields > Person and click on add custom field. Single option [TRUE/FALSE].\n- If you have only one email inbox, you can delete one of the Gmail nodes.\n- If you have more than two email inboxes, you can duplicate a Gmail node as many times as you like. Just connect it to the Get email node, and you are good to go!\n- In the Gmail inbox nodes, select Inbox under label names and uncheck Simplify."
},
"typeVersion": 1
},
{
"id": "c1aaee97-11f4-4e9d-9a71-90ca3f5773a9",
"name": "Email box 2",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
-1300,
600
],
"parameters": {
"simple": false,
"filters": {
"labelIds": []
},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Get email": {
"main": [
[
{
"node": "Search Person in CRM",
"type": "main",
"index": 0
}
]
]
},
"Email box 1": {
"main": [
[
{
"node": "Get email",
"type": "main",
"index": 0
}
]
]
},
"Email box 2": {
"main": [
[
{
"node": "Get email",
"type": "main",
"index": 0
}
]
]
},
"Get response": {
"main": [
[
{
"node": "IF interested",
"type": "main",
"index": 0
}
]
]
},
"In campaign?": {
"main": [
[
{
"node": "Is interested?",
"type": "main",
"index": 0
}
]
]
},
"IF interested": {
"main": [
[
{
"node": "Create deal in CRM",
"type": "main",
"index": 0
}
]
]
},
"Is interested?": {
"main": [
[
{
"node": "Get response",
"type": "main",
"index": 0
}
]
]
},
"Get person from CRM": {
"main": [
[
{
"node": "In campaign?",
"type": "main",
"index": 0
}
]
]
},
"Search Person in CRM": {
"main": [
[
{
"node": "Get person from CRM",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,268 @@
{
"meta": {
"instanceId": "c59a6b1daf09a846754bc2cf0a94db3299bd5a69fb14687c3a5e692704c548dd"
},
"nodes": [
{
"id": "2165cd37-10ff-46bd-88a5-c8377bf4bef7",
"name": "Google Drive",
"type": "n8n-nodes-base.googleDrive",
"position": [
1280,
1100
],
"parameters": {
"limit": 100,
"options": {
"spaces": [
"*"
],
"corpora": "allDrives"
},
"operation": "list",
"queryString": "='{{ $json[\"Folder ID\"] }}' in parents",
"authentication": "oAuth2",
"useQueryString": true
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "KJE0ZORR1Q1fJCd5",
"name": "Google Drive account 2"
}
},
"typeVersion": 1
},
{
"id": "5061db5e-2137-4c50-8902-a24cd53a6bdf",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
1480,
1160
],
"parameters": {
"options": {},
"batchSize": 50
},
"typeVersion": 3
},
{
"id": "62a16fb8-9bfc-46db-a556-23fac7f403f5",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
1720,
1020
],
"parameters": {
"mode": "combine",
"options": {},
"combinationMode": "multiplex"
},
"typeVersion": 2.1
},
{
"id": "bd410148-e745-43a2-960b-128bbb49828f",
"name": "Set Folder ID",
"type": "n8n-nodes-base.set",
"notes": "Enter desired Folder",
"position": [
1120,
1100
],
"parameters": {
"fields": {
"values": [
{
"name": "Folder ID",
"stringValue": "Enter Your Folder ID here"
}
]
},
"options": {}
},
"notesInFlow": true,
"typeVersion": 3.2
},
{
"id": "16def9df-5c8b-4359-a879-11e66f191f92",
"name": "Manual Execute Workflow",
"type": "n8n-nodes-base.manualTrigger",
"notes": "Optional",
"position": [
940,
1100
],
"parameters": {},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "e7d54620-e5e6-470e-add5-ccefdfb2a979",
"name": "Generate Download Links",
"type": "n8n-nodes-base.code",
"position": [
1480,
980
],
"parameters": {
"jsCode": "// This function will create an array of file links from the given Google Drive folder\nreturn items.map(file => {\n return { json: { 'link': `https://drive.google.com/u/3/uc?id=${file.json.id}&export=download&confirm=t&authuser=0`, 'name': file.json.name } };\n});"
},
"typeVersion": 2
},
{
"id": "04e71edf-c40f-4c80-961c-f511e145232c",
"name": "Change Status",
"type": "n8n-nodes-base.googleDrive",
"notes": "Make Files Public to anyone with a link",
"position": [
1660,
1180
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {
"supportsAllDrives": true
},
"operation": "share",
"permissionsUi": {
"permissionsValues": {
"role": "reader",
"type": "anyone"
}
},
"authentication": "oAuth2"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "KJE0ZORR1Q1fJCd5",
"name": "Google Drive account 2"
}
},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "4452cd81-e94a-465e-987b-5acf46e25428",
"name": "Replace Me",
"type": "n8n-nodes-base.noOp",
"position": [
1880,
1020
],
"parameters": {},
"typeVersion": 1
},
{
"id": "dab69e10-d9af-4ece-a6c6-cb35468e3bf0",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
880,
820
],
"parameters": {
"width": 1235.0111197082438,
"height": 545.6382804772701,
"content": "## Example Output:\n```JSON\n{\n\"link\": \"https://drive.google.com/u/3/uc?id=1hojqPfXchNTY8YRTNkxSo-8txK9re-V4&export=download&confirm=t&authuser=0\",\n\"name\": \"firefox_rNjA0ybKu7.png\",\n\"kind\": \"drive#permission\",\n\"id\": \"anyoneWithLink\",\n\"type\": \"anyone\",\n\"role\": \"reader\",\n\"allowFileDiscovery\": false\n}\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n### You can store the output data with any data store node you want\n### for example save them into Excel Sheet or Airtable etc..."
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Merge": {
"main": [
[
{
"node": "Replace Me",
"type": "main",
"index": 0
}
]
]
},
"Google Drive": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
},
{
"node": "Generate Download Links",
"type": "main",
"index": 0
}
]
]
},
"Change Status": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Set Folder ID": {
"main": [
[
{
"node": "Google Drive",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
],
[
{
"node": "Change Status",
"type": "main",
"index": 0
}
]
]
},
"Generate Download Links": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Manual Execute Workflow": {
"main": [
[
{
"node": "Set Folder ID",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,934 @@
{
"meta": {
"instanceId": "f0a68da631efd4ed052a324b63ff90f7a844426af0398a68338f44245d1dd9e5"
},
"nodes": [
{
"id": "edef59f6-0197-408e-a819-141c1ca8dedd",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-1820,
780
],
"parameters": {},
"typeVersion": 1
},
{
"id": "d56deaf5-1ad8-45a2-bbf0-3b71560f8036",
"name": "Extract next start value",
"type": "n8n-nodes-base.code",
"position": [
-640,
480
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "let nextUrl\n\nif ($json && $json[\"serpapi_pagination\"] && $json[\"serpapi_pagination\"][\"next\"]) {\n nextUrl = $json[\"serpapi_pagination\"][\"next\"];\n\n$input.item.json.start = nextUrl.split('&').find(param => param.startsWith('start=')).split('=')[1];\n}\n\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "6562c236-c957-437b-91a9-15e98af09858",
"name": "Merge all values from SERPAPI",
"type": "n8n-nodes-base.code",
"position": [
-140,
680
],
"parameters": {
"jsCode": "const allData = []\n\nlet counter = 0;\ndo {\n try {\n const items = $items(\"SERPAPI - Scrape Google Maps URL\", 0, counter).map(item => item.json.local_results);\n allData.push.apply(allData, items);\n } catch (error) {\n return [{json: {allData}}]; \n }\n\n counter++;\n} while(true);\nreturn $input.all();"
},
"typeVersion": 2
},
{
"id": "357e8a57-dbb5-4241-b9c2-863b3ae3fd96",
"name": "Transform data in the right format",
"type": "n8n-nodes-base.code",
"position": [
380,
680
],
"parameters": {
"jsCode": "console.log($input.all())\n\n\nconst data = $input.all()\n\nconsole.log(\"error\",data)\n\nfunction mergeData(data) {\n let merged = [];\n data.forEach(entry => {\n for (const key in entry.json) {\n merged.push(entry.json[key]);\n }\n });\n return merged;\n}\n\nconst mergedData = mergeData(data);\nconsole.log(mergedData);\n\n\nreturn mergedData.filter(item => item !== null);"
},
"typeVersion": 2
},
{
"id": "4e8e9ae9-f5e3-4bfd-b0d4-9403a1c38d39",
"name": "Add rows in Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
760,
680
],
"parameters": {
"columns": {
"value": {},
"schema": [
{
"id": "position",
"type": "string",
"display": true,
"required": false,
"displayName": "position",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "title",
"type": "string",
"display": true,
"required": false,
"displayName": "title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "phone",
"type": "string",
"display": true,
"required": false,
"displayName": "phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "website",
"type": "string",
"display": true,
"required": false,
"displayName": "website",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "rating",
"type": "string",
"display": true,
"required": false,
"displayName": "rating",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "reviews",
"type": "string",
"display": true,
"required": false,
"displayName": "reviews",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "place_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "place_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "data_id",
"type": "string",
"display": true,
"required": false,
"displayName": "data_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "data_cid",
"type": "string",
"display": true,
"required": false,
"displayName": "data_cid",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "reviews_link",
"type": "string",
"display": true,
"required": false,
"displayName": "reviews_link",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "photos_link",
"type": "string",
"display": true,
"required": false,
"displayName": "photos_link",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "gps_coordinates",
"type": "string",
"display": true,
"required": false,
"displayName": "gps_coordinates",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "place_id_search",
"type": "string",
"display": true,
"required": false,
"displayName": "place_id_search",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "provider_id",
"type": "string",
"display": true,
"required": false,
"displayName": "provider_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "price",
"type": "string",
"display": true,
"required": false,
"displayName": "price",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "type",
"type": "string",
"display": true,
"required": false,
"displayName": "type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "types",
"type": "string",
"display": true,
"required": false,
"displayName": "types",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "address",
"type": "string",
"display": true,
"required": false,
"displayName": "address",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "open_state",
"type": "string",
"display": true,
"required": false,
"displayName": "open_state",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "hours",
"type": "string",
"display": true,
"required": false,
"displayName": "hours",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "operating_hours",
"type": "string",
"display": true,
"required": false,
"displayName": "operating_hours",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "description",
"type": "string",
"display": true,
"required": false,
"displayName": "description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "service_options",
"type": "string",
"display": true,
"required": false,
"displayName": "service_options",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "order_online",
"type": "string",
"display": true,
"required": false,
"displayName": "order_online",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "thumbnail",
"type": "string",
"display": true,
"required": false,
"displayName": "thumbnail",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "editorial_reviews",
"type": "string",
"display": true,
"required": false,
"displayName": "editorial_reviews",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "unclaimed_listing",
"type": "string",
"display": true,
"required": false,
"displayName": "unclaimed_listing",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "autoMapInputData",
"matchingColumns": [
"place_id"
]
},
"options": {
"cellFormat": "RAW"
},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 2023033319,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/170osqaLBql9M-4RAH3_lBKR7ZMaQqyLUkAD-88xGuEQ/edit#gid=2023033319",
"cachedResultName": "Results"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/170osqaLBql9M-4RAH3_lBKR7ZMaQqyLUkAD-88xGuEQ/edit#gid=2023033319"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "2",
"name": "Google Sheets account lucas"
}
},
"typeVersion": 4.2
},
{
"id": "90194aa3-5960-47bd-9e0e-efee827004c4",
"name": "SERPAPI - Scrape Google Maps URL",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"position": [
-920,
500
],
"parameters": {
"url": "https://serpapi.com/search.json",
"options": {},
"sendQuery": true,
"authentication": "predefinedCredentialType",
"queryParameters": {
"parameters": [
{
"name": "engine",
"value": "google_maps"
},
{
"name": "q",
"value": "={{$json?.search_parameters?.q || $json.keyword }} "
},
{
"name": "ll",
"value": "={{ $json?.search_parameters?.ll|| $json.geo }}"
},
{
"name": "type",
"value": "search"
},
{
"name": "start",
"value": "={{ $json.start|| 0 }}"
}
]
},
"nodeCredentialType": "serpApi"
},
"credentials": {
"serpApi": {
"id": "MP9W6wBcEfhc6ofn",
"name": "SerpAPI account"
}
},
"typeVersion": 4.1
},
{
"id": "9cfd1e76-2d7c-4618-86d6-e1f3c7729044",
"name": "Remove duplicate items",
"type": "n8n-nodes-base.itemLists",
"position": [
580,
680
],
"parameters": {
"compare": "selectedFields",
"options": {},
"operation": "removeDuplicates",
"fieldsToCompare": "place_id"
},
"typeVersion": 3.1
},
{
"id": "e47dfa17-fc84-4694-950d-165302a9075f",
"name": "Split out items",
"type": "n8n-nodes-base.itemLists",
"position": [
40,
680
],
"parameters": {
"options": {},
"fieldToSplitOut": "allData"
},
"typeVersion": 3.1
},
{
"id": "decc89ee-f836-4ec2-9a1a-0371a8889ef5",
"name": "Remove empty values",
"type": "n8n-nodes-base.filter",
"position": [
200,
680
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json[0] }}",
"operation": "isNotEmpty"
}
]
}
},
"typeVersion": 1
},
{
"id": "bd7ca3a5-2697-4653-abf6-372088d925e6",
"name": "Google Sheets - Get searches to scrap",
"type": "n8n-nodes-base.googleSheets",
"position": [
-1380,
500
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupColumn": "Status"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/170osqaLBql9M-4RAH3_lBKR7ZMaQqyLUkAD-88xGuEQ/edit#gid=0",
"cachedResultName": "Add your search here"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/170osqaLBql9M-4RAH3_lBKR7ZMaQqyLUkAD-88xGuEQ/edit#gid=0"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "2",
"name": "Google Sheets account lucas"
}
},
"typeVersion": 4.2
},
{
"id": "6d0be482-b1ad-4fa0-8d2e-bef9a4414924",
"name": "Extract keyword and location from URL",
"type": "n8n-nodes-base.set",
"position": [
-1160,
500
],
"parameters": {
"fields": {
"values": [
{
"name": "keyword",
"stringValue": "={{ $json.URL.match(/\\/search\\/(.*?)\\//)[1] }}"
},
{
"name": "geo",
"stringValue": "={{ $json.URL.match(/(@[^\\/?]+)/)[1]}}"
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "f6fb8db2-8444-4605-af9d-22ca71c7937d",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1920,
200
],
"parameters": {
"width": 312.2965981499806,
"height": 266.8807730722022,
"content": "## Adjust frequency to your own needs"
},
"typeVersion": 1
},
{
"id": "234b5d18-75d7-495b-960f-06192d9e7c61",
"name": "Run workflow every hours",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-1800,
300
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "ea2e9b89-e52c-4e25-803a-ce33c41c20fc",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1460,
200
],
"parameters": {
"height": 511.2196121145973,
"content": "## Copy my template and connect it to n8n\n\nTemplate link: \n https://docs.google.com/spreadsheets/d/170osqaLBql9M-4RAH3_lBKR7ZMaQqyLUkAD-88xGuEQ/edit?usp=sharing"
},
"typeVersion": 1
},
{
"id": "de515d8d-641d-4b91-9472-608ad3878e32",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-980,
169.3107006491615
],
"parameters": {
"height": 535.9388810024284,
"content": "## Add your SERPAPI API Key\n\nStart a free trial at serpapi.com and get your API key in \"Your account\" section"
},
"typeVersion": 1
},
{
"id": "a5a63861-7ff3-4072-a669-acce938939d8",
"name": "Update Status to Success",
"type": "n8n-nodes-base.googleSheets",
"position": [
940,
680
],
"parameters": {
"columns": {
"value": {
"URL": "={{ $('Google Sheets - Get searches to scrap').first().json.URL }}",
"Status": "✅"
},
"schema": [
{
"id": "URL",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"URL"
]
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/170osqaLBql9M-4RAH3_lBKR7ZMaQqyLUkAD-88xGuEQ/edit#gid=0",
"cachedResultName": "Add your search url here"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/170osqaLBql9M-4RAH3_lBKR7ZMaQqyLUkAD-88xGuEQ/edit#gid=0"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "2",
"name": "Google Sheets account lucas"
}
},
"executeOnce": true,
"typeVersion": 4.2
},
{
"id": "2bdc5b63-e3bb-453e-a236-9c83cd12cc03",
"name": "Update Status to Error",
"type": "n8n-nodes-base.googleSheets",
"position": [
-640,
620
],
"parameters": {
"columns": {
"value": {
"URL": "={{ $('Google Sheets - Get searches to scrap').first().json.URL }}",
"Status": "❌"
},
"schema": [
{
"id": "URL",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"URL"
]
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/170osqaLBql9M-4RAH3_lBKR7ZMaQqyLUkAD-88xGuEQ/edit#gid=0",
"cachedResultName": "Add your search url here"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/170osqaLBql9M-4RAH3_lBKR7ZMaQqyLUkAD-88xGuEQ/edit#gid=0"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "2",
"name": "Google Sheets account lucas"
}
},
"executeOnce": true,
"typeVersion": 4.2
},
{
"id": "f413f4ef-fd6d-4967-8df7-a4fee4361246",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1920,
640
],
"parameters": {
"width": 312.2965981499806,
"height": 310.4703136043695,
"content": "## Click on Execute Workflow to run the workflow manually"
},
"typeVersion": 1
},
{
"id": "cbc53e43-f141-434c-a655-feb1f7d3c65b",
"name": "Continue IF Loop is complete",
"type": "n8n-nodes-base.if",
"position": [
-380,
620
],
"parameters": {
"conditions": {
"number": [
{
"value1": "={{ $json.search_parameters.start }}",
"operation": "isNotEmpty"
}
],
"string": [
{
"value1": "={{ $json.serpapi_pagination.next }}",
"operation": "isNotEmpty"
}
]
}
},
"typeVersion": 1
},
{
"id": "229f9652-b703-490e-aa3c-c28b0fe1f2e8",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2340,
200
],
"parameters": {
"width": 357.33341618921213,
"height": 532.3420004517685,
"content": "## Read Me\n\nThis workflow allows to scrape Google Maps data in an efficient way using SerpAPI. \n\nYou'll get all data from Gmaps at a cheaper cost than Google Maps API.\n\nAdd as input, your Google Maps search URL and you'll get a list of places with many data points such as:\n- phone number\n- website\n- rating\n- reviews\n- address\n\nAnd much more.\n\n\n**Full guide to implement the workflow is here**: \n\nhttps://lempire.notion.site/Scrape-Google-Maps-places-with-n8n-b7f1785c3d474e858b7ee61ad4c21136?pvs=4"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Split out items": {
"main": [
[
{
"node": "Remove empty values",
"type": "main",
"index": 0
}
]
]
},
"Remove empty values": {
"main": [
[
{
"node": "Transform data in the right format",
"type": "main",
"index": 0
}
]
]
},
"Remove duplicate items": {
"main": [
[
{
"node": "Add rows in Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"Extract next start value": {
"main": [
[
{
"node": "Continue IF Loop is complete",
"type": "main",
"index": 0
}
]
]
},
"Run workflow every hours": {
"main": [
[
{
"node": "Google Sheets - Get searches to scrap",
"type": "main",
"index": 0
}
]
]
},
"Add rows in Google Sheets": {
"main": [
[
{
"node": "Update Status to Success",
"type": "main",
"index": 0
}
]
]
},
"Continue IF Loop is complete": {
"main": [
[
{
"node": "SERPAPI - Scrape Google Maps URL",
"type": "main",
"index": 0
}
],
[
{
"node": "Merge all values from SERPAPI",
"type": "main",
"index": 0
}
]
]
},
"Merge all values from SERPAPI": {
"main": [
[
{
"node": "Split out items",
"type": "main",
"index": 0
}
]
]
},
"SERPAPI - Scrape Google Maps URL": {
"main": [
[
{
"node": "Extract next start value",
"type": "main",
"index": 0
}
],
[
{
"node": "Update Status to Error",
"type": "main",
"index": 0
}
]
]
},
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Google Sheets - Get searches to scrap",
"type": "main",
"index": 0
}
]
]
},
"Transform data in the right format": {
"main": [
[
{
"node": "Remove duplicate items",
"type": "main",
"index": 0
}
]
]
},
"Extract keyword and location from URL": {
"main": [
[
{
"node": "SERPAPI - Scrape Google Maps URL",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets - Get searches to scrap": {
"main": [
[
{
"node": "Extract keyword and location from URL",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,437 @@
{
"meta": {
"instanceId": "257476b1ef58bf3cb6a46e65fac7ee34a53a5e1a8492d5c6e4da5f87c9b82833",
"templateId": "2070"
},
"nodes": [
{
"id": "99daceb3-fb96-4324-ac87-4ffef333dc81",
"name": "Get Automated Task",
"type": "n8n-nodes-base.airtable",
"position": [
1040,
660
],
"parameters": {
"id": "={{ $('Entered View \"First Task - Create Task\"').item.json[\"id\"] }}",
"base": {
"__rl": true,
"mode": "id",
"value": "={{ $item(\"0\").$node[\"Airtable Base ID's\"].json[\"base_id\"] }}"
},
"table": {
"__rl": true,
"mode": "id",
"value": "={{ $item(\"0\").$node[\"Airtable Base ID's\"].json[\"table_automate_id\"] }}"
},
"options": {},
"operation": "get"
},
"typeVersion": 2
},
{
"id": "4a29d735-5039-4935-9803-66df6a67e590",
"name": "Create Task",
"type": "n8n-nodes-base.httpRequest",
"position": [
2140,
660
],
"parameters": {
"url": "=https://api.airtable.com/v0/{{ $item(\"0\").$node[\"Airtable Base ID's\"].json[\"base_id\"] }}/{{ $item(\"0\").$node[\"Airtable Base ID's\"].json[\"table_task_id\"] }}",
"method": "POST",
"options": {},
"jsonBody": "={\n \"records\": [\n {\n \"fields\": {\n \"Status\": \"Todo\",\n \"Task Name\": \"{{ $item(\"0\").$node[\"Get Task Template\"].json[\"Template Name\"] }}\",\n \"Task Description\": \"{{ $('Get Task Template').item.json[\"Description\"].replace(/\\r?\\n/g, \"\\\\n\") }}\",\n \"Kickoff Date\": \"{{ $('Calculate Dates').item.json[\"Kickoff Date\"] }}\",\n \"Soft Due Date\": \"{{ $('Calculate Dates').item.json[\"Soft Due Date\"] }}\",\n \"Hard Due Date\": \"{{ $('Calculate Dates').item.json[\"Hard Due Date\"] }}\",\n \"Assignee\": [\n \"{{ $('Get Assignee').item.json[\"id\"] }}\"\n ],\n \"Template\": [\n \"{{ $('Get Task Template').item.json[\"id\"] }}\"\n ],\n \"Client\": [\n \"{{ $('Get Client').item.json[\"id\"] }}\"\n ]\n }\n }\n ]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "airtableTokenApi"
},
"typeVersion": 4.1
},
{
"id": "4d5e25f4-395f-4c47-8181-7dc7191b3b88",
"name": "Get Task Template",
"type": "n8n-nodes-base.airtable",
"position": [
1240,
660
],
"parameters": {
"id": "={{ $item(\"0\").$node[\"Get Automated Task\"].json[\"Template\"][\"0\"] }}",
"base": {
"__rl": true,
"mode": "id",
"value": "={{ $item(\"0\").$node[\"Airtable Base ID's\"].json[\"base_id\"] }}"
},
"table": {
"__rl": true,
"mode": "id",
"value": "={{ $item(\"0\").$node[\"Airtable Base ID's\"].json[\"table_template_id\"] }}"
},
"options": {},
"operation": "get"
},
"typeVersion": 2
},
{
"id": "fe7a3c49-738b-46d2-9276-2398dff3a449",
"name": "Get Assignee",
"type": "n8n-nodes-base.airtable",
"position": [
1460,
660
],
"parameters": {
"id": "={{ $item(\"0\").$node[\"Get Automated Task\"].json[\"Assigned Team Member\"][\"0\"] }}",
"base": {
"__rl": true,
"mode": "id",
"value": "={{ $item(\"0\").$node[\"Airtable Base ID's\"].json[\"base_id\"] }}"
},
"table": {
"__rl": true,
"mode": "id",
"value": "={{ $item(\"0\").$node[\"Airtable Base ID's\"].json[\"table_team_id\"] }}"
},
"options": {},
"operation": "get"
},
"typeVersion": 2
},
{
"id": "2b2e96a9-dd25-4d5f-a4db-aafd29fff907",
"name": "Get Client",
"type": "n8n-nodes-base.airtable",
"position": [
1660,
660
],
"parameters": {
"id": "={{ $item(\"0\").$node[\"Get Automated Task\"].json[\"Client\"][\"0\"] }}",
"base": {
"__rl": true,
"mode": "id",
"value": "={{ $item(\"0\").$node[\"Airtable Base ID's\"].json[\"base_id\"] }}"
},
"table": {
"__rl": true,
"mode": "id",
"value": "={{ $item(\"0\").$node[\"Airtable Base ID's\"].json[\"table_clients_id\"] }}"
},
"options": {},
"operation": "get"
},
"typeVersion": 2
},
{
"id": "504b3d7a-339c-42b7-b2ef-4a180ccc0f78",
"name": "Calculate Dates",
"type": "n8n-nodes-base.code",
"position": [
1880,
660
],
"parameters": {
"jsCode": "// Retrieve values from the previous node\nconst firstTaskCreated = $item(\"0\").$node[\"Get Automated Task\"].json[\"First Task Created\"];\nconst startDate = $item(\"0\").$node[\"Get Automated Task\"].json[\"Start Date\"];\nconst lastTaskCreated = $item(\"0\").$node[\"Get Automated Task\"].json[\"Last Task Created\"];\nconst timeValue = $item(\"0\").$node[\"Get Automated Task\"].json[\"Time Value\"];\nconst daysForSoftDueDate = $item(\"0\").$node[\"Get Automated Task\"].json[\"Days for Soft Due Date\"];\n\n// Helper function to add days to a date\nfunction addDays(date, days) {\n let result = new Date(date);\n result.setDate(result.getDate() + days);\n return result;\n}\n\n// Helper function to format date in MM/DD/YYYY\nfunction formatDate(date) {\n return (date.getMonth() + 1) + '/' + date.getDate() + '/' + date.getFullYear();\n}\n\n// Calculate Kickoff Date\nlet kickoffDate;\nif (firstTaskCreated === \"false\") {\n kickoffDate = new Date(startDate);\n} else {\n kickoffDate = addDays(new Date(lastTaskCreated), timeValue);\n}\n\n// Calculate Soft Due Date\nconst softDueDate = addDays(kickoffDate, timeValue - daysForSoftDueDate);\n\n// Calculate Hard Due Date\nconst hardDueDate = addDays(kickoffDate, timeValue);\n\n// Get today's date\nconst today = new Date();\n\n// Calculate Next Task Creation Date (Hard Due Date minus 1 day)\nconst nextTaskCreationDate = addDays(hardDueDate, -1);\n\n// Prepare the output\nreturn [{\n json: {\n \"Kickoff Date\": formatDate(kickoffDate),\n \"Soft Due Date\": formatDate(softDueDate),\n \"Hard Due Date\": formatDate(hardDueDate),\n \"Today\": formatDate(today),\n \"Next Task Creation Date\": formatDate(nextTaskCreationDate)\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "ba33b165-57cf-4e9a-8f86-52b248333a04",
"name": "Update Automated Record",
"type": "n8n-nodes-base.httpRequest",
"position": [
2420,
660
],
"parameters": {
"url": "=https://api.airtable.com/v0/{{ $item(\"0\").$node[\"Airtable Base ID's\"].json[\"base_id\"] }}/{{ $item(\"0\").$node[\"Airtable Base ID's\"].json[\"table_automate_id\"] }}",
"method": "PATCH",
"options": {},
"jsonBody": "={\n \"records\": [\n {\n \"id\": \"{{ $item(\"0\").$node[\"Get Automated Task\"].json[\"id\"] }}\",\n \"fields\": {\n \"First Task Created\": \"true\",\n \"Last Task Created\": \"{{ $('Calculate Dates').item.json[\"Today\"] }}\",\n \"Next Task Creation Date\": \"{{ $('Calculate Dates').item.json[\"Next Task Creation Date\"] }}\"\n }\n }\n ]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "airtableTokenApi"
},
"typeVersion": 4.1
},
{
"id": "c2893a32-1b48-4974-8216-7fee6e6dd576",
"name": "Notify Assignee",
"type": "n8n-nodes-base.slack",
"disabled": true,
"position": [
2680,
660
],
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": ""
},
"otherOptions": {}
},
"typeVersion": 2.1
},
{
"id": "734d5319-2f55-4f21-b4d3-dae9e9adbf19",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
380,
240
],
"parameters": {
"width": 577.8258549588782,
"height": 149.31896574204097,
"content": "## Resources\nThe Airtable template can be found here - https://www.airtable.com/universe/expDZ9rbZ9ZwZuTmX/recurring-tasks-automation"
},
"typeVersion": 1
},
{
"id": "fed9b237-3ff4-4c70-8e55-081b02f36d61",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1860,
520
],
"parameters": {
"width": 519.2937872252622,
"height": 478.35585536865557,
"content": "### These nodes should be adapted to your custom Airtable Base. These nodes and the field names correspond to the template fields, but will not work if your tables field names, field type are different"
},
"typeVersion": 1
},
{
"id": "038f9b3d-60ca-4196-8a48-009d8a696a33",
"name": "Entered View \"First Task - Create Task\"",
"type": "n8n-nodes-base.airtableTrigger",
"position": [
500,
660
],
"parameters": {
"baseId": {
"__rl": true,
"mode": "id",
"value": "appPL3AkBc0iw5Z3x"
},
"tableId": {
"__rl": true,
"mode": "id",
"value": "tblp4KpAUGY9RqbMj"
},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"triggerField": "updated_at",
"authentication": "airtableTokenApi",
"additionalFields": {
"viewId": "viwsays8X5yn5Xl7g"
}
},
"typeVersion": 1
},
{
"id": "140e4d5d-7d2a-4e3a-bf3b-de993c8a65a1",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
960,
240
],
"parameters": {
"width": 408.1448240473296,
"height": 146.75862096834132,
"content": "## Walkthrough and Overview\n\n### https://www.youtube.com/watch?v=if3wr0tY-gk"
},
"typeVersion": 1
},
{
"id": "263c7619-763d-476f-8fe4-d79edfa874bc",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
520
],
"parameters": {
"width": 400.220686283071,
"height": 575.7793015940845,
"content": "## Setup Checklist\n\n1. Go to the Airtable Template and copy the latest version of the base\n2. Go to the `Automate` table and open the view `First Task - Create Task`. From here, copy the BaseId, TableId and ViewId into the trigger. Make that the field \"updated_at\" is visible in the \"First Task - Create Task\" View\n3. Input your Airtable Id's in the second node \"Airtable Base ID's\"\n\n### The setup is now complete, now for testing:\n\n1. Go to the Airtable Interface Page called \"Automate a Template ⚙️\" and create an entry utilizing the dummy data.\n2. **Important** If you want to test the automation live, the Start Date should be set to TODAY. Please ensure your n8n automation is live."
},
"typeVersion": 1
},
{
"id": "709f91d9-6028-41c3-91a1-2335b31e94b2",
"name": "Airtable Base ID's",
"type": "n8n-nodes-base.set",
"position": [
720,
660
],
"parameters": {
"fields": {
"values": [
{
"name": "base_id",
"stringValue": "appVtUCDmP7LnG8bV"
},
{
"name": "table_task_id",
"stringValue": "tblbkEKwqEAuY6kBW"
},
{
"name": "table_template_id",
"stringValue": "tbl7f8iV3qLUvirPX"
},
{
"name": "table_clients_id",
"stringValue": "tbljzJBlyrHwzEXXK"
},
{
"name": "table_team_id",
"stringValue": "tblKlBfYzCWVzY0Mh"
},
{
"name": "table_automate_id",
"stringValue": "tblvMBrTFj5CI1kUH"
}
]
},
"include": "none",
"options": {}
},
"typeVersion": 3.2
}
],
"pinData": {},
"connections": {
"Get Client": {
"main": [
[
{
"node": "Calculate Dates",
"type": "main",
"index": 0
}
]
]
},
"Create Task": {
"main": [
[
{
"node": "Update Automated Record",
"type": "main",
"index": 0
}
]
]
},
"Get Assignee": {
"main": [
[
{
"node": "Get Client",
"type": "main",
"index": 0
}
]
]
},
"Calculate Dates": {
"main": [
[
{
"node": "Create Task",
"type": "main",
"index": 0
}
]
]
},
"Get Task Template": {
"main": [
[
{
"node": "Get Assignee",
"type": "main",
"index": 0
}
]
]
},
"Airtable Base ID's": {
"main": [
[
{
"node": "Get Automated Task",
"type": "main",
"index": 0
}
]
]
},
"Get Automated Task": {
"main": [
[
{
"node": "Get Task Template",
"type": "main",
"index": 0
}
]
]
},
"Update Automated Record": {
"main": [
[
{
"node": "Notify Assignee",
"type": "main",
"index": 0
}
]
]
},
"Entered View \"First Task - Create Task\"": {
"main": [
[
{
"node": "Airtable Base ID's",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,644 @@
{
"meta": {
"instanceId": "f0a68da631efd4ed052a324b63ff90f7a844426af0398a68338f44245d1dd9e5"
},
"nodes": [
{
"id": "0d901abb-f11b-4fdc-88d0-1bbd906ff332",
"name": "Split results",
"type": "n8n-nodes-base.itemLists",
"position": [
1040,
460
],
"parameters": {
"options": {},
"fieldToSplitOut": "results"
},
"typeVersion": 1
},
{
"id": "b522f5bc-480c-4a6a-a44b-55ca68c66ad5",
"name": "Piloterr - Get Recent Fundraise - Serie A",
"type": "n8n-nodes-base.httpRequest",
"position": [
740,
460
],
"parameters": {
"url": "https://piloterr.com/api/v2/crunchbase/funding_rounds",
"options": {},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "days_since_announcement",
"value": "1"
},
{
"name": "investment_type",
"value": "series_a"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"id": "123",
"name": "Pilotr"
}
},
"typeVersion": 3
},
{
"id": "5965b7cd-66f4-4c5b-82a2-e9526fb4b366",
"name": "Piloterr - Get Recent Fundraise - Serie B",
"type": "n8n-nodes-base.httpRequest",
"position": [
740,
660
],
"parameters": {
"url": "https://piloterr.com/api/v2/crunchbase/funding_rounds",
"options": {},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "days_since_announcement",
"value": "1"
},
{
"name": "investment_type",
"value": "series_b"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"id": "123",
"name": "Pilotr"
}
},
"typeVersion": 3
},
{
"id": "04ab7fe9-6422-45c3-b165-139577a0e27f",
"name": "Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
2360,
480
],
"parameters": {
"columns": {
"value": {
"link": "={{ $json.link }}",
"type": "={{ $json.type }}",
"country": "={{ $json.country }}",
"event_link": "={{ $json.event_link }}",
"website_url": "={{ $json.website_url }}",
"announced_on": "={{ $json.announced_on }}",
"company_name": "={{ $json.company_name }}",
"founded_date": "={{ $json.founded_date }}",
"linkedin_url": "={{ $json.linkedin_url }}",
"money_raised": "={{ $json.money_raised }}",
"funding_total": "={{ $json.funding_total }}",
"employee_count": "={{ $json.employee_count }}",
"investment_type": "={{ $json.investment_type }}",
"monthly_traffic_semrush": "={{ $json.monthly_traffic_semrush }}"
},
"schema": [
{
"id": "company_name",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "company_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "website_url",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "website_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "type",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "money_raised",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "money_raised",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "linkedin_url",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "linkedin_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "announced_on",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "announced_on",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "funding_total",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "funding_total",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "link",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "link",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "monthly_traffic_semrush",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "monthly_traffic_semrush",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "event_link",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "event_link",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "employee_count",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "employee_count",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "country",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "country",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "founded_date",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "founded_date",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"event_link"
]
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IZ7BJUtBdezesDS5oBDzFeW-btiH7qB4gdIcwcC01xs/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/1IZ7BJUtBdezesDS5oBDzFeW-btiH7qB4gdIcwcC01xs/edit#gid=0",
"__regex": "https:\\/\\/(?:drive|docs)\\.google\\.com\\/\\w+\\/d\\/([0-9a-zA-Z\\-_]+)(?:\\/.*|)"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "2",
"name": "Google Sheets account lucas"
}
},
"typeVersion": 4
},
{
"id": "f88a862c-c413-4248-b061-2a449c6ee0fb",
"name": "Piloterr - Get Recent Fundraise - Seed",
"type": "n8n-nodes-base.httpRequest",
"position": [
740,
860
],
"parameters": {
"url": "https://piloterr.com/api/v2/crunchbase/funding_rounds",
"options": {},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "days_since_announcement",
"value": "1"
},
{
"name": "investment_type",
"value": "seed"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"id": "123",
"name": "Pilotr"
}
},
"typeVersion": 3
},
{
"id": "38521229-d315-4bb3-bece-72ff64f602e8",
"name": "Prepare data",
"type": "n8n-nodes-base.set",
"position": [
1280,
460
],
"parameters": {
"values": {
"string": [
{
"name": "type",
"value": "={{ $json.investment_type }}"
},
{
"name": "money_raised",
"value": "={{ $json.money_raised.value_usd }}"
},
{
"name": "announced_on",
"value": "={{ $json.announced_on }}"
},
{
"name": "company_name",
"value": "={{ $json.funded_organization_identifier.value }}"
},
{
"name": "link",
"value": "={{ $json.funded_organization_identifier.permalink }}"
},
{
"name": "event_link",
"value": "={{ $json.identifier.permalink }}"
}
]
},
"options": {},
"keepOnlySet": true
},
"typeVersion": 2
},
{
"id": "8fad9822-dfe3-4106-981f-f2c8163ce8a0",
"name": "Piloterr - Enrich company",
"type": "n8n-nodes-base.httpRequest",
"position": [
1520,
580
],
"parameters": {
"url": "https://piloterr.com/api/v2/crunchbase/company/info",
"options": {
"batching": {
"batch": {
"batchSize": 3
}
}
},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "query",
"value": "=https://www.crunchbase.com/organization/{{ $json[\"link\"] }}"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"id": "123",
"name": "Pilotr"
}
},
"typeVersion": 3,
"continueOnFail": true
},
{
"id": "78289f0d-5721-4615-a883-38a1e48ebb34",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
2100,
480
],
"parameters": {
"mode": "combine",
"options": {},
"combinationMode": "mergeByPosition"
},
"typeVersion": 2.1
},
{
"id": "d5e659d7-28ba-4cd7-a6bf-ea7b48d5f34c",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
20,
280
],
"parameters": {
"width": 318.8857938718665,
"height": 287.01949860724255,
"content": "## Read me\n\nThis workflow will scrape recent fundraising events from Crunchbase, and add them in Google Sheets.\n\nFull guide here: https://lempire.notion.site/Get-recent-fundraising-in-Google-Sheets-dafbbda2635544b4925c4fb04abac8f5?pvs=74\n"
},
"typeVersion": 1
},
{
"id": "888f5bf2-4a7f-4f84-95c8-4173fa8d8f83",
"name": "Schedule Trigger - Run Workflow Every Day",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
460,
460
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 8
}
]
}
},
"typeVersion": 1
},
{
"id": "84f02477-b19c-405f-abde-3e32280208e9",
"name": "Prepare data before importing to Gsheets",
"type": "n8n-nodes-base.set",
"position": [
1860,
580
],
"parameters": {
"values": {
"string": [
{
"name": "website_url",
"value": "={{ $json.website.match(/https?:\\/\\/(?:www\\.)?([^\\/]+)/)[1] }}"
},
{
"name": "monthly_traffic_semrush",
"value": "={{ $json.semrush_summary.semrush_visits_latest_month }}"
},
{
"name": "funding_total",
"value": "={{ $json.funding_rounds_headline.funding_total.value }}"
},
{
"name": "linkedin_url",
"value": "={{ $json.linkedin_url }}"
},
{
"name": "employee_count",
"value": "={{ $json.employee_count }}"
},
{
"name": "country",
"value": "={{ $json.location[2].name }}"
},
{
"name": "founded_date",
"value": "={{ $json.founded }}"
}
]
},
"options": {},
"keepOnlySet": true
},
"typeVersion": 2
},
{
"id": "b4952b2f-7202-4b6a-81ec-7251b0d6c308",
"name": "Get Linkedin URL from object",
"type": "n8n-nodes-base.code",
"position": [
1680,
580
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Find the LinkedIn object\nlet linkedinObject = $json.social_networks.find(e => e.name === 'linkedin');\n\n// If the LinkedIn object exists, get the URL; otherwise, set to null or handle error\n$input.item.json.linkedin_url = linkedinObject ? linkedinObject.url : null;\n\n// Check if the URL was set\nif (!$input.item.json.linkedin_url) {\n console.error('No LinkedIn URL found!');\n // Handle the error as required for your application\n}\n\nreturn $input.item;"
},
"typeVersion": 1
},
{
"id": "9e98198d-b9f1-42e4-b703-153f98ffce7c",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
680,
254.26329864271463
],
"parameters": {
"height": 818.134682564936,
"content": "Create an account at piloterr.com to get your API key\n\nFeel free to delete the node that are not useful to you. For instance \"Serie B\" and \"Seed\" if you want only to scrape Serie A events"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Merge": {
"main": [
[
{
"node": "Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"Prepare data": {
"main": [
[
{
"node": "Piloterr - Enrich company",
"type": "main",
"index": 0
},
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Split results": {
"main": [
[
{
"node": "Prepare data",
"type": "main",
"index": 0
}
]
]
},
"Piloterr - Enrich company": {
"main": [
[
{
"node": "Get Linkedin URL from object",
"type": "main",
"index": 0
}
]
]
},
"Get Linkedin URL from object": {
"main": [
[
{
"node": "Prepare data before importing to Gsheets",
"type": "main",
"index": 0
}
]
]
},
"Piloterr - Get Recent Fundraise - Seed": {
"main": [
[
{
"node": "Split results",
"type": "main",
"index": 0
}
]
]
},
"Prepare data before importing to Gsheets": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Piloterr - Get Recent Fundraise - Serie A": {
"main": [
[
{
"node": "Split results",
"type": "main",
"index": 0
}
]
]
},
"Piloterr - Get Recent Fundraise - Serie B": {
"main": [
[
{
"node": "Split results",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger - Run Workflow Every Day": {
"main": [
[
{
"node": "Piloterr - Get Recent Fundraise - Serie A",
"type": "main",
"index": 0
},
{
"node": "Piloterr - Get Recent Fundraise - Serie B",
"type": "main",
"index": 0
},
{
"node": "Piloterr - Get Recent Fundraise - Seed",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,725 @@
{
"meta": {
"instanceId": "408f9fb9940c3cb18ffdef0e0150fe342d6e655c3a9fac21f0f644e8bedabcd9",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "f3f7546a-8bb3-484c-b0a1-750a8d7d3a74",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
520
],
"parameters": {
"color": 7,
"width": 1549,
"height": 612,
"content": "### Sub-workflow: Custom tool\nThis can be called by the agent above. It returns three different types of data from the Google Sheet, which can be used together for more complex queries without returning the whole sheet (which might be too big for GPT to handle)"
},
"typeVersion": 1
},
{
"id": "a5afaa40-0b68-4d7c-8f78-5cb176fde81d",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
-40
],
"parameters": {
"color": 7,
"width": 1068,
"height": 547,
"content": "### Main workflow: AI agent using custom tool"
},
"typeVersion": 1
},
{
"id": "2dc0ce2f-a09c-4804-9962-f542ec78eb87",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-160,
40
],
"parameters": {
"width": 185.9375,
"height": 183.85014518022527,
"content": "## Try me out\n\nClick the 'Chat' button at the bottom and enter:\n\n_Which is our biggest customer?_"
},
"typeVersion": 1
},
{
"id": "8fc97d52-1c18-47ed-ba6f-4c7c9ce8b7d4",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
480,
240
],
"parameters": {
"color": 7,
"width": 572,
"height": 219,
"content": "These tools all call the sub-workflow below"
},
"typeVersion": 1
},
{
"id": "60cafb69-818f-49fe-b75e-e770304a6aa9",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
260,
740
],
"parameters": {
"width": 179.99762227826224,
"height": 226.64416053838073,
"content": "Change the URL of the Google Sheet here"
},
"typeVersion": 1
},
{
"id": "cd8d92fa-f7ef-47d8-a1a7-5b5aa6faaa96",
"name": "When chat message received",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"position": [
120,
40
],
"webhookId": "7668b567-b983-479f-a3e0-ad945707ae6b",
"parameters": {
"options": {}
},
"typeVersion": 1.1
},
{
"id": "72ac3bdb-56dc-4634-a0ab-0b1c82e2b23d",
"name": "OpenAI Chat Model1",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
160,
320
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"id": "8gccIjcuf3gvaoEr",
"name": "OpenAi account"
}
},
"typeVersion": 1.2
},
{
"id": "37e5bca6-4274-4714-a9e7-a8e4b7d732e5",
"name": "Simple Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
340,
320
],
"parameters": {},
"typeVersion": 1.3
},
{
"id": "81bf7de8-c4c6-49ce-a2cf-4afca5dce7e2",
"name": "When Executed by Another Workflow",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
80,
800
],
"parameters": {
"workflowInputs": {
"values": [
{
"name": "operation"
},
{
"name": "query"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "24e5a49d-8f1f-4569-8072-c35f059ebd46",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
480,
40
],
"parameters": {
"options": {}
},
"typeVersion": 1.8
},
{
"id": "8cc35cb9-3371-4034-86b6-3d125d70d81b",
"name": "List columns tool",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"position": [
540,
320
],
"parameters": {
"name": "list_columns",
"workflowId": {
"__rl": true,
"mode": "id",
"value": "={{ $workflow.id }}"
},
"description": "List all column names in customer data\n\nCall this tool to find out what data is available for each customer. It should be called first at the beginning to understand which columns are available for querying.",
"workflowInputs": {
"value": {
"query": "none",
"operation": "column_names"
},
"schema": [
{
"id": "query",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "query",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "operation",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "operation",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"typeVersion": 2
},
{
"id": "acf19978-dd84-4cfb-8034-eb9f14dd9c86",
"name": "Get column values tool",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"position": [
720,
320
],
"parameters": {
"name": "column_values",
"workflowId": {
"__rl": true,
"mode": "id",
"value": "={{ $workflow.id }}"
},
"description": "Get the specified column value for all customers\n\nUse this tool to find out which customers have a certain value for a given column. Returns an array of JSON objects, one per customer. Each JSON object includes the column being requested plus the row_number column. Input should be a single string representing the name of the column to fetch.\n",
"workflowInputs": {
"value": {
"query": "none",
"operation": "column_values"
},
"schema": [
{
"id": "operation",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "operation",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "query",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "query",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"operation"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"typeVersion": 2
},
{
"id": "acd8dad6-2ce5-4a53-9909-da7bc03cf0e2",
"name": "Get customer tool",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"position": [
900,
320
],
"parameters": {
"name": "get_customer",
"workflowId": {
"__rl": true,
"mode": "id",
"value": "={{ $workflow.id }}",
"cachedResultName": "={{ $workflow.id }}"
},
"description": "Get all columns for a given customer\n\nThe input should be a stringified row number of the customer to fetch; only single string inputs are allowed. Returns a JSON object with all the column names and their values.",
"workflowInputs": {
"value": {
"query": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('query', ``, 'string') }}",
"operation": "row"
},
"schema": [
{
"id": "operation",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "operation",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "query",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "query",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"operation"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"typeVersion": 2
},
{
"id": "43988dff-2270-4d09-a091-8b719c281faf",
"name": "Set Google Sheet URL",
"type": "n8n-nodes-base.set",
"position": [
300,
800
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "a96650f2-3a0c-45cb-afdd-ef7ca90b21cc",
"name": "sheetUrl",
"type": "string",
"value": "https://docs.google.com/spreadsheets/d/1GjFBV8HpraNWG_JyuaQAgTb3zUGguh0S_25nO0CMd8A/edit#gid=736425281"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "536225e9-e33c-4819-a2dc-994d8f7c3f30",
"name": "Get Google sheet contents",
"type": "n8n-nodes-base.googleSheets",
"position": [
520,
800
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "customer_data"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "={{ $json.sheetUrl }}"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "XHvC7jIRR8A2TlUl",
"name": "Google Sheets account"
}
},
"typeVersion": 4.5
},
{
"id": "a5668364-8824-44cc-8a0f-516906d8f821",
"name": "Check operation",
"type": "n8n-nodes-base.switch",
"position": [
740,
800
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Column Names",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "db07e0a3-0a1d-44bd-84a7-59a9442e63a6",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('When Executed by Another Workflow').item.json.operation }}",
"rightValue": "column_names"
}
]
},
"renameOutput": true
},
{
"outputKey": "Column Values",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "96c82351-de4a-4299-903d-8b9b3a3bb931",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('When Executed by Another Workflow').item.json.operation }}",
"rightValue": "column_values"
}
]
},
"renameOutput": true
},
{
"outputKey": "Rows",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "fbc2afd0-361f-4181-94e3-4addc65a9086",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('When Executed by Another Workflow').item.json.operation }}",
"rightValue": "row"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "46c3a8c4-ffaf-4373-b421-4d7ee65567b2",
"name": "Get column names",
"type": "n8n-nodes-base.set",
"position": [
1040,
620
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "36a7be13-e792-4c0a-9997-f61fe2a7b225",
"name": "response",
"type": "array",
"value": "={{ Object.keys($json) }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "190c48e8-4d4b-4e95-a694-49ab76b730da",
"name": "Prepare column data",
"type": "n8n-nodes-base.set",
"position": [
1040,
800
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "bcb75b32-3253-4a31-9771-4faaf12cc2ed",
"name": "row_number",
"type": "number",
"value": "={{ $json.row_number }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "9c65e5e6-a3ac-4ed7-8546-b7f2b09b4f96",
"name": "Filter",
"type": "n8n-nodes-base.filter",
"position": [
1040,
980
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "dbe89d36-e411-4765-8d4e-91a6425350ac",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.row_number.toString() }}",
"rightValue": "={{ $('When Executed by Another Workflow').item.json.query }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "39265f3e-a2ea-4174-952a-3665747ff856",
"name": "Prepare output",
"type": "n8n-nodes-base.code",
"position": [
1340,
800
],
"parameters": {
"jsCode": "return {\n 'response': JSON.stringify($input.all().map(x => x.json))\n}"
},
"executeOnce": true,
"typeVersion": 2
}
],
"pinData": {},
"connections": {
"Filter": {
"main": [
[
{
"node": "Prepare output",
"type": "main",
"index": 0
}
]
]
},
"Simple Memory": {
"ai_memory": [
[
{
"node": "AI Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Check operation": {
"main": [
[
{
"node": "Get column names",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare column data",
"type": "main",
"index": 0
}
],
[
{
"node": "Filter",
"type": "main",
"index": 0
}
]
]
},
"Get column names": {
"main": [
[
{
"node": "Prepare output",
"type": "main",
"index": 0
}
]
]
},
"Get customer tool": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"List columns tool": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"OpenAI Chat Model1": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Prepare column data": {
"main": [
[
{
"node": "Prepare output",
"type": "main",
"index": 0
}
]
]
},
"Set Google Sheet URL": {
"main": [
[
{
"node": "Get Google sheet contents",
"type": "main",
"index": 0
}
]
]
},
"Get column values tool": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Get Google sheet contents": {
"main": [
[
{
"node": "Check operation",
"type": "main",
"index": 0
}
]
]
},
"When chat message received": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"When Executed by Another Workflow": {
"main": [
[
{
"node": "Set Google Sheet URL",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,344 @@
{
"meta": {
"instanceId": "3c58c896c9089c8fb4d7f2b069bf3119193f239a1f538829758e2f4d6b5f5b24"
},
"nodes": [
{
"id": "9aa9fa6c-5ccb-4f2b-b6a8-2b91f4a58355",
"name": "Setup",
"type": "n8n-nodes-base.set",
"position": [
420,
680
],
"parameters": {
"fields": {
"values": [
{
"name": "apiKey",
"stringValue": "32aa914c947342169c4998b6701a77e0"
},
{
"name": "newsAge",
"type": "numberValue",
"numberValue": "10"
},
{
"name": "maxArticles",
"stringValue": "20"
},
{
"name": "emails"
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "6f471217-b69b-4f67-981d-c7c1e2d710b6",
"name": "Extract company name",
"type": "n8n-nodes-base.set",
"position": [
1100,
480
],
"parameters": {
"fields": {
"values": [
{
"name": "companyName",
"stringValue": "={{ $json.summary.toLowerCase().replace('meeting with', '').replace('call with', '').trim() }}"
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "9bb5adfa-5a36-453e-ad8d-59229ca2f1ab",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
200,
320
],
"parameters": {
"color": 4,
"width": 436,
"height": 192,
"content": "### Latest company news before a call\n\nThis workflow will send you a list of latest news about a company for every meeting in your calendar each day, keeping you up to date with your leads and meeting partners.\n"
},
"typeVersion": 1
},
{
"id": "ddfa92e0-ff37-4733-9002-65fe21989d8a",
"name": "Every morning @ 7",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
200,
680
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 7
}
]
}
},
"typeVersion": 1.1
},
{
"id": "b71c3683-6077-41b4-ab23-66ee22f64532",
"name": "Filter meetings",
"type": "n8n-nodes-base.if",
"position": [
840,
680
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "bcfb06b1-d365-43a8-9802-869529baca98",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.summary.toLowerCase() }}",
"rightValue": "call with"
},
{
"id": "4ea43ccf-d586-4985-87db-fc1e9f734351",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.summary.toLowerCase() }}",
"rightValue": "meeting with"
}
]
}
},
"typeVersion": 2
},
{
"id": "34c4241e-e29a-4d9a-b8a8-130b9f19383f",
"name": "Get latest news",
"type": "n8n-nodes-base.httpRequest",
"position": [
1300,
480
],
"parameters": {
"url": "=https://newsapi.org/v2/everything?apiKey={{ $('Setup').first().json.apiKey }}&q={{ $json.companyName }}&from={{ DateTime.now().minus({ days: $('Setup').first().json.newsAge }).toFormat('yyyy-MM-dd') }}&sortBy=publishedAt&language=en&pageSize={{ $('Setup').first().json.maxArticles }}&searchIn=title",
"options": {}
},
"typeVersion": 4.1
},
{
"id": "51059db7-5fec-4287-bf3f-a6a4e76ac5a4",
"name": "Format for email",
"type": "n8n-nodes-base.code",
"position": [
1500,
480
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "let html = `<table style=\"width: 100%\">`;\nhtml += '</table>';\n\nfor(article of $input.item.json.articles) {\n console.log(article)\n html += `\n <tr>\n <td style=\"display: flex; background-color: #f2f4f8; font-family: sans-serif; padding: 0.3em 0.5em\">\n <div style=\"padding: 1em\">\n <a style=\"display: block; margin-bottom: 10px; font-size: 1.2em\" href=\"${article.url}\">${article.title}</a>\n <i>\n ${article.description ? article.description : article.content}\n </i>\n <div style=\"margin-top: 1em\">\n ${ article.source?.name ? '<b>Source:</b> ' + article.source?.name : '' }\n </div>\n </div>\n </td>\n </tr>\n `\n}\nreturn { \"html\": html };"
},
"typeVersion": 2
},
{
"id": "9b4351a8-edf9-49ef-829e-6998cb1eea2c",
"name": "Send news",
"type": "n8n-nodes-base.gmail",
"position": [
1700,
480
],
"parameters": {
"sendTo": "={{ $('Setup').first().json.emails }}",
"message": "={{ $json.html }}",
"options": {},
"subject": "=Latest news for '{{ $('Extract company name').item.json.summary }}'"
},
"credentials": {
"gmailOAuth2": {
"id": "10",
"name": "mrdosija@gmail.com"
}
},
"typeVersion": 2.1
},
{
"id": "182504b0-3cf6-4afe-ba93-1d2bf7a02fa3",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
360,
640
],
"parameters": {
"height": 511.499984507795,
"content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n### Configure your workflow here\n1. `apiKey` - Your API key for [News API](https://newsapi.org)\n2. `newsAge` - How old should news be, in days\n3. `maxArticles` - Number of articles that will be sent, max 100\n4. `emails`- List of email addresses that should receive the news, separated by commas"
},
"typeVersion": 1
},
{
"id": "604bc73b-f805-40df-baa0-eb3de4c515f3",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
820,
660
],
"parameters": {
"width": 231.52514020446353,
"height": 275.2500697149263,
"content": "\n\n\n\n\n\n\n\n\n\n\n\n\nThis will get all meetings that start with *Meeting with* or *Call with* but feel free to update the filter to suit your needs."
},
"typeVersion": 1
},
{
"id": "318b2bdc-712f-42a8-b224-8f0dc2c9c4e5",
"name": "No meetings today",
"type": "n8n-nodes-base.noOp",
"position": [
1700,
700
],
"parameters": {},
"typeVersion": 1
},
{
"id": "96b075cd-5c16-453e-93a6-348b22b704bb",
"name": "Get meetings for today",
"type": "n8n-nodes-base.googleCalendar",
"position": [
660,
680
],
"parameters": {
"options": {
"timeMax": "={{ $today.plus({ days: 1 }) }}",
"timeMin": "={{ $today }}",
"singleEvents": true
},
"calendar": {
"__rl": true,
"mode": "list",
"value": "milorad.filipovic19@gmail.com",
"cachedResultName": "milorad.filipovic19@gmail.com"
},
"operation": "getAll"
},
"credentials": {
"googleCalendarOAuth2Api": {
"id": "22",
"name": "Google Calendar account"
}
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Setup": {
"main": [
[
{
"node": "Get meetings for today",
"type": "main",
"index": 0
}
]
]
},
"Filter meetings": {
"main": [
[
{
"node": "Extract company name",
"type": "main",
"index": 0
}
],
[
{
"node": "No meetings today",
"type": "main",
"index": 0
}
]
]
},
"Get latest news": {
"main": [
[
{
"node": "Format for email",
"type": "main",
"index": 0
}
]
]
},
"Format for email": {
"main": [
[
{
"node": "Send news",
"type": "main",
"index": 0
}
]
]
},
"Every morning @ 7": {
"main": [
[
{
"node": "Setup",
"type": "main",
"index": 0
}
]
]
},
"Extract company name": {
"main": [
[
{
"node": "Get latest news",
"type": "main",
"index": 0
}
]
]
},
"Get meetings for today": {
"main": [
[
{
"node": "Filter meetings",
"type": "main",
"index": 0
}
]
]
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,796 @@
{
"meta": {
"instanceId": "cb484ba7b742928a2048bf8829668bed5b5ad9787579adea888f05980292a4a7"
},
"nodes": [
{
"id": "7d11aa76-c7bf-4aa3-9f94-fb2231f5055b",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-1460,
2080
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "bea6febe-077f-4b90-887c-9c82954ef5d9",
"name": "Fetch Linear team details",
"type": "n8n-nodes-base.graphql",
"position": [
-1220,
1760
],
"parameters": {
"query": "=query GetTeamsAndProjects {\n teams(filter: {name: {contains: \"{{ $json['Linear team name'] }}\"}}) {\n nodes {\n id\n name\n members {\n nodes {\n id\n name\n email\n }\n }\n projects {\n nodes {\n id\n name\n description\n }\n }\n }\n }\n}\n",
"endpoint": "https://api.linear.app/graphql",
"requestMethod": "GET",
"authentication": "headerAuth"
},
"credentials": {
"httpHeaderAuth": {
"id": "zYILrk4RKFqdP66s",
"name": "[Omar] Notion credentials for GraphQL API"
}
},
"executeOnce": true,
"typeVersion": 1,
"continueOnFail": true
},
{
"id": "27a2111b-716b-4150-af92-ab90d7e83642",
"name": "Get issue contents",
"type": "n8n-nodes-base.notion",
"position": [
-1220,
2340
],
"parameters": {
"blockId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Set assignee and title').item.json.id }}"
},
"resource": "block",
"operation": "getAll",
"returnAll": true,
"simplifyOutput": false,
"fetchNestedBlocks": true
},
"credentials": {
"notionApi": {
"id": "80",
"name": "Notion david-internal"
}
},
"typeVersion": 2.1,
"alwaysOutputData": true
},
{
"id": "96bde9b1-b84e-445a-b90c-6dd4031076aa",
"name": "Aggregate",
"type": "n8n-nodes-base.aggregate",
"position": [
-560,
2340
],
"parameters": {
"options": {},
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "markdown"
}
]
}
},
"typeVersion": 1
},
{
"id": "45c63a2d-d457-4882-ac99-4b8e4a63ae43",
"name": "Prepare issue data",
"type": "n8n-nodes-base.set",
"position": [
-1220,
2620
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "e1b44489-ee32-4da4-816e-f56d640a9731",
"name": "title",
"type": "string",
"value": "={{ $if($('Set assignee and title').item.json.title.length <= 70, $('Set assignee and title').item.json.title, $('Shorten title').item.json.message.content) }}"
},
{
"id": "f3fab4f6-8ea3-4b93-91ea-ec08c2d9eded",
"name": "description",
"type": "string",
"value": "=_Issue created automatically from a [Notion block]({{ $('Set page URL').last().json.page_url + '?pvs=4#' + $('Loop Over Items').item.json.id.replaceAll('-', '') }})_ {{ $if($('Set assignee and title').item.json.assignee_fragment && !$('Set assignee and title').item.json.assignee, \"\\nAssignee '\" + $('Set assignee and title').item.json.assignee_fragment + \"' not found\", '') }}\n\n{{ $json.markdown?.join('\\n') }}"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "ce619c7d-8363-48ec-86c8-a1f16097eb3e",
"name": "Create linear issue",
"type": "n8n-nodes-base.linear",
"position": [
-1000,
2620
],
"parameters": {
"title": "={{ $json.title }}",
"teamId": "={{ $('Set team ID').item.json.team_id }}",
"additionalFields": {
"assigneeId": "={{ $('Set assignee and title').item.json.assignee.id }}",
"description": "={{ $json.description }}"
}
},
"credentials": {
"linearApi": {
"id": "218",
"name": "Linear account (David)"
}
},
"typeVersion": 1
},
{
"id": "cc365fa1-a2cc-430c-8cb4-5d708b7a66b9",
"name": "Set assignee and title",
"type": "n8n-nodes-base.code",
"position": [
-1220,
2080
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Set the title and the assignee based on the first line of the item\n\nlet firstLine = $json[$json.type].text.reduce((s, o) => {\n return s + o.text.content\n}, \"\")\nconsole.log('firstLin', firstLine)\nconst regex = /^(\\[[^\\]]*\\]\\s)?(.+)$/;\nconst match = firstLine.match(regex);\nconsole.log('match', match)\n\nif (match) {\n // If the first part is not present, match[1] will be undefined\n item.json.assignee_fragment = match[1]?.slice(1, -2) || null;\n item.json.title = match[2];\n} else {\n item.json.title = firstLine;\n item.json.assignee_fragment = null;\n}\n\n// Set the new title in Notion format\n// $url will be set later, once we have it\nconst prefix_link = [\n {\"text\":{\"content\":\"[\"}},\n {\"text\":{\"content\":\"In Linear\", \"link\":{\"url\": \"$url\"} }},\n {\"text\":{\"content\":\"] \"}}\n]\nitem.json.new_content = {\n \"rich_text\": [...prefix_link, ...item.json.to_do.text]\n}\n\n// Find a matching assignee\nconst members = $('Fetch Linear team details').item.json.data.teams.nodes[0].members.nodes\nconsole.log('people', members)\nconsole.log('fragment', item.json.assignee_fragment)\nconst matching_people = members.filter(p => \n p.name.toLowerCase().startsWith(item.json.assignee_fragment?.toLowerCase())\n)\nconsole.log('mpeople', matching_people)\nif (matching_people.length > 0) {\n item.json.assignee = matching_people[0]\n}\n\nitem.pairedItem = 0\n\nreturn item"
},
"typeVersion": 2
},
{
"id": "15d9c526-95f3-4209-9a9f-a0ba4c09a67e",
"name": "Team missing?",
"type": "n8n-nodes-base.if",
"position": [
-1000,
1760
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "047fbe62-ebab-44ab-89b1-232f5f15874d",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.data.teams?.nodes?.length < 1 }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2
},
{
"id": "491a1176-18a7-442a-8f64-a8c946ac25dc",
"name": "Set page URL",
"type": "n8n-nodes-base.set",
"position": [
-1000,
2340
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "0b0ced59-14c9-43e9-a5ee-f4b1862fccd6",
"name": "page_url",
"type": "string",
"value": "={{ $('n8n Form Trigger').item.json['Notion block URL'].substr(0, $('n8n Form Trigger').item.json['Notion block URL'].indexOf('?')) || $('n8n Form Trigger').item.json['Notion block URL'] }}"
},
{
"id": "3df2b2e6-38ca-4fb6-b00c-e3a6ceb3f9b3",
"name": "root_content",
"type": "object",
"value": "={{ $('Set assignee and title').item.json[$('Set assignee and title').item.json.type] }}"
},
{
"id": "41a18b43-49fd-45a5-850d-55b9f08f9b93",
"name": "root_id",
"type": "string",
"value": "={{ $('Set assignee and title').item.json.id }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.3
},
{
"id": "bd107990-b009-4b81-8fee-a80c9ab09b4c",
"name": "Set team ID",
"type": "n8n-nodes-base.set",
"position": [
-740,
1760
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "b22a4a67-67b5-415a-ab38-4d7f781e8b7e",
"name": "team_id",
"type": "string",
"value": "={{ $json.data.teams.nodes[0].id }}"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "010105b6-38a2-4859-9e5d-b9624addeacc",
"name": "Add link to Notion block",
"type": "n8n-nodes-base.httpRequest",
"position": [
-560,
2620
],
"parameters": {
"url": "=https://api.notion.com/v1/blocks/{{ $('Loop Over Items').item.json.id }}",
"method": "PATCH",
"options": {},
"jsonBody": "={\n \"to_do\":\n {{ JSON.stringify($('Set assignee and title').item.json.new_content).replace('$url', $json.data.issue.url) }}\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Notion-Version",
"value": "2022-06-28"
}
]
},
"nodeCredentialType": "notionApi"
},
"credentials": {
"notionApi": {
"id": "80",
"name": "Notion david-internal"
}
},
"typeVersion": 4.1
},
{
"id": "bb88f7df-deef-4c9d-b8af-3e59bf0e0b7d",
"name": "Get issue URL",
"type": "n8n-nodes-base.graphql",
"position": [
-780,
2620
],
"parameters": {
"query": "=query IssueDetails {\n issue(id: \"{{ $json.id }}\") {\n url\n }\n}",
"endpoint": "https://api.linear.app/graphql",
"requestMethod": "GET",
"authentication": "headerAuth"
},
"credentials": {
"httpHeaderAuth": {
"id": "zYILrk4RKFqdP66s",
"name": "[Omar] Notion credentials for GraphQL API"
}
},
"executeOnce": true,
"typeVersion": 1,
"continueOnFail": true
},
{
"id": "00736596-08ee-4ff6-b907-bd454dd406d9",
"name": "Shorten title",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
-1000,
2080
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4",
"cachedResultName": "GPT-4"
},
"options": {},
"messages": {
"values": [
{
"content": "=Make the following text more concise, so that it's max 150 chars long. If it's already less than 70 chars long, just return the original text. Do not return anything else other than the text.\n\nTEXT:\n{{ $json.title }}"
}
]
}
},
"credentials": {
"openAiApi": {
"id": "VQtv7frm7eLiEDnd",
"name": "OpenAi account 7"
}
},
"typeVersion": 1
},
{
"id": "729928a2-8a5e-4b25-82be-ba1916b9953f",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1260,
2020
],
"parameters": {
"color": 7,
"width": 877.8549621677266,
"height": 214.7985362687051,
"content": "### Figure out issue assignee and title (shortening if necessary)"
},
"typeVersion": 1
},
{
"id": "86d3b72f-f235-4b37-877f-c8ab1f39ba30",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1260,
2280
],
"parameters": {
"color": 7,
"width": 877.8549621677266,
"height": 216.9904777194533,
"content": "### Compose issue description"
},
"typeVersion": 1
},
{
"id": "01c06352-76fa-4d63-b37b-2a0239d81302",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1260,
2560
],
"parameters": {
"color": 7,
"width": 877.8549621677266,
"height": 216.9904777194533,
"content": "### Create issue and add link to it in Notion"
},
"typeVersion": 1
},
{
"id": "1f152f08-0c5a-4806-bd85-7e9a5e386fe4",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1260,
1500
],
"parameters": {
"color": 7,
"width": 1164.99929221574,
"height": 442.760447146518,
"content": "### Get the issues to create from Notion (and load Linear team details)"
},
"typeVersion": 1
},
{
"id": "839d1815-419d-4acb-8668-94f1cbe45f1f",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1780,
1700
],
"parameters": {
"height": 278.9250421966361,
"content": "# Try me out\n1. In the form trigger node, enter the names of your Linear team(s) to display on the form \n2. Make sure your Notion page is formatted according to the [spec](https://www.notion.so/n8n/Template-for-design-review-automatic-Linear-import-8848dd09892341969faedd1313eea586?pvs=4) and shared with your Notion integration\n2. Click the 'test workflow' button below"
},
"typeVersion": 1
},
{
"id": "f9bc6e67-a9f0-4c9b-a9b3-fdea1fd9de3e",
"name": "Unimported, unchecked to_do blocks only",
"type": "n8n-nodes-base.filter",
"position": [
-220,
1760
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d7e85c09-8548-4fc8-a8a9-636e4529e9d9",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.type }}",
"rightValue": "to_do"
},
{
"id": "13fb565d-8951-4c89-9684-85c357459794",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ !$json.to_do.text.reduce((s, o) => s + o.plain_text, \"\").startsWith('[In Linear]') }}",
"rightValue": ""
},
{
"id": "0a9c8e94-11ec-4317-8de5-f22862555b78",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.to_do.checked }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2
},
{
"id": "186a4272-4550-441e-9ef2-66de2dac5b8a",
"name": "n8n Form Trigger",
"type": "n8n-nodes-base.formTrigger",
"position": [
-1460,
1760
],
"webhookId": "5a631d63-f899-4967-acad-69924674e96a",
"parameters": {
"path": "5a631d63-f899-4967-acad-69924674e96a",
"formTitle": "Import Linear issues from Notion",
"formFields": {
"values": [
{
"fieldLabel": "Notion page URL",
"requiredField": true
},
{
"fieldType": "dropdown",
"fieldLabel": "Linear team name",
"fieldOptions": {
"values": [
{
"option": "AI"
},
{
"option": "Adore"
},
{
"option": "Payday"
},
{
"option": "NODES"
}
]
},
"requiredField": true
}
]
},
"responseMode": "responseNode",
"formDescription": "More information on Notion formatting required here: https://www.notion.so/n8n/8848dd09892341969faedd1313eea586"
},
"typeVersion": 2
},
{
"id": "0fed5dbe-54e9-4bc3-8ab3-6175347ecce7",
"name": "Get issues",
"type": "n8n-nodes-base.notion",
"onError": "continueErrorOutput",
"position": [
-500,
1760
],
"parameters": {
"blockId": {
"__rl": true,
"mode": "url",
"value": "={{ $('n8n Form Trigger').item.json['Notion page URL'] }}"
},
"resource": "block",
"operation": "getAll",
"returnAll": true,
"simplifyOutput": false
},
"credentials": {
"notionApi": {
"id": "80",
"name": "Notion david-internal"
}
},
"typeVersion": 2.1
},
{
"id": "7cc756ad-55f3-4596-989a-df90d4c829c7",
"name": "Convert contents to Markdown",
"type": "n8n-nodes-base.code",
"position": [
-780,
2340
],
"parameters": {
"jsCode": "function extractMarkdown(obj) {\n console.log('obj', obj.text)\n return obj.text.reduce((s, o) => {\n if(o.text?.link) {\n return s + '[' + o.text.content + '](' + o.text.link?.url + ')'\n }\n return s + o.text.content\n }, \"\")\n}\n\n\nconst indent = \" \"; // Four spaces\nlet parent_ids = [$input.all()[0].json.root_id]\n\nfor(item of $input.all()){\n\n // Generate the markdown\n\n if(item.json.type) {\n \n const type = item.json.type\n \n if(type == 'bulleted_list_item' || type == 'toggle') {\n item.json.markdown = '* ' + extractMarkdown(item.json[type])\n } else if(type == 'numbered_list_item') {\n item.json.markdown = '1. ' + extractMarkdown(item.json[type])\n } else if(type == 'to_do') {\n item.json.markdown = '+ [ ] ' + extractMarkdown(item.json[type])\n } else if(type == 'image') {\n item.json.markdown = '![image]('+item.json.image[item.json.image.type].url+')'\n } else if(type == 'video') {\n item.json.markdown = '[🎬 Video]('+$input.all()[0].json.page_url + '?pvs=4#' + item.json.id.replaceAll('-', '') +')'\n } else {\n item.json.markdown = extractMarkdown(item.json[type])\n }\n \n // Figure out how much to indent it\n // If parent ID is in list, remove everything after that ID\n // If parent ID is not in list, add it\n // If parent is the same, do nothing\n const parent_id_index = parent_ids.indexOf(item.json.parent_id);\n \n // Check if the value is found\n if (parent_id_index !== -1) {\n // Remove all elements after the first occurrence\n parent_ids.splice(parent_id_index + 1);\n } else {\n parent_ids.push(item.json.parent_id)\n }\n \n // Indent the markdown\n //if (type != \"image\") {\n item.json.markdown = indent.repeat(parent_ids.length - 1) + item.json.markdown\n //}\n }\n}\n\n// On returning, add in the root block content at the beginning\nreturn [\n ...[\n {\n \"json\": {\n \"markdown\":\nextractMarkdown($input.all()[0].json.root_content)\n },\n \"pairedItem\": 0\n }\n ],\n ...$input.all()\n]"
},
"typeVersion": 2
},
{
"id": "2bbd43a2-aebe-4ee5-8682-d76791214168",
"name": "Respond with error",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
-220,
1580
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "{\n \"formSubmittedText\": \"Couldn't fetch page content from Notion. Is it shared with your Notion integration?\"\n}"
},
"typeVersion": 1
},
{
"id": "c39912c4-d33f-4d79-9d5c-e71bd0f2517d",
"name": "Respond with error1",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
-740,
1580
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={\n \"formSubmittedText\": \"Couldn't find the team called '\" + {{ $('n8n Form Trigger').item.json['Linear team name'] }} + \"'\"\n} "
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Aggregate": {
"main": [
[
{
"node": "Prepare issue data",
"type": "main",
"index": 0
}
]
]
},
"Get issues": {
"main": [
[
{
"node": "Unimported, unchecked to_do blocks only",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond with error",
"type": "main",
"index": 0
}
]
]
},
"Set team ID": {
"main": [
[
{
"node": "Get issues",
"type": "main",
"index": 0
}
]
]
},
"Set page URL": {
"main": [
[
{
"node": "Convert contents to Markdown",
"type": "main",
"index": 0
}
]
]
},
"Get issue URL": {
"main": [
[
{
"node": "Add link to Notion block",
"type": "main",
"index": 0
}
]
]
},
"Shorten title": {
"main": [
[
{
"node": "Get issue contents",
"type": "main",
"index": 0
}
]
]
},
"Team missing?": {
"main": [
[
{
"node": "Respond with error1",
"type": "main",
"index": 0
}
],
[
{
"node": "Set team ID",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
null,
[
{
"node": "Set assignee and title",
"type": "main",
"index": 0
}
]
]
},
"n8n Form Trigger": {
"main": [
[
{
"node": "Fetch Linear team details",
"type": "main",
"index": 0
}
]
]
},
"Get issue contents": {
"main": [
[
{
"node": "Set page URL",
"type": "main",
"index": 0
}
]
]
},
"Prepare issue data": {
"main": [
[
{
"node": "Create linear issue",
"type": "main",
"index": 0
}
]
]
},
"Create linear issue": {
"main": [
[
{
"node": "Get issue URL",
"type": "main",
"index": 0
}
]
]
},
"Set assignee and title": {
"main": [
[
{
"node": "Shorten title",
"type": "main",
"index": 0
}
]
]
},
"Add link to Notion block": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Fetch Linear team details": {
"main": [
[
{
"node": "Team missing?",
"type": "main",
"index": 0
}
]
]
},
"Convert contents to Markdown": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
]
]
},
"Unimported, unchecked to_do blocks only": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,370 @@
{
"nodes": [
{
"id": "d49ee203-5bd1-45c0-859d-f1b248bfdf71",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
280,
40
],
"parameters": {
"color": 5,
"width": 424.4907862645661,
"height": 154.7766688696994,
"content": "### 👨‍🎤 Setup\n1. Add Todoist creds\n2. Create a `template` list to copy from in Todoist. Add days and due times on each task as necessary.\n3. Set the projects to copy from and to write to in each **Todoist** node"
},
"typeVersion": 1
},
{
"id": "e69dd4e2-7ff6-4613-a1c9-ac1f3da37955",
"name": "Get all tasks from template project",
"type": "n8n-nodes-base.todoist",
"position": [
860,
420
],
"parameters": {
"filters": {
"projectId": "2299363018"
},
"operation": "getAll",
"returnAll": true
},
"credentials": {
"todoistApi": {
"id": "1",
"name": "Todoist account"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 1
},
{
"id": "fa907d45-3822-4549-9f84-8385bb4183cc",
"name": "Parse task details",
"type": "n8n-nodes-base.code",
"position": [
1080,
420
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = {};\n\nitem.description = $input.item.json.description;\nitem.content = $input.item.json.content;\n\nconst parts = item.description.split(';').map((v) => v.trim());\nparts.forEach((v) => {\n const tag = v.split(':');\n if (tag && tag.length === 2) {\n item[tag[0]] = tag[1].trim();\n }\n});\n\nif (item.due) {\n item.due = parseTimeString(item.due);\n}\n\nreturn item;\n\nfunction parseTimeString(timeString) {\n const regex = /^(\\d{1,2})(\\.)?(\\d{2})?([ap]m)$/i;\n const match = timeString.match(regex);\n \n if (!match) {\n throw new Error(\"Invalid time format\");\n }\n\n let hours = parseInt(match[1], 10);\n let minutes = match[3] ? parseInt(match[3], 10) : 0;\n const period = match[4].toLowerCase();\n\n if (hours === 12) {\n hours = period === 'am' ? 0 : 12;\n } else {\n hours = period === 'pm' ? hours + 12 : hours;\n }\n\n // Check if minutes are valid\n if (minutes < 0 || minutes >= 60) {\n throw new Error(\"Invalid minutes\");\n }\n\n const now = DateTime.now().set({ hour: hours, minute: minutes, second: 0, millisecond: 0 });\n return now.toUTC();\n}\n"
},
"typeVersion": 1
},
{
"id": "4989bac6-0741-4cdc-bc9c-e7800f9b3019",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1140,
600
],
"parameters": {
"color": 7,
"width": 351.4230769230764,
"height": 222.50000000000006,
"content": "### 👆 This adds due dates to tasks from description.. \n### For example in the description of a task\n`days:mon,tues; due:8am`\n### So that it will create a task every Monday and Tuesday that's due at 8am ⏰"
},
"typeVersion": 1
},
{
"id": "accc330b-1b67-4181-8735-94b0debc8d70",
"name": "Keep tasks that match today",
"type": "n8n-nodes-base.filter",
"position": [
1300,
420
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.days }}",
"value2": "={{ ['sun', 'mon', 'tues', 'wed', 'thurs', 'fri', 'sat', 'sun'][new Date().getDay()] }}",
"operation": "contains"
},
{
"value1": "={{ $json.days }}",
"value2": "={{ ['sun', 'mon', 'tues', 'wed', 'thurs', 'fri', 'sat', 'sun'][new Date().getDay()] }}",
"operation": "contains"
}
]
},
"combineConditions": "OR"
},
"typeVersion": 1
},
{
"id": "dbe1fc24-1833-493b-b444-de21a4b3c3c5",
"name": "Every day at 5:10am",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
620,
420
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 5,
"triggerAtMinute": 10
}
]
}
},
"typeVersion": 1.1
},
{
"id": "b4737822-89aa-4ca0-bd9b-c5f9a16360c0",
"name": "Every day at 5am",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
400,
220
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 5,
"triggerAtMinute": 10
}
]
}
},
"typeVersion": 1.1
},
{
"id": "2a9adc4b-552b-47a9-a32c-54d8d4bfb669",
"name": "Get all tasks from Inbox",
"type": "n8n-nodes-base.todoist",
"position": [
620,
220
],
"parameters": {
"filters": {
"projectId": "938017196"
},
"operation": "getAll",
"returnAll": true
},
"credentials": {
"todoistApi": {
"id": "1",
"name": "Todoist account"
}
},
"executeOnce": false,
"retryOnFail": true,
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "d4794543-3002-4663-8979-360eb437fb4e",
"name": "If list not empty",
"type": "n8n-nodes-base.if",
"position": [
840,
220
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json[\"id\"] }}",
"operation": "isNotEmpty"
}
]
}
},
"typeVersion": 1
},
{
"id": "297fcbcb-efe3-4965-b836-34e78a3b452d",
"name": "if it has daily label",
"type": "n8n-nodes-base.if",
"position": [
1080,
220
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ ($json[\"labels\"] || []).includes('daily') }}",
"value2": true
}
]
}
},
"typeVersion": 1
},
{
"id": "0365a865-f03b-4afc-a535-4e3892fc3add",
"name": "Delete task",
"type": "n8n-nodes-base.todoist",
"position": [
1280,
220
],
"parameters": {
"taskId": "={{ $json[\"id\"] }}",
"operation": "delete"
},
"credentials": {
"todoistApi": {
"id": "1",
"name": "Todoist account"
}
},
"retryOnFail": true,
"typeVersion": 1
},
{
"id": "b14a8ecc-ee07-4a33-ab4b-122c98694c60",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1740,
440
],
"parameters": {
"color": 7,
"width": 256.14371825927645,
"height": 100,
"content": "### 👈🏽 Every new task has `daily` label that gets deleted in the other flow"
},
"typeVersion": 1
},
{
"id": "d951f461-685e-4507-b010-bce2be0e3709",
"name": "Create new task in Inbox",
"type": "n8n-nodes-base.todoist",
"position": [
1520,
420
],
"parameters": {
"labels": [
"daily"
],
"content": "={{ $json.content }}",
"options": {
"description": "={{ $json.description }}",
"dueDateTime": "={{ $json.due }}"
},
"project": {
"__rl": true,
"mode": "list",
"value": "938017196",
"cachedResultName": "Inbox"
}
},
"credentials": {
"todoistApi": {
"id": "1",
"name": "Todoist account"
}
},
"retryOnFail": true,
"typeVersion": 2,
"alwaysOutputData": false
}
],
"pinData": {},
"connections": {
"Every day at 5am": {
"main": [
[
{
"node": "Get all tasks from Inbox",
"type": "main",
"index": 0
}
]
]
},
"If list not empty": {
"main": [
[
{
"node": "if it has daily label",
"type": "main",
"index": 0
}
]
]
},
"Parse task details": {
"main": [
[
{
"node": "Keep tasks that match today",
"type": "main",
"index": 0
}
]
]
},
"Every day at 5:10am": {
"main": [
[
{
"node": "Get all tasks from template project",
"type": "main",
"index": 0
}
]
]
},
"if it has daily label": {
"main": [
[
{
"node": "Delete task",
"type": "main",
"index": 0
}
]
]
},
"Get all tasks from Inbox": {
"main": [
[
{
"node": "If list not empty",
"type": "main",
"index": 0
}
]
]
},
"Keep tasks that match today": {
"main": [
[
{
"node": "Create new task in Inbox",
"type": "main",
"index": 0
}
]
]
},
"Get all tasks from template project": {
"main": [
[
{
"node": "Parse task details",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,903 @@
{
"meta": {},
"name": "[n8n] YouTube Channel Advanced RSS Feeds Generator",
"tags": [
{
"id": "Q29tbWVudHBpY2tlcg",
"name": "Commentpicker",
"createdAt": "2024-04-16T14:29:17.942Z",
"updatedAt": "2024-04-16T14:29:17.942Z"
},
{
"id": "Rm9ybVRyaWdnZXI",
"name": "FormTrigger",
"createdAt": "2024-04-16T14:29:17.942Z",
"updatedAt": "2024-04-16T14:29:17.942Z"
},
{
"id": "SHR0cFJlcXVlc3Q",
"name": "HttpRequest",
"createdAt": "2024-04-16T14:29:17.942Z",
"updatedAt": "2024-04-16T14:29:17.942Z"
},
{
"id": "QWdncmVnYXRl",
"name": "Aggregate",
"createdAt": "2024-04-16T14:29:17.942Z",
"updatedAt": "2024-04-16T14:29:17.942Z"
},
{
"id": "UmVzcG9uZFRvV2ViaG9vaw",
"name": "RespondToWebhook",
"createdAt": "2024-04-16T14:29:17.942Z",
"updatedAt": "2024-04-16T14:29:17.942Z"
},
{
"id": "Q29kZQ",
"name": "Code",
"createdAt": "2024-04-16T14:29:17.942Z",
"updatedAt": "2024-04-16T14:29:17.942Z"
}
],
"nodes": [
{
"name": "n8n Form Trigger",
"type": "n8n-nodes-base.formTrigger",
"position": [
-300,
-260
],
"webhookId": "68a70315-9f74-4cf5-9c68-828396b0f23b",
"parameters": {
"path": "Youtube",
"formTitle": "Youtube RSS Generator",
"formFields": {
"values": [
{
"fieldLabel": "youtube Channel username or ID",
"requiredField": true
}
]
},
"responseMode": "responseNode",
"formDescription": "=Youtube Username Example: @username\n\nYoutube ID Example: UCxxxxxxxxxxxxxxxxxx\n\nYoutube Video URL Example 1: https://www.youtube.com/watch?v=mn-br82ENxc\n\nYoutube Video URL Example 2: https://youtu.be/mn-br82ENxc\n\nYoutube Channel URL Example 1: https://www.youtube.com/@NewMedia_Life\n\nYoutube Channel URL Example 2: https://www.youtube.com/channel/UC_UDAiqQj-QfgTixKkW51qA"
},
"typeVersion": 2
},
{
"name": "Get Channel ID",
"type": "n8n-nodes-base.httpRequest",
"notes": "3rd party API request",
"position": [
700,
-440
],
"parameters": {
"url": "https://commentpicker.com/actions/youtube-channel-id.php",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
},
"sendQuery": true,
"sendHeaders": true,
"queryParameters": {
"parameters": [
{
"name": "url",
"value": "=https://www.googleapis.com/youtube/v3/channels?part=id,snippet,statistics,contentDetails,status&forHandle={{ $item(\"0\").$node[\"Set Channel Username\"].json[\"channel name\"] }}"
},
{
"name": "token",
"value": "={{ $item(\"0\").$node[\"Get Temporary Token\"].json[\"data\"] }}"
},
{
"name": "isPremium",
"value": "false"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "authority",
"value": "commentpicker.com"
},
{
"name": "cookie",
"value": "ezosuibasgeneris-1=690da322-c7c8-44e2-6154-8591a44d12aa; ezoab_186623=mod99-c; active_template::186623=pub_site.1711138973; lp_186623=https://commentpicker.com/youtube-channel-id.php; fontsLoaded=true; PHPSESSID=12ltjv3rr293h943c8h35nh3cg"
},
{
"name": "referer",
"value": "https://commentpicker.com/youtube-channel-id.php"
},
{
"name": "user-agent",
"value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
}
]
}
},
"notesInFlow": true,
"typeVersion": 4.1
},
{
"name": "Set XML URL",
"type": "n8n-nodes-base.set",
"notes": "🤖Generate XML Feed URL",
"position": [
900,
-440
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "bf0ea151-e325-4860-af02-76e51f692f2c",
"name": "rss",
"type": "string",
"value": "=https://www.youtube.com/feeds/videos.xml?channel_id={{ $json.items[0].id }}"
}
]
}
},
"notesInFlow": true,
"typeVersion": 3.3
},
{
"name": "Set Channel Username",
"type": "n8n-nodes-base.set",
"position": [
520,
-440
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "0837a847-4c6b-4b39-bb90-f200233bf7e1",
"name": "channel name",
"type": "string",
"value": "={{ $item(\"0\").$node[\"Switch\"].json[\"value\"] }}"
}
]
}
},
"typeVersion": 3.3
},
{
"name": "Set XML Feed URL",
"type": "n8n-nodes-base.set",
"notes": "🤖Generate XML Feed URL",
"position": [
900,
-260
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "bf0ea151-e325-4860-af02-76e51f692f2c",
"name": "rss",
"type": "string",
"value": "=https://www.youtube.com/feeds/videos.xml?channel_id={{ $item(\"0\").$node[\"Switch\"].json[\"value\"] }}"
}
]
}
},
"notesInFlow": true,
"typeVersion": 3.3
},
{
"name": "Set Video ID",
"type": "n8n-nodes-base.set",
"position": [
520,
-80
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "0837a847-4c6b-4b39-bb90-f200233bf7e1",
"name": "Video ID",
"type": "string",
"value": "={{ $item(\"0\").$node[\"Switch\"].json[\"value\"] }}"
}
]
}
},
"typeVersion": 3.3
},
{
"name": "Get Video ID Channel ID",
"type": "n8n-nodes-base.httpRequest",
"notes": "3rd party API request",
"position": [
700,
-80
],
"parameters": {
"url": "https://commentpicker.com/actions/youtube-channel-id.php",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
},
"sendQuery": true,
"sendHeaders": true,
"queryParameters": {
"parameters": [
{
"name": "url",
"value": "=https://www.googleapis.com/youtube/v3/videos?part=snippet&id={{ $json[\"Video ID\"] }}"
},
{
"name": "token",
"value": "={{ $item(\"0\").$node[\"GTT\"].json[\"data\"] }}"
},
{
"name": "isPremium",
"value": "true"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "authority",
"value": "commentpicker.com"
},
{
"name": "cookie",
"value": "ezosuibasgeneris-1=690da322-c7c8-44e2-6154-8591a44d12aa; ezoab_186623=mod99-c; active_template::186623=pub_site.1711138973; lp_186623=https://commentpicker.com/youtube-channel-id.php; fontsLoaded=true; PHPSESSID=12ltjv3rr293h943c8h35nh3cg"
},
{
"name": "referer",
"value": "https://commentpicker.com/youtube-channel-id.php"
},
{
"name": "user-agent",
"value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
}
]
}
},
"notesInFlow": true,
"typeVersion": 4.1
},
{
"name": "Set XML Feed",
"type": "n8n-nodes-base.set",
"notes": "🤖Generate XML Feed URL",
"position": [
900,
-80
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "bf0ea151-e325-4860-af02-76e51f692f2c",
"name": "rss",
"type": "string",
"value": "=https://www.youtube.com/feeds/videos.xml?channel_id={{ $item(\"0\").$node[\"Get Video ID Channel ID\"].json[\"items\"][\"0\"][\"snippet\"][\"channelId\"] }}"
}
]
}
},
"notesInFlow": true,
"typeVersion": 3.3
},
{
"name": "Get Temporary Token",
"type": "n8n-nodes-base.httpRequest",
"notes": "3rd party API request",
"position": [
320,
-440
],
"parameters": {
"url": "https://commentpicker.com/actions/token.php",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "authority",
"value": "commentpicker.com"
},
{
"name": "cookie",
"value": "ezosuibasgeneris-1=690da322-c7c8-44e2-6154-8591a44d12aa; fontsLoaded=true; PHPSESSID=12ltjv3rr293h943c8h35nh3cg; ezoab_186623=mod54-c; active_template::186623=pub_site.1711191989"
},
{
"name": "referer",
"value": "https://commentpicker.com/youtube-channel-id.php"
},
{
"name": "user-agent",
"value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
}
]
}
},
"notesInFlow": true,
"typeVersion": 4.1
},
{
"name": "GTT",
"type": "n8n-nodes-base.httpRequest",
"notes": "3rd party API request",
"position": [
320,
-80
],
"parameters": {
"url": "https://commentpicker.com/actions/token.php",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "authority",
"value": "commentpicker.com"
},
{
"name": "cookie",
"value": "ezosuibasgeneris-1=690da322-c7c8-44e2-6154-8591a44d12aa; fontsLoaded=true; PHPSESSID=12ltjv3rr293h943c8h35nh3cg; ezoab_186623=mod54-c; active_template::186623=pub_site.1711191989"
},
{
"name": "referer",
"value": "https://commentpicker.com/youtube-channel-id.php"
},
{
"name": "user-agent",
"value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
}
]
}
},
"notesInFlow": true,
"typeVersion": 4.1
},
{
"name": "Aggregate",
"type": "n8n-nodes-base.aggregate",
"notes": "🤖Combine results in one",
"position": [
1080,
-260
],
"parameters": {
"options": {},
"fieldsToAggregate": {
"fieldToAggregate": [
{
"renameField": true,
"outputFieldName": "rss url",
"fieldToAggregate": "rss"
}
]
}
},
"notesInFlow": true,
"typeVersion": 1
},
{
"name": "Youtube Channel Videos RSS Formats",
"type": "n8n-nodes-base.set",
"notes": "RSS Feed for channel Posts",
"position": [
1260,
-180
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "6af1de72-9940-4843-9a98-94e36b2878a3",
"name": "=Videos - HTML format response",
"type": "string",
"value": "=https://rss-bridge.org/bridge01/?action=display&bridge=YoutubeBridge&context=By+channel+id&c={{ $item(\"0\").$node[\"Aggregate\"].json[\"rss url\"][\"0\"].match(/channel_id=([^&?/]+)/)[1] }}&duration_min=&duration_max=&format=Html"
},
{
"id": "2b486723-1dff-4525-8169-6d977dee6862",
"name": "Videos - ATOM format response",
"type": "string",
"value": "=https://rss-bridge.org/bridge01/?action=display&bridge=YoutubeBridge&context=By+channel+id&c={{ $item(\"0\").$node[\"Aggregate\"].json[\"rss url\"][\"0\"].match(/channel_id=([^&?/]+)/)[1] }}&duration_min=&duration_max=&format=Atom"
},
{
"id": "10b3c04a-2c8c-4533-944b-13123bd22743",
"name": "Videos - JSON format response",
"type": "string",
"value": "=https://rss-bridge.org/bridge01/?action=display&bridge=YoutubeBridge&context=By+channel+id&c={{ $item(\"0\").$node[\"Aggregate\"].json[\"rss url\"][\"0\"].match(/channel_id=([^&?/]+)/)[1] }}&duration_min=&duration_max=&format=Json"
},
{
"id": "ee8910de-76ab-47a3-b23f-1a1e837fb885",
"name": "Videos - MRSS format response",
"type": "string",
"value": "=https://rss-bridge.org/bridge01/?action=display&bridge=YoutubeBridge&context=By+channel+id&c={{ $item(\"0\").$node[\"Aggregate\"].json[\"rss url\"][\"0\"].match(/channel_id=([^&?/]+)/)[1] }}&duration_min=&duration_max=&format=Mrss"
},
{
"id": "8684437c-11f0-4cc2-b9b2-00ecb6768175",
"name": "Videos - TEXT format response",
"type": "string",
"value": "=https://rss-bridge.org/bridge01/?action=display&bridge=YoutubeBridge&context=By+channel+id&c={{ $item(\"0\").$node[\"Aggregate\"].json[\"rss url\"][\"0\"].match(/channel_id=([^&?/]+)/)[1] }}&duration_min=&duration_max=&format=Plaintext"
},
{
"id": "a53d1d0a-bfd1-41f4-9ab3-edd1e20adaa2",
"name": "Videos - SFEED format response",
"type": "string",
"value": "=https://rss-bridge.org/bridge01/?action=display&bridge=YoutubeBridge&context=By+channel+id&c={{ $item(\"0\").$node[\"Aggregate\"].json[\"rss url\"][\"0\"].match(/channel_id=([^&?/]+)/)[1] }}&duration_min=&duration_max=&format=Sfeed"
},
{
"id": "d17fd2e0-0e4a-45c9-bc60-86ca6a7940d4",
"name": "Videos - XML format response",
"type": "string",
"value": "={{ $json[\"rss url\"][\"0\"] }}"
}
]
}
},
"notesInFlow": true,
"typeVersion": 3.3
},
{
"name": "Youtube Channel Community RSS Formats",
"type": "n8n-nodes-base.set",
"notes": "RSS Feed for channel Posts",
"position": [
1260,
-400
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "6af1de72-9940-4843-9a98-94e36b2878a3",
"name": "=Community - HTML format response",
"type": "string",
"value": "=https://rss-bridge.org/bridge01/?action=display&bridge=YouTubeCommunityTabBridge&context=By+channel+ID&channel={{ $item(\"0\").$node[\"Aggregate\"].json[\"rss url\"][\"0\"].match(/channel_id=([^&?/]+)/)[1] }}&format=HTML"
},
{
"id": "2b486723-1dff-4525-8169-6d977dee6862",
"name": "Community - ATOM format response",
"type": "string",
"value": "=https://rss-bridge.org/bridge01/?action=display&bridge=YouTubeCommunityTabBridge&context=By+channel+ID&channel={{ $item(\"0\").$node[\"Aggregate\"].json[\"rss url\"][\"0\"].match(/channel_id=([^&?/]+)/)[1] }}&format=Atom"
},
{
"id": "10b3c04a-2c8c-4533-944b-13123bd22743",
"name": "Community - JSON format response",
"type": "string",
"value": "=https://rss-bridge.org/bridge01/?action=display&bridge=YouTubeCommunityTabBridge&context=By+channel+ID&channel={{ $item(\"0\").$node[\"Aggregate\"].json[\"rss url\"][\"0\"].match(/channel_id=([^&?/]+)/)[1] }}&format=Json"
},
{
"id": "ee8910de-76ab-47a3-b23f-1a1e837fb885",
"name": "Community - MRSS format response",
"type": "string",
"value": "=https://rss-bridge.org/bridge01/?action=display&bridge=YouTubeCommunityTabBridge&context=By+channel+ID&channel={{ $item(\"0\").$node[\"Aggregate\"].json[\"rss url\"][\"0\"].match(/channel_id=([^&?/]+)/)[1] }}&format=Mrss"
},
{
"id": "8684437c-11f0-4cc2-b9b2-00ecb6768175",
"name": "Community - TEXT format response",
"type": "string",
"value": "=https://rss-bridge.org/bridge01/?action=display&bridge=YouTubeCommunityTabBridge&context=By+channel+ID&channel={{ $item(\"0\").$node[\"Aggregate\"].json[\"rss url\"][\"0\"].match(/channel_id=([^&?/]+)/)[1] }}&format=Plaintext"
},
{
"id": "a53d1d0a-bfd1-41f4-9ab3-edd1e20adaa2",
"name": "Community - SFEED format response",
"type": "string",
"value": "=https://rss-bridge.org/bridge01/?action=display&bridge=YouTubeCommunityTabBridge&context=By+channel+ID&channel={{ $item(\"0\").$node[\"Aggregate\"].json[\"rss url\"][\"0\"].match(/channel_id=([^&?/]+)/)[1] }}&format=Sfeed"
}
]
}
},
"notesInFlow": true,
"typeVersion": 3.3
},
{
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"notes": "Reply to the webhook request with table",
"position": [
1900,
-280
],
"parameters": {
"options": {},
"respondWith": "text",
"responseBody": "={{ $json[\"html\"] }}"
},
"notesInFlow": true,
"typeVersion": 1
},
{
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-360,
140
],
"parameters": {
"color": 7,
"width": 2425.409405354546,
"height": 200.24482670360715,
"content": "## **Workarounds And Information**\n\n### - **No need to acquire Google Cloud API** to retrieve channel data. I have implemented a free workaround method.\n### - The workflow code has been **tested and proven to work** with all YouTube methods, whether for videos or channels. Regardless of whether you input URLs or usernames, the result will always be the channel ID.\n### - Please be aware that the provided workarounds may become **obsolete or non-functional** in the future. I will ensure to stay updated; however, if this workflow does not work for you, please reach out to me on the n8n community.\n### - We have utilized a 3rd party method to generate **multiple syntaxes of RSS feeds** as outlined below. (*The mentioned source is also capable of constructing multi-channel YouTube RSS feeds*, which I will create later for BULK channel RSS.)"
},
"typeVersion": 1
},
{
"name": "Switch",
"type": "n8n-nodes-base.switch",
"position": [
60,
-260
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Username",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.type }}",
"rightValue": "channel username"
}
]
},
"renameOutput": true
},
{
"outputKey": "Direct",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "5af4921b-6266-436a-901c-ab52de68aaf4",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.type }}",
"rightValue": "=channel ID"
}
]
},
"renameOutput": true
},
{
"outputKey": "Video-ID",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a5baa5e6-879f-484a-b521-af802b6d79a9",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.type }}",
"rightValue": "=video ID"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3
},
{
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-360,
-520
],
"parameters": {
"color": 3,
"width": 2429.6732915601406,
"height": 644.5128280596109,
"content": "## 🌐 **Generate RSS Feeds for Public Youtube Channel (No API Or Administrator permissions Required 😉)**\n**``Yes, As you heard``** This Workflow using `3rd party` APIs & Solutions to get the job done. **``no need to setup anything``.**\n\n## Workflow Steps:\n- Run **`Test Workflow`**.\n- Enter Channel or Video URL or ID or Username.\n- Finally, the result will provide **``13 URLs (6x Community + 6x Videos + 1 XML)``**:\n - 6 Formats Types is: `ATOM`, `JSON`, `MRSS`, `PLAINTEXT`, `SFEED`\n - The **``13th URL``** is from YouTube Directly that contain XML file data.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n[![N8N Creator Profile](https://cdn.statically.io/gh/Automations-Project/n8n-templates/main/stats.min.svg)](https://n8n.io/creators/nskha)"
},
"typeVersion": 1
},
{
"name": "Validation Code",
"type": "n8n-nodes-base.code",
"notes": "🤓Validate the YouTube input",
"position": [
-120,
-260
],
"parameters": {
"jsCode": "// JavaScript code to extract YouTube channel ID, username, or video ID from a given input and return in n8n compatible format\n\n// Initialize an array to hold the output items\nconst items = [];\n\n// Extract the input value from the previous node's output using the $input API\nconst inputData = $input.all(); // Get all input data\n// Assuming 'youtube Channel username or ID' is the correct key, and it's in the first input item\nconst input = inputData.length > 0 ? inputData[0].json[\"youtube Channel username or ID\"] : null;\n\n// Check if input exists\nif (!input) {\n throw new Error('Input is undefined or not provided');\n}\n\n// Regular expressions for different YouTube URL and input formats\nconst usernamePattern = /^@?([a-zA-Z0-9_-]+)$/;\nconst channelIdPattern = /^(UC[a-zA-Z0-9_-]{22})$/; // Ensure channel ID starts with \"UC\"\nconst videoUrlPattern1 = /(?:https?:\\/\\/)?www\\.youtube\\.com\\/watch\\?v=([a-zA-Z0-9_-]+)/;\nconst videoUrlPattern2 = /(?:https?:\\/\\/)?youtu\\.be\\/([a-zA-Z0-9_-]+)/;\nconst channelUrlPattern1 = /(?:https?:\\/\\/)?www\\.youtube\\.com\\/@([a-zA-Z0-9_-]+)/;\nconst channelUrlPattern2 = /(?:https?:\\/\\/)?www\\.youtube\\.com\\/channel\\/(UC[a-zA-Z0-9_-]{22})/;\nconst customChannelUrlPattern = /(?:https?:\\/\\/)?www\\.youtube\\.com\\/c\\/([a-zA-Z0-9_-]+)/; // Pattern for custom channel URLs\n\n// Function to determine the type and value of the input\nfunction determineTypeAndValue(input) {\n if (channelIdPattern.test(input)) {\n return { type: 'channel ID', value: input };\n } else if (usernamePattern.test(input)) {\n return { type: 'channel username', value: input };\n } else if (videoUrlPattern1.test(input) || videoUrlPattern2.test(input)) {\n const videoId = videoUrlPattern1.test(input) ? input.match(videoUrlPattern1)[1] : input.match(videoUrlPattern2)[1];\n return { type: 'video ID', value: videoId };\n } else if (channelUrlPattern1.test(input) || customChannelUrlPattern.test(input)) {\n const username = channelUrlPattern1.test(input) ? input.match(channelUrlPattern1)[1] : input.match(customChannelUrlPattern)[1];\n return { type: 'channel username', value: username };\n } else if (channelUrlPattern2.test(input)) {\n return { type: 'channel ID', value: input.match(channelUrlPattern2)[1] };\n } else {\n return { error: 'Invalid input or unsupported format.' };\n }\n}\n\n// Process the input and add the result to the items array\nconst result = determineTypeAndValue(input);\nitems.push({ json: result });\n\nreturn items; // Return the array of items\n"
},
"notesInFlow": true,
"typeVersion": 2
},
{
"name": "Format response as HTML Table",
"type": "n8n-nodes-base.code",
"position": [
1680,
-280
],
"parameters": {
"jsCode": "// Assuming inputData is dynamically retrieved as follows\nconst inputData = $item(\"0\").$node[\"Merga Data of Youtube & Community RSS\"].json;\n\n// Initialize HTML with a modern styled table\nlet html = `\n<style>\n table {\n width: 100%;\n border-collapse: collapse;\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n box-shadow: 0 2px 15px rgba(0,0,0,0.1);\n border-radius: 8px;\n overflow: hidden;\n margin-top: 20px;\n }\n th, td {\n border: 1px solid #ddd;\n padding: 8px 16px;\n text-align: left;\n color: #333;\n }\n th {\n background-color: #f0f0f0;\n color: #000;\n font-weight: 600;\n }\n tr:nth-child(even) {background-color: #f9f9f9;}\n tr:hover {background-color: #f1f1f1;}\n a {\n text-decoration: none;\n color: #0645AD;\n }\n a:hover {\n text-decoration: underline;\n }\n</style>\n<table>\n<tr>\n <th>Type</th>\n <th>Format</th>\n <th>URL</th>\n</tr>`;\n\n// Function to process each item and add it to the HTML table\nObject.entries(inputData).forEach(([key, value]) => {\n // Extract type and format from the key, assuming key format 'Category - Format'\n const [type, format] = key.split(' - ');\n html += `<tr>\n <td>${type} RSS</td>\n <td>${format}</td>\n <td><a href=\"${value}\" target=\"_blank\">${value}</a></td>\n </tr>`;\n});\n\n// Close the HTML table tag\nhtml += `</table>`;\n\n// Return the HTML string as output\nreturn [{json: {html: html}}];\n"
},
"typeVersion": 2
},
{
"name": "Merga Data of Youtube & Community RSS",
"type": "n8n-nodes-base.merge",
"position": [
1480,
-280
],
"parameters": {
"mode": "combine",
"options": {},
"combinationMode": "multiplex"
},
"typeVersion": 2.1
}
],
"active": "false",
"pinData": {},
"settings": {
"timezone": "Asia/Baghdad",
"callerPolicy": "workflowsFromSameOwner",
"errorWorkflow": "",
"executionOrder": "v1",
"executionTimeout": 600,
"saveManualExecutions": true,
"saveExecutionProgress": true
},
"staticData": "",
"connections": {
"GTT": {
"main": [
[
{
"node": "Set Video ID",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "Get Temporary Token",
"type": "main",
"index": 0
}
],
[
{
"node": "Set XML Feed URL",
"type": "main",
"index": 0
}
],
[
{
"node": "GTT",
"type": "main",
"index": 0
}
]
]
},
"Aggregate": {
"main": [
[
{
"node": "Youtube Channel Community RSS Formats",
"type": "main",
"index": 0
},
{
"node": "Youtube Channel Videos RSS Formats",
"type": "main",
"index": 0
}
]
]
},
"Set XML URL": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
]
]
},
"Set Video ID": {
"main": [
[
{
"node": "Get Video ID Channel ID",
"type": "main",
"index": 0
}
]
]
},
"Set XML Feed": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
]
]
},
"Get Channel ID": {
"main": [
[
{
"node": "Set XML URL",
"type": "main",
"index": 0
}
]
]
},
"Validation Code": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Set XML Feed URL": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
]
]
},
"n8n Form Trigger": {
"main": [
[
{
"node": "Validation Code",
"type": "main",
"index": 0
}
]
]
},
"Get Temporary Token": {
"main": [
[
{
"node": "Set Channel Username",
"type": "main",
"index": 0
}
]
]
},
"Set Channel Username": {
"main": [
[
{
"node": "Get Channel ID",
"type": "main",
"index": 0
}
]
]
},
"Get Video ID Channel ID": {
"main": [
[
{
"node": "Set XML Feed",
"type": "main",
"index": 0
}
]
]
},
"Format response as HTML Table": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Youtube Channel Videos RSS Formats": {
"main": [
[
{
"node": "Merga Data of Youtube & Community RSS",
"type": "main",
"index": 1
}
]
]
},
"Merga Data of Youtube & Community RSS": {
"main": [
[
{
"node": "Format response as HTML Table",
"type": "main",
"index": 0
}
]
]
},
"Youtube Channel Community RSS Formats": {
"main": [
[
{
"node": "Merga Data of Youtube & Community RSS",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,247 @@
{
"nodes": [
{
"id": "aea55995-2c2c-4f59-8b68-43fa1871bb4c",
"name": "Replace Images",
"type": "n8n-nodes-base.httpRequest",
"position": [
860,
140
],
"parameters": {
"url": "=https://slides.googleapis.com/v1/presentations/{{ $('Webhook').item.json[\"body\"][\"presentation_id\"] }}:batchUpdate ",
"method": "POST",
"options": {},
"jsonBody": "={\n \"requests\": [\n {\n \"replaceImage\": {\n \"imageObjectId\": \"{{ $json.objectId }}\",\n \"url\": \"{{ $('Webhook').item.json[\"body\"][\"image_url\"] }}\",\n \"imageReplaceMethod\": \"CENTER_CROP\"\n }\n },\n {\n \"updatePageElementAltText\": {\n \"objectId\": \"{{ $json.objectId }}\",\n \"description\": \"{{ $('Webhook').item.json[\"body\"][\"image_key\"] }}\"\n }\n }\n ]\n} \n ",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleSlidesOAuth2Api"
},
"credentials": {
"googleSlidesOAuth2Api": {
"id": "XnM5YeAtI5QnYrMh",
"name": "Google Slides account"
}
},
"typeVersion": 4.2
},
{
"id": "92eeca3a-47b2-4daa-ac51-5b957c8d7d56",
"name": "Error Missing Fields",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
500,
340
],
"parameters": {
"options": {
"responseCode": 500
},
"respondWith": "json",
"responseBody": "{\n \"error\": \"Missing fields.\"\n}"
},
"typeVersion": 1.1
},
{
"id": "14878542-6a42-4fe4-8dd6-328450a883eb",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1040,
140
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "{\n \"message\": \"Image replaced.\"\n}"
},
"typeVersion": 1.1
},
{
"id": "ac42249b-3c7d-4ba1-be7d-ba6e1ae652cd",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
60,
-540
],
"parameters": {
"width": 596.8395976509729,
"height": 654.4370838798395,
"content": "## Dynamically Replace Images in Google Slides\nThis workflow exposes an API endpoint that lets you dynamically replace an image in Google Slides, perfect for automating deck presentations like updating backgrounds or client logos.\n\n### Step 1: Set Up a Key Identifier in Google Slides\nAdd a unique key identifier to the images you want to replace.\n1. Click on the image.\n2. Go to **Format Options** and then **Alt Text**.\n3. Enter your unique identifier, like `client_logo` or `background`.\n\n### Step 2: Use a POST Request to Update the Image\nSend a POST request to the workflow endpoint with the following parameters in the body:\n- `presentation_id`: The ID of your Google Slides presentation.\nYou can find it in the URL of your Google presentation : `https://docs.google.com/presentation/d/{this-part}/edit#slide=id.p`)\n- `image_key`: The unique identifier you created.\n- `image_url`: The URL of the new image.\n\nThat's it! The specified image in your Google Slides presentation will be replaced with the new one from the provided URL.\n\nThis workflow is designed to be flexible, allowing you to use the same identifier across multiple slides and presentations. I hope it streamlines your slide automation process!\n\nHappy automating!\nThe n8Ninja"
},
"typeVersion": 1
},
{
"id": "735c5c4e-df8f-47ad-b0d7-ed57453a84d0",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
60,
160
],
"webhookId": "df3b8b83-fd6d-40f8-be13-42bae85dcf63",
"parameters": {
"path": "replace-image-in-slide",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "22d1dd70-0716-4407-8e25-703355969e95",
"name": "Retrieve matching Images ObjectIds",
"type": "n8n-nodes-base.code",
"position": [
680,
140
],
"parameters": {
"jsCode": "const key = $('Webhook').item.json.body.image_key;\n\nconst pageElements = $input\n .all()\n .flatMap(item => item.json.slides)\n .flatMap(slide => slide.pageElements.filter(el => el.image && el.description === key));\n\nconst objectIds = pageElements.map(el => ({ objectId: el.objectId }));\n\nreturn objectIds"
},
"typeVersion": 2
},
{
"id": "f942a8de-9fa8-4855-9be1-4247bae887e5",
"name": "Retrieve All Slide Elements",
"type": "n8n-nodes-base.httpRequest",
"position": [
500,
140
],
"parameters": {
"url": "=https://slides.googleapis.com/v1/presentations/{{ $('Webhook').item.json.body.presentation_id }}",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleSlidesOAuth2Api"
},
"credentials": {
"googleSlidesOAuth2Api": {
"id": "XnM5YeAtI5QnYrMh",
"name": "Google Slides account"
}
},
"typeVersion": 4.2
},
{
"id": "ddcbe7ed-9abc-49ac-98e5-4d5222a641d4",
"name": "Check if all params are provided",
"type": "n8n-nodes-base.if",
"position": [
260,
160
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "3272f7e8-4bc2-44bd-9760-437b2992e6e7",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $json.body.presentation_id }}",
"rightValue": ""
},
{
"id": "9e8abf56-622d-4704-95ea-c0f5f31683dd",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $json.body.image_key }}",
"rightValue": ""
},
{
"id": "d2cec4c9-2a90-4a24-ab6c-628689419698",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $json.body.image_url }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2
}
],
"pinData": {},
"connections": {
"Webhook": {
"main": [
[
{
"node": "Check if all params are provided",
"type": "main",
"index": 0
}
]
]
},
"Replace Images": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Retrieve All Slide Elements": {
"main": [
[
{
"node": "Retrieve matching Images ObjectIds",
"type": "main",
"index": 0
}
]
]
},
"Check if all params are provided": {
"main": [
[
{
"node": "Retrieve All Slide Elements",
"type": "main",
"index": 0
}
],
[
{
"node": "Error Missing Fields",
"type": "main",
"index": 0
}
]
]
},
"Retrieve matching Images ObjectIds": {
"main": [
[
{
"node": "Replace Images",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,941 @@
{
"nodes": [
{
"id": "adb2d3bc-c6ab-4bb6-b954-61956ca2836d",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1528.3572519550153,
3540
],
"parameters": {
"width": 830.4857444594224,
"height": 495.4835100729081,
"content": "## Workflow installation\n* Add a \"slug\" text property to each blog post (this parameter will be synced with Webflow and will be used to determine if a post is new or already present in your Webflow collection)\n* Add a \"Sync to Webflow?\" checkbox to each blog post\n* Connect your accounts and run a test to fill Webflow nodes with the right fields\n\n[![image.png](https://i.postimg.cc/xCymVp7w/image.png)](https://postimg.cc/BLbbxpJp)"
},
"typeVersion": 1
},
{
"id": "a5a79fd3-7adb-4e56-8aa7-2fd0cfc22927",
"name": "Get simple page data",
"type": "n8n-nodes-base.notion",
"position": [
-80,
4520
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"resource": "databasePage",
"operation": "get"
},
"credentials": {
"notionApi": {
"id": "rxtaEXgFPg96muhy",
"name": "My Notion account"
}
},
"executeOnce": true,
"typeVersion": 2.2
},
{
"id": "dbb56719-e091-4475-94fb-430cd58ce8bb",
"name": "Get all page data",
"type": "n8n-nodes-base.notion",
"position": [
-120,
4840
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"simple": false,
"resource": "databasePage",
"operation": "get"
},
"credentials": {
"notionApi": {
"id": "rxtaEXgFPg96muhy",
"name": "My Notion account"
}
},
"executeOnce": true,
"typeVersion": 2.2
},
{
"id": "af3fd27a-642e-4ec6-bc07-5d02076830e2",
"name": "Take cover url",
"type": "n8n-nodes-base.set",
"position": [
100,
4840
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "7f9960fb-9898-4d1a-b4d9-29c95fb7c144",
"name": "cover_url",
"type": "string",
"value": "={{ $json.cover.external.url }}"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "5910292c-2548-4ca2-b7e4-304f99712e8d",
"name": "Merge1",
"type": "n8n-nodes-base.merge",
"position": [
320,
4640
],
"parameters": {
"mode": "combine",
"options": {},
"combinationMode": "mergeByPosition"
},
"typeVersion": 2.1
},
{
"id": "65c81d79-770c-48d4-97b9-f22328c22465",
"name": "Data transporter1",
"type": "n8n-nodes-base.noOp",
"position": [
3220,
4900
],
"parameters": {},
"typeVersion": 1
},
{
"id": "1bc81efb-d293-4c97-bcb8-e114de0e482c",
"name": "Get all blog posts1",
"type": "n8n-nodes-base.notion",
"position": [
-1220,
4640
],
"parameters": {
"options": {},
"resource": "databasePage",
"operation": "getAll",
"returnAll": true,
"databaseId": {
"__rl": true,
"mode": "list",
"value": "4587b66c-d670-45b5-93f0-69ba1b0f3924",
"cachedResultUrl": "https://www.notion.so/4587b66cd67045b593f069ba1b0f3924",
"cachedResultName": "My blog"
}
},
"credentials": {
"notionApi": {
"id": "rxtaEXgFPg96muhy",
"name": "My Notion account"
}
},
"typeVersion": 2.2
},
{
"id": "56392232-05c7-477f-911f-7713d6cfa25f",
"name": "Is sync checked?1",
"type": "n8n-nodes-base.filter",
"position": [
-940,
4640
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "461a5a59-f894-4dda-9233-175a1e228d23",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.property_sync_to_webflow }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2
},
{
"id": "2a9fab27-612e-4eb9-935c-fd802f39c96e",
"name": "For each blog post1",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-360,
4640
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "8f6d8e51-b92b-4780-b782-3f72203f40aa",
"name": "Sticky Note17",
"type": "n8n-nodes-base.stickyNote",
"position": [
540,
4720
],
"parameters": {
"width": 777.880012347261,
"height": 287.94399632670337,
"content": "### ⚙️ Turn blocks into rich text\nThis is where the magic happens — Notion blocks are mapped and turned into their respective html version. Works with all the major rich text elements: headings 1, headings 2, headings 3, normal, bold and italic text, quotes, bulleted lists, numbered lists and images with captions."
},
"typeVersion": 1
},
{
"id": "9592c56d-9bb2-433e-b49c-ec634e3d1db2",
"name": "Sticky Note18",
"type": "n8n-nodes-base.stickyNote",
"position": [
1980,
4420
],
"parameters": {
"width": 218.00983675699544,
"height": 394.8629861599825,
"content": "### ✅ Create a new post or update an existing one?\nThis node compares (by slug) your Notion post with all your Webflow posts and chooses whether to create a new one (in \"A only\" branch) or update an existing one (in \"different\" branch)."
},
"typeVersion": 1
},
{
"id": "3ffb06d2-c1f1-4ce1-961f-8ece894d6cca",
"name": "Create post1",
"type": "n8n-nodes-base.webflow",
"position": [
2400,
4460
],
"parameters": {
"siteId": "65a40576635069142ed11d7c",
"fieldsUi": {
"fieldValues": [
{
"fieldId": "name",
"fieldValue": "={{ $json[\"name\"] }}"
},
{
"fieldId": "slug",
"fieldValue": "={{ $json.property_slug }}"
},
{
"fieldId": "blog-post-richt-text",
"fieldValue": "={{ $json.newRichText }}"
},
{
"fieldId": "_archived",
"fieldValue": "false"
},
{
"fieldId": "_draft",
"fieldValue": "true"
},
{
"fieldId": "blog-post-featured-image-photo",
"fieldValue": "={{ $json.cover_url }}"
},
{
"fieldId": "blog-post-thumbnail-image-photo",
"fieldValue": "={{ $json.cover_url }}"
}
]
},
"operation": "create",
"collectionId": "65a40577635069142ed11dd8",
"authentication": "oAuth2"
},
"credentials": {
"webflowOAuth2Api": {
"id": "cGhEXKKL99szTUa1",
"name": "Webflow account"
}
},
"retryOnFail": true,
"typeVersion": 1
},
{
"id": "e6490f39-b420-488c-b407-948425615764",
"name": "Sticky Note19",
"type": "n8n-nodes-base.stickyNote",
"position": [
-140,
3960
],
"parameters": {
"width": 233.87813121439967,
"height": 389.3234455133497,
"content": "### 🎉 Success\nSend a success message where you want.\n\nYou can remove this node.\n\nNote: If you're on it, you may need to refresh the Webflow page."
},
"typeVersion": 1
},
{
"id": "13568b0a-9665-4149-b848-2dc355b91126",
"name": "Update slug on posts1",
"type": "n8n-nodes-base.notion",
"position": [
2920,
4760
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Compare by slug1').item.json.different.id.inputA }}"
},
"options": {},
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "slug|rich_text",
"textContent": "={{ $json.slug }}"
}
]
}
},
"credentials": {
"notionApi": {
"id": "rxtaEXgFPg96muhy",
"name": "My Notion account"
}
},
"retryOnFail": true,
"typeVersion": 2.2
},
{
"id": "8574c1d2-491d-4bbb-bcc1-0bef64b321a2",
"name": "Slug uniqueness checker and differentiator1",
"type": "n8n-nodes-base.code",
"notes": "Add a number to the slug if it's not unique",
"position": [
-660,
4640
],
"parameters": {
"jsCode": "const data = $input.all().map(item => item.json)\nconst slugCount = {};\n\nreturn data.map(item => {\n let slug = item.property_slug;\n \n if (slugCount[slug]) {\n slugCount[slug] += 1;\n slug = `${slug}-${slugCount[slug]}`;\n } else {\n slugCount[slug] = 1;\n }\n \n item.property_slug = slug;\n return item;\n});"
},
"notesInFlow": true,
"typeVersion": 2
},
{
"id": "21755856-9123-4acd-b343-3af878d665ad",
"name": "Success message1",
"type": "n8n-nodes-base.slack",
"position": [
-80,
4175
],
"parameters": {
"text": "=[Notion to Webflow] — \"{{ $json.name }}\" successfully synced 🎉",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C07719A0GF5",
"cachedResultName": "general"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"id": "qY28oJXU3BH6OrP3",
"name": "Desengineers Account"
}
},
"typeVersion": 2.2
},
{
"id": "6c232d4a-464b-4d5a-992b-f649d955eb1e",
"name": "Merge2",
"type": "n8n-nodes-base.merge",
"position": [
2660,
4540
],
"parameters": {
"mode": "combine",
"options": {},
"combinationMode": "mergeByPosition"
},
"typeVersion": 2.1
},
{
"id": "6af0cab5-8f70-435f-a341-c22d157d9200",
"name": "Compare by slug1",
"type": "n8n-nodes-base.compareDatasets",
"position": [
2040,
4640
],
"parameters": {
"options": {},
"mergeByFields": {
"values": [
{
"field1": "property_slug",
"field2": "slug"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "54a7dcf6-188e-4ca5-bc1e-3e76d5536236",
"name": "Add slug to posts1",
"type": "n8n-nodes-base.notion",
"position": [
2900,
4540
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {},
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "slug|rich_text",
"textContent": "={{ $json.slug }}"
}
]
}
},
"credentials": {
"notionApi": {
"id": "rxtaEXgFPg96muhy",
"name": "My Notion account"
}
},
"retryOnFail": true,
"typeVersion": 2.2
},
{
"id": "f9a66b20-ce82-4f36-b145-283dadf97d34",
"name": "Get all collection posts1",
"type": "n8n-nodes-base.webflow",
"position": [
1720,
4780
],
"parameters": {
"siteId": "65a40576635069142ed11d7c",
"operation": "getAll",
"returnAll": true,
"collectionId": "65a40577635069142ed11dd8",
"authentication": "oAuth2"
},
"credentials": {
"webflowOAuth2Api": {
"id": "cGhEXKKL99szTUa1",
"name": "Webflow account"
}
},
"typeVersion": 1
},
{
"id": "c09f3782-12a1-4a91-945d-cd1ed14bfeb3",
"name": "Data transporter, Notion posts to sync1",
"type": "n8n-nodes-base.noOp",
"position": [
1720,
4480
],
"parameters": {},
"typeVersion": 1
},
{
"id": "9dc3ee15-4b4c-463c-a3b5-17b1dcb275da",
"name": "Craft the rich text element1",
"type": "n8n-nodes-base.code",
"position": [
1160,
4836
],
"parameters": {
"jsCode": "const blocks = $input.all().map(item => item.json);\n\nlet newRichText = '';\nlet bulletedListItems = [];\nlet numberedListItems = [];\n\nblocks.forEach(block => {\n if (block.type === 'bulleted_list_item') {\n bulletedListItems.push(block.html);\n } else if (block.type === 'numbered_list_item') {\n numberedListItems.push(block.html);\n } else {\n if (bulletedListItems.length > 0) {\n newRichText += `<ul>${bulletedListItems.join('')}</ul>`;\n bulletedListItems = [];\n }\n if (numberedListItems.length > 0) {\n newRichText += `<ol>${numberedListItems.join('')}</ol>`;\n numberedListItems = [];\n }\n newRichText += block.html;\n }\n});\n\nif (bulletedListItems.length > 0) {\n newRichText += `<ul>${bulletedListItems.join('')}</ul>`;\n}\nif (numberedListItems.length > 0) {\n newRichText += `<ol>${numberedListItems.join('')}</ol>`;\n}\n\nconst output = [{ newRichText }];\nreturn output;\n\n"
},
"typeVersion": 2
},
{
"id": "e4ca0e5a-21bb-4d38-8448-8195b8994c12",
"name": "Turn blocks into HTML1",
"type": "n8n-nodes-base.code",
"position": [
860,
4840
],
"parameters": {
"jsCode": "const blocks = $input.all().map(item => item.json);\nconst output = [];\n\nblocks.forEach(block => {\n let html = '';\n \n switch (block.type) {\n case 'heading_1':\n html = block.heading_1.text.map(item => item.text.content).join(' ');\n html = `<h1>${html}</h1>`;\n break;\n case 'heading_2':\n html = block.heading_2.text.map(item => item.text.content).join(' ');\n html = `<h2>${html}</h2>`;\n break;\n case 'heading_3':\n html = block.heading_3.text.map(item => item.text.content).join(' ');\n html = `<h3>${html}</h3>`;\n break;\n case 'paragraph':\n html = `<p>${block.paragraph.text.map(item => {\n let content = item.text.content.trim();\n if (item.annotations.bold) content = `<b>${content}</b>`;\n if (item.annotations.italic) content = `<i>${content}</i>`;\n if (item.text.link) content = `<a href=\"${item.text.link.url}\">${content}</a>`;\n return content;\n }).join(' ') || ' '}</p>`; // the space inside the apostrophes is on purpose, otherwise Webflow will automatically delete the empty blocks\n break;\n case 'quote':\n html = block.quote.text.map(item => item.text.content).join(' ');\n html = `<blockquote>${html}</blockquote>`;\n break;\n case 'bulleted_list_item':\n html = block.bulleted_list_item.text.map(item => item.text.content).join(' ');\n html = `<li>${html}</li>`;\n break;\n case 'numbered_list_item':\n html = block.numbered_list_item.text.map(item => item.text.content).join(' ');\n html = `<li>${html}</li>`;\n break;\n case 'image':\n const caption = block.image.caption.map(item => item.text.content).join(' ');\n html = `<figure><img src=\"${block.image.file.url}\" alt=\"${caption}\" /><figcaption>${caption}</figcaption></figure>`;\n break;\n case 'code':\n const codeContent = block.code.text.map(item => item.text.content).join('\\n')\n html = `<pre><code>${codeContent}</code></pre>`\n break\n default:\n html = block.content ? `<div>${block.content}</div>` : '';\n }\n\n if (html) {\n output.push({\n block_id: block.id,\n type: block.type,\n html\n });\n }\n});\n\nreturn output;\n"
},
"typeVersion": 2
},
{
"id": "719f5116-5e60-488c-81c2-d55cea2e2646",
"name": "Get blocks1",
"type": "n8n-nodes-base.notion",
"position": [
580,
4837
],
"parameters": {
"blockId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"resource": "block",
"operation": "getAll",
"returnAll": true,
"simplifyOutput": false
},
"credentials": {
"notionApi": {
"id": "rxtaEXgFPg96muhy",
"name": "My Notion account"
}
},
"typeVersion": 2.2
},
{
"id": "23f88f9c-ef4a-4158-bff5-728e2cf0383a",
"name": "Update in \"Blog Posts\"",
"type": "n8n-nodes-base.webflow",
"maxTries": 3,
"position": [
2660,
4780
],
"parameters": {
"itemId": "={{ $json.webflow_item_id }}",
"siteId": "65a40576635069142ed11d7c",
"fieldsUi": {
"fieldValues": [
{
"fieldId": "_draft",
"fieldValue": "true"
},
{
"fieldId": "_archived",
"fieldValue": "false"
},
{
"fieldId": "name",
"fieldValue": "={{ $json.name }}"
},
{
"fieldId": "slug",
"fieldValue": "={{ $json.property_slug }}"
},
{
"fieldId": "blog-post-richt-text",
"fieldValue": "={{ $json.newRichText }}"
},
{
"fieldId": "blog-post-featured-image-photo",
"fieldValue": "={{ $json.cover_url }}"
},
{
"fieldId": "blog-post-thumbnail-image-photo",
"fieldValue": "={{ $json.cover_url }}"
}
]
},
"operation": "update",
"collectionId": "65a40577635069142ed11dd8",
"authentication": "oAuth2"
},
"credentials": {
"webflowOAuth2Api": {
"id": "cGhEXKKL99szTUa1",
"name": "Webflow account"
}
},
"retryOnFail": true,
"typeVersion": 1,
"alwaysOutputData": false
},
{
"id": "6db40a4d-4acd-40f3-8830-f17e00678e39",
"name": "Add Webflow item id to Notion data",
"type": "n8n-nodes-base.code",
"position": [
2400,
4760
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const compareResult = $json\nconst notionData = $('Final Notion post data').item.json\n\nconst output = {\n ...notionData, // spread notion data\n webflow_item_id: compareResult.different._id.inputB // add the webflow item id\n}\n\nreturn output"
},
"typeVersion": 2
},
{
"id": "49e3d52c-a95a-4ac0-ae6a-69e4a722a628",
"name": "Final Notion post data",
"type": "n8n-nodes-base.merge",
"position": [
1380,
4640
],
"parameters": {
"mode": "combine",
"options": {},
"combinationMode": "mergeByPosition"
},
"typeVersion": 2.1
},
{
"id": "23755e8c-0012-4a72-ad9e-f450ceca1de4",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-146,
4720
],
"parameters": {
"width": 366.7438380520149,
"height": 282.04364735085795,
"content": "### No wastes\nThese nodes extract the cover image url of the Notion page to make it easy for you to use it in the collection fields."
},
"typeVersion": 1
},
{
"id": "cb16a61b-73bc-491b-b4ce-b4dc5a5f21fc",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-1480,
4640
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.2
}
],
"connections": {
"Merge1": {
"main": [
[
{
"node": "Final Notion post data",
"type": "main",
"index": 0
},
{
"node": "Get blocks1",
"type": "main",
"index": 0
}
]
]
},
"Merge2": {
"main": [
[
{
"node": "Add slug to posts1",
"type": "main",
"index": 0
}
]
]
},
"Get blocks1": {
"main": [
[
{
"node": "Turn blocks into HTML1",
"type": "main",
"index": 0
}
]
]
},
"Create post1": {
"main": [
[
{
"node": "Merge2",
"type": "main",
"index": 0
}
]
]
},
"Take cover url": {
"main": [
[
{
"node": "Merge1",
"type": "main",
"index": 1
}
]
]
},
"Compare by slug1": {
"main": [
[
{
"node": "Create post1",
"type": "main",
"index": 0
},
{
"node": "Merge2",
"type": "main",
"index": 1
}
],
null,
[
{
"node": "Add Webflow item id to Notion data",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Get all blog posts1",
"type": "main",
"index": 0
}
]
]
},
"Data transporter1": {
"main": [
[
{
"node": "For each blog post1",
"type": "main",
"index": 0
}
]
]
},
"Get all page data": {
"main": [
[
{
"node": "Take cover url",
"type": "main",
"index": 0
}
]
]
},
"Is sync checked?1": {
"main": [
[
{
"node": "Slug uniqueness checker and differentiator1",
"type": "main",
"index": 0
}
]
]
},
"Add slug to posts1": {
"main": [
[
{
"node": "Data transporter1",
"type": "main",
"index": 0
}
]
]
},
"For each blog post1": {
"main": [
[
{
"node": "Success message1",
"type": "main",
"index": 0
}
],
[
{
"node": "Get simple page data",
"type": "main",
"index": 0
},
{
"node": "Get all page data",
"type": "main",
"index": 0
}
]
]
},
"Get all blog posts1": {
"main": [
[
{
"node": "Is sync checked?1",
"type": "main",
"index": 0
}
]
]
},
"Get simple page data": {
"main": [
[
{
"node": "Merge1",
"type": "main",
"index": 0
}
]
]
},
"Update slug on posts1": {
"main": [
[
{
"node": "Data transporter1",
"type": "main",
"index": 0
}
]
]
},
"Final Notion post data": {
"main": [
[
{
"node": "Data transporter, Notion posts to sync1",
"type": "main",
"index": 0
},
{
"node": "Get all collection posts1",
"type": "main",
"index": 0
}
]
]
},
"Turn blocks into HTML1": {
"main": [
[
{
"node": "Craft the rich text element1",
"type": "main",
"index": 0
}
]
]
},
"Update in \"Blog Posts\"": {
"main": [
[
{
"node": "Update slug on posts1",
"type": "main",
"index": 0
}
]
]
},
"Get all collection posts1": {
"main": [
[
{
"node": "Compare by slug1",
"type": "main",
"index": 1
}
]
]
},
"Craft the rich text element1": {
"main": [
[
{
"node": "Final Notion post data",
"type": "main",
"index": 1
}
]
]
},
"Add Webflow item id to Notion data": {
"main": [
[
{
"node": "Update in \"Blog Posts\"",
"type": "main",
"index": 0
}
]
]
},
"Data transporter, Notion posts to sync1": {
"main": [
[
{
"node": "Compare by slug1",
"type": "main",
"index": 0
}
]
]
},
"Slug uniqueness checker and differentiator1": {
"main": [
[
{
"node": "For each blog post1",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,716 @@
{
"meta": {
"instanceId": "d6b502dfa4d9dd072cdc5c2bb763558661053f651289291352a84403e01b3d1b",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "4377c764-07f3-4304-8105-d3f009925917",
"name": "On clicking 'execute'",
"type": "n8n-nodes-base.manualTrigger",
"position": [
1780,
520
],
"parameters": {},
"typeVersion": 1
},
{
"id": "10f6ea70-c2cb-4463-972c-e2fdef3e837a",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1339.5461279763795,
900
],
"parameters": {
"color": 6,
"width": 2086.845881354743,
"height": 750.8363163824032,
"content": "## Subworkflow"
},
"typeVersion": 1
},
{
"id": "d22236c2-578c-400b-b3e5-354498620c39",
"name": "Return",
"type": "n8n-nodes-base.set",
"position": [
3220,
1100
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "8d513345-6484-431f-afb7-7cf045c90f4f",
"name": "Done",
"type": "boolean",
"value": true
}
]
}
},
"typeVersion": 3.3
},
{
"id": "943eed85-d4cd-4ec5-b278-d143b0f6bd15",
"name": "Get File",
"type": "n8n-nodes-base.httpRequest",
"position": [
2320,
980
],
"parameters": {
"url": "={{ $json.download_url }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "124ebdd7-c2c1-4fec-89d3-596f034e0fe7",
"name": "If file too large",
"type": "n8n-nodes-base.if",
"position": [
2120,
1000
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "45ce825e-9fa6-430c-8931-9aaf22c42585",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.content }}",
"rightValue": ""
},
{
"id": "9619a55f-7fb1-4f24-b1a7-7aeb82365806",
"operator": {
"type": "string",
"operation": "notExists",
"singleValue": true
},
"leftValue": "={{ $json.error }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2
},
{
"id": "751621b4-4f99-4178-a691-40fc7488874b",
"name": "Merge Items",
"type": "n8n-nodes-base.merge",
"position": [
2120,
1260
],
"parameters": {},
"typeVersion": 2
},
{
"id": "8892eb02-0e8e-4617-85e6-e6f188361f95",
"name": "isDiffOrNew",
"type": "n8n-nodes-base.code",
"position": [
2320,
1260
],
"parameters": {
"jsCode": "const orderJsonKeys = (jsonObj) => {\n const ordered = {};\n Object.keys(jsonObj).sort().forEach(key => {\n ordered[key] = jsonObj[key];\n });\n return ordered;\n}\n\n// Check if file returned with content\nif (Object.keys($input.all()[0].json).includes(\"content\")) {\n // Decode base64 content and parse JSON\n const origWorkflow = JSON.parse(Buffer.from($input.all()[0].json.content, 'base64').toString());\n const n8nWorkflow = $input.all()[1].json;\n \n // Order JSON objects\n const orderedOriginal = orderJsonKeys(origWorkflow);\n const orderedActual = orderJsonKeys(n8nWorkflow);\n\n // Determine difference\n if (JSON.stringify(orderedOriginal) === JSON.stringify(orderedActual)) {\n $input.all()[0].json.github_status = \"same\";\n } else {\n $input.all()[0].json.github_status = \"different\";\n $input.all()[0].json.n8n_data_stringy = JSON.stringify(orderedActual, null, 2);\n }\n $input.all()[0].json.content_decoded = orderedOriginal;\n// No file returned / new workflow\n} else if (Object.keys($input.all()[0].json).includes(\"data\")) {\n const origWorkflow = JSON.parse($input.all()[0].json.data);\n const n8nWorkflow = $input.all()[1].json;\n \n // Order JSON objects\n const orderedOriginal = orderJsonKeys(origWorkflow);\n const orderedActual = orderJsonKeys(n8nWorkflow);\n\n // Determine difference\n if (JSON.stringify(orderedOriginal) === JSON.stringify(orderedActual)) {\n $input.all()[0].json.github_status = \"same\";\n } else {\n $input.all()[0].json.github_status = \"different\";\n $input.all()[0].json.n8n_data_stringy = JSON.stringify(orderedActual, null, 2);\n }\n $input.all()[0].json.content_decoded = orderedOriginal;\n\n} else {\n // Order JSON object\n const n8nWorkflow = $input.all()[1].json;\n const orderedActual = orderJsonKeys(n8nWorkflow);\n \n // Proper formatting\n $input.all()[0].json.github_status = \"new\";\n $input.all()[0].json.n8n_data_stringy = JSON.stringify(orderedActual, null, 2);\n}\n\n// Return items\nreturn $input.all();"
},
"typeVersion": 1
},
{
"id": "bfddb2a2-c149-4710-bd77-b368d641114d",
"name": "Check Status",
"type": "n8n-nodes-base.switch",
"position": [
2540,
1260
],
"parameters": {
"rules": {
"rules": [
{
"value2": "same"
},
{
"output": 1,
"value2": "different"
},
{
"output": 2,
"value2": "new"
}
]
},
"value1": "={{$json.github_status}}",
"dataType": "string"
},
"typeVersion": 1
},
{
"id": "681e54af-b916-416d-9801-ac38a5882bcf",
"name": "Same file - Do nothing",
"type": "n8n-nodes-base.noOp",
"position": [
2760,
1100
],
"parameters": {},
"typeVersion": 1
},
{
"id": "38b2041d-1d56-436f-aa04-79d7241dcc74",
"name": "File is different",
"type": "n8n-nodes-base.noOp",
"position": [
2760,
1260
],
"parameters": {},
"typeVersion": 1
},
{
"id": "ae33280d-10d5-4882-be9b-7972394357e1",
"name": "File is new",
"type": "n8n-nodes-base.noOp",
"position": [
2760,
1420
],
"parameters": {},
"typeVersion": 1
},
{
"id": "bea3995f-9f34-4119-a6cf-20281e70d685",
"name": "Create new file",
"type": "n8n-nodes-base.github",
"position": [
2980,
1420
],
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "={{ $('Globals').item.json.repo.owner }}"
},
"filePath": "={{ $('Globals').item.json.repo.path }}{{$('Execute Workflow Trigger').first().json.id}}.json",
"resource": "file",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $('Globals').item.json.repo.name }}"
},
"fileContent": "={{$('isDiffOrNew').item.json[\"n8n_data_stringy\"]}}",
"commitMessage": "={{$('Execute Workflow Trigger').first().json.name}} ({{$json.github_status}})"
},
"credentials": {
"githubApi": {
"id": "3mfzXcMjoqNHsujs",
"name": "GitHub account"
}
},
"typeVersion": 1
},
{
"id": "d9172af3-55f8-4b99-b462-3e6e718b5a77",
"name": "Edit existing file",
"type": "n8n-nodes-base.github",
"position": [
2980,
1240
],
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "={{ $('Globals').item.json.repo.owner }}"
},
"filePath": "={{ $('Globals').item.json.repo.path }}{{$('Execute Workflow Trigger').first().json.id}}.json",
"resource": "file",
"operation": "edit",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $('Globals').item.json.repo.name }}"
},
"fileContent": "={{$('isDiffOrNew').item.json[\"n8n_data_stringy\"]}}",
"commitMessage": "={{$('Execute Workflow Trigger').first().json.name}} ({{$json.github_status}})"
},
"credentials": {
"githubApi": {
"id": "3mfzXcMjoqNHsujs",
"name": "GitHub account"
}
},
"typeVersion": 1
},
{
"id": "d9589e32-ed20-46e7-9427-1680c6222406",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
2380,
620
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "e1530650-aa76-4ab3-b5bb-cd6b805ea656",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
1780,
720
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 2
}
]
}
},
"typeVersion": 1.2
},
{
"id": "79910589-f40f-46fa-a704-eaa65157a17a",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1340,
278.28654385738866
],
"parameters": {
"color": 4,
"width": 365.19481715599653,
"height": 596.4810912485963,
"content": "## Backup to GitHub \nThis workflow will backup all instance credentials to GitHub.\n\nThe files are saved `ID.json` for the filename.\n\n### Setup\nOpen `Globals` node and update the values below 👇\n\n- **repo.owner:** your Github username\n- **repo.name:** the name of your repository\n- **repo.path:** the folder to use within the repository. If it doesn't exist it will be created.\n\n\nIf your username was `john-doe` and your repository was called `n8n-backups` and you wanted the credentials to go into a `credentials` folder you would set:\n\n- repo.owner - john-doe\n- repo.name - n8n-backups\n- repo.path - credentials/\n\n\nThe workflow calls itself using a subworkflow, to help reduce memory usage."
},
"typeVersion": 1
},
{
"id": "e16c9874-1a35-41c4-8410-0c42efe17770",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1740,
440
],
"parameters": {
"color": 7,
"width": 1028.7522287279464,
"height": 434.88564057365943,
"content": "## Main workflow loop"
},
"typeVersion": 1
},
{
"id": "a1464b91-516a-4fd9-9235-20de50e74cb2",
"name": "Get file data",
"type": "n8n-nodes-base.github",
"position": [
1920,
1000
],
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "={{ $json.repo.owner }}"
},
"filePath": "={{ $json.repo.path }}{{ $('Execute Workflow Trigger').item.json.id }}.json",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $json.repo.name }}"
},
"asBinaryProperty": false,
"additionalParameters": {}
},
"credentials": {
"githubApi": {
"id": "3mfzXcMjoqNHsujs",
"name": "GitHub account"
}
},
"typeVersion": 1,
"continueOnFail": true,
"alwaysOutputData": true
},
{
"id": "eb2fe87f-f3af-4215-ac1f-7c2b45e8aff6",
"name": "Globals",
"type": "n8n-nodes-base.set",
"position": [
1720,
1160
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "6cf546c5-5737-4dbd-851b-17d68e0a3780",
"name": "repo.owner",
"type": "string",
"value": "john-doe"
},
{
"id": "452efa28-2dc6-4ea3-a7a2-c35d100d0382",
"name": "repo.name",
"type": "string",
"value": "n8n-backup"
},
{
"id": "81c4dc54-86bf-4432-a23f-22c7ea831e74",
"name": "repo.path",
"type": "string",
"value": "credentials/"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "f4498ab4-1760-4849-9fe1-ecfcd7baa9f3",
"name": "Execute Command",
"type": "n8n-nodes-base.executeCommand",
"position": [
2000,
620
],
"parameters": {
"command": "npx n8n export:credentials --all --decrypted"
},
"typeVersion": 1
},
{
"id": "d453a000-40ef-43f5-b108-5eb30422d1a3",
"name": "JSON formatting",
"type": "n8n-nodes-base.code",
"position": [
2180,
620
],
"parameters": {
"jsCode": "// Function to beautify JSON\nfunction beautifyJson(jsonString) {\n try {\n // Parse the JSON string\n const jsonObject = JSON.parse(jsonString);\n\n // Format the JSON with indentation\n return jsonObject; // Return the parsed object directly\n } catch (error) {\n // Return the error message if JSON is invalid\n return `Invalid JSON: ${error.message}`;\n }\n}\n\n// Retrieve the JSON object from the input data\nconst input = $input.all()[0].json;\n\n// Extract the JSON string from the stdout field\nconst jsonString = input.stdout.match(/\\[{.*}\\]/s);\n\n// Check if a valid JSON string is found\nif (!jsonString) {\n return {\n json: {\n error: \"No valid JSON string found in stdout.\"\n }\n };\n}\n\n// Beautify the JSON\nconst beautifiedJson = beautifyJson(jsonString[0]);\n\n// Output the beautified JSON, ensuring each entry is in an object with a 'json' key\nconst output = beautifiedJson.map(entry => ({ json: entry }));\n\n// Return the output\nreturn output;\n"
},
"typeVersion": 2
},
{
"id": "49dbf875-7345-4241-a7fc-f42e53aef64e",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1680,
1060
],
"parameters": {
"color": 4,
"width": 150,
"height": 80,
"content": "## Edit this node 👇"
},
"typeVersion": 1
},
{
"id": "98158f3e-7aca-456b-994c-4c795d31c18c",
"name": "Execute Workflow",
"type": "n8n-nodes-base.executeWorkflow",
"position": [
2600,
620
],
"parameters": {
"mode": "each",
"options": {},
"workflowId": "={{ $workflow.id }}"
},
"typeVersion": 1
},
{
"id": "d8c52eb7-bcb0-49e7-bb32-7499b1ca22cd",
"name": "Execute Workflow Trigger",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
1440,
1280
],
"parameters": {
"inputSource": "passthrough"
},
"typeVersion": 1.1
}
],
"pinData": {},
"connections": {
"Globals": {
"main": [
[
{
"node": "Get file data",
"type": "main",
"index": 0
}
]
]
},
"Get File": {
"main": [
[
{
"node": "Merge Items",
"type": "main",
"index": 0
}
]
]
},
"File is new": {
"main": [
[
{
"node": "Create new file",
"type": "main",
"index": 0
}
]
]
},
"Merge Items": {
"main": [
[
{
"node": "isDiffOrNew",
"type": "main",
"index": 0
}
]
]
},
"isDiffOrNew": {
"main": [
[
{
"node": "Check Status",
"type": "main",
"index": 0
}
]
]
},
"Check Status": {
"main": [
[
{
"node": "Same file - Do nothing",
"type": "main",
"index": 0
}
],
[
{
"node": "File is different",
"type": "main",
"index": 0
}
],
[
{
"node": "File is new",
"type": "main",
"index": 0
}
]
]
},
"Get file data": {
"main": [
[
{
"node": "If file too large",
"type": "main",
"index": 0
}
]
]
},
"Create new file": {
"main": [
[
{
"node": "Return",
"type": "main",
"index": 0
}
]
]
},
"Execute Command": {
"main": [
[
{
"node": "JSON formatting",
"type": "main",
"index": 0
}
]
]
},
"JSON formatting": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "Execute Workflow",
"type": "main",
"index": 0
},
{
"node": "Execute Workflow",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Execute Command",
"type": "main",
"index": 0
}
]
]
},
"File is different": {
"main": [
[
{
"node": "Edit existing file",
"type": "main",
"index": 0
}
]
]
},
"If file too large": {
"main": [
[
{
"node": "Get File",
"type": "main",
"index": 0
}
],
[
{
"node": "Merge Items",
"type": "main",
"index": 0
}
]
]
},
"Edit existing file": {
"main": [
[
{
"node": "Return",
"type": "main",
"index": 0
}
]
]
},
"On clicking 'execute'": {
"main": [
[
{
"node": "Execute Command",
"type": "main",
"index": 0
}
]
]
},
"Same file - Do nothing": {
"main": [
[
{
"node": "Return",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow Trigger": {
"main": [
[
{
"node": "Globals",
"type": "main",
"index": 0
},
{
"node": "Merge Items",
"type": "main",
"index": 1
}
]
]
}
}
}

View File

@@ -0,0 +1,183 @@
{
"meta": {
"instanceId": "1dd912a1610cd0376bae7bb8f1b5838d2b601f42ac66a48e012166bb954fed5a",
"templateId": "2314"
},
"nodes": [
{
"id": "3409b6e3-aef1-4eb4-acfb-72a73101e109",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [
380,
240
],
"parameters": {},
"typeVersion": 1
},
{
"id": "4942cdfc-bc9a-43ac-a60d-06e1ddf52d07",
"name": "Write Result File to Disk",
"type": "n8n-nodes-base.readWriteFile",
"position": [
1360,
240
],
"parameters": {
"options": {},
"fileName": "document.pdf",
"operation": "write",
"dataPropertyName": "=data"
},
"typeVersion": 1
},
{
"id": "1467a9ab-144d-48cc-a52f-3dca86ca0e8b",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
880,
100
],
"parameters": {
"width": 218,
"height": 132,
"content": "## Authentication\nConversion requests must be authenticated. Please create \n[ConvertAPI account to get authentication secret](https://www.convertapi.com/a/signin)"
},
"typeVersion": 1
},
{
"id": "4d85a311-8e39-48ce-868e-95efec509247",
"name": "Create HTML",
"type": "n8n-nodes-base.set",
"position": [
580,
240
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "ad325c1b-1597-45ab-98cd-1801da32e3f1",
"name": "data",
"type": "string",
"value": "=<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>ConvertAPI Test Document</title>\n</head>\n<body>\n <h1>ConvertAPI Test Document</h1>\n <p>This is a minimal HTML5 document used for testing ConvertAPI's conversion capabilities.</p>\n <section>\n <h2>Section Title</h2>\n <p>This is a section within the document.</p>\n </section>\n <footer>\n <p>&copy; 2024 Test Document</p>\n </footer>\n</body>\n</html>"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "a0e4e17a-097f-4127-9b60-c6ae637816a0",
"name": "Convert HTML to File",
"type": "n8n-nodes-base.code",
"position": [
760,
240
],
"parameters": {
"jsCode": "const text = $node[\"Create HTML\"].json[\"data\"]\nconst buffer = Buffer.from(text, 'utf8');\nconst binaryData = {\n data: buffer.toString('base64'),\n mimeType: 'application/octet-stream',\n fileName: 'file.html',\n};\nitems[0].binary = { data: binaryData };\nreturn items;\n"
},
"typeVersion": 2
},
{
"id": "653b21eb-dae5-44e0-858a-a2905f495911",
"name": "Convert File to PDF",
"type": "n8n-nodes-base.httpRequest",
"position": [
940,
240
],
"parameters": {
"url": "https://v2.convertapi.com/convert/html/to/pdf",
"method": "POST",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
},
"sendBody": true,
"contentType": "multipart-form-data",
"sendHeaders": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "file",
"parameterType": "formBinaryData",
"inputDataFieldName": "data"
}
]
},
"genericAuthType": "httpQueryAuth",
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/octet-stream"
}
]
}
},
"credentials": {
"httpQueryAuth": {
"id": "WdAklDMod8fBEMRk",
"name": "Query Auth account"
}
},
"notesInFlow": true,
"typeVersion": 4.2
}
],
"pinData": {},
"connections": {
"Create HTML": {
"main": [
[
{
"node": "Convert HTML to File",
"type": "main",
"index": 0
}
]
]
},
"Convert File to PDF": {
"main": [
[
{
"node": "Write Result File to Disk",
"type": "main",
"index": 0
}
]
]
},
"Convert HTML to File": {
"main": [
[
{
"node": "Convert File to PDF",
"type": "main",
"index": 0
}
]
]
},
"When clicking Test workflow": {
"main": [
[
{
"node": "Create HTML",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,183 @@
{
"meta": {
"instanceId": "84ba6d895254e080ac2b4916d987aa66b000f88d4d919a6b9c76848f9b8a7616",
"templateId": "2353"
},
"nodes": [
{
"id": "8a36e8d4-a3bf-44e1-894a-db00bad99151",
"name": "Fetch Github Repo Releases",
"type": "n8n-nodes-base.httpRequest",
"position": [
880,
240
],
"parameters": {
"url": "=https://api.github.com/repos/{{ $json[\"github-org\"] }}/{{ $json[\"github-repo\"] }}/releases/latest",
"options": {}
},
"typeVersion": 4.2,
"alwaysOutputData": false
},
{
"id": "4803248b-3ff7-4994-a105-3d8ef68bd45d",
"name": "Daily Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
380,
240
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.2
},
{
"id": "0b2122d7-18cf-49b8-b10e-a8132df8ceb9",
"name": "RepoConfig",
"type": "n8n-nodes-base.code",
"position": [
620,
240
],
"parameters": {
"jsCode": "return [\n {\n \"github-org\": \"n8n-io\",\n \"github-repo\": \"n8n\"\n },\n {\n \"github-org\": \"home-assistant\",\n \"github-repo\": \"core\"\n }\n];"
},
"typeVersion": 2
},
{
"id": "60918b67-76bb-4c9e-bc84-845d59fced76",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
540,
100
],
"parameters": {
"width": 269,
"height": 278,
"content": "### Setup repos here to check releases for.\n\nAdd a new json object to the array setting the org and repo, these will be used by the following nodes"
},
"typeVersion": 1
},
{
"id": "66fbb663-cd52-471c-be8b-4175f754d02d",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1300,
120
],
"parameters": {
"height": 254,
"content": "### Setup Slack notification\n\nUpdate this node to customise your Slack notification"
},
"typeVersion": 1
},
{
"id": "9b04cdd2-e369-4862-b376-9945e93c0aaf",
"name": "Wether Release is new",
"type": "n8n-nodes-base.if",
"position": [
1080,
240
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "014670a7-6f9e-466c-a403-24ad4e230dff",
"operator": {
"type": "dateTime",
"operation": "after"
},
"leftValue": "={{ $json.published_at.toDateTime() }}",
"rightValue": "={{ DateTime.utc().minus(1, 'days') }}"
}
]
}
},
"typeVersion": 2
},
{
"id": "4ad55bb4-89d2-4f1d-bcb5-fe60aa4f8c79",
"name": "Send Message",
"type": "n8n-nodes-base.slack",
"position": [
1380,
220
],
"parameters": {
"text": "=:tada: New release for *{{ $('RepoConfig').item.json[\"github-repo\"] }}* - {{ $('Fetch Github Repo Releases').item.json[\"name\"] }}\n\n{{ $json.body.slice(0, 500) }}\n\n{{ $('Fetch Github Repo Releases').item.json[\"url\"] }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "#dk-test"
},
"otherOptions": {
"mrkdwn": true
}
},
"typeVersion": 2.2
}
],
"pinData": {},
"connections": {
"RepoConfig": {
"main": [
[
{
"node": "Fetch Github Repo Releases",
"type": "main",
"index": 0
}
]
]
},
"Daily Trigger": {
"main": [
[
{
"node": "RepoConfig",
"type": "main",
"index": 0
}
]
]
},
"Wether Release is new": {
"main": [
[
{
"node": "Send Message",
"type": "main",
"index": 0
}
]
]
},
"Fetch Github Repo Releases": {
"main": [
[
{
"node": "Wether Release is new",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,927 @@
{
"meta": {
"instanceId": "84ba6d895254e080ac2b4916d987aa66b000f88d4d919a6b9c76848f9b8a7616",
"templateId": "2358"
},
"nodes": [
{
"id": "fb774d11-da48-4481-ad4e-8c93274f123e",
"name": "Send message",
"type": "n8n-nodes-base.slack",
"position": [
2340,
580
],
"parameters": {
"text": "=Data from webhook: {{ $json.query.email }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C079GL6K3U6",
"cachedResultName": "general"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.2
},
{
"id": "5a3ad8f1-eba7-4076-80fc-0c1237aab50b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
380,
240
],
"parameters": {
"color": 7,
"width": 1163.3132111854613,
"height": 677.0358687053997,
"content": "![h](https://i.postimg.cc/9XLvL5dL/slide-sf-talk.png#full-width)"
},
"typeVersion": 1
},
{
"id": "01c59396-0fef-4d1c-aa1f-787669300650",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1860,
240
],
"parameters": {
"color": 7,
"width": 437,
"height": 99,
"content": "# What is n8n?\n### Low-code Automation Platform for technical teams"
},
"typeVersion": 1
},
{
"id": "0bdd4a35-7f5c-443c-a14a-4e6f7ed18712",
"name": "Execute JavaScript",
"type": "n8n-nodes-base.code",
"position": [
2340,
380
],
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"typeVersion": 2
},
{
"id": "4b1b6cc1-1a9f-4a0c-96d5-fd179c84c79d",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
4440,
240
],
"parameters": {
"color": 6,
"width": 318,
"height": 106,
"content": "# Example #2\n### RAG with PDF as source"
},
"typeVersion": 1
},
{
"id": "7e9e7802-5695-4240-83b9-d6f02192ad2b",
"name": "Recursive Character Text Splitter",
"type": "@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter",
"position": [
5120,
1000
],
"parameters": {
"options": {},
"chunkSize": 3000,
"chunkOverlap": 200
},
"typeVersion": 1
},
{
"id": "63783c21-af6d-4e70-8dec-c861641c53fb",
"name": "Embeddings OpenAI",
"type": "@n8n/n8n-nodes-langchain.embeddingsOpenAi",
"position": [
4880,
820
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "5742ce9c-2f73-4129-85eb-876f562cf6b1",
"name": "Default Data Loader",
"type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
"position": [
5100,
820
],
"parameters": {
"loader": "pdfLoader",
"options": {
"metadata": {
"metadataValues": [
{
"name": "document-title",
"value": "={{ $('PDFs to download').item.json.whitepaper_title }}"
},
{
"name": "document-publish-year",
"value": "={{ $('PDFs to download').item.json.publish_year }}"
},
{
"name": "document-author",
"value": "={{ $('PDFs to download').item.json.author }}"
}
]
}
},
"dataType": "binary"
},
"typeVersion": 1
},
{
"id": "686c63fa-4672-4107-bd58-ffbb0650b44b",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
5840,
840
],
"parameters": {
"model": "gpt-4o",
"options": {
"temperature": 0.3
}
},
"typeVersion": 1
},
{
"id": "73a7df02-aa2c-4f0f-aa88-38cbbbf3b1cb",
"name": "Embeddings OpenAI2",
"type": "@n8n/n8n-nodes-langchain.embeddingsOpenAi",
"position": [
5980,
1140
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "42737305-fd39-4ec7-b4ba-53f70085dd5f",
"name": "Vector Store Retriever",
"type": "@n8n/n8n-nodes-langchain.retrieverVectorStore",
"position": [
6040,
840
],
"parameters": {},
"typeVersion": 1
},
{
"id": "2c7a3666-e123-439d-8b74-41eb375f066c",
"name": "Download PDF",
"type": "n8n-nodes-base.httpRequest",
"position": [
4700,
600
],
"parameters": {
"url": "={{ $json.file_url }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "866eaeb9-6a7c-4209-b485-8ef13ed006b4",
"name": "PDFs to download",
"type": "n8n-nodes-base.noOp",
"notes": "BTC Whitepaper + metadata",
"position": [
4440,
600
],
"parameters": {},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "e78f2191-096c-4575-9d48-fb891fd18698",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
4440,
440
],
"parameters": {
"color": 4,
"width": 414.36616595939887,
"height": 91.0723900084547,
"content": "## A. Load PDF into Pinecone\nDownload the PDF, then text embeddings into Pincecone"
},
"typeVersion": 1
},
{
"id": "7c3ccf27-32b1-4ea7-b2ef-6997793de733",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
5600,
460
],
"parameters": {
"color": 4,
"width": 284.62109466374466,
"height": 86.95121951219511,
"content": "## B. Chat with PDF\nUse GPT4o to chat with Pinecone index"
},
"typeVersion": 1
},
{
"id": "6063d009-da6e-4cbf-899f-c86b879931a7",
"name": "Read Pinecone Vector Store",
"type": "@n8n/n8n-nodes-langchain.vectorStorePinecone",
"position": [
5980,
980
],
"parameters": {
"options": {
"pineconeNamespace": "whitepaper"
},
"pineconeIndex": {
"__rl": true,
"mode": "list",
"value": "whitepapers",
"cachedResultName": "whitepapers"
}
},
"typeVersion": 1
},
{
"id": "8aa52156-264d-4911-993c-ac5117a76b21",
"name": "Question and Answer Chain",
"type": "@n8n/n8n-nodes-langchain.chainRetrievalQa",
"position": [
5840,
620
],
"parameters": {
"text": "={{ $json.chatInput }}. \nOnly use vector store knowledge to answer the question. Don't make anything up. If you don't know the answer, tell the user that you don't know.",
"promptType": "define"
},
"typeVersion": 1.3
},
{
"id": "b394ee1d-a2ca-4db0-8caa-981f8f066787",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
7380,
240
],
"parameters": {
"color": 6,
"width": 504.25,
"height": 106,
"content": "# Example #3\n### AI Assistant that knows how to use predefined API endpoints "
},
"typeVersion": 1
},
{
"id": "37a8b8f2-c444-4c6e-9b02-b97a5c616e84",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
3020,
220
],
"parameters": {
"color": 6,
"width": 318,
"height": 111,
"content": "# Example #1\n### Categorize incoming emails with AI"
},
"typeVersion": 1
},
{
"id": "07123e8e-8760-4c89-acda-aaef6de68be2",
"name": "Anthropic Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
"position": [
7580,
700
],
"parameters": {
"options": {
"temperature": 0.4
}
},
"typeVersion": 1.2
},
{
"id": "e338a175-e823-4cd4-b77d-f5acbfcbdb9d",
"name": "Get calendar availability",
"type": "@n8n/n8n-nodes-langchain.toolHttpRequest",
"position": [
7900,
700
],
"parameters": {
"url": "https://www.googleapis.com/calendar/v3/freeBusy",
"method": "POST",
"jsonBody": "={\n \"timeMin\": \"{timeMin}\",\n \"timeMax\": \"{timeMax}\",\n \"timeZone\": \"Europe/Berlin\",\n \"groupExpansionMax\": 20,\n \"calendarExpansionMax\": 10,\n \"items\": [\n {\n \"id\": \"max@n8n.io\"\n }\n ]\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"toolDescription": "Call this tool to get the appointment availability for a particular period on the calendar. The tool may refer to availability as \"Free\" or \"Busy\". \n\nUse {timeMin} and {timeMax} to specify the window for the availability query. For example, to get availability for 25 July, 2024 the {timeMin} would be 2024-07-25T09:00:00+02:00 and {timeMax} would be 2024-07-25T17:00:00+02:00.\n\nIf the tool returns an empty response, it means that something went wrong. It does not mean that there is no availability.",
"nodeCredentialType": "googleCalendarOAuth2Api"
},
"typeVersion": 1
},
{
"id": "ae05933c-dfa9-4272-b610-8b5fc94d76fe",
"name": "Appointment booking agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
7680,
480
],
"parameters": {
"options": {
"systemMessage": "=You are an efficient and courteous assistant tasked with scheduling appointments with Max Tkacz.\n\nWhen users mention an appointment or meeting, they are referring to a meeting with Max.\nWhen users refer to the calendar or \"your schedule,\" they are referring to Max's calendar. \n\nYou can use various tools to access and manage Max's calendar. Your primary goal is to assist users in successfully booking an appointment with Max, ensuring no scheduling conflicts. Only book an appointment if the requested time slot is available (the tool may refer to this as \"Free\")\n\nToday's date is {{ $today.format('dd LLL yyyy') }}.\nAppointments are always 30 minutes in length. \n\n\nProvide accurate information at all times. If the tools are not functioning correctly, inform the user that you are unable to assist them at the moment.\n"
}
},
"typeVersion": 1.6
},
{
"id": "7e3b1797-150e-4c7c-93a5-306b981e0b6c",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
8300,
440
],
"parameters": {
"color": 7,
"width": 327.46658341463433,
"height": 571.8601927804875,
"content": "![h](https://i.imghippo.com/files/d9Bgv1721858679.png#full-width)\n[Open Calendar](https://calendar.google.com/calendar/u/0/r/day/2024/7/26)"
},
"typeVersion": 1
},
{
"id": "afe8d14d-d0d0-4a11-bb4f-57358de66bc1",
"name": "Window Buffer Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
7720,
700
],
"parameters": {
"contextWindowLength": 10
},
"typeVersion": 1.2
},
{
"id": "53d131ea-3235-4e4e-828b-dc22c9021e50",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
6380,
640
],
"parameters": {
"color": 7,
"width": 615.2162978341456,
"height": 403.1877919219511,
"content": "![h](https://i.postimg.cc/kXW9XrZt/Screenshot-2024-07-24-at-15-18-27.png#full-width)\nBTC Whitepaper references"
},
"typeVersion": 1
},
{
"id": "55a0f180-bb35-4b35-b72c-b9361698e5ad",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
9660,
240
],
"parameters": {
"color": 7,
"width": 345.33741540309194,
"height": 398.9629539487597,
"content": "### Connect with me or explore this demo 👇\n![QR](https://i.postimg.cc/VNkdCLQh/frame.png#full-width)"
},
"typeVersion": 1
},
{
"id": "14b3231d-aa96-4783-be8f-cb2f70b0bc7f",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
9220,
240
],
"parameters": {
"color": 7,
"width": 411.2946586626259,
"height": 197.19036476628202,
"content": "# Thank you and happy flowgramming 🤘\n\n### Max Tkacz | Senior Developer Advocate @ n8n"
},
"typeVersion": 1
},
{
"id": "c9a2fcdc-c8ab-4b9d-9979-4fd7cca1e8a8",
"name": "Insert into Pinecone vector store",
"type": "@n8n/n8n-nodes-langchain.vectorStorePinecone",
"position": [
4920,
600
],
"parameters": {
"mode": "insert",
"options": {
"clearNamespace": true,
"pineconeNamespace": "whitepaper"
},
"pineconeIndex": {
"__rl": true,
"mode": "list",
"value": "whitepapers",
"cachedResultName": "whitepapers"
}
},
"typeVersion": 1
},
{
"id": "6a890c74-67f9-4eee-bb56-7c9a68921ae1",
"name": "Book appointment",
"type": "@n8n/n8n-nodes-langchain.toolHttpRequest",
"position": [
8060,
700
],
"parameters": {
"url": "https://www.googleapis.com/calendar/v3/calendars/max@n8n.io/events",
"method": "POST",
"jsonBody": "={\n \"summary\": \"Appointment with {userName}\",\n \"start\": {\n \"dateTime\": \"{startTime}\",\n \"timeZone\": \"Europe/Berlin\"\n },\n \"end\": {\n \"dateTime\": \"{endTime}\",\n \"timeZone\": \"Europe/Berlin\"\n },\n \"attendees\": [\n {\"email\": \"max@n8n.io\"},\n {\"email\": \"{userEmail}\"}\n ]\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"toolDescription": "Call this tool to book an appointment in the calendar. ",
"nodeCredentialType": "googleCalendarOAuth2Api",
"placeholderDefinitions": {
"values": [
{
"name": "userName",
"description": "The full name of the user making the appointment. Like John Doe"
},
{
"name": "startTime",
"description": "The start time of the event in Europe/Berlin timezone. For example, 2024-07-24T10:00:00+02:00"
},
{
"name": "endTime",
"description": "The end time of the event in Europe/Berlin timezone. It should always be 30 minutes after the startTime. "
},
{
"name": "userEmail",
"description": "The email address of the user making the appointment"
}
]
}
},
"typeVersion": 1
},
{
"id": "7f6e62f2-2d72-4fd2-a6ef-e57028d0055b",
"name": "When chat message received",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"position": [
5600,
620
],
"webhookId": "c348693e-9c43-4bf2-90a5-23786273e809",
"parameters": {
"public": true,
"options": {
"title": "Book an appointment with Max"
},
"initialMessages": "Hi there! 👋\nI can help you schedule an appointment with Max Tkacz. On which day would you like to meet?"
},
"typeVersion": 1.1
},
{
"id": "52c65975-479d-4c76-bcd3-23f5c9bb6acf",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
9220,
460
],
"parameters": {
"color": 7,
"width": 411.2946586626259,
"height": 80,
"content": "### Explore 100+ AI Workflow templates on n8n.io\n[Open Templates Library](https://n8n.io/workflows)"
},
"typeVersion": 1
},
{
"id": "ba0635c0-2ca4-4b27-b960-3a0e0f93a56a",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
9220,
560
],
"parameters": {
"color": 7,
"width": 411.2946586626259,
"height": 80,
"content": "### Ask a question in our community (13k+ members)\n[Explore n8n community](https://community.n8n.io/)"
},
"typeVersion": 1
},
{
"id": "29227c52-a9cc-4bd1-b1a3-78fb805b659c",
"name": "OpenAI Chat Model1",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
3260,
660
],
"parameters": {
"model": "gpt-4o",
"options": {
"temperature": 0.5
}
},
"typeVersion": 1
},
{
"id": "494a2868-9ff5-402c-b83b-6dd2c3ddbcc9",
"name": "Add automation label",
"type": "n8n-nodes-base.gmail",
"position": [
3760,
300
],
"parameters": {
"labelIds": [
"Label_4763053241338138112"
],
"messageId": "={{ $json.id }}",
"operation": "addLabels"
},
"typeVersion": 2.1
},
{
"id": "0f9d834d-ec47-43f5-945b-8c464d371122",
"name": "On new email to nathan's inbox",
"type": "n8n-nodes-base.gmailTrigger",
"disabled": true,
"position": [
3040,
460
],
"parameters": {
"simple": false,
"filters": {},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "142e2a49-40bd-4bf5-9ba3-f14ecd68618e",
"name": "Add music label",
"type": "n8n-nodes-base.gmail",
"position": [
3760,
500
],
"parameters": {
"labelIds": [
"Label_6822395192337188416"
],
"messageId": "={{ $json.id }}",
"operation": "addLabels"
},
"typeVersion": 2.1
},
{
"id": "2eb46753-a0e8-43ec-a460-466b1dd265c9",
"name": "Assign label with AI",
"type": "@n8n/n8n-nodes-langchain.textClassifier",
"position": [
3280,
460
],
"parameters": {
"options": {},
"inputText": "={{ $json.text }}",
"categories": {
"categories": [
{
"category": "automation",
"description": "email on the topic of automation or workflows and automated processes, includes newsletters on this topic"
},
{
"category": "music",
"description": "email on the topic of music, for example from an artist "
}
]
}
},
"typeVersion": 1
},
{
"id": "576d8206-1b1e-4671-ba45-86e9d844a73b",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
1860,
460
],
"webhookId": "74facfd7-0f51-4605-9724-2c300594fcf9",
"parameters": {
"path": "74facfd7-0f51-4605-9724-2c300594fcf9",
"options": {}
},
"typeVersion": 2
},
{
"id": "1e612376-1a3b-4c48-9cd3-97867ba4cad5",
"name": "Whether email contains n8n",
"type": "n8n-nodes-base.if",
"position": [
2060,
460
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a0b16c44-03ea-4e96-9671-7b168697186d",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.query.email }}",
"rightValue": "@n8n"
}
]
}
},
"typeVersion": 2
}
],
"pinData": {},
"connections": {
"Webhook": {
"main": [
[
{
"node": "Whether email contains n8n",
"type": "main",
"index": 0
}
]
]
},
"Download PDF": {
"main": [
[
{
"node": "Insert into Pinecone vector store",
"type": "main",
"index": 0
}
]
]
},
"Book appointment": {
"ai_tool": [
[
{
"node": "Appointment booking agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"PDFs to download": {
"main": [
[
{
"node": "Download PDF",
"type": "main",
"index": 0
}
]
]
},
"Embeddings OpenAI": {
"ai_embedding": [
[
{
"node": "Insert into Pinecone vector store",
"type": "ai_embedding",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Question and Answer Chain",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Embeddings OpenAI2": {
"ai_embedding": [
[
{
"node": "Read Pinecone Vector Store",
"type": "ai_embedding",
"index": 0
}
]
]
},
"OpenAI Chat Model1": {
"ai_languageModel": [
[
{
"node": "Assign label with AI",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Default Data Loader": {
"ai_document": [
[
{
"node": "Insert into Pinecone vector store",
"type": "ai_document",
"index": 0
}
]
]
},
"Anthropic Chat Model": {
"ai_languageModel": [
[
{
"node": "Appointment booking agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Assign label with AI": {
"main": [
[
{
"node": "Add automation label",
"type": "main",
"index": 0
}
],
[
{
"node": "Add music label",
"type": "main",
"index": 0
}
]
]
},
"Window Buffer Memory": {
"ai_memory": [
[
{
"node": "Appointment booking agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Vector Store Retriever": {
"ai_retriever": [
[
{
"node": "Question and Answer Chain",
"type": "ai_retriever",
"index": 0
}
]
]
},
"Get calendar availability": {
"ai_tool": [
[
{
"node": "Appointment booking agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Read Pinecone Vector Store": {
"ai_vectorStore": [
[
{
"node": "Vector Store Retriever",
"type": "ai_vectorStore",
"index": 0
}
]
]
},
"When chat message received": {
"main": [
[
{
"node": "Question and Answer Chain",
"type": "main",
"index": 0
}
]
]
},
"Whether email contains n8n": {
"main": [
[
{
"node": "Execute JavaScript",
"type": "main",
"index": 0
},
{
"node": "Send message",
"type": "main",
"index": 0
}
]
]
},
"On new email to nathan's inbox": {
"main": [
[
{
"node": "Assign label with AI",
"type": "main",
"index": 0
}
]
]
},
"Recursive Character Text Splitter": {
"ai_textSplitter": [
[
{
"node": "Default Data Loader",
"type": "ai_textSplitter",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,153 @@
{
"nodes": [
{
"name": "SFTP zip file content",
"type": "n8n-nodes-base.ftp",
"position": [
1520,
680
],
"parameters": {
"path": "=zigbee_backups/zigbee_backup_{{ new Date().toISOString().replaceAll(':','_') }}.zip",
"protocol": "sftp",
"operation": "upload"
},
"credentials": {
"sftp": {
"name": "SFTP Zigbee Backups"
}
},
"typeVersion": 1
},
{
"name": "CRON Monday 2:45 am",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
860,
440
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "45 2 * * 1"
}
]
}
},
"typeVersion": 1.1
},
{
"name": "Send Zigbee2MQTT backup request",
"type": "n8n-nodes-base.mqtt",
"position": [
1040,
440
],
"parameters": {
"topic": "zigbee2mqtt/bridge/request/backup",
"message": "getbackup",
"options": {},
"sendInputData": false
},
"credentials": {
"mqtt": {
"name": "MQTT account"
}
},
"typeVersion": 1
},
{
"name": "MQTT Trigger - Backup Response",
"type": "n8n-nodes-base.mqttTrigger",
"position": [
860,
680
],
"parameters": {
"topics": "zigbee2mqtt/bridge/response/backup",
"options": {}
},
"credentials": {
"mqtt": {
"name": "MQTT account"
}
},
"typeVersion": 1
},
{
"name": "Parse JSON Object from Message Text",
"type": "n8n-nodes-base.code",
"position": [
1080,
680
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "\nlet containerObject = JSON.parse($json.message);\nlet messageObject = containerObject.data;\nreturn messageObject;"
},
"typeVersion": 2
},
{
"name": "Convert to File - base64 to binary",
"type": "n8n-nodes-base.convertToFile",
"position": [
1300,
680
],
"parameters": {
"options": {},
"operation": "toBinary",
"sourceProperty": "zip"
},
"typeVersion": 1
}
],
"connections": {
"CRON Monday 2:45 am": {
"main": [
[
{
"node": "Send Zigbee2MQTT backup request",
"type": "main",
"index": 0
}
]
]
},
"MQTT Trigger - Backup Response": {
"main": [
[
{
"node": "Parse JSON Object from Message Text",
"type": "main",
"index": 0
}
]
]
},
"Convert to File - base64 to binary": {
"main": [
[
{
"node": "SFTP zip file content",
"type": "main",
"index": 0
}
]
]
},
"Parse JSON Object from Message Text": {
"main": [
[
{
"node": "Convert to File - base64 to binary",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,411 @@
{
"meta": {
"instanceId": "bb6a1286a4ce98dce786d6c2748b867c1252d53458c87d87fbf6824b862d4c9c"
},
"nodes": [
{
"id": "faade37e-908d-494c-af74-93c8f01adcc5",
"name": "Everyday at 7PM",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
440,
520
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 0 19 * * *"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "4abddfea-fee9-419c-92c4-3055faa2dd09",
"name": "Airtable Get Today's Orders",
"type": "n8n-nodes-base.airtable",
"position": [
900,
520
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appdtUVSpfWswMwNC",
"cachedResultUrl": "https://airtable.com/appdtUVSpfWswMwNC",
"cachedResultName": "Untitled Base"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblu6F5rLbR3Axtgj",
"cachedResultUrl": "https://airtable.com/appdtUVSpfWswMwNC/tblu6F5rLbR3Axtgj",
"cachedResultName": "orders"
},
"options": {},
"operation": "search",
"filterByFormula": "=AND(time < \"{{ $json.now }}\", time > \"{{ $json.yesterday }}\")"
},
"credentials": {
"airtableTokenApi": {
"id": "uSxVhc7fcMM7uPM2",
"name": "Airtable Personal Access Token account"
}
},
"typeVersion": 2.1
},
{
"id": "ea29159e-3674-4385-a0bd-2a9df7d7117c",
"name": "Yesterday Date",
"type": "n8n-nodes-base.code",
"position": [
660,
520
],
"parameters": {
"jsCode": "// Create a new date object for yesterday, 7pm\nconst yesterday = new Date();\nyesterday.setDate( new Date().getDate() - 1); \nyesterday.setHours(19, 0, 0, 0);\nconst isoString = yesterday.toISOString();\nreturn {yesterday:isoString, now:new Date().toISOString()}"
},
"typeVersion": 2
},
{
"id": "8254aa63-2682-4c48-8843-c93830c724de",
"name": "HTML",
"type": "n8n-nodes-base.html",
"position": [
1120,
520
],
"parameters": {
"html": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"UTF-8\" />\n</head>\n<body>\n <table>\n <tr> \n {{ Object.keys($input.first().json).map(propname=>'<td>'+propname+'</td>').join('') \n }}\n </tr>\n \n {{ $input.all().map(order=>{\n \n return \"<tr>\"+Object.values(order.json).map(prop=>{\n return \"<td>\"+prop+\"</td>\"\n }).join('') +\"</tr>\"\n }).join('') \n }}\n </table>\n</body>\n</html>\n\n<style>\n.container {\n background-color: #ffffff;\n text-align: center;\n padding: 16px;\n border-radius: 8px;\n}\n\nh1 {\n color: #ff6d5a;\n font-size: 24px;\n font-weight: bold;\n padding: 8px;\n}\n\nh2 {\n color: #909399;\n font-size: 18px;\n font-weight: bold;\n padding: 8px;\n}\n</style>\n"
},
"executeOnce": true,
"typeVersion": 1.2
},
{
"id": "5e9f6ad7-e4fc-41e3-991b-cae9210dfb71",
"name": "Set Order Fields",
"type": "n8n-nodes-base.set",
"position": [
660,
220
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "2c2f9e3c-696a-466a-8bfe-5c8aa942c9ab",
"name": "time",
"type": "string",
"value": "={{ new Date().toISOString() }}"
},
{
"id": "5618b2a7-8149-469d-87ee-535f1adac121",
"name": "orderID",
"type": "string",
"value": "={{ $json.body.orderID }}"
},
{
"id": "dc31db55-24e4-468f-a9fd-456298f5e5ab",
"name": "orderPrice",
"type": "number",
"value": "={{ $json.body.orderPrice }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "68eaa8f7-3b67-484e-8bad-87e621adc1df",
"name": "Send to Gmail",
"type": "n8n-nodes-base.gmail",
"position": [
1340,
520
],
"parameters": {
"sendTo": "axelrose20272027@gmail.com",
"message": "={{ $json.html }}",
"options": {},
"subject": "Daily Order Summary"
},
"credentials": {
"gmailOAuth2": {
"id": "qMvN3j2E5MFAguNF",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "9f22bedc-fbe1-421b-8212-189c7d436cab",
"name": "Store Order",
"type": "n8n-nodes-base.airtable",
"position": [
900,
220
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appdtUVSpfWswMwNC",
"cachedResultUrl": "https://airtable.com/appdtUVSpfWswMwNC",
"cachedResultName": "Untitled Base"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblu6F5rLbR3Axtgj",
"cachedResultUrl": "https://airtable.com/appdtUVSpfWswMwNC/tblu6F5rLbR3Axtgj",
"cachedResultName": "orders"
},
"columns": {
"value": {
"orderID": 0,
"customerID": 0,
"orderPrice": 0
},
"schema": [
{
"id": "time",
"type": "dateTime",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "time",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "orderID",
"type": "number",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "orderID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "customerID",
"type": "number",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "customerID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "orderPrice",
"type": "number",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "orderPrice",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "orderStatus",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "orderStatus",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "autoMapInputData",
"matchingColumns": []
},
"options": {
"typecast": true
},
"operation": "create"
},
"credentials": {
"airtableTokenApi": {
"id": "uSxVhc7fcMM7uPM2",
"name": "Airtable Personal Access Token account"
}
},
"typeVersion": 2.1
},
{
"id": "6ace0e8f-85e1-45bc-ae81-331c5722ef46",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
340,
160
],
"parameters": {
"width": 857.9236217062975,
"height": 220.18022408852067,
"content": "### New order is sent to the Webhook via POST with params {orderID, orderPrice}"
},
"typeVersion": 1
},
{
"id": "6907ae8d-90b7-4e07-883d-3ebd4440d811",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
340,
460
],
"parameters": {
"width": 1202.2434730902464,
"height": 235.62797364881823,
"content": "### Daily summary sent to email at 7PM"
},
"typeVersion": 1
},
{
"id": "848c6acb-2f9c-4d85-8349-a4a31204922b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-620,
-80
],
"parameters": {
"color": 4,
"width": 607.7708924207209,
"height": 893.1187181589532,
"content": "# Aggregate Daily Orders with Airtable\n### This workflow will collect order data as it is produced, then send a summary email of all orders at the end of every day, formatted in a table.\n\n## Setup:\n 1. Create a new table in Airtable and give it a field *time* with type date, *orderID* with type number, and *orderPrice* also with type number. \n 2. Create a new access token if you haven't already at https://airtable.com/create/tokens/new. Make sure to give the token the scopes *data.records:read*, *data.records:write*, *schema.bases:read* and access to whichever table you choose to store the orders. A pop-up window appears with the token. Use this token to make `Create New Credential` > `Access Token` for Airtable in the `Store Order` and `Airtable Get Today's Orders` nodes.\n 3. Create access credentials for your Gmail as described here: https://developers.google.com/workspace/guides/create-credentials. Use the credentials from your *client_secret.json* in the `Send to Gmail` node.\n 4. In the `Store Order` node, change *Base* and *Table* to the database and table in your Airtable account you wish to use to store orders. Make sure to use these same values in the `Airtable Get Today's Orders` node.\n 5. Every time an order is created in your system, send a POST request to Webhook from your order software. Each request must contain a single order containing fields *'orderID'* and *'orderPrice'* (or, edit `Set Order Fields` to select which incoming fields you wish to save)\n 6. Change the schedule time for sending email from `Everyday at 7PM` to whichever time you choose. \n \n\n## Test:\n- Activate the workflow.\n- From the node `Webhook`, copy *Production URL*\n- Send the following CURL request to the URL given to you:\n` curl -X POST -H \"Content-Type: application/json\" -d '{\"orderID\": 12345, \"orderPrice\": 99.99}' YOUR_URL_HERE`\n- It should say *Node executed successfully*. Now check your Airtable and confirm the order was stored in the right place."
},
"typeVersion": 1
},
{
"id": "d9a5ef05-beba-480f-967e-840cf1b71248",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
200,
240
],
"parameters": {
"color": 3,
"width": 170,
"height": 80,
"content": "- New Order!"
},
"typeVersion": 1
},
{
"id": "0f433e34-79cd-42d0-9b56-4a306eb91907",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
200,
540
],
"parameters": {
"color": 3,
"width": 170,
"height": 80,
"content": " - It's 7PM!"
},
"typeVersion": 1
},
{
"id": "fb9c4b49-ee1f-4233-8277-4c35fb423fde",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
440,
220
],
"webhookId": "e9e62c98-390d-4d16-bc77-a13b043bf1cf",
"parameters": {
"path": "e9e62c98-390d-4d16-bc77-a13b043bf1cf",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2
}
],
"pinData": {},
"connections": {
"HTML": {
"main": [
[
{
"node": "Send to Gmail",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Set Order Fields",
"type": "main",
"index": 0
}
]
]
},
"Yesterday Date": {
"main": [
[
{
"node": "Airtable Get Today's Orders",
"type": "main",
"index": 0
}
]
]
},
"Everyday at 7PM": {
"main": [
[
{
"node": "Yesterday Date",
"type": "main",
"index": 0
}
]
]
},
"Set Order Fields": {
"main": [
[
{
"node": "Store Order",
"type": "main",
"index": 0
}
]
]
},
"Airtable Get Today's Orders": {
"main": [
[
{
"node": "HTML",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,401 @@
{
"meta": {
"instanceId": "408f9fb9940c3cb18ffdef0e0150fe342d6e655c3a9fac21f0f644e8bedabcd9"
},
"nodes": [
{
"id": "0b64edf1-57e0-4704-b78c-c8ab2b91f74d",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [
480,
300
],
"parameters": {},
"typeVersion": 1
},
{
"id": "a875d1c5-ccfe-4bbf-b429-56a42b0ca778",
"name": "Google Gemini Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
1280,
720
],
"parameters": {
"options": {},
"modelName": "models/gemini-1.5-flash"
},
"credentials": {
"googlePalmApi": {
"id": "dSxo6ns5wn658r8N",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "a5e00543-dbaa-4e62-afb0-825ebefae3f3",
"name": "Structured Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
1480,
720
],
"parameters": {
"jsonSchemaExample": "{\n\t\"caption_title\": \"\",\n\t\"caption_text\": \"\"\n}"
},
"typeVersion": 1.2
},
{
"id": "bb9af9c6-6c81-4e92-a29f-18ab3afbe327",
"name": "Get Info",
"type": "n8n-nodes-base.editImage",
"position": [
1100,
400
],
"parameters": {
"operation": "information"
},
"typeVersion": 1
},
{
"id": "8a0dbd5d-5886-484a-80a0-486f349a9856",
"name": "Resize For AI",
"type": "n8n-nodes-base.editImage",
"position": [
1100,
560
],
"parameters": {
"width": 512,
"height": 512,
"options": {},
"operation": "resize"
},
"typeVersion": 1
},
{
"id": "d29f254a-5fa3-46fa-b153-19dfd8e8c6a7",
"name": "Calculate Positioning",
"type": "n8n-nodes-base.code",
"position": [
2020,
720
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const { size, output } = $input.item.json;\n\nconst lineHeight = 35;\nconst fontSize = Math.round(size.height / lineHeight);\nconst maxLineLength = Math.round(size.width/fontSize) * 2;\nconst text = `\"${output.caption_title}\". ${output.caption_text}`;\nconst numLinesOccupied = Math.round(text.length / maxLineLength);\n\nconst verticalPadding = size.height * 0.02;\nconst horizontalPadding = size.width * 0.02;\nconst rectPosX = 0;\nconst rectPosY = size.height - (verticalPadding * 2.5) - (numLinesOccupied * fontSize);\nconst textPosX = horizontalPadding;\nconst textPosY = size.height - (numLinesOccupied * fontSize) - (verticalPadding/2);\n\nreturn {\n caption: {\n fontSize,\n maxLineLength,\n numLinesOccupied,\n rectPosX,\n rectPosY,\n textPosX,\n textPosY,\n verticalPadding,\n horizontalPadding,\n }\n}\n"
},
"typeVersion": 2
},
{
"id": "12a7f2d6-8684-48a5-aa41-40a8a4f98c79",
"name": "Apply Caption to Image",
"type": "n8n-nodes-base.editImage",
"position": [
2380,
560
],
"parameters": {
"options": {},
"operation": "multiStep",
"operations": {
"operations": [
{
"color": "=#0000008c",
"operation": "draw",
"endPositionX": "={{ $json.size.width }}",
"endPositionY": "={{ $json.size.height }}",
"startPositionX": "={{ $json.caption.rectPosX }}",
"startPositionY": "={{ $json.caption.rectPosY }}"
},
{
"font": "/usr/share/fonts/truetype/msttcorefonts/Arial.ttf",
"text": "=\"{{ $json.output.caption_title }}\". {{ $json.output.caption_text }}",
"fontSize": "={{ $json.caption.fontSize }}",
"fontColor": "#FFFFFF",
"operation": "text",
"positionX": "={{ $json.caption.textPosX }}",
"positionY": "={{ $json.caption.textPosY }}",
"lineLength": "={{ $json.caption.maxLineLength }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "4d569ec8-04c2-4d21-96e1-86543b26892d",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-120,
80
],
"parameters": {
"width": 423.75,
"height": 431.76353488372104,
"content": "## Try it out!\n\n### This workflow takes an image and generates a caption for it using AI. The OpenAI node has been able to do this for a while but this workflow demonstrates how to achieve the same with other multimodal vision models such as Google's Gemini.\n\nAdditional, we'll use the Edit Image node to overlay the generated caption onto the image. This can be useful for publications or can be repurposed for copyrights and/or watermarks.\n\n### Need Help?\nJoin the [Discord](https://discord.com/invite/XPKeKXeB7d) or ask in the [Forum](https://community.n8n.io/)!\n"
},
"typeVersion": 1
},
{
"id": "45d37945-5a7a-42eb-8c8c-5940ea276072",
"name": "Merge Image & Caption",
"type": "n8n-nodes-base.merge",
"position": [
1620,
400
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3
},
{
"id": "53a26842-ad56-4c8d-a59d-4f6d3f9e2407",
"name": "Merge Caption & Positions",
"type": "n8n-nodes-base.merge",
"position": [
2200,
560
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3
},
{
"id": "b6c28913-b16a-4c59-aa49-47e9bb97f86d",
"name": "Get Image",
"type": "n8n-nodes-base.httpRequest",
"position": [
680,
300
],
"parameters": {
"url": "https://images.pexels.com/photos/1267338/pexels-photo-1267338.jpeg?auto=compress&cs=tinysrgb&w=600",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "6c25054d-8103-4be9-bea7-6c3dd47f49a3",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
340,
80
],
"parameters": {
"color": 7,
"width": 586.25,
"height": 486.25,
"content": "## 1. Import an Image \n[Read more about the HTTP request node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest)\n\nFor this demonstration, we'll grab an image off Pexels.com - a popular free stock photography site - by using the HTTP request node to download.\n\nIn your own workflows, this can be replaces by other triggers such as webhooks."
},
"typeVersion": 1
},
{
"id": "d1b708e2-31c3-4cd1-a353-678bc33d4022",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
960,
140
],
"parameters": {
"color": 7,
"width": 888.75,
"height": 783.75,
"content": "## 2. Using Vision Model to Generate Caption\n[Learn more about the Basic LLM Chain](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.chainllm)\n\nn8n's basic LLM node supports multimodal input by allowing you to specify either a binary or an image url to send to a compatible LLM. This makes it easy to start utilising this powerful feature for visual classification or OCR tasks which have previously depended on more dedicated OCR models.\n\nHere, we've simply passed our image binary as a \"user message\" option, asking the LLM to help us generate a caption title and text which is appropriate for the given subject. Once generated, we'll pass this text along with the image to combine them both."
},
"typeVersion": 1
},
{
"id": "36a39871-340f-4c44-90e6-74393b9be324",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1880,
280
],
"parameters": {
"color": 7,
"width": 753.75,
"height": 635,
"content": "## 3. Overlay Caption on Image \n[Read more about the Edit Image node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.editimage)\n\nFinally, well perform some basic calculations to place the generated caption onto the image. With n8n's user-friendly image editing features, this can be done entirely within the workflow!\n\nThe Code node tool is ideal for these types of calculations and is used here to position the caption at the bottom of the image. To create the overlay, the Edit Image node enables us to insert text onto the image, which well use to add the generated caption."
},
"typeVersion": 1
},
{
"id": "d175fe97-064e-41da-95fd-b15668c330c4",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
2660,
280
],
"parameters": {
"width": 563.75,
"height": 411.25,
"content": "**FIG 1.** Example input image with AI generated caption\n![Example Output](https://res.cloudinary.com/daglih2g8/image/upload/f_auto,q_auto/v1/n8n-workflows/l5xbb4ze4wyxwwefqmnc#full-width)"
},
"typeVersion": 1
},
{
"id": "23db0c90-45b6-4b85-b017-a52ad5a9ad5b",
"name": "Image Captioning Agent",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
1280,
560
],
"parameters": {
"text": "Generate a caption for this image.",
"messages": {
"messageValues": [
{
"message": "=You role is to provide an appropriate image caption for user provided images.\n\nThe individual components of a caption are as follows: who, when, where, context and miscellaneous. For a really good caption, follow this template: who + when + where + context + miscellaneous\n\nGive the caption a punny title."
},
{
"type": "HumanMessagePromptTemplate",
"messageType": "imageBinary"
}
]
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.4
}
],
"pinData": {},
"connections": {
"Get Info": {
"main": [
[
{
"node": "Merge Image & Caption",
"type": "main",
"index": 0
}
]
]
},
"Get Image": {
"main": [
[
{
"node": "Resize For AI",
"type": "main",
"index": 0
},
{
"node": "Get Info",
"type": "main",
"index": 0
}
]
]
},
"Resize For AI": {
"main": [
[
{
"node": "Image Captioning Agent",
"type": "main",
"index": 0
}
]
]
},
"Calculate Positioning": {
"main": [
[
{
"node": "Merge Caption & Positions",
"type": "main",
"index": 1
}
]
]
},
"Merge Image & Caption": {
"main": [
[
{
"node": "Calculate Positioning",
"type": "main",
"index": 0
},
{
"node": "Merge Caption & Positions",
"type": "main",
"index": 0
}
]
]
},
"Image Captioning Agent": {
"main": [
[
{
"node": "Merge Image & Caption",
"type": "main",
"index": 1
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "Image Captioning Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "Image Captioning Agent",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Merge Caption & Positions": {
"main": [
[
{
"node": "Apply Caption to Image",
"type": "main",
"index": 0
}
]
]
},
"When clicking Test workflow": {
"main": [
[
{
"node": "Get Image",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,506 @@
{
"meta": {
"instanceId": "408f9fb9940c3cb18ffdef0e0150fe342d6e655c3a9fac21f0f644e8bedabcd9"
},
"nodes": [
{
"id": "490493d1-e9ac-458a-ac9e-a86048ce6169",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-700,
260
],
"parameters": {},
"typeVersion": 1
},
{
"id": "116f1137-632f-4021-ad0f-cf59ed1776fd",
"name": "Google Gemini Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
980,
440
],
"parameters": {
"options": {},
"modelName": "models/gemini-1.5-pro-latest"
},
"credentials": {
"googlePalmApi": {
"id": "dSxo6ns5wn658r8N",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "44695b4f-702c-4230-9ec3-e37447fed38e",
"name": "Sort Pages",
"type": "n8n-nodes-base.sort",
"position": [
400,
320
],
"parameters": {
"options": {},
"sortFieldsUi": {
"sortField": [
{
"fieldName": "fileName"
}
]
}
},
"typeVersion": 1
},
{
"id": "f2575b2c-0808-464e-b982-1eed8e0d9df7",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1280,
0
],
"parameters": {
"width": 437.0502325581392,
"height": 430.522325581395,
"content": "## Try Me Out!\n\n### This workflow converts a bank statement to markdown, faithfully capturing the details using the power of Vision Language Models (\"VLMs\"). The resulting markdown can then be parsed again by your standard LLM to extract data such as identifying all deposit table rows in the document.\n\nThis workflow is able to handle both downloaded PDFs as well as scanned PDFs. Be sure to protect sensitive data before running this workflow.\n\n### Need Help?\nJoin the [Discord](https://discord.com/invite/XPKeKXeB7d) or ask in the [Forum](https://community.n8n.io/)!"
},
"typeVersion": 1
},
{
"id": "d62d7b0e-29eb-48a9-a471-4279e663c521",
"name": "Get Bank Statement",
"type": "n8n-nodes-base.googleDrive",
"position": [
-500,
260
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "1wS9U7MQDthj57CvEcqG_Llkr-ek6RqGA"
},
"options": {},
"operation": "download"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "yOwz41gMQclOadgu",
"name": "Google Drive account"
}
},
"typeVersion": 3
},
{
"id": "1329973b-a4e0-4272-9e24-3674bb9d4923",
"name": "Split PDF into Images",
"type": "n8n-nodes-base.httpRequest",
"position": [
-140,
320
],
"parameters": {
"url": "http://stirling-pdf:8080/api/v1/convert/pdf/img",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "multipart-form-data",
"bodyParameters": {
"parameters": [
{
"name": "fileInput",
"parameterType": "formBinaryData",
"inputDataFieldName": "data"
},
{
"name": "imageFormat",
"value": "jpg"
},
{
"name": "singleOrMultiple",
"value": "multiple"
},
{
"name": "dpi",
"value": "300"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "4e263346-9f55-4316-a505-4a54061ccfbb",
"name": "Extract Zip File",
"type": "n8n-nodes-base.compression",
"position": [
40,
320
],
"parameters": {},
"typeVersion": 1.1
},
{
"id": "5e97072f-a7c5-45aa-99d1-3231a9230b53",
"name": "Images To List",
"type": "n8n-nodes-base.code",
"position": [
220,
320
],
"parameters": {
"jsCode": "let results = [];\n\nfor (item of items) {\n for (key of Object.keys(item.binary)) {\n results.push({\n json: {\n fileName: item.binary[key].fileName\n },\n binary: {\n data: item.binary[key],\n }\n });\n }\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "62836c73-4cf7-4225-a45d-0cd62b7e227d",
"name": "Resize Images For AI",
"type": "n8n-nodes-base.editImage",
"position": [
800,
280
],
"parameters": {
"width": 75,
"height": 75,
"options": {},
"operation": "resize",
"resizeOption": "percent"
},
"typeVersion": 1
},
{
"id": "59fc6716-9826-4463-be33-923a8f6f33f1",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-820,
0
],
"parameters": {
"color": 7,
"width": 546.4534883720931,
"height": 478.89348837209275,
"content": "## 1. Download Bank Statement PDF\n[Read more about Google Drive node](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.googledrive)\n\nFor this demonstration, we'll pull an example bank statement off Google Drive however, you can also swap this out for other triggers such as webhook.\n\nYou can use the example bank statement created specifically for this workflow here: https://drive.google.com/file/d/1wS9U7MQDthj57CvEcqG_Llkr-ek6RqGA/view?usp=sharing"
},
"typeVersion": 1
},
{
"id": "8e68a295-ff35-4d28-86bb-c8ea5664b3c6",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-240,
3.173953488372149
],
"parameters": {
"color": 7,
"width": 848.0232558139535,
"height": 533.5469767441862,
"content": "## 2. Split PDF Pages into Seperate Images\n\nCurrently, the vision model we'll be using can't accept raw PDFs so we'll have to convert our PDF to a image in order to use it. To achieve this, we'll use the free [Stirling PDF webservice](https://stirlingpdf.io/) for convenience but if we need data privacy (recommended!), we could self-host our own [Stirling PDF instance](https://github.com/Stirling-Tools/Stirling-PDF/) instead. Alternatively, feel free to swap this service out for one of your own as long as it can convert PDFs into images!\n\nWe will ask the PDF service to return each page of our statement as separate images, which it does so as a zip file. Next steps is to just unzip the file and convert the output as a list of images."
},
"typeVersion": 1
},
{
"id": "5286aa35-9687-4d5b-987c-79322a1ddc84",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
-40
],
"parameters": {
"color": 7,
"width": 775.3441860465115,
"height": 636.0809302325588,
"content": "## 3. Convert PDF Pages to Markdown Using Vision Model\n[Learn more about using the Basic LLM node](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.chainllm)\n\nUnlike traditional OCR, vision models (\"VLMs\") \"transcribe\" what they see so while we shouldn't expect an exact replication of a document, they may perform better making sense of complex document layouts ie. such as with horizontally stacked tables.\n \nIn this demonstration, we can transcribe our bank statement scans to markdown text for the purpose of further processing. With markdown, we can retain tables or columnar data found in the document. We'll employ two optimisations however as a workaround for token and timeout limits (1) we'll only transcribe one page at a time and (2) we'll shrink the pages just a little just enough to speed up processing but not enough to reduce our required resolution."
},
"typeVersion": 1
},
{
"id": "49deef00-4617-4b19-a56f-08fd195dfb82",
"name": "Google Gemini Chat Model1",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
1760,
480
],
"parameters": {
"options": {
"safetySettings": {
"values": [
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "BLOCK_NONE"
}
]
}
},
"modelName": "models/gemini-1.5-pro-latest"
},
"credentials": {
"googlePalmApi": {
"id": "dSxo6ns5wn658r8N",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "8e9c5d1d-d610-4bad-8feb-7ff0d5e1e64f",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1440,
80
],
"parameters": {
"color": 7,
"width": 719.7534883720941,
"height": 574.3134883720929,
"content": "## 4. Extract Key Data Confidently From Statement\n[Read more about the Information Extractor](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.information-extractor)\n\nWith our newly generated transcript, let's pull just the deposit line items from our statement. Processing all pages together as images may have been compute-extensive but as text, this is usually no problem at all for our LLM.\n\nFor our example bank statement PDF, the resulting extraction should be 8 table rows where a value exists in the \"deposits\" column."
},
"typeVersion": 1
},
{
"id": "f849ad3c-69ec-443c-b7cd-ab24e210af73",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-640,
500
],
"parameters": {
"color": 5,
"width": 366.00558139534894,
"height": 125.41023255813957,
"content": "### 💡 About the Example PDF\nScanned PDFs (ie. where each page is a scanned image) are a use-case where extracting PDF text content will not work. Vision models are a great solution as this workflow aims to demonstrate!"
},
"typeVersion": 1
},
{
"id": "be6f529b-8220-4879-bd99-4333b4d764b6",
"name": "Combine All Pages",
"type": "n8n-nodes-base.aggregate",
"position": [
1580,
320
],
"parameters": {
"options": {},
"fieldsToAggregate": {
"fieldToAggregate": [
{
"renameField": true,
"outputFieldName": "pages",
"fieldToAggregate": "text"
}
]
}
},
"typeVersion": 1
},
{
"id": "2b35755c-7bae-4896-b9f9-1e9110209526",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-190.1172093023256,
280
],
"parameters": {
"width": 199.23348837209306,
"height": 374.95069767441856,
"content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n### Privacy Warning!\nThis example uses a public third party service. If your data is senstive, please swap this out for the self-hosted version!"
},
"typeVersion": 1
},
{
"id": "f638ba05-9ae2-447f-82af-eb22d8b9d6f1",
"name": "Extract All Deposit Table Rows",
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"position": [
1760,
320
],
"parameters": {
"text": "= {{ $json.pages.join('---') }}",
"options": {
"systemPromptTemplate": "This statement contains tables with rows showing deposit and withdrawal made to the user's account. Deposits and withdrawals are identified by have the amount in their respective columns. What are the deposits to the account found in this statement?"
},
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"array\",\n \"items\": {\n\t\"type\": \"object\",\n\t\"properties\": {\n \"date\": { \"type\": \"string\" },\n \"description\": { \"type\": \"string\" },\n \"amount\": { \"type\": \"number\" }\n\t}\n }\n}"
},
"typeVersion": 1
},
{
"id": "cf1e8d85-5c92-469d-98af-7bdd5f469167",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
913.9944186046506,
620
],
"parameters": {
"color": 5,
"width": 498.18790697674433,
"height": 130.35162790697677,
"content": "### 💡 Don't use Google?\nFeel free to swap the model out for any state-of-the-art multimodal model which supports image inputs such as GPT4o(-mini) or Claude Sonnet/Opus. Note, I've found Gemini to produce the most accurate and consistent for this example use-case so no guarantees if you switch!"
},
"typeVersion": 1
},
{
"id": "20f33372-a6b6-4f4d-987d-a94c85313fa8",
"name": "Transcribe to Markdown",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
980,
280
],
"parameters": {
"text": "transcribe the image to markdown.",
"messages": {
"messageValues": [
{
"message": "=You help transcribe documents to markdown, keeping faithful to all text printed and visible to the best of your ability. Ensure you capture all headings, subheadings, titles as well as small print.\nFor any tables found with the document, convert them to markdown tables. If table row descriptions overflow into more than 1 row, concatanate and fit them into a single row. If two or more tables are adjacent horizontally, stack the tables vertically instead. There should be a newline after every markdown table.\nFor any graphics, use replace with a description of the image. Images of scanned checks should be converted to the phrase \"<scanned image of check>\"."
},
{
"type": "HumanMessagePromptTemplate",
"messageType": "imageBinary"
}
]
},
"promptType": "define"
},
"typeVersion": 1.4
}
],
"pinData": {},
"connections": {
"Sort Pages": {
"main": [
[
{
"node": "Resize Images For AI",
"type": "main",
"index": 0
}
]
]
},
"Images To List": {
"main": [
[
{
"node": "Sort Pages",
"type": "main",
"index": 0
}
]
]
},
"Extract Zip File": {
"main": [
[
{
"node": "Images To List",
"type": "main",
"index": 0
}
]
]
},
"Combine All Pages": {
"main": [
[
{
"node": "Extract All Deposit Table Rows",
"type": "main",
"index": 0
}
]
]
},
"Get Bank Statement": {
"main": [
[
{
"node": "Split PDF into Images",
"type": "main",
"index": 0
}
]
]
},
"Resize Images For AI": {
"main": [
[
{
"node": "Transcribe to Markdown",
"type": "main",
"index": 0
}
]
]
},
"Split PDF into Images": {
"main": [
[
{
"node": "Extract Zip File",
"type": "main",
"index": 0
}
]
]
},
"Transcribe to Markdown": {
"main": [
[
{
"node": "Combine All Pages",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "Transcribe to Markdown",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Google Gemini Chat Model1": {
"ai_languageModel": [
[
{
"node": "Extract All Deposit Table Rows",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"When clicking Test workflow": {
"main": [
[
{
"node": "Get Bank Statement",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,390 @@
{
"meta": {
"instanceId": "ecec1cfe760b632dcb0132ecf2ac7c047c6f290f3f4a5640e2e2466f0269ccaf"
},
"nodes": [
{
"id": "a30e02b0-b807-4a4c-b2a6-19bacf5f2f8f",
"name": "When clicking \"Test workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"position": [
800,
180
],
"parameters": {},
"typeVersion": 1
},
{
"id": "558afdb5-7311-48f1-9464-01b6933eaffe",
"name": "Get Meta BG",
"type": "n8n-nodes-base.editImage",
"position": [
1300,
60
],
"parameters": {
"operation": "information"
},
"typeVersion": 1
},
{
"id": "66bf1414-725b-40e3-be08-76f02a5d130f",
"name": "Nest Top Meta",
"type": "n8n-nodes-base.set",
"position": [
1480,
320
],
"parameters": {
"options": {
"includeBinary": true
},
"assignments": {
"assignments": [
{
"id": "2fb3fd91-c13d-45ce-a7ec-612319a008fc",
"name": "metaTop",
"type": "object",
"value": "={{ $json }}"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "29e77ce2-15a0-47a8-8b1c-8f457ae435c6",
"name": "Nest Bg Meta",
"type": "n8n-nodes-base.set",
"position": [
1480,
60
],
"parameters": {
"options": {
"includeBinary": true
},
"assignments": {
"assignments": [
{
"id": "2fb3fd91-c13d-45ce-a7ec-612319a008fc",
"name": "metaBg",
"type": "object",
"value": "={{ $json }}"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "dcdf4737-f881-4414-8fdb-1ce334e60093",
"name": "Calculate Center",
"type": "n8n-nodes-base.code",
"position": [
2280,
180
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "\n\n const centerX = ($input.item.json.metaBg.size.width + $input.item.json.metaTop.size.width) / 2;\n const centerY = ($input.item.json.metaBg.size.height + $input.item.json.metaTop.size.height) / 2;\n\n $input.item.json.center = { x: centerX, y: centerY };\n\nreturn $input.item"
},
"typeVersion": 2
},
{
"id": "7b146616-cbc7-4e21-a899-46fdc8e5c914",
"name": "Get Logo for the Watermark",
"type": "n8n-nodes-base.httpRequest",
"position": [
1100,
320
],
"parameters": {
"url": "https://cloud.let-the-work-flow.com/workflow-data/logo-shadow.png",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "7167d1b8-f0c4-4068-b5c8-bb23d5a5a589",
"name": "Get the Image for Background",
"type": "n8n-nodes-base.httpRequest",
"position": [
1100,
60
],
"parameters": {
"url": "https://cloud.let-the-work-flow.com/workflow-data/robot-1.png",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "df6b4e01-76aa-42dd-bf1f-8eb259cd4079",
"name": "Wait for both Images and merge Binary in one Item",
"type": "n8n-nodes-base.merge",
"position": [
1980,
180
],
"parameters": {
"mode": "combine",
"options": {},
"combinationMode": "mergeByPosition"
},
"typeVersion": 2.1
},
{
"id": "d5161149-275c-4e2d-9d55-7f1c18716933",
"name": "Rename Image Binary Top Image",
"type": "n8n-nodes-base.code",
"position": [
1660,
320
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "$input.item.binary.top = $input.item.binary.data;\ndelete $input.item.binary.data;\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "90b0e990-d330-4875-b492-28d52019784d",
"name": "Rename Image Binary Background Image",
"type": "n8n-nodes-base.code",
"position": [
1660,
60
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "$input.item.binary.bg = $input.item.binary.data;\ndelete $input.item.binary.data;\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "a2b3eaa3-61bb-4e91-a225-b6a9b5dd725c",
"name": "Get Meta Top",
"type": "n8n-nodes-base.editImage",
"position": [
1300,
320
],
"parameters": {
"operation": "information"
},
"typeVersion": 1
},
{
"id": "46b4e344-8ea6-4d87-9dc3-c3d80f17a9d5",
"name": "Let \"top\" overlay \"bg\"",
"type": "n8n-nodes-base.editImage",
"position": [
2600,
180
],
"parameters": {
"options": {
"format": "jpeg",
"fileName": "out.png"
},
"operation": "composite",
"positionX": "={{ $json.center.x - $json.metaTop.size.width }}",
"positionY": "={{ $json.center.y - $json.metaTop.size.height }}",
"dataPropertyName": "bg",
"dataPropertyNameComposite": "top"
},
"typeVersion": 1
},
{
"id": "ee7787f1-c717-416c-b076-18200e3109a0",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1020,
-69.74382694102701
],
"parameters": {
"width": 820.7320856852112,
"height": 612.1135700636542,
"content": "## Retrieve the Background Image and fetch Meta from the File\n### Like Sizes, to properly place the \"Top Image\" a.k.a \"Watermark\" a.k.a \"Overlay\" above the \"Background\"-Image"
},
"typeVersion": 1
},
{
"id": "80925b86-42dc-4cf9-8a3b-b8df913d4d8c",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
2180,
60
],
"parameters": {
"width": 296.5141962579569,
"height": 568.2663488290325,
"content": "## Calculate the Position for the \"Top\" Image\n\n\n\n\n\n\n\n\n\n\n\n\n\nWe want to place the \"Top\"-Image it dead-center on the \"Background\"-Image. But the upper-left-corner is the origin for the operation. \n\nYou may adjust it to your needs, to for example adjust the size of your Overlay-Image, or place it in some corner. Adjust the Formular to your needs.\n\n**⚠️ Limitation:** The Image that Overlays the Background-Image has to be <= the size of the background image to work properly."
},
"typeVersion": 1
},
{
"id": "89dafe6a-d49a-43f7-94d2-3c5de5b67c9f",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2520,
360
],
"parameters": {
"color": 4,
"width": 257.68541919015513,
"height": 99.86957475347333,
"content": "### 🖼️ Binary Property *bg* should now be the composite image and be overlayed by *top*"
},
"typeVersion": 1
},
{
"id": "384bd626-fdbb-4073-ad9d-671b4aefe19e",
"name": "Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
301.53703835383794,
-60
],
"parameters": {
"width": 448.40729941128825,
"height": 745.9248098393447,
"content": "## Instructions\n\nThis automation *overlays* a `background` image with another image, making it easy to add watermarks or logos.\n\nYou can use this automation to **watermark** your images by overlaying them with a transparent version of your logo. If you'd like to **place your logo in a specific corner**, feel free to _adjust the position_ of the overlay image in the code node.\n\n### How it Works\n\n1. Both images are downloaded, so we can process binary files (you can modify the source, tho.)\n2. We extract metadata, focusing on the dimensions of each image.\n3. The position of the overlay image is calculated (default: dead center of the background image).\n4. The two images are *composited* together.\n\n### Limitations and Optimization Opportunities\n\n1. The overlay image must be the same size or smaller than the background image for proper alignment.\n2. The overlay image does not automatically scale to match the proportions of the background image.\n\n![Image](https://cloud.let-the-work-flow.com/logo-64.png) \nEnjoy the workflow! ❤️ \n[let the workf low](https://let-the-work-flow.com) — Workflow Automation & Development"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Get Meta BG": {
"main": [
[
{
"node": "Nest Bg Meta",
"type": "main",
"index": 0
}
]
]
},
"Get Meta Top": {
"main": [
[
{
"node": "Nest Top Meta",
"type": "main",
"index": 0
}
]
]
},
"Nest Bg Meta": {
"main": [
[
{
"node": "Rename Image Binary Background Image",
"type": "main",
"index": 0
}
]
]
},
"Nest Top Meta": {
"main": [
[
{
"node": "Rename Image Binary Top Image",
"type": "main",
"index": 0
}
]
]
},
"Calculate Center": {
"main": [
[
{
"node": "Let \"top\" overlay \"bg\"",
"type": "main",
"index": 0
}
]
]
},
"Get Logo for the Watermark": {
"main": [
[
{
"node": "Get Meta Top",
"type": "main",
"index": 0
}
]
]
},
"Get the Image for Background": {
"main": [
[
{
"node": "Get Meta BG",
"type": "main",
"index": 0
}
]
]
},
"Rename Image Binary Top Image": {
"main": [
[
{
"node": "Wait for both Images and merge Binary in one Item",
"type": "main",
"index": 1
}
]
]
},
"When clicking \"Test workflow\"": {
"main": [
[
{
"node": "Get the Image for Background",
"type": "main",
"index": 0
},
{
"node": "Get Logo for the Watermark",
"type": "main",
"index": 0
}
]
]
},
"Rename Image Binary Background Image": {
"main": [
[
{
"node": "Wait for both Images and merge Binary in one Item",
"type": "main",
"index": 0
}
]
]
},
"Wait for both Images and merge Binary in one Item": {
"main": [
[
{
"node": "Calculate Center",
"type": "main",
"index": 0
}
]
]
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,994 @@
{
"nodes": [
{
"id": "1e89a8ad-90cf-4040-b59e-1b4933ea4e69",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1740,
-80
],
"parameters": {
"color": 4,
"width": 982.895112064014,
"height": 248.06218763804304,
"content": "MOVE CURRENT BACKUPS TO OLD FOLDER"
},
"typeVersion": 1
},
{
"id": "f998e295-eafd-420a-9ba9-69571b4ab005",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1740,
500
],
"parameters": {
"width": 980.8812626356395,
"height": 188.38611225559103,
"content": "PURGE BACKUPS OLDER THEN 30 DAYS\n"
},
"typeVersion": 1
},
{
"id": "a94facb5-c0df-4ba4-8620-3427aca24333",
"name": "Move Binary Data",
"type": "n8n-nodes-base.moveBinaryData",
"position": [
2000,
280
],
"parameters": {
"mode": "jsonToBinary",
"options": {
"fileName": "={{ $json.name }}-{{ $json.active === false ? 'inactive' : $json.active === true ? 'active' : 'unknown' }}",
"useRawData": true
}
},
"typeVersion": 1
},
{
"id": "049ac29e-36f2-4a14-9d3a-6fd9c9d8a744",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
260,
-80
],
"parameters": {
"color": 2,
"width": 1003.460056384994,
"height": 755.833854865218,
"content": "## get Google Drive folders"
},
"typeVersion": 1
},
{
"id": "e830c989-815d-4c79-806e-136a82a18d72",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1300,
-80
],
"parameters": {
"color": 6,
"width": 427.1093081837156,
"height": 753.2799109651138,
"content": "## Ignore any other folders other than: n8n_backups\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n (it is important that you created this folder)"
},
"typeVersion": 1
},
{
"id": "4197519c-0cf7-49dc-be45-a5c0ab7598c2",
"name": "IGNORE FILES",
"type": "n8n-nodes-base.filter",
"position": [
1440,
40
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "98415e9e-5354-4223-9107-ef3ade30c2f0",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $node[\"GET CURRENT FOLDER\"].json.name }}",
"rightValue": "n8n_backups"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "d3f6191a-80c6-43dd-923f-e98f9ade02f4",
"name": "Create n8n_backups",
"type": "n8n-nodes-base.googleDrive",
"position": [
1000,
340
],
"parameters": {
"name": "n8n_backups",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {},
"folderId": {
"__rl": true,
"mode": "list",
"value": "root",
"cachedResultName": "/ (Root folder)"
},
"resource": "folder"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "o1CgNemxQmc5Fyzd",
"name": "Google Drive Alejandro Lobato"
}
},
"typeVersion": 3
},
{
"id": "b0ff6563-4ad5-4615-844a-aea766cf0d40",
"name": "Create n8n_old",
"type": "n8n-nodes-base.googleDrive",
"position": [
1000,
500
],
"parameters": {
"name": "n8n_old",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {},
"folderId": {
"__rl": true,
"mode": "list",
"value": "root",
"cachedResultName": "/ (Root folder)"
},
"resource": "folder"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "o1CgNemxQmc5Fyzd",
"name": "Google Drive Alejandro Lobato"
}
},
"typeVersion": 3
},
{
"id": "d22a25ea-e1fd-4434-b050-480760f6ba11",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
300,
540
],
"parameters": {
"color": 6,
"width": 355.73762189847923,
"height": 105.6805438265643,
"content": "## Contact me \n**By Mail**. [Send Mail](mailto:nuntius.creative.hub@gmail.com)"
},
"typeVersion": 1
},
{
"id": "b34e1e76-a8b8-4e0d-921b-1a773192e027",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
900,
220
],
"parameters": {
"color": 5,
"width": 327.6965514381564,
"height": 451.756147757587,
"content": "## Since the folder does not exist, it creates a new one.\nn8n_backups\nn8n_old"
},
"typeVersion": 1
},
{
"id": "f0796631-ecb8-4603-838f-0ac1d1bf0a7b",
"name": "GET CURRENT FOLDER",
"type": "n8n-nodes-base.googleDrive",
"onError": "continueRegularOutput",
"position": [
320,
240
],
"parameters": {
"filter": {
"whatToSearch": "folders"
},
"options": {},
"resource": "fileFolder",
"returnAll": true
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "o1CgNemxQmc5Fyzd",
"name": "Google Drive Alejandro Lobato"
}
},
"executeOnce": true,
"notesInFlow": true,
"retryOnFail": true,
"typeVersion": 3,
"alwaysOutputData": true
},
{
"id": "8afbde8d-ae70-427c-8883-ffd49aea7ba7",
"name": "Code",
"type": "n8n-nodes-base.code",
"position": [
500,
240
],
"parameters": {
"jsCode": "const items = $input.all();\nconst requiredNames = [\"n8n_old\", \"n8n_backups\"];\n\n// Filtrar los nombres de la entrada\nconst folderNames = items.map(item => item.json.name);\n\n// Encontrar los nombres que faltan\nconst missingNames = requiredNames.filter(name => !folderNames.includes(name));\n\nif (missingNames.length === 0) {\n return [{ json: { message: \"ok\" } }];\n} else {\n return [{ json: { message: `Faltan los siguientes: ${missingNames.join(', ')}` } }];\n}\n"
},
"typeVersion": 2
},
{
"id": "2130d3d8-23e4-48d6-b3a0-7eab5971a71d",
"name": "If n8n_old",
"type": "n8n-nodes-base.if",
"position": [
680,
360
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "43bd468e-1018-4b45-9448-c51835ed65bc",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.message }}",
"rightValue": "n8n_old"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "76a4ab52-b260-4a1e-be77-a7246a06b963",
"name": "If1 n8n_backups",
"type": "n8n-nodes-base.if",
"position": [
680,
120
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "43bd468e-1018-4b45-9448-c51835ed65bc",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.message }}",
"rightValue": "n8n_backups"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "0a215059-a7bf-4892-b584-1f037b42a59c",
"name": "GET CURRENT FOLDER CREATES",
"type": "n8n-nodes-base.googleDrive",
"onError": "continueRegularOutput",
"position": [
1100,
40
],
"parameters": {
"filter": {
"whatToSearch": "folders"
},
"options": {},
"resource": "fileFolder",
"returnAll": true
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "o1CgNemxQmc5Fyzd",
"name": "Google Drive Alejandro Lobato"
}
},
"executeOnce": true,
"notesInFlow": true,
"retryOnFail": true,
"typeVersion": 3,
"alwaysOutputData": true
},
{
"id": "653d641c-b56f-4a02-b3bf-990b4f6b99f3",
"name": "Merge mensage",
"type": "n8n-nodes-base.merge",
"position": [
920,
40
],
"parameters": {
"mode": "combine",
"options": {},
"combinationMode": "mergeByPosition"
},
"typeVersion": 2.1
},
{
"id": "ae940b77-107a-4e6f-a635-a69876b342ea",
"name": "GET CURRENT BACKUPS1",
"type": "n8n-nodes-base.googleDrive",
"position": [
1800,
0
],
"parameters": {
"filter": {
"folderId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
}
},
"options": {
"fields": [
"name",
"id"
]
},
"resource": "fileFolder",
"returnAll": true,
"queryString": ".json"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "o1CgNemxQmc5Fyzd",
"name": "Google Drive Alejandro Lobato"
}
},
"typeVersion": 3
},
{
"id": "7caa0190-9bd5-4572-80e3-e3f3b34885a6",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
-40
],
"parameters": {
"color": 7,
"width": 203.08765089203305,
"height": 542.95115693689,
"content": "## Does a folder exist?, if it does not exist it creates it"
},
"typeVersion": 1
},
{
"id": "1a77a0fd-dfdd-456d-adfc-6da34a4ccbab",
"name": "MOVE INTO OLD FOLDER",
"type": "n8n-nodes-base.googleDrive",
"onError": "continueRegularOutput",
"position": [
2480,
-20
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive",
"cachedResultUrl": "https://drive.google.com/drive/my-drive",
"cachedResultName": "My Drive"
},
"folderId": {
"__rl": true,
"mode": "id",
"value": "={{ $('GET CURRENT FOLDER').item.json.id }}"
},
"operation": "move"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "o1CgNemxQmc5Fyzd",
"name": "Google Drive Alejandro Lobato"
}
},
"typeVersion": 3,
"alwaysOutputData": true
},
{
"id": "f9fad351-8e82-49a3-a7da-7a43b0735c34",
"name": "UPLOAD WORKFLOWS",
"type": "n8n-nodes-base.googleDrive",
"position": [
2480,
260
],
"parameters": {
"name": "={{ $('Split In Batches').item.binary.data.fileName }}-{{ $node[\"n8n\"].json[\"updatedAt\"] }}.json\n\n",
"options": {},
"parents": [
"={{ $('IGNORE FILES').item.json.id }}"
],
"binaryData": true,
"authentication": "oAuth2"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "o1CgNemxQmc5Fyzd",
"name": "Google Drive Alejandro Lobato"
}
},
"typeVersion": 1
},
{
"id": "c8496ac4-b767-4fc3-bda3-12c0550763c4",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-180,
-80
],
"parameters": {
"color": 3,
"width": 397.07512898799075,
"height": 759.2757638563562,
"content": "## Description\nThis template creates a nightly backup of all n8n workflows and saves them to a GitHub folder. Each night, the previous night's backups are moved to an “n8n_old” folder and renamed with the corresponding date.\n\nBackups older than a specified age are automatically deleted (this feature is active for 30 days, you can remove it if you don't want the backups to be deleted).\n\n## Prerequisites\n\n- Google Drive account and credentials **Get** from the following link. [Link](https://console.cloud.google.com/apis/credentials/oauthclient/)\n- n8n version 1.67.1 or higher\n- N8n api key **Guide** from the following link. [Link](https://witmovil.com/where-to-create-the-api-key-in-n8n/)\n- A destination folder for backups:\n“n8n_old”\n“n8n_backups”\n(if it doesn't exist, create it)\n\n## Configuration\nUpdate all Google Drive nodes with your credentials.\nEdit the Schedule Trigger node with the desired time to run the backup.\nIf you want to automatically purge old backups.\n\nEdit the “PURGE DAYS” node to specify the age of the backups you want to delete.\nEnable the “PURGE DAYS” node and the 3 subsequent nodes.\nEnable the workflow to run on the specified schedule."
},
"typeVersion": 1
},
{
"id": "4654d857-8436-4922-aa9a-9f00d357e581",
"name": "Item Lists",
"type": "n8n-nodes-base.itemLists",
"position": [
2000,
0
],
"parameters": {
"options": {},
"fieldToSplitOut": "id"
},
"typeVersion": 3
},
{
"id": "9e9cc97d-1eff-40ea-9a1d-896681330b5e",
"name": "Split In Batches2",
"type": "n8n-nodes-base.splitInBatches",
"position": [
2220,
0
],
"parameters": {
"options": {
"reset": false
},
"batchSize": 1
},
"typeVersion": 2
},
{
"id": "1bd963e2-6533-4d71-8790-fa840af822ab",
"name": "Split In Batches",
"type": "n8n-nodes-base.splitInBatches",
"position": [
2220,
280
],
"parameters": {
"options": {
"reset": false
},
"batchSize": 1
},
"typeVersion": 2
},
{
"id": "aa9a5b1c-2c6b-4aff-af66-f15271eed643",
"name": "n8n",
"type": "n8n-nodes-base.n8n",
"position": [
1800,
280
],
"parameters": {
"filters": {},
"returnAll": false,
"requestOptions": {}
},
"credentials": {
"n8nApi": {
"id": "vPlm2YAtWv47eJLp",
"name": "n8n witmovil"
}
},
"typeVersion": 1
},
{
"id": "d6455261-c3af-4f5a-8f7e-0dd57c5306e5",
"name": "LIST OLD BACKUPS",
"type": "n8n-nodes-base.googleDrive",
"position": [
1960,
520
],
"parameters": {
"filter": {
"folderId": {
"__rl": true,
"mode": "list",
"value": "1UcusrWKnbFl3cJYIjaDdp1VCgreg2oeV",
"cachedResultUrl": "https://drive.google.com/drive/folders/1UcusrWKnbFl3cJYIjaDdp1VCgreg2oeV",
"cachedResultName": "n8n_old"
}
},
"options": {
"fields": [
"name",
"id"
]
},
"resource": "fileFolder",
"returnAll": true,
"queryString": ".json"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "o1CgNemxQmc5Fyzd",
"name": "Google Drive Alejandro Lobato"
}
},
"typeVersion": 3
},
{
"id": "aa1878bd-b90e-4f37-bf2e-bb4fd72b4571",
"name": "DELETE OLD BACKUP",
"type": "n8n-nodes-base.googleDrive",
"onError": "continueRegularOutput",
"position": [
2560,
500
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {
"deletePermanently": true
},
"operation": "deleteFile"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "o1CgNemxQmc5Fyzd",
"name": "Google Drive Alejandro Lobato"
}
},
"typeVersion": 3,
"alwaysOutputData": true
},
{
"id": "bde79076-3fb4-4f03-a907-fc492f88a17e",
"name": "Item Lists old",
"type": "n8n-nodes-base.itemLists",
"position": [
2160,
520
],
"parameters": {
"options": {},
"fieldToSplitOut": "id"
},
"typeVersion": 3
},
{
"id": "0bd6da8c-99ed-47ea-bb26-11e08e2f76e1",
"name": "Split In Batches old",
"type": "n8n-nodes-base.splitInBatches",
"position": [
2360,
520
],
"parameters": {
"options": {
"reset": false
},
"batchSize": 1
},
"typeVersion": 2
},
{
"id": "fa6fb3be-baba-4bbe-9900-b0949507d164",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
1320,
380
],
"parameters": {
"color": 3,
"width": 344.2988945561964,
"height": 232.84367238845454,
"content": "## Bug fixes v3:\n* Fixed move section now detects more than 13 files and moves them to n8n_old folder\n* Changed file filtering\n* In the next version \"Split In Batches\" will be changed to \"Loop Over Items\""
},
"typeVersion": 1
},
{
"id": "cf2d27b7-8601-466a-8331-c767b9c0c25a",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1740,
220
],
"parameters": {
"color": 5,
"width": 984.8018228465335,
"height": 267.23574473121596,
"content": "BACKUP ALL CURRENT WORKFLOWS limit 100 (Change)"
},
"typeVersion": 1
},
{
"id": "484b37a9-8b21-4887-9443-bcb8ca34b57d",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
320,
20
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.1
},
{
"id": "40a6f21f-f044-4bb5-8d01-1fbdc4185eae",
"name": "Schedule Trigger1",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
1760,
560
],
"parameters": {
"rule": {
"interval": [
{
"daysInterval": 30
}
]
}
},
"typeVersion": 1.1
}
],
"pinData": {},
"connections": {
"n8n": {
"main": [
[
{
"node": "Move Binary Data",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "If n8n_old",
"type": "main",
"index": 0
},
{
"node": "If1 n8n_backups",
"type": "main",
"index": 0
}
]
]
},
"If n8n_old": {
"main": [
[
{
"node": "Create n8n_old",
"type": "main",
"index": 0
}
],
[
{
"node": "Merge mensage",
"type": "main",
"index": 1
}
]
]
},
"Item Lists": {
"main": [
[
{
"node": "Split In Batches2",
"type": "main",
"index": 0
}
]
]
},
"IGNORE FILES": {
"main": [
[
{
"node": "GET CURRENT BACKUPS1",
"type": "main",
"index": 0
},
{
"node": "n8n",
"type": "main",
"index": 0
}
]
]
},
"Merge mensage": {
"main": [
[
{
"node": "GET CURRENT FOLDER CREATES",
"type": "main",
"index": 0
}
]
]
},
"Create n8n_old": {
"main": [
[
{
"node": "GET CURRENT FOLDER",
"type": "main",
"index": 0
}
]
]
},
"Item Lists old": {
"main": [
[
{
"node": "Split In Batches old",
"type": "main",
"index": 0
}
]
]
},
"If1 n8n_backups": {
"main": [
[
{
"node": "Create n8n_backups",
"type": "main",
"index": 0
}
],
[
{
"node": "Merge mensage",
"type": "main",
"index": 0
}
]
]
},
"LIST OLD BACKUPS": {
"main": [
[
{
"node": "Item Lists old",
"type": "main",
"index": 0
}
]
]
},
"Move Binary Data": {
"main": [
[
{
"node": "Split In Batches",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "GET CURRENT FOLDER",
"type": "main",
"index": 0
}
]
]
},
"Split In Batches": {
"main": [
[
{
"node": "UPLOAD WORKFLOWS",
"type": "main",
"index": 0
}
]
]
},
"UPLOAD WORKFLOWS": {
"main": [
[
{
"node": "Split In Batches",
"type": "main",
"index": 0
}
]
]
},
"DELETE OLD BACKUP": {
"main": [
[
{
"node": "Split In Batches old",
"type": "main",
"index": 0
}
]
]
},
"Split In Batches2": {
"main": [
[
{
"node": "MOVE INTO OLD FOLDER",
"type": "main",
"index": 0
}
]
]
},
"Create n8n_backups": {
"main": [
[
{
"node": "GET CURRENT FOLDER",
"type": "main",
"index": 0
}
]
]
},
"GET CURRENT FOLDER": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"GET CURRENT BACKUPS1": {
"main": [
[
{
"node": "Item Lists",
"type": "main",
"index": 0
}
]
]
},
"MOVE INTO OLD FOLDER": {
"main": [
[
{
"node": "Split In Batches2",
"type": "main",
"index": 0
}
]
]
},
"Split In Batches old": {
"main": [
[
{
"node": "DELETE OLD BACKUP",
"type": "main",
"index": 0
}
]
]
},
"GET CURRENT FOLDER CREATES": {
"main": [
[
{
"node": "IGNORE FILES",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,469 @@
{
"name": "Backup workflows to git repository",
"nodes": [
{
"id": "b09ae4c6-ad75-4b3b-a78a-4cc2d48b2d24",
"name": "GitHub",
"type": "n8n-nodes-base.github",
"position": [
-40,
-20
],
"parameters": {
"owner": "={{$node[\"Globals\"].json[\"repo\"][\"owner\"]}}",
"filePath": "={{$node[\"Globals\"].json[\"repo\"][\"path\"]}}{{$json[\"name\"]}}.json",
"resource": "file",
"operation": "get",
"repository": "={{$node[\"Globals\"].json[\"repo\"][\"name\"]}}",
"asBinaryProperty": false,
"additionalParameters": {}
},
"credentials": {
"githubApi": {
"id": "lSdxakI6ik5M2np2",
"name": "Shashikanth | GitHub account"
}
},
"typeVersion": 1,
"continueOnFail": true,
"alwaysOutputData": true
},
{
"id": "639582ef-f13e-4844-bd10-647718079121",
"name": "Globals",
"type": "n8n-nodes-base.set",
"position": [
-740,
-100
],
"parameters": {
"values": {
"string": [
{
"name": "repo.owner",
"value": "shashikanth171"
},
{
"name": "repo.name",
"value": "n8n-backup"
},
{
"name": "repo.path",
"value": "workflows/"
}
]
},
"options": {}
},
"typeVersion": 1
},
{
"id": "9df89713-220e-43b9-b234-b8f5612629cf",
"name": "n8n",
"type": "n8n-nodes-base.n8n",
"position": [
-500,
-100
],
"parameters": {
"filters": {},
"requestOptions": {}
},
"credentials": {
"n8nApi": {
"id": "RgwFr3HsPUEjFJNO",
"name": "n8n account"
}
},
"typeVersion": 1
},
{
"id": "43a60315-d381-4ac4-be4c-f6a158651a00",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-280,
-100
],
"parameters": {
"options": {}
},
"executeOnce": false,
"typeVersion": 3
},
{
"id": "41a7da89-1c8c-4100-8c30-d0788962efc1",
"name": "If",
"type": "n8n-nodes-base.if",
"position": [
160,
-20
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "16a9182d-059d-4774-ba95-654fb4293fdb",
"operator": {
"type": "string",
"operation": "notExists",
"singleValue": true
},
"leftValue": "={{ $json.error }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "ab9246eb-a253-4d76-b33b-5f8f12342542",
"name": "If1",
"type": "n8n-nodes-base.if",
"position": [
1040,
260
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "e0c66624-429a-4f1f-bf7b-1cc1b32bad7b",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $json.content }}",
"rightValue": "={{ $('Loop Over Items').item.json.toJsonString() }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "72e4a5a4-6dfe-4b5c-b57b-7c1c9625e967",
"name": "Code",
"type": "n8n-nodes-base.code",
"position": [
720,
-40
],
"parameters": {
"jsCode": "let items = $input.all()\n\nfor (item of items) {\n item.json.content = Buffer.from(item.json.content, 'base64').toString('utf8')\n}\n\nreturn items;\n"
},
"typeVersion": 2
},
{
"id": "68f14ac5-14d6-432e-9e6b-25df610eadac",
"name": "Create new file and commit",
"type": "n8n-nodes-base.github",
"position": [
340,
140
],
"parameters": {
"owner": "={{$node[\"Globals\"].json[\"repo\"][\"owner\"]}}",
"filePath": "={{$node[\"Globals\"].json[\"repo\"][\"path\"]}}{{ $('Loop Over Items').item.json.name }}.json",
"resource": "file",
"repository": "={{$node[\"Globals\"].json[\"repo\"][\"name\"]}}",
"fileContent": "={{ $('Loop Over Items').item.json.toJsonString() }}",
"commitMessage": "=[N8N Backup] {{ $('Loop Over Items').item.json.name }}.json"
},
"credentials": {
"githubApi": {
"id": "lSdxakI6ik5M2np2",
"name": "Shashikanth | GitHub account"
}
},
"typeVersion": 1
},
{
"id": "e50f00a3-292c-4285-b767-8d6ee4606575",
"name": "Update file content and commit",
"type": "n8n-nodes-base.github",
"position": [
1400,
460
],
"parameters": {
"owner": "={{$node[\"Globals\"].json[\"repo\"][\"owner\"]}}",
"filePath": "={{$node[\"Globals\"].json[\"repo\"][\"path\"]}}{{ $('Loop Over Items').item.json.name }}.json",
"resource": "file",
"operation": "edit",
"repository": "={{$node[\"Globals\"].json[\"repo\"][\"name\"]}}",
"fileContent": "={{ $('Loop Over Items').item.json.toJsonString() }}",
"commitMessage": "=[N8N Backup] {{ $('Loop Over Items').item.json.name }}.json"
},
"credentials": {
"githubApi": {
"id": "lSdxakI6ik5M2np2",
"name": "Shashikanth | GitHub account"
}
},
"typeVersion": 1
},
{
"id": "4b2d375c-a339-404c-babd-555bd2fc4091",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-960,
-100
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "ea026e96-0db1-41fd-b003-2f2bf4662696",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1560,
480
],
"parameters": {
"height": 80,
"content": "Workflow changes committed to the repository"
},
"typeVersion": 1
},
{
"id": "9c402daa-6d03-485d-b8a0-58f1b65d396d",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1180,
260
],
"parameters": {
"height": 80,
"content": "Check if there are any changes in the workflow"
},
"typeVersion": 1
},
{
"id": "1d9216d9-bf8d-4945-8a58-22fb1ffc9be8",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
460,
160
],
"parameters": {
"height": 80,
"content": "Create a new file for the workflow"
},
"typeVersion": 1
},
{
"id": "60a3953b-d9f1-4afd-b299-e314116b96c6",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
-120
],
"parameters": {
"height": 80,
"content": "Check if file exists in the repository"
},
"typeVersion": 1
},
{
"id": "6df689fb-cb49-4634-9d1e-59648a1e7219",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
660,
-140
],
"parameters": {
"height": 80,
"content": "Convert the file contents to JSON string"
},
"typeVersion": 1
},
{
"id": "f2340ad0-71a1-4c74-8d90-bcb974b8b305",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-560,
-200
],
"parameters": {
"height": 80,
"content": "Get all workflows"
},
"typeVersion": 1
},
{
"id": "617bea19-341a-4e9d-b6fd-6b417e58d756",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-820,
40
],
"parameters": {
"height": 80,
"content": "Set variables"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"If": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
],
[
{
"node": "Create new file and commit",
"type": "main",
"index": 0
}
]
]
},
"If1": {
"main": [
[
{
"node": "Update file content and commit",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"n8n": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "If1",
"type": "main",
"index": 0
}
]
]
},
"GitHub": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"Globals": {
"main": [
[
{
"node": "n8n",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "GitHub",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Globals",
"type": "main",
"index": 0
}
]
]
},
"Create new file and commit": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Update file content and commit": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,244 @@
{
"nodes": [
{
"id": "517fad39-50ec-4eae-94c4-aca5b111a093",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-120,
-100
],
"webhookId": "a227afae-e16e-44c2-bb5c-e69fe553b455",
"parameters": {
"path": "a227afae-e16e-44c2-bb5c-e69fe553b455",
"options": {}
},
"typeVersion": 2
},
{
"id": "cbd978df-9b95-4148-a054-7772213f5b8f",
"name": "continue with valid token",
"type": "n8n-nodes-base.noOp",
"position": [
1020,
-40
],
"parameters": {},
"typeVersion": 1
},
{
"id": "65167cf9-3ec5-4727-a604-a318e86bb54e",
"name": "get new accessToken",
"type": "n8n-nodes-base.httpRequest",
"position": [
560,
80
],
"parameters": {
"url": "http://your-api.com",
"options": {
"response": {
"response": {
"fullResponse": true
}
}
}
},
"notesInFlow": false,
"typeVersion": 4.2
},
{
"id": "b17e01d2-c43a-486f-ab08-d81e05f8d110",
"name": "2. set token in static data",
"type": "n8n-nodes-base.code",
"position": [
780,
80
],
"parameters": {
"jsCode": "const workflowStaticData = $getWorkflowStaticData('global');\n\n// get new access token\nworkflowStaticData.accessToken = $input.first().json.AccessToken;\n// set timestamp of new access token\nworkflowStaticData.timestamp = $now.toISO().toString();\n\nreturn [\n {\n // data: $input.all(),\n accessToken: workflowStaticData.accessToken,\n timestamp: workflowStaticData.timestamp,\n // today: $today\n }\n];"
},
"typeVersion": 2
},
{
"id": "31fd494a-f323-47cc-8f89-0bb2f2332e0f",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-120,
60
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.2
},
{
"id": "77623419-99f9-4369-9546-375eaf6f5732",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-180,
240
],
"parameters": {
"width": 660,
"height": 400,
"content": "# StaticData Demo\n\n\nThis workflow demonstrates how to use the [`workflowStaticData()` function](https://docs.n8n.io/code/cookbook/builtin/get-workflow-static-data/\n) to set any type of variable that will persist within workflow executions. \n\nThis can be useful for working with access tokens that expire after a certain time period. \n\nhttps://docs.n8n.io/code/cookbook/builtin/get-workflow-static-data/\n\n## Important\n\nStatic Data only persists across **_production_** executions, i.e. triggered by Webhooks or Schedule Triggers (not manual executions!)\nFor this the workflow will have to be activated. \n\n\n\n"
},
"typeVersion": 1
},
{
"id": "e4cbdbf7-7b3d-4c52-9d41-bc427d63df5d",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
520,
60
],
"parameters": {
"color": 5,
"width": 180,
"height": 420,
"content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n### HTTP Request\n\nToggle \n`Include Response Headers and Status` \noption if access token is not sent in the body"
},
"typeVersion": 1
},
{
"id": "bed68570-bf35-4fa9-984c-1b67a53b59ba",
"name": "if token is valid",
"type": "n8n-nodes-base.if",
"notes": "(1 minute expiration)",
"position": [
340,
-20
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "65f5c979-3e7d-4e50-92c3-3ae39f1bba3d",
"operator": {
"type": "dateTime",
"operation": "afterOrEquals"
},
"leftValue": "={{ $json.timestamp }}",
"rightValue": "={{ $now.minus(1,'minute') }}"
}
]
},
"looseTypeValidation": true
},
"notesInFlow": true,
"typeVersion": 2.2
},
{
"id": "57a4f5f9-eb77-4fd4-b6b1-55137f108374",
"name": "1. initiate static data",
"type": "n8n-nodes-base.code",
"position": [
120,
-20
],
"parameters": {
"jsCode": "// initialize staticData object\nconst workflowStaticData = $getWorkflowStaticData('global');\n\n// initialize accessToken on staticData if it desn't exist yet\nif (!workflowStaticData.hasOwnProperty('accessToken')) {\n workflowStaticData.accessToken = 0\n}\n\n// initializing any other variables on the staticData object\nif (!workflowStaticData.hasOwnProperty('timestamp')) {\n workflowStaticData.timestamp = $now.toISO()\n}\n\nreturn [\n {\n // data: $input.all(),\n accessToken: workflowStaticData.accessToken,\n timestamp: workflowStaticData.timestamp,\n // today: $today\n }\n];"
},
"notesInFlow": false,
"typeVersion": 2
}
],
"pinData": {
"get new accessToken": [
{
"AccessToken": "5763273631"
}
]
},
"connections": {
"Webhook": {
"main": [
[
{
"node": "1. initiate static data",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "1. initiate static data",
"type": "main",
"index": 0
}
]
]
},
"if token is valid": {
"main": [
[
{
"node": "continue with valid token",
"type": "main",
"index": 0
}
],
[
{
"node": "get new accessToken",
"type": "main",
"index": 0
}
]
]
},
"get new accessToken": {
"main": [
[
{
"node": "2. set token in static data",
"type": "main",
"index": 0
}
]
]
},
"1. initiate static data": {
"main": [
[
{
"node": "if token is valid",
"type": "main",
"index": 0
}
]
]
},
"2. set token in static data": {
"main": [
[
{
"node": "continue with valid token",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,279 @@
{
"nodes": [
{
"id": "396bb28b-e40d-4bea-aa80-4abd04db045a",
"name": "Friday 8pm",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
100,
120
],
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtDay": [
5
],
"triggerAtHour": 20
}
]
}
},
"typeVersion": 1.1
},
{
"id": "993f0d31-5639-4cea-b2f8-d1a41ecdeb83",
"name": "Create Meal Plan",
"type": "n8n-nodes-base.httpRequest",
"position": [
1080,
120
],
"parameters": {
"url": "={{ $('Config').first().json.mealieBaseUrl }}/api/households/mealplans",
"method": "POST",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
},
"jsonBody": "={{ $json }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"id": "oVwF1hVdy3Srvi9P",
"name": "Mealie Header Auth"
}
},
"typeVersion": 4.1
},
{
"id": "ad53512d-7246-49f4-a86b-f258b7c1c47e",
"name": "When clicking \"Test workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"position": [
100,
320
],
"parameters": {},
"typeVersion": 1
},
{
"id": "c0d1d7e0-9411-4e6a-871a-0374b8a9f5db",
"name": "Get Recipes",
"type": "n8n-nodes-base.httpRequest",
"position": [
640,
120
],
"parameters": {
"url": "={{ $json.mealieBaseUrl }}/api/recipes",
"options": {},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "perPage",
"value": "100"
},
{
"name": "categories",
"value": "={{ $json.mealieCategoryId }}"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"id": "oVwF1hVdy3Srvi9P",
"name": "Mealie Header Auth"
}
},
"typeVersion": 4.1
},
{
"id": "2f9757fc-77f5-4bda-ae2e-7088ea5c114d",
"name": "Config",
"type": "n8n-nodes-base.set",
"position": [
380,
120
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "cd2665dd-b505-41e4-936d-cfa2de7bd09b",
"name": "numberOfRecipes",
"type": "number",
"value": 5
},
{
"id": "e09da5c5-3f0d-4cd3-909d-e3df2888abde",
"name": "offsetPlanDays",
"type": "number",
"value": 3
},
{
"id": "80e95139-83df-45ae-99a0-fc50d3e9475f",
"name": "mealieCategoryId",
"type": "string",
"value": "6ec172b7-a87d-4877-8fe3-34cecc20f2c5"
},
{
"id": "f511e874-c373-4648-9e49-120367474d6d",
"name": "mealieBaseUrl",
"type": "string",
"value": "http://192.168.1.5:9925"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "fed805ea-0580-444d-8312-a68b25e91bbd",
"name": "Generate Random Items",
"type": "n8n-nodes-base.code",
"position": [
860,
120
],
"parameters": {
"jsCode": "const numberOfRecipes = $('Config').first().json.numberOfRecipes;\nconst offsetPlanDays = $('Config').first().json.offsetPlanDays;\nconst items = $input.first().json.items;\n\nlet planFirstDate = new Date();\nplanFirstDate.setDate(planFirstDate.getDate() + offsetPlanDays);\n\nconst recipeList = [];\nconst randomNums = [];\nlet currentItem = 0;\n\nwhile (recipeList.length < numberOfRecipes) {\n const randomNum = Math.floor(Math.random() * Math.floor(items.length));\n\n if (!randomNums.includes(randomNum)) {\n const thisRecipe = items[randomNum];\n\n const newDate = new Date(planFirstDate);\n newDate.setDate(planFirstDate.getDate() + currentItem);\n \n const planDate = [\n newDate.getFullYear(),\n ('0' + (newDate.getMonth() + 1)).slice(-2),\n ('0' + newDate.getDate()).slice(-2)\n ].join('-');\n \n const planDay = {\n \"date\": planDate,\n \"entryType\": \"dinner\",\n \"recipeId\": thisRecipe.id,\n \"name\": thisRecipe.name\n };\n\n currentItem++;\n recipeList.push(planDay);\n randomNums.push(randomNum);\n }\n}\n\nreturn recipeList;"
},
"typeVersion": 2
},
{
"id": "f440ce9d-cc27-4982-a0bd-b0ce2e5217d9",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
40,
-60
],
"parameters": {
"color": 4,
"height": 340,
"content": "## Trigger\nSet the trigger to run when you like"
},
"typeVersion": 1
},
{
"id": "2bac2f08-2969-4f47-9fce-0e7de416cd09",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
280,
-60
],
"parameters": {
"color": 5,
"width": 300,
"height": 340,
"content": "## Update this Config\nSet the base Url of your Mealie instance\nSet number of recipes to generate and number of days to offset the plan (0 will start today).\nGrab a category id from Mealie (or leave blank for all categories)"
},
"typeVersion": 1
},
{
"id": "a2850e39-c25f-4210-8f9e-a657c0c63bf5",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
40,
-280
],
"parameters": {
"width": 540,
"height": 220,
"content": "## Get started\n* Set up a credential for your Mealie API token\n* Apply the credential to the 2 Http request nodes\n* Set schedule trigger and desired config"
},
"typeVersion": 1
},
{
"id": "20d7301c-8946-45c3-8f5f-fbe2fc80cf37",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
580,
-60
],
"parameters": {
"color": 7,
"width": 660,
"height": 340,
"content": "## Workflow logic\n* Get all recipes from Mealie (within category if supplied)\n* Randomly pick out the number set in the config\n* Create dinner meal plans for the upcoming days"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Config": {
"main": [
[
{
"node": "Get Recipes",
"type": "main",
"index": 0
}
]
]
},
"Friday 8pm": {
"main": [
[
{
"node": "Config",
"type": "main",
"index": 0
}
]
]
},
"Get Recipes": {
"main": [
[
{
"node": "Generate Random Items",
"type": "main",
"index": 0
}
]
]
},
"Generate Random Items": {
"main": [
[
{
"node": "Create Meal Plan",
"type": "main",
"index": 0
}
]
]
},
"When clicking \"Test workflow\"": {
"main": [
[
{
"node": "Config",
"type": "main",
"index": 0
}
]
]
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,367 @@
{
"nodes": [
{
"id": "bae5d407-9210-4bd0-99a3-3637ee893065",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-1440,
-280
],
"parameters": {},
"typeVersion": 1
},
{
"id": "c5a14c8e-4aeb-4a4e-b202-f88e837b6efb",
"name": "Get Variables",
"type": "n8n-nodes-base.set",
"position": [
-200,
-180
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "b455afe0-2311-4d3f-8751-269624d76cf1",
"name": "coords",
"type": "array",
"value": "={{ $json.candidates[0].content.parts[0].text.parseJson() }}"
},
{
"id": "92f09465-9a0b-443c-aa72-6d208e4df39c",
"name": "width",
"type": "string",
"value": "={{ $('Get Image Info').item.json.size.width }}"
},
{
"id": "da98ce2a-4600-46a6-b4cb-159ea515cb50",
"name": "height",
"type": "string",
"value": "={{ $('Get Image Info').item.json.size.height }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "f24017c9-05bc-4f75-a18c-29efe99bfe0e",
"name": "Get Test Image",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1260,
-280
],
"parameters": {
"url": "https://www.stonhambarns.co.uk/wp-content/uploads/jennys-ark-petting-zoo-for-website-6.jpg",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "c0f6a9f7-ba65-48a3-8752-ce5d80fe33cf",
"name": "Gemini 2.0 Object Detection",
"type": "n8n-nodes-base.httpRequest",
"position": [
-680,
-180
],
"parameters": {
"url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent",
"method": "POST",
"options": {},
"jsonBody": "={{\n{\n \"contents\": [{\n \"parts\":[\n {\"text\": \"I want to see all bounding boxes of rabbits in this image.\"},\n {\n \"inline_data\": {\n \"mime_type\":\"image/jpeg\",\n \"data\": $input.item.binary.data.data\n }\n }\n ]\n }],\n \"generationConfig\": {\n \"response_mime_type\": \"application/json\",\n \"response_schema\": {\n \"type\": \"ARRAY\",\n \"items\": {\n \"type\": \"OBJECT\",\n \"properties\": {\n \"box_2d\": {\"type\":\"ARRAY\", \"items\": { \"type\": \"NUMBER\" } },\n \"label\": { \"type\": \"STRING\"}\n }\n }\n }\n }\n}\n}}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googlePalmApi"
},
"credentials": {
"googlePalmApi": {
"id": "dSxo6ns5wn658r8N",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 4.2
},
{
"id": "edbc1152-4642-4656-9a3a-308dae42bac6",
"name": "Scale Normalised Coords",
"type": "n8n-nodes-base.code",
"position": [
-20,
-180
],
"parameters": {
"jsCode": "const { coords, width, height } = $input.first().json;\n\nconst scale = 1000;\nconst scaleCoordX = (val) => (val * width) / scale;\nconst scaleCoordY = (val) => (val * height) / scale;\n \nconst normalisedOutput = coords\n .filter(coord => coord.box_2d.length === 4)\n .map(coord => {\n return {\n xmin: coord.box_2d[1] ? scaleCoordX(coord.box_2d[1]) : coord.box_2d[1],\n xmax: coord.box_2d[3] ? scaleCoordX(coord.box_2d[3]) : coord.box_2d[3],\n ymin: coord.box_2d[0] ? scaleCoordY(coord.box_2d[0]) : coord.box_2d[0],\n ymax: coord.box_2d[2] ? scaleCoordY(coord.box_2d[2]) : coord.box_2d[2],\n }\n });\n\nreturn {\n json: {\n coords: normalisedOutput\n },\n binary: $('Get Test Image').first().binary\n}"
},
"typeVersion": 2
},
{
"id": "e0380611-ac7d-48d8-8eeb-35de35dbe56a",
"name": "Draw Bounding Boxes",
"type": "n8n-nodes-base.editImage",
"position": [
400,
-180
],
"parameters": {
"options": {},
"operation": "multiStep",
"operations": {
"operations": [
{
"color": "#ff00f277",
"operation": "draw",
"endPositionX": "={{ $json.coords[0].xmax }}",
"endPositionY": "={{ $json.coords[0].ymax }}",
"startPositionX": "={{ $json.coords[0].xmin }}",
"startPositionY": "={{ $json.coords[0].ymin }}"
},
{
"color": "#ff00f277",
"operation": "draw",
"endPositionX": "={{ $json.coords[1].xmax }}",
"endPositionY": "={{ $json.coords[1].ymax }}",
"startPositionX": "={{ $json.coords[1].xmin }}",
"startPositionY": "={{ $json.coords[1].ymin }}"
},
{
"color": "#ff00f277",
"operation": "draw",
"endPositionX": "={{ $json.coords[2].xmax }}",
"endPositionY": "={{ $json.coords[2].ymax }}",
"startPositionX": "={{ $json.coords[2].xmin }}",
"startPositionY": "={{ $json.coords[2].ymin }}"
},
{
"color": "#ff00f277",
"operation": "draw",
"endPositionX": "={{ $json.coords[3].xmax }}",
"endPositionY": "={{ $json.coords[3].ymax }}",
"startPositionX": "={{ $json.coords[3].xmin }}",
"startPositionY": "={{ $json.coords[3].ymin }}"
},
{
"color": "#ff00f277",
"operation": "draw",
"endPositionX": "={{ $json.coords[4].xmax }}",
"endPositionY": "={{ $json.coords[4].ymax }}",
"startPositionX": "={{ $json.coords[4].xmin }}",
"startPositionY": "={{ $json.coords[4].ymin }}"
},
{
"color": "#ff00f277",
"operation": "draw",
"cornerRadius": "=0",
"endPositionX": "={{ $json.coords[5].xmax }}",
"endPositionY": "={{ $json.coords[5].ymax }}",
"startPositionX": "={{ $json.coords[5].xmin }}",
"startPositionY": "={{ $json.coords[5].ymin }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "52daac1b-5ba3-4302-b47b-df3f410b40fc",
"name": "Get Image Info",
"type": "n8n-nodes-base.editImage",
"position": [
-1080,
-280
],
"parameters": {
"operation": "information"
},
"typeVersion": 1
},
{
"id": "0d2ab96a-3323-472d-82ff-2af5e7d815a1",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
740,
-460
],
"parameters": {
"width": 440,
"height": 380,
"content": "Fig 1. Output of Object Detection\n![](https://res.cloudinary.com/daglih2g8/image/upload/f_auto,q_auto/v1/n8n-workflows/download_1_qmqyyo#full-width)"
},
"typeVersion": 1
},
{
"id": "c1806400-57da-4ef2-a50d-6ed211d5df29",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1520,
-480
],
"parameters": {
"color": 7,
"width": 600,
"height": 420,
"content": "## 1. Download Test Image\n[Read more about the HTTP node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest)\n\nAny compatible image will do ([see docs](https://ai.google.dev/gemini-api/docs/vision?lang=rest#technical-details-image)) but best if it isn't too busy or the subjects too obscure. Most importantly, you are able to retrieve the width and height as this is required for a later step."
},
"typeVersion": 1
},
{
"id": "3ae12a7c-a20f-4087-868e-b118cc09fa9a",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-900,
-480
],
"parameters": {
"color": 7,
"width": 560,
"height": 540,
"content": "## 2. Use Prompt-Based Object Detection\n[Read more about the HTTP node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest)\n\nWe've had generalised object detection before ([see my other template using ResNet](https://n8n.io/workflows/2331-build-your-own-image-search-using-ai-object-detection-cdn-and-elasticsearch/)) but being able to prompt for what you're looking for is a very exciting proposition! Not only could this reduce the effort in post-detection filtering but also introduce contextual use-cases such as searching by \"emotion\", \"locality\", \"anomolies\" and many more!\n\nI found the the output json schema of `{ \"box_2d\": { \"type\": \"array\", ... } }` works best for Gemini to return coordinates. "
},
"typeVersion": 1
},
{
"id": "35673272-7207-41d1-985e-08032355846e",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-320,
-400
],
"parameters": {
"color": 7,
"width": 520,
"height": 440,
"content": "## 3. Scale Coords to Fit Original Image\n[Read more about the Code node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/)\n\nAccording to the Gemini 2.0 overview on [how it calculates bounding boxes](https://ai.google.dev/gemini-api/docs/models/gemini-v2?_gl=1*187cb6v*_up*MQ..*_ga*MTU1ODkzMDc0Mi4xNzM0NDM0NDg2*_ga_P1DBVKWT6V*MTczNDQzNDQ4Ni4xLjAuMTczNDQzNDQ4Ni4wLjAuMjEzNzc5MjU0Ng..#bounding-box), we'll have to rescale the coordinate values as they are normalised to a 0-1000 range. Nothing a little code node can't help with!"
},
"typeVersion": 1
},
{
"id": "d3d4470d-0fe1-47fd-a892-10a19b6a6ecc",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-660,
80
],
"parameters": {
"color": 5,
"width": 340,
"height": 100,
"content": "### Q. Why not use the Basic LLM node?\nAt time of writing, Langchain version does not recognise Gemini 2.0 to be a multimodal model."
},
"typeVersion": 1
},
{
"id": "5b2c1eff-6329-4d9a-9d3d-3a48fb3bd753",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
220,
-400
],
"parameters": {
"color": 7,
"width": 500,
"height": 440,
"content": "## 4. Draw!\n[Read more about the Edit Image node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.editimage/)\n\nFinally for this demonstration, we can use the \"Edit Image\" node to draw the bounding boxes on top of the original image. In my test run, I can see Gemini did miss out one of the bunnies but seeing how this is the experimental version we're playing with, it's pretty good to see it doesn't do too bad of a job."
},
"typeVersion": 1
},
{
"id": "965d791b-a183-46b0-b2a6-dd961d630c13",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1960,
-740
],
"parameters": {
"width": 420,
"height": 680,
"content": "## Try it out!\n### This n8n template demonstrates how to use Gemini 2.0's new Bounding Box detection capabilities your workflows.\n\nThe key difference being this enables prompt-based object detection for images which is pretty powerful for things like contextual search over an image. eg. \"Put a bounding box around all adults with children in this image\" or \"Put a bounding box around cars parked out of bounds of a parking space\".\n\n## How it works\n* An image is downloaded via the HTTP node and an \"Edit Image\" node is used to extract the file's width and height.\n* The image is then given to the Gemini 2.0 API to parse and return coordinates of the bounding box of the requested subjects. In this demo, we've asked for the AI to identify all bunnies.\n* The coordinates are then rescaled with the original image's width and height to correctl align them.\n* Finally to measure the accuracy of the object detection, we use the \"Edit Image\" node to draw the bounding boxes onto the original image.\n\n\n### Need Help?\nJoin the [Discord](https://discord.com/invite/XPKeKXeB7d) or ask in the [Forum](https://community.n8n.io/)!\n\nHappy Hacking!"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Get Variables": {
"main": [
[
{
"node": "Scale Normalised Coords",
"type": "main",
"index": 0
}
]
]
},
"Get Image Info": {
"main": [
[
{
"node": "Gemini 2.0 Object Detection",
"type": "main",
"index": 0
}
]
]
},
"Get Test Image": {
"main": [
[
{
"node": "Get Image Info",
"type": "main",
"index": 0
}
]
]
},
"Draw Bounding Boxes": {
"main": [
[]
]
},
"Scale Normalised Coords": {
"main": [
[
{
"node": "Draw Bounding Boxes",
"type": "main",
"index": 0
}
]
]
},
"Gemini 2.0 Object Detection": {
"main": [
[
{
"node": "Get Variables",
"type": "main",
"index": 0
}
]
]
},
"When clicking Test workflow": {
"main": [
[
{
"node": "Get Test Image",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,706 @@
{
"meta": {
"instanceId": "d6b502dfa4d9dd072cdc5c2bb763558661053f651289291352a84403e01b3d1b",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "42cc4260-626e-4f83-b1c3-c78c99b78b38",
"name": "On clicking 'execute'",
"type": "n8n-nodes-base.manualTrigger",
"position": [
1780,
520
],
"parameters": {},
"typeVersion": 1
},
{
"id": "f21386ff-f8db-4f5d-a44c-15484d1e4ab7",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1340,
900
],
"parameters": {
"color": 6,
"width": 2086.845881354743,
"height": 750.8363163824032,
"content": "## Subworkflow"
},
"typeVersion": 1
},
{
"id": "82851e4a-33a1-461b-965f-f51efcb5af90",
"name": "n8n",
"type": "n8n-nodes-base.n8n",
"position": [
2040,
620
],
"parameters": {
"filters": {},
"requestOptions": {}
},
"credentials": {
"n8nApi": {
"id": "1SDBLwjifPzb02W8",
"name": "n8n account"
}
},
"typeVersion": 1
},
{
"id": "90cac8e2-9509-4d48-9038-bb653ffbdf9d",
"name": "Return",
"type": "n8n-nodes-base.set",
"position": [
3220,
1100
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "8d513345-6484-431f-afb7-7cf045c90f4f",
"name": "Done",
"type": "boolean",
"value": true
}
]
}
},
"typeVersion": 3.3
},
{
"id": "11046021-89ba-4e61-b03f-d606e7dd0a56",
"name": "Get File",
"type": "n8n-nodes-base.httpRequest",
"position": [
2320,
980
],
"parameters": {
"url": "={{ $json.download_url }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "08af670c-ac82-422f-9938-c649dfdfbcf6",
"name": "If file too large",
"type": "n8n-nodes-base.if",
"position": [
2120,
1000
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "45ce825e-9fa6-430c-8931-9aaf22c42585",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.content }}",
"rightValue": ""
},
{
"id": "9619a55f-7fb1-4f24-b1a7-7aeb82365806",
"operator": {
"type": "string",
"operation": "notExists",
"singleValue": true
},
"leftValue": "={{ $json.error }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2
},
{
"id": "795fd895-94b2-46f1-b559-748b0db01c49",
"name": "Merge Items",
"type": "n8n-nodes-base.merge",
"position": [
2120,
1260
],
"parameters": {},
"typeVersion": 2
},
{
"id": "3d3399f3-bbfb-48ab-8644-91b28e731026",
"name": "isDiffOrNew",
"type": "n8n-nodes-base.code",
"position": [
2320,
1260
],
"parameters": {
"jsCode": "const orderJsonKeys = (jsonObj) => {\n const ordered = {};\n Object.keys(jsonObj).sort().forEach(key => {\n ordered[key] = jsonObj[key];\n });\n return ordered;\n}\n\n// Check if file returned with content\nif (Object.keys($input.all()[0].json).includes(\"content\")) {\n // Decode base64 content and parse JSON\n const origWorkflow = JSON.parse(Buffer.from($input.all()[0].json.content, 'base64').toString());\n const n8nWorkflow = $input.all()[1].json;\n \n // Order JSON objects\n const orderedOriginal = orderJsonKeys(origWorkflow);\n const orderedActual = orderJsonKeys(n8nWorkflow);\n\n // Determine difference\n if (JSON.stringify(orderedOriginal) === JSON.stringify(orderedActual)) {\n $input.all()[0].json.github_status = \"same\";\n } else {\n $input.all()[0].json.github_status = \"different\";\n $input.all()[0].json.n8n_data_stringy = JSON.stringify(orderedActual, null, 2);\n }\n $input.all()[0].json.content_decoded = orderedOriginal;\n// No file returned / new workflow\n} else if (Object.keys($input.all()[0].json).includes(\"data\")) {\n const origWorkflow = JSON.parse($input.all()[0].json.data);\n const n8nWorkflow = $input.all()[1].json;\n \n // Order JSON objects\n const orderedOriginal = orderJsonKeys(origWorkflow);\n const orderedActual = orderJsonKeys(n8nWorkflow);\n\n // Determine difference\n if (JSON.stringify(orderedOriginal) === JSON.stringify(orderedActual)) {\n $input.all()[0].json.github_status = \"same\";\n } else {\n $input.all()[0].json.github_status = \"different\";\n $input.all()[0].json.n8n_data_stringy = JSON.stringify(orderedActual, null, 2);\n }\n $input.all()[0].json.content_decoded = orderedOriginal;\n\n} else {\n // Order JSON object\n const n8nWorkflow = $input.all()[1].json;\n const orderedActual = orderJsonKeys(n8nWorkflow);\n \n // Proper formatting\n $input.all()[0].json.github_status = \"new\";\n $input.all()[0].json.n8n_data_stringy = JSON.stringify(orderedActual, null, 2);\n}\n\n// Return items\nreturn $input.all();"
},
"typeVersion": 1
},
{
"id": "2f2f42d0-d27c-4856-a263-4d5e9eda2c4c",
"name": "Check Status",
"type": "n8n-nodes-base.switch",
"position": [
2540,
1260
],
"parameters": {
"rules": {
"rules": [
{
"value2": "same"
},
{
"output": 1,
"value2": "different"
},
{
"output": 2,
"value2": "new"
}
]
},
"value1": "={{$json.github_status}}",
"dataType": "string"
},
"typeVersion": 1
},
{
"id": "5316029f-f32f-4a8d-95de-50ee57051a08",
"name": "Same file - Do nothing",
"type": "n8n-nodes-base.noOp",
"position": [
2760,
1100
],
"parameters": {},
"typeVersion": 1
},
{
"id": "37c5983b-48fe-41d5-8e3a-eb56dec2140b",
"name": "File is different",
"type": "n8n-nodes-base.noOp",
"position": [
2760,
1260
],
"parameters": {},
"typeVersion": 1
},
{
"id": "a4dcce9e-b0d0-4b9e-ab16-9142e641c73d",
"name": "File is new",
"type": "n8n-nodes-base.noOp",
"position": [
2760,
1420
],
"parameters": {},
"typeVersion": 1
},
{
"id": "03fcfdc4-2e52-42f0-a129-3ebaf8dd8fc1",
"name": "Create new file",
"type": "n8n-nodes-base.github",
"position": [
2980,
1420
],
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "={{ $('Globals').item.json.repo.owner }}"
},
"filePath": "={{ $('Globals').item.json.repo.path }}{{$('Execute Workflow Trigger').first().json.id}}.json",
"resource": "file",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $('Globals').item.json.repo.name }}"
},
"fileContent": "={{$('isDiffOrNew').item.json[\"n8n_data_stringy\"]}}",
"commitMessage": "={{$('Execute Workflow Trigger').first().json.name}} ({{$json.github_status}})"
},
"credentials": {
"githubApi": {
"id": "3mfzXcMjoqNHsujs",
"name": "GitHub account"
}
},
"typeVersion": 1
},
{
"id": "dd35cc39-4ab4-4d53-b439-b425a2177e8f",
"name": "Edit existing file",
"type": "n8n-nodes-base.github",
"position": [
2980,
1240
],
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "={{ $('Globals').item.json.repo.owner }}"
},
"filePath": "={{ $('Globals').item.json.repo.path }}{{$('Execute Workflow Trigger').first().json.id}}.json",
"resource": "file",
"operation": "edit",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $('Globals').item.json.repo.name }}"
},
"fileContent": "={{$('isDiffOrNew').item.json[\"n8n_data_stringy\"]}}",
"commitMessage": "={{$('Execute Workflow Trigger').first().json.name}} ({{$json.github_status}})"
},
"credentials": {
"githubApi": {
"id": "3mfzXcMjoqNHsujs",
"name": "GitHub account"
}
},
"typeVersion": 1
},
{
"id": "d05e2a25-24be-43fb-baa4-9c3391840e70",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
2240,
620
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "2a139d59-1387-4899-88b3-21106cd01099",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
1780,
720
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 2
}
]
}
},
"typeVersion": 1.2
},
{
"id": "04e6c245-3117-4ef8-a181-754e616e958b",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1340,
273.8835396388249
],
"parameters": {
"color": 4,
"width": 371.1995072042308,
"height": 600.88409546716,
"content": "## Backup to GitHub \nThis workflow will backup all instance workflows to GitHub.\n\nThe files are saved `ID.json` for the filename.\n\n### Setup\nOpen `Globals` node and update the values below 👇\n\n- **repo.owner:** your Github username\n- **repo.name:** the name of your repository\n- **repo.path:** the folder to use within the repository. If it doesn't exist it will be created.\n\n\nIf your username was `john-doe` and your repository was called `n8n-backups` and you wanted the workflows to go into a `workflows` folder you would set:\n\n- repo.owner - john-doe\n- repo.name - n8n-backups\n- repo.path - workflows/\n\n\nThe workflow calls itself using a subworkflow, to help reduce memory usage."
},
"typeVersion": 1
},
{
"id": "3d996985-0064-4749-85a1-2191c73746c9",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1740,
440
],
"parameters": {
"color": 7,
"width": 886.4410237965205,
"height": 434.88564057365943,
"content": "## Main workflow loop"
},
"typeVersion": 1
},
{
"id": "c9bfa393-e120-4bfe-b957-702756b91aaf",
"name": "Get file data",
"type": "n8n-nodes-base.github",
"position": [
1920,
1000
],
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "={{ $json.repo.owner }}"
},
"filePath": "={{ $json.repo.path }}{{ $('Execute Workflow Trigger').item.json.id }}.json",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $json.repo.name }}"
},
"asBinaryProperty": false,
"additionalParameters": {}
},
"credentials": {
"githubApi": {
"id": "3mfzXcMjoqNHsujs",
"name": "GitHub account"
}
},
"typeVersion": 1,
"continueOnFail": true,
"alwaysOutputData": true
},
{
"id": "d42ddc37-3bd9-4f19-8831-695bec4d0137",
"name": "Globals",
"type": "n8n-nodes-base.set",
"position": [
1700,
1160
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "6cf546c5-5737-4dbd-851b-17d68e0a3780",
"name": "repo.owner",
"type": "string",
"value": "john-doe"
},
{
"id": "452efa28-2dc6-4ea3-a7a2-c35d100d0382",
"name": "repo.name",
"type": "string",
"value": "n8n-backup"
},
{
"id": "81c4dc54-86bf-4432-a23f-22c7ea831e74",
"name": "repo.path",
"type": "string",
"value": "workflows/"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "e970c63c-2aa2-46f9-be04-f045b6a938de",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1660,
1060
],
"parameters": {
"color": 4,
"width": 150,
"height": 80,
"content": "## Edit this node 👇"
},
"typeVersion": 1
},
{
"id": "5b1991f7-0351-44de-908d-9aa8b8262d60",
"name": "Execute Workflow Trigger",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
1420,
1280
],
"parameters": {
"inputSource": "passthrough"
},
"typeVersion": 1.1
},
{
"id": "8e5b3f71-0c5e-4e78-a3f7-0b574c9ddf06",
"name": "Execute Workflow",
"type": "n8n-nodes-base.executeWorkflow",
"position": [
2460,
620
],
"parameters": {
"mode": "each",
"options": {},
"workflowId": {
"__rl": true,
"mode": "id",
"value": "={{ $workflow.id }}"
},
"workflowInputs": {
"value": {},
"schema": [],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
}
},
"typeVersion": 1.2
}
],
"pinData": {},
"connections": {
"n8n": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Globals": {
"main": [
[
{
"node": "Get file data",
"type": "main",
"index": 0
}
]
]
},
"Get File": {
"main": [
[
{
"node": "Merge Items",
"type": "main",
"index": 0
}
]
]
},
"File is new": {
"main": [
[
{
"node": "Create new file",
"type": "main",
"index": 0
}
]
]
},
"Merge Items": {
"main": [
[
{
"node": "isDiffOrNew",
"type": "main",
"index": 0
}
]
]
},
"isDiffOrNew": {
"main": [
[
{
"node": "Check Status",
"type": "main",
"index": 0
}
]
]
},
"Check Status": {
"main": [
[
{
"node": "Same file - Do nothing",
"type": "main",
"index": 0
}
],
[
{
"node": "File is different",
"type": "main",
"index": 0
}
],
[
{
"node": "File is new",
"type": "main",
"index": 0
}
]
]
},
"Get file data": {
"main": [
[
{
"node": "If file too large",
"type": "main",
"index": 0
}
]
]
},
"Create new file": {
"main": [
[
{
"node": "Return",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "Execute Workflow",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "n8n",
"type": "main",
"index": 0
}
]
]
},
"File is different": {
"main": [
[
{
"node": "Edit existing file",
"type": "main",
"index": 0
}
]
]
},
"If file too large": {
"main": [
[
{
"node": "Get File",
"type": "main",
"index": 0
}
],
[
{
"node": "Merge Items",
"type": "main",
"index": 0
}
]
]
},
"Edit existing file": {
"main": [
[
{
"node": "Return",
"type": "main",
"index": 0
}
]
]
},
"On clicking 'execute'": {
"main": [
[
{
"node": "n8n",
"type": "main",
"index": 0
}
]
]
},
"Same file - Do nothing": {
"main": [
[
{
"node": "Return",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow Trigger": {
"main": [
[
{
"node": "Globals",
"type": "main",
"index": 0
},
{
"node": "Merge Items",
"type": "main",
"index": 1
}
]
]
}
}
}

View File

@@ -0,0 +1,601 @@
{
"nodes": [
{
"id": "9df72ef9-3b9d-40e4-9cb5-a5ada153c0bb",
"name": "Google Drive",
"type": "n8n-nodes-base.googleDrive",
"position": [
120,
-180
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {},
"operation": "download"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "wpiZXesxk9S8fkVG",
"name": "Google Drive account 2"
}
},
"typeVersion": 3
},
{
"id": "e21bb906-658c-4a52-9c7b-b77d6e0e7ea5",
"name": "Upload File",
"type": "n8n-nodes-base.httpRequest",
"position": [
360,
-180
],
"parameters": {
"url": "https://api.cloud.llamaindex.ai/api/parsing/upload",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "multipart-form-data",
"sendHeaders": true,
"bodyParameters": {
"parameters": [
{
"name": "webhook_url",
"value": "https://n8n.lowcoding.dev/webhook/0f7f5ebb-8b66-453b-a818-20cc3647c783"
},
{
"name": "file",
"parameterType": "formBinaryData",
"inputDataFieldName": "data"
},
{
"name": "disable_ocr",
"value": "true"
},
{
"name": "disable_image_extraction",
"value": "True"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "accept",
"value": "application/json"
},
{
"name": "Authorization",
"value": "Bearer "
},
{
"name": "parsing_instruction",
"value": "Please extract invoice line items: Name, Quantity, Unit Price, Amount "
}
]
}
},
"typeVersion": 4.2
},
{
"id": "2a0c2331-4612-4b92-a0cc-b316bc663907",
"name": "Google Drive Trigger",
"type": "n8n-nodes-base.googleDriveTrigger",
"position": [
-80,
-180
],
"parameters": {
"event": "fileCreated",
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"triggerOn": "specificFolder",
"folderToWatch": {
"__rl": true,
"mode": "list",
"value": "1IC39VXU8rewBU85offxYlBd9QlYzf8S7",
"cachedResultUrl": "https://drive.google.com/drive/folders/1IC39VXU8rewBU85offxYlBd9QlYzf8S7",
"cachedResultName": "Invoices"
}
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "wpiZXesxk9S8fkVG",
"name": "Google Drive account 2"
}
},
"typeVersion": 1
},
{
"id": "4ad70b03-54f1-4715-9848-56fa6ba18278",
"name": "Create Invoice",
"type": "n8n-nodes-base.airtable",
"position": [
400,
340
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appndgSF4faN4jPXi",
"cachedResultUrl": "https://airtable.com/appndgSF4faN4jPXi",
"cachedResultName": "Philipp's Base"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tbloPc7Eay4Cvwysq",
"cachedResultUrl": "https://airtable.com/appndgSF4faN4jPXi/tbloPc7Eay4Cvwysq",
"cachedResultName": "Invoices"
},
"columns": {
"value": {},
"schema": [
{
"id": "Name",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Line Items",
"type": "array",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Line Items",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": []
},
"options": {},
"operation": "create"
},
"credentials": {
"airtableTokenApi": {
"id": "XT7hvl1w201jtBhx",
"name": "Airtable Personal Access Token account"
}
},
"typeVersion": 2.1
},
{
"id": "a408eeb4-2dc2-45ff-a989-92676356f596",
"name": "Create Line Item",
"type": "n8n-nodes-base.airtable",
"position": [
800,
340
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appndgSF4faN4jPXi",
"cachedResultUrl": "https://airtable.com/appndgSF4faN4jPXi",
"cachedResultName": "Philipp's Base"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblIuVR9ocAomznzK",
"cachedResultUrl": "https://airtable.com/appndgSF4faN4jPXi/tblIuVR9ocAomznzK",
"cachedResultName": "Line Items"
},
"columns": {
"value": {
"Qty": "={{ $json.qty }}",
"Amount": "={{ parseFloat($json.amount.replace('$', '').trim()) }}",
"Invoices": "=[\"{{ $('Create Invoice').item.json.id }}\"]",
"Unit price": "={{ parseFloat($json.unit_price.replace('$', '').trim()) }}",
"Description": "={{ $json.description }}"
},
"schema": [
{
"id": "Name",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Description",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Qty",
"type": "number",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Qty",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Unit price",
"type": "number",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Unit price",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Amount",
"type": "number",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Amount",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Invoices",
"type": "array",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Invoices",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": []
},
"options": {},
"operation": "create"
},
"credentials": {
"airtableTokenApi": {
"id": "XT7hvl1w201jtBhx",
"name": "Airtable Personal Access Token account"
}
},
"typeVersion": 2.1
},
{
"id": "7ee324e8-6df3-48d6-b1b8-6fdb610b1ec7",
"name": "OpenAI - Extract Line Items",
"type": "n8n-nodes-base.httpRequest",
"position": [
180,
340
],
"parameters": {
"url": "=https://api.openai.com/v1/chat/completions",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"gpt-4o-mini\",\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": {{ JSON.stringify($('Set Fields').item.json.prompt) }}\n },\n {\n \"role\": \"user\",\n \"content\": {{ JSON.stringify( JSON.stringify($('Webhook').item.json.body.json[0].items) ) }}\n }\n ],\n \"response_format\":{ \"type\": \"json_schema\", \"json_schema\": {{ $('Set Fields').item.json.schema }}\n\n }\n }",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi"
},
"credentials": {
"openAiApi": {
"id": "9RivS2BmSh1DDBFm",
"name": "OpenAi account 3"
}
},
"typeVersion": 4.2
},
{
"id": "eda31919-9091-4d45-bd73-4609b71f93a9",
"name": "Set Fields",
"type": "n8n-nodes-base.set",
"position": [
-40,
340
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "dc09a5b4-ff6a-4cee-b87e-35de7336ac05",
"name": "prompt",
"type": "string",
"value": "Please, process parsed data and return only needed."
},
{
"id": "4e0f9af6-517f-42af-9ced-df0e8a7118b0",
"name": "schema",
"type": "string",
"value": "={\n \"name\": \"generate_schema\",\n \"description\": \"Generate schema for an array of objects representing items with their descriptions, quantities, unit prices, and amounts.\",\n \"strict\": true,\n \"schema\": {\n \"type\": \"object\",\n \"required\": [\n \"items\"\n ],\n \"properties\": {\n \"items\": {\n \"type\": \"array\",\n \"description\": \"Array of item objects\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"description\",\n \"qty\",\n \"unit_price\",\n \"amount\"\n ],\n \"properties\": {\n \"description\": {\n \"type\": \"string\",\n \"description\": \"Description of the item\"\n },\n \"qty\": {\n \"type\": \"string\",\n \"description\": \"Quantity of the item\"\n },\n \"unit_price\": {\n \"type\": \"string\",\n \"description\": \"Unit price of the item formatted as a string\"\n },\n \"amount\": {\n \"type\": \"string\",\n \"description\": \"Total amount for the item formatted as a string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n }\n}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "cc0d97d8-fb62-43eb-b484-4dd39f8db4b4",
"name": "Process Line Items",
"type": "n8n-nodes-base.code",
"position": [
600,
340
],
"parameters": {
"jsCode": "// Get the input from the \"OpenAI - Extract Line Items\" node\nconst input = $(\"OpenAI - Extract Line Items\").first().json;\n\n// Initialize an array for the output\nconst outputItems = [];\n\n// Navigate to the 'content' field in the choices array\nconst content = input.choices[0]?.message?.content;\n\nif (content) {\n try {\n // Parse the stringified JSON in the 'content' field\n const parsedContent = JSON.parse(content);\n\n // Extract 'items' and add them to the output array\n if (Array.isArray(parsedContent.items)) {\n outputItems.push(...parsedContent.items.map(i => ({ json: i })));\n }\n } catch (error) {\n // Handle any parsing errors\n console.error('Error parsing content:', error);\n }\n}\n\n// Return the extracted items\nreturn outputItems;\n"
},
"typeVersion": 2
},
{
"id": "741dc44e-6d47-4a77-80c2-5e18b291da33",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-220,
340
],
"webhookId": "0f7f5ebb-8b66-453b-a818-20cc3647c783",
"parameters": {
"path": "0f7f5ebb-8b66-453b-a818-20cc3647c783",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2
},
{
"id": "fbc196c8-7518-4deb-ac47-f37f1b8150eb",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-260,
-300
],
"parameters": {
"width": 920,
"height": 400,
"content": "## Scenario 1\n\n"
},
"typeVersion": 1
},
{
"id": "96368d41-7886-487f-a8a7-e4dac3b01f45",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-280,
240
],
"parameters": {
"width": 1340,
"height": 460,
"content": "## Scenario 2\n\n"
},
"typeVersion": 1
},
{
"id": "6b7c94d7-c844-4246-ba1a-cea5937792db",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-60,
0
],
"parameters": {
"color": 3,
"width": 270,
"height": 80,
"content": "### Replace Google Drive connection"
},
"typeVersion": 1
},
{
"id": "9c8141d0-428a-44e5-b900-b07fa64db4f5",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
320,
0
],
"parameters": {
"color": 3,
"width": 170,
"height": 80,
"content": "### Replace API key in header"
},
"typeVersion": 1
},
{
"id": "48243fe4-4ed1-43dc-b508-8b3f9472bb67",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
140,
540
],
"parameters": {
"color": 3,
"width": 170,
"height": 80,
"content": "### Replace OpenAI connection"
},
"typeVersion": 1
},
{
"id": "ffc6b530-69ab-4ccb-945d-94f8fdc1e3ab",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
400,
540
],
"parameters": {
"color": 3,
"width": 530,
"height": 80,
"content": "### Replace Airtable connection"
},
"typeVersion": 1
},
{
"id": "15047f43-5f7e-4c70-a754-fffb41c04611",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
-760,
380
],
"parameters": {
"color": 7,
"width": 330.5152611046425,
"height": 239.5888196628349,
"content": "### ... or watch set up video [7 min]\n[![Youtube Thumbnail](https://cflobdhpqwnoisuctsoc.supabase.co/storage/v1/object/public/my_storage/Video%2010%20-%20Parser%20invoices%20Blur.png)](https://youtu.be/E4I0nru-fa8)\n"
},
"typeVersion": 1
},
{
"id": "812f6cc7-a093-41d0-9750-48253d9f04a8",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1060,
-300
],
"parameters": {
"color": 7,
"width": 636,
"height": 657,
"content": "![5min Logo](https://cflobdhpqwnoisuctsoc.supabase.co/storage/v1/object/public/my_storage/banner.png)\n## AI Agent for realtime insights on meetings\n**Made by [Mark Shcherbakov](https://www.linkedin.com/in/marklowcoding/) from community [5minAI](https://www.skool.com/5minai)**\n\nTranscribing meetings manually can be tedious and prone to error. This workflow automates the transcription process in real-time, ensuring that key discussions and decisions are accurately captured and easily accessible for later review, thus enhancing productivity and clarity in communications.\n\nThe workflow leverages n8n and LlamaParse to automatically detect new invoices in a designated Google Drive folder, parse essential billing details, and store the extracted data in a structured format. The key functionalities include:\n- Real-time detection of new invoices via Google Drive triggers.\n- Automated HTTP requests to initiate parsing through Lama Cloud.\n- Structured storage of invoice details and line items in a database for future reference.\n\n1. **Google Drive Integration**: Monitors a specific folder in Google Drive for new invoice uploads.\n2. **Parsing with LlamaParse**: Automatically sends invoices for parsing and processes results through webhooks.\n3. **Data Storage in Airtable**: Creates records for invoices and their associated line items, allowing for detailed tracking."
},
"typeVersion": 1
},
{
"id": "a80e6528-cf79-4229-8c58-6856fd86b6e7",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1060,
380
],
"parameters": {
"color": 7,
"width": 280,
"height": 626,
"content": "### Set up steps\n\n1. **Google Drive Trigger**: \n - Set up a trigger to detect new files in a specified folder dedicated to invoices.\n\n2. **File Upload to LlamaParse**: \n - Create an HTTP request that sends the invoice file to LlamaParse for parsing, including relevant header settings and webhook URL.\n\n3. **Webhook Processing**: \n - Establish a webhook node to handle parsed results from LlamaParse, extracting needed invoice details effectively.\n\n4. **Invoice Record Creation**: \n - Create initial records for invoices in your database using the parsed details received from the webhook.\n\n5. **Line Item Processing**: \n - Transform string data into structured line item arrays and create individual records for each item linked to the main invoice."
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Webhook": {
"main": [
[
{
"node": "Set Fields",
"type": "main",
"index": 0
}
]
]
},
"Set Fields": {
"main": [
[
{
"node": "OpenAI - Extract Line Items",
"type": "main",
"index": 0
}
]
]
},
"Google Drive": {
"main": [
[
{
"node": "Upload File",
"type": "main",
"index": 0
}
]
]
},
"Create Invoice": {
"main": [
[
{
"node": "Process Line Items",
"type": "main",
"index": 0
}
]
]
},
"Process Line Items": {
"main": [
[
{
"node": "Create Line Item",
"type": "main",
"index": 0
}
]
]
},
"Google Drive Trigger": {
"main": [
[
{
"node": "Google Drive",
"type": "main",
"index": 0
}
]
]
},
"OpenAI - Extract Line Items": {
"main": [
[
{
"node": "Create Invoice",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,600 @@
{
"meta": {
"instanceId": "03e9d14e9196363fe7191ce21dc0bb17387a6e755dcc9acc4f5904752919dca8"
},
"nodes": [
{
"id": "1bad6bfc-9ec9-48a5-b8f7-73c4de3d08cf",
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
1480,
160
],
"parameters": {
"simple": false,
"filters": {},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"gmailOAuth2": {
"id": "kkhNhqKpZt6IUZd0",
"name": " Gmail"
}
},
"typeVersion": 1.2
},
{
"id": "9ac747a1-4fd8-46ba-b4c1-75fd17aab2ed",
"name": "Microsoft Outlook Trigger",
"type": "n8n-nodes-base.microsoftOutlookTrigger",
"disabled": true,
"position": [
1480,
720
],
"parameters": {
"fields": [
"body",
"toRecipients",
"subject",
"bodyPreview"
],
"output": "fields",
"filters": {},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "vTCK0oVQ0WjFrI5H",
"name": " Outlook Credential"
}
},
"typeVersion": 1
},
{
"id": "5bf9b0e8-b84e-44a2-aad2-45dde3e4ab1b",
"name": "Screenshot HTML",
"type": "n8n-nodes-base.httpRequest",
"position": [
2520,
480
],
"parameters": {
"url": "https://hcti.io/v1/image",
"method": "POST",
"options": {},
"sendBody": true,
"sendQuery": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "html",
"value": "={{ $json.htmlBody }}"
}
]
},
"genericAuthType": "httpBasicAuth",
"queryParameters": {
"parameters": [
{}
]
}
},
"credentials": {
"httpBasicAuth": {
"id": "8tm8mUWmPvtmPFPk",
"name": "hcti.io"
}
},
"typeVersion": 4.2
},
{
"id": "fc770d1d-6c18-4d14-8344-1dc042464df6",
"name": "Retrieve Screenshot",
"type": "n8n-nodes-base.httpRequest",
"position": [
2700,
480
],
"parameters": {
"url": "={{ $json.url }}",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth"
},
"credentials": {
"httpBasicAuth": {
"id": "8tm8mUWmPvtmPFPk",
"name": "hcti.io"
}
},
"typeVersion": 4.2
},
{
"id": "2f3e5cc0-24e8-450a-898b-71e2d6f7bb58",
"name": "Set Outlook Variables",
"type": "n8n-nodes-base.set",
"position": [
2020,
720
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "38bd3db2-1a8d-4c40-a2dd-336e0cc84224",
"name": "htmlBody",
"type": "string",
"value": "={{ $('Microsoft Outlook Trigger').item.json.body.content }}"
},
{
"id": "13bdd95b-ef02-486e-b38b-d14bd05a4a8a",
"name": "headers",
"type": "string",
"value": "={{ $json}}"
},
{
"id": "20566ad4-7eb7-42b1-8a0d-f8b759610f10",
"name": "subject",
"type": "string",
"value": "={{ $('Microsoft Outlook Trigger').item.json.subject }}"
},
{
"id": "7171998f-a5a2-4e23-946a-9c1ad75710e7",
"name": "recipient",
"type": "string",
"value": "={{ $('Microsoft Outlook Trigger').item.json.toRecipients[0].emailAddress.address }}"
},
{
"id": "cc262634-2470-4524-8319-abe2518a6335",
"name": "textBody",
"type": "string",
"value": "={{ $('Retrieve Headers of Email').item.json.body.content }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "374e5b16-a666-4706-9fd2-762b2927012d",
"name": "Set Gmail Variables",
"type": "n8n-nodes-base.set",
"position": [
2040,
160
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "38bd3db2-1a8d-4c40-a2dd-336e0cc84224",
"name": "htmlBody",
"type": "string",
"value": "={{ $json.html }}"
},
{
"id": "18fbcf78-6d3c-4036-b3a2-fb5adf22176a",
"name": "headers",
"type": "string",
"value": "={{ $json.headers }}"
},
{
"id": "1d690098-be2a-4604-baf8-62f314930929",
"name": "subject",
"type": "string",
"value": "={{ $json.subject }}"
},
{
"id": "8009f00a-547f-4eb1-b52d-2e7305248885",
"name": "recipient",
"type": "string",
"value": "={{ $json.to.text }}"
},
{
"id": "1932e97d-b03b-4964-b8bc-8262aaaa1f7a",
"name": "textBody",
"type": "string",
"value": "={{ $json.text }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "3166738e-d0a3-475b-8b19-51afd519ee3a",
"name": "Retrieve Headers of Email",
"type": "n8n-nodes-base.httpRequest",
"position": [
1680,
720
],
"parameters": {
"url": "=https://graph.microsoft.com/v1.0/me/messages/{{ $json.id }}?$select=internetMessageHeaders,body",
"options": {},
"sendHeaders": true,
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/json"
},
{
"name": "Prefer",
"value": "outlook.body-content-type=\"text\""
}
]
},
"nodeCredentialType": "microsoftOutlookOAuth2Api"
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "vTCK0oVQ0WjFrI5H",
"name": " Outlook Credential"
}
},
"typeVersion": 4.2
},
{
"id": "25ae222c-088f-4565-98d6-803c8c1b0826",
"name": "Format Headers",
"type": "n8n-nodes-base.code",
"position": [
1860,
720
],
"parameters": {
"jsCode": "const input = $('Retrieve Headers of Email').item.json.internetMessageHeaders;\n\nconst result = input.reduce((acc, { name, value }) => {\n if (!acc[name]) acc[name] = [];\n acc[name].push(value);\n return acc;\n}, {});\n\nreturn result;"
},
"typeVersion": 2
},
{
"id": "8f14f267-1074-43ea-968d-26a6ab36fd7b",
"name": "Set Email Variables",
"type": "n8n-nodes-base.set",
"position": [
2360,
480
],
"parameters": {
"options": {},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "45d156aa-91f4-483c-91d4-c9de4a4f595d",
"name": "ChatGPT Analysis",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
3100,
480
],
"parameters": {
"text": "=Describe this image. Determine if the email could be a phishing email. The message headers are as follows:\n{{ $('Set Email Variables').item.json.headers }}\n\nFormat the response for Jira who uses a wiki-style renderer. Do not include ``` around your response.",
"modelId": {
"__rl": true,
"mode": "list",
"value": "chatgpt-4o-latest",
"cachedResultName": "CHATGPT-4O-LATEST"
},
"options": {
"maxTokens": 1500
},
"resource": "image",
"inputType": "base64",
"operation": "analyze"
},
"credentials": {
"openAiApi": {
"id": "76",
"name": "OpenAi account"
}
},
"typeVersion": 1.6
},
{
"id": "62ca591b-6627-496c-96a7-95cb0081480d",
"name": "Create Jira Ticket",
"type": "n8n-nodes-base.jira",
"position": [
3500,
480
],
"parameters": {
"project": {
"__rl": true,
"mode": "list",
"value": "10001",
"cachedResultName": "Support"
},
"summary": "=Phishing Email Reported: \"{{ $('Set Email Variables').item.json.subject }}\"",
"issueType": {
"__rl": true,
"mode": "list",
"value": "10008",
"cachedResultName": "Task"
},
"additionalFields": {
"description": "=A phishing email was reported by {{ $('Set Email Variables').item.json.recipient }} with the subject line \"{{ $('Set Email Variables').item.json.subject }}\" and body:\n{{ $('Set Email Variables').item.json.textBody }}\n\\\\\n\\\\\n\\\\\nh2. Here is ChatGPT's analysis of the email:\n{{ $json.content }}"
}
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "BZmmGUrNIsgM9fDj",
"name": "New Jira Cloud"
}
},
"typeVersion": 1
},
{
"id": "071380c8-8070-4f8f-86c6-87c4ee3bc261",
"name": "Rename Screenshot",
"type": "n8n-nodes-base.code",
"position": [
3680,
480
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "$('Retrieve Screenshot').item.binary.data.fileName = 'emailScreenshot.png'\n\nreturn $('Retrieve Screenshot').item;"
},
"typeVersion": 2
},
{
"id": "05c57490-c1ee-48f0-9e38-244c9a995e22",
"name": "Upload Screenshot of Email to Jira",
"type": "n8n-nodes-base.jira",
"position": [
3860,
480
],
"parameters": {
"issueKey": "={{ $('Create Jira Ticket').item.json.key }}",
"resource": "issueAttachment"
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "BZmmGUrNIsgM9fDj",
"name": "New Jira Cloud"
}
},
"typeVersion": 1
},
{
"id": "be02770d-a943-41f5-98a9-5c433a6a3dbf",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1420,
-107.36679523834897
],
"parameters": {
"color": 7,
"width": 792.3026315789474,
"height": 426.314163659402,
"content": "![Gmail](https://uploads.n8n.io/templates/gmail.png)\n## Gmail Integration and Data Extraction\n\nThis section of the workflow connects to a Gmail account using the **Gmail Trigger** node, capturing incoming emails in real-time, with checks performed every minute. Once an email is detected, its key components—such as the subject, recipient, body, and headers—are extracted and assigned to variables using the **Set Gmail Variables** node. These variables are structured for subsequent analysis and processing in later steps."
},
"typeVersion": 1
},
{
"id": "c1d2f691-669a-46de-9ef8-59ce4e6980c5",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1420,
380.6918768014301
],
"parameters": {
"color": 7,
"width": 792.3026315789474,
"height": 532.3344389880435,
"content": "![Gmail](https://uploads.n8n.io/templates/outlook.png)\n## Microsoft Outlook Integration and Email Header Processing\n\nThis section connects to a Microsoft Outlook account to monitor incoming emails using the **Microsoft Outlook Trigger** node, which checks for new messages every minute. Emails are then processed to retrieve detailed headers and body content via the **Retrieve Headers of Email** node. The headers are structured into a user-friendly format using the **Format Headers** code node, ensuring clarity for further analysis. Key details, including the email's subject, recipient, and body content, are assigned to variables with the **Set Outlook Variables** node for streamlined integration into subsequent workflow steps."
},
"typeVersion": 1
},
{
"id": "c189e2e0-9f51-4bc0-a483-8b7f0528be70",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2287.3684210526317,
46.18421052631584
],
"parameters": {
"color": 7,
"width": 580.4605263157906,
"height": 615.460526315789,
"content": "![hctiapi](https://uploads.n8n.io/templates/hctiapi.png)\n## HTML Screenshot Generation and Email Visualization\n\nThis section processes an emails HTML content to create a visual representation, useful for documentation or phishing detection workflows. The **Set Email Variables** node organizes the email's HTML body into a format ready for processing. The **Screenshot HTML** node sends this HTML content to the **hcti.io** API, which generates a screenshot of the email's layout. The **Retrieve Screenshot** node then fetches the image URL for further use in the workflow. This setup ensures that the email's appearance is preserved in a visually accessible format, simplifying review and reporting. Keep in mind however that this exposes the email content to a third party. If you self host n8n, you can deploy a cli tool to rasterize locally instead."
},
"typeVersion": 1
},
{
"id": "9076f9e9-f4fb-409a-9580-1ae459094c31",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2880,
123.72476075009968
],
"parameters": {
"color": 7,
"width": 507.82894736842223,
"height": 537.9199760920052,
"content": "![hctiapi](https://uploads.n8n.io/templates/openai.png)\n## AI-Powered Email Analysis with ChatGPT\n\nThis section leverages AI to analyze email content and headers for phishing indicators. The **ChatGPT Analysis** node utilizes the ChatGPT-4 model to review the email screenshot and associated metadata, including message headers. It generates a detailed report indicating whether the email might be a phishing attempt. The output is formatted specifically for Jiras wiki-style renderer, making it ready for seamless integration into ticketing workflows. This ensures thorough and automated email threat assessments."
},
"typeVersion": 1
},
{
"id": "ca2488af-e787-4675-802a-8b4f2d845376",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
3400,
122.88662032580646
],
"parameters": {
"color": 7,
"width": 692.434210526317,
"height": 529.5475902005091,
"content": "![hctiapi](https://uploads.n8n.io/templates/jira.png)\n## Automated Jira Ticket Creation for Phishing Reports\n\nThis section streamlines the process of reporting phishing emails by automatically creating detailed Jira tickets. The **Create Jira Ticket** node compiles email information, including the subject, recipient, body text, and ChatGPT's phishing analysis, into a structured ticket. The **Rename Screenshot** node ensures that the email screenshot file is appropriately labeled for attachment. Finally, the **Upload Screenshot of Email to Jira** node attaches the emails visual representation to the ticket, providing additional context for the security team. This integration ensures that phishing reports are logged with all necessary details, enabling efficient tracking and resolution."
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Gmail Trigger": {
"main": [
[
{
"node": "Set Gmail Variables",
"type": "main",
"index": 0
}
]
]
},
"Format Headers": {
"main": [
[
{
"node": "Set Outlook Variables",
"type": "main",
"index": 0
}
]
]
},
"Screenshot HTML": {
"main": [
[
{
"node": "Retrieve Screenshot",
"type": "main",
"index": 0
}
]
]
},
"ChatGPT Analysis": {
"main": [
[
{
"node": "Create Jira Ticket",
"type": "main",
"index": 0
}
]
]
},
"Rename Screenshot": {
"main": [
[
{
"node": "Upload Screenshot of Email to Jira",
"type": "main",
"index": 0
}
]
]
},
"Create Jira Ticket": {
"main": [
[
{
"node": "Rename Screenshot",
"type": "main",
"index": 0
}
]
]
},
"Retrieve Screenshot": {
"main": [
[
{
"node": "ChatGPT Analysis",
"type": "main",
"index": 0
}
]
]
},
"Set Email Variables": {
"main": [
[
{
"node": "Screenshot HTML",
"type": "main",
"index": 0
}
]
]
},
"Set Gmail Variables": {
"main": [
[
{
"node": "Set Email Variables",
"type": "main",
"index": 0
}
]
]
},
"Set Outlook Variables": {
"main": [
[
{
"node": "Set Email Variables",
"type": "main",
"index": 0
}
]
]
},
"Microsoft Outlook Trigger": {
"main": [
[
{
"node": "Retrieve Headers of Email",
"type": "main",
"index": 0
}
]
]
},
"Retrieve Headers of Email": {
"main": [
[
{
"node": "Format Headers",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,828 @@
{
"meta": {
"instanceId": "03e9d14e9196363fe7191ce21dc0bb17387a6e755dcc9acc4f5904752919dca8"
},
"nodes": [
{
"id": "94dd7f48-0013-4fb5-89c4-826ecd7f2d66",
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
1460,
120
],
"parameters": {
"simple": false,
"filters": {},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"gmailOAuth2": {
"id": "kkhNhqKpZt6IUZd0",
"name": "Gmail"
}
},
"typeVersion": 1.2
},
{
"id": "ca2023fa-ceca-4923-80e4-a3843803536c",
"name": "Microsoft Outlook Trigger",
"type": "n8n-nodes-base.microsoftOutlookTrigger",
"disabled": true,
"position": [
1480,
680
],
"parameters": {
"fields": [
"body",
"toRecipients",
"subject",
"bodyPreview"
],
"output": "fields",
"filters": {},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "vTCK0oVQ0WjFrI5H",
"name": " Outlook Credential"
}
},
"typeVersion": 1
},
{
"id": "1f011214-91a0-4cfa-9d9e-29864937c0a3",
"name": "Screenshot HTML",
"type": "n8n-nodes-base.httpRequest",
"position": [
2620,
420
],
"parameters": {
"url": "https://hcti.io/v1/image",
"method": "POST",
"options": {},
"sendBody": true,
"sendQuery": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "html",
"value": "={{ $('Set Email Variables').item.json.htmlBody }}"
}
]
},
"genericAuthType": "httpBasicAuth",
"queryParameters": {
"parameters": [
{}
]
}
},
"credentials": {
"httpBasicAuth": {
"id": "8tm8mUWmPvtmPFPk",
"name": "hcti.io"
}
},
"typeVersion": 4.2
},
{
"id": "64f4789f-9de8-414f-af62-ddc339f0d0ac",
"name": "Retrieve Screenshot",
"type": "n8n-nodes-base.httpRequest",
"position": [
2800,
420
],
"parameters": {
"url": "={{ $json.url }}",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth"
},
"credentials": {
"httpBasicAuth": {
"id": "8tm8mUWmPvtmPFPk",
"name": "hcti.io"
}
},
"typeVersion": 4.2
},
{
"id": "db707bd9-6abc-4ab7-8ffa-ad25c5e8adc4",
"name": "Set Outlook Variables",
"type": "n8n-nodes-base.set",
"position": [
2040,
680
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "38bd3db2-1a8d-4c40-a2dd-336e0cc84224",
"name": "htmlBody",
"type": "string",
"value": "={{ $('Microsoft Outlook Trigger').item.json.body.content }}"
},
{
"id": "13bdd95b-ef02-486e-b38b-d14bd05a4a8a",
"name": "headers",
"type": "string",
"value": "={{ $json}}"
},
{
"id": "20566ad4-7eb7-42b1-8a0d-f8b759610f10",
"name": "subject",
"type": "string",
"value": "={{ $('Microsoft Outlook Trigger').item.json.subject }}"
},
{
"id": "7171998f-a5a2-4e23-946a-9c1ad75710e7",
"name": "recipient",
"type": "string",
"value": "={{ $('Microsoft Outlook Trigger').item.json.toRecipients[0].emailAddress.address }}"
},
{
"id": "cc262634-2470-4524-8319-abe2518a6335",
"name": "textBody",
"type": "string",
"value": "={{ $('Retrieve Headers of Email').item.json.body.content }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "7a3622c0-6949-4ea3-ae13-46a1ee26de7b",
"name": "Set Gmail Variables",
"type": "n8n-nodes-base.set",
"position": [
2020,
120
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "38bd3db2-1a8d-4c40-a2dd-336e0cc84224",
"name": "htmlBody",
"type": "string",
"value": "={{ $json.html }}"
},
{
"id": "18fbcf78-6d3c-4036-b3a2-fb5adf22176a",
"name": "headers",
"type": "string",
"value": "={{ $json.headers }}"
},
{
"id": "1d690098-be2a-4604-baf8-62f314930929",
"name": "subject",
"type": "string",
"value": "={{ $json.subject }}"
},
{
"id": "8009f00a-547f-4eb1-b52d-2e7305248885",
"name": "recipient",
"type": "string",
"value": "={{ $json.to.text }}"
},
{
"id": "1932e97d-b03b-4964-b8bc-8262aaaa1f7a",
"name": "textBody",
"type": "string",
"value": "={{ $json.text }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "4b4c6b34-f74c-4402-91a1-4d002e02a3bd",
"name": "Retrieve Headers of Email",
"type": "n8n-nodes-base.httpRequest",
"position": [
1700,
680
],
"parameters": {
"url": "=https://graph.microsoft.com/v1.0/me/messages/{{ $json.id }}?$select=internetMessageHeaders,body",
"options": {},
"sendHeaders": true,
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/json"
},
{
"name": "Prefer",
"value": "outlook.body-content-type=\"text\""
}
]
},
"nodeCredentialType": "microsoftOutlookOAuth2Api"
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "vTCK0oVQ0WjFrI5H",
"name": " Outlook Credential"
}
},
"typeVersion": 4.2
},
{
"id": "0c9883b5-3eb7-45db-9803-d1b30166a3b5",
"name": "Format Headers",
"type": "n8n-nodes-base.code",
"position": [
1880,
680
],
"parameters": {
"jsCode": "const input = $('Retrieve Headers of Email').item.json.internetMessageHeaders;\n\nconst result = input.reduce((acc, { name, value }) => {\n if (!acc[name]) acc[name] = [];\n acc[name].push(value);\n return acc;\n}, {});\n\nreturn result;"
},
"typeVersion": 2
},
{
"id": "c21a976c-00e5-4823-bd94-4c95a7d60438",
"name": "Analyze Email with ChatGPT",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
3000,
420
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o",
"cachedResultName": "GPT-4O"
},
"options": {},
"messages": {
"values": [
{
"content": "=Describe the following email using the HTML body and headers. Determine if the email could be a phishing email. \n\nHere is the HTML body:\n{{ $('Set Email Variables').item.json.htmlBody }}\n\nThe message headers are as follows:\n{{ $('Set Email Variables').item.json.headers }}\n\n"
},
{
"role": "system",
"content": "Please make sure to output all responses using the following structured JSON output:\n{\n \"malicious\": false,\n \"summary\": \"The email appears to be a legitimate communication from a known sender. It contains no suspicious links, attachments, or language that indicates phishing or malicious intent.\"\n}\n\nFormat the response for Jira who uses a wiki-style renderer. Do not include ``` around your response. Make the summary as verbose as possible including a full breakdown of why the email is benign or malicious."
}
]
},
"jsonOutput": true
},
"credentials": {
"openAiApi": {
"id": "76",
"name": "OpenAi account"
}
},
"typeVersion": 1.6
},
{
"id": "a91f4095-9245-4276-b21f-f415de22df62",
"name": "Create Potentially Malicious Ticket",
"type": "n8n-nodes-base.jira",
"position": [
3640,
400
],
"parameters": {
"project": {
"__rl": true,
"mode": "list",
"value": "10001",
"cachedResultName": "Support"
},
"summary": "=Potentially Malicious - Phishing Email Reported: \"{{ $('Set Email Variables').item.json.subject }}\"",
"issueType": {
"__rl": true,
"mode": "list",
"value": "10008",
"cachedResultName": "Task"
},
"additionalFields": {
"description": "=A phishing email was reported by {{ $('Set Email Variables').item.json.recipient }} with the subject line \"{{ $('Set Email Variables').item.json.subject }}\"\n\\\\\nh2. Here is ChatGPT's analysis of the email:\n{{ $json.message.content.summary }}"
}
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "BZmmGUrNIsgM9fDj",
"name": "New Jira Cloud"
}
},
"typeVersion": 1
},
{
"id": "a5a66a0e-9d8a-45a9-b1ae-aec78ddfec27",
"name": "Create Potentially Benign Ticket",
"type": "n8n-nodes-base.jira",
"position": [
3640,
580
],
"parameters": {
"project": {
"__rl": true,
"mode": "list",
"value": "10001",
"cachedResultName": "Support"
},
"summary": "=Potentially Benign - Phishing Email Reported: \"{{ $('Set Email Variables').item.json.subject }}\"",
"issueType": {
"__rl": true,
"mode": "list",
"value": "10008",
"cachedResultName": "Task"
},
"additionalFields": {
"description": "=A phishing email was reported by {{ $('Set Email Variables').item.json.recipient }} with the subject line \"{{ $('Set Email Variables').item.json.subject }}\"\n\\\\\nh2. Here is ChatGPT's analysis of the email:\n{{ $json.message.content.summary }}"
}
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "BZmmGUrNIsgM9fDj",
"name": "New Jira Cloud"
}
},
"typeVersion": 1
},
{
"id": "5af0d60b-d021-4dd9-98f7-b2842800764a",
"name": "Rename Screenshot",
"type": "n8n-nodes-base.code",
"position": [
4020,
480
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "$('Retrieve Screenshot').item.binary.data.fileName = 'emailScreenshot.png'\n\nreturn $('Retrieve Screenshot').item;"
},
"typeVersion": 2
},
{
"id": "441c4cbb-bd93-4213-bd34-e18f2a49389f",
"name": "Set Jira ID",
"type": "n8n-nodes-base.set",
"position": [
3860,
480
],
"parameters": {
"options": {},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "4c71188c-011d-4f8e-a36c-87900bfab59a",
"name": "Upload Screenshot of Email to Jira",
"type": "n8n-nodes-base.jira",
"position": [
4220,
480
],
"parameters": {
"issueKey": "={{ $('Set Jira ID').item.json.key }}",
"resource": "issueAttachment"
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "BZmmGUrNIsgM9fDj",
"name": "New Jira Cloud"
}
},
"typeVersion": 1
},
{
"id": "3c031c34-8306-44e1-8e0e-a584c5323112",
"name": "Upload Email Body to Jira",
"type": "n8n-nodes-base.jira",
"position": [
4620,
480
],
"parameters": {
"issueKey": "={{ $('Set Jira ID').item.json.key }}",
"resource": "issueAttachment"
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "BZmmGUrNIsgM9fDj",
"name": "New Jira Cloud"
}
},
"typeVersion": 1
},
{
"id": "d033dcbd-7ccb-451f-ab81-cc6d32d2e01f",
"name": "Convert Email Body to File",
"type": "n8n-nodes-base.convertToFile",
"position": [
2420,
420
],
"parameters": {
"options": {
"fileName": "emailBody.txt"
},
"operation": "toText",
"sourceProperty": "textBody"
},
"typeVersion": 1.1
},
{
"id": "bda5e2fe-d8c0-456b-975a-35e82ff02816",
"name": "Set Email Variables",
"type": "n8n-nodes-base.set",
"position": [
2240,
420
],
"parameters": {
"options": {},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "54ecd8ab-ac4a-4b6b-bd1b-bf8c70082a33",
"name": "Rename Email Body Screenshot",
"type": "n8n-nodes-base.code",
"position": [
4420,
480
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "$('Convert Email Body to File').item.binary.data.fileName = 'emailBody.txt'\n\nreturn $('Convert Email Body to File').item;"
},
"typeVersion": 2
},
{
"id": "fe5b82cc-b4bb-4c97-9477-075d5a280e9f",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2574.536755825029,
0
],
"parameters": {
"color": 7,
"width": 376.8280004374956,
"height": 595.590013880477,
"content": "![hctiapi](https://uploads.n8n.io/templates/hctiapi2.png)\n## Email Body Screenshot Creation\n\nThe **Screenshot HTML** node sends the email's HTML body to the **hcti.io** API, generating a screenshot that visually represents the email's layout. The **Retrieve Screenshot** node then fetches this image, making it available for attachment or review in subsequent steps. This dual-format processing ensures both clarity and flexibility in email analysis workflows."
},
"typeVersion": 1
},
{
"id": "86b21049-f65e-4c6a-a854-c4376f870da9",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1380,
-149.99110983560342
],
"parameters": {
"color": 7,
"width": 814.4556539379754,
"height": 444.5525554815556,
"content": "![Gmail](https://uploads.n8n.io/templates/gmail.png)\n## Gmail Integration and Data Extraction\n\nThis section of the workflow connects to a Gmail account using the **Gmail Trigger** node, capturing incoming emails in real-time, with checks performed every minute. Once an email is detected, its key components—such as the subject, recipient, body, and headers—are extracted and assigned to variables using the **Set Gmail Variables** node. These variables are structured for subsequent analysis and processing in later steps."
},
"typeVersion": 1
},
{
"id": "b1a786cf-7a8d-49e1-90ed-31f3d0e65b13",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1380,
308
],
"parameters": {
"color": 7,
"width": 809.7918597571277,
"height": 602.9002284617277,
"content": "![Gmail](https://uploads.n8n.io/templates/outlook.png)\n## Microsoft Outlook Integration and Email Header Processing\n\nThis section enables the integration of Microsoft Outlook to monitor and capture incoming emails. The Microsoft Outlook Trigger node checks for new messages every minute. Once an email is detected, the Retrieve Headers of Email node fetches detailed header and body content via the Microsoft Graph API. The Format Headers node organizes the email headers into a structured format using a JavaScript function, ensuring clarity and readiness for further processing. Finally, the Set Outlook Variables node extracts and assigns key details—such as the email subject, recipient, body, and formatted headers—to variables for use in subsequent workflow steps. This section is essential for processing Outlook emails and preparing them for analysis and reporting.\n\n\n\n\n\n\n"
},
"typeVersion": 1
},
{
"id": "e7ace035-b5f5-4ef3-a117-22c7c938868d",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2958.4325220284563,
24.744924120002338
],
"parameters": {
"color": 7,
"width": 593.0990401534098,
"height": 573.1750519720028,
"content": "![hctiapi](https://uploads.n8n.io/templates/openai.png)\n## AI-Powered Email Analysis and Threat Detection\n\nThis section leverages ChatGPT for advanced email content and header analysis to determine potential phishing threats. The **Analyze Email with ChatGPT** node processes the email's HTML body and headers, generating a detailed JSON response that categorizes the email as malicious or benign. The response includes a verbose explanation, formatted for Jira, outlining the reasons for the classification. The **Check if Malicious** node evaluates the AI output to determine the next steps based on the email's threat status. If flagged as malicious, subsequent actions like reporting and ticket creation are triggered. This section ensures precise, AI-driven analysis to enhance email security workflows."
},
"typeVersion": 1
},
{
"id": "02c1ad8e-f952-42d2-ae9f-cf3a77e49e52",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
3562.4948140707697,
-125.79607719303533
],
"parameters": {
"color": 7,
"width": 1251.7025543502837,
"height": 891.579206098173,
"content": "![hctiapi](https://uploads.n8n.io/templates/jira.png)\n## Automated Jira Ticket Creation and Email Attachment\n\nThis section streamlines the process of logging phishing email reports in Jira, complete with detailed analysis and attachments. The workflow creates two distinct Jira tickets depending on the AI classification of the email:\n\n1. **Potentially Malicious**: The **Create Potentially Malicious Ticket** node generates a ticket if the email is flagged as a phishing attempt, including a summary of ChatGPT's analysis and the emails details.\n2. **Potentially Benign**: If the email is classified as safe, the **Create Potentially Benign Ticket** node logs a ticket with similar details but under a non-malicious category.\n\n\nThe **Set Jira ID** node ensures the generated ticket's ID is tracked for subsequent operations. Attachments are handled efficiently:\n\n- **Rename Screenshot** prepares the email screenshot for upload.\n- **Upload Screenshot of Email to Jira** adds the screenshot to the Jira ticket for visual context.\n- **Rename Email Body Screenshot** and **Upload Email Body to Jira** manage the attachment of the email's text body as a `.txt` file.\n\n\nThis section enhances reporting by automating ticket creation, ensuring all relevant email data is readily available for review by security teams."
},
"typeVersion": 1
},
{
"id": "597ef23e-c61c-4e27-8c14-74ec20079c96",
"name": "Check if Malicious",
"type": "n8n-nodes-base.if",
"position": [
3400,
420
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "493f412c-5f11-4173-8940-90f5bc7f5fab",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.message.content.malicious }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "af512af9-924b-4019-bdf9-62aac9cd0dac",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
2200,
39.041733604283195
],
"parameters": {
"color": 7,
"width": 365.6458805720866,
"height": 559.8072303111675,
"content": "![n8n](https://uploads.n8n.io/templates/n8n.png)\n## Email Body Conversion\n\nThis section processes the email body into both text and visual formats for detailed analysis and reporting. The **Set Email Variables** node organizes the email's data, including its HTML body and text content, to prepare it for further steps. The **Convert Email Body to File** node creates a `.txt` file containing the plain text version of the email body, useful for documentation or further analysis."
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Set Jira ID": {
"main": [
[
{
"node": "Rename Screenshot",
"type": "main",
"index": 0
}
]
]
},
"Gmail Trigger": {
"main": [
[
{
"node": "Set Gmail Variables",
"type": "main",
"index": 0
}
]
]
},
"Format Headers": {
"main": [
[
{
"node": "Set Outlook Variables",
"type": "main",
"index": 0
}
]
]
},
"Screenshot HTML": {
"main": [
[
{
"node": "Retrieve Screenshot",
"type": "main",
"index": 0
}
]
]
},
"Rename Screenshot": {
"main": [
[
{
"node": "Upload Screenshot of Email to Jira",
"type": "main",
"index": 0
}
]
]
},
"Check if Malicious": {
"main": [
[
{
"node": "Create Potentially Malicious Ticket",
"type": "main",
"index": 0
}
],
[
{
"node": "Create Potentially Benign Ticket",
"type": "main",
"index": 0
}
]
]
},
"Retrieve Screenshot": {
"main": [
[
{
"node": "Analyze Email with ChatGPT",
"type": "main",
"index": 0
}
]
]
},
"Set Email Variables": {
"main": [
[
{
"node": "Convert Email Body to File",
"type": "main",
"index": 0
}
]
]
},
"Set Gmail Variables": {
"main": [
[
{
"node": "Set Email Variables",
"type": "main",
"index": 0
}
]
]
},
"Set Outlook Variables": {
"main": [
[
{
"node": "Set Email Variables",
"type": "main",
"index": 0
}
]
]
},
"Microsoft Outlook Trigger": {
"main": [
[
{
"node": "Retrieve Headers of Email",
"type": "main",
"index": 0
}
]
]
},
"Retrieve Headers of Email": {
"main": [
[
{
"node": "Format Headers",
"type": "main",
"index": 0
}
]
]
},
"Analyze Email with ChatGPT": {
"main": [
[
{
"node": "Check if Malicious",
"type": "main",
"index": 0
}
]
]
},
"Convert Email Body to File": {
"main": [
[
{
"node": "Screenshot HTML",
"type": "main",
"index": 0
}
]
]
},
"Rename Email Body Screenshot": {
"main": [
[
{
"node": "Upload Email Body to Jira",
"type": "main",
"index": 0
}
]
]
},
"Create Potentially Benign Ticket": {
"main": [
[
{
"node": "Set Jira ID",
"type": "main",
"index": 0
}
]
]
},
"Upload Screenshot of Email to Jira": {
"main": [
[
{
"node": "Rename Email Body Screenshot",
"type": "main",
"index": 0
}
]
]
},
"Create Potentially Malicious Ticket": {
"main": [
[
{
"node": "Set Jira ID",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,575 @@
{
"nodes": [
{
"id": "b73fed9b-d56c-4175-a310-8c09ed51acd2",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
80,
60
],
"parameters": {
"width": 464,
"height": 303,
"content": "## Testing \n\nTesting can be done with CURL or similar.\n\nFor File posting using Form Data\ncurl -X POST \"https://yoururl.com/webhook-test/tool/csv-to-json\" \\\n -H \"Content-Type: text/csv\" \\\n --data-binary @path/to/your/file.csv\n\n\nThis can also be tested using the Test workflow"
},
"typeVersion": 1
},
{
"id": "6ed4b2cc-444f-44e2-ab91-34337acd7a9b",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1680,
580
],
"parameters": {
"color": 4,
"width": 396,
"height": 256,
"content": "## Response\nWhere possible we will be returning a binary object.\n```\nIf there is an error\n```\n{\n \"status\": \"error\",\n \"data\": \"error message to display\"\n}\n```"
},
"typeVersion": 1
},
{
"id": "4eff962e-e636-4704-835a-672ccd705e16",
"name": "Extract From File",
"type": "n8n-nodes-base.extractFromFile",
"onError": "continueErrorOutput",
"position": [
680,
80
],
"parameters": {
"options": {},
"binaryPropertyName": "data0"
},
"typeVersion": 1
},
{
"id": "ccc66f1e-e000-4048-a492-b80fbf8c8fce",
"name": "Error Response",
"type": "n8n-nodes-base.respondToWebhook",
"onError": "continueErrorOutput",
"position": [
1900,
900
],
"parameters": {
"options": {
"responseCode": 500
},
"respondWith": "json",
"responseBody": "{\n \"status\": \"error\",\n \"data\": \"There was a problem converting your CSV. Please refresh the page and try again.\"\n}"
},
"typeVersion": 1
},
{
"id": "a7d34aba-6ded-4cc8-8866-7d4aa6ae3255",
"name": "Success Response",
"type": "n8n-nodes-base.respondToWebhook",
"onError": "continueErrorOutput",
"position": [
1920,
220
],
"parameters": {
"options": {
"responseCode": 200
},
"respondWith": "json",
"responseBody": "={\n \"status\": \"OK\",\n \"data\": {{ JSON.stringify($json.jsondata) }}\n}"
},
"typeVersion": 1
},
{
"id": "3484b148-4ba5-4b54-9401-44010ac31178",
"name": "Change Field",
"type": "n8n-nodes-base.set",
"onError": "continueErrorOutput",
"position": [
680,
320
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "b2e3bec3-221e-4f1d-b439-f75174f68ed1",
"name": "csv",
"type": "string",
"value": "={{ $json.body }}"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "f35635fe-8943-486b-b5fa-4f566dd8f938",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
60,
40
],
"parameters": {
"color": 7,
"width": 2298,
"height": 1027,
"content": ""
},
"typeVersion": 1
},
{
"id": "cede2fad-f0ee-4082-a403-81f6d8eb188e",
"name": "Switch",
"type": "n8n-nodes-base.switch",
"position": [
340,
400
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "File",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "object",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $binary }}",
"rightValue": ""
}
]
},
"renameOutput": true
},
{
"outputKey": "Data/Text",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "8930ce1a-a4cc-4094-b08f-a23a13dec40c",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.headers['content-type'] }}",
"rightValue": "text/plain"
}
]
},
"renameOutput": true
},
{
"outputKey": "appJSON",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "e3108952-daa2-425c-8c70-7d2ce0949e0c",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.headers['content-type'] }}",
"rightValue": "=application/json"
}
]
},
"renameOutput": true
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"typeVersion": 3
},
{
"id": "a2d92aeb-25eb-4d3c-82ad-16d2124099a8",
"name": "Send to Error Channel",
"type": "n8n-nodes-base.slack",
"position": [
2380,
880
],
"webhookId": "d8e1201d-cbcc-4153-a164-51d7b3e17c84",
"parameters": {
"text": ":interrobang: Error in XML to JSON tool",
"select": "channel",
"blocksUi": "={\n\t\"blocks\": [\n{\n\t\t\t\"type\": \"section\",\n\t\t\t\"text\": {\n\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\"text\": \":interrobang: Error in CSV to JSON tool\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"type\": \"section\",\n\t\t\t\"text\": {\n\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\"text\": \"*Time:*\\n{{ $now.format('dd/MM/yyyy HH:mm:ss') }}\\n*Execution ID:*\\n{{ $execution.id }}\\n\"\n\t\t\t},\n\t\t\t\"accessory\": {\n\t\t\t\t\"type\": \"button\",\n\t\t\t\t\"text\": {\n\t\t\t\t\t\"type\": \"plain_text\",\n\t\t\t\t\t\"text\": \"Go to Error\",\n\t\t\t\t\t\"emoji\": true\n\t\t\t\t},\n\t\t\t\t\"value\": \"error\",\n\t\t\t\t\"url\": \"[insert URL here]{{ $workflow.id }}/executions/{{ $execution.id }}\",\n\t\t\t\t\"action_id\": \"button-action\",\n\t\t\t\t\"style\": \"primary\"\n\t\t\t}\n\t\t}\n\t]\n}",
"channelId": {
"__rl": true,
"mode": "id",
"value": "C0832GBAEN4"
},
"messageType": "block",
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.1
},
{
"id": "b21c88d1-6f21-4ada-95ef-8ea91463e7ad",
"name": "Convert Raw Text To CSV",
"type": "n8n-nodes-base.code",
"onError": "continueRegularOutput",
"position": [
940,
300
],
"parameters": {
"jsCode": "const csvData = $input.all()[0]?.json?.csv;\n\n// Use a regex to split on either ',' or ';'\nconst lines = csvData.split(\"\\n\");\nconst headers = lines[0].split(/,|;/);\n\nconst jsonData = lines.slice(1).map((line) => {\n // Split on ',' or ';' for each line\n const data = line.split(/,|;/);\n let obj = {};\n headers.forEach((header, i) => {\n obj[header] = data[i];\n });\n return obj;\n});\n\nif (jsonData.length === 0) {\n throw new Error(\"No data to process\");\n}\n\nreturn jsonData;\n"
},
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "a9803789-0397-4f5f-9cd2-cb630f983efc",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
2380,
40
],
"parameters": {
"color": 7,
"width": 700,
"height": 600,
"content": "## Sample of Raw CSV Data Send\nUse the HTTP request node below to see how to send the Raw CSV data into this workflow. Don't forget to include the \\n's "
},
"typeVersion": 1
},
{
"id": "8fb97224-706b-41de-a7ab-cbe2191436e9",
"name": "Check if Value",
"type": "n8n-nodes-base.if",
"position": [
1180,
300
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d8d4cfda-f384-4154-8ad2-c3eabcb8c7ce",
"operator": {
"type": "string",
"operation": "notExists",
"singleValue": true
},
"leftValue": "={{ $json.error }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "4484f424-429b-449f-85c2-dd6a135972a0",
"name": "Send Raw CSV",
"type": "n8n-nodes-base.httpRequest",
"position": [
2480,
200
],
"parameters": {
"url": "[insert URL here]",
"body": "album, year, US_peak_chart_post\nThe White Stripes, 1999, -\nDe Stijl, 2000, -\nWhite Blood Cells, 2001, 61\nElephant, 2003, 6\nGet Behind Me Satan, 2005, 3\nIcky Thump, 2007, 2\nUnder Great White Northern Lights, 2010, 11\nLive in Mississippi, 2011, -\nLive at the Gold Dollar, 2012, -\nNine Miles from the White City, 2013, -\n",
"method": "POST",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "text/plain"
},
"typeVersion": 4.2
},
{
"id": "70a46bce-32da-4868-a960-3ee1cefbed1f",
"name": "POST",
"type": "n8n-nodes-base.webhook",
"position": [
140,
420
],
"webhookId": "add125c9-1591-4e1c-b68c-8032b99b6010",
"parameters": {
"path": "tool/csv-to-json",
"options": {
"binaryPropertyName": "data"
},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 1.1
},
{
"id": "116cfc2c-6e5f-4367-8c80-e1341e7d196a",
"name": "Aggregate",
"type": "n8n-nodes-base.aggregate",
"position": [
1580,
220
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "jsondata"
},
"typeVersion": 1
},
{
"id": "967dc555-2599-4fb0-b3e1-00164bae4120",
"name": "Aggregate1",
"type": "n8n-nodes-base.aggregate",
"position": [
1580,
360
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "jsondata"
},
"typeVersion": 1
},
{
"id": "51c77def-cdf7-41da-bfd1-e585f0553672",
"name": "Success Response2",
"type": "n8n-nodes-base.respondToWebhook",
"onError": "continueErrorOutput",
"position": [
1900,
400
],
"parameters": {
"options": {
"responseCode": 200
},
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json.jsondata) }}"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"POST": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "Extract From File",
"type": "main",
"index": 0
}
],
[
{
"node": "Change Field",
"type": "main",
"index": 0
}
],
[
{
"node": "Error Response",
"type": "main",
"index": 0
}
],
[
{
"node": "Error Response",
"type": "main",
"index": 0
}
]
]
},
"Aggregate": {
"main": [
[
{
"node": "Success Response",
"type": "main",
"index": 0
}
]
]
},
"Aggregate1": {
"main": [
[
{
"node": "Success Response2",
"type": "main",
"index": 0
}
]
]
},
"Change Field": {
"main": [
[
{
"node": "Convert Raw Text To CSV",
"type": "main",
"index": 0
}
],
[
{
"node": "Error Response",
"type": "main",
"index": 0
}
]
]
},
"Check if Value": {
"main": [
[
{
"node": "Aggregate1",
"type": "main",
"index": 0
}
],
[
{
"node": "Error Response",
"type": "main",
"index": 0
}
]
]
},
"Error Response": {
"main": [
[
{
"node": "Send to Error Channel",
"type": "main",
"index": 0
}
],
[
{
"node": "Send to Error Channel",
"type": "main",
"index": 0
}
]
]
},
"Success Response": {
"main": [
[],
[
{
"node": "Send to Error Channel",
"type": "main",
"index": 0
}
]
]
},
"Extract From File": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
],
[
{
"node": "Error Response",
"type": "main",
"index": 0
}
]
]
},
"Success Response2": {
"main": [
[],
[
{
"node": "Send to Error Channel",
"type": "main",
"index": 0
}
]
]
},
"Convert Raw Text To CSV": {
"main": [
[
{
"node": "Check if Value",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,733 @@
{
"meta": {
"instanceId": "e4f78845dfed9ddcfba1945ae00d12e9a7d76eab052afd19299228ce02349d86"
},
"nodes": [
{
"id": "23291d25-3e1a-4b0d-9b1d-d066e8c04a1f",
"name": "Customer Lead AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-640,
460
],
"parameters": {
"text": "=**System Prompt:**\n\nYou are an AI assistant designed to process new leads and generate appropriate responses. Your role includes analyzing lead notes, categorizing them, and generating an email from the system to inform the relevant contact about the inquiry. Do not send the email as if it is directly from the customer; instead, draft it as a notification from the system summarizing the inquiry.\n\n### **Process Flow**\n\n1. **Analyzing Lead Notes:**\n - Extract key details such as the customer name, organization, contact information, and their specific request. \n - Determine if the inquiry relates to products, services, or solutions offered by the company.\n\n2. **Finding the Appropriate Contact(s):**\n - Search the contact database to find the responsible person(s) for the relevant product, service, or solution. \n - If one person is responsible, provide their email. \n - If multiple people are responsible, list all emails separated by commas.\n\n3. **Generating an Email Notification:**\n - Draft a professional email as a notification from the system.\n - Summarize the customers inquiry.\n - Include all relevant details to assist the recipient in addressing the inquiry.\n\n4. **Handling Invalid Leads:**\n - If the inquiry is unrelated to products, services, or solutions (e.g., job inquiries or general product inquiries), classify it as invalid and return: \n `\"Invalid Lead - Not related to products, services, or solutions.\"`\n\n### **Output Requirements**\n\n1. **For Relevant Leads:**\n - **Email Address(es):** Provide the appropriate email(s). \n - **Email Message Body:** Generate an email notification from the system summarizing the inquiry.\n\n2. **For Invalid Leads:**\n - Return: `\"Invalid Lead - Not related to products, services, or solutions.\"`\n\n\n### **Email Template for Relevant Leads**\n\n**Email Address(es):** [Relevant Email IDs]\n\n**Email Message Body:**\n\n_Subject: New Inquiry from Customer Regarding [Product/Service/Solution]_ \n\nDear [Recipient(s)], \n\nWe have received a new inquiry from a customer through our system. Below are the details: \n\n**Customer Name:** [Customer Name] \n**Organization:** [Organization Name] \n**Contact Information:** [Contact Details] \n\n**Inquiry Summary:** \n[Summarized description of the customer's request, e.g., “The customer is seeking to upgrade their restroom facilities with touchless soap dispensers and tissue holders installed behind mirrors. They have requested a site visit to assess the location and provide a proposal.”] \n\n**Action Required:** \nPlease prioritize this inquiry and reach out to the customer promptly to address their requirements. \n\nThank you, \n[Your System Name] \n\n\n### **Example Output**\n\n**Input Lead Notes:**\n*\"Dear Syncbricks, We are looking to Develop Workflow Automation Soluition for our company, can you let us know the details what do you offer in tems of this.\"*\n\n**Output:**\n\n- **Email Address(es):** employee@syncbricks.com\n\n- **Email Message Body:** \n\n_Subject: Workflow Automation Platform Integration_ \n\nDear -Emploiyee Name (s) --, \n\nWe have received a new inquiry from a customer through our system. Below are the details: \n\n**Customer Name:** Amjid Ali \n**Organization:** Syncbricks LLC\n**Contact Information:** 123456789 \n\n**Inquiry Summary:** \nThe customer is asking for workflow automation for their company \n\n**Action Required:** \nPlease prioritize this inquiry and reach out to the customer promptly to address their requirements. \n\nThank you, \nSyncbricks LLC\n\n---\nHere are the Lead Details\nLead Name : {{ $json.data.lead_name }}\nCompany : {{ $json.data.company_name }}\nSource : {{ $json.data.source }}\nNotes : {{ $json.data.notes }}\nCity : {{ $json.data.city }}\nCountry : {{ $json.data.country }}\nMobile : {{ $json.data.mobile_no }}",
"options": {},
"promptType": "define"
},
"typeVersion": 1.7
},
{
"id": "1831dc36-910b-4a72-a90e-b411f105a8c3",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-800,
800
],
"parameters": {
"options": {}
},
"credentials": {
"openAiApi": {
"id": "hTl3a2XqteCwExYY",
"name": "OpenAi account"
}
},
"typeVersion": 1
},
{
"id": "79713c56-2f7c-4872-90e4-331715f54048",
"name": "Abbriviations",
"type": "n8n-nodes-base.googleSheetsTool",
"position": [
-640,
800
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1gtdrAe-jjQH9gQdXA9PJ5y3dSAN4i6k_Rs5sDyALIfU/edit#gid=0",
"cachedResultName": "abbrivaitions"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1gtdrAe-jjQH9gQdXA9PJ5y3dSAN4i6k_Rs5sDyALIfU",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1gtdrAe-jjQH9gQdXA9PJ5y3dSAN4i6k_Rs5sDyALIfU/edit?usp=drivesdk",
"cachedResultName": "Abbriviations List"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "L3lApjbQfMm36LLX",
"name": "Google Sheets account"
}
},
"typeVersion": 4.5
},
{
"id": "73b1e3c9-4703-4f87-8399-e7a9bf368d4c",
"name": "Lead Body",
"type": "n8n-nodes-base.set",
"position": [
-1640,
640
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "82a674a2-4d12-45f2-b276-cc95cf7b2e93",
"name": "body",
"type": "object",
"value": "={{ $json.body }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "5f25d846-c639-49e5-bea2-160000bfb104",
"name": "Source Website and Status Open",
"type": "n8n-nodes-base.if",
"position": [
-1920,
640
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "2b184de2-a64e-44e3-8f25-645539681533",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.source }}",
"rightValue": "Website"
},
{
"id": "9632cf65-11a1-483c-95c8-94bfe84fb243",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.status }}",
"rightValue": "Open"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "12ba65c9-0890-4862-9704-98492eb8f637",
"name": "Microsoft Outlook",
"type": "n8n-nodes-base.microsoftOutlook",
"position": [
1180,
580
],
"parameters": {
"subject": "={{ $('Fields for Outlook').item.json.subject }}",
"bodyContent": "={{ $json.html }}\n<a href=\"https://erpnext.syncbricks.com/app/lead/{{ $('Webhook').item.json.body.name }}\" target=\"_blank\" rel=\"noopener noreferrer\">Here is Lead {{ $('Source Website and Status Open').item.json.body.name }} </a>\n",
"toRecipients": "= {{ $('Fields for Outlook').item.json.email_addresses }}",
"additionalFields": {
"bodyContentType": "html"
}
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "9gy3uvf3pmBdpEsq",
"name": "Microsoft Outlook Al Ansari"
}
},
"typeVersion": 2
},
{
"id": "b1410997-3705-4234-918e-a14e4ccc6b70",
"name": "Email Body Text Generated by AI",
"type": "n8n-nodes-base.set",
"position": [
700,
580
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "cdce31fb-2ec9-45ce-a4ac-a6ff9c811dc3",
"name": "email_body",
"type": "string",
"value": "={{ $json.email_body }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "b10684b9-9f72-42b3-a9f9-c54e711ceb59",
"name": "Fields for Outlook",
"type": "n8n-nodes-base.code",
"position": [
360,
600
],
"parameters": {
"jsCode": "// Input text from the `output` field\nconst textOutput = $json?.output || '';\n\n// Function to extract values from the text\nfunction extractFields(text) {\n const fields = {};\n\n // Regular expressions to extract each field\n const emailMatch = text.match(/\\*\\*Email Address\\(es\\):\\*\\*\\s*([^\\n]+)/);\n const subjectMatch = text.match(/_Subject:\\s*([^_]+)/);\n const emailBodyMatch = text.match(/Dear[\\s\\S]+/);\n\n // Assign extracted values to the fields\n fields.email_addresses = emailMatch ? emailMatch[1].trim() : null;\n fields.subject = subjectMatch ? subjectMatch[1].trim() : null;\n fields.email_body = emailBodyMatch ? emailBodyMatch[0].trim() : null;\n\n return fields;\n}\n\n// Extract fields from the output\nconst extractedFields = extractFields(textOutput);\n\n// Return the fields as JSON\nreturn {\n json: extractedFields\n};\n"
},
"typeVersion": 2
},
{
"id": "e2c10569-fde2-425c-8b20-fdb32a6e2bd5",
"name": "Email Body for Outlook",
"type": "n8n-nodes-base.code",
"position": [
860,
580
],
"parameters": {
"jsCode": "// Input email body\nconst emailBody = $json.email_body || '';\n\n// Function to convert plain text email body into HTML\nfunction formatEmailBodyAsHtml(body) {\n // Replace markdown-like sections with corresponding HTML\n let htmlBody = body\n .replace(/\\*\\*Customer Name:\\*\\* (.+)/, '<p><strong>Customer Name:</strong> $1</p>')\n .replace(/\\*\\*Organization:\\*\\* (.+)/, '<p><strong>Organization:</strong> $1</p>')\n .replace(/\\*\\*Contact Information:\\*\\* (.+)/, '<p><strong>Contact Information:</strong> $1</p>')\n .replace(/\\*\\*Inquiry Summary:\\*\\*\\s*([\\s\\S]+?)(?=\\n\\n\\*\\*Action Required:)/, '<p><strong>Inquiry Summary:</strong> $1</p>')\n .replace(/\\*\\*Action Required:\\*\\*\\s*([\\s\\S]+)/, '<p><strong>Action Required:</strong> $1</p>');\n\n // Wrap each paragraph in `<p>` tags for better readability\n htmlBody = htmlBody\n .replace(/Dear (.+?),/, '<p>Dear <strong>$1</strong>,</p>')\n .replace(/Thank you,\\s+(.+)/, '<p>Thank you,<br><strong>$1</strong></p>');\n\n return htmlBody;\n}\n\n// Convert the email body into HTML\nconst formattedHtmlBody = formatEmailBodyAsHtml(emailBody);\n\n// Return the formatted HTML\nreturn {\n html: formattedHtmlBody\n};\n"
},
"typeVersion": 2
},
{
"id": "3297550b-ed78-4528-ad65-facdc879590a",
"name": "Inquiry has Notes",
"type": "n8n-nodes-base.if",
"position": [
-1080,
640
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "bc81994a-2ad8-4af7-8c58-2c7e58a0fd2e",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $json.data.notes }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "e2544a27-8b6d-4bb0-84f1-00c3a5e66978",
"name": "Inquiry is Valid?",
"type": "n8n-nodes-base.if",
"position": [
40,
620
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "ddd5e8a2-277f-4db6-b38d-28a7b91a2f66",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $json.output }}",
"rightValue": "**Invalid Lead - Not related to products, services, or solutions.**"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "39cc73e7-ceb3-4e8e-a5bc-55648595f784",
"name": "Company Profile",
"type": "n8n-nodes-base.googleDocsTool",
"position": [
-540,
800
],
"parameters": {
"operation": "get",
"documentURL": "you-must-provide-the-doc-id"
},
"credentials": {
"googleDocsOAuth2Api": {
"id": "RdTuYvYpBqEKhIQ3",
"name": "Google Docs account"
}
},
"typeVersion": 2
},
{
"id": "8ee24c59-1acb-4d76-a136-74e69d694a49",
"name": "Company Policies",
"type": "n8n-nodes-base.googleDocsTool",
"position": [
-420,
780
],
"parameters": {
"operation": "get",
"documentURL": "you-must-provide-the-doc-id"
},
"credentials": {
"googleDocsOAuth2Api": {
"id": "RdTuYvYpBqEKhIQ3",
"name": "Google Docs account"
}
},
"typeVersion": 2
},
{
"id": "a5db3aa7-8a77-4553-9c13-a96c51f32745",
"name": "Company Contact Database",
"type": "n8n-nodes-base.googleSheetsTool",
"position": [
-300,
780
],
"parameters": {
"sheetName": {
"__rl": true,
"mode": "list",
"value": "",
"cachedResultUrl": "",
"cachedResultName": ""
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "=Telephone Directory"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "L3lApjbQfMm36LLX",
"name": "Google Sheets account"
}
},
"typeVersion": 4.5
},
{
"id": "f3e73266-faa4-4e6d-8c60-92669d64233b",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2000,
257.53836663807056
],
"parameters": {
"color": 6,
"width": 297.84037615575886,
"height": 643.0692298205195,
"content": "### Filter the Lead\nI have done only for theose which are open and where the source is Website. You can remove this if you want to have all leads."
},
"typeVersion": 1
},
{
"id": "0056e35c-4901-406d-9a95-f6da26808841",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-60,
280
],
"parameters": {
"color": 3,
"width": 302.58963031819115,
"height": 660,
"content": "### Output from AI Agent\nIf the INquiry is invalid, not related to the products and services offered, it will invalidate that you can optionnally link the invalid output to email or anything. Options are limitless"
},
"typeVersion": 1
},
{
"id": "5e0e9561-0fb8-4225-aa59-58e25abc8ca1",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-880,
280.0000000000002
],
"parameters": {
"color": 7,
"width": 764.2159851725196,
"height": 648.5051458745236,
"content": "### Customer Experience Agent (AI)\nNow this Node is an AI Agent who is speicalized to understand the Lead Source and the Inquiry sent by Cusomter. The Agent will look at company information, which has detials of producuts and services defined in Google Docs, and the Contacts Sheet where a column must be added mentioning that who is the person dealing in which products, solutions and services. Once the inquiry is about speicifc product solution and service it will look from the sheet and then will decide to whom the email has to be sent. Details is defined in the Agent.\nMake sure to drag fields from http request node"
},
"typeVersion": 1
},
{
"id": "5a3ca9c9-07c2-4c74-ba8c-6b14f487fc4d",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2420,
260
],
"parameters": {
"color": 5,
"width": 398,
"height": 642,
"content": "### Once the Lead is generatd in ERP\n\nConsider creating creating an inquiry web form in ERPNext and let the Website Visitor fill that Inquiry form, as soon as the iqnuiry form is filled this workflow will start.\n\nMake sure to create a webhook in ERPNext. Follow Below steps in ERPNext.\n\nGo to Wehbooks \nDoctype : Lead\nTrigger : on_insert\n\nPaste this webhook there, as test first and finally production"
},
"typeVersion": 1
},
{
"id": "cf930f52-d06b-40c1-91f5-fa1c3dfee09a",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
618.1625654107004,
260
],
"parameters": {
"color": 4,
"width": 388.6432532629275,
"height": 662,
"content": "### Email Body\nGet only Email body from Previous Node and then Convert this to HTML Format so that it looks professional. \n"
},
"typeVersion": 1
},
{
"id": "a1023b2b-3e0d-486f-9050-8ff98ff060b5",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1440,
260
],
"parameters": {
"color": 5,
"width": 248.905549047384,
"height": 654.6630436071407,
"content": "### Get Details of Lead from ERPNext. For us most important is Notes"
},
"typeVersion": 1
},
{
"id": "732046b2-967a-4e0c-85e4-ae04e8c0f9cf",
"name": "Sticky Note13",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1680,
260
],
"parameters": {
"color": 6,
"width": 222.5278407657604,
"height": 651.0941643427163,
"content": "### Get Lead ID\nThis will extract the Lead Name in ERPNext. Ensure to send doc.name from the webhook in ERPnext\n\nIt will then send this to next node to get full details of this lead."
},
"typeVersion": 1
},
{
"id": "b80448ee-5a15-4569-99e4-c3e616a5600d",
"name": "Sticky Note14",
"type": "n8n-nodes-base.stickyNote",
"position": [
1035.2592266730085,
260
],
"parameters": {
"color": 6,
"width": 399.43186296400074,
"height": 662,
"content": "### Send Email\n\nNow drag and drop the fields from Previous Nodes. Email Addresses Subject and Body.\n\nRemember all fields are selected by AI Agent, whom to send email, what to send and so on. \n\nYou can alternatively inform your employees by whatsapp for quick action."
},
"typeVersion": 1
},
{
"id": "3d190d34-f6e0-47bc-9216-d312d1d6ee38",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2920,
260
],
"parameters": {
"color": 4,
"width": 475.27306699862953,
"height": 636.1483291619771,
"content": "## Developed by Amjid Ali\n\nThank you for using this workflow template. It has taken me countless hours of hard work, research, and dedication to develop, and I sincerely hope it adds value to your work.\n\nIf you find this template helpful, I kindly ask you to consider supporting my efforts. Your support will help me continue improving and creating more valuable resources.\n\nYou can contribute via PayPal here:\n\nhttp://paypal.me/pmptraining\n\nFor Full Course about ERPNext or Automation using AI follow below link\n\nhttp://lms.syncbricks.com\n\nAdditionally, when sharing this template, I would greatly appreciate it if you include my original information to ensure proper credit is given.\n\nThank you for your generosity and support!\nEmail : amjid@amjidali.com\nhttps://linkedin.com/in/amjidali\nhttps://syncbricks.com\nhttps://youtube.com/@syncbricks"
},
"typeVersion": 1
},
{
"id": "cfd7effc-92aa-43c6-9fc5-054b53de74a2",
"name": "Sticky Note16",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1160,
280
],
"parameters": {
"color": 5,
"width": 248.905549047384,
"height": 654.6630436071407,
"content": "### Inquiry with Notes\nIf inquiry is having notes then only it will forward to next node."
},
"typeVersion": 1
},
{
"id": "e5b0992c-e360-4323-82cb-c7ddec45deb5",
"name": "Get Lead Data from ERPNext",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1360,
640
],
"parameters": {
"url": "=https://erpnext.syncbricks.com/api/resource/Lead/{{ $('Source Website and Status Open').item.json.body.name }}",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "erpNextApi"
},
"credentials": {
"erpNextApi": {
"id": "PInpnsxvPkvaiW0z",
"name": "ERPNext account"
}
},
"typeVersion": 4.2
},
{
"id": "87508043-baf5-4fa6-aa38-0f06881dc267",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
280,
280
],
"parameters": {
"color": 3,
"width": 302.58963031819115,
"height": 660,
"content": "### Prepare for Email\nThis node will get approprate Fields for Email \nEmail Addresses:\nSubject : \nEmail Body : "
},
"typeVersion": 1
},
{
"id": "2b4c1e91-c64b-43cb-aba2-c6f8f5a17c79",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-2300,
640
],
"webhookId": "a39ea4e2-99b7-4ae1-baff-9fb370333e2a",
"parameters": {
"path": "new-lead-generated-in-erpnext",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2
}
],
"pinData": {},
"connections": {
"Webhook": {
"main": [
[
{
"node": "Source Website and Status Open",
"type": "main",
"index": 0
}
]
]
},
"Lead Body": {
"main": [
[
{
"node": "Get Lead Data from ERPNext",
"type": "main",
"index": 0
}
]
]
},
"Abbriviations": {
"ai_tool": [
[
{
"node": "Customer Lead AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Company Profile": {
"ai_tool": [
[
{
"node": "Customer Lead AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Company Policies": {
"ai_tool": [
[
{
"node": "Customer Lead AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Inquiry has Notes": {
"main": [
[
{
"node": "Customer Lead AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Inquiry is Valid?": {
"main": [
[
{
"node": "Fields for Outlook",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Customer Lead AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Fields for Outlook": {
"main": [
[
{
"node": "Email Body Text Generated by AI",
"type": "main",
"index": 0
}
]
]
},
"Customer Lead AI Agent": {
"main": [
[
{
"node": "Inquiry is Valid?",
"type": "main",
"index": 0
}
]
]
},
"Email Body for Outlook": {
"main": [
[
{
"node": "Microsoft Outlook",
"type": "main",
"index": 0
}
]
]
},
"Company Contact Database": {
"ai_tool": [
[
{
"node": "Customer Lead AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Get Lead Data from ERPNext": {
"main": [
[
{
"node": "Inquiry has Notes",
"type": "main",
"index": 0
}
]
]
},
"Source Website and Status Open": {
"main": [
[
{
"node": "Lead Body",
"type": "main",
"index": 0
}
]
]
},
"Email Body Text Generated by AI": {
"main": [
[
{
"node": "Email Body for Outlook",
"type": "main",
"index": 0
}
]
]
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,765 @@
{
"meta": {
"instanceId": "e0be1457dbb383bea07059c263a59b383a5b9420e6a22d3e5f1d80ae7f4f6629"
},
"nodes": [
{
"id": "200098a9-1a49-49c1-8703-eea0c496a020",
"name": "Refresh Token",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1300,
100
],
"parameters": {
"url": "https://graph.threads.net/refresh_access_token",
"options": {},
"queryParametersUi": {
"parameter": [
{
"name": "grant_type",
"value": "th_refresh_token"
},
{
"name": "access_token",
"value": "=Your Threads Long-Live Token"
}
]
}
},
"typeVersion": 1
},
{
"id": "58373d28-8f22-4224-8ef1-aca9c24d5777",
"name": "Get Post",
"type": "n8n-nodes-base.httpRequest",
"position": [
-960,
100
],
"parameters": {
"url": "https://graph.threads.net/v1.0/<Your Threads ID>/threads?fields=id,media_product_type,media_type,media_url,permalink,owner,username,text,timestamp,shortcode,thumbnail_url,children,is_quote_post",
"options": {},
"queryParametersUi": {
"parameter": [
{
"name": "since",
"value": "={{ new Date(new Date().setDate(new Date().getDate() - 1)).toISOString().split('T')[0] }}"
},
{
"name": "access_token",
"value": "={{ $json.access_token }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "7d9923b5-2fdc-46d4-8734-fe044a5a8951",
"name": "Get Post ID",
"type": "n8n-nodes-base.function",
"position": [
-640,
100
],
"parameters": {
"functionCode": "// 獲取 API 返回的完整資料 (假設只有一個 \"data\" 陣列)\nconst allData = items[0].json.data;\n\n// 過濾符合條件的貼文:\n// 條件 1: media_type = \"TEXT_POST\" 或 \"VIDEO\"\n// 條件 2: is_quote_post = false\nconst filteredPosts = allData.filter(post => {\n return (\npost.media_type === \"TEXT_POST\" || \npost.media_type === \"IMAGE\" || \npost.media_type === \"VIDEO\" || \npost.media_type === \"CAROUSEL_ALBUM\" || \npost.media_type === \"AUDIO\");\n});\n\n// 抽取所需的欄位id, permalink, timestamp\nconst extractedData = filteredPosts.map(post => {\n return {\n id: post.id,\n type: post.media_type,\n permalink: post.permalink,\n timestamp: post.timestamp,\n };\n});\n\n// 將結果以 n8n 所需格式輸出\nreturn extractedData.map(post => ({ json: post }));\n"
},
"typeVersion": 1
},
{
"id": "95ed0a59-7a6d-4358-aded-7ce49ef04916",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-300,
100
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "77720564-acf5-4a55-afa9-ae559965a5b9",
"name": "Replace Me",
"type": "n8n-nodes-base.noOp",
"position": [
2820,
200
],
"parameters": {},
"typeVersion": 1
},
{
"id": "3b4e5eda-f354-4ef4-a260-378c06708cb5",
"name": "Threads / Comments",
"type": "n8n-nodes-base.httpRequest",
"position": [
0,
180
],
"parameters": {
"url": "=https://graph.threads.net/v1.0/{{ $json.id }}/conversation?fields=id,text,username,permalink,timestamp,media_product_type,media_type,media_url,children{media_url}&reverse=false",
"options": {},
"queryParametersUi": {
"parameter": [
{
"name": "access_token",
"value": "={{ $('Refresh Token').first().json.access_token }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "6331c1f6-e1a4-4749-a17a-c129ab7ab0e0",
"name": "Threads / Root",
"type": "n8n-nodes-base.httpRequest",
"position": [
0,
0
],
"parameters": {
"url": "=https://graph.threads.net/v1.0/{{ $json.id }}?fields=id,media_product_type,media_type,media_url,children{media_url},permalink,owner,username,text,timestamp,children",
"options": {},
"queryParametersUi": {
"parameter": [
{
"name": "access_token",
"value": "={{ $('Refresh Token').first().json.access_token }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "76c518b6-3c21-4879-8f0a-080fab60895a",
"name": "Comment's Filter",
"type": "n8n-nodes-base.code",
"position": [
240,
180
],
"parameters": {
"jsCode": "// 確保 items 是否有內容\nif (!items || items.length === 0) {\n console.log('No items found');\n return [];\n}\n\n// 取得輸入數據\nconst inputData = items[0].json;\nconsole.log('Input data:', JSON.stringify(inputData, null, 2));\n\nif (!inputData || !inputData.data || !Array.isArray(inputData.data)) {\n console.log('Invalid data structure');\n return [];\n}\n\n// 過濾出 username 為 yourThreadsName 的資料\nconst filteredPosts = inputData.data.filter(post => post.username === 'geekaz');\nconsole.log('Filtered posts count:', filteredPosts.length);\n\n// 處理每個 post提取所需的資料\nconst processedData = filteredPosts.map(post => {\n // 初始化 mediaUrls用來存放所有的 media_url\n let mediaUrls = [];\n\n // 如果有 children則提取 children 裡的 media_url\n if (post.children?.data && Array.isArray(post.children.data)) {\n mediaUrls = post.children.data\n .map(child => child.media_url) // 提取每個 child 的 media_url\n .filter(url => url); // 過濾掉 undefined 或 null 的 URL\n } else if (post.media_url) {\n // 如果沒有 children使用最外層的 media_url\n mediaUrls.push(post.media_url);\n }\n\n // 返回每個 post 的處理後結果\n return {\n text: post.text || '',\n media_urls: mediaUrls\n };\n});\n\nconsole.log('Processed data:', JSON.stringify(processedData, null, 2));\n\n// 將結果轉換為 n8n 所需格式\nreturn processedData.map(post => ({ json: post }));"
},
"typeVersion": 2
},
{
"id": "c0cae676-acff-493e-b957-26df0366cf98",
"name": "Root's Filter",
"type": "n8n-nodes-base.code",
"position": [
240,
0
],
"parameters": {
"jsCode": "// 確保 items 是否有內容\nif (!items || items.length === 0) {\n return [];\n}\n\n// 確保 items 的資料結構是正確的\nconst allData = items.map(item => item.json);\n\nconst processedData = allData.map(post => {\n // 初始化 mediaUrls用來存放所有的 media_url\n let mediaUrls = [];\n\n // 如果有 children則提取 children 裡的 media_url\n if (post.children?.data && Array.isArray(post.children.data)) {\n mediaUrls = post.children.data\n .map(child => child.media_url) // 提取每個 child 的 media_url\n .filter(url => url); // 過濾掉 undefined 或 null 的 URL\n } else if (post.media_url) {\n // 如果沒有 children使用最外層的 media_url\n mediaUrls.push(post.media_url);\n }\n\n // 返回每個 post 的處理後結果\n return {\n id: post.id || null,\n username: post.username || null,\n text: post.text || null,\n timestamp: post.timestamp || null,\n media_type: post.media_type || null,\n media_urls: mediaUrls, // 包含所有的媒體 URL\n permalink: post.permalink || null,\n };\n});\n\n// 將結果轉換為 n8n 所需格式\nreturn processedData.map(post => ({ json: post }));\n"
},
"typeVersion": 2
},
{
"id": "367c2475-4dff-4858-9756-ad8f8383521c",
"name": "Threads ID",
"type": "n8n-nodes-base.notion",
"position": [
1060,
100
],
"parameters": {
"simple": false,
"options": {},
"resource": "databasePage",
"operation": "getAll",
"returnAll": true,
"databaseId": {
"__rl": true,
"mode": "list",
"value": "175931b1-f5b8-8047-8620-f0e7ccde8407",
"cachedResultUrl": "https://www.notion.so/175931b1f5b880478620f0e7ccde8407",
"cachedResultName": "Posts Automation"
}
},
"credentials": {
"notionApi": {
"id": "P3mnylwFncmx1P3h",
"name": "Notion account"
}
},
"typeVersion": 2.2,
"alwaysOutputData": false
},
{
"id": "2aaa224f-598b-4f8a-a247-03a873ac19a3",
"name": "Extract IDs",
"type": "n8n-nodes-base.function",
"position": [
1260,
100
],
"parameters": {
"functionCode": "// 檢查輸入是否存在\nif (!items?.length) return [{ json: { threadsIds: [] } }];\n\n// 取得所有頁面\nconst pages = items.map(item => item.json).flat();\nconsole.log('Number of pages:', pages.length);\n\n// 提取所有 Threads ID\nconst threadsIds = pages\n .map(page => {\n if (!page?.properties) return null;\n const threadsIdField = page.properties['Threads ID'];\n if (!threadsIdField?.rich_text?.length) return null;\n return threadsIdField.rich_text[0]?.text?.content || null;\n })\n .filter(Boolean);\n\nconsole.log('Found Threads IDs:', threadsIds);\n\n// 將結果轉換為 n8n 所需格式\nreturn [{ json: { threadsIds } }];"
},
"typeVersion": 1
},
{
"id": "83fdaf18-47e7-4b1c-8f1b-523f87a439f3",
"name": "Compare IDs",
"type": "n8n-nodes-base.function",
"position": [
1500,
100
],
"parameters": {
"functionCode": "// 檢查輸入是否存在\nif (!items?.length) return [{ json: { isExist: false } }];\n\n// 從 Threads Post 節點取得 ID\nconst newId = $('Threads Post').last().json.id;\nconst existingIds = $json.threadsIds || [];\n\n// 檢查是否重複\nconst isExist = existingIds.includes(newId);\n\nreturn [{ json: { isExist } }];\n"
},
"typeVersion": 1
},
{
"id": "f1a831b1-fc5f-4569-9b9b-7de0bce9b9cd",
"name": "Create Page",
"type": "n8n-nodes-base.notion",
"position": [
2080,
20
],
"parameters": {
"simple": false,
"options": {
"iconType": "emoji"
},
"resource": "databasePage",
"databaseId": {
"__rl": true,
"mode": "list",
"value": "175931b1-f5b8-8047-8620-f0e7ccde8407",
"cachedResultUrl": "https://www.notion.so/175931b1f5b880478620f0e7ccde8407",
"cachedResultName": "Posts Automation"
},
"propertiesUi": {
"propertyValues": [
{
"key": "Name|title",
"title": "={{ $('Threads Post').first().json.permalink }}"
},
{
"key": "Threads ID|rich_text",
"textContent": "={{ $('Threads Post').first().json.id }}"
},
{
"key": "Post Date|date",
"date": "={{ $('Threads Post').first().json.timestamp }}",
"timezone": "America/Los_Angeles",
"includeTime": false
},
{
"key": "Source|multi_select",
"multiSelectValue": "=Threads"
},
{
"key": "Import Check|checkbox"
},
{
"key": "Username|rich_text",
"textContent": "={{ $('Threads Post').first().json.username }}"
}
]
}
},
"credentials": {
"notionApi": {
"id": "P3mnylwFncmx1P3h",
"name": "Notion account"
}
},
"typeVersion": 1
},
{
"id": "8a5e4752-a8fa-480f-8271-c15a66679e00",
"name": "Upload Medias",
"type": "n8n-nodes-base.httpRequest",
"position": [
2560,
20
],
"parameters": {
"url": "=https://api.notion.com/v1/blocks/{{ $('Create Page').item.json.id }}/children",
"options": {},
"requestMethod": "PATCH",
"jsonParameters": true,
"bodyParametersJson": "={{ { \"children\": $('Threads Post').last().json.blocks } }}",
"queryParametersJson": "=",
"headerParametersJson": "{\n \"Authorization\": \"bearer Your Notion Token\",\n \"Content-Type\": \"application/json\",\n \"Notion-Version\": \"2022-06-28\"\n}"
},
"typeVersion": 1
},
{
"id": "f3f3a8f7-1137-4013-83dd-b5efc18ab095",
"name": "If Post Exist",
"type": "n8n-nodes-base.switch",
"position": [
1740,
100
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Create Page",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.isExist }}",
"rightValue": "=false"
}
]
},
"renameOutput": true
},
{
"outputKey": "Update Page",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "38564f41-157d-46ed-843f-4e5a43415e21",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.isExist }}",
"rightValue": ""
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "a378c107-d3f5-43cd-bc8c-ad9a39a9ec60",
"name": "Threads Post",
"type": "n8n-nodes-base.code",
"position": [
800,
100
],
"parameters": {
"jsCode": "// 確保 items 是否有內容\nif (!items || items.length === 0) {\n console.log('No items found');\n return [];\n}\n\n// 取得所有貼文\nconst posts = items.map(item => item.json).flat();\nconsole.log('Number of posts:', posts.length);\n\n// 取得第一篇貼文的基本資訊\nconst firstPost = posts[0] || {};\n\n// 生成 blocks 結構\nconst blocks = [];\n\n// 處理每個貼文\nposts.forEach(post => {\n // 如果有文字,加入文字區塊\n if (post.text) {\n blocks.push({\n object: \"block\",\n type: \"paragraph\",\n paragraph: {\n rich_text: [{\n type: \"text\",\n text: { content: post.text }\n }]\n }\n });\n }\n \n // 如果有媒體連結,加入 embed 區塊\n if (post.media_urls && post.media_urls.length > 0) {\n post.media_urls.forEach(url => {\n blocks.push({\n object: \"block\",\n type: \"embed\",\n embed: { url }\n });\n });\n }\n});\n\n// 合併基本資訊和 blocks\nconst combinedPost = {\n id: firstPost.id || '',\n permalink: firstPost.permalink || '',\n username: firstPost.username || '',\n timestamp: firstPost.timestamp || '',\n blocks\n};\n\n// 將結果轉換為 n8n 所需格式\nreturn [{ json: combinedPost }];"
},
"typeVersion": 2
},
{
"id": "d2e6c8dd-5751-48f9-a158-c3b39f279f60",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
540,
100
],
"parameters": {},
"typeVersion": 3
},
{
"id": "a79e46eb-bb45-4d80-9f99-adae1e51f94d",
"name": "Run This First to Get Long Live Access Token",
"type": "n8n-nodes-base.httpRequest",
"position": [
-940,
-340
],
"parameters": {
"url": "https://graph.threads.net/access_token",
"options": {},
"queryParametersUi": {
"parameter": [
{
"name": "grant_type",
"value": "th_exchange_token"
},
{
"name": "client_secret",
"value": "=Threads App Secret"
},
{
"name": "access_token",
"value": "=Short Live Access Token"
}
]
}
},
"typeVersion": 1
},
{
"id": "6b7a17d2-c58c-45f6-9ab1-1e39fbc7e18c",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1260,
-380
],
"parameters": {
"height": 240,
"content": "## Get Threads API Access Token\n\nGet Threads Access Token Tutorial and ID/教學 [Link](https://nijialin.com/2024/08/17/python-threads-sdk-introduction/)\n\nPlease get your access token and Threads ID first before you start\n(It only need to run once)"
},
"typeVersion": 1
},
{
"id": "a8b5b6f0-b2ec-4aa3-bd9d-375acffd6655",
"name": "Get Posts Schedule",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-1660,
100
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.2
},
{
"id": "a86f7373-3a98-4ec2-bf66-88dd835ad17f",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1360,
260
],
"parameters": {
"height": 180,
"content": "## Refresh Token\n\nUpdate your long live token here / 在此放上剛剛拿到的長期 Token\n\n[Check Facebook Docs Refresh Token](https://developers.facebook.com/docs/threads/get-started/long-lived-tokens/)"
},
"typeVersion": 1
},
{
"id": "1b9b7fe0-78d3-4a70-8df7-a06b0c0f6fda",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1020,
260
],
"parameters": {
"height": 600,
"content": "## Set your Theads ID & Post Time\n\nChage the your with your Threads ID to get your posts / 請先透過上方教學獲取 Threads ID\n\nSet the time of the Post you wanna get / 設置抓取的貼文時間\n\n[Check Facebook Docs Post API](https://developers.facebook.com/docs/threads/threads-media)\n\nsince is scrape the post after the date /\nsince 為抓取日期之後的貼文\n\nuntil is scrape the post before the date /\nuntil 為抓取日期之前的貼文\n\nsince can set\n\n{{ new Date(new Date().setDate(new Date().getDate() - 1)).toISOString().split('T')[0] }}\n\nit will scrape the post since one day ago"
},
"typeVersion": 1
},
{
"id": "eed94a4e-7fc4-4a23-8581-d5903e7a2ec4",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1000,
-60
],
"parameters": {
"height": 140,
"content": "## Set Notion Acc\n\nSet your notion account and database you wanna save the post"
},
"typeVersion": 1
},
{
"id": "51bada43-0a37-48fa-b5f6-18731f605afb",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
2000,
-140
],
"parameters": {
"height": 140,
"content": "## Create Page\n\nBefore create page, please the properties of your post by your demands"
},
"typeVersion": 1
},
{
"id": "144b494d-515a-44e1-9720-35cc50d457da",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
2500,
-200
],
"parameters": {
"height": 200,
"content": "## Support Medias\n\nIt also can scrape the Threads Media like Images and Videos\n\nUpdate your Notion token here:\n\nbearer <your notion token>"
},
"typeVersion": 1
},
{
"id": "44657b1e-6537-4344-9f78-3e9ef440e27b",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2360,
80
],
"parameters": {
"width": 600,
"height": 180,
"content": "## Get your Threads Post automatically\n\nCreator: [Geekaz](https://www.threads.net/@geekaz?hl=zh-tw)\n\nIf your have any problems or question, please send message to my instagram!\n有任何問題都歡迎透過 Instagram 私訊詢問!"
},
"typeVersion": 1
},
{
"id": "6eeb4af1-7c4f-4f63-8386-384fd3549459",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
180,
400
],
"parameters": {
"height": 140,
"content": "## Comment's Filter\n\nSet your Threads Username"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Merge": {
"main": [
[
{
"node": "Threads Post",
"type": "main",
"index": 0
}
]
]
},
"Get Post": {
"main": [
[
{
"node": "Get Post ID",
"type": "main",
"index": 0
}
]
]
},
"Replace Me": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Threads ID": {
"main": [
[
{
"node": "Extract IDs",
"type": "main",
"index": 0
}
]
]
},
"Compare IDs": {
"main": [
[
{
"node": "If Post Exist",
"type": "main",
"index": 0
}
]
]
},
"Create Page": {
"main": [
[
{
"node": "Upload Medias",
"type": "main",
"index": 0
}
]
]
},
"Extract IDs": {
"main": [
[
{
"node": "Compare IDs",
"type": "main",
"index": 0
}
]
]
},
"Get Post ID": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Threads Post": {
"main": [
[
{
"node": "Threads ID",
"type": "main",
"index": 0
}
]
]
},
"If Post Exist": {
"main": [
[
{
"node": "Create Page",
"type": "main",
"index": 0
}
],
[
{
"node": "Replace Me",
"type": "main",
"index": 0
}
]
]
},
"Refresh Token": {
"main": [
[
{
"node": "Get Post",
"type": "main",
"index": 0
}
]
]
},
"Root's Filter": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Upload Medias": {
"main": [
[
{
"node": "Replace Me",
"type": "main",
"index": 0
}
]
]
},
"Threads / Root": {
"main": [
[
{
"node": "Root's Filter",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "Threads / Root",
"type": "main",
"index": 0
},
{
"node": "Threads / Comments",
"type": "main",
"index": 0
}
]
]
},
"Comment's Filter": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Get Posts Schedule": {
"main": [
[
{
"node": "Refresh Token",
"type": "main",
"index": 0
}
]
]
},
"Threads / Comments": {
"main": [
[
{
"node": "Comment's Filter",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,683 @@
{
"nodes": [
{
"id": "835afb8f-5bb3-42da-9694-d04646a80cef",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [
0,
0
],
"parameters": {},
"typeVersion": 1
},
{
"id": "1e85bf4f-52d5-4ec0-8d0b-a1deeb30c9c6",
"name": "Call Rapid API",
"type": "n8n-nodes-base.httpRequest",
"position": [
880,
0
],
"parameters": {
"url": "https://fresh-linkedin-profile-data.p.rapidapi.com/get-linkedin-profile",
"options": {},
"sendQuery": true,
"sendHeaders": true,
"queryParameters": {
"parameters": [
{
"name": "linkedin_url",
"value": "={{ $json[\"Linkedin Profile\"] }}"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "x-rapidapi-key"
},
{
"name": "x-rapidapi-host"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "9fa011f4-d1fe-46d2-abda-28ae33929874",
"name": "Filter already enriched",
"type": "n8n-nodes-base.filter",
"position": [
440,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "5907d2f7-b15d-41cc-8fee-45631bb874e1",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.about }}",
"rightValue": ""
},
{
"id": "2857554e-a635-43d3-bf9e-a617b85009ca",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.linkedin_url }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "3f0b5717-38b4-4371-b3fa-9f19acf3e624",
"name": "Encode URI",
"type": "n8n-nodes-base.set",
"position": [
660,
0
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "fd914708-c85f-4c0e-a277-d8164c616699",
"name": "Linkedin Profile",
"type": "string",
"value": "={{ encodeURI($json.linkedin_url) }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "632e2555-5474-4d00-85f0-e95ee984c0dd",
"name": "FiIter out all arrays",
"type": "n8n-nodes-base.code",
"position": [
1100,
0
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Initialize an empty object to store filtered items\nlet filteredData = {};\n\n// Loop through each item in $input.item.json.data\nfor (const item in $input.item.json.data) {\n // Check if the item is not an array\n if (!Array.isArray($input.item.json.data[item])) {\n // Add the item to the filteredData object\n filteredData[item] = $input.item.json.data[item];\n }\n}\nfilteredData['row_number'] = $('Pull linkedin profiles').first().json.row_number\n// Return the filteredData array\nreturn filteredData;"
},
"typeVersion": 2
},
{
"id": "24b27c51-0f22-400c-bdc3-a09186c74639",
"name": "Update the profile",
"type": "n8n-nodes-base.googleSheets",
"position": [
1320,
0
],
"parameters": {
"columns": {
"value": {},
"schema": [
{
"id": "linkedin_url",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "linkedin_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "about",
"type": "string",
"display": true,
"required": false,
"displayName": "about",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "city",
"type": "string",
"display": true,
"required": false,
"displayName": "city",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "company",
"type": "string",
"display": true,
"required": false,
"displayName": "company",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "company_description",
"type": "string",
"display": true,
"required": false,
"displayName": "company_description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "company_domain",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "company_domain",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "company_employee_range",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "company_employee_range",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "company_industry",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "company_industry",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "company_linkedin_url",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "company_linkedin_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "company_logo_url",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "company_logo_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "company_website",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "company_website",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "company_year_founded",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "company_year_founded",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "connection_count",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "connection_count",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "country",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "country",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "current_company_join_month",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "current_company_join_month",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "current_company_join_year",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "current_company_join_year",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "current_job_duration",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "current_job_duration",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "email",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "first_name",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "first_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "follower_count",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "follower_count",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "full_name",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "full_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "headline",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "headline",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "hq_city",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "hq_city",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "hq_country",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "hq_country",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "hq_region",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "hq_region",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "job_title",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "job_title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "languages",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "languages",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "last_name",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "last_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "location",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "location",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "phone",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "profile_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "profile_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "profile_image_url",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "profile_image_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "public_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "public_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "school",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "school",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "state",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "state",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "urn",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "urn",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "autoMapInputData",
"matchingColumns": [
"linkedin_url"
]
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/10cSUaj-YZhrgwXLIGpJzLjv6RMN6cYiw9EK-rNw0-AM/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "10cSUaj-YZhrgwXLIGpJzLjv6RMN6cYiw9EK-rNw0-AM",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/10cSUaj-YZhrgwXLIGpJzLjv6RMN6cYiw9EK-rNw0-AM/edit?usp=drivesdk",
"cachedResultName": "Linkedin contact info"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "gdLmm513ROUyH6oU",
"name": "Google Sheets account"
}
},
"typeVersion": 4.5
},
{
"id": "41e0e213-a1f4-47ff-aebd-6cd08df06eae",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
-200
],
"parameters": {
"color": 4,
"width": 220,
"height": 380,
"content": "## Create a Google sheet\nWith just one column named \"linkedin_url\" and fill it with the profiles you want to enrich"
},
"typeVersion": 1
},
{
"id": "da28d424-10ce-499d-95c9-81979dab0f6b",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
780,
-300
],
"parameters": {
"color": 4,
"width": 300,
"height": 480,
"content": "## Call RapidAPI Fresh Linkedin Profile Data\nYou have to create an account in [RapidAPI](https://rapidapi.com) and subscribe to Fresh LinkedIn Profile Data. With a free account you will be able to scrape 100 profile / month.\nAfter your subscription you will have to replace the header values: \"x-rapidapi-key\" and \"x-rapidapi-host\" with the values given in the RapidAPI interface\n"
},
"typeVersion": 1
},
{
"id": "2bae0a2a-0c88-465b-854d-728280539e90",
"name": "Pull linkedin profiles",
"type": "n8n-nodes-base.googleSheets",
"position": [
220,
0
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/10cSUaj-YZhrgwXLIGpJzLjv6RMN6cYiw9EK-rNw0-AM/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "10cSUaj-YZhrgwXLIGpJzLjv6RMN6cYiw9EK-rNw0-AM",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/10cSUaj-YZhrgwXLIGpJzLjv6RMN6cYiw9EK-rNw0-AM/edit?usp=drivesdk",
"cachedResultName": "Linkedin contact info"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "gdLmm513ROUyH6oU",
"name": "Google Sheets account"
}
},
"typeVersion": 4.5
},
{
"id": "d93a0d4c-1db8-4604-85e1-7d02bbbdcdb8",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-500,
-760
],
"parameters": {
"color": 7,
"width": 460,
"height": 1160,
"content": "### LinkedIn Profile Enrichment Workflow\n\n#### Who is this for?\n\nThis workflow is ideal for recruiters, sales professionals, and marketing teams who need to enrich LinkedIn profiles with additional data for lead generation, talent sourcing, or market research.\n\n#### What problem is this workflow solving?\n\nManually gathering detailed LinkedIn profile information can be time-consuming and prone to errors. This workflow automates the process of enriching profile data from LinkedIn, saving time and ensuring accuracy.\n\n#### What this workflow does\n\n1. **Input**: Reads LinkedIn profile URLs from a Google Sheet.\n2. **Validation**: Filters out already enriched profiles to avoid redundant processing.\n3. **Data Enrichment**: Uses RapidAPI's Fresh LinkedIn Profile Data API to retrieve detailed profile information.\n4. **Output**: Updates the Google Sheet with enriched profile data, appending new information efficiently.\n\n#### Setup\n\n1. **Google Sheet**: Create a sheet with a column named `linkedin_url` and populate it with the profile URLs to enrich.\n2. **RapidAPI Account**: Sign up at [RapidAPI](https://rapidapi.com) and subscribe to the Fresh LinkedIn Profile Data API.\n3. **API Integration**: Replace the `x-rapidapi-key` and `x-rapidapi-host` values with your credentials from RapidAPI.\n4. **Run the Workflow**: Trigger the workflow and monitor the updates to your Google Sheet.\n\n#### How to customize this workflow\n\n* **Filter Criteria**: Modify the filter step to include additional conditions for processing profiles.\n* **API Configuration**: Adjust API parameters to retrieve specific fields or extend usage.\n* **Output Format**: Customize how the enriched data is appended to the Google Sheet (e.g., format, column mappings).\n* **Error Handling**: Add steps to handle API rate limits or missing data for smoother automation.\n\nThis workflow streamlines LinkedIn profile enrichment, making it faster and more effective for data-driven decision-making."
},
"typeVersion": 1
}
],
"connections": {
"Encode URI": {
"main": [
[
{
"node": "Call Rapid API",
"type": "main",
"index": 0
}
]
]
},
"Call Rapid API": {
"main": [
[
{
"node": "FiIter out all arrays",
"type": "main",
"index": 0
}
]
]
},
"Update the profile": {
"main": [
[]
]
},
"FiIter out all arrays": {
"main": [
[
{
"node": "Update the profile",
"type": "main",
"index": 0
}
]
]
},
"Pull linkedin profiles": {
"main": [
[
{
"node": "Filter already enriched",
"type": "main",
"index": 0
}
]
]
},
"Filter already enriched": {
"main": [
[
{
"node": "Encode URI",
"type": "main",
"index": 0
}
]
]
},
"When clicking Test workflow": {
"main": [
[
{
"node": "Pull linkedin profiles",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,693 @@
{
"nodes": [
{
"id": "a768bce6-ae26-464c-95fc-009edea4f94d",
"name": "Set your company's variables",
"type": "n8n-nodes-base.set",
"position": [
440,
0
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "6a8063b6-1fd8-429a-9f13-b7512066c702",
"name": "your_company_name",
"type": "string",
"value": "Pollup Data Services"
},
{
"id": "3e6780d6-86d0-4353-aa17-8470a91f63a8",
"name": "your_company_activity",
"type": "string",
"value": "Whether its automating recurring tasks, analysing data faster, or personalising customer interactions, we build bespoke AI agents to help your workforce work smarter."
},
{
"id": "1b42f1b3-20ed-4278-952d-f28fe0f03fa3",
"name": "your_email",
"type": "string",
"value": "thomas@pollup.net"
},
{
"id": "7c109ba2-d855-49d5-8700-624b01a05bc1",
"name": "your_name",
"type": "string",
"value": "Justin"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "ca729f8d-cab8-4221-addb-aa23813d80b4",
"name": "Get linkedin Posts",
"type": "n8n-nodes-base.httpRequest",
"position": [
1300,
0
],
"parameters": {
"url": "https://fresh-linkedin-profile-data.p.rapidapi.com/get-profile-posts",
"options": {},
"sendQuery": true,
"sendHeaders": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "linkedin_url",
"value": "={{ $('Google Sheets Trigger').item.json.linkedin_url }}"
},
{
"name": "type",
"value": "posts"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "x-rapidapi-host",
"value": "fresh-linkedin-profile-data.p.rapidapi.com"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"id": "nhoVFnkO31mejJrI",
"name": "RapidAPI Key"
}
},
"typeVersion": 4.2
},
{
"id": "b9559958-f8ac-4ab6-93c6-50eb04113808",
"name": "Get twitter ID",
"type": "n8n-nodes-base.httpRequest",
"position": [
680,
0
],
"parameters": {
"url": "https://twitter-api47.p.rapidapi.com/v2/user/by-username",
"options": {},
"sendQuery": true,
"sendHeaders": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "username",
"value": "={{ $('Google Sheets Trigger').item.json.twitter_handler }}"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "x-rapidapi-host",
"value": "twitter-api47.p.rapidapi.com"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"id": "nhoVFnkO31mejJrI",
"name": "RapidAPI Key"
}
},
"typeVersion": 4.2
},
{
"id": "3e85565f-ebfa-4568-9391-869961c5b3ed",
"name": "Get tweets",
"type": "n8n-nodes-base.httpRequest",
"position": [
880,
0
],
"parameters": {
"url": "https://twitter-api47.p.rapidapi.com/v2/user/tweets",
"options": {},
"sendQuery": true,
"sendHeaders": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "userId",
"value": "={{ $json.rest_id }}"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "x-rapidapi-host",
"value": "twitter-api47.p.rapidapi.com"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"id": "nhoVFnkO31mejJrI",
"name": "RapidAPI Key"
}
},
"typeVersion": 4.2
},
{
"id": "6e060b21-9eaf-49e6-9665-c051b3f2397e",
"name": "Extract and limit Linkedin",
"type": "n8n-nodes-base.code",
"position": [
1520,
0
],
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\noutput = []\nmax_posts = 10\nlet counter = 0\nfor (const item of $input.all()[0].json.data) {\n let post = {\n title: item.article_title,\n text: item.text\n }\n output.push(post)\n if(counter++ >= max_posts) break;\n}\n\nreturn {\"linkedIn posts\": output};"
},
"typeVersion": 2
},
{
"id": "e65bc472-e7c6-43c5-8e84-fe8c4512e92f",
"name": "Exract and limit X",
"type": "n8n-nodes-base.code",
"position": [
1100,
0
],
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\noutput = []\nmax_posts = 10\nlet counter = 0\nfor (const item of $input.all()[0].json.tweets) {\n if(!item.content.hasOwnProperty('itemContent')) continue\n let post = {\n text: item.content.itemContent?.tweet_results?.result.legacy?.full_text\n }\n console.log(post)\n output.push(post)\n if(counter++ >= max_posts) break;\n}\n\nreturn {\"Twitter tweets\": output};"
},
"typeVersion": 2
},
{
"id": "10f088a0-0479-428e-96cf-fe0df9b37877",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1740,
200
],
"parameters": {
"model": "gpt-4o",
"options": {}
},
"credentials": {
"openAiApi": {
"id": "yepsCCAriRlCkICW",
"name": "OpenAi account"
}
},
"typeVersion": 1
},
{
"id": "9adfd648-8348-4a0a-8b9b-d54dc3b715bb",
"name": "Structured Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
1920,
220
],
"parameters": {
"jsonSchemaExample": "{\n \"subject\": \"\",\n \"cover_letter\": \"\"\n}"
},
"typeVersion": 1.2
},
{
"id": "af96003c-539d-4728-832c-4819d85bbbcc",
"name": "Generate Subject and cover letter based on match",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
1720,
0
],
"parameters": {
"text": "=## Me\n- My company name is: {{ $('Set your company\\'s variables').item.json.your_company_name }}\n- My company's activity is: {{ $('Set your company\\'s variables').item.json.your_company_activity }}\n- My name is: {{ $('Set your company\\'s variables').item.json.your_name }}\n- My email is: {{ $('Set your company\\'s variables').item.json.your_email }}\n\n## My lead:\nHis name: {{ $('Google Sheets Trigger').item.json.name }}\n\n## What I want you to do\n- According to the info about me, and the linkedin posts an twitter post of a user given below, I want you to find a common activity that I could propose to this person and generate a cover letter about it\n- Return ONLY the cover letter and the subject as a json like this:\n{\n \"subject\": \"\",\n \"cover_letter\": \"\"\n}\n\nTHe cover letter should be in HTML format\n\n## The Linkedin Posts:\n{{ JSON.stringify($json[\"linkedIn posts\"])}}\n\n## THe Twitter posts:\n{{ JSON.stringify($('Exract and limit X').item.json['Twitter tweets']) }}\n",
"messages": {
"messageValues": [
{
"message": "You are a helpful Marketing assistant"
}
]
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.5
},
{
"id": "6954285f-7ea5-4e3d-8be2-03051d716d03",
"name": "Send Cover letter and CC me",
"type": "n8n-nodes-base.emailSend",
"position": [
2080,
0
],
"parameters": {
"html": "={{ $json.output.cover_letter }}",
"options": {},
"subject": "={{ $json.output.subject }}",
"toEmail": "={{ $('Google Sheets Trigger').item.json.email }}, {{ $('Set your company\\'s variables').item.json.your_email }}",
"fromEmail": "thomas@pollup.net"
},
"credentials": {
"smtp": {
"id": "yrsGGdbYvSB8u7sx",
"name": "SMTP account"
}
},
"typeVersion": 2.1
},
{
"id": "357477a8-98c3-48a5-8c88-965f90a4beb2",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
360,
-280
],
"parameters": {
"color": 4,
"height": 480,
"content": "## Personalize here\n\n### Set: \n- your name\n- your company name\n- your company activity, used to find a match with your leads\n- your email, used as the sender"
},
"typeVersion": 1
},
{
"id": "0c26383c-c8f1-44b1-995e-2c88118061bb",
"name": "Google Sheets Trigger",
"type": "n8n-nodes-base.googleSheetsTrigger",
"position": [
-40,
20
],
"parameters": {
"options": {
"dataLocationOnSheet": {
"values": {
"rangeDefinition": "specifyRange"
}
}
},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IcvbbG_WScVNyutXhzqyE9NxdxNbY90Dd63R8Y1UrAw/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1IcvbbG_WScVNyutXhzqyE9NxdxNbY90Dd63R8Y1UrAw",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IcvbbG_WScVNyutXhzqyE9NxdxNbY90Dd63R8Y1UrAw/edit?usp=drivesdk",
"cachedResultName": "Analyze social media of a lead"
}
},
"credentials": {
"googleSheetsTriggerOAuth2Api": {
"id": "LBJHhfLqklwl9les",
"name": "Google Sheets Trigger account"
}
},
"typeVersion": 1
},
{
"id": "923cca3d-69a9-4d26-80a3-e9062d42d8a8",
"name": "Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
2280,
0
],
"parameters": {
"columns": {
"value": {
"done": "X",
"linkedin_url": "={{ $('Google Sheets Trigger').item.json.linkedin_url }}"
},
"schema": [
{
"id": "linkedin_url",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "linkedin_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "name",
"type": "string",
"display": true,
"required": false,
"displayName": "name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "twitter_handler",
"type": "string",
"display": true,
"required": false,
"displayName": "twitter_handler",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "email",
"type": "string",
"display": true,
"required": false,
"displayName": "email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "done",
"type": "string",
"display": true,
"required": false,
"displayName": "done",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"linkedin_url"
]
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IcvbbG_WScVNyutXhzqyE9NxdxNbY90Dd63R8Y1UrAw/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1IcvbbG_WScVNyutXhzqyE9NxdxNbY90Dd63R8Y1UrAw",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1IcvbbG_WScVNyutXhzqyE9NxdxNbY90Dd63R8Y1UrAw/edit?usp=drivesdk",
"cachedResultName": "Analyze social media of a lead"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "gdLmm513ROUyH6oU",
"name": "Google Sheets account"
}
},
"typeVersion": 4.5
},
{
"id": "6df02119-09db-4d87-b435-7753693b27aa",
"name": "If",
"type": "n8n-nodes-base.if",
"position": [
180,
20
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "3839b337-6c33-4907-ba75-8ef04cefc14c",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.done }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"executeOnce": false,
"typeVersion": 2.2,
"alwaysOutputData": true
},
{
"id": "2edaa85e-ef69-490c-9835-cf8779cada6d",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-120,
-320
],
"parameters": {
"color": 4,
"width": 260,
"height": 500,
"content": "## Create a Gooogle sheet with the following columns:\n- linkedin_url\n- name\n- twitter_handler \n- email\n- done\n\nAnd put some data in it except in \"done\" that should remain empty."
},
"typeVersion": 1
},
{
"id": "19210bba-1db1-4568-b34e-4e9de002b0eb",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1680,
-160
],
"parameters": {
"color": 5,
"width": 340,
"height": 300,
"content": "## Here you can modify the prompt\n- make it better by adding some examples\n- Follow a known framework\netc."
},
"typeVersion": 1
},
{
"id": "bebab4e5-35fa-49b7-bb85-a85231c44389",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
660,
-280
],
"parameters": {
"color": 4,
"width": 340,
"height": 480,
"content": "## Call RapidAPI Twitter API Profile Data\nYou have to create an account in [RapidAPI](https://rapidapi.com/restocked-gAGxip8a_/api/twitter-api47) and subscribe to Twiiter API. With a free account you will be able to scrape 500 tweets / month.\nAfter your subscription you will have to choose as Generic Auth Type: Header Auth and then put as header name: \"x-rapidapi-key\" and the value given in the RapidAPI interface\n"
},
"typeVersion": 1
},
{
"id": "42df4665-2d46-4020-938c-f082db6f09d0",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1220,
-300
],
"parameters": {
"color": 4,
"width": 280,
"height": 480,
"content": "## Call RapidAPI Fresh Linkedin Profile Data\nYou have to create an account in [RapidAPI](https://rapidapi.com) and subscribe to Fresh LinkedIn Profile Data. With a free account you will be able to scrape 100 profile / month.\nAfter your subscription you will have to choose as Generic Auth Type: Header Auth and then put as header name: \"x-rapidapi-key\" and the value given in the RapidAPI interface\n"
},
"typeVersion": 1
},
{
"id": "4a14febd-bd82-428c-8c97-15f1ba724b02",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-840,
-620
],
"parameters": {
"width": 700,
"height": 1180,
"content": "## Social Media Analysis and Automated Email Generation\n\n> by Thomas Vie [Thomas@pollup.net](mailto:thomas@pollup.net)\n\n### **Who is this for?**\nThis template is ideal for marketers, lead generation specialists, and business professionals seeking to analyze social media profiles of potential leads and automate personalized email outreach efficiently.\n\n\n### **What problem is this workflow solving?**\nManually analyzing social media profiles and crafting personalized emails can be time-consuming and prone to errors. This workflow streamlines the process by integrating social media APIs with AI to generate tailored communication, saving time and increasing outreach effectiveness.\n\n### **What this workflow does:**\n1. **Google Sheets Integration:** Start with a Google Sheet containing lead information such as LinkedIn URL, Twitter handle, name, and email.\n2. **Social Media Data Extraction:** Automatically fetch profile and activity data from Twitter and LinkedIn using RapidAPI integrations.\n3. **AI-Powered Content Generation:** Use OpenAI's Chat Model to analyze the extracted data and generate personalized email subject lines and cover letters.\n4. **Automated Email Dispatch:** Send the generated email directly to the lead, with a copy sent to yourself for tracking purposes.\n5. **Progress Tracking:** Update the Google Sheet to indicate completed actions.\n\n#### **Setup:**\n1. **Google Sheets:**\n - Create a sheet with the columns: LinkedIn URL, name, Twitter handle, email, and a \"done\" column for tracking.\n - Populate the sheet with your leads.\n\n2. **RapidAPI Accounts:**\n - Sign up for RapidAPI and subscribe to the Twitter and LinkedIn API plans.\n - Configure API authentication keys in the workflow.\n\n3. **AI Configuration:**\n - Connect OpenAI Chat Model with your API key for text generation.\n\n4. **Email Integration:**\n - Add your email credentials or service (SMTP or third-party service like Gmail) for sending automated emails.\n\n#### **How to customize this workflow to your needs:**\n- **Modify the AI Prompt:** Adapt the prompt in the AI node to better align with your tone, style, or specific messaging framework.\n- **Expand Data Fields:** Add additional data fields in Google Sheets if you require further personalization.\n- **API Limits:** Adjust API configurations to fit your usage limits or upgrade to higher tiers for increased data scraping capabilities.\n- **Personalize Email Templates:** Tweak email formats to suit different audiences or use cases.\n- **Extend Functionality:** Integrate additional social media platforms or CRM tools as needed.\n\nBy implementing this workflow, youll save time on repetitive tasks and create more effective lead generation strategies."
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"If": {
"main": [
[
{
"node": "Set your company's variables",
"type": "main",
"index": 0
}
]
]
},
"Get tweets": {
"main": [
[
{
"node": "Exract and limit X",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets": {
"main": [
[]
]
},
"Get twitter ID": {
"main": [
[
{
"node": "Get tweets",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Generate Subject and cover letter based on match",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Exract and limit X": {
"main": [
[
{
"node": "Get linkedin Posts",
"type": "main",
"index": 0
}
]
]
},
"Get linkedin Posts": {
"main": [
[
{
"node": "Extract and limit Linkedin",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets Trigger": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "Generate Subject and cover letter based on match",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Extract and limit Linkedin": {
"main": [
[
{
"node": "Generate Subject and cover letter based on match",
"type": "main",
"index": 0
}
]
]
},
"Send Cover letter and CC me": {
"main": [
[
{
"node": "Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"Set your company's variables": {
"main": [
[
{
"node": "Get twitter ID",
"type": "main",
"index": 0
}
]
]
},
"Generate Subject and cover letter based on match": {
"main": [
[
{
"node": "Send Cover letter and CC me",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,823 @@
{
"meta": {
"instanceId": "a7dcffb2764d1b10c84b837267686e7094bf753c8ca242421ba2029587943438",
"templateId": "2652"
},
"nodes": [
{
"id": "42cc4260-626e-4f83-b1c3-c78c99b78b38",
"name": "On clicking 'execute'",
"type": "n8n-nodes-base.manualTrigger",
"position": [
820,
486.1164603611751
],
"parameters": {},
"typeVersion": 1
},
{
"id": "f21386ff-f8db-4f5d-a44c-15484d1e4ab7",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
380,
866.1164603611751
],
"parameters": {
"color": 6,
"width": 2547,
"height": 751,
"content": "## Subworkflow"
},
"typeVersion": 1
},
{
"id": "82851e4a-33a1-461b-965f-f51efcb5af90",
"name": "n8n",
"type": "n8n-nodes-base.n8n",
"position": [
1080,
580
],
"parameters": {
"filters": {},
"requestOptions": {}
},
"credentials": {
"n8nApi": {
"id": "hYWXj2T43Yhf6coc",
"name": "Hirempire"
}
},
"typeVersion": 1
},
{
"id": "90cac8e2-9509-4d48-9038-bb653ffbdf9d",
"name": "Return",
"type": "n8n-nodes-base.set",
"position": [
2720,
1080
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "8d513345-6484-431f-afb7-7cf045c90f4f",
"name": "Done",
"type": "boolean",
"value": true
}
]
}
},
"typeVersion": 3.3
},
{
"id": "11046021-89ba-4e61-b03f-d606e7dd0a56",
"name": "Get File",
"type": "n8n-nodes-base.httpRequest",
"position": [
1820,
960
],
"parameters": {
"url": "={{ $json.download_url }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "08af670c-ac82-422f-9938-c649dfdfbcf6",
"name": "If file too large",
"type": "n8n-nodes-base.if",
"position": [
1620,
980
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "45ce825e-9fa6-430c-8931-9aaf22c42585",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.content }}",
"rightValue": ""
},
{
"id": "9619a55f-7fb1-4f24-b1a7-7aeb82365806",
"operator": {
"type": "string",
"operation": "notExists",
"singleValue": true
},
"leftValue": "={{ $json.error }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2
},
{
"id": "795fd895-94b2-46f1-b559-748b0db01c49",
"name": "Merge Items",
"type": "n8n-nodes-base.merge",
"position": [
1620,
1240
],
"parameters": {},
"typeVersion": 2
},
{
"id": "3d3399f3-bbfb-48ab-8644-91b28e731026",
"name": "isDiffOrNew",
"type": "n8n-nodes-base.code",
"position": [
1820,
1240
],
"parameters": {
"jsCode": "const orderJsonKeys = (jsonObj) => {\n const ordered = {};\n Object.keys(jsonObj).sort().forEach(key => {\n ordered[key] = jsonObj[key];\n });\n return ordered;\n}\n\n// Check if file returned with content\nif (Object.keys($input.all()[0].json).includes(\"content\")) {\n // Decode base64 content and parse JSON\n const origWorkflow = JSON.parse(Buffer.from($input.all()[0].json.content, 'base64').toString());\n const n8nWorkflow = $input.all()[1].json;\n \n // Order JSON objects\n const orderedOriginal = orderJsonKeys(origWorkflow);\n const orderedActual = orderJsonKeys(n8nWorkflow);\n\n // Determine difference\n if (JSON.stringify(orderedOriginal) === JSON.stringify(orderedActual)) {\n $input.all()[0].json.github_status = \"same\";\n } else {\n $input.all()[0].json.github_status = \"different\";\n $input.all()[0].json.n8n_data_stringy = JSON.stringify(orderedActual, null, 2);\n }\n $input.all()[0].json.content_decoded = orderedOriginal;\n// No file returned / new workflow\n} else if (Object.keys($input.all()[0].json).includes(\"data\")) {\n const origWorkflow = JSON.parse($input.all()[0].json.data);\n const n8nWorkflow = $input.all()[1].json;\n \n // Order JSON objects\n const orderedOriginal = orderJsonKeys(origWorkflow);\n const orderedActual = orderJsonKeys(n8nWorkflow);\n\n // Determine difference\n if (JSON.stringify(orderedOriginal) === JSON.stringify(orderedActual)) {\n $input.all()[0].json.github_status = \"same\";\n } else {\n $input.all()[0].json.github_status = \"different\";\n $input.all()[0].json.n8n_data_stringy = JSON.stringify(orderedActual, null, 2);\n }\n $input.all()[0].json.content_decoded = orderedOriginal;\n\n} else {\n // Order JSON object\n const n8nWorkflow = $input.all()[1].json;\n const orderedActual = orderJsonKeys(n8nWorkflow);\n \n // Proper formatting\n $input.all()[0].json.github_status = \"new\";\n $input.all()[0].json.n8n_data_stringy = JSON.stringify(orderedActual, null, 2);\n}\n\n// Return items\nreturn $input.all();"
},
"typeVersion": 1
},
{
"id": "2f2f42d0-d27c-4856-a263-4d5e9eda2c4c",
"name": "Check Status",
"type": "n8n-nodes-base.switch",
"position": [
2040,
1240
],
"parameters": {
"rules": {
"rules": [
{
"value2": "same"
},
{
"output": 1,
"value2": "different"
},
{
"output": 2,
"value2": "new"
}
]
},
"value1": "={{$json.github_status}}",
"dataType": "string"
},
"typeVersion": 1
},
{
"id": "5316029f-f32f-4a8d-95de-50ee57051a08",
"name": "Same file - Do nothing",
"type": "n8n-nodes-base.noOp",
"position": [
2260,
1080
],
"parameters": {},
"typeVersion": 1
},
{
"id": "37c5983b-48fe-41d5-8e3a-eb56dec2140b",
"name": "File is different",
"type": "n8n-nodes-base.noOp",
"position": [
2260,
1240
],
"parameters": {},
"typeVersion": 1
},
{
"id": "a4dcce9e-b0d0-4b9e-ab16-9142e641c73d",
"name": "File is new",
"type": "n8n-nodes-base.noOp",
"position": [
2260,
1400
],
"parameters": {},
"typeVersion": 1
},
{
"id": "03fcfdc4-2e52-42f0-a129-3ebaf8dd8fc1",
"name": "Create new file",
"type": "n8n-nodes-base.github",
"position": [
2480,
1400
],
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "={{ $('Globals').item.json.repo.owner }}"
},
"filePath": "={{ $('Globals').item.json.repo.path }}{{$('Execute Workflow Trigger').first().json.id}}.json",
"resource": "file",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $('Globals').item.json.repo.name }}"
},
"fileContent": "={{$('isDiffOrNew').item.json[\"n8n_data_stringy\"]}}",
"commitMessage": "={{$('Execute Workflow Trigger').first().json.name}} ({{$json.github_status}})"
},
"credentials": {
"githubApi": {
"id": "YDLAGVFazg3z5vF9",
"name": "islamnazmi"
}
},
"typeVersion": 1
},
{
"id": "dd35cc39-4ab4-4d53-b439-b425a2177e8f",
"name": "Edit existing file",
"type": "n8n-nodes-base.github",
"position": [
2480,
1220
],
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "={{ $('Globals').item.json.repo.owner }}"
},
"filePath": "={{ $('Globals').item.json.repo.path }}{{$('Execute Workflow Trigger').first().json.id}}.json",
"resource": "file",
"operation": "edit",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $('Globals').item.json.repo.name }}"
},
"fileContent": "={{$('isDiffOrNew').item.json[\"n8n_data_stringy\"]}}",
"commitMessage": "={{$('Execute Workflow Trigger').first().json.name}} ({{$json.github_status}})"
},
"credentials": {
"githubApi": {
"id": "YDLAGVFazg3z5vF9",
"name": "islamnazmi"
}
},
"typeVersion": 1
},
{
"id": "d05e2a25-24be-43fb-baa4-9c3391840e70",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
1280,
586.1164603611751
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "2a139d59-1387-4899-88b3-21106cd01099",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
820,
686.1164603611751
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 7
}
]
}
},
"typeVersion": 1.2
},
{
"id": "04e6c245-3117-4ef8-a181-754e616e958b",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
380,
240
],
"parameters": {
"color": 4,
"width": 371.1995072042308,
"height": 600.88409546716,
"content": "## Backup to GitHub \nThis workflow will backup all instance workflows to GitHub.\n\nThe files are saved `ID.json` for the filename.\n\n### Setup\nOpen `Globals` node and update the values below 👇\n\n- **repo.owner:** your Github username\n- **repo.name:** the name of your repository\n- **repo.path:** the folder to use within the repository. If it doesn't exist it will be created.\n\n\nIf your username was `john-doe` and your repository was called `n8n-backups` and you wanted the workflows to go into a `workflows` folder you would set:\n\n- repo.owner - john-doe\n- repo.name - n8n-backups\n- repo.path - workflows/\n\n\nThe workflow calls itself using a subworkflow, to help reduce memory usage."
},
"typeVersion": 1
},
{
"id": "3d996985-0064-4749-85a1-2191c73746c9",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
780,
406.1164603611751
],
"parameters": {
"color": 7,
"width": 886.4410237965205,
"height": 434.88564057365943,
"content": "## Main workflow loop"
},
"typeVersion": 1
},
{
"id": "c9bfa393-e120-4bfe-b957-702756b91aaf",
"name": "Get file data",
"type": "n8n-nodes-base.github",
"position": [
1420,
980
],
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "={{ $json.repo.owner }}"
},
"filePath": "={{ $json.repo.path }}{{ $('Execute Workflow Trigger').item.json.id }}.json",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $json.repo.name }}"
},
"asBinaryProperty": false,
"additionalParameters": {}
},
"credentials": {
"githubApi": {
"id": "YDLAGVFazg3z5vF9",
"name": "islamnazmi"
}
},
"typeVersion": 1,
"continueOnFail": true,
"alwaysOutputData": true
},
{
"id": "d42ddc37-3bd9-4f19-8831-695bec4d0137",
"name": "Globals",
"type": "n8n-nodes-base.set",
"position": [
1200,
1140
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "6cf546c5-5737-4dbd-851b-17d68e0a3780",
"name": "repo.owner",
"type": "string",
"value": "islamnazmi"
},
{
"id": "452efa28-2dc6-4ea3-a7a2-c35d100d0382",
"name": "repo.name",
"type": "string",
"value": "n8n"
},
{
"id": "81c4dc54-86bf-4432-a23f-22c7ea831e74",
"name": "repo.path",
"type": "string",
"value": "=workflows/{{ $json.tags[0].name }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "e970c63c-2aa2-46f9-be04-f045b6a938de",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1180,
1020
],
"parameters": {
"color": 4,
"width": 150,
"height": 80,
"content": "## Edit this node 👇"
},
"typeVersion": 1
},
{
"id": "5b1991f7-0351-44de-908d-9aa8b8262d60",
"name": "Execute Workflow Trigger",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
480,
1320
],
"parameters": {
"inputSource": "passthrough"
},
"typeVersion": 1.1
},
{
"id": "8e5b3f71-0c5e-4e78-a3f7-0b574c9ddf06",
"name": "Execute Workflow",
"type": "n8n-nodes-base.executeWorkflow",
"position": [
1500,
580
],
"parameters": {
"mode": "each",
"options": {},
"workflowId": {
"__rl": true,
"mode": "id",
"value": "={{ $workflow.id }}"
},
"workflowInputs": {
"value": {},
"schema": [],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
}
},
"typeVersion": 1.2
},
{
"id": "399bd193-4886-4292-be71-6f996f00a6d2",
"name": "/",
"type": "n8n-nodes-base.set",
"position": [
960,
1040
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "12cad226-e091-4bbb-aed9-a8e01311772c",
"name": "tags[0].name",
"type": "string",
"value": "={{ $('Execute Workflow Trigger').item.json.tags[0].name }}/"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "e90328e1-4ada-424b-879a-20fb2a7270c0",
"name": "tag?",
"type": "n8n-nodes-base.switch",
"position": [
720,
1140
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "tag",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "object",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $json.tags[0] }}",
"rightValue": ""
}
]
},
"renameOutput": true
},
{
"outputKey": "none",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "2656fbe3-fe35-4770-9c03-9a455ec618e4",
"operator": {
"type": "object",
"operation": "notExists",
"singleValue": true
},
"leftValue": "={{ $json.tags[0] }}",
"rightValue": ""
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
}
],
"pinData": {},
"connections": {
"/": {
"main": [
[
{
"node": "Globals",
"type": "main",
"index": 0
}
]
]
},
"n8n": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"tag?": {
"main": [
[
{
"node": "/",
"type": "main",
"index": 0
}
],
[
{
"node": "Globals",
"type": "main",
"index": 0
}
]
]
},
"Globals": {
"main": [
[
{
"node": "Get file data",
"type": "main",
"index": 0
}
]
]
},
"Get File": {
"main": [
[
{
"node": "Merge Items",
"type": "main",
"index": 0
}
]
]
},
"File is new": {
"main": [
[
{
"node": "Create new file",
"type": "main",
"index": 0
}
]
]
},
"Merge Items": {
"main": [
[
{
"node": "isDiffOrNew",
"type": "main",
"index": 0
}
]
]
},
"isDiffOrNew": {
"main": [
[
{
"node": "Check Status",
"type": "main",
"index": 0
}
]
]
},
"Check Status": {
"main": [
[
{
"node": "Same file - Do nothing",
"type": "main",
"index": 0
}
],
[
{
"node": "File is different",
"type": "main",
"index": 0
}
],
[
{
"node": "File is new",
"type": "main",
"index": 0
}
]
]
},
"Get file data": {
"main": [
[
{
"node": "If file too large",
"type": "main",
"index": 0
}
]
]
},
"Create new file": {
"main": [
[
{
"node": "Return",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "Execute Workflow",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "n8n",
"type": "main",
"index": 0
}
]
]
},
"File is different": {
"main": [
[
{
"node": "Edit existing file",
"type": "main",
"index": 0
}
]
]
},
"If file too large": {
"main": [
[
{
"node": "Get File",
"type": "main",
"index": 0
}
],
[
{
"node": "Merge Items",
"type": "main",
"index": 0
}
]
]
},
"Edit existing file": {
"main": [
[
{
"node": "Return",
"type": "main",
"index": 0
}
]
]
},
"On clicking 'execute'": {
"main": [
[
{
"node": "n8n",
"type": "main",
"index": 0
}
]
]
},
"Same file - Do nothing": {
"main": [
[
{
"node": "Return",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow Trigger": {
"main": [
[
{
"node": "Merge Items",
"type": "main",
"index": 1
},
{
"node": "tag?",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,192 @@
{
"nodes": [
{
"id": "4ca55c6e-cf2e-4239-82a9-88d0a201e761",
"name": "List upgradable packages",
"type": "n8n-nodes-base.ssh",
"notes": "apt list --upgradable",
"position": [
-280,
0
],
"parameters": {
"command": "apt list --upgradable"
},
"credentials": {
"sshPassword": {
"id": "Ps31IKTeseWFlA0g",
"name": "SSH Password account"
}
},
"notesInFlow": true,
"typeVersion": 1,
"alwaysOutputData": false
},
{
"id": "ae1f0a55-31aa-494b-baa6-822dc606188e",
"name": "Send Email through SMTP",
"type": "n8n-nodes-base.emailSend",
"position": [
380,
0
],
"webhookId": "8073c571-b36f-4330-a510-ca2ff2924fbf",
"parameters": {
"html": "=The following packages can be updated on your server:\n\n{{ $json.htmlList }}\n\nPlease login and perform upgrade.",
"options": {},
"subject": "Server needs updates",
"toEmail": "change.me@example.com",
"fromEmail": "change.me@example.com"
},
"credentials": {
"smtp": {
"id": "uiNePdJaDng5a43S",
"name": "SMTP account"
}
},
"typeVersion": 2.1
},
{
"id": "e1d76671-d94c-40d5-9364-623db9319f11",
"name": "Run workflow every day",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-540,
0
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.2
},
{
"id": "ec4d722a-b88c-42da-971c-28ad5774596d",
"name": "Format as HTML list",
"type": "n8n-nodes-base.code",
"position": [
-60,
0
],
"parameters": {
"jsCode": "function formatStdoutAsHtmlList(stdoutData) {\n\n // Split the stdout into lines and map to HTML list items\n const htmlListItems = stdoutData.split('\\n').map((line) => {\n if (line.trim() && line !== \"Listing...\") { // Optionally skip empty lines or headers\n return `<li>${line.trim()}</li>`;\n }\n }).filter(item => item); // Remove any undefined items due to empty lines or skipped headers\n\n // Wrap the list items in a <ul> tag\n const htmlList = `<ul>${htmlListItems.join('')}</ul>`;\n\n // Return the formatted HTML list as part of an object\n return { \"htmlList\": htmlList };\n}\n\nreturn formatStdoutAsHtmlList($input.first().json.stdout);"
},
"typeVersion": 2
},
{
"id": "6f14eb02-c505-4f83-a5bb-68094e763fd9",
"name": "Check if there are updates",
"type": "n8n-nodes-base.if",
"position": [
140,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "db66d892-26fb-406c-a0ac-2e4b8a60310a",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $json.htmlList }}",
"rightValue": "<ul></ul>"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "3924c696-5b0e-4ae2-b2e2-435fed344028",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-740,
-180
],
"parameters": {
"width": 300,
"content": "## VPS upgrade notify \nThis workflow will everyday check if server has upgradable packages and inform you by email if there is."
},
"typeVersion": 1
},
{
"id": "bb8ade2a-4ffe-4c79-91eb-55af568eb1b1",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
380,
-180
],
"parameters": {
"width": 300,
"content": "## Update email addresses\nUpdate From and To email addresses in this node to receive notifications"
},
"typeVersion": 1
}
],
"connections": {
"Format as HTML list": {
"main": [
[
{
"node": "Check if there are updates",
"type": "main",
"index": 0
}
]
]
},
"Run workflow every day": {
"main": [
[
{
"node": "List upgradable packages",
"type": "main",
"index": 0
}
]
]
},
"Send Email through SMTP": {
"main": [
[]
]
},
"List upgradable packages": {
"main": [
[
{
"node": "Format as HTML list",
"type": "main",
"index": 0
}
]
]
},
"Check if there are updates": {
"main": [
[
{
"node": "Send Email through SMTP",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,456 @@
{
"id": "2pMoIW58KP6ZeGir",
"meta": {
"instanceId": "ecc960f484e18b0e09045fd93acf0d47f4cfff25cc212ea348a08ac3aae81850",
"templateCredsSetupCompleted": true
},
"name": "Luma AI Dream Machine - Simple v1 - AK",
"tags": [
{
"id": "tUlWC9t8VhwpFaci",
"name": "Alex - WIP",
"createdAt": "2025-02-20T17:17:53.411Z",
"updatedAt": "2025-02-20T17:17:53.411Z"
}
],
"nodes": [
{
"id": "dbe1dbcc-05a0-4439-869c-157e51a99dd1",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-440,
0
],
"parameters": {},
"typeVersion": 1
},
{
"id": "603f7fdd-e590-4a51-b606-a9bb9396a0c0",
"name": "Text 2 Video",
"type": "n8n-nodes-base.httpRequest",
"position": [
220,
0
],
"parameters": {
"url": "https://api.lumalabs.ai/dream-machine/v1/generations",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"ray-2\",\n \"prompt\": {{ JSON.stringify($('Global SETTINGS').first().json.video_prompt + \"; camera motion: \" + $json.action) }},\n \"aspect_ratio\": \"{{ $('Global SETTINGS').first().json.aspect_ratio }}\",\n \"duration\": \"{{ $('Global SETTINGS').item.json.duration }}\",\n \"loop\": {{ $('Global SETTINGS').first().json.loop }},\n \"callback_url\": \"{{ $('Global SETTINGS').first().json.callback_url }}\"\n \n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "accept",
"value": "application/json"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"id": "zzIlODir90EUTwHh",
"name": "Luma Header Auth account"
}
},
"typeVersion": 4.2
},
{
"id": "494ac05e-e0c5-465e-b805-2749683ab789",
"name": "RANDOM Camera Motion",
"type": "n8n-nodes-base.code",
"position": [
0,
0
],
"parameters": {
"jsCode": "const items = [\n \"Static\",\n \"Move Left\",\n \"Move Right\",\n \"Move Up\",\n \"Move Down\",\n \"Push In\",\n \"Pull Out\",\n \"Zoom In\",\n \"Zoom Out\",\n \"Pan Left\",\n \"Pan Right\",\n \"Orbit Left\",\n \"Orbit Right\",\n \"Crane Up\",\n \"Crane Down\"\n];\n\nconst randomItem = items[Math.floor(Math.random() * items.length)];\n\nreturn [{ json: { action: randomItem } }];\n"
},
"typeVersion": 2
},
{
"id": "30ba7cfc-d2c3-478f-ae01-0a3397ceb439",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-260,
-120
],
"parameters": {
"color": 3,
"width": 180,
"content": "## Define your SETTINGS here"
},
"typeVersion": 1
},
{
"id": "12924397-b2a4-43a0-8ec5-1b13c0357e40",
"name": "Global SETTINGS",
"type": "n8n-nodes-base.set",
"position": [
-220,
0
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "7064f685-d91f-4049-9fcb-dd7018c1bc8d",
"name": "aspect_ratio",
"type": "string",
"value": "9:16"
},
{
"id": "3d6d3fe0-4e4a-4d1b-9f6a-08037a4e2785",
"name": "video_prompt",
"type": "string",
"value": "a superhero flying through a volcano"
},
{
"id": "7ae48bee-0be5-487f-8d6d-ea7fe98fdd36",
"name": "loop",
"type": "string",
"value": "true"
},
{
"id": "82930db0-971e-4de4-911d-ff5a7fab5d67",
"name": "duration",
"type": "string",
"value": "5s"
},
{
"id": "b51d9834-87c8-4358-a257-6a02ebe2576d",
"name": "cluster_id",
"type": "string",
"value": "={{ Date.now() + '_' + Math.random().toString(36).slice(2, 10) }}"
},
{
"id": "8756fe2d-df04-48d4-9cd4-d29b8d9a3ab1",
"name": "airtable_base",
"type": "string",
"value": "appvk87mtcwRve5p5"
},
{
"id": "a83707ef-3a1c-4b3c-939c-1376bc43cc76",
"name": "airtable_table_generated_videos",
"type": "string",
"value": "tblOzRFWgcsfttRWK"
},
{
"id": "694528cd-c51e-45ac-8dbe-1b33b347f590",
"name": "callback_url",
"type": "string",
"value": "https://YOURURL.com/luma-ai"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "9f4732b5-8e3e-4fb6-942f-32c72b3eb041",
"name": "ADD Video Info",
"type": "n8n-nodes-base.airtable",
"position": [
660,
0
],
"parameters": {
"base": {
"__rl": true,
"mode": "id",
"value": "={{ $('Global SETTINGS').first().json.airtable_base }}"
},
"table": {
"__rl": true,
"mode": "id",
"value": "={{ $('Global SETTINGS').first().json.airtable_table_generated_videos }}"
},
"columns": {
"value": {
"Model": "={{ $json.model }}",
"Aspect": "={{ $json.request.aspect_ratio }}",
"Length": "={{ $json.request.duration }}",
"Prompt": "={{ $('Global SETTINGS').first().json.video_prompt }}",
"Status": "Done",
"Cluster ID": "={{ $('Global SETTINGS').first().json.cluster_id }}",
"Resolution": "={{ $json.request.resolution }}",
"Generation ID": "={{ $json.id }}"
},
"schema": [
{
"id": "Generation ID",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Generation ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "options",
"display": true,
"options": [
{
"name": "Todo",
"value": "Todo"
},
{
"name": "In progress",
"value": "In progress"
},
{
"name": "Done",
"value": "Done"
}
],
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Content Title",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Content Title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Video URL",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Video URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Thumb URL",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Thumb URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Prompt",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Prompt",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "VO",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "VO",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Aspect",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Aspect",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Model",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Model",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Resolution",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Resolution",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Length",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Length",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Created",
"type": "string",
"display": true,
"removed": false,
"readOnly": true,
"required": false,
"displayName": "Created",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Cluster ID",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Cluster ID",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "create"
},
"credentials": {
"airtableTokenApi": {
"id": "yqBrLbgHXLcwqH0p",
"name": "AlexK Airtable Personal Access Token account"
}
},
"typeVersion": 2.1
},
{
"id": "9923373d-d4ce-42bb-9f2d-34350f64ac5b",
"name": "Execution Data",
"type": "n8n-nodes-base.executionData",
"position": [
440,
0
],
"parameters": {},
"typeVersion": 1
},
{
"id": "5044e1f2-c985-4c3a-9386-f4fe4f85f37b",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-40,
-120
],
"parameters": {
"color": 5,
"width": 840,
"content": "## This is where the magic happens... "
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "e756199d-31fc-4e2f-8937-3625295a147c",
"connections": {
"Text 2 Video": {
"main": [
[
{
"node": "Execution Data",
"type": "main",
"index": 0
}
]
]
},
"ADD Video Info": {
"main": [
[]
]
},
"Execution Data": {
"main": [
[
{
"node": "ADD Video Info",
"type": "main",
"index": 0
}
]
]
},
"Global SETTINGS": {
"main": [
[
{
"node": "RANDOM Camera Motion",
"type": "main",
"index": 0
}
]
]
},
"RANDOM Camera Motion": {
"main": [
[
{
"node": "Text 2 Video",
"type": "main",
"index": 0
}
]
]
},
"When clicking Test workflow": {
"main": [
[
{
"node": "Global SETTINGS",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,770 @@
{
"meta": {
"instanceId": "d6b502dfa4d9dd072cdc5c2bb763558661053f651289291352a84403e01b3d1b",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "0951fd33-1811-4a89-b84f-f46dc9e6fde1",
"name": "When chat message received",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"position": [
20,
-340
],
"webhookId": "cdc03fce-33b6-4eed-86b5-f628994e5e31",
"parameters": {
"options": {}
},
"typeVersion": 1.1
},
{
"id": "699c2f89-5547-4d28-92a9-5e216aecb251",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
240,
-340
],
"parameters": {
"options": {
"maxIterations": 15,
"systemMessage": "=You are a helpful assistant.\nCurrent timestamp is {{ $now }}"
}
},
"typeVersion": 1.7
},
{
"id": "640c29f7-b67e-49f6-a864-c9b396c446b7",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
160,
-100
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o",
"cachedResultName": "gpt-4o"
},
"options": {
"temperature": 0.2
}
},
"credentials": {
"openAiApi": {
"id": "5LVOlVwHUgB8MAj2",
"name": "OpenAI - n8n project"
}
},
"typeVersion": 1.2
},
{
"id": "807630b4-c138-4b66-a438-fb70eab12a07",
"name": "Calculator",
"type": "@n8n/n8n-nodes-langchain.toolCalculator",
"position": [
840,
60
],
"parameters": {},
"typeVersion": 1
},
{
"id": "132a97a3-239c-403f-843f-55b652e3efc5",
"name": "Code",
"type": "n8n-nodes-base.code",
"position": [
840,
640
],
"parameters": {
"jsCode": "// Ensure there's at least one input item.\nif (!items || items.length === 0) {\n throw new Error(\"No input items found.\");\n}\n\n// Our input is expected to have a 'data' property containing the JSONP string.\nconst input = items[0].json;\n\nif (!input.data) {\n throw new Error(\"Input JSON does not have a 'data' property.\");\n}\n\nconst rawData = input.data;\n\n// Use a regex to extract the JSON content from the Google Visualization JSONP response.\nconst regex = /google\\.visualization\\.Query\\.setResponse\\((.*)\\);?$/s;\nconst match = rawData.match(regex);\n\nif (!match) {\n throw new Error(\"Input data does not match the expected Google Visualization JSONP format.\");\n}\n\nconst jsonString = match[1];\n\n// Parse the extracted JSON string.\nlet parsed;\ntry {\n parsed = JSON.parse(jsonString);\n} catch (error) {\n throw new Error(\"Failed to parse JSON: \" + error.message);\n}\n\n// Verify that the parsed JSON has the expected 'table' structure with 'cols' and 'rows'.\nif (!parsed.table || !Array.isArray(parsed.table.cols) || !Array.isArray(parsed.table.rows)) {\n throw new Error(\"Parsed JSON does not have the expected 'table' structure with 'cols' and 'rows'.\");\n}\n\nconst cols = parsed.table.cols;\nconst rows = parsed.table.rows;\n\n// Helper function to convert date string from \"Date(YYYY,M,D)\" to \"YYYY-MM-DD\"\nfunction formatDate(dateStr) {\n const match = dateStr.match(/^Date\\((\\d+),(\\d+),(\\d+)\\)$/);\n if (!match) return dateStr;\n const year = parseInt(match[1], 10);\n const month = parseInt(match[2], 10) + 1; // JavaScript months are 0-indexed\n const day = parseInt(match[3], 10);\n // Format with leading zeros\n return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;\n}\n\n// Map each row into an object using the column labels as keys.\nconst newItems = rows.map(row => {\n const obj = {};\n cols.forEach((col, index) => {\n let value = row.c && row.c[index] ? row.c[index].v : null;\n // If the column type is \"date\" and the value is a string that looks like \"Date(YYYY,M,D)\"\n if (col.type === \"date\" && typeof value === \"string\") {\n value = formatDate(value);\n }\n obj[col.label] = value;\n });\n return { json: obj };\n});\n\n// Return the new array of items.\nreturn newItems;\n"
},
"typeVersion": 2
},
{
"id": "3dc1e670-bfb1-4b63-b9c8-85656134c843",
"name": "When Executed by Another Workflow",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
280,
640
],
"parameters": {
"workflowInputs": {
"values": [
{
"name": "start_date"
},
{
"name": "end_date"
},
{
"name": "status"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "52a26e43-12a5-4b4a-a224-d70cdabf6aaf",
"name": "Records by date",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"position": [
1020,
-120
],
"parameters": {
"name": "records_by_date_and_or_status",
"workflowId": {
"__rl": true,
"mode": "list",
"value": "a2BIIjr2gLBay06M",
"cachedResultName": "Template | Your first AI Data Analyst"
},
"description": "Use this tool to get records filtered by date. You can also filter by status at the same time, if you want.",
"workflowInputs": {
"value": {
"status": "={{ $fromAI(\"status\", \"Status of the transaction. Can be Completed, Refund or Error. Leave empty if you don't need this now.\", \"string\") }}",
"end_date": "={{ $fromAI(\"end_date\", \"End date in format YYYY-MM-DD\", \"string\") }}",
"start_date": "={{ $fromAI(\"start_date\", \"Start date in format YYYY-MM-DD\", \"string\") }}"
},
"schema": [
{
"id": "start_date",
"type": "string",
"display": true,
"required": false,
"displayName": "start_date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "end_date",
"type": "string",
"display": true,
"required": false,
"displayName": "end_date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"typeVersion": 2
},
{
"id": "e1811519-8699-4243-8c64-0db1ab26004d",
"name": "Aggregate",
"type": "n8n-nodes-base.aggregate",
"position": [
1280,
640
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData"
},
"typeVersion": 1
},
{
"id": "3b129abd-ac9a-460c-abb3-007e2c94e284",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1220,
400
],
"parameters": {
"color": 7,
"width": 220,
"height": 400,
"content": "To send all the items back to the AI, we need to finish with everything aggregated into one single item.\n\nOtherwise it will respond with one item at a time, and the AI will only get the first item that arrives."
},
"typeVersion": 1
},
{
"id": "645ac0f9-8022-4f2c-8c6c-5aadd6cf09cc",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
460,
400
],
"parameters": {
"color": 7,
"width": 300,
"height": 400,
"content": "This node sends a custom HTTP Request to the Google Sheets API.\n\nFiltering by date range in the Google Sheets API is very complicated.\n\nThis node solves that problem.\n\nBut doing the same in a database is much simpler. A tool could do it without needing a sub-workflow."
},
"typeVersion": 1
},
{
"id": "14221a72-914d-4c75-866a-d64ba7f8109f",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
780,
400
],
"parameters": {
"color": 7,
"width": 220,
"height": 400,
"content": "The output from this complex request is also messy.\n\nSo we use some code generated by ChatGPT to transform the data into JSON objects."
},
"typeVersion": 1
},
{
"id": "f12668ea-b59d-4caf-a997-381f78b7cfe7",
"name": "Google Sheets request",
"type": "n8n-nodes-base.httpRequest",
"position": [
560,
640
],
"parameters": {
"url": "https://docs.google.com/spreadsheets/d/18A4d7KYrk8-uEMbu7shoQe_UIzmbTLV1FMN43bjA7qc/gviz/tq",
"options": {},
"sendQuery": true,
"authentication": "predefinedCredentialType",
"queryParameters": {
"parameters": [
{
"name": "sheet",
"value": "Sheet1"
},
{
"name": "tq",
"value": "=SELECT * WHERE A >= DATE \"{{ $json.start_date }}\" AND A <= DATE \"{{ $json.end_date }}\""
}
]
},
"nodeCredentialType": "googleSheetsOAuth2Api"
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "YR4pbjuZM5Xs4CTD",
"name": "Google Sheets"
}
},
"typeVersion": 4.2
},
{
"id": "f59a2606-0981-43d1-9a07-b802891b9220",
"name": "Get transactions by product name",
"type": "n8n-nodes-base.googleSheetsTool",
"position": [
1020,
-320
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $fromAI(\"product_name\", \"The product name\", \"string\") }}",
"lookupColumn": "Product"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/18A4d7KYrk8-uEMbu7shoQe_UIzmbTLV1FMN43bjA7qc/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/18A4d7KYrk8-uEMbu7shoQe_UIzmbTLV1FMN43bjA7qc/edit?usp=sharing"
},
"descriptionType": "manual",
"toolDescription": "Find transactions by product.\nOur products are:\n- Widget A\n- Widget B\n- Widget C\n- Widget D"
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "YR4pbjuZM5Xs4CTD",
"name": "Google Sheets"
}
},
"typeVersion": 4.5
},
{
"id": "1ed7168c-1639-4b3b-a3b4-ed162bcef880",
"name": "Get all transactions",
"type": "n8n-nodes-base.googleSheetsTool",
"position": [
840,
-120
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/18A4d7KYrk8-uEMbu7shoQe_UIzmbTLV1FMN43bjA7qc/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/18A4d7KYrk8-uEMbu7shoQe_UIzmbTLV1FMN43bjA7qc/edit?usp=sharing"
},
"descriptionType": "manual",
"toolDescription": "Only use this as last resort, because it will pull all data at once."
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "YR4pbjuZM5Xs4CTD",
"name": "Google Sheets"
}
},
"typeVersion": 4.5
},
{
"id": "798453da-8a65-4d14-ae0a-778d64ab02ad",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-360,
-340
],
"parameters": {
"color": 4,
"width": 320,
"height": 340,
"content": "## Some questions to try\nThere's a red button on this page that you can click to chat with the AI.\n\nTry asking it these questions:\n\n- How many refunds in January and what was the amount refunded?\n\n- How many successful sales did we have in January 2025 and what was the final income of those?\n\n- What is the most frequent reason for refunds?"
},
"typeVersion": 1
},
{
"id": "b8336f1a-3855-4247-9589-2f9aa35d211f",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-780,
-340
],
"parameters": {
"color": 4,
"width": 400,
"content": "## Copy this Sheets file to your Google Drive\nhttps://docs.google.com/spreadsheets/d/18A4d7KYrk8-uEMbu7shoQe_UIzmbTLV1FMN43bjA7qc/edit?gid=0#gid=0"
},
"typeVersion": 1
},
{
"id": "99a55b39-965b-4454-b416-d3991f0bdfbc",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
940,
60
],
"parameters": {
"color": 7,
"width": 200,
"height": 140,
"content": "### 👈\nThe Calculator is a tool that allows an agent to run mathematical calculations."
},
"typeVersion": 1
},
{
"id": "7ebebf56-e065-41c4-8270-f636785b0def",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-780,
-160
],
"parameters": {
"color": 4,
"width": 400,
"content": "### How to connect to Google Sheets?\nTo connect your n8n to your Google Sheets you're gonna need Google OAuth credentials\n\nSee documentation **[here](https://docs.n8n.io/integrations/builtin/credentials/google/oauth-single-service/)**"
},
"typeVersion": 1
},
{
"id": "b64df0dd-6425-4fc2-9f60-8c5a85412d61",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
120,
20
],
"parameters": {
"color": 7,
"width": 170,
"height": 260,
"content": "## 👆\nYou can use many models here, including the free Google Gemini options.\n\nMake sure to test it thoroughly. Some models are better for data analysis."
},
"typeVersion": 1
},
{
"id": "23c7bb52-b189-45f1-949b-ea588f065583",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
340,
20
],
"parameters": {
"color": 7,
"width": 150,
"height": 260,
"content": "## 👆\nThis is a short term memory. It will remember the 5 previous interactions during the chat"
},
"typeVersion": 1
},
{
"id": "6097e5a1-139b-4329-81ff-4fda16ea5221",
"name": "Buffer Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
360,
-100
],
"parameters": {},
"typeVersion": 1.3
},
{
"id": "6de4a7f2-5c58-4401-bd7c-19c5a73ba775",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
1160,
-320
],
"parameters": {
"color": 7,
"width": 340,
"height": 180,
"content": "The **AI Tools Agent** has access to all the tools at the same time. It uses the name and description to decide when to use each tool.\n\nNotice I'm using `$fromAI` function in all of them.\n\nSee documentations **[here](https://docs.n8n.io/advanced-ai/examples/using-the-fromai-function/)**"
},
"typeVersion": 1
},
{
"id": "a308d895-bc18-4b2c-9567-78f6c29f79e8",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
1160,
-120
],
"parameters": {
"color": 7,
"width": 340,
"height": 320,
"content": "## 👈 This is a special tool\nIt is used to call another workflow.\nThis concept is called sub-workflow.\n\nSee documentation [here](https://docs.n8n.io/flow-logic/subworkflows/).\n\nInstead of running a completely separate workflow, we are calling the one below.\n\nIt's contained in the same workflow, but we are using the trigger to define it will run only when called by this tool."
},
"typeVersion": 1
},
{
"id": "0a6d94bc-21e1-4949-b7f4-c93abbecf08c",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
120,
340
],
"parameters": {
"color": 7,
"width": 1380,
"height": 520,
"content": "# Sub-workflow\nThe AI can call this sub-workflow anytime,\nby using the **Records by date** tool.\n\nThe sub-workflow automatically return\n the result of the last executed node to the AI."
},
"typeVersion": 1
},
{
"id": "3e424615-6e49-4bd3-b066-005b9f0f773e",
"name": "Filter by status",
"type": "n8n-nodes-base.filter",
"position": [
1060,
640
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "e50da873-bbbd-41d3-a418-83193907977c",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.Status }}",
"rightValue": "={{ $('When Executed by Another Workflow').item.json.status }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "0ad0102c-adb9-4ec9-bdf3-b1ce425b88ba",
"name": "Get transactions by status",
"type": "n8n-nodes-base.googleSheetsTool",
"position": [
840,
-320
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $fromAI(\"transaction_status\", \"Transaction status can be Refund, Completed or Error\", \"string\") }}",
"lookupColumn": "Status"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/18A4d7KYrk8-uEMbu7shoQe_UIzmbTLV1FMN43bjA7qc/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/18A4d7KYrk8-uEMbu7shoQe_UIzmbTLV1FMN43bjA7qc/edit?usp=sharing"
},
"descriptionType": "manual",
"toolDescription": "Find transactions by status"
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "YR4pbjuZM5Xs4CTD",
"name": "Google Sheets"
}
},
"typeVersion": 4.5
},
{
"id": "5b80cb08-6e19-47b2-8146-c299e709a34a",
"name": "Sticky Note13",
"type": "n8n-nodes-base.stickyNote",
"position": [
820,
-540
],
"parameters": {
"color": 4,
"width": 300,
"content": "## Change the URL of the Sheets file in all the Sheets tools 👇"
},
"typeVersion": 1
},
{
"id": "ddc1351e-0ad0-480f-9742-30f2aa860d61",
"name": "Sticky Note14",
"type": "n8n-nodes-base.stickyNote",
"position": [
500,
820
],
"parameters": {
"color": 4,
"width": 260,
"height": 100,
"content": "## 👆 Change the URL of the Sheets file"
},
"typeVersion": 1
},
{
"id": "ab837a10-932f-4b14-8e2c-546077ca2c86",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
-780,
20
],
"parameters": {
"color": 7,
"width": 740,
"height": 580,
"content": "# Author\n![Solomon](https://gravatar.com/avatar/79aa147f090807fe0f618fb47a1de932669e385bb0c84bf3a7f891ae7d174256?r=pg&d=retro&size=200)\n### Solomon\nFreelance consultant from Brazil, specializing in automations and data analysis. I work with select clients, addressing their toughest projects.\n\nCurrently running the [Scrapes community](https://www.skool.com/scrapes/about?ref=21f10ad99f4d46ba9b8aaea8c9f58c34) with Simon 💪\n\nFor business inquiries, email me at automations.solomon@gmail.com\nOr message me on [Telegram](https://t.me/salomaoguilherme) for a faster response.\n\n## Check out my other templates\n### 👉 https://n8n.io/creators/solomon/\n"
},
"typeVersion": 1
},
{
"id": "e58351b3-3b18-4c03-9435-27ba853d03bb",
"name": "Sticky Note15",
"type": "n8n-nodes-base.stickyNote",
"position": [
-780,
620
],
"parameters": {
"width": 740,
"height": 180,
"content": "# Need help?\nFor getting help with this workflow, please create a topic on the community forums here:\nhttps://community.n8n.io/c/questions/"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Code": {
"main": [
[
{
"node": "Filter by status",
"type": "main",
"index": 0
}
]
]
},
"Calculator": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Buffer Memory": {
"ai_memory": [
[
{
"node": "AI Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Records by date": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Filter by status": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Get all transactions": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Google Sheets request": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Get transactions by status": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"When chat message received": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Get transactions by product name": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"When Executed by Another Workflow": {
"main": [
[
{
"node": "Google Sheets request",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,254 @@
{
"name": "Automatically Update YouTube Video Descriptions with Inserted Text",
"tags": [],
"nodes": [
{
"id": "19cafddc-6199-4418-8213-9743c34c9176",
"name": "Get All Videos",
"type": "n8n-nodes-base.youTube",
"position": [
480,
380
],
"parameters": {
"limit": 3,
"filters": {},
"options": {
"order": "date"
},
"resource": "video"
},
"typeVersion": 1
},
{
"id": "63a6a8e6-994f-46ab-a731-609549fec99f",
"name": "Update Video Description",
"type": "n8n-nodes-base.youTube",
"position": [
1320,
460
],
"parameters": {
"title": "={{ $('Get Specific Video').item.json.snippet.title }}",
"videoId": "={{ $('Get Specific Video').item.json.id}}",
"resource": "video",
"operation": "update",
"categoryId": "={{ $('Get Specific Video').item.json.snippet.categoryId }}",
"regionCode": "US",
"updateFields": {
"tags": "={{ $('Get Specific Video').item.json.snippet.tags.join() }}",
"description": "={{ $json.updatedDescription }}"
}
},
"typeVersion": 1
},
{
"id": "ce147272-f6c3-4cfb-954b-9a77c63a6232",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [
120,
380
],
"parameters": {},
"typeVersion": 1
},
{
"id": "9ba206b2-1161-41a3-8581-d60dae665096",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
100,
120
],
"parameters": {
"color": 5,
"width": 580,
"height": 180,
"content": "## Insert Text into YouTube Video Descriptions\n**Automatically insert a row of text between two specified rows** in all your YouTube video descriptions. \n\nThis workflow is ideal for YouTubers who need to update multiple video descriptions at once. Easily add a new link or text between existing lines, ensuring consistency across all your video descriptions without manual edits."
},
"typeVersion": 1
},
{
"id": "e05f5b9c-c160-45d7-b67a-62d68acc0829",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
100,
560
],
"parameters": {
"color": 4,
"width": 340,
"height": 260,
"content": "## Configure text string to insert 👆 \nDefine the text string (row) that will be added to your YouTube video descriptions.\n\n### Variables\n- **rowBefore** → The new row will be inserted *after* this line.\n- **rowToInsert** -→ The text or link you want to add.\n- **rowAfter**→ The new row will be inserted *before* this line.\n\n"
},
"typeVersion": 1
},
{
"id": "51a3fd15-8767-4cc0-98a8-fe98ec90db70",
"name": "Set String to Insert",
"type": "n8n-nodes-base.set",
"position": [
300,
380
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "a05b56b1-6f18-4359-aa4b-127399877301",
"name": "rowBefore",
"type": "string",
"value": "=https://firstlink.com"
},
{
"id": "95ac4a95-cdf4-4d7a-b9a3-78d54c879115",
"name": "rowToInsert",
"type": "string",
"value": "https://mynewlinktoinsert.com"
},
{
"id": "ded86a1f-f0a5-42b8-9176-9be4038f6290",
"name": "rowAfter",
"type": "string",
"value": "https://secondlink.com"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "590b8bb3-6eb4-4bb8-af4c-c2d95221f045",
"name": "Loop Over Videos",
"type": "n8n-nodes-base.splitInBatches",
"position": [
700,
380
],
"parameters": {
"options": {
"reset": false
}
},
"typeVersion": 3
},
{
"id": "a80ac941-0a99-4eab-8a6c-effef1e136fa",
"name": "Get Specific Video",
"type": "n8n-nodes-base.youTube",
"position": [
900,
460
],
"parameters": {
"options": {},
"videoId": "={{ $json.id.videoId }}",
"resource": "video",
"operation": "get"
},
"typeVersion": 1
},
{
"id": "2c4519e2-1af9-42d7-818c-8165365587fb",
"name": "Create New Video Description with Row Inserted",
"type": "n8n-nodes-base.code",
"position": [
1100,
460
],
"parameters": {
"jsCode": "// Access the input data (YouTube description)\nconst description = $('Get Specific Video').first().json.snippet.description;\n//console.log(inputData)\n\nconst variables = $('Set String to Insert').first().json\n// Define the rows to search for and the row to insert\nconst rowBefore = variables.rowBefore;\nconst rowAfter = variables.rowAfter;\nconst rowToInsert = variables.rowToInsert;\n\n// Split the description into an array of rows\nconst rows = description.split(\"\\n\");\nconsole.log(rows)\n// Find the index of the rowBefore and rowAfter\nconst indexBefore = rows.findIndex(row => row.trim() === rowBefore);\nconst indexAfter = rows.findIndex(row => row.trim() === rowAfter);\n\n// Check if both rows are found and rowBefore comes before rowAfter\nif (indexBefore !== -1 && indexAfter !== -1 && indexBefore < indexAfter) {\n // Insert the new row between rowBefore and rowAfter\n rows.splice(indexBefore + 1, 0, rowToInsert);\n}\n\n// Join the rows back into a single string\nconst updatedDescription = rows.join(\"\\n\");\n\n// Return the updated description in the correct n8n output structure\nreturn [\n {\n json: {\n updatedDescription: updatedDescription\n }\n }\n];"
},
"typeVersion": 2
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "50fd0bcb-7441-45eb-ab58-ca2a7de78516",
"connections": {
"Get All Videos": {
"main": [
[
{
"node": "Loop Over Videos",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Videos": {
"main": [
[],
[
{
"node": "Get Specific Video",
"type": "main",
"index": 0
}
]
]
},
"Get Specific Video": {
"main": [
[
{
"node": "Create New Video Description with Row Inserted",
"type": "main",
"index": 0
}
]
]
},
"Set String to Insert": {
"main": [
[
{
"node": "Get All Videos",
"type": "main",
"index": 0
}
]
]
},
"Update Video Description": {
"main": [
[
{
"node": "Loop Over Videos",
"type": "main",
"index": 0
}
]
]
},
"When clicking Test workflow": {
"main": [
[
{
"node": "Set String to Insert",
"type": "main",
"index": 0
}
]
]
},
"Create New Video Description with Row Inserted": {
"main": [
[
{
"node": "Update Video Description",
"type": "main",
"index": 0
}
]
]
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,180 @@
{
"meta": {
"instanceId": "c911aed9995230b93fd0d9bc41c258d697c2fe97a3bab8c02baf85963eeda618",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "3239827a-ba1c-4131-bfbe-6fa7d35bfaae",
"name": "Parameters",
"type": "n8n-nodes-base.set",
"position": [
360,
720
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "1b65def6-4984-497d-a4bc-232af22927ad",
"name": "directory",
"type": "string",
"value": "https://drive.google.com/drive/folders/your-directory-id"
},
{
"id": "c8c98f88-9f22-4574-88b8-1db99f6e4ec4",
"name": "parentdrive",
"type": "string",
"value": "https://drive.google.com/drive/my-drive"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "de6411b5-5d53-4d42-b3b6-0fc4b84c52ea",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
180,
720
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 1,
"triggerAtMinute": 30
}
]
}
},
"typeVersion": 1.2
},
{
"id": "5b25b86a-c957-4aa3-9c10-b884ee30d9a1",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
460
],
"parameters": {
"color": 3,
"width": 560,
"height": 140,
"content": "## Simplest n8n Workflow Backup Automating Your Data Security in Google Drive"
},
"typeVersion": 1
},
{
"id": "f5033398-ccf6-4126-9039-6fa8a5968552",
"name": "Code",
"type": "n8n-nodes-base.code",
"position": [
720,
720
],
"parameters": {
"jsCode": "return items.map(item => {\n const jsonData = JSON.stringify(item.json);\n const binaryData = Buffer.from(jsonData).toString('base64');\n item.binary = {\n data: {\n data: binaryData,\n mimeType: 'application/json',\n fileName: 'data.json'\n }\n };\n return item;\n});"
},
"typeVersion": 2
},
{
"id": "b8532f27-a619-4683-a835-096f3a450397",
"name": "Get all n8n Workflows",
"type": "n8n-nodes-base.n8n",
"position": [
540,
720
],
"parameters": {
"filters": {},
"requestOptions": {}
},
"credentials": {
"n8nApi": {
"id": "lkbDvgt244nzvwuE",
"name": "n8n account"
}
},
"typeVersion": 1
},
{
"id": "e6c815c6-00ac-4d91-b92f-dfc0c962bcd3",
"name": "Backup to Google Drive",
"type": "n8n-nodes-base.googleDrive",
"position": [
900,
720
],
"parameters": {
"name": "={{ $json.name+ \".json\"}}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive",
"cachedResultUrl": "https://drive.google.com/drive/my-drive",
"cachedResultName": "My Drive"
},
"options": {},
"folderId": {
"__rl": true,
"mode": "url",
"value": "={{ $('Parameters').item.json.directory }}"
}
},
"retryOnFail": true,
"typeVersion": 3
}
],
"pinData": {},
"connections": {
"Code": {
"main": [
[
{
"node": "Backup to Google Drive",
"type": "main",
"index": 0
}
]
]
},
"Parameters": {
"main": [
[
{
"node": "Get all n8n Workflows",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Parameters",
"type": "main",
"index": 0
}
]
]
},
"Get all n8n Workflows": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,214 @@
{
"meta": {
"instanceId": "7614f731d9ac88c16c6149bd495fa54d325e3f79ab527ffc7e3b1b1f42dbf884",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "56e70371-54a2-4421-9ce2-e626d9c6ef60",
"name": "Form",
"type": "n8n-nodes-base.formTrigger",
"position": [
-440,
-120
],
"webhookId": "622256ee-9248-43a2-840e-b28436800aac",
"parameters": {
"options": {},
"formTitle": "Form",
"formFields": {
"values": [
{
"fieldLabel": "name",
"requiredField": true
}
]
}
},
"typeVersion": 2.2
},
{
"id": "7cbd263e-ca5b-436e-bdce-c30a66df73a6",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-440,
100
],
"parameters": {
"color": 3,
"width": 320,
"content": "# 👆\nPlease add authentication to form by selecting Basic Auth to prevent unauthorized access to the form.\n"
},
"typeVersion": 1
},
{
"id": "e1c4d0a8-6e48-45d9-bec6-ee8bb3751b4f",
"name": "Copy template file",
"type": "n8n-nodes-base.googleDrive",
"position": [
-220,
-120
],
"parameters": {
"name": "={{ $json.name }}",
"fileId": {
"__rl": true,
"mode": "list",
"value": "1KyR0UMIOpEkjwa6o1gTggNBP2A6EWwppV59Y6NQuDYw",
"cachedResultUrl": "https://docs.google.com/document/d/1KyR0UMIOpEkjwa6o1gTggNBP2A6EWwppV59Y6NQuDYw/edit?usp=drivesdk",
"cachedResultName": "Szablon: Dokument testowy"
},
"options": {},
"operation": "copy"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "aPSwizdvnxio0J7A",
"name": "Google Drive account 2"
}
},
"typeVersion": 3
},
{
"id": "52a27a15-ca68-4381-9a0d-faa1127d7de9",
"name": "Format form data",
"type": "n8n-nodes-base.code",
"position": [
0,
-120
],
"parameters": {
"jsCode": "const data = [];\n\nObject.keys($('Form').all().map((item) => {\n Object.keys(item.json).map((bodyProperty) => {\n data.push({\n key: bodyProperty,\n value: item.json[bodyProperty],\n });\n })\n}));\n\nreturn {\n webhook_data: data,\n pairedItem: 0,\n};"
},
"typeVersion": 2
},
{
"id": "08dbeb42-16f6-4771-bbf8-a358fda54097",
"name": "Format form data to Google Doc API",
"type": "n8n-nodes-base.code",
"position": [
220,
-120
],
"parameters": {
"jsCode": "const result = [];\n\n$('Format form data').all().map((item) => {\n item.json.webhook_data.map((data) => {\n if (\"submittedAt\" !== data.key && \"formMode\" !== data.key) {\n result.push({\n \"replaceAllText\": {\n \"containsText\": {\n \"text\": `{{${data.key}}}`, \n \"matchCase\": true\n },\n \"replaceText\": `${data.value}`\n },\n });\n }\n });\n})\n\nreturn {\n data: result,\n pairedItem: 0,\n};"
},
"typeVersion": 2
},
{
"id": "99b03034-8c9b-4e23-8cc9-bf9960a4e06a",
"name": "Replace data in Google Doc",
"type": "n8n-nodes-base.httpRequest",
"position": [
440,
-120
],
"parameters": {
"url": "=https://docs.googleapis.com/v1/documents/{{ $('Copy template file').first().json.id }}:batchUpdate",
"method": "POST",
"options": {},
"sendBody": true,
"authentication": "predefinedCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "requests",
"value": "={{ $json.data }}"
}
]
},
"nodeCredentialType": "googleDocsOAuth2Api"
},
"credentials": {
"googleDocsOAuth2Api": {
"id": "uhqGUvBF00zGb9vB",
"name": "Google Docs account 2"
}
},
"typeVersion": 4.2
},
{
"id": "204b57da-2791-40e3-84f5-23a0ed5c8beb",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-440,
-420
],
"parameters": {
"color": 6,
"width": 520,
"height": 180,
"content": "# 🙋‍♂️\nThe workflow automatically fetches all form fields and converts them into variables in Google Doc. For example, if you add a text field to the form called \"address,\" you can use the variable {{address}} in the Google Doc template."
},
"typeVersion": 1
},
{
"id": "fa17044d-191e-45eb-9559-563889ad2aef",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
440,
100
],
"parameters": {
"color": 3,
"content": "# 👆\nIn Authentication, you need to select Predefined Credential Type and then choose Google Docs OAuth API."
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Form": {
"main": [
[
{
"node": "Copy template file",
"type": "main",
"index": 0
}
]
]
},
"Format form data": {
"main": [
[
{
"node": "Format form data to Google Doc API",
"type": "main",
"index": 0
}
]
]
},
"Copy template file": {
"main": [
[
{
"node": "Format form data",
"type": "main",
"index": 0
}
]
]
},
"Replace data in Google Doc": {
"main": [
[]
]
},
"Format form data to Google Doc API": {
"main": [
[
{
"node": "Replace data in Google Doc",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,326 @@
{
"meta": {
"instanceId": "4a11afdb3c52fd098e3eae9fad4b39fdf1bbcde142f596adda46c795e366b326"
},
"nodes": [
{
"id": "17ca0437-6101-4277-9ed2-e37e6b92df02",
"name": "When clicking 'Test workflow'",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-160,
280
],
"parameters": {},
"typeVersion": 1
},
{
"id": "d3dd600a-2ab5-4d52-92ef-ab3f29dd1790",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
260,
400
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {}
},
"typeVersion": 1.2
},
{
"id": "c29d58a2-243b-41ab-99c6-f8a8c92219cf",
"name": "Structured Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
460,
400
],
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"mail_object\": {\n \"type\": \"string\"\n }\n }\n}"
},
"typeVersion": 1.2
},
{
"id": "3cb31448-5bc3-47c2-a119-d9e33a464d1f",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-160,
80
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.2
},
{
"id": "18b243a5-db1f-4a27-a8a1-3a7c74135d6d",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
580,
20
],
"parameters": {
"width": 260,
"height": 120,
"content": "## ElevenlabsAPI key\n**Click** to get your Elevenlabs API key. [Elevenlabs](https://try.elevenlabs.io/text-audio)"
},
"typeVersion": 1
},
{
"id": "62a9bd08-27f8-45a8-9eb4-30950500a36f",
"name": "Change filename",
"type": "n8n-nodes-base.code",
"position": [
880,
180
],
"parameters": {
"jsCode": "/*\n * Filename: addFileName.js\n * Purpose: Add a file name to binary data in an n8n workflow using mail_object from input\n */\n\nconst mailObject = $input.first().json.output.mail_object;\nconst fileName = `${mailObject}.mp3`;\n\nreturn items.map(item => {\n if (item.binary && item.binary.data) {\n item.binary.data.fileName = fileName;\n }\n return item;\n});"
},
"typeVersion": 2
},
{
"id": "41043058-ca06-4c3a-8b7d-597e2941d92b",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1020,
20
],
"parameters": {
"width": 300,
"height": 120,
"content": "## Gmail API Credentials \n**Click here** to view the [documentation](https://docs.n8n.io/integrations/builtin/credentials/google/) and configure your access permissions for the Google Gmail API."
},
"typeVersion": 1
},
{
"id": "3475e3ae-439d-4245-8994-4444266a67e3",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
0
],
"parameters": {
"width": 300,
"height": 140,
"content": "## Calendar API Credentials \n**Click here** to view the [documentation](https://docs.n8n.io/integrations/builtin/credentials/google/) and configure your access permissions for the Google Calendar API."
},
"typeVersion": 1
},
{
"id": "7784fc2d-3e64-40f0-990f-965fba4ad67c",
"name": "Generate Voice Reminder",
"type": "n8n-nodes-base.httpRequest",
"position": [
660,
180
],
"parameters": {
"url": "https://api.elevenlabs.io/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb",
"method": "POST",
"options": {},
"sendBody": true,
"sendQuery": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "text",
"value": "={{ $json.output.message }}"
},
{
"name": "model_id",
"value": "eleven_multilingual_v2"
}
]
},
"genericAuthType": "httpCustomAuth",
"queryParameters": {
"parameters": [
{
"name": "output_format",
"value": "mp3_22050_32"
}
]
}
},
"notesInFlow": true,
"retryOnFail": true,
"typeVersion": 4.2
},
{
"id": "a2081f29-493b-43c0-bad5-1b273d5db527",
"name": "Send Voice Reminder",
"type": "n8n-nodes-base.gmail",
"position": [
1100,
180
],
"webhookId": "5ba2c8cb-84f1-4363-8410-b8d138286c3a",
"parameters": {
"sendTo": "={{ $('Get Appointments').item.json.attendees[0].email }}",
"message": "=👇 Information for tomorrow 🗣️",
"options": {
"senderName": "John Carpenter",
"attachmentsUi": {
"attachmentsBinary": [
{}
]
},
"appendAttribution": false
},
"subject": "={{ $('create message').item.json.output.mail_object }}"
},
"typeVersion": 2.1
},
{
"id": "dd3bf7b2-f951-452a-8912-47ceace50cc0",
"name": "create message",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
280,
180
],
"parameters": {
"text": "=name: {{ $json.summary }}\ntime: {{ $json.start.dateTime }}\naddress: {{ $json.location }}\nToday's date: {{ $now }}",
"messages": {
"messageValues": [
{
"message": "=You are an assistant. You will create a structured message in JSON.\n\n**\nmessage:\nGenerate a voice script reminder for a real estate appointment. The message should be clear, professional, and engaging.\n\nIt must include:\n1. The recipient's name.\n2. The date and time of the appointment, expressed naturally (e.g., at noon, quarter past noon, half past three, quarter to five).\n3. The complete address of the property, expressed naturally (e.g., 12 Baker Street in London, Madison Avenue in New York, 5 Oakwood Drive in Los Angeles).\n4. A mention of the sender: Mr. John Carpenter from Super Agency.\n5. A confirmation sentence or an invitation to contact if needed.\n\nInput variables:\n• Recipient's name (prefixed with Mr. or Ms.)\n• Time: Appointment time\n• Address: Complete property address (only the street, number, and city; not the postal code)\n\nThe tone should be cordial and professional, suitable for an automated voice message.\n\nExample expected output: \"Hello Mrs. Richard, this is Mr. John Carpenter from Super Immo Agency.\nI am reminding you of your appointment scheduled for tomorrow at 8:15, at 63 Taverniers Road in Talence. If you have any questions or need to reschedule, please do not hesitate to contact me. See you tomorrow and have a great day!\"\n\n**\nmail_object: a very short email subject\nExample: Your appointment reminder for tomorrow"
}
]
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.5
},
{
"id": "63806db8-6814-4fe4-ba2e-80511273ee51",
"name": "Get Appointments",
"type": "n8n-nodes-base.googleCalendar",
"position": [
60,
180
],
"parameters": {
"limit": 2,
"options": {},
"timeMax": "={{ $now.plus({ day: 2 }) }}",
"calendar": {
"__rl": true,
"mode": "list",
"value": "mymail@gmail.com",
"cachedResultName": "mymail@gmail.com"
},
"operation": "getAll"
},
"typeVersion": 1.3
}
],
"pinData": {},
"connections": {
"create message": {
"main": [
[
{
"node": "Generate Voice Reminder",
"type": "main",
"index": 0
}
]
]
},
"Change filename": {
"main": [
[
{
"node": "Send Voice Reminder",
"type": "main",
"index": 0
}
]
]
},
"Get Appointments": {
"main": [
[
{
"node": "create message",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Get Appointments",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "create message",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Generate Voice Reminder": {
"main": [
[
{
"node": "Change filename",
"type": "main",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "create message",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"When clicking 'Test workflow'": {
"main": [
[
{
"node": "Get Appointments",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,367 @@
{
"meta": {
"instanceId": "e634e668fe1fc93a75c4f2a7fc0dad807ca318b79654157eadb9578496acbc76",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "58c6003f-3311-448b-a949-4fbc22b38e2e",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-560,
80
],
"parameters": {},
"typeVersion": 1
},
{
"id": "67e4f66c-256f-4e45-b98e-d2872a416ff5",
"name": "Get all Users",
"type": "n8n-nodes-base.httpRequest",
"position": [
80,
100
],
"parameters": {
"url": "={{ $json.n8n_url }}",
"options": {
"pagination": {
"pagination": {
"parameters": {
"parameters": [
{
"name": "cursor",
"value": "={{ $response.body.nextCursor }}"
}
]
},
"completeExpression": "={{ !$response.body.nextCursor }}",
"paginationCompleteWhen": "other"
}
}
},
"sendQuery": true,
"authentication": "predefinedCredentialType",
"queryParameters": {
"parameters": [
{
"name": "limit",
"value": "5"
}
]
},
"nodeCredentialType": "n8nApi"
},
"credentials": {
"n8nApi": {
"id": "dzYjDgtEXtpRPKhe",
"name": "n8n account"
},
"httpHeaderAuth": {
"id": "iiLmD473RYjGLbCA",
"name": "Squarespace API key - Apps script"
}
},
"typeVersion": 4.2
},
{
"id": "2a66ddc7-5fde-4e2b-9ad6-7c68968214ae",
"name": "Get all rows",
"type": "n8n-nodes-base.googleSheets",
"position": [
80,
-180
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/15A3ZWzIBfONL4U_1XGJvtsS8HtMQ69qrpxd5C5L6Akg/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "15A3ZWzIBfONL4U_1XGJvtsS8HtMQ69qrpxd5C5L6Akg",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/15A3ZWzIBfONL4U_1XGJvtsS8HtMQ69qrpxd5C5L6Akg/edit?usp=drivesdk",
"cachedResultName": "n8n-submission"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "JgI9maibw5DnBXRP",
"name": "Google Sheets account"
}
},
"typeVersion": 4.5
},
{
"id": "f220c6db-eafb-4bb5-9cbe-43edcf563a67",
"name": "Get non-users",
"type": "n8n-nodes-base.merge",
"position": [
620,
-100
],
"parameters": {
"mode": "combine",
"options": {},
"advanced": true,
"joinMode": "keepNonMatches",
"mergeByFields": {
"values": [
{
"field1": "Email Address",
"field2": "email"
}
]
},
"outputDataFrom": "input1"
},
"typeVersion": 3
},
{
"id": "906e8dde-4c58-4e93-9e07-3064a5dd60dd",
"name": "Invite Users",
"type": "n8n-nodes-base.httpRequest",
"position": [
1100,
-100
],
"parameters": {
"url": "={{ $('Edit Fields').item.json.n8n_url }}",
"method": "POST",
"options": {},
"jsonBody": "={{ [$json] }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "n8nApi"
},
"credentials": {
"n8nApi": {
"id": "dzYjDgtEXtpRPKhe",
"name": "n8n account"
},
"httpHeaderAuth": {
"id": "iiLmD473RYjGLbCA",
"name": "Squarespace API key - Apps script"
}
},
"typeVersion": 4.2
},
{
"id": "195d0c33-611a-4a16-b62c-8ba1f4f31e19",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-560,
-160
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.2
},
{
"id": "dd453b5b-f238-43b1-8c44-2c3ed3a3d7ba",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"position": [
-220,
-20
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "c3a7a1ee-d1a2-4a29-b4b3-dcadf0fc16e2",
"name": "n8n_url",
"type": "string",
"value": "https://{n8n-url}/api/v1/users"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "07e678c7-7c98-4f09-89d8-5e4d7d442a8f",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-280,
-160
],
"parameters": {
"color": 4,
"width": 230,
"height": 300,
"content": "## Edit this node 👇\nChange n8n_url to your instance URL\nhttps://docs.n8n.io/api/authentication/#call-the-api-using-your-key"
},
"typeVersion": 1
},
{
"id": "2bfb10b6-220b-4c73-a15f-190412f2dda2",
"name": "Create users list",
"type": "n8n-nodes-base.set",
"position": [
880,
-100
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "36282722-07ec-47b1-ab08-c649b7901ed7",
"name": "email",
"type": "string",
"value": "={{ $json['Email Address'] }}"
},
{
"id": "9b073e1d-8c16-45b1-b333-97dfe635eb73",
"name": "role",
"type": "string",
"value": "global:member"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "221ca946-e305-4283-bca1-4289b8a7db28",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1000,
-300
],
"parameters": {
"color": 4,
"width": 371.1995072042308,
"height": 600.88409546716,
"content": "## Invite users to n8n from Google sheets\nThis workflow will get all Users from n8n and compare against the rows from Google sheets and create new users\n\nInvitation emails will be sent once the new users created\n\nYou can run the workflow on demand or by schedule\n\n## Spreadsheet template\n\nThe sheet columns are inspire from Squarespace newsletter block connection, but you can change the node to adapt new columns format\n\nClone the [sample sheet here](https://docs.google.com/spreadsheets/d/1wi2Ucb4b35e0-fuf-96sMnyzTft0ADz3MwdE_cG_WnQ/edit?usp=sharing)\n- Submitted On\t\n- Email Address\t\n- Name"
},
"typeVersion": 1
},
{
"id": "c956e102-7fe3-4ee4-90e0-32cb11556c2c",
"name": "Combine all paginated results",
"type": "n8n-nodes-base.code",
"position": [
320,
100
],
"parameters": {
"jsCode": "let results = [];\nfor (let i = 0; i < $input.all().length; i++) {\n results = results.concat($input.all()[i].json.data);\n}\n\nreturn results;"
},
"typeVersion": 2
}
],
"pinData": {},
"connections": {
"Edit Fields": {
"main": [
[
{
"node": "Get all rows",
"type": "main",
"index": 0
},
{
"node": "Get all Users",
"type": "main",
"index": 0
}
]
]
},
"Get all rows": {
"main": [
[
{
"node": "Get non-users",
"type": "main",
"index": 0
}
]
]
},
"Get all Users": {
"main": [
[
{
"node": "Combine all paginated results",
"type": "main",
"index": 0
}
]
]
},
"Get non-users": {
"main": [
[
{
"node": "Create users list",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"Create users list": {
"main": [
[
{
"node": "Invite Users",
"type": "main",
"index": 0
}
]
]
},
"Combine all paginated results": {
"main": [
[
{
"node": "Get non-users",
"type": "main",
"index": 1
}
]
]
},
"When clicking Test workflow": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,477 @@
{
"meta": {
"instanceId": "=",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "4815105b-4175-45ad-85bc-07917de9526c",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-140,
-720
],
"parameters": {},
"typeVersion": 1
},
{
"id": "b8f2a706-4868-4f0d-99a1-c31e1f7022e3",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1220,
-580
],
"parameters": {
"text": "=Article Title: {{ $json.title }}\nArticle Link: {{ $json.link }}\nArticle Content: {{ $json.clean_content }}",
"options": {
"systemMessage": "=You are a content marketing assistant. Based on the article metadata (ID, title) and cleaned content, generate a short LinkedIn promotional message for a professional audience.\n\nFollow this structure:\n\nStart with a hook that grabs attention (a bold insight, surprising fact, or thought-provoking question).\n\nBriefly summarize the articles value — what readers will learn or gain from it.\n\nInclude a clear call-to-action encouraging readers to read the article.\n\nEnd with this author signature and invitation:\n“—\nSamir Saci\nSupply Chain Data Scientist & Founder of LogiGreen\n📩 Contact me: https://logi-green.com/contactus”\n\nUse a professional and engaging tone. Do not include hashtags or Markdown formatting."
},
"promptType": "define"
},
"typeVersion": 1.8
},
{
"id": "ac1538f6-67ef-4fd0-b4a9-d44b49149e5f",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1160,
-420
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {}
},
"typeVersion": 1.2
},
{
"id": "bac79ecf-b92d-42ba-bb0f-f1e1f85ca1c9",
"name": "Clean HTML",
"type": "n8n-nodes-base.code",
"position": [
780,
-620
],
"parameters": {
"jsCode": "const htmlContent = $input.first().json.content;\n\nconst cleanText = htmlContent\n .replace(/<[^>]*>/g, '') // remove tags\n .replace(/\\s+/g, ' ') // normalize spaces\n .replace(/&nbsp;/g, ' ') // decode common entity\n .trim();\n\nreturn [\n {\n json: {\n clean_content: cleanText\n }\n }\n];\n"
},
"typeVersion": 2
},
{
"id": "fa57b494-370a-4f37-bcbe-38ba6138da76",
"name": "Extract Blog Posts",
"type": "n8n-nodes-base.ghost",
"position": [
80,
-720
],
"parameters": {
"limit": 3,
"options": {},
"operation": "getAll"
},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "dc19b6a4-fa17-41b4-8f8c-352519f07569",
"name": "Extract Post Content",
"type": "n8n-nodes-base.set",
"position": [
300,
-720
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "00b337cd-1c61-4f19-8c51-b76f3a8dece1",
"name": "id",
"type": "string",
"value": "={{ $json.id }}"
},
{
"id": "8d38f4bc-bca6-4343-8c5e-5d9fd9cbe178",
"name": "title",
"type": "string",
"value": "={{ $json.title }}"
},
{
"id": "c34ddd76-0db6-4225-82fa-04d5542f9c7c",
"name": "featured_image",
"type": "string",
"value": "={{ $json.feature_image }}"
},
{
"id": "c0f9593c-0d5a-4659-9e25-91b098318bd6",
"name": "excerpt",
"type": "string",
"value": "={{ $json.excerpt }}"
},
{
"id": "0d11d3d5-49f8-473a-8602-b49769f88005",
"name": "content",
"type": "string",
"value": "={{ $json.html }}"
},
{
"id": "ec89a00d-9d76-4594-a8ce-98aa177e6737",
"name": "link",
"type": "string",
"value": "={{ $json.url }}"
}
]
}
},
"notesInFlow": true,
"typeVersion": 3.4
},
{
"id": "45656e13-5f03-48f9-8422-0ea3993e3289",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-180,
-1080
],
"parameters": {
"color": 7,
"width": 200,
"height": 520,
"content": "### 1. Workflow Trigger\nThis workflow uses simple trigger.\n\n#### How to setup?\n*Nothing to do.*\n"
},
"typeVersion": 1
},
{
"id": "7b8c3c49-069f-464b-acd2-a1a047fb2138",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
40,
-1080
],
"parameters": {
"color": 7,
"width": 400,
"height": 520,
"content": "### 2. Extract Blog Posts Content\nThe Ghost node extracts all the posts of your blog with content and metadata. In the second node, we extract description, URL, content and features image url.\n\n#### How to setup?\n- **Ghost Account API**:\n 1. Add your Ghost Blog Account Credentials\n 2. Select the number of Blog Posts you want to collect\n [Learn more about the Ghost Node](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.ghost)\n\n"
},
"typeVersion": 1
},
{
"id": "0a5e4045-7df2-4713-a475-509844c58344",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
480,
-1080
],
"parameters": {
"color": 7,
"width": 1520,
"height": 800,
"content": "### 3. Generate a Linkedin Post for each Post with an AI Agent\nThis block loops through all the posts pulled by the Ghost Node, send the content to the AI agent that generates a Linkedin post. The results are combined and pulled in a Google Sheet.\n\n#### How to setup?\n- **AI Agent with the Chat Model**:\n 1. Add a **chat model** with the required credentials *(Example: Open AI 4o-mini)*\n 2. Adapt the system prompt with your **post signature** and additional points you want to add in your posts\n [Learn more about the AI Agent Node](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent)\n- **Record Long Break in the Google Sheet Node**:\n 1. Add your Google Sheet API credentials to access the Google Sheet file\n 2. Select the file using the list, an URL or an ID\n 3. Select the sheet in which you want to record your working sessions\n 4. Map the fields\n [Learn more about the Google Sheet Node](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.googlesheets)\n\n"
},
"typeVersion": 1
},
{
"id": "29b09c7f-c39c-414d-b9d5-897b0d540328",
"name": "Record the posts",
"type": "n8n-nodes-base.googleSheets",
"position": [
1840,
-480
],
"parameters": {
"columns": {
"value": {
"id": "={{ $json.id }}",
"title": "={{ $json.title }}",
"content": "={{ $json.content }}",
"excerpt": "={{ $json.excerpt }}",
"clean_content": "={{ $json.clean_content }}",
"linkedin_post": "={{ $json.output }}",
"featured_image": "={{ $json.featured_image }}"
},
"schema": [
{
"id": "id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "id",
"defaultMatch": true,
"canBeUsedToMatch": true
},
{
"id": "title",
"type": "string",
"display": true,
"required": false,
"displayName": "title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "featured_image",
"type": "string",
"display": true,
"required": false,
"displayName": "featured_image",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "excerpt",
"type": "string",
"display": true,
"required": false,
"displayName": "excerpt",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "content",
"type": "string",
"display": true,
"required": false,
"displayName": "content",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "clean_content",
"type": "string",
"display": true,
"required": false,
"displayName": "clean_content",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "linkedin_post",
"type": "string",
"display": true,
"required": false,
"displayName": "linkedin_post",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "=",
"cachedResultName": "="
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "=",
"cachedResultUrl": "=",
"cachedResultName": "="
}
},
"notesInFlow": true,
"typeVersion": 4.5
},
{
"id": "6f1c58db-a4bf-421a-a182-8149dac28725",
"name": "Merge Linkedin",
"type": "n8n-nodes-base.merge",
"position": [
1600,
-720
],
"parameters": {
"mode": "combineBySql"
},
"notesInFlow": true,
"typeVersion": 3
},
{
"id": "ebae3ccc-2727-44d9-9309-320c7d8e8349",
"name": "Add Clean HTML",
"type": "n8n-nodes-base.merge",
"position": [
1020,
-720
],
"parameters": {
"mode": "combineBySql"
},
"notesInFlow": true,
"typeVersion": 3
},
{
"id": "d839ca8d-f898-4617-955f-9c6d9a5412b7",
"name": "Loop Over Posts",
"type": "n8n-nodes-base.splitInBatches",
"position": [
580,
-720
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "7e759203-b524-4cc5-89df-5e113c800504",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-180,
-540
],
"parameters": {
"width": 660,
"height": 460,
"content": "### [📺Complete Tutorial](https://www.youtube.com/watch?v=Lhi6hV6rWEo)\n![Thumbnail](https://www.samirsaci.com/content/images/2025/04/temp-4.png)\n"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"AI Agent": {
"main": [
[
{
"node": "Merge Linkedin",
"type": "main",
"index": 1
}
]
]
},
"Clean HTML": {
"main": [
[
{
"node": "Add Clean HTML",
"type": "main",
"index": 1
}
]
]
},
"Add Clean HTML": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
},
{
"node": "Merge Linkedin",
"type": "main",
"index": 0
}
]
]
},
"Merge Linkedin": {
"main": [
[
{
"node": "Record the posts",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Posts": {
"main": [
[],
[
{
"node": "Clean HTML",
"type": "main",
"index": 0
},
{
"node": "Add Clean HTML",
"type": "main",
"index": 0
}
]
]
},
"Record the posts": {
"main": [
[
{
"node": "Loop Over Posts",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Extract Blog Posts": {
"main": [
[
{
"node": "Extract Post Content",
"type": "main",
"index": 0
}
]
]
},
"Extract Post Content": {
"main": [
[
{
"node": "Loop Over Posts",
"type": "main",
"index": 0
}
]
]
},
"When clicking Test workflow": {
"main": [
[
{
"node": "Extract Blog Posts",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,719 @@
{
"meta": {
"instanceId": "5e2cdd86a9e1ca2fc82cc63db38d1710d5d6a5c6fe352258a6f7112815bcd512",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "c4dca8f0-98fa-4b06-a806-1ab271f024a2",
"name": "Config",
"type": "n8n-nodes-base.set",
"position": [
120,
460
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "a916dcbd-d681-4e09-9ce9-0f50a1b4290b",
"name": "keep",
"type": "string",
"value": "=last"
},
{
"id": "949a2f76-5981-4fd2-9665-b10db26e2f48",
"name": "action",
"type": "string",
"value": "=flag"
},
{
"id": "7f4502b4-c330-4c9c-ab89-ba53874aafbb",
"name": "owner",
"type": "string",
"value": "={{ $json.owner || $json.owners[0].emailAddress }}"
},
{
"id": "592eb79e-28db-4470-8347-36b2a661cb03",
"name": "folder",
"type": "string",
"value": "={{ $json.folder || $json.parents[0]}}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "2562ed4a-8ecd-4a32-ae51-bc85daa9817b",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"position": [
1800,
440
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "1d28f976-2467-4d18-8698-556d29a5f8c0",
"name": "isDuplicate",
"type": "boolean",
"value": "={{ $json.isDuplicate }}"
},
{
"id": "e9d8eb20-7668-4287-bfb4-d4f66c019f73",
"name": "id",
"type": "string",
"value": "={{ $json.id }}"
},
{
"id": "587e5f8e-bd94-4ec5-80f2-066c99922135",
"name": "name",
"type": "string",
"value": "={{ $json.name }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "e7f0482c-77c7-46a0-8a36-e61bb624c422",
"name": "Filter",
"type": "n8n-nodes-base.filter",
"position": [
2020,
440
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "bd33247c-4c88-4c0b-bdfe-6f9dca0205e3",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.isDuplicate }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "28768732-29a4-4446-8b12-dda187976bf9",
"name": "Deduplicate Keep First",
"type": "n8n-nodes-base.code",
"position": [
1580,
560
],
"parameters": {
"jsCode": "// Sort files by creation time (oldest first)\nconst sorted = items.sort((a, b) => \n new Date(a.json.createdTime) - new Date(b.json.createdTime));\n\nconst seen = {};\nfor (const item of sorted) {\n const md5 = item.json.md5Checksum;\n\n // Failsafe: Skip if md5Checksum is missing or empty\n if (!md5) {\n item.json.isDuplicate = false; // Mark as not duplicate to avoid issues\n continue; // Skip to the next item\n }\n\n item.json.isDuplicate = md5 in seen;\n if (!item.json.isDuplicate) seen[md5] = true;\n}\nreturn items;"
},
"executeOnce": false,
"typeVersion": 2
},
{
"id": "1f6f9529-2283-4806-ad5a-b0425f9f68e2",
"name": "Deduplicate Keep Last",
"type": "n8n-nodes-base.code",
"position": [
1580,
360
],
"parameters": {
"jsCode": "// Sort files by creation time (latest first)\nconst sorted = items.sort((a, b) => \n new Date(b.json.createdTime) - new Date(a.json.createdTime));\n\nconst seen = {};\nfor (const item of sorted) {\n const md5 = item.json.md5Checksum;\n\n // Failsafe: Skip if md5Checksum is missing or empty\n if (!md5) {\n item.json.isDuplicate = false; // Mark as not duplicate to avoid issues\n continue; // Skip to the next item\n }\n\n if (md5 in seen) {\n item.json.isDuplicate = true;\n } else {\n item.json.isDuplicate = false;\n seen[md5] = true;\n }\n}\nreturn items;"
},
"executeOnce": false,
"typeVersion": 2
},
{
"id": "c5250dd1-6eeb-4b89-b2e7-e44a8d88212c",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-40,
-120
],
"parameters": {
"color": 5,
"width": 440,
"height": 800,
"content": "# 2. Configuration\nChoose the **keep** and **action** behavior of the workflow\n\n1. The **keep** parameter let's you decide whether to keep the first or last received file when duplicates are detected. (possible values: `first`, `last`. Default: `last`)\n2. The **action** parameter let's you decide what to do with the detected duplicates. Send them to the trash or flag them by renaming them with prefix DUPLICATE- (possible values: `trash`, `flag`. Default: `flag`) flag already prexied by DUPLICATE- are not flagged again.\n\n\nThe parameters `owner` and `folder` are taken from the trigger and will probably never need to be changed:\n- The **folder** points to the folder to work with. By default it is taken from the trigger.\n- The **owner** parameter needs to match the owner of the files. The workflow only works with files owned by this user. It is specified with the user email and is taken from the first file owner of the trigger."
},
"typeVersion": 1
},
{
"id": "67c4d02f-b170-4504-9bae-7bf14db7abd3",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
460,
180
],
"parameters": {
"color": 7,
"width": 320,
"height": 500,
"content": "## Working Folder\nThe \"Working Folder\" node let's you choose Files to deduplicate.\n\nThis workflow includes a filter to work on just 1 folder at depth level 1. It doesn't work with files in nested folders\n\nYou can remove the Folder filter to work on the entire drive instead or add different filters."
},
"typeVersion": 1
},
{
"id": "9ed26ef0-da89-43c5-9e12-2ec97b2e51f6",
"name": "Send Duplicates to Trash",
"type": "n8n-nodes-base.googleDrive",
"position": [
2760,
320
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {},
"operation": "deleteFile"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "VypmUgEf64twpmiZ",
"name": "Google Drive account"
}
},
"typeVersion": 3
},
{
"id": "fcfd08fa-7a19-4974-b3bb-6ed27a2030cf",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"position": [
2800,
600
],
"parameters": {},
"typeVersion": 1
},
{
"id": "de7967e7-eb3b-456c-b12e-6de3165ad29a",
"name": "Is Flagged",
"type": "n8n-nodes-base.if",
"position": [
2540,
620
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "c8d8eac5-e03a-4673-bcf9-a8acaa95cb8e",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $('Trash/Flag Duplicates').item.json.name }}",
"rightValue": "DUPLICATE-"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "d227d6ee-97e7-4b4d-b1a2-4cd402be99d5",
"name": "Google Drive Trigger",
"type": "n8n-nodes-base.googleDriveTrigger",
"position": [
-360,
460
],
"parameters": {
"event": "fileCreated",
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyX",
"unit": "minutes",
"value": 15
}
]
},
"triggerOn": "specificFolder",
"folderToWatch": {
"__rl": true,
"mode": "list",
"value": "1-tjf96Ooj0SL8qaE04BGIeCGnd-O1R8c",
"cachedResultUrl": "https://drive.google.com/drive/folders/1-tjf96Ooj0SL8qaE04BGIeCGnd-O1R8c",
"cachedResultName": "2025/04\n"
}
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "VypmUgEf64twpmiZ",
"name": "Google Drive account"
}
},
"typeVersion": 1
},
{
"id": "22e1638e-5c2e-41bc-b66e-fcee6af05762",
"name": "Drop Google Apps files",
"type": "n8n-nodes-base.filter",
"position": [
940,
460
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1e7d9666-fba0-4fe7-b03a-1a4e5c07b389",
"operator": {
"type": "string",
"operation": "notStartsWith"
},
"leftValue": "={{ $json.mimeType }}",
"rightValue": "application/vnd.google-apps"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "ec80f4de-5dff-4693-bff4-2509fd581d70",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
840,
180
],
"parameters": {
"color": 7,
"width": 320,
"height": 500,
"content": "# Discard found Google Apps documents\nDocs, Sheets, Forms, Slides, Drawins etc. are discarded because they are not actual binary files and their content can't be directly checked."
},
"typeVersion": 1
},
{
"id": "66ee766a-3dea-449f-827c-1922c6e053f3",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-520,
-120
],
"parameters": {
"color": 5,
"width": 440,
"height": 800,
"content": "# 1. Trigger Settings and Working Folder\n\nWhen using Google Drive Trigger configure the **Poll times** and the **Folder** to work with.\n\nBy Default the trigger is configured to check for *file uploads* every 15 minutes.\n\nWhen configured with a specific folder in the drive the workflow works only with files directly in the folder (It will not check/modify files in sub-folders).\n\nWhen configured with the root (/) folder of the drive it will check all files in all folders and sub-folders so **USE THIS WITH CAUTION** since it might lead to trashing/renaming of important files. "
},
"typeVersion": 1
},
{
"id": "6f8a7855-2ee3-426d-879f-afb303d5aa20",
"name": "Working Folder",
"type": "n8n-nodes-base.googleDrive",
"position": [
560,
460
],
"parameters": {
"filter": {
"folderId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Config').item.json.folder }}"
},
"whatToSearch": "files"
},
"options": {
"fields": [
"*"
]
},
"resource": "fileFolder",
"returnAll": true,
"queryString": "='{{$('Config').item.json.owner}}' in owners",
"searchMethod": "query"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "VypmUgEf64twpmiZ",
"name": "Google Drive account"
}
},
"typeVersion": 3
},
{
"id": "6f69e6d3-96ca-4411-9a48-160ebdb2a273",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
2500,
540
],
"parameters": {
"color": 7,
"width": 540,
"height": 220,
"content": "### Files that already start with *DUPLICATE-* are not flagged again."
},
"typeVersion": 1
},
{
"id": "65b4ba42-89ce-437c-a3e8-bf3f9b01cc21",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2500,
780
],
"parameters": {
"color": 7,
"width": 360,
"height": 240,
"content": "### In Google Drive Trashed files are kept for 30 days before being permanently deleted. \nThey can be reviewed and restored during that 30 day interval."
},
"typeVersion": 1
},
{
"id": "99374aa8-e597-4919-8b64-c376b246621a",
"name": "Google Drive",
"type": "n8n-nodes-base.googleDrive",
"position": [
2880,
800
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {},
"operation": "update",
"newUpdatedFileName": "=DUPLICATE-{{ $json.name }}"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "VypmUgEf64twpmiZ",
"name": "Google Drive account"
}
},
"typeVersion": 3
},
{
"id": "6ae62c31-4cf0-48e7-aa42-19fc259c5981",
"name": "Keep First/Last",
"type": "n8n-nodes-base.switch",
"position": [
1300,
460
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "last",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "7f5ba21d-8f3d-4736-9c34-ac7ebd6a9699",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Config').item.json.keep }}",
"rightValue": "last"
}
]
},
"renameOutput": true
},
{
"outputKey": "first",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "93a013f6-6c59-47ad-bce3-8b34cc8f026c",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Config').item.json.keep }}",
"rightValue": "first"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "9cb84da7-3cd9-4a53-af09-8b63f1cf8a34",
"name": "Trash/Flag Duplicates",
"type": "n8n-nodes-base.switch",
"position": [
2240,
440
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "send to trash",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "0314ac48-e7b7-406b-abcd-8cd1ab872c79",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Config').item.json.action }}",
"rightValue": "trash"
}
]
},
"renameOutput": true
},
{
"outputKey": "flag as duplicate",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "70d8e5f1-16a6-4921-ad9c-ab00049e507d",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Config').item.json.action }}",
"rightValue": "flag"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
}
],
"pinData": {},
"connections": {
"Config": {
"main": [
[
{
"node": "Working Folder",
"type": "main",
"index": 0
}
]
]
},
"Filter": {
"main": [
[
{
"node": "Trash/Flag Duplicates",
"type": "main",
"index": 0
}
]
]
},
"Is Flagged": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
],
[
{
"node": "Google Drive",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "Filter",
"type": "main",
"index": 0
}
]
]
},
"Working Folder": {
"main": [
[
{
"node": "Drop Google Apps files",
"type": "main",
"index": 0
}
]
]
},
"Keep First/Last": {
"main": [
[
{
"node": "Deduplicate Keep Last",
"type": "main",
"index": 0
}
],
[
{
"node": "Deduplicate Keep First",
"type": "main",
"index": 0
}
]
]
},
"Google Drive Trigger": {
"main": [
[
{
"node": "Config",
"type": "main",
"index": 0
}
]
]
},
"Deduplicate Keep Last": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"Trash/Flag Duplicates": {
"main": [
[
{
"node": "Send Duplicates to Trash",
"type": "main",
"index": 0
}
],
[
{
"node": "Is Flagged",
"type": "main",
"index": 0
}
]
]
},
"Deduplicate Keep First": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"Drop Google Apps files": {
"main": [
[
{
"node": "Keep First/Last",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,690 @@
{
"meta": {
"instanceId": "408f9fb9940c3cb18ffdef0e0150fe342d6e655c3a9fac21f0f644e8bedabcd9",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "b87cc222-82ec-4b46-9573-68f41d096969",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
620
],
"parameters": {
"color": 7,
"width": 740,
"height": 680,
"content": "## 2. Manually Convert XLSX to Markdown\n[Learn more about the Extract From File node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.extractfromfile/)\n\nToday's LLMs cannot parse Excel files directly so the best we can do is to convert the spreadsheet into a format that they can, namely markdown. This conversion is also a good solution for excels which aren't really datasheets - the cells are used like layout elements - which is still common for invoices and purchase orders.\n\nTo perform the conversion, we can use the 'Extract from File' node to get the each row from the xlsx and then iterate and concatenate to form our markdown table using the code node."
},
"typeVersion": 1
},
{
"id": "c4c55042-02c8-4364-ae7e-d1ec5a75437a",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1400,
620
],
"parameters": {
"color": 7,
"width": 640,
"height": 680,
"content": "## 3. Extract Purchase Order Details using AI\n[Learn more about the Information Extractor](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.information-extractor)\n\nData entry is probably the number one reason as to why we need AI/LLMs. This time consuming and menial task can be completed in seconds and with a high degree of accuracy. Here, we ask the AI to extract each event with the term dates to a list of events using structured output."
},
"typeVersion": 1
},
{
"id": "b9530f93-464b-4116-add7-da218fe8eb12",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-700,
-80
],
"parameters": {
"width": 460,
"height": 1400,
"content": "## Try it out!\n### This n8n template imports purchase order submissions from Outlook and converts attached purchase order form in XLSX format into structured output.\n\nData entry jobs with user-submitted XLSX forms is a time consuming, incredibly mundane but necessary tasks which in likelihood are inherited and critical to business operation.\n\nWhile we could dream of system overhauls and modernisation, the fact is that change is hard. There is another way however - using n8n and AI!\n\n### How it works\n* An Outlook trigger is used to watch for incoming purchase order forms submitted via a shared inbox.\n* The email attachment for the submission is a form in xlsx format - like this one https://1drv.ms/x/c/8f1f7dda12b7a145/ETWH8dKwgZ1OiVz7ISUWYf8BwiyihBjXPXEbCYkVi8XDyw?e=WWU2eR - which is imported into the workflow.\n* The 'Extract from File' node is used with the 'code' node to convert the xlsx file to markdown. This is so our LLM can understand it.\n* The Information Extractor node is used to read and extract the relevant purchase order details and line items from the form.\n* A simple validation step is used to check for common errors such as missing PO number or the amounts not matching up. A notification is automated to reply to the buyer if so.\n* Once validation passes, a confirmation is sent to the buyer and the purchase order structured output can be sent along to internal systems.\n\n### How to use\n* This template only works if you're expecting and receiving forms in XLSX format. These can be invoices, request forms as well as purchase order forms.\n* Update the Outlook nodes with your email or other emails as required.\n* What's next? I've omitted the last steps to send to an ERP or accounting system as this is dependent on your org.\n\n### Requirements\n* Outlook for Emails\n * Check out how to setup credentials here: https://docs.n8n.io/integrations/builtin/credentials/microsoft/\n* OpenAI for LLM document understanding and extraction.\n\n### Customising the workflow\n* This template should work for other Excel files. Some will be more complicated than others so experiment with different parsers and extraction tools and strategies.\n* Customise the Information Extractor Schema to pull out the specific data you need. For example, capture any notes or comments given by the buyer.\n\n### Need Help?\nJoin the [Discord](https://discord.com/invite/XPKeKXeB7d) or ask in the [Forum](https://community.n8n.io/)!\n\nHappy Hacking!"
},
"typeVersion": 1
},
{
"id": "f5a2d1e7-f73b-4bfa-8e02-f30db275bbcc",
"name": "Extract Purchase Order Details",
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"position": [
1500,
920
],
"parameters": {
"text": "={{ $json.table }}",
"options": {
"systemPromptTemplate": "Capture the values as seen. Do not convert dates."
},
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"purchase_order_number\": { \"type\": \"string\" },\n \"purchase_order_date\": { \"type\": \"string\" },\n \"purchase_order_total\": { \"type\": \"number\" },\n \"vendor_name\": { \"type\": \"string\" },\n \"vendor_address\": { \"type\": \"string\" },\n \"vendor_contact\": { \"type\": \"string\" },\n \"delivery_contact\": { \"type\": \"string\" },\n \"delivery_address\": { \"type\": \"string\" },\n \"delivery_method\": { \"type\": \"string\" },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"description\": { \"type\": \"string\" },\n \"part_number\": { \"type\": \"string\" },\n \"quantity\": { \"type\": \"number\" },\n \"unit\": { \"type\": \"number\" },\n \"unit_price\": { \"type\": \"number\" }\n }\n }\n }\n }\n}"
},
"typeVersion": 1
},
{
"id": "0ce545f0-8147-4ad2-bb9e-14ef0b0c26ef",
"name": "Is Excel Document?",
"type": "n8n-nodes-base.if",
"position": [
760,
1020
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f723ab0a-8f2d-4501-8273-fd6455c57cdd",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $binary.data.mimeType }}",
"rightValue": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "ccbd9531-66be-4e07-8b73-faf996622f9f",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-220,
460
],
"parameters": {
"color": 5,
"width": 340,
"height": 140,
"content": "### PURCHASE ORDER EXAMPLE\nThis is the purchase order XLSX which is used an example for this template.\nhttps://1drv.ms/x/c/8f1f7dda12b7a145/ETWH8dKwgZ1OiVz7ISUWYf8BwiyihBjXPXEbCYkVi8XDyw?e=WWU2eR"
},
"typeVersion": 1
},
{
"id": "ef8b00eb-dba6-47dd-a825-1aa5c85ee215",
"name": "Run Checks",
"type": "n8n-nodes-base.set",
"position": [
2160,
940
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "049c7aca-7663-4eed-93b4-9eec3760c058",
"name": "has_po_number",
"type": "boolean",
"value": "={{ Boolean($json.output.purchase_order_number) }}"
},
{
"id": "94d2224a-cf81-4a42-acd0-de5276a5e493",
"name": "has_valid_po_date",
"type": "boolean",
"value": "={{ $json.output.purchase_order_date.toDateTime() < $now.plus({ 'day': 1 }) }}"
},
{
"id": "a8f69605-dad6-4ec2-a22f-d13ff99e27cd",
"name": "has_items",
"type": "boolean",
"value": "={{ $json.output.items.length > 0 }}"
},
{
"id": "c11db99e-9cc2-40b7-b3a5-f3c65f88dc13",
"name": "is_math_correct",
"type": "boolean",
"value": "={{\n$json.output.items.map(item => item.unit_price * item.quantity).sum().round(2) === $json.output.purchase_order_total.round(2) }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "801848cc-558c-4a30-aab5-eb403564b68f",
"name": "Is Valid Purchase Order?",
"type": "n8n-nodes-base.if",
"position": [
2360,
940
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "11fa8087-7809-4bc9-9fbe-32bfd35821a6",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.has_po_number }}",
"rightValue": ""
},
{
"id": "c45ae85a-e060-4416-aa2c-daf58db8ba0e",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.has_valid_po_date }}",
"rightValue": ""
},
{
"id": "d0ae9518-2f4b-43fb-87b1-7108a6a75424",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.has_items }}",
"rightValue": ""
},
{
"id": "eed09f78-ce1a-4e09-8940-febcf7e41078",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.is_math_correct }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "7c7dd7a0-45fe-4549-8341-3b3fd18e1725",
"name": "Extract from File",
"type": "n8n-nodes-base.extractFromFile",
"position": [
980,
920
],
"parameters": {
"options": {
"rawData": true,
"headerRow": false,
"includeEmptyCells": true
},
"operation": "xlsx"
},
"typeVersion": 1
},
{
"id": "dfb6b00f-fe50-42d6-8597-8fdcb562714b",
"name": "XLSX to Markdown Table",
"type": "n8n-nodes-base.code",
"position": [
1180,
920
],
"parameters": {
"jsCode": "const rows = $input.all().map(item => item.json.row);\nconst maxLength = Math.max(...rows.map(row => row.length));\n\nconst table = [\n '|' + rows[0].join('|') + '|',\n '|' + Array(maxLength).fill(0).map(_ => '-').join('|') + '|',\n rows.slice(1, rows.length)\n .filter(row => row.some(Boolean))\n .map(row =>\n '|' + row.join('|') + '|'\n ).join('\\n')\n].join('\\n')\n\nreturn { table }"
},
"typeVersion": 2
},
{
"id": "1a3de516-1d21-4664-b2e3-8c8d6ec90ef2",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1600,
1080
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"id": "8gccIjcuf3gvaoEr",
"name": "OpenAi account"
}
},
"typeVersion": 1.2
},
{
"id": "1a29236f-5eaa-4a38-a0a1-6e19abd77d2c",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2060,
620
],
"parameters": {
"color": 7,
"width": 940,
"height": 680,
"content": "## 4. Use Simple Validation to Save Time and Effort\n[Learn more about the Edit Fields node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set)\n\nWith our extracted output, we can run simple validation checks to save on admin time. Common errors such as missing purchase order numbers or miscalculated cost amounts are easy to detect and a quick response can be given. Once validation passes, it's up to you how you use the extracted output next."
},
"typeVersion": 1
},
{
"id": "79a39a03-5f71-4021-bcfd-06edbc285e8a",
"name": "Reply Invalid Format",
"type": "n8n-nodes-base.microsoftOutlook",
"position": [
980,
1120
],
"webhookId": "9464583e-9505-49ec-865e-58aa1ab3c2ed",
"parameters": {
"message": "PO rejected due to invalid file format. Please try again with XLSX.",
"options": {},
"messageId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Outlook Trigger').first().json.id }}"
},
"operation": "reply",
"additionalFields": {},
"replyToSenderOnly": true
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "EWg6sbhPKcM5y3Mr",
"name": "Microsoft Outlook account"
}
},
"typeVersion": 2
},
{
"id": "ec973438-4d6c-4d2e-8702-1d195f514528",
"name": "Outlook Trigger",
"type": "n8n-nodes-base.microsoftOutlookTrigger",
"position": [
-120,
920
],
"parameters": {
"fields": [
"body",
"categories",
"conversationId",
"from",
"hasAttachments",
"internetMessageId",
"sender",
"subject",
"toRecipients",
"receivedDateTime",
"webLink"
],
"output": "fields",
"filters": {
"hasAttachments": true,
"foldersToInclude": []
},
"options": {
"downloadAttachments": true
},
"pollTimes": {
"item": [
{
"mode": "everyHour"
}
]
}
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "EWg6sbhPKcM5y3Mr",
"name": "Microsoft Outlook account"
}
},
"typeVersion": 1
},
{
"id": "fcb173ce-7dad-497a-9376-9650c2a24a84",
"name": "Reply Rejection",
"type": "n8n-nodes-base.microsoftOutlook",
"position": [
2580,
1040
],
"webhookId": "9464583e-9505-49ec-865e-58aa1ab3c2ed",
"parameters": {
"message": "=PO Rejected due to the following errors:\n{{\n[\n !$json.has_po_number ? '* PO number was not provided' : '',\n !$json.has_valid_po_date ? '* PO date was missing or invalid' : '',\n !$json.has_items ? '* No line items detected' : '',\n !$json.is_math_correct ? '* Line items prices do not match up to PO total' : ''\n]\n .compact()\n .join('\\n')\n}}",
"options": {},
"messageId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Outlook Trigger').first().json.id }}"
},
"operation": "reply",
"additionalFields": {},
"replyToSenderOnly": true
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "EWg6sbhPKcM5y3Mr",
"name": "Microsoft Outlook account"
}
},
"typeVersion": 2
},
{
"id": "64ced193-6b12-4ee9-b1e2-735040648051",
"name": "Reply Accepted",
"type": "n8n-nodes-base.microsoftOutlook",
"position": [
2580,
820
],
"webhookId": "9464583e-9505-49ec-865e-58aa1ab3c2ed",
"parameters": {
"message": "=Thank you for the purchase order.\nThis is an automated reply.",
"options": {},
"messageId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Outlook Trigger').first().json.id }}"
},
"operation": "reply",
"additionalFields": {},
"replyToSenderOnly": true
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "EWg6sbhPKcM5y3Mr",
"name": "Microsoft Outlook account"
}
},
"typeVersion": 2
},
{
"id": "7bfe0e44-cd5d-4290-ba2e-0064c95bc4e2",
"name": "Do Something with Purchase Order",
"type": "n8n-nodes-base.noOp",
"position": [
2800,
940
],
"parameters": {},
"typeVersion": 1
},
{
"id": "6f517f2f-6072-46a2-8a9d-cca4e958d601",
"name": "Fix Excel Dates",
"type": "n8n-nodes-base.set",
"position": [
1840,
920
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={{\n{\n output: {\n ...$json.output,\n purchase_order_date: $json.output.purchase_order_date\n ? new Date((new Date(1900, 0, 1)).getTime() + (Number($json.output.purchase_order_date) - 2) * (24 * 60 * 60 * 1000))\n : $json.output.purchase_order_date\n }\n}\n}}"
},
"typeVersion": 3.4
},
{
"id": "f3a31b63-ebcb-4d93-8c5a-f626897b7d68",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-220,
620
],
"parameters": {
"color": 7,
"width": 840,
"height": 680,
"content": "## 1. Wait For Incoming Purchase Orders\n[Read more about the Outlook trigger](https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.microsoftoutlooktrigger)\n\nOur template starts by watching for new emails to a shared inbox (eg. \"purchase-orders@example.com\") using the Outlook Trigger node. Our goal is to identify and capture buyer purchase orders so that we can automating validate and use AI to reduce the data entry time and cost at scale.\n\nWe can also use the Text Classifier node to validate intent. This ensures we catch valid submissions are not just queries about purchase-orders or replies."
},
"typeVersion": 1
},
{
"id": "bb395dfc-2831-4e57-90c9-62f13f84302e",
"name": "Is Submitting a Purchase Order?",
"type": "@n8n/n8n-nodes-langchain.textClassifier",
"position": [
80,
920
],
"parameters": {
"options": {
"fallback": "other"
},
"inputText": "=from: {{ $json.from.emailAddress.name }} <{{ $json.from.emailAddress.address }}>\nsubject: {{ $json.subject }}\nmessage:\n{{ $json.body.content }}",
"categories": {
"categories": [
{
"category": "is_purchase_order",
"description": "The message's intent is to submit a purchase order"
}
]
}
},
"typeVersion": 1
},
{
"id": "e52ec2e2-8be5-40ab-b1f8-8d7c0b161e1a",
"name": "Do Nothing",
"type": "n8n-nodes-base.noOp",
"position": [
420,
1040
],
"parameters": {},
"typeVersion": 1
},
{
"id": "5ca6be4e-bc33-42d7-91bc-d30f7ccfdd25",
"name": "OpenAI Chat Model1",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
180,
1080
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "gpt-4o-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"id": "8gccIjcuf3gvaoEr",
"name": "OpenAi account"
}
},
"typeVersion": 1.2
}
],
"pinData": {},
"connections": {
"Run Checks": {
"main": [
[
{
"node": "Is Valid Purchase Order?",
"type": "main",
"index": 0
}
]
]
},
"Reply Accepted": {
"main": [
[
{
"node": "Do Something with Purchase Order",
"type": "main",
"index": 0
}
]
]
},
"Fix Excel Dates": {
"main": [
[
{
"node": "Run Checks",
"type": "main",
"index": 0
}
]
]
},
"Outlook Trigger": {
"main": [
[
{
"node": "Is Submitting a Purchase Order?",
"type": "main",
"index": 0
}
]
]
},
"Extract from File": {
"main": [
[
{
"node": "XLSX to Markdown Table",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Extract Purchase Order Details",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Is Excel Document?": {
"main": [
[
{
"node": "Extract from File",
"type": "main",
"index": 0
}
],
[
{
"node": "Reply Invalid Format",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model1": {
"ai_languageModel": [
[
{
"node": "Is Submitting a Purchase Order?",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"XLSX to Markdown Table": {
"main": [
[
{
"node": "Extract Purchase Order Details",
"type": "main",
"index": 0
}
]
]
},
"Is Valid Purchase Order?": {
"main": [
[
{
"node": "Reply Accepted",
"type": "main",
"index": 0
}
],
[
{
"node": "Reply Rejection",
"type": "main",
"index": 0
}
]
]
},
"Extract Purchase Order Details": {
"main": [
[
{
"node": "Fix Excel Dates",
"type": "main",
"index": 0
}
]
]
},
"Is Submitting a Purchase Order?": {
"main": [
[
{
"node": "Is Excel Document?",
"type": "main",
"index": 0
}
],
[
{
"node": "Do Nothing",
"type": "main",
"index": 0
}
]
]
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,405 @@
{
"meta": {
"instanceId": "431926ace0ab32761b92304a05ffb4819a2a2a8ee5de45404953945769b5412a"
},
"nodes": [
{
"id": "53bf4cb6-8f55-4d8d-b4af-48345f75cdd5",
"name": "Daily Trigger1",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-660,
6580
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1
},
{
"id": "774624c1-cb4d-4355-9ed7-448d393c5f3b",
"name": "Set Date1",
"type": "n8n-nodes-base.set",
"position": [
-440,
6580
],
"parameters": {
"values": {
"string": [
{
"name": "today",
"value": "={{ new Date().toISOString().split('T')[0] }}"
}
]
},
"options": {}
},
"typeVersion": 1
},
{
"id": "951eb189-8143-48d7-88c9-3ce235de83f6",
"name": "Sticky Note16",
"type": "n8n-nodes-base.stickyNote",
"position": [
-260,
6400
],
"parameters": {
"content": "### 🔐 How to Get Your Product Hunt Token\n\nTo get your Product Hunt token, follow the official guide here: \n👉 [Product Hunt OAuth Token Guide](https://api.producthunt.com/v2/docs/oauth_user_authentication/oauth_authorize_ask_for_access_grant_code_on_behalf_of_the_user)\n"
},
"typeVersion": 1
},
{
"id": "ae83bb19-a981-4b28-8dcd-ecd9501bd3d0",
"name": "Sticky Note17",
"type": "n8n-nodes-base.stickyNote",
"position": [
820,
6280
],
"parameters": {
"width": 360,
"height": 280,
"content": "### 📄 How to Connect Google Sheets in n8n\n\nTo connect your Google Sheets to n8n:\n\n1. Go to your n8n Credentials page.\n2. Select **Google Sheets** and add new credentials.\n3. Authenticate your Google account and give the required permissions.\n\nFollow the full guide here: \n👉 https://www.youtube.com/watch?v=pWGXlZBGu4k\n"
},
"typeVersion": 1
},
{
"id": "4a0c04d4-3ce2-4ebb-94a3-2a0441e25e23",
"name": "Fetches todays Product Hunt posts via API.",
"type": "n8n-nodes-base.httpRequest",
"notes": "### 🔐 How to Get Your Product Hunt Token\n\nTo get your Product Hunt token, follow the official guide here: \n👉 [Product Hunt OAuth Token Guide](https://api.producthunt.com/v2/docs/oauth_user_authentication/oauth_authorize_ask_for_access_grant_code_on_behalf_of_the_user)\n",
"position": [
-220,
6580
],
"parameters": {
"url": "https://api.producthunt.com/v2/api/graphql",
"method": "POST",
"options": {},
"sendBody": true,
"sendHeaders": true,
"bodyParameters": {
"parameters": [
{
"name": "query",
"value": "query { posts(first: 10, postedAfter: \"{{ $node[\\\"Set Date1\\\"].json[\\\"today\\\"] }}T00:00:00Z\", postedBefore: \"{{ $node[\\\"Set Date1\\\"].json[\\\"today\\\"] }}T23:59:59Z\") { edges { node { name tagline description website } cursor } pageInfo { hasNextPage endCursor } } }"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer YOUR_PRODUCT_HUNT_API_KEY"
},
{
"name": "User-Agent",
"value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
]
}
},
"notesInFlow": false,
"typeVersion": 4.2
},
{
"id": "994c8a22-ce3a-42cf-95e1-9512f1525fd7",
"name": "Extracts Product Info",
"type": "n8n-nodes-base.code",
"position": [
0,
6580
],
"parameters": {
"jsCode": "return $json.data.posts.edges.map(edge => {\n return {\n json: {\n name: edge.node.name,\n tagline: edge.node.tagline,\n description: edge.node.description,\n website: edge.node.website\n }\n };\n});\n"
},
"typeVersion": 2
},
{
"id": "f7846147-cd50-4b5e-bb79-0f17ff7d5900",
"name": "Resolve Website Redirection",
"type": "n8n-nodes-base.httpRequest",
"position": [
220,
6680
],
"parameters": {
"url": "={{ $json.website }}\n",
"options": {
"fullResponse": true,
"followRedirect": false,
"followAllRedirects": false,
"ignoreResponseCode": true
},
"responseFormat": "string",
"dataPropertyName": "body",
"allowUnauthorizedCerts": true
},
"typeVersion": 1
},
{
"id": "11f5df7a-bc46-4ae6-b97d-0ce8c15d804d",
"name": "Data 2 (website url)",
"type": "n8n-nodes-base.set",
"position": [
440,
6680
],
"parameters": {
"values": {
"string": [
{
"name": "next_url",
"value": "={{$json[\"headers\"][\"location\"]}}"
}
]
},
"options": {},
"keepOnlySet": true
},
"typeVersion": 1
},
{
"id": "3fd9b50e-c30b-44dd-ac53-83b0a597db2e",
"name": "Data 1 (product info)",
"type": "n8n-nodes-base.set",
"position": [
440,
6480
],
"parameters": {
"values": {
"string": [
{
"name": "name",
"value": "={{ $json.name }}"
},
{
"name": "tagline",
"value": "={{ $json.tagline }}"
},
{
"name": "description",
"value": "={{ $json.description }}"
}
]
},
"options": {},
"keepOnlySet": true
},
"typeVersion": 1
},
{
"id": "68acc44b-10cd-4bae-bf01-b304cd753f15",
"name": "Merge Data",
"type": "n8n-nodes-base.function",
"position": [
660,
6580
],
"parameters": {
"functionCode": "// Initialize empty arrays for both data sources\nlet productData = [];\nlet redirectData = [];\n\ntry {\n productData = $items(\"Data to Keep4\");\n} catch (error) {\n console.log(\"Error fetching product data:\", error.message);\n}\n\ntry {\n redirectData = $items(\"Data to Keep3\");\n} catch (error) {\n console.log(\"Error fetching redirect data:\", error.message);\n}\n\nconst mergedItems = [];\n\nfor (let i = 0; i < productData.length; i++) {\n const product = productData[i].json;\n \n const mergedItem = {\n name: product.name,\n tagline: product.tagline,\n description: product.description,\n next_url: null\n };\n \n if (i < redirectData.length && redirectData[i] && redirectData[i].json) {\n let url = redirectData[i].json.next_url;\n // Remove ?ref=producthunt from the URL\n if (url && url.includes('?ref=producthunt')) {\n url = url.replace('?ref=producthunt', '');\n }\n mergedItem.next_url = url;\n }\n \n mergedItems.push({ json: mergedItem });\n}\n\nconsole.log(`Product data items: ${productData.length}`);\nconsole.log(`Redirect data items: ${redirectData.length}`);\nconsole.log(`Merged items: ${mergedItems.length}`);\n\nreturn mergedItems;"
},
"typeVersion": 1
},
{
"id": "39429f34-19d1-488a-9603-7b25f6042fa6",
"name": "Appends all details",
"type": "n8n-nodes-base.googleSheets",
"position": [
880,
6580
],
"parameters": {
"columns": {
"value": {
"name": "={{ $json.name }}",
"tagline": "={{ $json.tagline }}",
"description": "={{ $json.description }}"
},
"schema": [
{
"id": "name",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "tagline",
"type": "string",
"display": true,
"required": false,
"displayName": "tagline",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "description",
"type": "string",
"display": true,
"required": false,
"displayName": "description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "next_url",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "next_url",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "autoMapInputData",
"matchingColumns": [
"name"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "demo",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "demo",
"cachedResultUrl": "demo",
"cachedResultName": "Get product hunt products"
},
"authentication": "serviceAccount"
},
"typeVersion": 4.5
},
{
"id": "6be5f1a1-c6e9-4dea-9199-523cd7f4b659",
"name": "Sticky Note18",
"type": "n8n-nodes-base.stickyNote",
"position": [
-980,
6380
],
"parameters": {
"width": 280,
"height": 260,
"content": "### About Me \n\nHey there! Im **Ajetomobi Ifeoluwa** the brains (and vibe) behind this template. When Im not crafting cool workflows, Im busy making the web more beautiful and functional as a **UI/UX Designer** and **Vibe Coder**. Want your project to stand out? Lets chat! Check out my [portfolio](https://ifeoluwaajetomobi.framer.website/) and my work on [Behance](https://www.behance.net/ajetomoifeoluw). Lets create something awesome together! 🎨✨\n\n"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Set Date1": {
"main": [
[
{
"node": "Fetches todays Product Hunt posts via API.",
"type": "main",
"index": 0
}
]
]
},
"Merge Data": {
"main": [
[
{
"node": "Appends all details",
"type": "main",
"index": 0
}
]
]
},
"Daily Trigger1": {
"main": [
[
{
"node": "Set Date1",
"type": "main",
"index": 0
}
]
]
},
"Data 2 (website url)": {
"main": [
[
{
"node": "Merge Data",
"type": "main",
"index": 0
}
]
]
},
"Data 1 (product info)": {
"main": [
[
{
"node": "Merge Data",
"type": "main",
"index": 0
}
]
]
},
"Extracts Product Info": {
"main": [
[
{
"node": "Resolve Website Redirection",
"type": "main",
"index": 0
},
{
"node": "Data 1 (product info)",
"type": "main",
"index": 0
}
]
]
},
"Resolve Website Redirection": {
"main": [
[
{
"node": "Data 2 (website url)",
"type": "main",
"index": 0
}
]
]
},
"Fetches todays Product Hunt posts via API.": {
"main": [
[
{
"node": "Extracts Product Info",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,458 @@
{
"meta": {
"instanceId": "4de3652773c3e67f6210deb1e1d390d75b23715f2e2cca0340008f99419607e6"
},
"nodes": [
{
"id": "4c9256c8-8dd7-4e81-8aef-0789e6808808",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-260,
80
],
"parameters": {},
"typeVersion": 1
},
{
"id": "1935ad6a-ade4-4073-9205-0c3dd1091c0f",
"name": "Set parameters for next run",
"type": "n8n-nodes-base.code",
"position": [
1520,
460
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const desired_path = $('Create desired path').item.json.desired_path;\ndesired_path.shift();\n\nreturn {\n desired_path: desired_path,\n google_drive_folder_id: $json.id,\n}"
},
"typeVersion": 2
},
{
"id": "5d99a9c4-57c6-4052-b093-fb0c32d9ff56",
"name": "Execute Workflow Trigger",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
-40,
460
],
"parameters": {},
"typeVersion": 1
},
{
"id": "879b92ae-edab-4d73-96d0-4df36d12fbb2",
"name": "Dummy input data",
"type": "n8n-nodes-base.set",
"position": [
-40,
80
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "041e1077-f4dc-476f-b75a-6d60d9c8d0b9",
"name": "google_drive_folder_id",
"type": "string",
"value": "root"
},
{
"id": "843e3a7f-c59e-48c1-80f8-c9995515e340",
"name": "desired_path",
"type": "string",
"value": "testXavier/2024/Q4/03 Documenten"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "822d45f1-149d-430c-8daf-183998c01166",
"name": "Split the desired path",
"type": "n8n-nodes-base.code",
"position": [
340,
260
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Add a new field called 'myNewField' to the JSON of the item\n$input.item.json.desired_path = $input.item.json.desired_path.split('/');\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "e2aba13a-fec6-4d1e-aa1c-af95d3f957ad",
"name": "Create desired path",
"type": "n8n-nodes-base.code",
"position": [
580,
260
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "return {\n google_drive_folder_id: $json.google_drive_folder_id,\n desired_path: $json.desired_path,\n};"
},
"typeVersion": 2
},
{
"id": "aa3f9b95-3197-4b89-bcb2-9e723b8496a0",
"name": "Check if top folder exists",
"type": "n8n-nodes-base.googleDrive",
"position": [
800,
260
],
"parameters": {
"filter": {
"folderId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.google_drive_folder_id }}"
},
"whatToSearch": "folders"
},
"options": {},
"resource": "fileFolder",
"queryString": "={{ $json.desired_path[0] }}"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "Xk1mfDiQRaqwWUaU",
"name": "Google Drive account 2"
}
},
"typeVersion": 3,
"alwaysOutputData": true
},
{
"id": "969b7823-2720-45c5-b98c-1cc659fe62df",
"name": "If top folder doesn't exist",
"type": "n8n-nodes-base.if",
"position": [
1040,
260
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "59e55ba1-5db4-455e-95a1-bb8e4c1d0d31",
"operator": {
"type": "object",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "2cd3932d-b066-438a-b968-4078dfc9dbe7",
"name": "Create new subfolder",
"type": "n8n-nodes-base.googleDrive",
"position": [
1340,
240
],
"parameters": {
"name": "={{ $('Create desired path').item.json.desired_path[0] }}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {},
"folderId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Create desired path').item.json.google_drive_folder_id }}"
},
"resource": "folder"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "Xk1mfDiQRaqwWUaU",
"name": "Google Drive account 2"
}
},
"typeVersion": 3
},
{
"id": "f9322682-b77f-4bad-8bbc-13868c126063",
"name": "If path has been completely created",
"type": "n8n-nodes-base.if",
"position": [
1740,
460
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d95b4b2e-68c5-4d82-84af-a46fbb84035c",
"operator": {
"type": "array",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.desired_path }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "94c4694b-0a32-4681-b977-c01e3232d9e8",
"name": "Return the ID of the last folder",
"type": "n8n-nodes-base.set",
"position": [
2040,
440
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "692a23db-71c8-4154-af87-a0177045b63d",
"name": "google_drive_folder_id",
"type": "string",
"value": "={{ $('Set parameters for next run').item.json.google_drive_folder_id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "5e9f327d-61bb-46af-b16b-21499f5c22e0",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-820,
-80
],
"parameters": {
"width": 480,
"height": 880,
"content": "# Create Google Drive Folders by Path\nThis workflow created nested Google Drive folder from a path string and returns the ID of the final folder for immediate use.\n\nUse this workflow in your other flows by calling it directly with the following data:\n- `google_drive_folder_id` -> The ID of the folder where you want to create additional folders in. You can use \"root\" if you want to begin at root level of your Drive.\n- `desired_path` -> The folder structure you'd like to create in Google Drive. Each folder is separated by a slash, eg: `Projects/Clients/Reports`"
},
"typeVersion": 1
},
{
"id": "35b3741f-465a-4846-9f62-4dedc40ca884",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-280,
-20
],
"parameters": {
"color": 5,
"width": 500,
"height": 80,
"content": "## Test data for the workflow\nUse this in case you want to test the workflow."
},
"typeVersion": 1
},
{
"id": "3b7fe210-d966-4988-aaf4-5e07567b3054",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-280,
320
],
"parameters": {
"color": 5,
"width": 500,
"height": 120,
"content": "## Triggered from another workflow\nThis workflow is intended to be triggered by other workflows. Don't copy/paste this workflow as it will be more difficult to maintain and keep up-to-date."
},
"typeVersion": 1
},
{
"id": "16477e77-656e-4bff-914f-633d61477d38",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
560,
80
],
"parameters": {
"color": 5,
"width": 1320,
"height": 120,
"content": "## Main loop\nTake the desired_path and split it into parts. Eg: `Projects/Clients/Reports` will turn into 3 parts: Projects, Clients, Reports.\nWe then check if the top folder exists and create it if not. We repeat this process until all subfolders have been created and correctly nested."
},
"typeVersion": 1
},
{
"id": "57404f59-28b8-4969-b483-fb8a3320a592",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1980,
80
],
"parameters": {
"color": 5,
"width": 280,
"height": 120,
"content": "## Rerturn data\nHere we return the ID of the last folder in the path, so you can start uploading new files to it."
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Dummy input data": {
"main": [
[
{
"node": "Split the desired path",
"type": "main",
"index": 0
}
]
]
},
"Create desired path": {
"main": [
[
{
"node": "Check if top folder exists",
"type": "main",
"index": 0
}
]
]
},
"Create new subfolder": {
"main": [
[
{
"node": "Set parameters for next run",
"type": "main",
"index": 0
}
]
]
},
"Split the desired path": {
"main": [
[
{
"node": "Create desired path",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow Trigger": {
"main": [
[
{
"node": "Split the desired path",
"type": "main",
"index": 0
}
]
]
},
"Check if top folder exists": {
"main": [
[
{
"node": "If top folder doesn't exist",
"type": "main",
"index": 0
}
]
]
},
"If top folder doesn't exist": {
"main": [
[
{
"node": "Create new subfolder",
"type": "main",
"index": 0
}
],
[
{
"node": "Set parameters for next run",
"type": "main",
"index": 0
}
]
]
},
"Set parameters for next run": {
"main": [
[
{
"node": "If path has been completely created",
"type": "main",
"index": 0
}
]
]
},
"When clicking Test workflow": {
"main": [
[
{
"node": "Dummy input data",
"type": "main",
"index": 0
}
]
]
},
"If path has been completely created": {
"main": [
[
{
"node": "Return the ID of the last folder",
"type": "main",
"index": 0
}
],
[
{
"node": "Create desired path",
"type": "main",
"index": 0
}
]
]
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,540 @@
{
"meta": {
"instanceId": "d73282515b90623d4a8783919a2d772c706425d649e1512792f37ac51e07e4a8"
},
"nodes": [
{
"id": "62b3c7cb-1993-44f1-8b86-38a34ca1d029",
"name": "Information Extractor",
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"position": [
-200,
500
],
"parameters": {
"text": "={{ $json.query }}",
"options": {},
"schemaType": "fromJson",
"jsonSchemaExample": "{\n \"name\": \"Information Extractor\",\n \"type\": \"n8n-nodes-base.informationExtractor\",\n \"parameters\": {\n \"extract\": [\n {\n \"name\": \"items\",\n \"pattern\": \"(latte|coffee|tea|cappuccino)\"\n },\n {\n \"name\": \"quantity\",\n \"pattern\": \"\\\\d+\"\n },\n {\n \"name\": \"table\",\n \"pattern\": \"table number (\\\\d+)\"\n }\n ]\n }\n}\n"
},
"typeVersion": 1
},
{
"id": "75883f27-af58-4791-9e1a-a70b83e1cead",
"name": "OpenAI Chat Model1",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-180,
740
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"id": "OizdHUANhz9NIHyd",
"name": "OpenAi account"
}
},
"typeVersion": 1.2
},
{
"id": "aeefdd4b-bf7d-4824-97d8-0afc356fb7d6",
"name": "If",
"type": "n8n-nodes-base.if",
"position": [
120,
540
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "8a5dda0c-a567-4305-83a3-68d6fb573dd3",
"operator": {
"type": "array",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.output.parameters.extract }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "9e3f8a1b-ccd8-4f4d-91cb-b99cc46f412f",
"name": "Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
840,
420
],
"parameters": {
"columns": {
"value": {
"Item": "={{ $json.item }}",
"Quantity": "={{ $json.quantity }}",
"Table No": "={{ $json.table }}",
"Timestamp": "={{ $now }}"
},
"schema": [
{
"id": "Timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "Timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Table No",
"type": "string",
"display": true,
"required": false,
"displayName": "Table No",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Item",
"type": "string",
"display": true,
"required": false,
"displayName": "Item",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Quantity",
"type": "string",
"display": true,
"required": false,
"displayName": "Quantity",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/16fXaxEcfnq_-oif9tp94-3uTeHSFWoSnuBPNTljuW-k/edit#gid=0",
"cachedResultName": "Order log"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/16fXaxEcfnq_-oif9tp94-3uTeHSFWoSnuBPNTljuW-k/edit?usp=sharing"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "0RSJGMBcFzxY9GkS",
"name": "Google Sheets account"
}
},
"typeVersion": 4.5
},
{
"id": "4cc1818f-1585-42e1-a111-7b55557aebcb",
"name": "Code",
"type": "n8n-nodes-base.code",
"position": [
380,
560
],
"parameters": {
"language": "python",
"pythonCode": "# Input from n8n\ninput_data = items\n\n# Get the extracted list\nextract_data = input_data[0].get('json', {}).get('output', {}).get('parameters', {}).get('extract', [])\n\n# Prepare variables\norder_items = []\ntable_number = None\n\n# Separate entries by type\nitems_list = []\nquantities = []\n\n# Parse all entries\nfor entry in extract_data:\n if entry['name'] == 'table number':\n table_number = entry['pattern']\n elif entry['name'] == 'item':\n items_list.append(entry['pattern'])\n elif entry['name'] == 'quantity':\n quantities.append(int(entry['pattern']))\n\n# Pair items and quantities\nfor i in range(len(items_list)):\n item_data = {\n 'item': items_list[i],\n 'quantity': quantities[i] if i < len(quantities) else None,\n 'table': table_number\n }\n order_items.append(item_data)\n\n# Set final output\noutput = [{'json': item} for item in order_items]\n\nreturn output"
},
"typeVersion": 2
},
{
"id": "a92d2745-148b-4e2a-b8f7-82d3993ff34f",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
620,
500
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "aea89e6c-37a9-4859-adc8-b7e449701503",
"name": "Replace Me",
"type": "n8n-nodes-base.noOp",
"position": [
800,
660
],
"parameters": {},
"typeVersion": 1
},
{
"id": "b31dba52-b27e-4267-be32-a7730b4d08a8",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"position": [
440,
400
],
"parameters": {},
"typeVersion": 1
},
{
"id": "d7f9a381-6bc2-44d0-81ac-6e0fbe77d70a",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-260,
220
],
"parameters": {
"color": 3,
"width": 340,
"height": 680,
"content": "## JSON PARSER\n\n1.converts the textual data final order like\nitem name \nquantity \nand table name in a json.\n\n2.if the data doesn't include the above it returns null."
},
"typeVersion": 1
},
{
"id": "acc7a528-f767-4576-b08d-6fc386f57648",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
100,
220
],
"parameters": {
"color": 2,
"width": 460,
"height": 680,
"content": "## Refine/Split the jsons into multiple items\n\nIf the data from previous item is not null the custom code block splits the data into multiple json items in a list."
},
"typeVersion": 1
},
{
"id": "857a3102-f5e1-4db5-afb4-154544414701",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
580,
220
],
"parameters": {
"color": 4,
"width": 440,
"height": 680,
"content": "## Send each item as a record in Google sheet\n\n\n**Each item is looped over and produce a batch of 1 item and appended as row in sheet with timestamp.\n"
},
"typeVersion": 1
},
{
"id": "a1ff2b0f-0b48-4ea2-8121-4e2d72197ef7",
"name": "Triggered on Restaurant Chat workflow",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
-440,
500
],
"parameters": {
"inputSource": "passthrough"
},
"typeVersion": 1.1
},
{
"id": "8689b773-a1c4-4de4-a66e-fab8c9eb6244",
"name": "When chat message received",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"position": [
-140,
-280
],
"webhookId": "d931c4a7-02f5-4359-918f-7ad3fae7b144",
"parameters": {
"public": true,
"options": {}
},
"typeVersion": 1.1
},
{
"id": "de310ce2-3868-4a0f-aa9b-38253e75dbda",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
100,
-260
],
"parameters": {
"options": {
"systemMessage": "\n\nYou are a polite and efficient restaurant assistant.\n\nYour job is to take customer orders, verify the order details, correct any mistakes, and confirm the order.\n\nFollow these steps:\n\nGreeting and Asking for the Order\n\nIf the customer greets you (e.g., \"Hello\", \"Hi\", \"Good evening\"), respond with:\n\n\"Hello! How can I assist you today? What would you like to order?\"\n\nOrder Parsing and Understanding\n\nAccept orders in flexible formats, such as:\n\n\"1 latte, 2 coffee, table number 5\"\n\n\"latte 2, pepsi 1, table 3\"\n\n\"1 cappucino\"\n\n\"1 tea table no 4\"\n\nYour goal is to extract the following:\n\nItem names (e.g., latte, coffee, chocolate, tea, pepsi)\n\nQuantities (must be numeric)\n\nTable number (must be numeric)\n\nVerify and Handle Missing or Incorrect Information\n\nFor each item in the order:\n\nIf the item name is missing, respond:\n\"Sorry, the item name is missing. What would you like to order?\"\n\nIf the quantity is missing, respond:\n\"How many [item] would you like?\"\n\nIf the table number is missing, respond:\n\"Could you please provide a table number?\"\n\nIf there are spelling mistakes in the item name, suggest corrections. Example:\n\"Did you mean chocolate instead of chocolat? Please confirm.\"\n\nUse fuzzy matching to detect common variations and typos.\n\nFinal Confirmation\n\nOnce all necessary details are collected, present an order summary like this:\n\nHeres your order summary:\n\n1 latte\n\n2 coffee\n\nTable number: 5\nShall I confirm this order?\n\nOn Confirmation: Use the Tool\n\nWhen the user confirms, use the tool ConfirmOrder to send the final confirmation message as plain text in this format:\n\nThank you for confirming! Your order will be prepared shortly. Enjoy your time with us!\n\nOrder details are following:\nitem quantity\nlatte 1\ncoffee 2\n\nAdded to table number 5\n\nEnsure numeric values (quantities and table numbers) are correctly extracted, even if they appear at the start or end. Always confirm with the user if there is any uncertainty.\n\n\n\n\n\n\n\n\n"
}
},
"typeVersion": 1.9
},
{
"id": "9dda45ee-0a92-448c-8a7e-8daa99282cda",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-20,
20
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {
"responseFormat": "text"
}
},
"credentials": {
"openAiApi": {
"id": "OizdHUANhz9NIHyd",
"name": "OpenAi account"
}
},
"typeVersion": 1.2
},
{
"id": "0c0189d5-8fb4-4679-b2e2-221a3e2a4c88",
"name": "Call n8n Workflow Tool",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"position": [
360,
20
],
"parameters": {
"workflowId": {
"__rl": true,
"mode": "list",
"value": "wgaJ0eJQtYA8oKSC",
"cachedResultName": "Restaurant POS workflow"
},
"description": "This tool sends the text output generated by the AI Agent node to another n8n workflow for additional handling or automation.",
"workflowInputs": {
"value": {},
"schema": [],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"notesInFlow": false,
"typeVersion": 2.2
},
{
"id": "9292db7f-6ffc-486e-b31a-bcbd6ef7ab98",
"name": "Last 5 conversations Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
140,
40
],
"parameters": {},
"typeVersion": 1.3
},
{
"id": "2782d5b6-d33b-4c89-ac79-90bf380f0828",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
60,
-380
],
"parameters": {
"width": 340,
"height": 300,
"content": "## Restaurant Order Chat bot\n** It chats with the user and refines the order for the pos system in another workflow."
},
"typeVersion": 1
},
{
"id": "7c298718-e9e3-40d3-a612-94c578bd3100",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
500,
-20
],
"parameters": {
"color": 5,
"content": "## Call the subworkflow\nit passes the data to the subworkflow for further process\n"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"If": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
],
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"AI Agent": {
"main": [
[]
]
},
"Replace Me": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[
{
"node": "Google Sheets",
"type": "main",
"index": 0
}
],
[
{
"node": "Replace Me",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"OpenAI Chat Model1": {
"ai_languageModel": [
[
{
"node": "Information Extractor",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Information Extractor": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"Call n8n Workflow Tool": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"When chat message received": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Last 5 conversations Memory": {
"ai_memory": [
[
{
"node": "AI Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Triggered on Restaurant Chat workflow": {
"main": [
[
{
"node": "Information Extractor",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,398 @@
{
"meta": {
"instanceId": "dfb8aefc80b77b230bd90d6a6e5210eb7a28e6e1d2a8b1d27d54942b54eb9e7a",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "4f42007b-3813-410f-a608-5af89459b14f",
"name": "Check Authorization Header",
"type": "n8n-nodes-base.if",
"position": [
-20,
20
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $('Webhook').item.json.headers.authorization }}",
"value2": "=Bearer {{ $json.config.bearerToken }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "86d6157e-593d-4370-a480-1a9417300555",
"name": "401 Unauthorized",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
340,
280
],
"parameters": {
"options": {
"responseCode": 401
},
"respondWith": "json",
"responseBody": "{\n \"code\": 401,\n \"message\": \"Unauthorized: Missing or invalid authorization token.\",\n \"hint\": \"Ensure the request includes a valid 'Authorization' header (e.g., 'Bearer YOUR_SECRET_TOKEN').\"\n}"
},
"typeVersion": 1
},
{
"id": "0831093a-adef-41dc-8ac0-2e1998fc22ad",
"name": "200 OK",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1140,
20
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "b4f42651-c7f6-43a3-a695-7d5197b45642",
"name": "Configuration",
"type": "n8n-nodes-base.set",
"position": [
-300,
20
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "4c35898d-5a70-41bc-9fb6-9d63bbbee222",
"name": "config.bearerToken",
"type": "string",
"value": "123"
},
{
"id": "822739a6-15da-48df-8f92-c4b1adce5fef",
"name": "config.requiredFields.message",
"type": "string",
"value": "true"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "f1539109-8585-4cf2-9b9b-f3012544ac6c",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-580,
20
],
"webhookId": "2c5b9b70-1b08-44b1-a007-dc3d9f7e70db",
"parameters": {
"path": "secure-webhook",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 1
},
{
"id": "bcf1183c-9a3d-41eb-89f7-1666d3a6c5fc",
"name": "Has required fields?",
"type": "n8n-nodes-base.code",
"position": [
220,
20
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "if(! $json.config.requiredFields) {\n return { json: { valid: true } };\n}\n\nconst body = $('Webhook').first().json.body;\n\nlet requiredFields = $json.config.requiredFields;\n\nfor (let [key, value] of Object.entries(requiredFields)) {\n console.log(`${key}: ${value}`);\n if (!(key in body)) {\n return { json: { valid: false } };\n }\n}\n\nreturn { json: { valid: true } };"
},
"typeVersion": 2
},
{
"id": "81b125f1-faa0-4998-8624-431746052a84",
"name": "Check Valid Request",
"type": "n8n-nodes-base.if",
"position": [
440,
20
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "8c7fe174-f284-4e41-b851-8939f0c2d19f",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.valid }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "906c671d-e2a6-4a9e-b7df-d7b9142ffeb4",
"name": "400 Bad Request",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
780,
280
],
"parameters": {
"options": {
"responseCode": 401
},
"respondWith": "json",
"responseBody": "{\n \"code\": 400,\n \"message\": \"Bad Request: Missing required fields\",\n \"hint\": \"Make sure all required fields are included in the request body.\"\n}"
},
"typeVersion": 1
},
{
"id": "ce657170-34e4-4b40-ba22-bb4638fa98c6",
"name": "Create Response",
"type": "n8n-nodes-base.set",
"position": [
920,
20
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "c6258b81-6f40-4dd5-8a60-89e2b0322490",
"name": "message",
"type": "string",
"value": "Success! Workflow completed."
}
]
}
},
"typeVersion": 3.4
},
{
"id": "0a6b9f12-9b60-458e-85de-014a66063e50",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-440,
-280
],
"parameters": {
"color": 6,
"width": 360,
"height": 460,
"content": "### 🛠️ Config Node Setup\n\n*This node defines the configuration for the secure webhook.*\n\n- `config.bearerToken`: The expected Bearer token for authentication.\n\n- `config.requiredFields`: Set one key for each required field in the incoming request body (e.g., `config.requiredFields.message`.\n*👉 The value doesn't matter, only the keys are checked.*"
},
"typeVersion": 1
},
{
"id": "bba24ba5-3c8d-40f7-99e0-44115b1025e0",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-440,
200
],
"parameters": {
"color": 3,
"width": 1740,
"height": 240,
"content": "### 🚫 Error Handling Nodes\n\n*These nodes return standardized JSON error responses:*\n\n- 🔒 `401 Unauthorized`:\nTriggered when the request is missing a valid Bearer token.\n\n- 📭 `400 Bad Request`:\nTriggered when required fields are missing from the request body."
},
"typeVersion": 1
},
{
"id": "f451c9be-4cfb-4628-8aa7-66b66ad86bab",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
840,
-280
],
"parameters": {
"color": 4,
"width": 460,
"height": 460,
"content": "### ✅ Set & 200 Response Nodes\n\n- 🧱 `Create Response`\nBuilds the JSON response from the incoming request.\nUse this to extract, transform, or forward specific values (e.g., message, sender, etc.).\n\n- 📬 `200 OK`\nReturns a successful response using values from the `Create Response` node."
},
"typeVersion": 1
},
{
"id": "8d4e8406-c3fe-4e8a-bfa8-18407fe5e67a",
"name": "Add workflow nodes here",
"type": "n8n-nodes-base.noOp",
"position": [
680,
20
],
"parameters": {},
"typeVersion": 1
},
{
"id": "f3f461a6-dc48-42cd-ac75-d045795006d0",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
-280
],
"parameters": {
"color": 7,
"width": 440,
"height": 460,
"content": "### 🔍 Required Fields Validator\n\n*This Code node checks if all fields defined in config.requiredFields are present in the incoming request body.*\n\n- Reads the body from the Webhook node.\n\n- Loops through each key in config.requiredFields.\n\n- Returns `{ valid: true }` if all are present, otherwise `{ valid: false }`."
},
"typeVersion": 1
},
{
"id": "2766dae8-8def-462f-a53c-0f51606eea0a",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1220,
-340
],
"parameters": {
"color": 5,
"width": 760,
"height": 780,
"content": "## 🔐 Secure Webhook Summary\n\n*This workflow protects a public webhook with **authentication** and **payload validation**.*\n\n\n---\n\n#### 🧩 Why use it?\n- ✅ Ensure only trusted clients can call your workflow (via Bearer token).\n- ✅ Validate that all expected fields are present in the request body.\n- ✅ Return helpful and consistent JSON responses (`200`, `400`, `401`).\n\n---\n\n#### ⚙️ How it works:\n1. **`Webhook`** Entry point for external `POST` requests.\n2. **`Configuration`** Defines `config.bearerToken` and `config.requiredFields`.\n3. **`Check Authorization Header`** Compares incoming Bearer token with config.\n4. **`401 Unauthorized`** Returned if the token is missing or incorrect.\n5. **`Has required fields?`** JS code checks for required fields in the request body.\n6. **`400 Bad Request`** Returned if any required field is missing.\n7. **`Create Response` & `200 OK`** Returns a custom success message.\n\n---\n\n#### 🛠 Setup Instructions:\n- Set your desired Bearer token in `config.bearerToken`.\n- For each required field, set a key in `config.requiredFields` \n *(e.g., `config.requiredFields.message)*.\n*👉 The value doesn't matter, only the keys are checked.*\n- Replace the **`Add workflow nodes here`** node with your own workflow logic.\n- Edit the `Create Response` node to build your response.\n\n---\n\n📌 *Great for building secure, reusable webhook endpoints for APIs, forms, or 3rd-party services.*"
},
"typeVersion": 1
},
{
"id": "70c8f060-587a-4524-ab32-7362cc0c4cf9",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1220,
-600
],
"parameters": {
"color": 6,
"width": 760,
"height": 240,
"content": "## Support My Work! ❤️\n\n**👋 Hello! I'm Audun / xqus** \n🔗 My work: [xqus.com](https://xqus.com)\n💸 n8n shop: [xqus.gumroad.com](https://xqus.gumroad.com)\n\n**If you find this workflow helpful**, consider downloading or purchasing it on [Gumroad](https://xqus.gumroad.com/l/hasgi).\n\nYour support helps me create more useful n8n workflows and resources for the community. \n-Thanks a lot! 🙌"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"200 OK": {
"main": [
[]
]
},
"Webhook": {
"main": [
[
{
"node": "Configuration",
"type": "main",
"index": 0
}
]
]
},
"Configuration": {
"main": [
[
{
"node": "Check Authorization Header",
"type": "main",
"index": 0
}
]
]
},
"Create Response": {
"main": [
[
{
"node": "200 OK",
"type": "main",
"index": 0
}
]
]
},
"Check Valid Request": {
"main": [
[
{
"node": "Add workflow nodes here",
"type": "main",
"index": 0
}
],
[
{
"node": "400 Bad Request",
"type": "main",
"index": 0
}
]
]
},
"Has required fields?": {
"main": [
[
{
"node": "Check Valid Request",
"type": "main",
"index": 0
}
]
]
},
"Add workflow nodes here": {
"main": [
[
{
"node": "Create Response",
"type": "main",
"index": 0
}
]
]
},
"Check Authorization Header": {
"main": [
[
{
"node": "Has required fields?",
"type": "main",
"index": 0
}
],
[
{
"node": "401 Unauthorized",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,370 @@
{
"meta": {
"instanceId": "32014bf2061907b54debfd6d86e0e8dc3f3ec9cdd9123c339fc7506178206d83",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "1874c66a-97f0-4a33-a4e9-ab27b950edb4",
"name": "Webhook1",
"type": "n8n-nodes-base.webhook",
"position": [
-1820,
860
],
"webhookId": "7116a2e3-c07f-4638-9140-3548a7957d15",
"parameters": {
"path": "flow",
"options": {
"responseHeaders": {
"entries": [
{
"name": "Content-Type",
"value": "text/plain"
}
]
}
},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "ae85225c-addf-44e8-a60f-f9e0f07a9bc0",
"name": "Json Parser",
"type": "n8n-nodes-base.code",
"position": [
-1060,
860
],
"parameters": {
"jsCode": "function processPayload(items) {\n // Create a new array to store the processed items\n const processedItems = [];\n \n // Process each item in the input array\n for (const item of items) {\n try {\n // Extract the decryptedPayload string from the current item\n const decryptedPayloadString = item.json.decryptedPayload;\n \n // Parse the decryptedPayload string into a JavaScript object\n const decryptedPayloadObject = JSON.parse(decryptedPayloadString);\n \n // Extract the date from the data object\n const date = decryptedPayloadObject.data.date;\n \n // Extract the screen value\n const screen = decryptedPayloadObject.screen;\n\n // Extract the flow_token object\n const flow_token = decryptedPayloadObject.flow_token;\n \n // Create a new item with the extracted date and screen\n const newItem = {\n json: {\n date: date,\n screen: screen,\n flow_token: flow_token,\n // Optionally preserve original data\n originalPayload: item.json\n }\n };\n \n // Add the processed item to our array\n processedItems.push(newItem);\n } catch (error) {\n // If there's an error, create an item with error information\n processedItems.push({\n json: {\n error: error.message,\n originalItem: item.json\n }\n });\n }\n }\n \n return processedItems;\n}\n\nreturn processPayload(items);"
},
"typeVersion": 2
},
{
"id": "8ee86c97-ed4f-48d1-924f-4252e1c07aa5",
"name": "Switch",
"type": "n8n-nodes-base.switch",
"position": [
-740,
860
],
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "aa929857-8458-49da-a027-0b4d4a7f75f7",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.screen }}",
"rightValue": "APPOINTMENT"
}
]
}
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d83dd890-5ee5-480e-b338-efc5eb26b494",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.screen }}",
"rightValue": "DATE_SELECTION_SCREEN"
}
]
}
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "76fad406-2591-4531-acab-01cbfcf41c3f",
"name": "Respond to Webhook1",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
40,
760
],
"parameters": {
"options": {
"responseCode": 200
},
"respondWith": "text",
"responseBody": "={{ $json.body }}"
},
"typeVersion": 1.1
},
{
"id": "56cb338a-9d7a-4f1a-9c55-5ca9db4f3560",
"name": "Data Extraction Code",
"type": "n8n-nodes-base.code",
"position": [
-400,
760
],
"parameters": {
"jsCode": "const groupedAppointments = items.reduce((acc, { json: { appointment_date, start_time } }) => {\n const dateKey = new Date(appointment_date).toISOString().split('T')[0];\n if (!acc[dateKey]) {\n acc[dateKey] = [];\n }\n acc[dateKey].push(start_time);\n return acc;\n}, {});\n\nreturn Object.entries(groupedAppointments).map(([date, times]) => ({\n json: { appointment_date: date, start_times: times }\n}));\n"
},
"typeVersion": 2
},
{
"id": "8bd15faf-3a9b-4bb4-ac83-c913a7373480",
"name": "Respond to Webhook2",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
40,
1000
],
"parameters": {
"options": {
"responseCode": 200
},
"respondWith": "text",
"responseBody": "={{ $json.body }}"
},
"typeVersion": 1.1
},
{
"id": "67b06ae5-81c1-4efd-993e-a54e36bc5ce7",
"name": "Data Extraction Code1",
"type": "n8n-nodes-base.code",
"position": [
-400,
1000
],
"parameters": {
"jsCode": "const jsonData = items;\n\n// Parse the decryptedPayload string into a JSON object\nconst decryptedPayload = JSON.parse(jsonData[0].json.originalPayload.decryptedPayload);\n\n// Extract the seats array\nconst seats = decryptedPayload.data.seats;\n\n// Return the result properly formatted for n8n\nreturn seats.map(seat => ({ json: { seat } }));\n"
},
"typeVersion": 2
},
{
"id": "2d05f87c-a2c5-4790-9a85-c6cda46db927",
"name": "move to base64",
"type": "n8n-nodes-base.code",
"position": [
-1600,
860
],
"parameters": {
"jsCode": "console.log($json);\n\nreturn [\n {\n encryptedFlowData: Buffer.from($json.body?.encrypted_flow_data || \"\", \"base64\"),\n encryptedAesKey: Buffer.from($json.body?.encrypted_aes_key || \"\", \"base64\"),\n initialVector: Buffer.from($json.body?.initial_vector || \"\", \"base64\"),\n }\n];\n"
},
"typeVersion": 2
},
{
"id": "760536f8-c3f4-4d24-be36-4ac08004eb48",
"name": "Decryption Code",
"type": "n8n-nodes-base.code",
"position": [
-1320,
860
],
"parameters": {
"jsCode": "const crypto = require(\"crypto\");\n\nconst privateKey = `-----BEGIN PRIVATE KEY-----\n[INSERT YOUR KEY HERE]\n-----END PRIVATE KEY-----`;\n\n// Convert input buffers\nconst encryptedAesKeyBuffer = Buffer.from($json.encryptedAesKey.data);\nconst initialVector = Buffer.from($json.initialVector.data);\nconst encryptedFlowData = Buffer.from($json.encryptedFlowData.data);\n\n// Check if encrypted AES key, IV, and encrypted flow data exist\nif (!encryptedAesKeyBuffer || !initialVector || !encryptedFlowData) {\n throw new Error(\"Missing required data (encrypted AES key, IV, or flow data)\");\n}\n\n// Decrypt AES key using RSA\nconst decryptedKey = crypto.privateDecrypt(\n {\n key: privateKey,\n padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,\n oaepHash: \"sha256\",\n },\n encryptedAesKeyBuffer\n);\n\n// Ensure AES key is exactly 16 bytes (AES-128 requires it)\nconst aesKey = decryptedKey.slice(0, 16);\nif (aesKey.length !== 16) {\n throw new Error(\"Invalid AES Key length\");\n}\n\n// Handle initialization vector (IV): If needed, flip the IV bits (standardize behavior)\nconst standardizedIv = Buffer.from(initialVector);\nif (standardizedIv.length !== 16) {\n throw new Error(\"Invalid IV length, must be 16 bytes\");\n}\n\n// Extract the last 16 bytes as the authentication tag (GCM uses 16-byte tags)\nconst authTag = encryptedFlowData.slice(-16);\nconst encryptedDataWithoutTag = encryptedFlowData.slice(0, -16);\n\n// AES Decryption\nconst decipher = crypto.createDecipheriv(\"aes-128-gcm\", aesKey, standardizedIv);\ndecipher.setAuthTag(authTag);\n\nlet decrypted;\ntry {\n decrypted = Buffer.concat([\n decipher.update(encryptedDataWithoutTag),\n decipher.final(),\n ]);\n} catch (error) {\n throw new Error(\"Decryption failed: \" + error.message);\n}\n\nreturn [{ \n decryptedPayload: decrypted.toString(\"utf-8\"),\n aesKey: aesKey.toString(\"base64\")\n}];\n"
},
"typeVersion": 2
},
{
"id": "17c055f3-c278-48c4-89d4-d305a35bc526",
"name": "Encrypt Return",
"type": "n8n-nodes-base.code",
"position": [
-200,
760
],
"parameters": {
"jsCode": "const crypto = require(\"crypto\");\n\n// Access initial_vector from the correct path\nconst initialVector = $('move to base64').first().json.initialVector;\n\nif (!initialVector) {\n throw new Error(\"Initial Vector is undefined or missing.\");\n}\n\n// Check if 'data' is a property of initialVector\nconst ivData = initialVector.data || initialVector; // Fallback to initialVector if no 'data' property\n\nif (!ivData) {\n throw new Error(\"Initial Vector 'data' is undefined or missing.\");\n}\n\n// Check for various formats of initialVector\nlet ivBuffer;\nif (typeof ivData === \"string\") {\n ivBuffer = Buffer.from(ivData, 'base64');\n} else if (Buffer.isBuffer(ivData)) {\n ivBuffer = ivData;\n} else if (Array.isArray(ivData)) {\n ivBuffer = Buffer.from(ivData);\n} else {\n throw new Error(\"Initial Vector 'data' is in an unsupported format.\");\n}\n\n// Invert Initialization Vector\nconst invertedIV = Buffer.from(ivBuffer.map((b) => ~b & 0xFF)); // Ensure the result stays a valid byte\n\n// Access AES Key from the correct path\nconst aesKeyBase64 = $('Decryption Code').first().json.aesKey || \"\";\nif (!aesKeyBase64) {\n throw new Error(\"AES Key is missing.\");\n}\n\nconst aesKey = Buffer.from(aesKeyBase64, \"base64\");\n\n// Extract data from the input with proper error handling\nlet date = \"2025-03-14\"; // Default fallback date\nlet startTimes = []; // Default empty array for start times\n\n// Check if $json exists and has the expected structure\nif ($json) {\n // Check if $json is an array\n if (Array.isArray($json) && $json.length > 0) {\n const appointmentData = $json[0];\n if (appointmentData && appointmentData.appointment_date) {\n date = appointmentData.appointment_date;\n }\n if (appointmentData && Array.isArray(appointmentData.start_times)) {\n startTimes = appointmentData.start_times;\n }\n } else if ($json.appointment_date) {\n // If $json is not an array but has appointment_date directly\n date = $json.appointment_date;\n if (Array.isArray($json.start_times)) {\n startTimes = $json.start_times;\n }\n }\n}\n\n// Log the structure of $json for debugging\nconsole.log(\"Input JSON structure:\", JSON.stringify($json, null, 2));\n\n// Ensure we have time slots (use defaults if none found)\nif (!startTimes.length) {\n console.log(\"No time slots found in input, using defaults\");\n startTimes = [\"12:00:00\", \"12:30:00\", \"13:30:00\", \"14:00:00\"];\n}\n\n// Map the time slots to the required format\nconst timeSlots = startTimes.map((timeString, index) => ({\n id: `time_${index + 1}`,\n title: timeString\n}));\n\n// Map the date slots for each time slot\nconst dateSlots = [{\n id: \"date_1\",\n title: date\n}];\n\n// Define the response data with the extracted time and date\nconst responseData = {\n status: \"active\",\n time: timeSlots,\n date: dateSlots\n};\n\n// Define the flow_token (accessed from the correct path)\nconst flowToken = $('Json Parser').first().json.flow_token || \"\"; // Fetch the flow_token dynamically from the path\n\nif (!flowToken) {\n throw new Error(\"Flow token is missing.\");\n}\n\n// Define the next screen (this should be based on your flow logic)\nconst nextScreen = \"APPOINTMENT\"; // You can set this dynamically depending on the flow\n\n// Define Response Message (updated to match the required response format)\nconst responseMessage = JSON.stringify({\n version: \"3.0\", // Fixed version as per your requirements\n action: \"data_exchange\", // Since we're responding to a data exchange request\n screen: nextScreen, // The next screen that the user will be redirected to\n data: responseData, // Data to send back (includes the time and date)\n flow_token: flowToken, // Flow token for session identification\n});\n\n// Encrypt Response using AES-GCM\nconst cipher = crypto.createCipheriv(\"aes-128-gcm\", aesKey, invertedIV);\nlet encryptedResponse = Buffer.concat([\n cipher.update(responseMessage, \"utf-8\"),\n cipher.final()\n]);\n\n// Get the authentication tag\nconst authTag = cipher.getAuthTag();\n\n// Append the authentication tag to the encrypted response\nconst result = Buffer.concat([encryptedResponse, authTag]);\n\n// Encode the entire response as Base64\nconst base64Response = result.toString(\"base64\");\n\n// Return the Base64-encoded response as the body\nreturn [{ body: base64Response }];\n"
},
"typeVersion": 2
},
{
"id": "412f55e3-5867-4e65-a494-3e3bf991d59c",
"name": "Encrypt Return1",
"type": "n8n-nodes-base.code",
"position": [
-200,
1000
],
"parameters": {
"jsCode": "const crypto = require(\"crypto\");\n\nconst jsonData = items;\n\n// Parse the decryptedPayload string into a JSON object\nconst decryptedPayload = JSON.parse(jsonData[0].json.originalPayload.decryptedPayload);\n\n// Extract the seats array\nconst seats = decryptedPayload.data.seats;\n\nif (!seats || !Array.isArray(seats) || seats.length === 0) {\n throw new Error(\"Seats data is missing or invalid.\");\n}\n\n// Access initial_vector from the correct path\nconst initialVector = $('move to base64').first().json.initialVector;\nif (!initialVector) {\n throw new Error(\"Initial Vector is undefined or missing.\");\n}\n\nconst ivData = initialVector.data || initialVector;\nif (!ivData) {\n throw new Error(\"Initial Vector 'data' is undefined or missing.\");\n}\n\nlet ivBuffer;\nif (typeof ivData === \"string\") {\n ivBuffer = Buffer.from(ivData, 'base64');\n} else if (Buffer.isBuffer(ivData)) {\n ivBuffer = ivData;\n} else if (Array.isArray(ivData)) {\n ivBuffer = Buffer.from(ivData);\n} else {\n throw new Error(\"Initial Vector 'data' is in an unsupported format.\");\n}\n\nconst invertedIV = Buffer.from(ivBuffer.map((b) => ~b & 0xFF));\n\n// Access AES Key from the correct path\nconst aesKeyBase64 = $('Decryption Code').first().json.aesKey || \"\";\nif (!aesKeyBase64) {\n throw new Error(\"AES Key is missing.\");\n}\nconst aesKey = Buffer.from(aesKeyBase64, \"base64\");\n\n// Define the response data with the extracted seats\nconst responseData = {\n status: \"active\",\n seats: seats.map((seat, index) => ({\n id: `seat_${index + 1}`,\n title: seat\n }))\n};\n\n// Define the flow_token\nconst flowToken = $('Json Parser').first().json.flow_token || \"\";\nif (!flowToken) {\n throw new Error(\"Flow token is missing.\");\n}\n\nconst nextScreen = \"SUMMARY\";\n\nconst responseMessage = JSON.stringify({\n version: \"3.0\",\n action: \"data_exchange\",\n screen: nextScreen,\n data: responseData,\n flow_token: flowToken,\n});\n\n// Encrypt Response using AES-GCM\nconst cipher = crypto.createCipheriv(\"aes-128-gcm\", aesKey, invertedIV);\nlet encryptedResponse = Buffer.concat([\n cipher.update(responseMessage, \"utf-8\"),\n cipher.final()\n]);\n\nconst authTag = cipher.getAuthTag();\nconst result = Buffer.concat([encryptedResponse, authTag]);\nconst base64Response = result.toString(\"base64\");\n\n// Return the encrypted response\nreturn [{ body: base64Response }];\n"
},
"typeVersion": 2
},
{
"id": "6c130dfe-bec9-4ca5-af1a-9b55ed593b84",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2480,
140
],
"parameters": {
"width": 580,
"height": 1900,
"content": "## Try it out\n\n### 🔗 **1. Webhook Entry & Initial Decryption Block**\n\n**Nodes involved:**\n\n* `Webhook1`\n* `move to base64`\n* `[partially visible node for decryption using RSA + AES]`\n\n**Description:**\n\nThe workflow begins with the `Webhook1` node, which listens for incoming HTTP POST requests. These requests typically contain encrypted data that needs to be decoded to proceed with processing.\n\nOnce received, the `move to base64` node reformats the incoming encrypted components (`encrypted_flow_data`, `encrypted_aes_key`, and `initial_vector`) into binary buffers. These are required inputs for decryption.\n\nThen, the custom JavaScript code (cut off in your snippet) uses a private RSA key to decrypt the AES key, which in turn is used to decrypt the actual data payload (likely using AES-GCM). This is a secure hybrid encryption method—RSA for key exchange, AES for data encryption.\n\n---\n\n### 🧠 **2. Payload Parsing & Preprocessing Block**\n\n**Node involved:**\n\n* `Json Parser`\n\n**Description:**\n\nHere, we take the decrypted JSON payload from Whatsapp Flows and parse key elements from it. This helps standardize and clean the input before deciding what kind of logic or response should follow based on user interaction.\n\n---\n\n### 🔀 **3. Flow Decision Block**\n\n**Node involved:**\n\n* `Switch`\n\n**Description:**\n\nThis decision-making node routes the workflow depending on the screen context extracted earlier.\n\nE.g., If the screen where the user is exchanging information is appointment date:\n\n* `\"APPOINTMENT\"` → follow the logic that handles scheduling data.\n\nThis allows dynamic routing within the workflow, making it adaptable to different user journey steps or screens.\n\n---\n\n### 📆 **4. Appointment Data Handling Block**\n\n**Nodes involved:**\n\n* `Data Extraction Code`\n* `Respond to Webhook1`\n\n**Description:**\n\nWhen the screen is `\"APPOINTMENT\"`, the `Data Extraction Code` node processes appointment data—typically grouping appointment slots by date. This is useful for summarizing available times, perhaps to show a user a calendar view of options.\n\nThe results are then sent back as a plain text response using `Respond to Webhook1`, which finalizes the API call and ensures a secure end-to-end interaction using Whatsapp Flows.\n\n\n### 🧩 **Summary**\n\nThis n8n workflow handles encrypted user interactions and adapts dynamically based on the screen or step the user is currently in. Here's the general pattern:\n\n1. **Webhook receives encrypted data**\n2. **Data is decrypted using hybrid RSA-AES encryption**\n3. **Parsed to extract the current step (`screen`)**\n4. **Conditional logic decides which path to follow**\n5. **Extracts relevant information (e.g., appointments)**\n6. **Returns response back to the user interface or chatbot**\n"
},
"typeVersion": 1
}
],
"pinData": {
"Webhook1": [
{
"body": {
"initial_vector": "PFfPS7sPwJqYWLySIGWF/Q==",
"encrypted_aes_key": "A2BJ/NRN0WsSHZ8KeH1mUreTHICGMprbvh8BP7vEAIyIxeADgtYODJNkJ5P77WsAtJkIx8BwibiWlPfdJlBFaYeQx86hllirf4GygagECsgEJyNX0B98rpx/0eic4FqdR/8bqDWNFZbi7i78vMDG4x+9PArJIwkXWtzuLaLtM2J5j/SAx2y3PV5pKeYqcfg7w/uYlubmkKZjJYuSLmIOHbdO5mmvblDBm8ap5COVvEzK18K3VYyT8BVzawUgfxxhlyCBd7bB36vcS8iKkTl6EFgkPqFmpcCOmZNSmnsJ5tu+e7uRX8OgwryqbFNfb/plZGUPTQJZlrObFO8rw22yJQ==",
"encrypted_flow_data": "tkGedq3MER+FadPJh3W6amE18m0x1Xzge6cqPeb5sNkBgOfTtHkRrHuuLjrLG+MvOd9oSzFXdx4sT90cliJSLfp0uUBtVCnBT33Qa5PF87E/iNRtyOCW4Jcp1yv1po54jSVWnVjhgZRCt9akyjBYK1v2YJW5qxarsvFDFsZMsEOOMMOLtOWHGgGGS+tKR5PB7X4WwMHrlCLG9j0yT1U="
},
"query": {},
"params": {},
"headers": {
"host": "n8n.doubleit.com.br",
"accept": "*/*",
"connection": "upgrade",
"user-agent": "facebookexternalua",
"content-type": "application/json",
"content-length": "657",
"accept-encoding": "deflate, gzip",
"x-hub-signature": "sha256=8e8d012f89e53d0a67aa31c19b472636e55b2e86e1569af9b200eb65839a39ce",
"x-hub-signature-256": "sha256=5deea4ea13d95f1da43be49579528f5928e29cb7772abd2455d319ff7396df4e"
},
"webhookUrl": "https://n8n.doubleit.com.br/webhook/flow",
"executionMode": "production"
}
]
},
"connections": {
"Switch": {
"main": [
[
{
"node": "Data Extraction Code",
"type": "main",
"index": 0
}
],
[
{
"node": "Data Extraction Code1",
"type": "main",
"index": 0
}
]
]
},
"Webhook1": {
"main": [
[
{
"node": "move to base64",
"type": "main",
"index": 0
}
]
]
},
"Json Parser": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Encrypt Return": {
"main": [
[
{
"node": "Respond to Webhook1",
"type": "main",
"index": 0
}
]
]
},
"move to base64": {
"main": [
[
{
"node": "Decryption Code",
"type": "main",
"index": 0
}
]
]
},
"Decryption Code": {
"main": [
[
{
"node": "Json Parser",
"type": "main",
"index": 0
}
]
]
},
"Encrypt Return1": {
"main": [
[
{
"node": "Respond to Webhook2",
"type": "main",
"index": 0
}
]
]
},
"Data Extraction Code": {
"main": [
[
{
"node": "Encrypt Return",
"type": "main",
"index": 0
}
]
]
},
"Data Extraction Code1": {
"main": [
[
{
"node": "Encrypt Return1",
"type": "main",
"index": 0
}
]
]
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,178 @@
{
"id": "4wPgPbxtojrUO7Dx",
"meta": {
"instanceId": "f46651348590f9c7e3e7fe91218ed49590c553ab737d5cc247951397ff85fa93"
},
"name": "Google Page Entity Extraction Template",
"tags": [
{
"id": "hBkrfz3jN0GbUgJa",
"name": "Google Page Entity Extraction Template",
"createdAt": "2025-05-08T23:29:39.011Z",
"updatedAt": "2025-05-08T23:29:39.011Z"
}
],
"nodes": [
{
"id": "8719f1de-2a3e-4c34-9edc-e4b8f993b525",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1240,
-420
],
"parameters": {
"options": {}
},
"typeVersion": 1.1
},
{
"id": "01420fd5-3483-4e74-b9fc-971199898449",
"name": "Google Entities",
"type": "n8n-nodes-base.httpRequest",
"position": [
1020,
-420
],
"parameters": {
"url": "https://language.googleapis.com/v1/documents:analyzeEntities",
"method": "POST",
"options": {},
"jsonBody": "={{ $json.apiRequest }}",
"sendBody": true,
"sendQuery": true,
"sendHeaders": true,
"specifyBody": "json",
"queryParameters": {
"parameters": [
{
"name": "key",
"value": "YOUR-GOOGLE-API-KEY"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "5c1c258a-44ed-4d5a-a22d-cddb4df09018",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-300,
-700
],
"parameters": {
"color": 4,
"width": 620,
"height": 880,
"content": "# Google Page Entity Extraction Template\n\n## What this workflow does\nThis workflow allows you to extract named entities (people, organizations, locations, etc.) from any web page using Google's Natural Language API. Simply send a URL to the webhook endpoint, and the workflow will fetch the page content, process it through Google's entity recognition service, and return the structured entity data.\n\n### How to use\n1. Replace \"YOUR-GOOGLE-API-KEY\" with your actual Google Cloud API key (Natural Language API must be enabled)\n2. Activate the workflow and use the webhook URL as your endpoint\n3. Send a POST request to the webhook with a JSON body containing the URL you want to analyze: {\"url\": \"https://example.com/page\"}\n4. Review the returned entity analysis with categories, salience scores, and metadata\n\n## Webhook Input Format\nThe webhook expects a POST request with a JSON body in this format:\n```json\n{\n \"url\": \"https://website-to-analyze.com/page\"\n}\n```\n### Response Format\nThe webhook returns a JSON response containing the full entity analysis from Google's Natural Language API, including:\n\nEntity names and types (PERSON, LOCATION, ORGANIZATION, etc.)\nSalience scores indicating entity importance\nMetadata and mentions within the text\nEntity sentiment (if available)"
},
"typeVersion": 1
},
{
"id": "79add9a7-adca-4ce5-8a6a-5fcb75288846",
"name": "Get Url",
"type": "n8n-nodes-base.webhook",
"position": [
360,
-420
],
"webhookId": "2944c8f6-03cd-4ab8-8b8e-cb033edf877a",
"parameters": {
"path": "2944c8f6-03cd-4ab8-8b8e-cb033edf877a",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "081a52bc-2da7-44fb-bdc3-4cb73cbf8dd3",
"name": "Get URL Page Contents",
"type": "n8n-nodes-base.httpRequest",
"position": [
580,
-420
],
"parameters": {
"url": "={{ $json.body.url }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "dda5ef3d-f031-4dd6-b117-c1f69aa66b63",
"name": "Respond with detected entities",
"type": "n8n-nodes-base.code",
"position": [
800,
-420
],
"parameters": {
"jsCode": "// Clean and prepare HTML for API request\nconst html = $input.item.json.data;\n// Trim if too large (optional)\nconst trimmedHtml = html.length > 100000 ? html.substring(0, 100000) : html;\n\nreturn {\n json: {\n apiRequest: {\n document: {\n type: \"HTML\",\n content: trimmedHtml\n },\n encodingType: \"UTF8\"\n }\n }\n}"
},
"typeVersion": 2
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "432203af-190a-4a89-81d8-f86682a0b63f",
"connections": {
"Get Url": {
"main": [
[
{
"node": "Get URL Page Contents",
"type": "main",
"index": 0
}
]
]
},
"Google Entities": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Get URL Page Contents": {
"main": [
[
{
"node": "Respond with detected entities",
"type": "main",
"index": 0
}
]
]
},
"Respond with detected entities": {
"main": [
[
{
"node": "Google Entities",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,609 @@
{
"id": "6bMVzmrbPexvBe6q",
"meta": {
"instanceId": "558d88703fb65b2d0e44613bc35916258b0f0bf983c5d4730c00c424b77ca36a",
"templateCredsSetupCompleted": true
},
"name": "YouTube to Airtable Anonym",
"tags": [
{
"id": "1iR8rLF2nlFdk8Iy",
"name": "Tool",
"createdAt": "2025-04-10T20:38:51.198Z",
"updatedAt": "2025-04-10T20:38:51.198Z"
},
{
"id": "kY9rLUshnq9TIJVU",
"name": "Freebie",
"createdAt": "2025-04-11T17:35:46.605Z",
"updatedAt": "2025-04-11T17:35:46.605Z"
}
],
"nodes": [
{
"id": "eb18fe74-8812-48ab-b977-41f5cedf9a76",
"name": "Get video transcript",
"type": "n8n-nodes-base.httpRequest",
"position": [
880,
0
],
"parameters": {
"url": "https://youtube-video-summarizer-gpt-ai.p.rapidapi.com/api/v1/get-transcript-v2",
"options": {},
"sendQuery": true,
"sendHeaders": true,
"queryParameters": {
"parameters": [
{
"name": "video_id",
"value": "={{ $json.videoId }}"
},
{
"name": "platform",
"value": "youtube"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "X-Rapidapi-Key",
"value": "{YOUR-API-KEY}"
},
{
"name": "X-Rapidapi-Host",
"value": "youtube-video-summarizer-gpt-ai.p.rapidapi.com"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "7a672f41-646f-46bf-be8b-96a84c1b0dd7",
"name": "Get Video ID",
"type": "n8n-nodes-base.code",
"position": [
660,
0
],
"parameters": {
"jsCode": "// Loop over input items\nfor (const item of $input.all()) {\n // Extract the YouTube video ID using a regular expression\n const Source = item.json.Source;\n const videoIdMatch = Source.match(/(?:v=|\\/)([a-zA-Z0-9_-]{11})/);\n\n const videoId = videoIdMatch ? videoIdMatch[1] : null; // Extracted video ID or null if not found\n\n // Add the video ID to the JSON\n item.json.videoId = videoId;\n}\n\n// Return all items with the new videoId field\nreturn $input.all();\n"
},
"typeVersion": 2
},
{
"id": "4b026bcf-7563-4444-8ce1-9e9a89eef56d",
"name": "Combine Transcripts",
"type": "n8n-nodes-base.code",
"position": [
1320,
0
],
"parameters": {
"jsCode": "// Initialize an empty string to hold the concatenated transcript\nlet Transcript = \"\";\n\n// Safeguard against undefined paths and ensure required properties exist\nif ($json.data && $json.data.transcripts) {\n // Loop through all transcript objects\n for (const key in $json.data.transcripts) {\n if ($json.data.transcripts[key].custom) {\n const customArray = $json.data.transcripts[key].custom;\n\n // Extract and append text from each transcript entry\n for (const item of customArray) {\n if (item.text) {\n Transcript += item.text + \" \"; // Add a space between texts\n }\n }\n }\n }\n}\n\n// Return the combined transcript as a new field\nreturn [\n {\n json: {\n Transcript: Transcript.trim(), // Trim to clean up extra spaces\n },\n },\n];\n"
},
"typeVersion": 2
},
{
"id": "693ab15f-307e-4fdf-9752-2cc64a80307d",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
240,
0
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "1df795e2-ac7e-42a8-a1f4-2c292b704613",
"name": "Airtable",
"type": "n8n-nodes-base.airtable",
"position": [
460,
0
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appgNpFtbtaGHM4g0",
"cachedResultUrl": "https://airtable.com/appgNpFtbtaGHM4g0",
"cachedResultName": "Content Hub"
},
"limit": 1,
"table": {
"__rl": true,
"mode": "list",
"value": "tblwBVudDpOMkUGKL",
"cachedResultUrl": "https://airtable.com/appgNpFtbtaGHM4g0/tblwBVudDpOMkUGKL",
"cachedResultName": "Ideas"
},
"options": {},
"operation": "search",
"returnAll": false,
"filterByFormula": "AND( {Status} = \"\", {Type} = \"Youtube Video\" )"
},
"credentials": {
"airtableTokenApi": {
"id": "g540vJVYsNT8ZS11",
"name": "Airtable Personal Access Token account"
}
},
"typeVersion": 2.1
},
{
"id": "8b2cfd44-4897-403c-9393-5cc917baa673",
"name": "Get Full Transcript",
"type": "n8n-nodes-base.set",
"position": [
1540,
0
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "2eaa3e02-95f7-47d9-a27d-64974f9c1a7b",
"name": "Transcript",
"type": "string",
"value": "={{ $json.Transcript }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "8ea3c1a7-d428-467f-aff2-9b3f572f911f",
"name": "Get All Transcripts",
"type": "n8n-nodes-base.set",
"position": [
1100,
0
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "01fb3072-61c6-47f6-b6dd-7cbf817f5b4a",
"name": "data.transcripts",
"type": "object",
"value": "={{ $json.data.transcripts }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "6cff40d0-18ba-4ae0-a1c9-070d8fb39347",
"name": "Get Main Idea & Key Takeaways",
"type": "n8n-nodes-base.set",
"position": [
2120,
0
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "e9b7f303-562b-40bd-8c3c-8c7138bd4329",
"name": "Main Idea",
"type": "string",
"value": "={{ $json.output.MainIdea }}"
},
{
"id": "572627f4-b9b3-4c59-a570-5bd810f68825",
"name": "Key Takeaways",
"type": "array",
"value": "={{ $json.output['Key Takeaways'] }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "1e71d56b-3140-4dd8-b4d8-cdbe9eb24bde",
"name": "Extract detailed summary",
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"position": [
1760,
0
],
"parameters": {
"text": "=Your job is to generate detailed summary of \"{{ $json.Transcript }}\".\n\nAlways output your answer in the following format:\n\n- Main Idea\n- Takeaways",
"options": {},
"schemaType": "fromJson",
"jsonSchemaExample": "{\n\t\"MainIdea\": \"The video provides a step-by-step guide on how to build an MCP (Model Context Protocol) server to connect agents to various data sources, specifically focusing on retrieving stock prices from Yahoo Finance. It explains the setup process, including creating functions, integrating with agents, and connecting to other tools.\",\n\t\"Key Takeaways\": [\"1. **MCP Overview**: MCP allows AI engineers to define tools once and reuse them across different frameworks, simplifying the integration process. 2. **Building the Server**: The video demonstrates how to create a Python function to fetch stock prices using the Y Finance library, and how to wrap this function into an MCP server. 3. **Testing the Server**: It shows how to use a visual inspector to test the MCP server before deploying it. 4. **Connecting to Agents**: The tutorial covers how to connect the MCP server to agents using HuggingFace's smaller agents library, enabling the retrieval of stock prices through prompts. 5. **Adding More Tools**: Viewers learn how to expand the server's capabilities by adding additional tools for stock information and income statements. 6. **Integration with Other Platforms**: The video explains how to integrate the MCP server with platforms like Cursor and Langflow, showcasing the flexibility of the MCP setup. 7. **Advanced Features**: It touches on additional MCP capabilities such as storing prompts and creating resources for data access, enhancing the server's functionality.\"]\n}"
},
"typeVersion": 1
},
{
"id": "942a77e1-5773-4657-a38c-2b1013af6544",
"name": "Update Airtable",
"type": "n8n-nodes-base.airtable",
"position": [
2320,
0
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appgNpFtbtaGHM4g0",
"cachedResultUrl": "https://airtable.com/appgNpFtbtaGHM4g0",
"cachedResultName": "Content Hub"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblwBVudDpOMkUGKL",
"cachedResultUrl": "https://airtable.com/appgNpFtbtaGHM4g0/tblwBVudDpOMkUGKL",
"cachedResultName": "Ideas"
},
"columns": {
"value": {
"id": "={{ $('Airtable').item.json.id }}",
"Status": true,
"Main Idea": "={{ $json['Main Idea'] }}",
"Takeaways": "={{ $json['Key Takeaways'] }}"
},
"schema": [
{
"id": "id",
"type": "string",
"display": true,
"removed": false,
"readOnly": true,
"required": false,
"displayName": "id",
"defaultMatch": true
},
{
"id": "Title",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Description",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Main Idea",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Main Idea",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Takeaways",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Takeaways",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "boolean",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Source",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Source",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Type",
"type": "options",
"display": true,
"options": [
{
"name": "Youtube Video",
"value": "Youtube Video"
},
{
"name": "Web Article",
"value": "Web Article"
},
{
"name": "Own Notes",
"value": "Own Notes"
},
{
"name": "E-Book",
"value": "E-Book"
},
{
"name": "Twitter",
"value": "Twitter"
},
{
"name": "Linkedin",
"value": "Linkedin"
}
],
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Draft",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Draft",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Attachment - Video",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Attachment - Video",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Attachment - Image",
"type": "string",
"display": true,
"removed": true,
"readOnly": false,
"required": false,
"displayName": "Attachment - Image",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Created",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "Created",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Last Modified",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "Last Modified",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {
"typecast": true
},
"operation": "update"
},
"credentials": {
"airtableTokenApi": {
"id": "g540vJVYsNT8ZS11",
"name": "Airtable Personal Access Token account"
}
},
"typeVersion": 2.1
},
{
"id": "4cc08263-7293-4c2b-8684-d15a67a61d33",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
760,
-580
],
"parameters": {
"width": 460,
"height": 200,
"content": "## 📝 Description\n\nAutomatically turn YouTube videos into clear, structured content ideas stored in Airtable. This workflow pulls new video links from Airtable, extracts transcripts using a RapidAPI service, summarizes them with your favourite LLM, and logs the main idea and key takeaways—keeping your content pipeline fresh with minimal effort."
},
"typeVersion": 1
},
{
"id": "299c6f10-c4a1-4da7-a198-121b054c8882",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
760,
-340
],
"parameters": {
"width": 460,
"height": 220,
"content": "## ⚙️ What It Does\n- **Scans** Airtable for new YouTube video links every 5 minutes..\n- **Extracts** the transcript of the video using a third-party API via RapidAPI.\n- **Summarizes** the content to generate a main idea and takeaways.\n- **Updates** the original Airtable entry with the insights and marks it as completed."
},
"typeVersion": 1
},
{
"id": "48d11dd7-556d-4154-b580-396c55aa5645",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1300,
-580
],
"parameters": {
"width": 500,
"height": 460,
"content": "## 🧰 Setup Instructions\n1. Clone this template into your n8n workspace.\n2. Open the Get YouTube Sources node and configure your Airtable credentials.\n3. In the Get video transcript node:\n - Enter your X-RapidAPI-Key under headers.\n - The API endpoint is pre-configured.\n4. Connect your LLM credentials to the Extract detailed summary node.\n\n5. (Optional) Adjust the summarization prompt in the LangChain node to better suit your tone.\n6. Set your preferred schedule in the Trigger node.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "1f4b0e52-7589-4c9c-8102-9105e296577b",
"connections": {
"Airtable": {
"main": [
[
{
"node": "Get Video ID",
"type": "main",
"index": 0
}
]
]
},
"Get Video ID": {
"main": [
[
{
"node": "Get video transcript",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Airtable",
"type": "main",
"index": 0
}
]
]
},
"Combine Transcripts": {
"main": [
[
{
"node": "Get Full Transcript",
"type": "main",
"index": 0
}
]
]
},
"Get All Transcripts": {
"main": [
[
{
"node": "Combine Transcripts",
"type": "main",
"index": 0
}
]
]
},
"Get Full Transcript": {
"main": [
[
{
"node": "Extract detailed summary",
"type": "main",
"index": 0
}
]
]
},
"Get video transcript": {
"main": [
[
{
"node": "Get All Transcripts",
"type": "main",
"index": 0
}
]
]
},
"Extract detailed summary": {
"main": [
[
{
"node": "Get Main Idea & Key Takeaways",
"type": "main",
"index": 0
}
]
]
},
"Get Main Idea & Key Takeaways": {
"main": [
[
{
"node": "Update Airtable",
"type": "main",
"index": 0
}
]
]
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,488 @@
{
"id": "XSyVFC1tsGSxNwX9",
"meta": {
"instanceId": "60ad864624415060d2d0a0e71acd8b3b40e4ee2e9ef4b439d9937d3d33537a96"
},
"name": "Complete Youtube",
"tags": [],
"nodes": [
{
"id": "fd74706b-609b-4723-b4a4-067e1b064194",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
300,
60
],
"parameters": {
"options": {
"systemMessage": "=You help youtube creators find trending videos based on a specific niche.\n\nVerify if the user informed a niche before doing anything. If not, then ask him for one by giving him suggestions for him to select from.\n\nAfter you know what type of content the user might produce, use the \"youtube_search\" tool up to 3 times with different search terms based on the user's content type and niche.\n\nThe tool will answer with a list of videos from the last 2 days that had the most amount of relevancy. It returns a list of json's covering each video's id, view count, like count, comment count, description, channel title, tags and channel id. Each video is separated by \"### NEXT VIDEO FOUND: ###\".\n\nYou should then proceed to understand the data received then provide the user with insightful data of what could be trending from the past 2 days. Provide the user links to the trending videos which should be in this structure:\n\nhttps://www.youtube.com/watch?v={video_id}\n\nto reach the channel's link you should use:\n\nhttps://www.youtube.com/channel/{channel_id}\n\nFind patterns in the tags, titles and especially in the related content for the videos found.\n\nYour mission isn't to find the trending videos. It's to provide the user with valuable information of what is trending in that niche in terms of content news. Remember to provide the user with the numbers of views, likes and comments while commenting about any video. So you should not talk about any particular video, focus rather in explaining the overall senario of all that was found.\n\nExample of response:\n\n\"It seems like what is trending in digital marketing right now is talking about mental triggers, since 3 of the most trending videos in the last 2 days were about...\""
}
},
"typeVersion": 1.6
},
{
"id": "ced4b937-b590-4727-b1f2-a5e88b96091a",
"name": "chat_message_received",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"position": [
80,
60
],
"webhookId": "ff9622a4-a6ec-4396-b9de-c95bd834c23c",
"parameters": {
"options": {}
},
"typeVersion": 1.1
},
{
"id": "35a91359-5007-407d-a750-d6642e595690",
"name": "youtube_search",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"position": [
540,
180
],
"parameters": {
"name": "youtube_search",
"workflowId": {
"__rl": true,
"mode": "list",
"value": "N9DveO781xbNf8qs",
"cachedResultName": "Youtube Search Workflow"
},
"description": "Call this tool to search for trending videos based on a query.",
"jsonSchemaExample": "{\n\t\"search_term\": \"some_value\"\n}",
"specifyInputSchema": true
},
"typeVersion": 1.2
},
{
"id": "42f41096-531d-4587-833a-6f659ef78dd0",
"name": "openai_llm",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
260,
180
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "e4bda5b9-abd4-4cd6-8c95-126a01aa6e21",
"name": "window_buffer_memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
400,
180
],
"parameters": {},
"typeVersion": 1.2
},
{
"id": "f6d86c5a-393a-4bcf-bdaf-3b06c79fa51d",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
0
],
"parameters": {
"color": 7,
"width": 693.2572054941234,
"height": 354.53098948245656,
"content": "Main Workflow"
},
"typeVersion": 1
},
{
"id": "4ddbc3f0-e3d7-4ce4-a732-d731c05024d2",
"name": "find_video_data1",
"type": "n8n-nodes-base.httpRequest",
"position": [
700,
720
],
"parameters": {
"url": "https://www.googleapis.com/youtube/v3/videos?",
"options": {},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "key",
"value": "={{ $env[\"GOOGLE_API_KEY\"] }}"
},
{
"name": "id",
"value": "={{ $json.id.videoId }}"
},
{
"name": "part",
"value": "contentDetails, snippet, statistics"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "fdb28635-801d-4ce0-8919-11446c6a7a82",
"name": "get_videos1",
"type": "n8n-nodes-base.youTube",
"position": [
280,
560
],
"parameters": {
"limit": 3,
"filters": {
"q": "={{ $json.query.search_term }}",
"regionCode": "US",
"publishedAfter": "={{ new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString() }}"
},
"options": {
"order": "relevance",
"safeSearch": "moderate"
},
"resource": "video"
},
"credentials": {
"youTubeOAuth2Api": {
"id": "dCyrga3t1tlgQQy0",
"name": "YouTube account"
}
},
"typeVersion": 1
},
{
"id": "60e9e61d-0e5e-4212-8b55-71299aeec4d5",
"name": "response1",
"type": "n8n-nodes-base.set",
"position": [
1100,
500
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "b9b9117b-ea14-482e-a13b-e68b8e6b441d",
"name": "response",
"type": "string",
"value": "={{ $input.all() }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "254a6740-8b25-4898-9795-4c3f0009471f",
"name": "group_data1",
"type": "n8n-nodes-base.set",
"position": [
1160,
700
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "47c172ad-90c8-4cf6-a9f5-50607e04cc90",
"name": "id",
"type": "string",
"value": "={{ $json.items[0].id }}"
},
{
"id": "9e639efa-0714-4b06-9847-f7b4b2fbef59",
"name": "viewCount",
"type": "string",
"value": "={{ $json.items[0].statistics.viewCount }}"
},
{
"id": "93328f00-91b8-425b-ad0f-a330b2f95242",
"name": "likeCount",
"type": "string",
"value": "={{ $json.items[0].statistics.likeCount }}"
},
{
"id": "015b0fb2-2a98-464c-a21b-51100616f26a",
"name": "commentCount",
"type": "string",
"value": "={{ $json.items[0].statistics.commentCount }}"
},
{
"id": "cf1e1ec3-a138-42b8-8747-d249afa58dd3",
"name": "description",
"type": "string",
"value": "={{ $json.items[0].snippet.description }}"
},
{
"id": "c5c9a3a2-b820-4932-a38a-e21102992215",
"name": "title",
"type": "string",
"value": "={{ $json.items[0].snippet.title }}"
},
{
"id": "38216ead-1f8d-4f93-b6ad-5ef709a1ad2a",
"name": "channelTitle",
"type": "string",
"value": "={{ $json.items[0].snippet.channelTitle }}"
},
{
"id": "ff34194d-3d46-43a8-9127-84708987f536",
"name": "tags",
"type": "string",
"value": "={{ $json.items[0].snippet.tags.join(', ') }}"
},
{
"id": "e50b0f7b-3e37-4557-8863-d68d4fa505c8",
"name": "channelId",
"type": "string",
"value": "={{ $json.items[0].snippet.channelId }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "124c19a9-cbbd-4010-be37-50523c05f64b",
"name": "save_data_to_memory1",
"type": "n8n-nodes-base.code",
"position": [
1360,
700
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const workflowStaticData = $getWorkflowStaticData('global');\n\nif (typeof workflowStaticData.lastExecution !== 'object') {\n workflowStaticData.lastExecution = {\n response: \"\"\n };\n}\n\nfunction removeEmojis(text) {\n return text.replace(/[\\u{1F600}-\\u{1F64F}|\\u{1F300}-\\u{1F5FF}|\\u{1F680}-\\u{1F6FF}|\\u{2600}-\\u{26FF}|\\u{2700}-\\u{27BF}]/gu, '');\n}\n\nfunction cleanDescription(description) {\n return description\n .replace(/https?:\\/\\/\\S+/g, '')\n .replace(/www\\.\\S+/g, '')\n .replace(/ +/g, ' ')\n .trim();\n}\n\nconst currentItem = { ...$input.item };\n\nif (currentItem.description) {\n currentItem.description = cleanDescription(currentItem.description);\n}\n\nlet sanitizedItem = JSON.stringify(currentItem)\n .replace(/\\\\r/g, ' ')\n .replace(/https?:\\/\\/\\S+/g, '')\n .replace(/www\\.\\S+/g, '')\n .replace(/\\\\n/g, ' ')\n .replace(/\\n/g, ' ')\n .replace(/\\\\/g, '')\n .replace(/ +/g, ' ')\n .trim();\n\nif (workflowStaticData.lastExecution.response) {\n workflowStaticData.lastExecution.response += ' ### NEXT VIDEO FOUND: ### ';\n}\n\nworkflowStaticData.lastExecution.response += removeEmojis(sanitizedItem);\n\nreturn workflowStaticData.lastExecution;\n"
},
"typeVersion": 2
},
{
"id": "67f92ec4-71c0-49df-a0ea-11d2e3cf0f94",
"name": "retrieve_data_from_memory1",
"type": "n8n-nodes-base.code",
"position": [
780,
500
],
"parameters": {
"jsCode": "const workflowStaticData = $getWorkflowStaticData('global');\n\nconst lastExecution = workflowStaticData.lastExecution;\n\nreturn lastExecution;"
},
"typeVersion": 2
},
{
"id": "685820ba-b089-4cdc-984d-52f134754b5c",
"name": "loop_over_items1",
"type": "n8n-nodes-base.splitInBatches",
"position": [
500,
560
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "3d4d5a4b-d06b-41db-bb78-a64a266d5308",
"name": "if_longer_than_3_",
"type": "n8n-nodes-base.if",
"position": [
880,
720
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "08ba3db9-6bcf-47f8-a74d-9e26f28cb08f",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ \n (() => {\n const duration = $json.items[0].contentDetails.duration;\n\n // Helper function to convert ISO 8601 duration to seconds\n const iso8601ToSeconds = iso8601 => {\n const match = iso8601.match(/PT(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?/);\n const hours = parseInt(match[1] || 0, 10);\n const minutes = parseInt(match[2] || 0, 10);\n const seconds = parseInt(match[3] || 0, 10);\n return hours * 3600 + minutes * 60 + seconds;\n };\n\n // Convert duration to seconds\n const durationInSeconds = iso8601ToSeconds(duration);\n\n // Check if greater than 210 seconds (3 minutes 30 seconds)\n return durationInSeconds > 210;\n })() \n}}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "7c6b8b82-fd6c-4f44-bccf-88c5a76f0319",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
420
],
"parameters": {
"color": 5,
"width": 1607,
"height": 520,
"content": "This part should be abstracted to another workflow and called inside the \"youtube_search\" tool of the main AI Agent."
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "cea84238-2b82-4a32-85dd-0c71ad685d47",
"connections": {
"openai_llm": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"get_videos1": {
"main": [
[
{
"node": "loop_over_items1",
"type": "main",
"index": 0
}
]
]
},
"group_data1": {
"main": [
[
{
"node": "save_data_to_memory1",
"type": "main",
"index": 0
}
]
]
},
"youtube_search": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"find_video_data1": {
"main": [
[
{
"node": "if_longer_than_3_",
"type": "main",
"index": 0
}
]
]
},
"loop_over_items1": {
"main": [
[
{
"node": "retrieve_data_from_memory1",
"type": "main",
"index": 0
}
],
[
{
"node": "find_video_data1",
"type": "main",
"index": 0
}
]
]
},
"if_longer_than_3_": {
"main": [
[
{
"node": "group_data1",
"type": "main",
"index": 0
}
],
[
{
"node": "loop_over_items1",
"type": "main",
"index": 0
}
]
]
},
"save_data_to_memory1": {
"main": [
[
{
"node": "loop_over_items1",
"type": "main",
"index": 0
}
]
]
},
"window_buffer_memory": {
"ai_memory": [
[
{
"node": "AI Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"chat_message_received": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"retrieve_data_from_memory1": {
"main": [
[
{
"node": "response1",
"type": "main",
"index": 0
}
]
]
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,400 @@
{
"id": "AMQub0Da16qevkJS",
"meta": {
"instanceId": "1df58c4f9c75efc3206f24d952dcf4aad97b5bd5e4c3d0b251ca64e7a7153e89",
"templateCredsSetupCompleted": true
},
"name": "Code Review workflow",
"tags": [],
"nodes": [
{
"id": "62ef8e9f-df1a-46dd-b025-a206ac888f97",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-100,
140
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"id": "",
"name": ""
}
},
"typeVersion": 1.2
},
{
"id": "35361983-8c66-457e-8cb6-7585a18f8aaf",
"name": "PR Trigger",
"type": "n8n-nodes-base.githubTrigger",
"position": [
-740,
-80
],
"webhookId": "2b8ec7bd-e65b-46d2-a2d9-082b137dd880",
"parameters": {
"owner": {
"__rl": true,
"mode": "list",
"value": "",
"cachedResultUrl": "",
"cachedResultName": ""
},
"events": [
"pull_request"
],
"options": {},
"repository": {
"__rl": true,
"mode": "list",
"value": "",
"cachedResultUrl": "",
"cachedResultName": ""
},
"authentication": "oAuth2"
},
"credentials": {
"githubOAuth2Api": {
"id": "",
"name": ""
}
},
"notesInFlow": false,
"typeVersion": 1
},
{
"id": "25d17a0d-c409-406d-bd97-00ec71261c16",
"name": "Get file's Diffs from PR",
"type": "n8n-nodes-base.httpRequest",
"position": [
-520,
-80
],
"parameters": {
"url": "=https://api.github.com/repos/{{$json.body.sender.login}}/{{$json.body.repository.name}}/pulls/{{$json.body.number}}/files",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "f984f872-c4b0-4752-bc54-1f311fa36feb",
"name": "Create target Prompt from PR Diffs",
"type": "n8n-nodes-base.code",
"position": [
-300,
-80
],
"parameters": {
"jsCode": "const files = $input.all().map(item => item.json);\n\nlet diffs = '';\n\nfor (const file of files) {\n diffs += `### Fichier : ${file.filename}\\n\\n`;\n\n if (file.patch) {\n // IMPORTANT : On remplace tous les triple backticks par simple (ou échappement)\n const safePatch = file.patch.replace(/```/g, \"''\");\n\n diffs += \"```diff\\n\";\n diffs += safePatch;\n diffs += \"\\n```\\n\";\n } else {\n diffs += \"_Pas de patch disponible (probablement fichier binaire)._\";\n }\n\n diffs += \"\\n---\\n\\n\";\n}\n\nconst userMessage = `\nYou are a senior iOS developer. \nPlease review the following code changes in these files :\n\n${diffs}\n\n---\n\nYour mission:\n\n- Review the proposed code changes file by file and by significant modification.\n\n- Generate inline comments on the relevant lines of code.\n\n- Ignore files without patches.\n\n- Do not repeat the code snippet or the filename.\n\n- Write the comments directly, without introducing the context.\n`;\n\nreturn [\n {\n json: {\n user_message: userMessage.trim()\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "0d9790b1-9818-4e73-a202-57d4db039b35",
"name": "GitHub Robot",
"type": "n8n-nodes-base.github",
"position": [
296,
-80
],
"webhookId": "39c2fe8b-f686-4699-8450-2a5b4c263d93",
"parameters": {
"body": "={{ $json.output }}",
"event": "comment",
"owner": {
"__rl": true,
"mode": "list",
"value": "",
"cachedResultUrl": "",
"cachedResultName": ""
},
"resource": "review",
"repository": {
"__rl": true,
"mode": "list",
"value": "",
"cachedResultUrl": "",
"cachedResultName": ""
},
"additionalFields": {},
"pullRequestNumber": "={{ $('PR Trigger').first().json.body.number }}"
},
"credentials": {
"githubApi": {
"id": "",
"name": ""
}
},
"typeVersion": 1.1
},
{
"id": "234c235c-a88d-412b-b7b1-f9f0cc8eead9",
"name": "Add Label to PR",
"type": "n8n-nodes-base.github",
"position": [
516,
-80
],
"webhookId": "c98f39f1-603b-4013-9149-53b4cc31b611",
"parameters": {
"owner": {
"__rl": true,
"mode": "list",
"value": "",
"cachedResultUrl": "",
"cachedResultName": ""
},
"operation": "edit",
"editFields": {
"labels": [
{
"label": "ReviewedByAI"
}
]
},
"repository": {
"__rl": true,
"mode": "list",
"value": "",
"cachedResultUrl": "",
"cachedResultName": ""
},
"issueNumber": "={{ $('PR Trigger').first().json.body.number }}",
"authentication": "oAuth2"
},
"credentials": {
"githubOAuth2Api": {
"id": "",
"name": ""
}
},
"typeVersion": 1
},
{
"id": "34d9842f-928e-4d19-9d91-336c85f53485",
"name": "Code Best Practices",
"type": "n8n-nodes-base.googleSheetsTool",
"position": [
68,
140
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "name",
"value": ""
},
"documentId": {
"__rl": true,
"mode": "url",
"value": ""
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "",
"name": ""
}
},
"typeVersion": 4.5
},
{
"id": "ab6c0b9d-1c76-448c-896e-7fdb15365b72",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-880,
-260
],
"parameters": {
"content": "**1-The GitHub Trigger** node initiates the workflow whenever a pull request event occurs on a specified repository. It enables real-time automation based on GitHub activity.\n"
},
"typeVersion": 1
},
{
"id": "27752afa-4d97-4e23-be58-6171b5e17f1b",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-680,
100
],
"parameters": {
"color": 3,
"width": 340,
"height": 220,
"content": "**2-Get PR Diffs**\nThe HTTP Request node fetches the list of changed files and their diffs from the pull request that triggered the workflow. It uses the GitHub REST API to retrieve this data dynamically based on the trigger payload.\n\nhttps://api.github.com/repos/{{$json.body.sender.login}}/{{$json.body.repository.name}}/pulls/{{$json.body.number}}/files"
},
"typeVersion": 1
},
{
"id": "c201133c-3d54-4fe0-8442-11ff92dcc89e",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-420,
-340
],
"parameters": {
"color": 2,
"width": 360,
"height": 240,
"content": "**3-Create Prompt from diffs**\nThis Code node runs a JavaScript snippet to:\n-Parse file diffs from the previous HTTP Request node\n-Format each diff with its file name\n-Build a structured natural language prompt for the AI agent\n\nThe final output is a clear, contextual instruction like:\n*\"You are a senior iOS developer. Please review the following code changes in these files...\"*\n"
},
"typeVersion": 1
},
{
"id": "6f6c78b2-ad75-43fa-a082-9f345f9b5f30",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
200,
-260
],
"parameters": {
"color": 5,
"content": "**Github Comment Poster**\nPosts the AI-generated review as a comment on the pull request using GitHub API."
},
"typeVersion": 1
},
{
"id": "ac7b6754-2bef-408d-8f53-fb51ece1673e",
"name": "Code Review Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-80,
-80
],
"parameters": {
"text": "={{ $json.user_message }}",
"options": {},
"promptType": "define"
},
"typeVersion": 1.9
},
{
"id": "30655e04-f429-40bb-b6b7-9a11ffa4e607",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
460,
-220
],
"parameters": {
"color": 7,
"height": 120,
"content": "**PR Labeler (optional)**\nAutomatically adds a label like *ReviewedByAI* to the pull request once the AI comment is posted."
},
"typeVersion": 1
},
{
"id": "76fbb269-e7ce-4d8a-a609-a5ab454258d8",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
180,
120
],
"parameters": {
"color": 6,
"width": 260,
"content": "**Google Sheet Best Practices**\nEnables the AI agent to reference to your team coding guidelines stored in a Google Sheet for more accurate and opinionated reviews.\nYou can replace Google Sheets with any other database or tool."
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "9d1650b2-38a1-40bf-ad65-1902f069a06f",
"connections": {
"PR Trigger": {
"main": [
[
{
"node": "Get file's Diffs from PR",
"type": "main",
"index": 0
}
]
]
},
"GitHub Robot": {
"main": [
[
{
"node": "Add Label to PR",
"type": "main",
"index": 0
}
]
]
},
"Code Review Agent": {
"main": [
[
{
"node": "GitHub Robot",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Code Review Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Code Best Practices": {
"ai_tool": [
[
{
"node": "Code Review Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Get file's Diffs from PR": {
"main": [
[
{
"node": "Create target Prompt from PR Diffs",
"type": "main",
"index": 0
}
]
]
},
"Create target Prompt from PR Diffs": {
"main": [
[
{
"node": "Code Review Agent",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,927 @@
{
"meta": {
"instanceId": "84ba6d895254e080ac2b4916d987aa66b000f88d4d919a6b9c76848f9b8a7616",
"templateId": "2358"
},
"nodes": [
{
"id": "fb774d11-da48-4481-ad4e-8c93274f123e",
"name": "Send message",
"type": "n8n-nodes-base.slack",
"position": [
2340,
580
],
"parameters": {
"text": "=Data from webhook: {{ $json.query.email }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C079GL6K3U6",
"cachedResultName": "general"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.2
},
{
"id": "5a3ad8f1-eba7-4076-80fc-0c1237aab50b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
380,
240
],
"parameters": {
"color": 7,
"width": 1163.3132111854613,
"height": 677.0358687053997,
"content": "![h](https://i.postimg.cc/9XLvL5dL/slide-sf-talk.png#full-width)"
},
"typeVersion": 1
},
{
"id": "01c59396-0fef-4d1c-aa1f-787669300650",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1860,
240
],
"parameters": {
"color": 7,
"width": 437,
"height": 99,
"content": "# What is n8n?\n### Low-code Automation Platform for technical teams"
},
"typeVersion": 1
},
{
"id": "0bdd4a35-7f5c-443c-a14a-4e6f7ed18712",
"name": "Execute JavaScript",
"type": "n8n-nodes-base.code",
"position": [
2340,
380
],
"parameters": {
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
},
"typeVersion": 2
},
{
"id": "4b1b6cc1-1a9f-4a0c-96d5-fd179c84c79d",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
4440,
240
],
"parameters": {
"color": 6,
"width": 318,
"height": 106,
"content": "# Example #2\n### RAG with PDF as source"
},
"typeVersion": 1
},
{
"id": "7e9e7802-5695-4240-83b9-d6f02192ad2b",
"name": "Recursive Character Text Splitter",
"type": "@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter",
"position": [
5120,
1000
],
"parameters": {
"options": {},
"chunkSize": 3000,
"chunkOverlap": 200
},
"typeVersion": 1
},
{
"id": "63783c21-af6d-4e70-8dec-c861641c53fb",
"name": "Embeddings OpenAI",
"type": "@n8n/n8n-nodes-langchain.embeddingsOpenAi",
"position": [
4880,
820
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "5742ce9c-2f73-4129-85eb-876f562cf6b1",
"name": "Default Data Loader",
"type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
"position": [
5100,
820
],
"parameters": {
"loader": "pdfLoader",
"options": {
"metadata": {
"metadataValues": [
{
"name": "document-title",
"value": "={{ $('PDFs to download').item.json.whitepaper_title }}"
},
{
"name": "document-publish-year",
"value": "={{ $('PDFs to download').item.json.publish_year }}"
},
{
"name": "document-author",
"value": "={{ $('PDFs to download').item.json.author }}"
}
]
}
},
"dataType": "binary"
},
"typeVersion": 1
},
{
"id": "686c63fa-4672-4107-bd58-ffbb0650b44b",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
5840,
840
],
"parameters": {
"model": "gpt-4o",
"options": {
"temperature": 0.3
}
},
"typeVersion": 1
},
{
"id": "73a7df02-aa2c-4f0f-aa88-38cbbbf3b1cb",
"name": "Embeddings OpenAI2",
"type": "@n8n/n8n-nodes-langchain.embeddingsOpenAi",
"position": [
5980,
1140
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "42737305-fd39-4ec7-b4ba-53f70085dd5f",
"name": "Vector Store Retriever",
"type": "@n8n/n8n-nodes-langchain.retrieverVectorStore",
"position": [
6040,
840
],
"parameters": {},
"typeVersion": 1
},
{
"id": "2c7a3666-e123-439d-8b74-41eb375f066c",
"name": "Download PDF",
"type": "n8n-nodes-base.httpRequest",
"position": [
4700,
600
],
"parameters": {
"url": "={{ $json.file_url }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "866eaeb9-6a7c-4209-b485-8ef13ed006b4",
"name": "PDFs to download",
"type": "n8n-nodes-base.noOp",
"notes": "BTC Whitepaper + metadata",
"position": [
4440,
600
],
"parameters": {},
"notesInFlow": true,
"typeVersion": 1
},
{
"id": "e78f2191-096c-4575-9d48-fb891fd18698",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
4440,
440
],
"parameters": {
"color": 4,
"width": 414.36616595939887,
"height": 91.0723900084547,
"content": "## A. Load PDF into Pinecone\nDownload the PDF, then text embeddings into Pincecone"
},
"typeVersion": 1
},
{
"id": "7c3ccf27-32b1-4ea7-b2ef-6997793de733",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
5600,
460
],
"parameters": {
"color": 4,
"width": 284.62109466374466,
"height": 86.95121951219511,
"content": "## B. Chat with PDF\nUse GPT4o to chat with Pinecone index"
},
"typeVersion": 1
},
{
"id": "6063d009-da6e-4cbf-899f-c86b879931a7",
"name": "Read Pinecone Vector Store",
"type": "@n8n/n8n-nodes-langchain.vectorStorePinecone",
"position": [
5980,
980
],
"parameters": {
"options": {
"pineconeNamespace": "whitepaper"
},
"pineconeIndex": {
"__rl": true,
"mode": "list",
"value": "whitepapers",
"cachedResultName": "whitepapers"
}
},
"typeVersion": 1
},
{
"id": "8aa52156-264d-4911-993c-ac5117a76b21",
"name": "Question and Answer Chain",
"type": "@n8n/n8n-nodes-langchain.chainRetrievalQa",
"position": [
5840,
620
],
"parameters": {
"text": "={{ $json.chatInput }}. \nOnly use vector store knowledge to answer the question. Don't make anything up. If you don't know the answer, tell the user that you don't know.",
"promptType": "define"
},
"typeVersion": 1.3
},
{
"id": "b394ee1d-a2ca-4db0-8caa-981f8f066787",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
7380,
240
],
"parameters": {
"color": 6,
"width": 504.25,
"height": 106,
"content": "# Example #3\n### AI Assistant that knows how to use predefined API endpoints "
},
"typeVersion": 1
},
{
"id": "37a8b8f2-c444-4c6e-9b02-b97a5c616e84",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
3020,
220
],
"parameters": {
"color": 6,
"width": 318,
"height": 111,
"content": "# Example #1\n### Categorize incoming emails with AI"
},
"typeVersion": 1
},
{
"id": "07123e8e-8760-4c89-acda-aaef6de68be2",
"name": "Anthropic Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
"position": [
7580,
700
],
"parameters": {
"options": {
"temperature": 0.4
}
},
"typeVersion": 1.2
},
{
"id": "e338a175-e823-4cd4-b77d-f5acbfcbdb9d",
"name": "Get calendar availability",
"type": "@n8n/n8n-nodes-langchain.toolHttpRequest",
"position": [
7900,
700
],
"parameters": {
"url": "https://www.googleapis.com/calendar/v3/freeBusy",
"method": "POST",
"jsonBody": "={\n \"timeMin\": \"{timeMin}\",\n \"timeMax\": \"{timeMax}\",\n \"timeZone\": \"Europe/Berlin\",\n \"groupExpansionMax\": 20,\n \"calendarExpansionMax\": 10,\n \"items\": [\n {\n \"id\": \"max@n8n.io\"\n }\n ]\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"toolDescription": "Call this tool to get the appointment availability for a particular period on the calendar. The tool may refer to availability as \"Free\" or \"Busy\". \n\nUse {timeMin} and {timeMax} to specify the window for the availability query. For example, to get availability for 25 July, 2024 the {timeMin} would be 2024-07-25T09:00:00+02:00 and {timeMax} would be 2024-07-25T17:00:00+02:00.\n\nIf the tool returns an empty response, it means that something went wrong. It does not mean that there is no availability.",
"nodeCredentialType": "googleCalendarOAuth2Api"
},
"typeVersion": 1
},
{
"id": "ae05933c-dfa9-4272-b610-8b5fc94d76fe",
"name": "Appointment booking agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
7680,
480
],
"parameters": {
"options": {
"systemMessage": "=You are an efficient and courteous assistant tasked with scheduling appointments with Max Tkacz.\n\nWhen users mention an appointment or meeting, they are referring to a meeting with Max.\nWhen users refer to the calendar or \"your schedule,\" they are referring to Max's calendar. \n\nYou can use various tools to access and manage Max's calendar. Your primary goal is to assist users in successfully booking an appointment with Max, ensuring no scheduling conflicts. Only book an appointment if the requested time slot is available (the tool may refer to this as \"Free\")\n\nToday's date is {{ $today.format('dd LLL yyyy') }}.\nAppointments are always 30 minutes in length. \n\n\nProvide accurate information at all times. If the tools are not functioning correctly, inform the user that you are unable to assist them at the moment.\n"
}
},
"typeVersion": 1.6
},
{
"id": "7e3b1797-150e-4c7c-93a5-306b981e0b6c",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
8300,
440
],
"parameters": {
"color": 7,
"width": 327.46658341463433,
"height": 571.8601927804875,
"content": "![h](https://i.imghippo.com/files/d9Bgv1721858679.png#full-width)\n[Open Calendar](https://calendar.google.com/calendar/u/0/r/day/2024/7/26)"
},
"typeVersion": 1
},
{
"id": "afe8d14d-d0d0-4a11-bb4f-57358de66bc1",
"name": "Window Buffer Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
7720,
700
],
"parameters": {
"contextWindowLength": 10
},
"typeVersion": 1.2
},
{
"id": "53d131ea-3235-4e4e-828b-dc22c9021e50",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
6380,
640
],
"parameters": {
"color": 7,
"width": 615.2162978341456,
"height": 403.1877919219511,
"content": "![h](https://i.postimg.cc/kXW9XrZt/Screenshot-2024-07-24-at-15-18-27.png#full-width)\nBTC Whitepaper references"
},
"typeVersion": 1
},
{
"id": "55a0f180-bb35-4b35-b72c-b9361698e5ad",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
9660,
240
],
"parameters": {
"color": 7,
"width": 345.33741540309194,
"height": 398.9629539487597,
"content": "### Connect with me or explore this demo \ud83d\udc47\n![QR](https://i.postimg.cc/VNkdCLQh/frame.png#full-width)"
},
"typeVersion": 1
},
{
"id": "14b3231d-aa96-4783-be8f-cb2f70b0bc7f",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
9220,
240
],
"parameters": {
"color": 7,
"width": 411.2946586626259,
"height": 197.19036476628202,
"content": "# Thank you and happy flowgramming \ud83e\udd18\n\n### Max Tkacz | Senior Developer Advocate @ n8n"
},
"typeVersion": 1
},
{
"id": "c9a2fcdc-c8ab-4b9d-9979-4fd7cca1e8a8",
"name": "Insert into Pinecone vector store",
"type": "@n8n/n8n-nodes-langchain.vectorStorePinecone",
"position": [
4920,
600
],
"parameters": {
"mode": "insert",
"options": {
"clearNamespace": true,
"pineconeNamespace": "whitepaper"
},
"pineconeIndex": {
"__rl": true,
"mode": "list",
"value": "whitepapers",
"cachedResultName": "whitepapers"
}
},
"typeVersion": 1
},
{
"id": "6a890c74-67f9-4eee-bb56-7c9a68921ae1",
"name": "Book appointment",
"type": "@n8n/n8n-nodes-langchain.toolHttpRequest",
"position": [
8060,
700
],
"parameters": {
"url": "https://www.googleapis.com/calendar/v3/calendars/max@n8n.io/events",
"method": "POST",
"jsonBody": "={\n \"summary\": \"Appointment with {userName}\",\n \"start\": {\n \"dateTime\": \"{startTime}\",\n \"timeZone\": \"Europe/Berlin\"\n },\n \"end\": {\n \"dateTime\": \"{endTime}\",\n \"timeZone\": \"Europe/Berlin\"\n },\n \"attendees\": [\n {\"email\": \"max@n8n.io\"},\n {\"email\": \"{userEmail}\"}\n ]\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"toolDescription": "Call this tool to book an appointment in the calendar. ",
"nodeCredentialType": "googleCalendarOAuth2Api",
"placeholderDefinitions": {
"values": [
{
"name": "userName",
"description": "The full name of the user making the appointment. Like John Doe"
},
{
"name": "startTime",
"description": "The start time of the event in Europe/Berlin timezone. For example, 2024-07-24T10:00:00+02:00"
},
{
"name": "endTime",
"description": "The end time of the event in Europe/Berlin timezone. It should always be 30 minutes after the startTime. "
},
{
"name": "userEmail",
"description": "The email address of the user making the appointment"
}
]
}
},
"typeVersion": 1
},
{
"id": "7f6e62f2-2d72-4fd2-a6ef-e57028d0055b",
"name": "When chat message received",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"position": [
5600,
620
],
"webhookId": "c348693e-9c43-4bf2-90a5-23786273e809",
"parameters": {
"public": true,
"options": {
"title": "Book an appointment with Max"
},
"initialMessages": "Hi there! \ud83d\udc4b\nI can help you schedule an appointment with Max Tkacz. On which day would you like to meet?"
},
"typeVersion": 1.1
},
{
"id": "52c65975-479d-4c76-bcd3-23f5c9bb6acf",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
9220,
460
],
"parameters": {
"color": 7,
"width": 411.2946586626259,
"height": 80,
"content": "### Explore 100+ AI Workflow templates on n8n.io\n[Open Templates Library](https://n8n.io/workflows)"
},
"typeVersion": 1
},
{
"id": "ba0635c0-2ca4-4b27-b960-3a0e0f93a56a",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
9220,
560
],
"parameters": {
"color": 7,
"width": 411.2946586626259,
"height": 80,
"content": "### Ask a question in our community (13k+ members)\n[Explore n8n community](https://community.n8n.io/)"
},
"typeVersion": 1
},
{
"id": "29227c52-a9cc-4bd1-b1a3-78fb805b659c",
"name": "OpenAI Chat Model1",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
3260,
660
],
"parameters": {
"model": "gpt-4o",
"options": {
"temperature": 0.5
}
},
"typeVersion": 1
},
{
"id": "494a2868-9ff5-402c-b83b-6dd2c3ddbcc9",
"name": "Add automation label",
"type": "n8n-nodes-base.gmail",
"position": [
3760,
300
],
"parameters": {
"labelIds": [
"Label_4763053241338138112"
],
"messageId": "={{ $json.id }}",
"operation": "addLabels"
},
"typeVersion": 2.1
},
{
"id": "0f9d834d-ec47-43f5-945b-8c464d371122",
"name": "On new email to nathan's inbox",
"type": "n8n-nodes-base.gmailTrigger",
"disabled": true,
"position": [
3040,
460
],
"parameters": {
"simple": false,
"filters": {},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "142e2a49-40bd-4bf5-9ba3-f14ecd68618e",
"name": "Add music label",
"type": "n8n-nodes-base.gmail",
"position": [
3760,
500
],
"parameters": {
"labelIds": [
"Label_6822395192337188416"
],
"messageId": "={{ $json.id }}",
"operation": "addLabels"
},
"typeVersion": 2.1
},
{
"id": "2eb46753-a0e8-43ec-a460-466b1dd265c9",
"name": "Assign label with AI",
"type": "@n8n/n8n-nodes-langchain.textClassifier",
"position": [
3280,
460
],
"parameters": {
"options": {},
"inputText": "={{ $json.text }}",
"categories": {
"categories": [
{
"category": "automation",
"description": "email on the topic of automation or workflows and automated processes, includes newsletters on this topic"
},
{
"category": "music",
"description": "email on the topic of music, for example from an artist "
}
]
}
},
"typeVersion": 1
},
{
"id": "576d8206-1b1e-4671-ba45-86e9d844a73b",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
1860,
460
],
"webhookId": "74facfd7-0f51-4605-9724-2c300594fcf9",
"parameters": {
"path": "74facfd7-0f51-4605-9724-2c300594fcf9",
"options": {}
},
"typeVersion": 2
},
{
"id": "1e612376-1a3b-4c48-9cd3-97867ba4cad5",
"name": "Whether email contains n8n",
"type": "n8n-nodes-base.if",
"position": [
2060,
460
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a0b16c44-03ea-4e96-9671-7b168697186d",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.query.email }}",
"rightValue": "@n8n"
}
]
}
},
"typeVersion": 2
}
],
"pinData": {},
"connections": {
"Webhook": {
"main": [
[
{
"node": "Whether email contains n8n",
"type": "main",
"index": 0
}
]
]
},
"Download PDF": {
"main": [
[
{
"node": "Insert into Pinecone vector store",
"type": "main",
"index": 0
}
]
]
},
"Book appointment": {
"ai_tool": [
[
{
"node": "Appointment booking agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"PDFs to download": {
"main": [
[
{
"node": "Download PDF",
"type": "main",
"index": 0
}
]
]
},
"Embeddings OpenAI": {
"ai_embedding": [
[
{
"node": "Insert into Pinecone vector store",
"type": "ai_embedding",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Question and Answer Chain",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Embeddings OpenAI2": {
"ai_embedding": [
[
{
"node": "Read Pinecone Vector Store",
"type": "ai_embedding",
"index": 0
}
]
]
},
"OpenAI Chat Model1": {
"ai_languageModel": [
[
{
"node": "Assign label with AI",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Default Data Loader": {
"ai_document": [
[
{
"node": "Insert into Pinecone vector store",
"type": "ai_document",
"index": 0
}
]
]
},
"Anthropic Chat Model": {
"ai_languageModel": [
[
{
"node": "Appointment booking agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Assign label with AI": {
"main": [
[
{
"node": "Add automation label",
"type": "main",
"index": 0
}
],
[
{
"node": "Add music label",
"type": "main",
"index": 0
}
]
]
},
"Window Buffer Memory": {
"ai_memory": [
[
{
"node": "Appointment booking agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Vector Store Retriever": {
"ai_retriever": [
[
{
"node": "Question and Answer Chain",
"type": "ai_retriever",
"index": 0
}
]
]
},
"Get calendar availability": {
"ai_tool": [
[
{
"node": "Appointment booking agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Read Pinecone Vector Store": {
"ai_vectorStore": [
[
{
"node": "Vector Store Retriever",
"type": "ai_vectorStore",
"index": 0
}
]
]
},
"When chat message received": {
"main": [
[
{
"node": "Question and Answer Chain",
"type": "main",
"index": 0
}
]
]
},
"Whether email contains n8n": {
"main": [
[
{
"node": "Execute JavaScript",
"type": "main",
"index": 0
},
{
"node": "Send message",
"type": "main",
"index": 0
}
]
]
},
"On new email to nathan's inbox": {
"main": [
[
{
"node": "Assign label with AI",
"type": "main",
"index": 0
}
]
]
},
"Recursive Character Text Splitter": {
"ai_textSplitter": [
[
{
"node": "Default Data Loader",
"type": "ai_textSplitter",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,620 @@
{
"id": "Agn9dzf5YTqcmQGN",
"meta": {
"instanceId": "8029058e18ae4ed6081000c1270d96039ad05959052aa2034dd96a215849bcf7",
"templateCredsSetupCompleted": true
},
"name": "Amazon Ads AI Optimization",
"tags": [
{
"id": "vjZ7QzTW2i7StzqX",
"name": "AI Flow",
"createdAt": "2025-04-10T00:32:55.235Z",
"updatedAt": "2025-04-10T00:32:55.235Z"
}
],
"nodes": [
{
"id": "0286c917-d771-4835-a5f8-71f79a5e59e8",
"name": "List Files",
"type": "n8n-nodes-base.googleDrive",
"position": [
-100,
-800
],
"parameters": {
"filter": {
"folderId": {
"__rl": true,
"mode": "list",
"value": "",
"cachedResultUrl": "",
"cachedResultName": "<choose report folder>"
}
},
"options": {},
"resource": "fileFolder",
"searchMethod": "query"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "UPKjIF2z8RkkmP21",
"name": "Google Drive account"
}
},
"typeVersion": 3
},
{
"id": "7d9b0c0a-86ee-4aae-8d73-66f409b0a57f",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1620,
-540
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o",
"cachedResultName": "gpt-4o"
},
"options": {}
},
"credentials": {
"openAiApi": {
"id": "qszlkCg3ypMJEWvt",
"name": "OpenAi account"
}
},
"typeVersion": 1.2
},
{
"id": "d3d58b0a-3107-4525-92a8-d54332e9a8a5",
"name": "is XLSX",
"type": "n8n-nodes-base.if",
"position": [
540,
-800
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "820b48a1-676d-400b-894f-3b3a5203eca7",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.name }}",
"rightValue": ".xlsx"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "884e4a08-3b19-4485-aba7-c69887607b82",
"name": "Get File",
"type": "n8n-nodes-base.googleDrive",
"position": [
100,
-800
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {
"binaryPropertyName": "data",
"googleFileConversion": {
"conversion": {}
}
},
"operation": "download"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "UPKjIF2z8RkkmP21",
"name": "Google Drive account"
}
},
"typeVersion": 3
},
{
"id": "c72fde38-de38-4734-a7e8-aa70e8638cad",
"name": "Merge XLSX and CSV",
"type": "n8n-nodes-base.merge",
"position": [
1200,
-800
],
"parameters": {},
"typeVersion": 3.1
},
{
"id": "cd23e23c-9bb7-4b8d-90ab-8917783cf1ab",
"name": "Format Data",
"type": "n8n-nodes-base.code",
"position": [
1420,
-800
],
"parameters": {
"jsCode": "const result = {};\n\nfor (const item of items) {\n const fileName = item.json.fileName || item.json.name || 'unknown_file';\n const baseName = fileName\n .split('.')[0]\n .replace(/\\s+/g, '_')\n .toLowerCase()\n .replace(/\\s*\\(\\d+\\)$/, '')\n .replace(/_+$/, '')\n .trim();\n\n // regex → result key\n const map = [\n { key: 'search_terms', regex: /search_term/ },\n { key: 'campaigns', regex: /campaign/ },\n { key: 'targeting', regex: /targeting/ },\n { key: 'placement', regex: /placement/ },\n { key: 'budgets', regex: /budget/ },\n ];\n\n const entry = map.find(m => m.regex.test(baseName));\n const mappedKey = entry ? entry.key : null;\n\n console.log('fileName:', fileName);\n console.log('baseName:', baseName);\n console.log('mappedKey:', mappedKey);\n\n if (!mappedKey) {\n throw new Error(`${fileName} → ${baseName} → Unrecognized file name structure`);\n }\n result[mappedKey] = result[mappedKey] || [];\n result[mappedKey].push(item.json);\n}\n\nreturn [{ json: result }];\n\n\n\n"
},
"typeVersion": 2
},
{
"id": "02172577-d867-45a4-96ea-eb105169deff",
"name": "Set fileName",
"type": "n8n-nodes-base.set",
"position": [
320,
-800
],
"parameters": {
"options": {
"dotNotation": true,
"ignoreConversionErrors": false
},
"assignments": {
"assignments": [
{
"id": "a467fabb-d7d0-482d-8a6a-afcd97cc0d8c",
"name": "fileName",
"type": "string",
"value": "={{ $json.name }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "31db008f-20e4-4fe3-a9d0-1815b3802690",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-140,
-1040
],
"parameters": {
"color": 3,
"width": 180,
"height": 200,
"content": "## Change\nChoose the \"folder\" in the filter options to the folder containing your Ad reports\n"
},
"typeVersion": 1
},
{
"id": "0ba8c273-8369-4009-9b93-b0fb243a3c85",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1640,
-1000
],
"parameters": {
"width": 260,
"content": "## AI Analysis\nUses GPT-4o to process the bundled reports and generate optimization instructions.\nPasses system instructions and cleaned data as input."
},
"typeVersion": 1
},
{
"id": "451bb016-1766-4688-aafc-75937e0d5c3f",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-660,
-580
],
"parameters": {
"width": 540,
"height": 700,
"content": "## Amazon Ads Report Scheduling Instructions\nTo run this workflow, schedule the following Sponsored Products reports in the Amazon Ads Console:\n\nUse \"Detailed\" for:\n\nSearch Term Report → Sponsored_Products_Search_Term_Detailed_L30\n\nTargeting Report → Sponsored_Products_Targeting_Detailed_L30\n\nUse \"Summary\" for:\n\nCampaign Report → Sponsored_Products_Campaign_L30\n\nPlacement Report → Sponsored_Products_Placement_L30\n\nBudget Report → Sponsored_Products_Budget_L30\n\nShared settings for all reports:\n\nDate Range: Last 30 Days\n\nFrequency: Daily\n\nFormat: .xlsx or .csv\n\nDelivery: Email + Console Download\n\nMake sure filenames match expectations so the workflow can route them correctly."
},
"typeVersion": 1
},
{
"id": "a671a4f1-05b0-4d7c-9cc1-8c2838593e34",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-60,
-580
],
"parameters": {
"width": 400,
"height": 520,
"content": "## Report Delivery\n\nHow to get reports into Google Drive\n\nUse one of the following:\n\n📥 Manual Upload Download emailed reports and move them to your Drive folder\n\n🤖 Automation Use n8n to watch Gmail for no-reply@amazon.com, extract attachments, and upload to Drive\n\n💻 Drive Sync Folder Use a local folder synced to Google Drive with rules for report types\n\nReports must match expected filenames so the flow can identify and classify them."
},
"typeVersion": 1
},
{
"id": "63a7f391-2bc7-41f9-a53f-e742950c60bf",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
360,
-580
],
"parameters": {
"width": 360,
"height": 520,
"content": "## Upgrade! 🚀\n\nApply for an Amazon Advertising API developer account to unlock full automation:\n\nGenerate reports programmatically via the Reports API\n\nFetch report files directly into n8n using HTTP or custom nodes\n\nEliminate email + Drive dependency entirely\n\n🔗 https://advertising.amazon.com/API/docs/en-us/\n\nOnce approved, you can schedule report generation and download all required data securely and automatically.\n**Double click** to edit me. [Guide](https://docs.n8n.io/workflows/sticky-notes/)"
},
"typeVersion": 1
},
{
"id": "e5a24705-0ad5-4629-b183-d279bdca8b29",
"name": "Preserve File Name",
"type": "n8n-nodes-base.set",
"position": [
980,
-900
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "d6883fe9-d04f-4c86-bc9a-f4dd526afca2",
"name": "fileName",
"type": "string",
"value": "={{ $('is XLSX').item.json.fileName }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "3c315a0c-a89e-490a-9a82-e3d96d2b94c7",
"name": "Email Optimizations",
"type": "n8n-nodes-base.gmail",
"position": [
2016,
-800
],
"webhookId": "b9d7c1a9-a1a3-4b97-97c9-a272f0e97127",
"parameters": {
"sendTo": "={{ $('Email Options').first().json.send_to }}",
"message": "={{\n (() => {\n let raw = $node[\"AI Analyze\"].json[\"text\"];\n\n // 🔧 Remove triple backticks and optional \"json\" tag\n raw = raw.replace(/^```json\\s*/i, \"\").replace(/```$/, \"\").trim();\n\n let data;\n\n try {\n data = JSON.parse(raw);\n } catch (err) {\n return `<p><strong>❌ Failed to parse AI output.</strong><br>${err.message}</p>`;\n }\n\n let msg = \"<h2>Amazon Ads Optimization Instructions</h2>\";\n\n // Optional Summary Totals\n const totalSpend = (data.campaign_adjustments || []).reduce((sum, c) => sum + (c.projected_daily_spend_usd || 0), 0);\n const totalSales = (data.campaign_adjustments || []).reduce((sum, c) => sum + (c.projected_daily_sales_usd || 0), 0);\n msg += `<p><strong>Total Budget Increase Recommended:</strong><br>`;\n msg += `Estimated daily spend: <strong>$${totalSpend.toFixed(2)}</strong><br>`;\n msg += `Estimated daily sales: <strong>$${totalSales.toFixed(2)}</strong></p>`;\n\n // Campaign Adjustments\n msg += \"<h3>Campaign Adjustments:</h3><ul>\";\n (data.campaign_adjustments || []).forEach(c => {\n msg += `<li><strong>${c.campaign_name}</strong><ul>`;\n if (c.default_bid_multiplier !== undefined) {\n const percent = Math.round((1 - c.default_bid_multiplier) * 100);\n msg += `<li>Default bid × ${c.default_bid_multiplier} (<em>${percent}%</em>)</li>`;\n }\n if (c.bid_adjustments) {\n msg += \"<li>Bid adjustments:<ul>\";\n msg += `<li>Top of Search: ${c.bid_adjustments.top_of_search ?? 0}%</li>`;\n msg += `<li>Rest of Search: ${c.bid_adjustments.rest_of_search ?? 0}%</li>`;\n msg += `<li>Product pages: ${c.bid_adjustments.product_pages ?? 0}%</li>`;\n msg += \"</ul></li>\";\n }\n if (c.budget_change?.action !== \"none\") {\n msg += `<li>Budget: ${c.budget_change.action} by ${c.budget_change.percent}%</li>`;\n }\n if (c.projected_daily_spend_usd && c.projected_daily_sales_usd) {\n msg += `<li>Est. daily spend: $${c.projected_daily_spend_usd.toFixed(2)}</li>`;\n msg += `<li>Est. daily sales: $${c.projected_daily_sales_usd.toFixed(2)}</li>`;\n if (c.estimated_acos_percent !== undefined) {\n msg += `<li>ACoS: ${c.estimated_acos_percent}%</li>`;\n }\n if (c.estimated_roas_multiple !== undefined) {\n const color = c.estimated_roas_multiple < 1.0 ? 'red' : 'green';\n msg += `<li>ROAS: <span style=\"color:${color}\">${c.estimated_roas_multiple.toFixed(2)}x</span></li>`;\n }\n }\n msg += \"</ul></li>\";\n });\n msg += \"</ul>\";\n\n // Keyword Recommendations\n if ((data.keyword_recommendations?.add_exact?.length || 0) > 0 ||\n (data.keyword_recommendations?.negative?.length || 0) > 0) {\n msg += \"<h3>Keyword Recommendations:</h3><ul>\";\n (data.keyword_recommendations.add_exact || []).forEach(k => {\n msg += `<li>Add exact: \"<strong>${k.term}</strong>\" in <em>${k.campaign_name} / ${k.ad_group_name}</em> at <strong>$${k.suggested_bid}</strong></li>`;\n });\n (data.keyword_recommendations.negative || []).forEach(n => {\n if (typeof n === 'string') {\n msg += `<li>Negative: \"<strong>${n}</strong>\"</li>`;\n } else {\n msg += `<li>Negative: \"<strong>${n.term}</strong>\" in <em>${n.campaign_name || 'Unspecified Campaign'}</em></li>`;\n }\n });\n msg += \"</ul>\";\n }\n\n // Targeting Recommendations\n if ((data.targeting_recommendations || []).length > 0) {\n msg += \"<h3>Targeting Recommendations:</h3><ul>\";\n data.targeting_recommendations.forEach(t => {\n const valueText = t.value ? ` by ${t.value}` : \"\";\n msg += `<li>${t.target} in <em>${t.campaign_name} / ${t.ad_group_name}</em>: <strong>${t.action}</strong>${valueText}</li>`;\n });\n msg += \"</ul>\";\n }\n\n return msg;\n })()\n}}\n",
"options": {},
"subject": "={{ $('Email Options').first().json.subject }}"
},
"credentials": {
"gmailOAuth2": {
"id": "6m7O3IpXy4mCRogW",
"name": "Brian Gmail"
}
},
"typeVersion": 2.1
},
{
"id": "f4fc0a70-2df9-4b7b-b60c-856b1b74ead7",
"name": "Extract XLSX Data",
"type": "n8n-nodes-base.extractFromFile",
"position": [
760,
-900
],
"parameters": {
"options": {},
"operation": "xlsx"
},
"typeVersion": 1
},
{
"id": "d0618a5b-1995-474d-a969-38e856b1b91a",
"name": "Extract CSV Data",
"type": "n8n-nodes-base.extractFromFile",
"position": [
760,
-700
],
"parameters": {
"options": {},
"binaryPropertyName": "=data"
},
"typeVersion": 1
},
{
"id": "67f9d0a2-2f34-416a-bc11-ef776e6e4ab3",
"name": "Preserve CSV File Name",
"type": "n8n-nodes-base.set",
"position": [
980,
-700
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "d6883fe9-d04f-4c86-bc9a-f4dd526afca2",
"name": "fileName",
"type": "string",
"value": "={{ $('is XLSX').item.json.fileName }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "818205c9-0fe9-4fe6-8556-657f087ba7b9",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-500,
-800
],
"parameters": {},
"typeVersion": 1
},
{
"id": "1612753d-0b7f-4ae5-9ec0-8ad39f1003b1",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-580,
-1040
],
"parameters": {
"width": 220,
"content": "## Trigger\nYou may replace this with a scheduled event or poll the folder for changes."
},
"typeVersion": 1
},
{
"id": "158da856-b682-4f98-afcc-4fa12b978db0",
"name": "Email Options",
"type": "n8n-nodes-base.set",
"position": [
-300,
-800
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "60c2189a-2ca3-43ac-bffc-371bbc3c123b",
"name": "send_to",
"type": "string",
"value": "<enter send to email address>"
},
{
"id": "c6f588b3-b8b9-4a83-817b-a68de36d2570",
"name": "subject",
"type": "string",
"value": "<enter the email subject for report emails>"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "4f1f251e-5cfb-468d-9531-9c2ba2c875f6",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-320,
-1040
],
"parameters": {
"color": 3,
"width": 160,
"content": "## Change!\nEdit these email options."
},
"typeVersion": 1
},
{
"id": "ca2f4a7c-5aa9-4f6a-bc04-aedce5e0aaed",
"name": "AI Analyze",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
1640,
-800
],
"parameters": {
"text": "={{JSON.stringify($json)}}",
"messages": {
"messageValues": [
{
"message": "You are an Amazon Ads Optimization Assistant. You will receive five structured datasets from Sponsored Products reports:\n- search_terms\n- campaigns\n- targeting\n- placement\n- budgets\n\nYour goal is to generate precise performance recommendations for bid strategy, targeting, and budget scaling.\n\n---\n\n1. Campaign Adjustments:\nFor each campaign, return:\n- campaign_name (string)\n- default_bid_multiplier (float, optional — only if bid should change)\n- bid_adjustments: { top_of_search, rest_of_search, product_pages } (percentages)\n- budget_change: { action: increase | decrease | none, percent: float }\n- projected_daily_spend_usd (float)\n- projected_daily_sales_usd (float)\n- estimated_acos_percent (float)\n- estimated_roas_multiple (float)\n\nBase projections on historical 30-day data. If a budget increase is recommended, scale projected spend and sales proportionally. Return NaN only if data is insufficient.\n\n---\n\n2. Keyword Recommendations:\nRecommend at least 5 exact-match keywords to add. Each must include:\n- term\n- campaign_name\n- ad_group_name\n- suggested_bid (USD)\n\nAlso return at least 3 negative keywords:\n- { term: \"...\", campaign_name?: \"...\" }\n\nDo not return keyword recommendations that lack campaign and ad group names.\n\n---\n\n3. Targeting Recommendations:\nRecommend at least 3 targets to pause or increase bids. Return:\n- target (ASIN, keyword, or match group)\n- campaign_name\n- ad_group_name\n- action: \"pause\" or \"increase_bid\"\n- value: float (if increasing bid)\n\n---\n\nRespond ONLY with a JSON object in this exact format. Do NOT include backticks, code blocks, or explanations:\n\n{\n \"campaign_adjustments\": [...],\n \"keyword_recommendations\": {\n \"add_exact\": [...],\n \"negative\": [...]\n },\n \"targeting_recommendations\": [...]\n}\n\n"
}
]
},
"promptType": "define"
},
"typeVersion": 1.6
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "286aae2a-f8df-489d-9f03-89d0b50b1800",
"connections": {
"is XLSX": {
"main": [
[
{
"node": "Extract XLSX Data",
"type": "main",
"index": 0
}
],
[
{
"node": "Extract CSV Data",
"type": "main",
"index": 0
}
]
]
},
"Get File": {
"main": [
[
{
"node": "Set fileName",
"type": "main",
"index": 0
}
]
]
},
"AI Analyze": {
"main": [
[
{
"node": "Email Optimizations",
"type": "main",
"index": 0
}
]
]
},
"List Files": {
"main": [
[
{
"node": "Get File",
"type": "main",
"index": 0
}
]
]
},
"Format Data": {
"main": [
[
{
"node": "AI Analyze",
"type": "main",
"index": 0
}
]
]
},
"Set fileName": {
"main": [
[
{
"node": "is XLSX",
"type": "main",
"index": 0
}
]
]
},
"Email Options": {
"main": [
[
{
"node": "List Files",
"type": "main",
"index": 0
}
]
]
},
"Extract CSV Data": {
"main": [
[
{
"node": "Preserve CSV File Name",
"type": "main",
"index": 0
}
]
]
},
"Extract XLSX Data": {
"main": [
[
{
"node": "Preserve File Name",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Analyze",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Merge XLSX and CSV": {
"main": [
[
{
"node": "Format Data",
"type": "main",
"index": 0
}
]
]
},
"Preserve File Name": {
"main": [
[
{
"node": "Merge XLSX and CSV",
"type": "main",
"index": 0
}
]
]
},
"Preserve CSV File Name": {
"main": [
[
{
"node": "Merge XLSX and CSV",
"type": "main",
"index": 1
}
]
]
},
"When clicking Test workflow": {
"main": [
[
{
"node": "Email Options",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,828 @@
{
"meta": {
"instanceId": "03e9d14e9196363fe7191ce21dc0bb17387a6e755dcc9acc4f5904752919dca8"
},
"nodes": [
{
"id": "94dd7f48-0013-4fb5-89c4-826ecd7f2d66",
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
1460,
120
],
"parameters": {
"simple": false,
"filters": {},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"gmailOAuth2": {
"id": "kkhNhqKpZt6IUZd0",
"name": "Gmail"
}
},
"typeVersion": 1.2
},
{
"id": "ca2023fa-ceca-4923-80e4-a3843803536c",
"name": "Microsoft Outlook Trigger",
"type": "n8n-nodes-base.microsoftOutlookTrigger",
"disabled": true,
"position": [
1480,
680
],
"parameters": {
"fields": [
"body",
"toRecipients",
"subject",
"bodyPreview"
],
"output": "fields",
"filters": {},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "vTCK0oVQ0WjFrI5H",
"name": " Outlook Credential"
}
},
"typeVersion": 1
},
{
"id": "1f011214-91a0-4cfa-9d9e-29864937c0a3",
"name": "Screenshot HTML",
"type": "n8n-nodes-base.httpRequest",
"position": [
2620,
420
],
"parameters": {
"url": "https://hcti.io/v1/image",
"method": "POST",
"options": {},
"sendBody": true,
"sendQuery": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "html",
"value": "={{ $('Set Email Variables').item.json.htmlBody }}"
}
]
},
"genericAuthType": "httpBasicAuth",
"queryParameters": {
"parameters": [
{}
]
}
},
"credentials": {
"httpBasicAuth": {
"id": "8tm8mUWmPvtmPFPk",
"name": "hcti.io"
}
},
"typeVersion": 4.2
},
{
"id": "64f4789f-9de8-414f-af62-ddc339f0d0ac",
"name": "Retrieve Screenshot",
"type": "n8n-nodes-base.httpRequest",
"position": [
2800,
420
],
"parameters": {
"url": "={{ $json.url }}",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth"
},
"credentials": {
"httpBasicAuth": {
"id": "8tm8mUWmPvtmPFPk",
"name": "hcti.io"
}
},
"typeVersion": 4.2
},
{
"id": "db707bd9-6abc-4ab7-8ffa-ad25c5e8adc4",
"name": "Set Outlook Variables",
"type": "n8n-nodes-base.set",
"position": [
2040,
680
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "38bd3db2-1a8d-4c40-a2dd-336e0cc84224",
"name": "htmlBody",
"type": "string",
"value": "={{ $('Microsoft Outlook Trigger').item.json.body.content }}"
},
{
"id": "13bdd95b-ef02-486e-b38b-d14bd05a4a8a",
"name": "headers",
"type": "string",
"value": "={{ $json}}"
},
{
"id": "20566ad4-7eb7-42b1-8a0d-f8b759610f10",
"name": "subject",
"type": "string",
"value": "={{ $('Microsoft Outlook Trigger').item.json.subject }}"
},
{
"id": "7171998f-a5a2-4e23-946a-9c1ad75710e7",
"name": "recipient",
"type": "string",
"value": "={{ $('Microsoft Outlook Trigger').item.json.toRecipients[0].emailAddress.address }}"
},
{
"id": "cc262634-2470-4524-8319-abe2518a6335",
"name": "textBody",
"type": "string",
"value": "={{ $('Retrieve Headers of Email').item.json.body.content }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "7a3622c0-6949-4ea3-ae13-46a1ee26de7b",
"name": "Set Gmail Variables",
"type": "n8n-nodes-base.set",
"position": [
2020,
120
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "38bd3db2-1a8d-4c40-a2dd-336e0cc84224",
"name": "htmlBody",
"type": "string",
"value": "={{ $json.html }}"
},
{
"id": "18fbcf78-6d3c-4036-b3a2-fb5adf22176a",
"name": "headers",
"type": "string",
"value": "={{ $json.headers }}"
},
{
"id": "1d690098-be2a-4604-baf8-62f314930929",
"name": "subject",
"type": "string",
"value": "={{ $json.subject }}"
},
{
"id": "8009f00a-547f-4eb1-b52d-2e7305248885",
"name": "recipient",
"type": "string",
"value": "={{ $json.to.text }}"
},
{
"id": "1932e97d-b03b-4964-b8bc-8262aaaa1f7a",
"name": "textBody",
"type": "string",
"value": "={{ $json.text }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "4b4c6b34-f74c-4402-91a1-4d002e02a3bd",
"name": "Retrieve Headers of Email",
"type": "n8n-nodes-base.httpRequest",
"position": [
1700,
680
],
"parameters": {
"url": "=https://graph.microsoft.com/v1.0/me/messages/{{ $json.id }}?$select=internetMessageHeaders,body",
"options": {},
"sendHeaders": true,
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/json"
},
{
"name": "Prefer",
"value": "outlook.body-content-type=\"text\""
}
]
},
"nodeCredentialType": "microsoftOutlookOAuth2Api"
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "vTCK0oVQ0WjFrI5H",
"name": " Outlook Credential"
}
},
"typeVersion": 4.2
},
{
"id": "0c9883b5-3eb7-45db-9803-d1b30166a3b5",
"name": "Format Headers",
"type": "n8n-nodes-base.code",
"position": [
1880,
680
],
"parameters": {
"jsCode": "const input = $('Retrieve Headers of Email').item.json.internetMessageHeaders;\n\nconst result = input.reduce((acc, { name, value }) => {\n if (!acc[name]) acc[name] = [];\n acc[name].push(value);\n return acc;\n}, {});\n\nreturn result;"
},
"typeVersion": 2
},
{
"id": "c21a976c-00e5-4823-bd94-4c95a7d60438",
"name": "Analyze Email with ChatGPT",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
3000,
420
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o",
"cachedResultName": "GPT-4O"
},
"options": {},
"messages": {
"values": [
{
"content": "=Describe the following email using the HTML body and headers. Determine if the email could be a phishing email. \n\nHere is the HTML body:\n{{ $('Set Email Variables').item.json.htmlBody }}\n\nThe message headers are as follows:\n{{ $('Set Email Variables').item.json.headers }}\n\n"
},
{
"role": "system",
"content": "Please make sure to output all responses using the following structured JSON output:\n{\n \"malicious\": false,\n \"summary\": \"The email appears to be a legitimate communication from a known sender. It contains no suspicious links, attachments, or language that indicates phishing or malicious intent.\"\n}\n\nFormat the response for Jira who uses a wiki-style renderer. Do not include ``` around your response. Make the summary as verbose as possible including a full breakdown of why the email is benign or malicious."
}
]
},
"jsonOutput": true
},
"credentials": {
"openAiApi": {
"id": "76",
"name": "OpenAi account"
}
},
"typeVersion": 1.6
},
{
"id": "a91f4095-9245-4276-b21f-f415de22df62",
"name": "Create Potentially Malicious Ticket",
"type": "n8n-nodes-base.jira",
"position": [
3640,
400
],
"parameters": {
"project": {
"__rl": true,
"mode": "list",
"value": "10001",
"cachedResultName": "Support"
},
"summary": "=Potentially Malicious - Phishing Email Reported: \"{{ $('Set Email Variables').item.json.subject }}\"",
"issueType": {
"__rl": true,
"mode": "list",
"value": "10008",
"cachedResultName": "Task"
},
"additionalFields": {
"description": "=A phishing email was reported by {{ $('Set Email Variables').item.json.recipient }} with the subject line \"{{ $('Set Email Variables').item.json.subject }}\"\n\\\\\nh2. Here is ChatGPT's analysis of the email:\n{{ $json.message.content.summary }}"
}
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "BZmmGUrNIsgM9fDj",
"name": "New Jira Cloud"
}
},
"typeVersion": 1
},
{
"id": "a5a66a0e-9d8a-45a9-b1ae-aec78ddfec27",
"name": "Create Potentially Benign Ticket",
"type": "n8n-nodes-base.jira",
"position": [
3640,
580
],
"parameters": {
"project": {
"__rl": true,
"mode": "list",
"value": "10001",
"cachedResultName": "Support"
},
"summary": "=Potentially Benign - Phishing Email Reported: \"{{ $('Set Email Variables').item.json.subject }}\"",
"issueType": {
"__rl": true,
"mode": "list",
"value": "10008",
"cachedResultName": "Task"
},
"additionalFields": {
"description": "=A phishing email was reported by {{ $('Set Email Variables').item.json.recipient }} with the subject line \"{{ $('Set Email Variables').item.json.subject }}\"\n\\\\\nh2. Here is ChatGPT's analysis of the email:\n{{ $json.message.content.summary }}"
}
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "BZmmGUrNIsgM9fDj",
"name": "New Jira Cloud"
}
},
"typeVersion": 1
},
{
"id": "5af0d60b-d021-4dd9-98f7-b2842800764a",
"name": "Rename Screenshot",
"type": "n8n-nodes-base.code",
"position": [
4020,
480
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "$('Retrieve Screenshot').item.binary.data.fileName = 'emailScreenshot.png'\n\nreturn $('Retrieve Screenshot').item;"
},
"typeVersion": 2
},
{
"id": "441c4cbb-bd93-4213-bd34-e18f2a49389f",
"name": "Set Jira ID",
"type": "n8n-nodes-base.set",
"position": [
3860,
480
],
"parameters": {
"options": {},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "4c71188c-011d-4f8e-a36c-87900bfab59a",
"name": "Upload Screenshot of Email to Jira",
"type": "n8n-nodes-base.jira",
"position": [
4220,
480
],
"parameters": {
"issueKey": "={{ $('Set Jira ID').item.json.key }}",
"resource": "issueAttachment"
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "BZmmGUrNIsgM9fDj",
"name": "New Jira Cloud"
}
},
"typeVersion": 1
},
{
"id": "3c031c34-8306-44e1-8e0e-a584c5323112",
"name": "Upload Email Body to Jira",
"type": "n8n-nodes-base.jira",
"position": [
4620,
480
],
"parameters": {
"issueKey": "={{ $('Set Jira ID').item.json.key }}",
"resource": "issueAttachment"
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "BZmmGUrNIsgM9fDj",
"name": "New Jira Cloud"
}
},
"typeVersion": 1
},
{
"id": "d033dcbd-7ccb-451f-ab81-cc6d32d2e01f",
"name": "Convert Email Body to File",
"type": "n8n-nodes-base.convertToFile",
"position": [
2420,
420
],
"parameters": {
"options": {
"fileName": "emailBody.txt"
},
"operation": "toText",
"sourceProperty": "textBody"
},
"typeVersion": 1.1
},
{
"id": "bda5e2fe-d8c0-456b-975a-35e82ff02816",
"name": "Set Email Variables",
"type": "n8n-nodes-base.set",
"position": [
2240,
420
],
"parameters": {
"options": {},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "54ecd8ab-ac4a-4b6b-bd1b-bf8c70082a33",
"name": "Rename Email Body Screenshot",
"type": "n8n-nodes-base.code",
"position": [
4420,
480
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "$('Convert Email Body to File').item.binary.data.fileName = 'emailBody.txt'\n\nreturn $('Convert Email Body to File').item;"
},
"typeVersion": 2
},
{
"id": "fe5b82cc-b4bb-4c97-9477-075d5a280e9f",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2574.536755825029,
0
],
"parameters": {
"color": 7,
"width": 376.8280004374956,
"height": 595.590013880477,
"content": "![hctiapi](https://uploads.n8n.io/templates/hctiapi2.png)\n## Email Body Screenshot Creation\n\nThe **Screenshot HTML** node sends the email's HTML body to the **hcti.io** API, generating a screenshot that visually represents the email's layout. The **Retrieve Screenshot** node then fetches this image, making it available for attachment or review in subsequent steps. This dual-format processing ensures both clarity and flexibility in email analysis workflows."
},
"typeVersion": 1
},
{
"id": "86b21049-f65e-4c6a-a854-c4376f870da9",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1380,
-149.99110983560342
],
"parameters": {
"color": 7,
"width": 814.4556539379754,
"height": 444.5525554815556,
"content": "![Gmail](https://uploads.n8n.io/templates/gmail.png)\n## Gmail Integration and Data Extraction\n\nThis section of the workflow connects to a Gmail account using the **Gmail Trigger** node, capturing incoming emails in real-time, with checks performed every minute. Once an email is detected, its key components\u2014such as the subject, recipient, body, and headers\u2014are extracted and assigned to variables using the **Set Gmail Variables** node. These variables are structured for subsequent analysis and processing in later steps."
},
"typeVersion": 1
},
{
"id": "b1a786cf-7a8d-49e1-90ed-31f3d0e65b13",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1380,
308
],
"parameters": {
"color": 7,
"width": 809.7918597571277,
"height": 602.9002284617277,
"content": "![Gmail](https://uploads.n8n.io/templates/outlook.png)\n## Microsoft Outlook Integration and Email Header Processing\n\nThis section enables the integration of Microsoft Outlook to monitor and capture incoming emails. The Microsoft Outlook Trigger node checks for new messages every minute. Once an email is detected, the Retrieve Headers of Email node fetches detailed header and body content via the Microsoft Graph API. The Format Headers node organizes the email headers into a structured format using a JavaScript function, ensuring clarity and readiness for further processing. Finally, the Set Outlook Variables node extracts and assigns key details\u2014such as the email subject, recipient, body, and formatted headers\u2014to variables for use in subsequent workflow steps. This section is essential for processing Outlook emails and preparing them for analysis and reporting.\n\n\n\n\n\n\n"
},
"typeVersion": 1
},
{
"id": "e7ace035-b5f5-4ef3-a117-22c7c938868d",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2958.4325220284563,
24.744924120002338
],
"parameters": {
"color": 7,
"width": 593.0990401534098,
"height": 573.1750519720028,
"content": "![hctiapi](https://uploads.n8n.io/templates/openai.png)\n## AI-Powered Email Analysis and Threat Detection\n\nThis section leverages ChatGPT for advanced email content and header analysis to determine potential phishing threats. The **Analyze Email with ChatGPT** node processes the email's HTML body and headers, generating a detailed JSON response that categorizes the email as malicious or benign. The response includes a verbose explanation, formatted for Jira, outlining the reasons for the classification. The **Check if Malicious** node evaluates the AI output to determine the next steps based on the email's threat status. If flagged as malicious, subsequent actions like reporting and ticket creation are triggered. This section ensures precise, AI-driven analysis to enhance email security workflows."
},
"typeVersion": 1
},
{
"id": "02c1ad8e-f952-42d2-ae9f-cf3a77e49e52",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
3562.4948140707697,
-125.79607719303533
],
"parameters": {
"color": 7,
"width": 1251.7025543502837,
"height": 891.579206098173,
"content": "![hctiapi](https://uploads.n8n.io/templates/jira.png)\n## Automated Jira Ticket Creation and Email Attachment\n\nThis section streamlines the process of logging phishing email reports in Jira, complete with detailed analysis and attachments. The workflow creates two distinct Jira tickets depending on the AI classification of the email:\n\n1. **Potentially Malicious**: The **Create Potentially Malicious Ticket** node generates a ticket if the email is flagged as a phishing attempt, including a summary of ChatGPT's analysis and the email\u2019s details.\n2. **Potentially Benign**: If the email is classified as safe, the **Create Potentially Benign Ticket** node logs a ticket with similar details but under a non-malicious category.\n\n\nThe **Set Jira ID** node ensures the generated ticket's ID is tracked for subsequent operations. Attachments are handled efficiently:\n\n- **Rename Screenshot** prepares the email screenshot for upload.\n- **Upload Screenshot of Email to Jira** adds the screenshot to the Jira ticket for visual context.\n- **Rename Email Body Screenshot** and **Upload Email Body to Jira** manage the attachment of the email's text body as a `.txt` file.\n\n\nThis section enhances reporting by automating ticket creation, ensuring all relevant email data is readily available for review by security teams."
},
"typeVersion": 1
},
{
"id": "597ef23e-c61c-4e27-8c14-74ec20079c96",
"name": "Check if Malicious",
"type": "n8n-nodes-base.if",
"position": [
3400,
420
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "493f412c-5f11-4173-8940-90f5bc7f5fab",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.message.content.malicious }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "af512af9-924b-4019-bdf9-62aac9cd0dac",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
2200,
39.041733604283195
],
"parameters": {
"color": 7,
"width": 365.6458805720866,
"height": 559.8072303111675,
"content": "![n8n](https://uploads.n8n.io/templates/n8n.png)\n## Email Body Conversion\n\nThis section processes the email body into both text and visual formats for detailed analysis and reporting. The **Set Email Variables** node organizes the email's data, including its HTML body and text content, to prepare it for further steps. The **Convert Email Body to File** node creates a `.txt` file containing the plain text version of the email body, useful for documentation or further analysis."
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Set Jira ID": {
"main": [
[
{
"node": "Rename Screenshot",
"type": "main",
"index": 0
}
]
]
},
"Gmail Trigger": {
"main": [
[
{
"node": "Set Gmail Variables",
"type": "main",
"index": 0
}
]
]
},
"Format Headers": {
"main": [
[
{
"node": "Set Outlook Variables",
"type": "main",
"index": 0
}
]
]
},
"Screenshot HTML": {
"main": [
[
{
"node": "Retrieve Screenshot",
"type": "main",
"index": 0
}
]
]
},
"Rename Screenshot": {
"main": [
[
{
"node": "Upload Screenshot of Email to Jira",
"type": "main",
"index": 0
}
]
]
},
"Check if Malicious": {
"main": [
[
{
"node": "Create Potentially Malicious Ticket",
"type": "main",
"index": 0
}
],
[
{
"node": "Create Potentially Benign Ticket",
"type": "main",
"index": 0
}
]
]
},
"Retrieve Screenshot": {
"main": [
[
{
"node": "Analyze Email with ChatGPT",
"type": "main",
"index": 0
}
]
]
},
"Set Email Variables": {
"main": [
[
{
"node": "Convert Email Body to File",
"type": "main",
"index": 0
}
]
]
},
"Set Gmail Variables": {
"main": [
[
{
"node": "Set Email Variables",
"type": "main",
"index": 0
}
]
]
},
"Set Outlook Variables": {
"main": [
[
{
"node": "Set Email Variables",
"type": "main",
"index": 0
}
]
]
},
"Microsoft Outlook Trigger": {
"main": [
[
{
"node": "Retrieve Headers of Email",
"type": "main",
"index": 0
}
]
]
},
"Retrieve Headers of Email": {
"main": [
[
{
"node": "Format Headers",
"type": "main",
"index": 0
}
]
]
},
"Analyze Email with ChatGPT": {
"main": [
[
{
"node": "Check if Malicious",
"type": "main",
"index": 0
}
]
]
},
"Convert Email Body to File": {
"main": [
[
{
"node": "Screenshot HTML",
"type": "main",
"index": 0
}
]
]
},
"Rename Email Body Screenshot": {
"main": [
[
{
"node": "Upload Email Body to Jira",
"type": "main",
"index": 0
}
]
]
},
"Create Potentially Benign Ticket": {
"main": [
[
{
"node": "Set Jira ID",
"type": "main",
"index": 0
}
]
]
},
"Upload Screenshot of Email to Jira": {
"main": [
[
{
"node": "Rename Email Body Screenshot",
"type": "main",
"index": 0
}
]
]
},
"Create Potentially Malicious Ticket": {
"main": [
[
{
"node": "Set Jira ID",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,600 @@
{
"meta": {
"instanceId": "03e9d14e9196363fe7191ce21dc0bb17387a6e755dcc9acc4f5904752919dca8"
},
"nodes": [
{
"id": "1bad6bfc-9ec9-48a5-b8f7-73c4de3d08cf",
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
1480,
160
],
"parameters": {
"simple": false,
"filters": {},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"gmailOAuth2": {
"id": "kkhNhqKpZt6IUZd0",
"name": " Gmail"
}
},
"typeVersion": 1.2
},
{
"id": "9ac747a1-4fd8-46ba-b4c1-75fd17aab2ed",
"name": "Microsoft Outlook Trigger",
"type": "n8n-nodes-base.microsoftOutlookTrigger",
"disabled": true,
"position": [
1480,
720
],
"parameters": {
"fields": [
"body",
"toRecipients",
"subject",
"bodyPreview"
],
"output": "fields",
"filters": {},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "vTCK0oVQ0WjFrI5H",
"name": " Outlook Credential"
}
},
"typeVersion": 1
},
{
"id": "5bf9b0e8-b84e-44a2-aad2-45dde3e4ab1b",
"name": "Screenshot HTML",
"type": "n8n-nodes-base.httpRequest",
"position": [
2520,
480
],
"parameters": {
"url": "https://hcti.io/v1/image",
"method": "POST",
"options": {},
"sendBody": true,
"sendQuery": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "html",
"value": "={{ $json.htmlBody }}"
}
]
},
"genericAuthType": "httpBasicAuth",
"queryParameters": {
"parameters": [
{}
]
}
},
"credentials": {
"httpBasicAuth": {
"id": "8tm8mUWmPvtmPFPk",
"name": "hcti.io"
}
},
"typeVersion": 4.2
},
{
"id": "fc770d1d-6c18-4d14-8344-1dc042464df6",
"name": "Retrieve Screenshot",
"type": "n8n-nodes-base.httpRequest",
"position": [
2700,
480
],
"parameters": {
"url": "={{ $json.url }}",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth"
},
"credentials": {
"httpBasicAuth": {
"id": "8tm8mUWmPvtmPFPk",
"name": "hcti.io"
}
},
"typeVersion": 4.2
},
{
"id": "2f3e5cc0-24e8-450a-898b-71e2d6f7bb58",
"name": "Set Outlook Variables",
"type": "n8n-nodes-base.set",
"position": [
2020,
720
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "38bd3db2-1a8d-4c40-a2dd-336e0cc84224",
"name": "htmlBody",
"type": "string",
"value": "={{ $('Microsoft Outlook Trigger').item.json.body.content }}"
},
{
"id": "13bdd95b-ef02-486e-b38b-d14bd05a4a8a",
"name": "headers",
"type": "string",
"value": "={{ $json}}"
},
{
"id": "20566ad4-7eb7-42b1-8a0d-f8b759610f10",
"name": "subject",
"type": "string",
"value": "={{ $('Microsoft Outlook Trigger').item.json.subject }}"
},
{
"id": "7171998f-a5a2-4e23-946a-9c1ad75710e7",
"name": "recipient",
"type": "string",
"value": "={{ $('Microsoft Outlook Trigger').item.json.toRecipients[0].emailAddress.address }}"
},
{
"id": "cc262634-2470-4524-8319-abe2518a6335",
"name": "textBody",
"type": "string",
"value": "={{ $('Retrieve Headers of Email').item.json.body.content }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "374e5b16-a666-4706-9fd2-762b2927012d",
"name": "Set Gmail Variables",
"type": "n8n-nodes-base.set",
"position": [
2040,
160
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "38bd3db2-1a8d-4c40-a2dd-336e0cc84224",
"name": "htmlBody",
"type": "string",
"value": "={{ $json.html }}"
},
{
"id": "18fbcf78-6d3c-4036-b3a2-fb5adf22176a",
"name": "headers",
"type": "string",
"value": "={{ $json.headers }}"
},
{
"id": "1d690098-be2a-4604-baf8-62f314930929",
"name": "subject",
"type": "string",
"value": "={{ $json.subject }}"
},
{
"id": "8009f00a-547f-4eb1-b52d-2e7305248885",
"name": "recipient",
"type": "string",
"value": "={{ $json.to.text }}"
},
{
"id": "1932e97d-b03b-4964-b8bc-8262aaaa1f7a",
"name": "textBody",
"type": "string",
"value": "={{ $json.text }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "3166738e-d0a3-475b-8b19-51afd519ee3a",
"name": "Retrieve Headers of Email",
"type": "n8n-nodes-base.httpRequest",
"position": [
1680,
720
],
"parameters": {
"url": "=https://graph.microsoft.com/v1.0/me/messages/{{ $json.id }}?$select=internetMessageHeaders,body",
"options": {},
"sendHeaders": true,
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/json"
},
{
"name": "Prefer",
"value": "outlook.body-content-type=\"text\""
}
]
},
"nodeCredentialType": "microsoftOutlookOAuth2Api"
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "vTCK0oVQ0WjFrI5H",
"name": " Outlook Credential"
}
},
"typeVersion": 4.2
},
{
"id": "25ae222c-088f-4565-98d6-803c8c1b0826",
"name": "Format Headers",
"type": "n8n-nodes-base.code",
"position": [
1860,
720
],
"parameters": {
"jsCode": "const input = $('Retrieve Headers of Email').item.json.internetMessageHeaders;\n\nconst result = input.reduce((acc, { name, value }) => {\n if (!acc[name]) acc[name] = [];\n acc[name].push(value);\n return acc;\n}, {});\n\nreturn result;"
},
"typeVersion": 2
},
{
"id": "8f14f267-1074-43ea-968d-26a6ab36fd7b",
"name": "Set Email Variables",
"type": "n8n-nodes-base.set",
"position": [
2360,
480
],
"parameters": {
"options": {},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "45d156aa-91f4-483c-91d4-c9de4a4f595d",
"name": "ChatGPT Analysis",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
3100,
480
],
"parameters": {
"text": "=Describe this image. Determine if the email could be a phishing email. The message headers are as follows:\n{{ $('Set Email Variables').item.json.headers }}\n\nFormat the response for Jira who uses a wiki-style renderer. Do not include ``` around your response.",
"modelId": {
"__rl": true,
"mode": "list",
"value": "chatgpt-4o-latest",
"cachedResultName": "CHATGPT-4O-LATEST"
},
"options": {
"maxTokens": 1500
},
"resource": "image",
"inputType": "base64",
"operation": "analyze"
},
"credentials": {
"openAiApi": {
"id": "76",
"name": "OpenAi account"
}
},
"typeVersion": 1.6
},
{
"id": "62ca591b-6627-496c-96a7-95cb0081480d",
"name": "Create Jira Ticket",
"type": "n8n-nodes-base.jira",
"position": [
3500,
480
],
"parameters": {
"project": {
"__rl": true,
"mode": "list",
"value": "10001",
"cachedResultName": "Support"
},
"summary": "=Phishing Email Reported: \"{{ $('Set Email Variables').item.json.subject }}\"",
"issueType": {
"__rl": true,
"mode": "list",
"value": "10008",
"cachedResultName": "Task"
},
"additionalFields": {
"description": "=A phishing email was reported by {{ $('Set Email Variables').item.json.recipient }} with the subject line \"{{ $('Set Email Variables').item.json.subject }}\" and body:\n{{ $('Set Email Variables').item.json.textBody }}\n\\\\\n\\\\\n\\\\\nh2. Here is ChatGPT's analysis of the email:\n{{ $json.content }}"
}
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "BZmmGUrNIsgM9fDj",
"name": "New Jira Cloud"
}
},
"typeVersion": 1
},
{
"id": "071380c8-8070-4f8f-86c6-87c4ee3bc261",
"name": "Rename Screenshot",
"type": "n8n-nodes-base.code",
"position": [
3680,
480
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "$('Retrieve Screenshot').item.binary.data.fileName = 'emailScreenshot.png'\n\nreturn $('Retrieve Screenshot').item;"
},
"typeVersion": 2
},
{
"id": "05c57490-c1ee-48f0-9e38-244c9a995e22",
"name": "Upload Screenshot of Email to Jira",
"type": "n8n-nodes-base.jira",
"position": [
3860,
480
],
"parameters": {
"issueKey": "={{ $('Create Jira Ticket').item.json.key }}",
"resource": "issueAttachment"
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "BZmmGUrNIsgM9fDj",
"name": "New Jira Cloud"
}
},
"typeVersion": 1
},
{
"id": "be02770d-a943-41f5-98a9-5c433a6a3dbf",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1420,
-107.36679523834897
],
"parameters": {
"color": 7,
"width": 792.3026315789474,
"height": 426.314163659402,
"content": "![Gmail](https://uploads.n8n.io/templates/gmail.png)\n## Gmail Integration and Data Extraction\n\nThis section of the workflow connects to a Gmail account using the **Gmail Trigger** node, capturing incoming emails in real-time, with checks performed every minute. Once an email is detected, its key components\u2014such as the subject, recipient, body, and headers\u2014are extracted and assigned to variables using the **Set Gmail Variables** node. These variables are structured for subsequent analysis and processing in later steps."
},
"typeVersion": 1
},
{
"id": "c1d2f691-669a-46de-9ef8-59ce4e6980c5",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1420,
380.6918768014301
],
"parameters": {
"color": 7,
"width": 792.3026315789474,
"height": 532.3344389880435,
"content": "![Gmail](https://uploads.n8n.io/templates/outlook.png)\n## Microsoft Outlook Integration and Email Header Processing\n\nThis section connects to a Microsoft Outlook account to monitor incoming emails using the **Microsoft Outlook Trigger** node, which checks for new messages every minute. Emails are then processed to retrieve detailed headers and body content via the **Retrieve Headers of Email** node. The headers are structured into a user-friendly format using the **Format Headers** code node, ensuring clarity for further analysis. Key details, including the email's subject, recipient, and body content, are assigned to variables with the **Set Outlook Variables** node for streamlined integration into subsequent workflow steps."
},
"typeVersion": 1
},
{
"id": "c189e2e0-9f51-4bc0-a483-8b7f0528be70",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2287.3684210526317,
46.18421052631584
],
"parameters": {
"color": 7,
"width": 580.4605263157906,
"height": 615.460526315789,
"content": "![hctiapi](https://uploads.n8n.io/templates/hctiapi.png)\n## HTML Screenshot Generation and Email Visualization\n\nThis section processes an email\u2019s HTML content to create a visual representation, useful for documentation or phishing detection workflows. The **Set Email Variables** node organizes the email's HTML body into a format ready for processing. The **Screenshot HTML** node sends this HTML content to the **hcti.io** API, which generates a screenshot of the email's layout. The **Retrieve Screenshot** node then fetches the image URL for further use in the workflow. This setup ensures that the email's appearance is preserved in a visually accessible format, simplifying review and reporting. Keep in mind however that this exposes the email content to a third party. If you self host n8n, you can deploy a cli tool to rasterize locally instead."
},
"typeVersion": 1
},
{
"id": "9076f9e9-f4fb-409a-9580-1ae459094c31",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2880,
123.72476075009968
],
"parameters": {
"color": 7,
"width": 507.82894736842223,
"height": 537.9199760920052,
"content": "![hctiapi](https://uploads.n8n.io/templates/openai.png)\n## AI-Powered Email Analysis with ChatGPT\n\nThis section leverages AI to analyze email content and headers for phishing indicators. The **ChatGPT Analysis** node utilizes the ChatGPT-4 model to review the email screenshot and associated metadata, including message headers. It generates a detailed report indicating whether the email might be a phishing attempt. The output is formatted specifically for Jira\u2019s wiki-style renderer, making it ready for seamless integration into ticketing workflows. This ensures thorough and automated email threat assessments."
},
"typeVersion": 1
},
{
"id": "ca2488af-e787-4675-802a-8b4f2d845376",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
3400,
122.88662032580646
],
"parameters": {
"color": 7,
"width": 692.434210526317,
"height": 529.5475902005091,
"content": "![hctiapi](https://uploads.n8n.io/templates/jira.png)\n## Automated Jira Ticket Creation for Phishing Reports\n\nThis section streamlines the process of reporting phishing emails by automatically creating detailed Jira tickets. The **Create Jira Ticket** node compiles email information, including the subject, recipient, body text, and ChatGPT's phishing analysis, into a structured ticket. The **Rename Screenshot** node ensures that the email screenshot file is appropriately labeled for attachment. Finally, the **Upload Screenshot of Email to Jira** node attaches the email\u2019s visual representation to the ticket, providing additional context for the security team. This integration ensures that phishing reports are logged with all necessary details, enabling efficient tracking and resolution."
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Gmail Trigger": {
"main": [
[
{
"node": "Set Gmail Variables",
"type": "main",
"index": 0
}
]
]
},
"Format Headers": {
"main": [
[
{
"node": "Set Outlook Variables",
"type": "main",
"index": 0
}
]
]
},
"Screenshot HTML": {
"main": [
[
{
"node": "Retrieve Screenshot",
"type": "main",
"index": 0
}
]
]
},
"ChatGPT Analysis": {
"main": [
[
{
"node": "Create Jira Ticket",
"type": "main",
"index": 0
}
]
]
},
"Rename Screenshot": {
"main": [
[
{
"node": "Upload Screenshot of Email to Jira",
"type": "main",
"index": 0
}
]
]
},
"Create Jira Ticket": {
"main": [
[
{
"node": "Rename Screenshot",
"type": "main",
"index": 0
}
]
]
},
"Retrieve Screenshot": {
"main": [
[
{
"node": "ChatGPT Analysis",
"type": "main",
"index": 0
}
]
]
},
"Set Email Variables": {
"main": [
[
{
"node": "Screenshot HTML",
"type": "main",
"index": 0
}
]
]
},
"Set Gmail Variables": {
"main": [
[
{
"node": "Set Email Variables",
"type": "main",
"index": 0
}
]
]
},
"Set Outlook Variables": {
"main": [
[
{
"node": "Set Email Variables",
"type": "main",
"index": 0
}
]
]
},
"Microsoft Outlook Trigger": {
"main": [
[
{
"node": "Retrieve Headers of Email",
"type": "main",
"index": 0
}
]
]
},
"Retrieve Headers of Email": {
"main": [
[
{
"node": "Format Headers",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,818 @@
{
"id": "LIAes1kWVZAWZBX2",
"meta": {
"instanceId": "31e69f7f4a77bf465b805824e303232f0227212ae922d12133a0f96ffeab4fef",
"templateCredsSetupCompleted": true
},
"name": "🎥 Analyze YouTube Video for Summaries, Transcripts & Content + Google Gemini AI",
"tags": [],
"nodes": [
{
"id": "6d96092e-a12e-42e7-9700-63d19c3f2403",
"name": "Config",
"type": "n8n-nodes-base.set",
"position": [
2760,
540
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "24e9b1c3-2955-4e0b-9b4b-a6b9d046fb72",
"name": "google_api_key",
"type": "string",
"value": "={{ $env.GOOGLE_API_KEY }}"
},
{
"id": "b6600a42-1b8d-486a-a51d-0868bc45452e",
"name": "youtube_url",
"type": "string",
"value": "=https://www.youtube.com/watch?v={{ $json[\"YouTube Video Id\"] }}"
},
{
"id": "ce9a9a40-5ae4-4106-ae61-0daba2ec185f",
"name": "prompt_type",
"type": "string",
"value": "={{ $json[\"Prompt Type\"] }}"
},
{
"id": "47094d96-2e89-4294-b6da-7ee66917bd98",
"name": "video_id",
"type": "string",
"value": "={{ $json[\"YouTube Video Id\"] }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "4b4373dd-6b54-41c5-a490-91ec78afdb0b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2320,
760
],
"parameters": {
"width": 300,
"height": 600,
"content": "### Prompt Options\n\n- **default**: Summarizes the video with emphasis on actionable insights, tools, strategies, and resources mentioned.\n\n- **transcribe**: Provides verbatim transcription of all spoken dialogue in the video without additional commentary.\n\n- **timestamps**: Creates a timestamped transcript of the video dialogue in [hh:mm:ss] format.\n\n- **summary**: Generates a concise bullet-point summary of the video's main points.\n\n- **scene**: Provides a comprehensive visual description of the video scene including setting, objects, people, lighting, colors, and camera techniques.\n\n- **clips**: Identifies shareable video segments with timestamps, transcripts, and explanations of their social media appeal.\n\n\n"
},
"typeVersion": 1
},
{
"id": "41605c14-9936-43f2-8f06-c411bfddda99",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
2660,
420
],
"parameters": {
"color": 7,
"width": 300,
"height": 300,
"content": "## Set Workflow Config Variables"
},
"typeVersion": 1
},
{
"id": "fdc9aeb2-35b4-4f33-9438-00a10f0cb0d5",
"name": "Get Video Audience MetaData",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
3440,
540
],
"parameters": {
"url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={{ $('Config').item.json.google_api_key }}",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({\n \"contents\": [\n {\n \"role\": \"user\",\n \"parts\": [\n {\n \"text\": $json.meta_prompt\n },\n { \n \"file_data\": { \n \"file_uri\": $('Config').item.json.youtube_url\n } \n }\n ]\n }\n ],\n \"generationConfig\": {\n \"temperature\": 0.2,\n \"topP\": 0.8,\n \"topK\": 40,\n \"maxOutputTokens\": 2048,\n },\n \"model\": \"gemini-1.5-flash\"\n}) }}\n",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2,
"alwaysOutputData": true
},
{
"id": "8cd500b5-7c78-4ae0-be2a-79862e599da3",
"name": "Compose Prompts",
"type": "n8n-nodes-base.set",
"position": [
2760,
980
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "90bd636a-aa19-4f6b-80b3-bb236f29b317",
"name": "content",
"type": "string",
"value": "=<default>\n<prompt>\nCreate a practical summary of this {{ $json.text.content_purpose }} about {{ $json.text.key_topics[0] }} for busy professionals in a {{ $json.text.video_tone }} tone seeking actionable takeaways. Use a structured format with primary and secondary bullets. Highlight specific tools, methodologies, and resources mentioned, including direct quotes when they provide valuable context. Provide only the response and avoid any preamble text or further explanations.\n</prompt>\n<model>\ngemini-1.5-flash\n</model>\n</default>\n\n<transcribe>\n<prompt>\nAct as a professional transcriptionist and transcribe this {{ $json.text.video_type }} video verbatim. Include only spoken dialogue, maintaining speech patterns and verbal tics. Omit background sounds, music, or descriptions. Provide only the response and avoid any preamble text or further explanations.\n</prompt>\n<model>\ngemini-1.5-flash\n</model>\n</transcribe>\n\n<timestamps>\n<prompt>\nCreate a professional timestamped transcript of this {{ $json.text.video_type }} video for {{ $json.text.primary_audience }}. Format each entry exactly as [hh:mm:ss] Dialogue. Capture speaker changes and significant pauses. Prioritize accuracy over completeness. Provide only the response and avoid any preamble text or further explanations.\n</prompt>\n<model>\ngemini-1.5-flash\n</model>\n</timestamps>\n\n<summary>\n<prompt>\nAnalyze this {{ $json.text.video_type }} video and create a concise summary (approximately 150 words) for {{ $json.text.primary_audience }}. Use nested bullets to organize key points. Include direct quotes only when they significantly enhance understanding. Begin immediately with the content. Provide only the response and avoid any preamble text or further explanations.\n</prompt>\n<model>\ngemini-1.5-flash\n</model>\n</summary>\n\n<scene>\n<prompt>\nAs a professional video production analyst, describe this scene comprehensively for {{ $json.text.content_purpose }}. Focus on setting, objects, people, lighting, colors, and camera techniques that contribute most to the scene's impact. Be specific with visual details that would matter to {{ $json.text.primary_audience }}. Provide only the response and avoid any preamble text or further explanations.\n</prompt>\n<model>\ngemini-1.5-flash\n</model>\n</scene>\n\n<clips>\n<prompt>\nIdentify 3-5 high-engagement segments from this video specifically for {{ $json.text.best_social_platforms }} users interested in {{ $json.text.key_topics }}. For each clip, provide exact timestamps [hh:mm:ss-hh:mm:ss], verbatim transcript, and a compelling rationale focused on virality potential (shares, comments, saves). Provide only the response and avoid any preamble text or further explanations.\n</prompt>\n<model>\ngemini-1.5-flash\n</model>\n</clips>\n\n\n\n\n"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "8f80f2f1-c46c-45ef-8468-0eb7dda2814e",
"name": "Extract MetaData Object",
"type": "n8n-nodes-base.set",
"position": [
3780,
540
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "e1a2e48b-0190-4f13-bf3f-8e74cbc8ab65",
"name": "text",
"type": "object",
"value": "={{ $json.candidates[0].content.parts[0].text.replaceAll('```json', '').replaceAll('```', '') }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "b1065050-1a32-423e-b15f-0cef3f377ae6",
"name": "Get Prompt by Prompt Type",
"type": "n8n-nodes-base.code",
"position": [
3100,
980
],
"parameters": {
"jsCode": "// Get the XML content from the input\nconst xmlContent = $input.first().json.content;\n\n// Get the tag name from the Config node\nconst tagName = $node[\"Config\"].json.prompt_type;\n\n// Create regex patterns for both prompt and model within the main tag\nconst promptRegex = new RegExp(`<${tagName}>[\\\\s\\\\S]*?<prompt>([\\\\s\\\\S]*?)</prompt>[\\\\s\\\\S]*?</${tagName}>`, \"i\");\nconst modelRegex = new RegExp(`<${tagName}>[\\\\s\\\\S]*?<model>([\\\\s\\\\S]*?)</model>[\\\\s\\\\S]*?</${tagName}>`, \"i\");\n\n// Use the match method to apply the regex patterns\nconst promptMatch = xmlContent.match(promptRegex);\nconst modelMatch = xmlContent.match(modelRegex);\n\n// Create the output item with proper structure\nlet outputItem = {\n json: {\n prompt: null,\n model: null\n }\n};\n\n// Extract prompt content if found\nif (promptMatch) {\n outputItem.json.prompt = promptMatch[1].trim();\n}\n\n// Extract model content if found\nif (modelMatch) {\n outputItem.json.model = modelMatch[1].trim();\n}\n\n// Return the properly structured item\nreturn [outputItem];\n"
},
"typeVersion": 2
},
{
"id": "a66e5240-ad86-47df-8a23-d45eb31e41ce",
"name": "Define Audience Meta Prompt",
"type": "n8n-nodes-base.set",
"position": [
3100,
540
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "c3524064-c7fb-4f63-8421-f18f35cf5556",
"name": "meta_prompt",
"type": "string",
"value": "=Analyze this YouTube video and extract key metadata to help optimize AI-generated content about it. Return ONLY a valid JSON object with the following fields:\n\n{\n \"video_type\": \"The video format/genre (tutorial, vlog, review, interview, etc.)\",\n \"primary_audience\": \"The main target audience based on content, language, and presentation style\",\n \"secondary_audiences\": [\"List of 2-3 other potential audience segments\"],\n \"content_purpose\": \"The main goal of the video (educate, entertain, persuade, etc.)\",\n \"key_topics\": [\"3-5 main topics or themes covered\"],\n \"best_social_platforms\": [\"2-3 platforms where clips would perform best\"],\n \"video_tone\": \"Overall tone (professional, casual, humorous, serious, etc.)\",\n \"engagement_drivers\": [\"2-3 aspects that would drive viewer engagement\"]\n}\n\nFocus on objective analysis of visual and verbal cues. Do not include subjective quality assessments.\n\nReturn your response as a valid JSON object without any markdown formatting, code blocks, or explanatory text. Always remove all ```json and ``` from final response. Avoid all preamble or further explanation.\n"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "cdb4ec99-37ac-45ae-9d5c-80851e992488",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
3340,
420
],
"parameters": {
"color": 3,
"width": 300,
"height": 300,
"content": "## Analyze YouTube Video for Audience MetaData"
},
"typeVersion": 1
},
{
"id": "904938e4-4242-4a90-b124-fe0ba10ee4ec",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
3340,
860
],
"parameters": {
"color": 3,
"width": 300,
"height": 300,
"content": "## Get YouTube Information by Prompt Type"
},
"typeVersion": 1
},
{
"id": "942789bd-ab7c-432c-9916-f1fdb5344e1e",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
3000,
420
],
"parameters": {
"color": 7,
"width": 300,
"height": 300,
"content": "## Define Audience Meta Prompt"
},
"typeVersion": 1
},
{
"id": "4c032c02-3eb7-48d1-8a74-75db0e02fe24",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
3680,
420
],
"parameters": {
"color": 7,
"width": 300,
"height": 300,
"content": "## Extract MetaData Object"
},
"typeVersion": 1
},
{
"id": "58e23d26-8cb1-4819-9d29-0658e8b7a95b",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
2660,
860
],
"parameters": {
"color": 7,
"width": 300,
"height": 300,
"content": "## Compose the Prompts with Audience MetaData"
},
"typeVersion": 1
},
{
"id": "ecadd3a9-7ce4-433d-9a04-76ebe4ba3875",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
3000,
860
],
"parameters": {
"color": 7,
"width": 300,
"height": 300,
"content": "## Get Prompt by Prompt Type"
},
"typeVersion": 1
},
{
"id": "a00048cb-e30c-4b14-9dd3-b986d2ee5f9c",
"name": "Get YouTube Information by Prompt Type",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
3440,
980
],
"parameters": {
"url": "=https://generativelanguage.googleapis.com/v1beta/models/{{ $json.model }}:generateContent?key={{$('Config').item.json.google_api_key }}",
"method": "POST",
"options": {},
"jsonBody": "={\n \"contents\": [{\n \"parts\": [\n { \"text\": {{ JSON.stringify($json.prompt) }} },\n { \"file_data\": { \n \"file_uri\": \"{{ $('Config').item.json.youtube_url }}\" \n } \n }\n ]\n }]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2,
"alwaysOutputData": true
},
{
"id": "a071b040-9091-4081-aedd-d8e8b9166568",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
2320,
420
],
"parameters": {
"color": 4,
"width": 300,
"height": 300,
"content": "## 👍Try Me!\nYouTube Video Id: wBuULAoJxok"
},
"typeVersion": 1
},
{
"id": "e243fe41-26fe-48d3-b215-20e033a0c0aa",
"name": "Save to Google Drive as Text File",
"type": "n8n-nodes-base.googleDrive",
"position": [
3780,
1320
],
"parameters": {
"name": "={{ $('Start Workflow').item.json['YouTube Video Id'] }} - {{ $now }}",
"content": "={{ $('Start Workflow').item.json['YouTube Video Id'] }} - {{ $now }}\n\n{{ $('Extract MetaData Object').item.json.text.key_topics[0] }}\n{{ $('Extract MetaData Object').item.json.text.content_purpose }}\n{{ $('Extract MetaData Object').item.json.text.primary_audience }}\n\n{{ $json.candidates[0].content.parts[0].text }}\n\nVideo Details:\n{{ $('Merge').item.json.items.toJsonString() }}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {},
"folderId": {
"__rl": true,
"mode": "list",
"value": "root",
"cachedResultName": "/ (Root folder)"
},
"operation": "createFromText"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "UhdXGYLTAJbsa0xX",
"name": "Google Drive account"
}
},
"typeVersion": 3
},
{
"id": "87ad7860-a364-406a-999b-5b9f9ef356e0",
"name": "Send to Gmail as HTML",
"type": "n8n-nodes-base.gmail",
"position": [
4120,
1320
],
"webhookId": "ccf34c87-14a3-4103-96fb-595cf9fa0636",
"parameters": {
"sendTo": "={{ $env.EMAIL_ADDRESS_JOE }}",
"message": "=<p>{{ $('Merge').item.json.items[0].snippet.title }}</p>\n<p>{{ $('Merge').item.json.items[0].id }}</p>\n\n<img src=\"{{ $('Merge').item.json.items[0].snippet.thumbnails.medium.url }}\">\n\n{{ $json.data }}",
"options": {
"appendAttribution": false
},
"subject": "={{ $('Start Workflow').item.json['YouTube Video Id'] }} - {{ $('Extract MetaData Object').item.json.text.key_topics[0] }}"
},
"credentials": {
"gmailOAuth2": {
"id": "1xpVDEQ1yx8gV022",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "d42e0de6-560e-4aa0-b2a5-8b79d84b660a",
"name": "Convert Markdown to HTML",
"type": "n8n-nodes-base.markdown",
"position": [
3780,
980
],
"parameters": {
"mode": "markdownToHtml",
"options": {},
"markdown": "={{ $json.candidates[0].content.parts[0].text }}"
},
"typeVersion": 1
},
{
"id": "3f5cac85-ee4d-45a5-9a95-07fc6e195bd8",
"name": "Provide YouTube Information to User as HTML",
"type": "n8n-nodes-base.form",
"position": [
4120,
980
],
"webhookId": "49b5f9c9-e4c2-4cc4-b01c-c27b1cdba918",
"parameters": {
"operation": "completion",
"respondWith": "showText",
"responseText": "=<img src=\"{{ $('Merge').item.json.items[0].snippet.thumbnails.medium.url }}\">\n\n{{ $json.data }}\n"
},
"typeVersion": 1
},
{
"id": "8f0ed7d9-9b78-49d2-858a-34418e1ee517",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
3340,
320
],
"parameters": {
"color": 5,
"width": 300,
"height": 100,
"content": "## Google Generative Language API"
},
"typeVersion": 1
},
{
"id": "271816a9-9e1a-4b4d-afe5-94f3023c9337",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
3340,
760
],
"parameters": {
"color": 5,
"width": 300,
"height": 100,
"content": "## Google Generative Language API"
},
"typeVersion": 1
},
{
"id": "ba68bd32-0f4b-4ce0-9af6-3dc87b8ae5ea",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
3680,
860
],
"parameters": {
"color": 7,
"width": 300,
"height": 300,
"content": "## Convert Markdown to HTML"
},
"typeVersion": 1
},
{
"id": "b69d97a9-748e-430a-b1af-5befecb226a3",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
3680,
1200
],
"parameters": {
"color": 7,
"width": 300,
"height": 300,
"content": "## Save YouTube Information to Google Drive"
},
"typeVersion": 1
},
{
"id": "3a0bca9c-56da-4425-b719-3e2ccb1cd1d8",
"name": "Sticky Note13",
"type": "n8n-nodes-base.stickyNote",
"position": [
4020,
1200
],
"parameters": {
"color": 7,
"width": 300,
"height": 300,
"content": "## Email YouTube Information"
},
"typeVersion": 1
},
{
"id": "004154f2-ac1a-4b79-a5f8-3af0959cc3ce",
"name": "Sticky Note14",
"type": "n8n-nodes-base.stickyNote",
"position": [
4020,
860
],
"parameters": {
"color": 4,
"width": 300,
"height": 300,
"content": "## Provide YouTube Information in Completion Form"
},
"typeVersion": 1
},
{
"id": "f815a2f5-2e12-40bf-8849-30429344afae",
"name": "Sticky Note15",
"type": "n8n-nodes-base.stickyNote",
"position": [
2280,
-120
],
"parameters": {
"color": 7,
"width": 2080,
"height": 1660,
"content": "# 🎥 Analyze YouTube Video for Summaries, Transcripts & Content + Google Gemini"
},
"typeVersion": 1
},
{
"id": "dbbae73b-735b-4bb8-bcad-6266c08d9fae",
"name": "Start Workflow",
"type": "n8n-nodes-base.formTrigger",
"position": [
2420,
540
],
"webhookId": "92148b0b-bbf7-4ce9-80a2-768207adee7b",
"parameters": {
"options": {},
"formTitle": "Extract Information from YouTube Videos",
"formFields": {
"values": [
{
"fieldType": "dropdown",
"fieldLabel": "Prompt Type",
"fieldOptions": {
"values": [
{
"option": "default"
},
{
"option": "transcribe"
},
{
"option": "timestamps"
},
{
"option": "summary"
},
{
"option": "scene"
},
{
"option": "clips"
}
]
},
"requiredField": true
},
{
"fieldLabel": "YouTube Video Id",
"placeholder": "wBuULAoJxok",
"requiredField": true
}
]
},
"responseMode": "lastNode",
"formDescription": "This workflow allows you to extract various types of actionable information from YouTube videos that is audience specific using dynamically composed prompts."
},
"typeVersion": 2.2
},
{
"id": "c63d236c-99d5-43f6-825e-836ddd41ad6f",
"name": "Create YouTube API URL",
"type": "n8n-nodes-base.code",
"position": [
3100,
100
],
"parameters": {
"jsCode": "// Define the base URL for the YouTube Data API\nconst BASE_URL = 'https://www.googleapis.com/youtube/v3/videos';\n\n// Get the first input item\nconst item = $input.first();\n\n// Extract the videoId and google_api_key from the input JSON\nconst VIDEO_ID = item.json.video_id;\nconst GOOGLE_API_KEY = item.json.google_api_key; // Dynamically retrieve API key\n\nif (!VIDEO_ID) {\n throw new Error('The video ID parameter is empty.');\n}\n\nif (!GOOGLE_API_KEY) {\n throw new Error('The Google API Key is missing.');\n}\n\n// Construct the API URL with the video ID and dynamically retrieved API key\nconst youtubeUrl = `${BASE_URL}?part=snippet,contentDetails,status,statistics,player,topicDetails&id=${VIDEO_ID}&key=${GOOGLE_API_KEY}`;\n\n// Return the constructed URL\nreturn [\n {\n json: {\n youtubeUrl: youtubeUrl,\n },\n },\n];\n"
},
"typeVersion": 2
},
{
"id": "17daf9d1-4bee-4632-b929-0696e71b9fa2",
"name": "Get YouTube Video Details",
"type": "n8n-nodes-base.httpRequest",
"position": [
3440,
100
],
"parameters": {
"url": "={{ $json.youtubeUrl }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "42b45f4c-9447-4ffa-ae7f-ffa68de395ba",
"name": "Sticky Note16",
"type": "n8n-nodes-base.stickyNote",
"position": [
3340,
-20
],
"parameters": {
"color": 3,
"width": 300,
"height": 300,
"content": "## Get YouTube Video Details"
},
"typeVersion": 1
},
{
"id": "3f3a5e5a-5c15-42a0-81d5-53248b76495e",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
4100,
540
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3
},
{
"id": "377870dd-7dfe-49dc-a444-67017a97e8c8",
"name": "Sticky Note17",
"type": "n8n-nodes-base.stickyNote",
"position": [
3000,
-20
],
"parameters": {
"color": 7,
"width": 300,
"height": 300,
"content": "## Create YouTube API URL"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "45041f00-7c30-4490-aa2b-807bcb91ca2b",
"connections": {
"Merge": {
"main": [
[
{
"node": "Compose Prompts",
"type": "main",
"index": 0
}
]
]
},
"Config": {
"main": [
[
{
"node": "Create YouTube API URL",
"type": "main",
"index": 0
},
{
"node": "Define Audience Meta Prompt",
"type": "main",
"index": 0
}
]
]
},
"Start Workflow": {
"main": [
[
{
"node": "Config",
"type": "main",
"index": 0
}
]
]
},
"Compose Prompts": {
"main": [
[
{
"node": "Get Prompt by Prompt Type",
"type": "main",
"index": 0
}
]
]
},
"Create YouTube API URL": {
"main": [
[
{
"node": "Get YouTube Video Details",
"type": "main",
"index": 0
}
]
]
},
"Extract MetaData Object": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Convert Markdown to HTML": {
"main": [
[
{
"node": "Send to Gmail as HTML",
"type": "main",
"index": 0
},
{
"node": "Provide YouTube Information to User as HTML",
"type": "main",
"index": 0
}
]
]
},
"Get Prompt by Prompt Type": {
"main": [
[
{
"node": "Get YouTube Information by Prompt Type",
"type": "main",
"index": 0
}
]
]
},
"Get YouTube Video Details": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Define Audience Meta Prompt": {
"main": [
[
{
"node": "Get Video Audience MetaData",
"type": "main",
"index": 0
}
]
]
},
"Get Video Audience MetaData": {
"main": [
[
{
"node": "Extract MetaData Object",
"type": "main",
"index": 0
}
]
]
},
"Get YouTube Information by Prompt Type": {
"main": [
[
{
"node": "Convert Markdown to HTML",
"type": "main",
"index": 0
},
{
"node": "Save to Google Drive as Text File",
"type": "main",
"index": 0
}
]
]
}
}
}

View File

@@ -0,0 +1,284 @@
{
"id": "As8TxF3PjyXygc0o",
"meta": {
"instanceId": "a059b3dfdab56aa587cc6a2c8635f6f2700cf0c7064dbfb5981c26f7ad9eab88"
},
"name": "🧹 Archive (delete) duplicate items from a Notion database",
"tags": [],
"nodes": [
{
"id": "b758ce01-7f5e-4bdc-a4c3-6c00d6bc022a",
"name": "Every day",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-180,
660
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.2
},
{
"id": "1ca45ba5-4635-4710-9807-26f22d535059",
"name": "Get pages from database",
"type": "n8n-nodes-base.notion",
"position": [
60,
560
],
"parameters": {
"options": {},
"resource": "databasePage",
"operation": "getAll",
"returnAll": true,
"databaseId": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"typeVersion": 2.2
},
{
"id": "ef8c8cfa-12fb-4fb9-8552-09f69f1f358d",
"name": "Aggregate all items",
"type": "n8n-nodes-base.aggregate",
"position": [
500,
560
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "pages"
},
"typeVersion": 1
},
{
"id": "f1c3c0ad-f904-4d63-a131-0b045a21ce04",
"name": "Format items properly",
"type": "n8n-nodes-base.set",
"position": [
280,
560
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "309a1e9b-f3e9-41a0-aadb-aa74bc993fe9",
"name": "id",
"type": "string",
"value": "={{ $json.id }}"
},
{
"id": "ad6e8fa9-9872-456d-971f-3cef940b7d8a",
"name": "property_to_check",
"type": "string",
"value": "=\"SET YOUR PROPERTY HERE\""
}
]
}
},
"typeVersion": 3.4
},
{
"id": "5d39d3b7-604d-4aca-bf9a-3bb09bddad66",
"name": "Filter duplicates",
"type": "n8n-nodes-base.code",
"position": [
720,
560
],
"parameters": {
"jsCode": "const inputData = $input.first().json.pages;\n\nconst seen = new Set();\nconst duplicates = new Map();\n\ninputData.forEach(item => {\n const propertyValue = item.property_to_check;\n if (seen.has(propertyValue)) {\n duplicates.set(propertyValue, item);\n } else {\n seen.add(propertyValue);\n }\n});\n\nconst output = Array.from(duplicates.values()).map(item => ({ json: item }));\n\nreturn output;"
},
"typeVersion": 2
},
{
"id": "55a8f0eb-702b-4056-a28c-96a7ade7c2cd",
"name": "Archive pages",
"type": "n8n-nodes-base.notion",
"position": [
920,
560
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"operation": "archive"
},
"typeVersion": 2.2
},
{
"id": "2c9655ea-401c-410b-a4b1-b001ae6dbe4b",
"name": "When a page is added to the database",
"type": "n8n-nodes-base.notionTrigger",
"position": [
-180,
460
],
"parameters": {
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"databaseId": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"typeVersion": 1
},
{
"id": "672b647c-d009-45c3-b69e-6dfe85992e15",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
0
],
"parameters": {
"width": 860,
"height": 460,
"content": "## 🧹 Archive (delete) extra duplicate items from Notion database\n### ABOUT THIS WORKFLOW\nThis n8n workflow automatically gets duplicate database pages based on a property and \"archives\" them (equivalent to deleting them), leaving just one copy.\n\n### SETUP\n1. Create a Notion credential.\n2. Add it to the Notion nodes, selecting the appropriate database.\n3. In the \"Set\" node (\"Format items properly\"), specify a reference to the property you want to check for duplicates and assign it to the field \"property_to_check\". I recommend using the n8n property drag-and-drop feature.\n4. Enjoy!\n\n### ABOUT THE TRIGGERS\nThis workflow offers two possible triggers by default:\n- Run every time a page is added to the database.\n- Run every day.\n\n\nYou can enable, disable, or modify these triggers as you like."
},
"typeVersion": 1
},
{
"id": "83881bd3-60e3-40be-a469-0b7acb21d2be",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-240,
400
],
"parameters": {
"color": 5,
"width": 220,
"height": 420,
"content": "## TRIGGERS"
},
"typeVersion": 1
},
{
"id": "cd4b8717-19ae-42d6-ac87-bbdd071dd774",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
480
],
"parameters": {
"color": 6,
"width": 860,
"height": 340,
"content": "## GET DUPLICATE PAGES"
},
"typeVersion": 1
},
{
"id": "087fb844-2241-4ed9-976d-9bdc7ccd8aa5",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
880,
400
],
"parameters": {
"color": 3,
"width": 180,
"height": 420,
"content": "## ARCHIVE (DELETE)"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "fdd2e5ad-4ff5-4432-a5f9-ebbeb1a1a6cb",
"connections": {
"Every day": {
"main": [
[
{
"node": "Get pages from database",
"type": "main",
"index": 0
}
]
]
},
"Filter duplicates": {
"main": [
[
{
"node": "Archive pages",
"type": "main",
"index": 0
}
]
]
},
"Aggregate all items": {
"main": [
[
{
"node": "Filter duplicates",
"type": "main",
"index": 0
}
]
]
},
"Format items properly": {
"main": [
[
{
"node": "Aggregate all items",
"type": "main",
"index": 0
}
]
]
},
"Get pages from database": {
"main": [
[
{
"node": "Format items properly",
"type": "main",
"index": 0
}
]
]
},
"When a page is added to the database": {
"main": [
[
{
"node": "Get pages from database",
"type": "main",
"index": 0
}
]
]
}
}
}

Some files were not shown because too many files have changed in this diff Show More