LinkedIn帖子生成器

高级

这是一个Content Creation、Multimodal AI领域的自动化工作流,包含 39 个节点。主要使用 Code、Merge、GoogleDrive、GoogleSheets、ScheduleTrigger 等节点。 LinkedIn内容工厂:使用GPT-5、DALL·E和Google Sheets自动生成帖子

前置要求
  • Google Drive API 凭证
  • Google Sheets API 凭证
  • OpenAI API Key
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "vnFLiG5oPRaa0GLT",
  "meta": {
    "instanceId": "4864679018d565a892ca43ce23dcbf870b964133cd1081846447be064da60377",
    "templateCredsSetupCompleted": true
  },
  "name": "Linkedin 帖子生成器",
  "tags": [],
  "nodes": [
    {
      "id": "5d8195ac-0e4d-46a2-b215-e76088726704",
      "name": "01_自动开始",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -336,
        96
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 10
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "3606e466-c0e3-47e6-aac8-000cd2960fab",
      "name": "02_构建简报",
      "type": "n8n-nodes-base.code",
      "position": [
        -112,
        96
      ],
      "parameters": {
        "jsCode": "return [{\n  json: {\n    brief: {\n      audience: \"Product Managers, Senior Product Managers, founders, RMG players, startup & AI enthusiasts, Product Management enthusiasts, AI agent enthusiasts, AI product Management, AI workflow Enthusiasts, AI Productivity hackers\",\n      geography: \"Global + India\",\n      goals: [\"Reach/Awareness\", \"Authority building\", \"Personal branding\"],\n      cta: [\"Comments\", \"Share\", \"Follow\"],\n      topics: \"Anything relevant to target audience (startups, AI, fintech, gaming, RMG, etc.)\",\n      angles: [\"Educational\", \"Contrarian\", \"Storytelling\", \"Framework-based\", \"Case study\"],\n      tone: \"Sharp\",\n      constraints: \"Avoid too much jargon, include data & sources\",\n      link: null,\n      avoid_duplication: false,\n      trigger: \"cron\"\n    }\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "d27e676c-fe72-41d0-ba06-64a31b0c2170",
      "name": "03_生成想法",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        96,
        96
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-chat-latest",
          "cachedResultName": "GPT-5-CHAT-LATEST"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are an AI that generates LinkedIn post ideas based on a given brief."
            },
            {
              "content": "=Audience: {{$json[\"brief\"][\"audience\"]}}\nGeography: {{$json[\"brief\"][\"geography\"]}}\nGoals: {{$json[\"brief\"][\"goals\"].join(\", \")}}\nCall-to-action: {{$json[\"brief\"][\"cta\"].join(\", \")}}\nTopics: {{$json[\"brief\"][\"topics\"]}}\nAngles: {{$json[\"brief\"][\"angles\"].join(\", \")}}\nTone: {{$json[\"brief\"][\"tone\"]}}\nConstraints: {{$json[\"brief\"][\"constraints\"]}}\n\nTask:\n- Give me 5 raw LinkedIn post ideas (short titles or one-line descriptions only).\n- Do not write the full post.\n- Make them relevant, engaging, and fit the brief.\n- Avoid repeating themes from the last 60 days.\n"
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "id": "X9ubpsApQNgfKwnt",
          "name": "OpenAi account 2"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "2c5ad466-a960-421d-b65e-7343957d354b",
      "name": "合并",
      "type": "n8n-nodes-base.merge",
      "position": [
        -304,
        512
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "e2fc1a59-8082-4b57-b907-62b6f9ef6ad4",
      "name": "05_挑选想法AI",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -144,
        512
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5",
          "cachedResultName": "GPT-5"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are an editorial board for a LinkedIn thought-leadership account.\nYour job: pick ONE best idea from a shortlist of 3–8 ideas. \nYou must explain WHY and also RANK all candidates.\n\nConstraints:\n- Keep audience, geography, goals, tone, and constraints from the brief.\n- Prefer ideas with strong comment potential, data-backed, contrarian or framework-based angles.\n- Be very strict with clarity: avoid jargon-heavy picks.\n- Return STRICT JSON only, no extra text.\n"
            },
            {
              "content": "=BRIEF:\n{{ JSON.stringify($item(0,\"02_BuildBrief\").$json.brief) }}\n\nCANDIDATE_IDEAS (after fuzzy-dedupe):\n{{ JSON.stringify($json.message.content.kept_fuzzy) }}\n\nTASK:\n1. Pick ONE best idea → \"chosenIdea\"\n2. Explain briefly why → \"why\"\n3. Rank ALL ideas with scores (1–10) and short notes → \"ranked\"\n\nOUTPUT SCHEMA:\n{\n  \"chosenIdea\": \"<the one>\",\n  \"why\": \"<reason>\",\n  \"ranked\": [\n    { \"idea\": \"<candidate>\", \"score\": <int>, \"note\": \"<short reason>\" }\n  ]\n}\n"
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "id": "X9ubpsApQNgfKwnt",
          "name": "OpenAi account 2"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "9aecf953-5e89-4434-b430-077ec3a598e1",
      "name": "05b_合并简报和挑选",
      "type": "n8n-nodes-base.merge",
      "position": [
        144,
        512
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "6881cd94-a7ed-43a5-9824-9594a9631596",
      "name": "06_生成帖子",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        496,
        512
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5",
          "cachedResultName": "GPT-5"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are an expert LinkedIn content strategist who writes sharp, scroll-stopping posts for Product Managers, founders, and AI enthusiasts. \nYou always:\n- Write in a clear, concise, and engaging style.\n- Use strong hooks, frameworks, and storytelling.\n- Avoid jargon and filler words.\n- Support with credible data/examples when relevant.\n- End with a simple call-to-action (CTA).\n"
            },
            {
              "content": "=Generate a LinkedIn post draft.\n\n### Inputs:\n- Chosen Idea: {{$json.chosenIdea}}\n- Why it was chosen: {{$json.why}}\n- Brief:\n  - Audience: {{$json.brief.audience}}\n  - Geography: {{$json.brief.geography}}\n  - Goals: {{$json.brief.goals}}\n  - CTA options: {{$json.brief.cta}}\n  - Topics: {{$json.brief.topics}}\n  - Angles: {{$json.brief.angles}}\n  - Tone: {{$json.brief.tone}}\n  - Constraints: {{$json.brief.constraints}}\n\n### Requirements:\n1. Start with a strong hook (1–2 lines).\n2. Expand into insight or framework (4-5 short paragraphs, max 20 lines each).\n3. Add supporting data/reference if relevant (keep sharp, not academic).\n4. End with a clear CTA (pick from the CTA options).\n5. Keep tone = {{$json.brief.tone}}.\n6. Avoid jargon, keep sentences punchy.\n\n\nReturn the output in JSON:\n{\n  \"post\": \"final LinkedIn post draft here\"\n}\n"
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "id": "X9ubpsApQNgfKwnt",
          "name": "OpenAi account 2"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "2038c6a7-a0a1-47b4-8f8c-07ecc9219f2c",
      "name": "05a_解析已选想法",
      "type": "n8n-nodes-base.code",
      "position": [
        320,
        512
      ],
      "parameters": {
        "jsCode": "try {\n  const raw = $json?.message?.content || \"{}\";\n  const data = JSON.parse(raw); // content ke andar jo JSON string hai, use parse karte hain\n  return [{\n    json: {\n      brief: $json.brief,            // brief ko carry forward\n      chosenIdea: data.chosenIdea,   // TOP-LEVEL bana diya\n      why: data.why,\n      ranked: data.ranked\n    }\n  }];\n} catch (e) {\n  return [{ json: { error: \"ParseError: content was not valid JSON\", detail: String(e), raw: $json?.message?.content } }];\n}\n"
      },
      "typeVersion": 2
    },
    {
      "id": "f82d70ea-08fa-49db-8c88-dca6637ab547",
      "name": "04_提取想法列表",
      "type": "n8n-nodes-base.code",
      "position": [
        416,
        96
      ],
      "parameters": {
        "jsCode": "// 1) Get the raw text from LLM\nconst raw = $json?.message?.content || \"\";\n\n// 2) Remove the meta lead-in and split lines\nlet lines = raw.split(\"\\n\")\n  .map(l => l.trim())\n  .filter(Boolean);\n\n// 3) Keep only likely idea lines (numbered items or bold lines)\n//    e.g. \"1. **\\\"Why ...\\\"**\"\nconst ideaLines = lines.filter(l =>\n  /^\\d+\\.\\s*/.test(l) ||                         // numbered\n  /^\\*\\*.+\\*\\*$/.test(l)                         // fully bold\n);\n\n// 4) Clean each line: strip numbering, bold, surrounding quotes, trailing punctuation\nfunction cleanOne(s){\n  return s\n    .replace(/^\\d+\\.\\s*/, \"\")                    // remove \"1. \"\n    .replace(/^\\*\\*|\\*\\*$/g, \"\")                 // remove **bold**\n    .replace(/^[-–•\\s]+/, \"\")                    // leading bullets/dashes\n    .replace(/^\"+|\"+$/g, \"\")                     // leading/trailing quotes\n    .replace(/[”“]/g, '\"')                       // smart quotes normalize\n    .replace(/^\"|\"$/g, \"\")                       // quotes again (after normalize)\n    .replace(/\\s{2,}/g, \" \")                     // collapse spaces\n    .replace(/\\.*\\s*$/,\".\")                      // ensure single period end\n    .trim();\n}\n\n// 5) Extract ideas; also catch cases where the text spans next line (ignore empty)\nlet ideas = ideaLines.map(cleanOne)\n  .filter(i => i.length > 0);\n\n// 6) If we somehow got nothing (format changed), fallback: regex capture quoted chunks or numbered after the colon\nif (ideas.length === 0) {\n  const numbered = [...raw.matchAll(/^\\s*\\d+\\.\\s*(.+)$/gm)].map(m => cleanOne(m[1]));\n  const quoted = [...raw.matchAll(/\"([^\"]{10,})\"/g)].map(m => cleanOne(m[1]));\n  ideas = (numbered.length ? numbered : quoted).filter(Boolean);\n}\n\n// 7) Deduplicate exacts and strip trailing doubled quotes if any\nideas = [...new Set(ideas.map(i => i.replace(/\"+$/,\"\").trim()))];\n\n// 8) Return as a single item\nreturn [{ json: { ideas } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "7a9e1242-f84c-4fc0-ba23-4fc3196479fe",
      "name": "05_精确去重检查",
      "type": "n8n-nodes-base.code",
      "position": [
        1264,
        80
      ],
      "parameters": {
        "jsCode": "const past = items[0].json.pastIdeas.map(p => p.trim().toLowerCase());\nconst ideas = items[0].json.ideas;\n\n\nconst kept_exact = [];\nconst rejected_exact = [];\n\nfor (const idea of ideas) {\n  if (past.includes(idea.trim().toLowerCase())) {\n    rejected_exact.push(idea);\n  } else {\n    kept_exact.push(idea);\n  }\n}\n\nreturn [{\n  json: {\n    kept_exact,\n    rejected_exact,\n    counts: {\n      kept: kept_exact.length,\n      rejected: rejected_exact.length,\n      past\n    }\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "8866dafc-c386-46ae-a75a-2402ecd35903",
      "name": "02_读取过往想法",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        656,
        96
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1945kuHLUlrCDROQxRPb7vNZQ4mEXd67sLRNVIB9lFFA/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1945kuHLUlrCDROQxRPb7vNZQ4mEXd67sLRNVIB9lFFA",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1945kuHLUlrCDROQxRPb7vNZQ4mEXd67sLRNVIB9lFFA/edit?usp=drivesdk",
          "cachedResultName": "Idea_Log"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "x2LQk3hNWfQ8t8iy",
          "name": "Google Sheets account 3"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "c606437f-5680-4691-adf3-1fbb17eaf9bf",
      "name": "03_标准化过往想法",
      "type": "n8n-nodes-base.code",
      "position": [
        864,
        96
      ],
      "parameters": {
        "jsCode": "const pastIdeas = items.map(i => i.json.idea).filter(Boolean);\n\nreturn [{\n  json: {\n    pastIdeas\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "2db1b5aa-eb84-4291-a43a-60c3353affe3",
      "name": "04.5_合并想法与过往想法",
      "type": "n8n-nodes-base.merge",
      "position": [
        1088,
        80
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "00f18696-842e-4261-ae0e-e24a07c9917f",
      "name": "06_模糊去重",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1456,
        80
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5",
          "cachedResultName": "GPT-5"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You remove near-duplicate LinkedIn post ideas using semantic similarity. \nBe conservative: if uncertain, KEEP the idea.\nReturn STRICT JSON only, no extra text.\n"
            },
            {
              "content": "=BRIEF (context only):\n{{ JSON.stringify($item(0,\"02_BuildBrief\").$json) }}\n\nCANDIDATE_IDEAS (after exact-dedupe):\n{{ JSON.stringify($json.kept_exact) }}\n\nPAST_IDEAS (from Idea_Log):\n{{ $json.counts.past }}\n\n\nRULES:\n- Mark an idea as duplicate only if it conveys the SAME core advice/angle as a past idea (not just same topic).\n- Prefer variety across angle (framework vs case study vs contrarian), audience (India + global), and metric focus.\n- If unsure, KEEP the idea (soft fuzzy dedupe).\n- Do NOT rewrite text; only classify.\n- Output STRICT JSON only.\n\nOUTPUT SCHEMA:\n{\n  \"kept_fuzzy\": [\"<idea 1>\", \"<idea 2>\"],\n  \"rejected_fuzzy\": [\n    { \"idea\": \"<candidate duplicated>\", \"matched\": \"<closest past idea>\", \"reason\": \"near-duplicate (same angle/advice)\" }\n  ]\n}\n"
            }
          ]
        },
        "jsonOutput": true
      },
      "credentials": {
        "openAiApi": {
          "id": "X9ubpsApQNgfKwnt",
          "name": "OpenAi account 2"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "2cc8e18a-4f29-4456-802d-68bb1c5a829e",
      "name": "10_提取发布包",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1008,
        528
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5",
          "cachedResultName": "GPT-5"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You extract structured components from a LinkedIn draft. \nReturn STRICT JSON only. No extra text. \nPreserve facts/sources; do not invent.\n"
            },
            {
              "content": "=INPUT DRAFT (JSON):\n{{ $json.message?.content || $json.post || \"{}\" }}\n\nBRIEF (for constraints; do not rewrite content):\n{{ JSON.stringify($item(0,\"08_ParseChosenIdea\").$json.brief) }}\n\nTASK:\n- HOOK: pick the strongest one-line opener from the draft (or rewrite lightly).\n- BODY: the cleaned post text (fix spacing only).\n- CTA: choose exactly one from {{ JSON.stringify($item(0,\"08_ParseChosenIdea\").$json.brief.cta) }} that best fits the draft ending.\n- HASHTAGS: suggest up to 8 relevant hashtags (start with #; audience/topic aligned).\n- FIRST_COMMENT: 1 line to invite discussion (optional).\n- CHAR_COUNT: integer = characters of BODY (not counting first_comment).\n\nOUTPUT (STRICT JSON):\n{\n  \"hook\": \"<one line>\",\n  \"body\": \"<full post text>\",\n  \"cta\": \"<one of the allowed list>\",\n  \"hashtags\": [\"#tag1\", \"#tag2\"],\n  \"first_comment\": \"<optional, 0-140 chars>\",\n  \"char_count\": 0\n}\nRULES:\n- Max 8 hashtags.\n- Keep numbers/sources exactly as in the draft.\n- No prose outside JSON.\n"
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "id": "X9ubpsApQNgfKwnt",
          "name": "OpenAi account 2"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "fd3c747c-5164-403e-9ca3-9de40e7bfc9b",
      "name": "合并1",
      "type": "n8n-nodes-base.merge",
      "position": [
        832,
        528
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "a1bff2e1-c584-4f05-983c-e93623947469",
      "name": "合并2",
      "type": "n8n-nodes-base.merge",
      "position": [
        1296,
        528
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "4beb81f3-d5e0-44d9-b6b6-183314ca883c",
      "name": "11_解析发布包",
      "type": "n8n-nodes-base.code",
      "position": [
        1504,
        528
      ],
      "parameters": {
        "jsCode": "try {\n  // 1) Extract the JSON string from LLM output\n  const raw = $json?.message?.content ?? \"{}\";\n  const pack = (typeof raw === \"string\") ? JSON.parse(raw) : raw;\n\n  // 2) Normalize fields\n  const hook = (pack.hook || \"\").trim();\n  const body = (pack.body || \"\").trim();\n  const cta  = (pack.cta  || \"\").trim();\n  const hashtags = Array.isArray(pack.hashtags) ? pack.hashtags : [];\n  const first_comment = (pack.first_comment || \"\").trim();\n\n  // 3) Attach brief from earlier node\n  const brief = $item(0, \"08_ParseChosenIdea\")?.$json?.brief\n             || $item(0, \"02_BuildBrief\")?.$json?.brief\n             || {};\n\n  // 4) Recompute char_count (hook + body as user sees on LinkedIn)\n  const combined = (hook ? hook + \"\\n\\n\" : \"\") + body;\n  const char_count = combined.length;\n\n  return [{\n    json: { hook, body, cta, hashtags, first_comment, char_count, brief }\n  }];\n} catch (e) {\n  return [{\n    json: {\n      error: \"ParseError_PublishPack\",\n      detail: String(e),\n      raw: $json?.message?.content ?? null\n    }\n  }];\n}\n"
      },
      "typeVersion": 2
    },
    {
      "id": "c3536683-2e43-4920-a169-38ab9e338ce3",
      "name": "12_具体性检查",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -336,
        896
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5",
          "cachedResultName": "GPT-5"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are a precision editor for LinkedIn posts. \nYour job is to add crisp specificity (examples, concrete details, brief citations) without bloating length or changing the core claim.\nFollow the brief’s tone. Preserve facts; never invent numbers.\nReturn STRICT JSON only. No extra text.\n"
            },
            {
              "content": "=INPUT:\nHOOK:\n{{$json.hook}}\n\nBODY:\n{{$json.body}}\n\nBRIEF (context/constraints):\n{{ JSON.stringify($json.brief) }}\n\nRULES:\n- Keep the hook’s intent. You may tighten phrasing slightly.\n- Add 2–3 specific details/examples that strengthen the argument (company names, UX patterns, workflow examples, market contexts).\n- DO NOT add new numeric claims beyond what’s already in BODY. You may add source NAMES (e.g., NPCI, ACI, FIS) if they’re already referenced, but no new figures.\n- Prefer one India and one global example when relevant.\n- Keep sentences punchy; avoid jargon. Preserve bullets and spacing.\n- Net length change ≤ +50 words (target total 150–230 words including hook).\n- End with the existing CTA sentiment intact (don’t change intent).\n\nOUTPUT (STRICT JSON):\n{\n  \"hook\": \"<tightened or original hook>\",\n  \"body\": \"<revised body with added specificity>\",\n  \"notes\": [\n    \"What was clarified or made specific in 1–2 bullets\"\n  ]\n}\n"
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "id": "X9ubpsApQNgfKwnt",
          "name": "OpenAi account 2"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "b51b8336-95f0-498c-9997-73fd6c8f2d0a",
      "name": "合并 3",
      "type": "n8n-nodes-base.merge",
      "position": [
        -16,
        896
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "391ac3da-c92c-4a5c-b2da-944af179c253",
      "name": "13_声音一致性",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        336,
        896
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5",
          "cachedResultName": "GPT-5"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are a voice refinement assistant.\nYour job is to take a draft post (hook, body, notes, brief) and adjust the language so it matches the user’s preferred voice:\n- Tone: Sharp, crisp, authoritative\n- Avoid heavy jargon; keep sentences short and punchy\n- Use simple, data-backed phrasing\n- Ensure scannability (short paragraphs, clean bullets if needed)\n\nDo NOT drop or alter the \"brief\" field.\nDo NOT remove citations, numbers, or data.\nAlways return the same JSON schema with {hook, body, notes, brief}.\n"
            },
            {
              "content": "=INPUT_HOOK:\n{{ $json.hook }}\n\nINPUT_BODY:\n{{ $json.body }}\n\nNOTES_FOR_CONTEXT (optional):\n{{ JSON.stringify($json.notes || []) }}\n\nBRIEF (do not modify; include unchanged in output):\n{{ JSON.stringify($json.brief) }}\n\nTASK:\n- Adjust HOOK and BODY to match the voice above.\n- Keep all citations/numbers intact.\n- Improve rhythm and scannability (line breaks, bullets).\n- Do not add new claims or change meaning.\n- Output JSON only:\n{\n  \"hook\": \"<refined one-liner>\",\n  \"body\": \"<refined post body>\",\n  \"notes\": {{ JSON.stringify($json.notes || []) }},\n  \"brief\": {{ JSON.stringify($json.brief) }}\n}\n"
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "id": "X9ubpsApQNgfKwnt",
          "name": "OpenAi account 2"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "ddb1f7e4-861f-4849-b228-50b695449e62",
      "name": "12a_解析具体性JSON",
      "type": "n8n-nodes-base.code",
      "position": [
        160,
        896
      ],
      "parameters": {
        "jsCode": "try {\n  const raw = $json?.message?.content ?? $json;\n  const data = typeof raw === 'string' ? JSON.parse(raw) : (raw || {});\n  return [{\n    json: {\n      hook: data.hook || \"\",\n      body: data.body || \"\",\n      notes: Array.isArray(data.notes) ? data.notes : [],\n      brief: $json.brief || $item(0,\"11_ParsePublishPack\")?.$json?.brief || {}\n    }\n  }];\n} catch (e) {\n  return [{ json: { error: \"ParseError_Specificity\", detail: String(e), raw: $json?.message?.content || null } }];\n}\n"
      },
      "typeVersion": 2
    },
    {
      "id": "ef02a5d6-5e50-4f22-aef9-41785fe92290",
      "name": "13a_解析声音JSON",
      "type": "n8n-nodes-base.code",
      "position": [
        656,
        896
      ],
      "parameters": {
        "jsCode": "try {\n  const raw = $json?.message?.content ?? $json;\n  const data = typeof raw === 'string' ? JSON.parse(raw) : (raw || {});\n  return [{\n    json: {\n      hook: data.hook || \"\",\n      body: data.body || \"\",\n      notes: Array.isArray(data.notes) ? data.notes : [],\n      brief: data.brief || {}\n    }\n  }];\n} catch (e) {\n  return [{\n    json: { error: \"ParseError_VoiceConformity\", detail: String(e), raw: $json?.message?.content || null }\n  }];\n}\n"
      },
      "typeVersion": 2
    },
    {
      "id": "b13a9c53-e561-4b10-8062-58a4c65260ee",
      "name": "14_构建CTA和话题标签_LLM",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        880,
        896
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You build CTA + hashtags + an optional first comment for a LinkedIn post.\nBe deterministic and rule-following.\n\nRules:\n- Choose exactly ONE CTA from the allowed list provided in \"brief.cta\".\n- Generate up to 8 hashtags. Short, relevant, no spaces, no emojis. Use PascalCase or established tags (e.g., #ProductManagement, #UPI). No duplicates.\n- First comment: 1 line, ≤ 140 characters, invites discussion (optional but preferred).\n- Compute char_count for the final visible text = length of:\n  <HOOK>[two newlines]<BODY>\n- Preserve facts and numbers from the body; do not add new numeric claims.\n- Return STRICT JSON only. No prose.\n- Do NOT rewrite hook or body; they will be merged downstream.\n"
            },
            {
              "content": "=INPUT:\n{\n  \"hook\": \"{{$json.hook}}\",\n  \"body\": \"{{$json.body}}\",\n  \"brief\": {{ JSON.stringify($json.brief) }}\n}\n\nTASK:\n1) Pick \"cta\" from brief.cta exactly (e.g., \"Comments\", \"Share\", or \"Follow\").\n2) Create \"hashtags\": max 8, relevant to the post and audience, PascalCase when possible, no spaces, no emojis, no duplicates.\n3) Write a concise \"first_comment\" (≤140 chars) that invites discussion; avoid repeating the hook verbatim.\n4) Compute \"char_count\" = character length of:\n   HOOK + \"\\n\\n\" + BODY\n   (count every character; do not include first_comment in this count).\n5) Output JSON ONLY in this schema:\n\n{\n  \"cta\": \"Comments\",\n  \"hashtags\": [\"#AI\", \"#ProductManagement\"],\n  \"first_comment\": \"Your one-line discussion prompt here...\",\n  \"char_count\": 1234\n}\n"
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "id": "X9ubpsApQNgfKwnt",
          "name": "OpenAi account 2"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "afb9ea3a-1c87-4e58-be14-ae140c074748",
      "name": "合并4",
      "type": "n8n-nodes-base.merge",
      "position": [
        1264,
        912
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "cfc7e722-2b69-44ae-a42e-6fc99fd304fc",
      "name": "14b_塑造合并包",
      "type": "n8n-nodes-base.code",
      "position": [
        1504,
        912
      ],
      "parameters": {
        "jsCode": "try {\n  // 1) Bring forward core fields (from Input A of Merge)\n  const hook  = ($json.hook || \"\").trim();\n  const body  = ($json.body || \"\").trim();\n  const notes = Array.isArray($json.notes) ? $json.notes : [];\n  const brief = $json.brief || {};\n\n  // 2) Parse LLM pack (from Input B of Merge)\n  let llmPack = {};\n  if ($json?.message?.content) {\n    llmPack = JSON.parse($json.message.content);\n  } else {\n    // if your LLM ever returns plain JSON at top-level\n    llmPack = $json;\n  }\n\n  // 3) Normalize CTA against allowed list from brief\n  const allowedCTAs = Array.isArray(brief.cta) && brief.cta.length ? brief.cta : [\"Comments\",\"Share\",\"Follow\"];\n  let cta = (llmPack.cta || \"\").trim();\n  if (!allowedCTAs.includes(cta)) cta = allowedCTAs[0];\n\n  // 4) Normalize hashtags: #prefix, dedupe, cap 8\n  let hashtags = Array.isArray(llmPack.hashtags) ? llmPack.hashtags : [];\n  hashtags = [...new Set(hashtags.map(h => \"#\" + h.replace(/^#/, \"\").replace(/\\s+/g, \"\")))].slice(0, 8);\n\n  // 5) First comment\n  const first_comment = (llmPack.first_comment || \"\").trim();\n\n  // 6) Deterministic visible char count (hook + body only)\n  const char_count = ((hook ? hook + \"\\n\\n\" : \"\") + body).length;\n\n  return [{\n    json: {\n      hook,\n      body,\n      notes,\n      cta,\n      hashtags,\n      first_comment,\n      char_count,\n      brief\n    }\n  }];\n} catch (e) {\n  return [{\n    json: { error: \"ShapeError_14b\", detail: String(e), raw: $json?.message?.content || null }\n  }];\n}\n"
      },
      "typeVersion": 2
    },
    {
      "id": "62b0dd6b-e736-456e-8cbb-cd27f721cbbe",
      "name": "15_互动健康度",
      "type": "n8n-nodes-base.code",
      "position": [
        -320,
        1248
      ],
      "parameters": {
        "jsCode": "try {\n  const hook = ($json.hook || \"\").trim();\n  let body = ($json.body || \"\").trim();\n  const notes = Array.isArray($json.notes) ? $json.notes : [];\n  const brief = $json.brief || {};\n  let cta = ($json.cta || \"Comments\").trim();\n  let hashtags = Array.isArray($json.hashtags) ? $json.hashtags.slice(0, 32) : [];\n  let first_comment = ($json.first_comment || \"\").trim();\n\n  // -------------------------\n  // 1) Normalize spacing & punctuation\n  // -------------------------\n  // Collapse 3+ newlines to 2\n  body = body.replace(/\\r/g, \"\")\n             .replace(/\\n{3,}/g, \"\\n\\n\")\n             .replace(/[ \\t]+\\n/g, \"\\n\");\n\n  // Fix spaced hyphenated words: \"AI- native\" -> \"AI-native\"\n  // Avoid bullet lines that begin with \"- \"\n  body = body.split(\"\\n\").map(line => {\n    if (/^\\s*-\\s/.test(line)) return line; // keep bullets as-is for now\n    return line.replace(/\\b([A-Za-z0-9]+)\\s*-\\s*([A-Za-z0-9]+)\\b/g, \"$1-$2\");\n  }).join(\"\\n\");\n\n  // -------------------------\n  // 2) Bullet hygiene & scannability\n  // -------------------------\n  // Ensure \"- \" (hyphen + space) bullets\n  body = body.replace(/^\\s*-\\s*/gm, \"- \");\n  // Add a blank line before and after bullet blocks\n  body = body.replace(/([^\\n])\\n(- .+(?:\\n- .+)+)/g, \"$1\\n\\n$2\")\n             .replace(/((?:- .+(?:\\n|$))+)(\\S)/g, \"$1\\n$2\");\n\n  // -------------------------\n  // 3) Ensure a question if CTA = Comments\n  // -------------------------\n  const endsWithQuestion = /\\?\\s*$/.test(body.trim());\n  if (cta === \"Comments\" && !endsWithQuestion) {\n    // If last 2 lines already contain a question, skip\n    const tail = body.split(\"\\n\").slice(-3).join(\" \");\n    if (!/\\?/.test(tail)) {\n      body = body.trim() + \"\\n\\nWhat do you think?\";\n    }\n  }\n\n  // -------------------------\n  // 4) Hashtag hygiene (dedupe, cap 8, casing)\n  // -------------------------\n  // Preferred casing map for common tags\n  const preferred = {\n    \"ai\": \"AI\",\n    \"upi\": \"UPI\",\n    \"productmanagement\": \"ProductManagement\",\n    \"productstrategy\": \"ProductStrategy\",\n    \"a iagents\": \"AIAgents\", // guard against odd spacing\n    \"aiagents\": \"AIAgents\",\n    \"indiatch\": \"IndiaTech\",\n    \"indiatech\": \"IndiaTech\",\n    \"distribution\": \"Distribution\",\n    \"growth\": \"Growth\",\n    \"fintech\": \"Fintech\",\n    \"digitalpayments\": \"DigitalPayments\",\n    \"developerexperience\": \"DeveloperExperience\",\n    \"platformstrategy\": \"PlatformStrategy\",\n    \"startups\": \"Startups\",\n    \"innovation\": \"Innovation\",\n    \"workflows\": \"Workflows\",\n  };\n\n  const toPascal = (s) => s\n    .split(/[^A-Za-z0-9]+/).filter(Boolean)\n    .map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())\n    .join(\"\");\n\n  hashtags = hashtags\n    .map(h => String(h).trim())\n    .filter(Boolean)\n    .map(h => h.replace(/^#/, \"\"))                 // strip leading #\n    .map(h => h.replace(/\\s+/g, \"\"))               // no spaces\n    .map(h => preferred[h.toLowerCase()] || toPascal(h)) // apply preferred casing or PascalCase\n    .map(h => \"#\" + h)\n    .filter((h, i, arr) => arr.indexOf(h) === i)   // dedupe\n    .slice(0, 8);                                   // cap 8\n\n  // -------------------------\n  // 5) First comment fallback (optional)\n  // -------------------------\n  if (!first_comment) {\n    first_comment = (cta === \"Comments\")\n      ? \"What’s one rail that would make your AI unavoidable this quarter?\"\n      : \"Follow for more product + AI breakdowns.\";\n  }\n\n  // -------------------------\n  // 6) Recompute char_count (visible: hook + body)\n  // -------------------------\n  const char_count = ((hook ? hook + \"\\n\\n\" : \"\") + body).length;\n\n  return [{\n    json: {\n      hook,\n      body,\n      notes,\n      cta,\n      hashtags,\n      first_comment,\n      char_count,\n      brief\n    }\n  }];\n} catch (e) {\n  return [{\n    json: {\n      error: \"EngagementHygieneError\",\n      detail: String(e),\n      raw: $json\n    }\n  }];\n}\n"
      },
      "typeVersion": 2
    },
    {
      "id": "53d3ea8a-56d2-469c-9410-8d858f2b0e8e",
      "name": "16_格式合规性",
      "type": "n8n-nodes-base.code",
      "position": [
        -96,
        1248
      ],
      "parameters": {
        "jsCode": "// Cleans duplicates of the hook from body, enforces LinkedIn limits, and returns a compliant pack.\n\nfunction smartTrim(text, limit) {\n  if (text.length <= limit) return text;\n  const cut = text.slice(0, limit);\n  // Try to end at a sentence or line boundary\n  const lastStop = Math.max(\n    cut.lastIndexOf(\". \"),\n    cut.lastIndexOf(\"! \"),\n    cut.lastIndexOf(\"? \"),\n    cut.lastIndexOf(\"\\n\")\n  );\n  if (lastStop > 40) return cut.slice(0, lastStop + 1).trim(); // keep a sensible boundary\n  return cut.trimEnd() + \"…\";\n}\n\nreturn items.map(item => {\n  const data = item.json || {};\n  const MAX_CHARS = 3000; // LinkedIn hard limit\n  const hook = (data.hook || \"\").trim();\n  let body = (data.body || \"\").replace(/\\r/g, \"\");\n  let hashtags = Array.isArray(data.hashtags) ? data.hashtags : [];\n  const cta = (data.cta || \"\").trim();\n  const first_comment = (data.first_comment || \"\").trim();\n  const brief = data.brief || {};\n  const notes = Array.isArray(data.notes) ? data.notes : [];\n\n  // 1) Remove duplicate hook lines from the start of body\n  const hookNorm = hook.trim();\n  let lines = body.split(\"\\n\");\n  while (lines.length && lines[0].trim() === hookNorm) lines.shift(); // remove repeated hook(s)\n  while (lines.length && lines[0].trim() === \"\") lines.shift();       // remove leading blank lines\n  body = lines.join(\"\\n\").trim();\n\n  // 2) Build visible text = hook + blank line + body\n  //    (always show hook at top once)\n  const visiblePrefix = hook ? hook + \"\\n\\n\" : \"\";\n  // enforce max by trimming ONLY the body portion\n  const allowedBodyLimit = Math.max(0, MAX_CHARS - visiblePrefix.length);\n  const bodyTrimmed = smartTrim(body, allowedBodyLimit);\n\n  const visible = visiblePrefix + bodyTrimmed;\n\n  // 3) Hashtag cap (10), normalize # and dedupe\n  hashtags = [...new Set(\n    hashtags\n      .map(h => \"#\" + String(h || \"\").replace(/^#/, \"\").replace(/\\s+/g, \"\"))\n  )].slice(0, 10);\n\n  // 4) Final char count of visible text\n  const char_count = visible.length;\n\n  return {\n    json: {\n      hook,                 // unchanged\n      body: bodyTrimmed,    // cleaned & possibly trimmed\n      notes,\n      cta,\n      hashtags,\n      first_comment,\n      char_count,\n      brief\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "401c8de0-761f-49d0-b3a0-0004f54f5651",
      "name": "生成图像",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        128,
        1248
      ],
      "parameters": {
        "model": "gpt-image-1",
        "prompt": "={\n  \"prompt\": \"Create a LinkedIn-style social graphic.\\n\\nUse this headline EXACTLY: \\\"{{$json.hook}}\\\".\\n\\nFrom the BODY below, infer 2–3 core concepts (nouns/themes) and represent them with simple, universal flat icons/illustrations (no brand logos). Prefer tech/product metaphors (e.g., phone, graph, gears, database, API plug, search, document, bolt, shield) ONLY if they match the body themes.\\n\\nBODY:\\n{{$json.body}}\\n\\nLayout:\\n- Large bold headline at top-left.\\n- Under headline, one short tagline derived from the body (≤120 chars). Do NOT paste full paragraphs.\\n- Beside/below the text, place the 2–3 icons you inferred. Arrange them in a clear left→right story flow.\\n\\nStyle:\\n- Clean, modern, minimalist, LinkedIn-friendly.\\n- Background: dark (#0B0F15) with subtle gradient; typography in white/light grey.\\n- Accent palette (use sparingly): blue #4A90E2, green #7ED321.\\n- Mix text + visuals (NOT plain text poster).\\n\\nFooter:\\n- Add a small, subtle footer strip with hashtags: {{ Array.isArray($json.hashtags) ? $json.hashtags.slice(0,3).join(' ') : '' }}\\n\\nConstraints:\\n- No faces/people. No brand marks or watermarks. High contrast, legible at mobile feed size.\",\n  \"size\": \"1024x1024\",\n  \"n\": 1\n}\n",
        "options": {},
        "resource": "image"
      },
      "credentials": {
        "openAiApi": {
          "id": "X9ubpsApQNgfKwnt",
          "name": "OpenAi account 2"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "2f764832-1f2d-494c-ae7e-016f3da41618",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -624,
        16
      ],
      "parameters": {
        "color": 3,
        "width": 2544,
        "height": 288,
        "content": "## 想法生成及与已发布帖子的去重"
      },
      "typeVersion": 1
    },
    {
      "id": "e10e205e-e5fc-4c1f-9df3-8a8605f977af",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -624,
        400
      ],
      "parameters": {
        "color": 4,
        "width": 2544,
        "height": 288,
        "content": "## 基于想法的帖子生成"
      },
      "typeVersion": 1
    },
    {
      "id": "a664f3a1-65ed-412c-a94e-ba9556f6395c",
      "name": "便签 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -624,
        816
      ],
      "parameters": {
        "color": 6,
        "width": 2544,
        "height": 288,
        "content": "## 想法润色、添加CTA和话题标签"
      },
      "typeVersion": 1
    },
    {
      "id": "3cc14f12-fd29-46b2-878a-e653b7616510",
      "name": "便签 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -624,
        1168
      ],
      "parameters": {
        "width": 2528,
        "height": 288,
        "content": "## 图片生成、最终帖子准备就绪并更新 Google 表格和 Google Drive"
      },
      "typeVersion": 1
    },
    {
      "id": "12dc9885-c076-4cb3-b178-b718ef116840",
      "name": "合并5",
      "type": "n8n-nodes-base.merge",
      "position": [
        320,
        1264
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "d4420fe7-8b84-456f-bc3c-7da1b0f6974e",
      "name": "18_发布打包器",
      "type": "n8n-nodes-base.code",
      "position": [
        480,
        1264
      ],
      "parameters": {
        "jsCode": "// Expecting merged item with text JSON + binary.image\nconst j = $json || {};\nconst b = $binary?.image || $binary?.data || null;\n\nif (!j.hook && !j.body) {\n  throw new Error('No post fields found. Ensure Merge node combines 16_FormatCompliance (JSON) with 17_GenerateImage (binary).');\n}\nif (!b) {\n  throw new Error('No image binary found. In Image node set Binary Property = \"image\" (or \"data\") and wire it into the Merge.');\n}\n\n// Normalize\nconst hashtags = Array.isArray(j.hashtags) ? j.hashtags : (j.hashtags ? String(j.hashtags).split(/\\s+/) : []);\nconst finalPost = `\n${j.hook || ''}\n\n${j.body || ''}\n\n${hashtags.length ? hashtags.join(' ') : ''}${j.cta ? `\\n\\n${j.cta}` : ''}\n`.trim();\n\nreturn [{\n  json: {\n    ...j,\n    final_post: finalPost,\n    image_provider: 'image_gen_node',\n    image_filename: b.fileName || 'post.png',\n    image_mime: b.mimeType || 'image/png',\n    image_size: b.fileSize || '',\n    image_binary_id: b.id || ''\n  },\n  binary: { image: b }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "6635de34-cf6c-4bd2-ac54-3aa932f510f1",
      "name": "上传文件",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        672,
        1264
      ],
      "parameters": {
        "name": "={{ $now.toFormat(\"yyyyLLdd_HHmmss\") + \"_\" + $json[\"hook\"].replace(/[^a-zA-Z0-9]/g,\"_\").slice(0,30) + \".png\" }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive",
          "cachedResultUrl": "https://drive.google.com/drive/my-drive",
          "cachedResultName": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "1M8-qn3kL5P0Fb7MZllIL3KL2jVxROFkm",
          "cachedResultUrl": "https://drive.google.com/drive/folders/1M8-qn3kL5P0Fb7MZllIL3KL2jVxROFkm",
          "cachedResultName": "Linkedin Post Generator"
        },
        "inputDataFieldName": "=image"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "4wtxqZRczqngQF8G",
          "name": "Google Drive account"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "c3afa08f-4402-4455-a99d-a8447a7483ce",
      "name": "18b_构建表格行",
      "type": "n8n-nodes-base.code",
      "position": [
        1072,
        1264
      ],
      "parameters": {
        "jsCode": "// 18b_BuildSheetRow\n\nconst rows = [];\n\nfor (const item of items) {\n  const x = item.json;\n\n  // === IDEA (dedupe safety) ===\n  const pickSentence = (txt) => {\n    if (!txt) return '';\n    const s = txt.split(/[.?!]/)[0];\n    return s.length > 100 ? s.slice(0, 100) : s;\n  };\n\n  const ideaTextRaw =\n    x.locked_idea ||\n    x.chosenIdea ||\n    x.idea ||\n    pickSentence(x.hook) ||\n    pickSentence(x.body) ||\n    pickSentence(x.final_post);\n\n  const idea = (ideaTextRaw || '').trim();\n  const ideaSlug = idea.toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-|-$/g, '');\n\n  // === Hash for exact dedupe ===\n  const crypto = require('crypto');\n  const ideaHash = crypto.createHash('md5').update(idea).digest('hex');\n\n  // === Timestamps ===\n  const tsISO = new Date().toISOString();\n  const tsIST = new Date().toLocaleString('en-IN', { timeZone: 'Asia/Kolkata' });\n\n  // === Sheet Row Object ===\n  rows.push({\n    timestamp_iso: tsISO,\n    timestamp_ist: tsIST,\n\n    post_id: x.id || '',\n\n    idea,\n    idea_slug: ideaSlug,\n    idea_hash: ideaHash,\n\n    hook: x.hook || '',\n    body: x.body || '',\n    cta: x.cta || '',\n    hashtags: Array.isArray(x.hashtags) ? x.hashtags.join(' ') : (x.hashtags || ''),\n    first_comment: x.first_comment || '',\n    final_post: x.final_post || '',\n\n    notes: Array.isArray(x.notes) ? x.notes.join(' | ') : (x.notes || ''),\n\n    image_url: x.webViewLink || x.webContentLink || '',\n    image_filename: x.image_filename || x.name || '',\n    image_mime: x.image_mime || x.mimeType || '',\n    image_size: x.image_size || x.fileSize || '',\n\n    // Extra debug/meta\n    image_binary_id: x.image_binary_id || '',\n    provider: x.image_provider || '',\n  });\n}\n\nreturn rows.map(r => ({ json: r }));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "855dfa96-f231-4f3c-8272-c1ce195eab59",
      "name": "合并6",
      "type": "n8n-nodes-base.merge",
      "position": [
        864,
        1264
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "a34b9b18-bbff-4ace-a89f-c7a057f7c3a6",
      "name": "19_表格追加",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1280,
        1264
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "timestamp_ist",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "timestamp_ist",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "post_id",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "post_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "hook",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "hook",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "body",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "body",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cta",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "cta",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "hashtags",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "hashtags",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "first_comment",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "first_comment",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "image_url",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "image_url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "image_filename",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "image_filename",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "notes",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "notes",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp_iso",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "timestamp_iso",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "idea",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "idea",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "idea_slug",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "idea_slug",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "idea_hash",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "idea_hash",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "final_post",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "final_post",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "image_mime",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "image_mime",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "image_size",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "image_size",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "image_binary_id",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "image_binary_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "provider",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "provider",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1945kuHLUlrCDROQxRPb7vNZQ4mEXd67sLRNVIB9lFFA/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1945kuHLUlrCDROQxRPb7vNZQ4mEXd67sLRNVIB9lFFA",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1945kuHLUlrCDROQxRPb7vNZQ4mEXd67sLRNVIB9lFFA/edit?usp=drivesdk",
          "cachedResultName": "Idea_Log"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "x2LQk3hNWfQ8t8iy",
          "name": "Google Sheets account 3"
        }
      },
      "typeVersion": 4.7
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "23358699-7458-4693-940e-06895ad60da1",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "05_PickIdeaAI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge1": {
      "main": [
        [
          {
            "node": "10_ExtractPublishPack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge2": {
      "main": [
        [
          {
            "node": "11_ParsePublishPack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge3": {
      "main": [
        [
          {
            "node": "12a_ParseSpecificityJSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge4": {
      "main": [
        [
          {
            "node": "14b_ShapeMergedPack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge5": {
      "main": [
        [
          {
            "node": "18_PublishPackager",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge6": {
      "main": [
        [
          {
            "node": "18b_BuildSheetRow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload file": {
      "main": [
        [
          {
            "node": "Merge6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "01_AutoStart": {
      "main": [
        [
          {
            "node": "02_BuildBrief",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "02_BuildBrief": {
      "main": [
        [
          {
            "node": "03_GenerateIdea",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          },
          {
            "node": "05b_MergeBriefAndPick",
            "type": "main",
            "index": 1
          },
          {
            "node": "Merge1",
            "type": "main",
            "index": 1
          },
          {
            "node": "Merge2",
            "type": "main",
            "index": 1
          },
          {
            "node": "Merge3",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "05_PickIdeaAI": {
      "main": [
        [
          {
            "node": "05b_MergeBriefAndPick",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "03_GenerateIdea": {
      "main": [
        [
          {
            "node": "04_ExtractIdeasList",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "06_GeneratePost": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "02_ReadPastIdeas": {
      "main": [
        [
          {
            "node": "03_NormalizePastIdeas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "18b_BuildSheetRow": {
      "main": [
        [
          {
            "node": "19_Sheets Append",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate an image": {
      "main": [
        [
          {
            "node": "Merge5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12_SpecificityPass": {
      "main": [
        [
          {
            "node": "Merge3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "13_VoiceConformity": {
      "main": [
        [
          {
            "node": "13a_ParseVoiceJSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "13a_ParseVoiceJSON": {
      "main": [
        [
          {
            "node": "14_BuildCTAHashtags_LLM",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge4",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "18_PublishPackager": {
      "main": [
        [
          {
            "node": "Upload file",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge6",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "04_ExtractIdeasList": {
      "main": [
        [
          {
            "node": "02_ReadPastIdeas",
            "type": "main",
            "index": 0
          },
          {
            "node": "04.5_MergeIdeasPastIdeas",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "05_ExactDedupeCheck": {
      "main": [
        [
          {
            "node": "06_FuzzyDeduplication",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "05a_ParsePickedIdea": {
      "main": [
        [
          {
            "node": "06_GeneratePost",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "11_ParsePublishPack": {
      "main": [
        [
          {
            "node": "12_SpecificityPass",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "14b_ShapeMergedPack": {
      "main": [
        [
          {
            "node": "15_EngagementHygiene",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "16_FormatCompliance": {
      "main": [
        [
          {
            "node": "Generate an image",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge5",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "15_EngagementHygiene": {
      "main": [
        [
          {
            "node": "16_FormatCompliance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "03_NormalizePastIdeas": {
      "main": [
        [
          {
            "node": "04.5_MergeIdeasPastIdeas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "05b_MergeBriefAndPick": {
      "main": [
        [
          {
            "node": "05a_ParsePickedIdea",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "06_FuzzyDeduplication": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "10_ExtractPublishPack": {
      "main": [
        [
          {
            "node": "Merge2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "14_BuildCTAHashtags_LLM": {
      "main": [
        [
          {
            "node": "Merge4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "04.5_MergeIdeasPastIdeas": {
      "main": [
        [
          {
            "node": "05_ExactDedupeCheck",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12a_ParseSpecificityJSON": {
      "main": [
        [
          {
            "node": "13_VoiceConformity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

这是一个高级难度的工作流,适用于Content Creation、Multimodal AI等场景。适合高级用户,包含 16+ 个节点的复杂工作流

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量39
分类2
节点类型7
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

外部链接
在 n8n.io 上查看 →

分享此工作流