Workflow: develop a modular n8n workflow that supports Code Schedule Create Scheduled
Integrations: code, function, httpRequest, merge, noOp, notion, scheduleTrigger, splitInBatches, stickyNote, switch

{"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": "// \u7372\u53d6 API \u8fd4\u56de\u7684\u5b8c\u6574\u8cc7\u6599 (\u5047\u8a2d\u53ea\u6709\u4e00\u500b \"data\" \u9663\u5217)\nconst allData = items[0].json.data;\n\n// \u904e\u6ffe\u7b26\u5408\u689d\u4ef6\u7684\u8cbc\u6587\uff1a\n// \u689d\u4ef6 1: media_type = \"TEXT_POST\" \u6216 \"VIDEO\"\n// \u689d\u4ef6 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// \u62bd\u53d6\u6240\u9700\u7684\u6b04\u4f4d\uff1aid, 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// \u5c07\u7d50\u679c\u4ee5 n8n \u6240\u9700\u683c\u5f0f\u8f38\u51fa\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": "// \u78ba\u4fdd items \u662f\u5426\u6709\u5167\u5bb9\nif (!items || items.length === 0) {\n  console.log('No items found');\n  return [];\n}\n\n// \u53d6\u5f97\u8f38\u5165\u6578\u64da\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// \u904e\u6ffe\u51fa username \u70ba yourThreadsName \u7684\u8cc7\u6599\nconst filteredPosts = inputData.data.filter(post => post.username === 'geekaz');\nconsole.log('Filtered posts count:', filteredPosts.length);\n\n// \u8655\u7406\u6bcf\u500b post\uff0c\u63d0\u53d6\u6240\u9700\u7684\u8cc7\u6599\nconst processedData = filteredPosts.map(post => {\n  // \u521d\u59cb\u5316 mediaUrls\uff0c\u7528\u4f86\u5b58\u653e\u6240\u6709\u7684 media_url\n  let mediaUrls = [];\n\n  // \u5982\u679c\u6709 children\uff0c\u5247\u63d0\u53d6 children \u88e1\u7684 media_url\n  if (post.children?.data && Array.isArray(post.children.data)) {\n    mediaUrls = post.children.data\n      .map(child => child.media_url) // \u63d0\u53d6\u6bcf\u500b child \u7684 media_url\n      .filter(url => url); // \u904e\u6ffe\u6389 undefined \u6216 null \u7684 URL\n  } else if (post.media_url) {\n    // \u5982\u679c\u6c92\u6709 children\uff0c\u4f7f\u7528\u6700\u5916\u5c64\u7684 media_url\n    mediaUrls.push(post.media_url);\n  }\n\n  // \u8fd4\u56de\u6bcf\u500b post \u7684\u8655\u7406\u5f8c\u7d50\u679c\n  return {\n    text: post.text || '',\n    media_urls: mediaUrls\n  };\n});\n\nconsole.log('Processed data:', JSON.stringify(processedData, null, 2));\n\n// \u5c07\u7d50\u679c\u8f49\u63db\u70ba n8n \u6240\u9700\u683c\u5f0f\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": "// \u78ba\u4fdd items \u662f\u5426\u6709\u5167\u5bb9\nif (!items || items.length === 0) {\n  return [];\n}\n\n// \u78ba\u4fdd items \u7684\u8cc7\u6599\u7d50\u69cb\u662f\u6b63\u78ba\u7684\nconst allData = items.map(item => item.json);\n\nconst processedData = allData.map(post => {\n  // \u521d\u59cb\u5316 mediaUrls\uff0c\u7528\u4f86\u5b58\u653e\u6240\u6709\u7684 media_url\n  let mediaUrls = [];\n\n  // \u5982\u679c\u6709 children\uff0c\u5247\u63d0\u53d6 children \u88e1\u7684 media_url\n  if (post.children?.data && Array.isArray(post.children.data)) {\n    mediaUrls = post.children.data\n      .map(child => child.media_url) // \u63d0\u53d6\u6bcf\u500b child \u7684 media_url\n      .filter(url => url); // \u904e\u6ffe\u6389 undefined \u6216 null \u7684 URL\n  } else if (post.media_url) {\n    // \u5982\u679c\u6c92\u6709 children\uff0c\u4f7f\u7528\u6700\u5916\u5c64\u7684 media_url\n    mediaUrls.push(post.media_url);\n  }\n\n  // \u8fd4\u56de\u6bcf\u500b post \u7684\u8655\u7406\u5f8c\u7d50\u679c\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, // \u5305\u542b\u6240\u6709\u7684\u5a92\u9ad4 URL\n    permalink: post.permalink || null,\n  };\n});\n\n// \u5c07\u7d50\u679c\u8f49\u63db\u70ba n8n \u6240\u9700\u683c\u5f0f\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": "// \u6aa2\u67e5\u8f38\u5165\u662f\u5426\u5b58\u5728\nif (!items?.length) return [{ json: { threadsIds: [] } }];\n\n// \u53d6\u5f97\u6240\u6709\u9801\u9762\nconst pages = items.map(item => item.json).flat();\nconsole.log('Number of pages:', pages.length);\n\n// \u63d0\u53d6\u6240\u6709 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// \u5c07\u7d50\u679c\u8f49\u63db\u70ba n8n \u6240\u9700\u683c\u5f0f\nreturn [{ json: { threadsIds } }];"}, "typeVersion": 1}, {"id": "83fdaf18-47e7-4b1c-8f1b-523f87a439f3", "name": "Compare IDs", "type": "n8n-nodes-base.function", "position": [1500, 100], "parameters": {"functionCode": "// \u6aa2\u67e5\u8f38\u5165\u662f\u5426\u5b58\u5728\nif (!items?.length) return [{ json: { isExist: false } }];\n\n// \u5f9e Threads Post \u7bc0\u9ede\u53d6\u5f97 ID\nconst newId = $('Threads Post').last().json.id;\nconst existingIds = $json.threadsIds || [];\n\n// \u6aa2\u67e5\u662f\u5426\u91cd\u8907\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": "// \u78ba\u4fdd items \u662f\u5426\u6709\u5167\u5bb9\nif (!items || items.length === 0) {\n  console.log('No items found');\n  return [];\n}\n\n// \u53d6\u5f97\u6240\u6709\u8cbc\u6587\nconst posts = items.map(item => item.json).flat();\nconsole.log('Number of posts:', posts.length);\n\n// \u53d6\u5f97\u7b2c\u4e00\u7bc7\u8cbc\u6587\u7684\u57fa\u672c\u8cc7\u8a0a\nconst firstPost = posts[0] || {};\n\n// \u751f\u6210 blocks \u7d50\u69cb\nconst blocks = [];\n\n// \u8655\u7406\u6bcf\u500b\u8cbc\u6587\nposts.forEach(post => {\n  // \u5982\u679c\u6709\u6587\u5b57\uff0c\u52a0\u5165\u6587\u5b57\u5340\u584a\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  // \u5982\u679c\u6709\u5a92\u9ad4\u9023\u7d50\uff0c\u52a0\u5165 embed \u5340\u584a\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// \u5408\u4f75\u57fa\u672c\u8cc7\u8a0a\u548c blocks\nconst combinedPost = {\n  id: firstPost.id || '',\n  permalink: firstPost.permalink || '',\n  username: firstPost.username || '',\n  timestamp: firstPost.timestamp || '',\n  blocks\n};\n\n// \u5c07\u7d50\u679c\u8f49\u63db\u70ba n8n \u6240\u9700\u683c\u5f0f\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/\u6559\u5b78 [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 / \u5728\u6b64\u653e\u4e0a\u525b\u525b\u62ff\u5230\u7684\u9577\u671f 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 / \u8acb\u5148\u900f\u904e\u4e0a\u65b9\u6559\u5b78\u7372\u53d6 Threads ID\n\nSet the time of the Post you wanna get / \u8a2d\u7f6e\u6293\u53d6\u7684\u8cbc\u6587\u6642\u9593\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 \u70ba\u6293\u53d6\u65e5\u671f\u4e4b\u5f8c\u7684\u8cbc\u6587\n\nuntil is scrape the post before the date /\nuntil \u70ba\u6293\u53d6\u65e5\u671f\u4e4b\u524d\u7684\u8cbc\u6587\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\u6709\u4efb\u4f55\u554f\u984c\u90fd\u6b61\u8fce\u900f\u904e Instagram \u79c1\u8a0a\u8a62\u554f\uff01"}, "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}]]}}}