{
  "id": "yRRlbNuKHdd9Fedc",
  "name": "Zen Content Automation \u2014 AI Article & Image Generator (Weekly Topics + Daily Article)",
  "tags": [],
  "nodes": [
    {
      "id": "28455f79-0749-4723-b5cb-0741011fffec",
      "name": "When clicking 'Execute workflow'",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        384,
        1344
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "b4c311c4-e881-4572-a0e9-4e20ef637848",
      "name": "Get next topic",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1472,
        1312
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Topics"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Search Spreadsheet').first().json.id }}"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "068bb0c4-1d6d-42e1-88ab-77e2b6393130",
      "name": "Filter New Topics",
      "type": "n8n-nodes-base.filter",
      "position": [
        1680,
        1328
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-status-new",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.status }}",
              "rightValue": "new"
            }
          ]
        }
      },
      "typeVersion": 2.2,
      "alwaysOutputData": true
    },
    {
      "id": "e40c9fc9-ec8f-4432-ba66-2cd3759c6050",
      "name": "Limit To One Topic",
      "type": "n8n-nodes-base.limit",
      "position": [
        2048,
        1296
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "18bdc65a-8e04-41a7-afb9-c984aa097f9d",
      "name": "LLM: Draft Article (Claude Haiku 4.5)",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        2400,
        1488
      ],
      "parameters": {
        "model": "anthropic/claude-haiku-4.5",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "95027998-e3eb-42f8-9eec-bcf36fa46149",
      "name": "Draft article",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2400,
        1312
      ],
      "parameters": {
        "text": "=Topic: {{ $json.title }}\nFormat: {{ $json.format }}\nHook: {{ $json.hook }}\n\nWrite an article on this topic.",
        "options": {
          "systemMessage": "You write articles for a Zen channel about historical events and little-known facts about women \u2014 in the style of established Zen history/biography channels (benchmark: World Lord, arts_tobe).\n\nSTRUCTURE AND FORMATTING (strict):\n- Do NOT use subheadings, do NOT break the text into numbered points \u2014 the article is written as continuous narrative, in paragraphs\n\nFIRST SENTENCE \u2014 one of two options:\n(a) A REAL, documented quote from the historical figure or a contemporary, if one exists in well-known sources \u2014 never invented\n(b) If there is no real quote \u2014 a short, dramatic statement with a concrete fact (a number, a date, an unusual detail), with no quote at all\n\nDo NOT start with generic phrases like \"When we imagine...\" or \"Throughout history there have been many...\"\nNEVER invent quotes or put words in the heroine's mouth that she never said \u2014 even with a hedge like \"she might have said\" or \"as if she said.\" If there is no real quote \u2014 use option (b).\n\nFORBIDDEN RHETORICAL CONSTRUCTIONS (these are the most recognizable AI clich\u00e9s \u2014 detectors catch them instantly):\n- \"not just X \u2014 it's/I Y\" and any variation (\"this isn't just a story, it's...\")\n- \"but the real [X] wasn't Y, it was Z\" style contrast constructions\n- consecutive rhetorical questions (\"Who was this woman? What became of her?\")\n- \"it's worth noting,\" \"one cannot fail to mention,\" \"it's important to understand,\" \"it's important to note,\" \"in the modern world,\" \"thus\"\n\nAfter that \u2014 a chronological or thematic unfolding of the story: events follow one another like a narrative, not a list of facts\n\nDEPTH OF COVERAGE (more important than length itself):\nUnfold the story across at least 5-6 separate episodes/scenes \u2014 not just a list of facts, but actual scenes with action, dialogue, or a concrete detail of the moment. Don't wrap up the story right after the first climactic event \u2014 if there were further consequences for the heroine afterward (publications, recognition, being forgotten, her later fate, how she's remembered), describe those as a separate episode too. If the article is a roundup of several people, distribute the word count evenly between them (for 3 people \u2014 roughly 200-250 words each), trimming details rather than cutting people.\n\nLENGTH: 700-1100 words (test mode) \u2014 this should be a consequence of fully covering the episodes, not a goal in itself. If the text ends up clearly under 600 words after writing, episodes from the list above were skipped \u2014 go back and add them.\n\nGRAMMATICAL GENDER AGREEMENT (critical \u2014 applies to the Russian output):\nThe article is about a woman \u2014 whenever referring to her via a noun, use the feminine grammatical form in Russian (e.g. \"\u0443\u0447\u0451\u043d\u0430\u044f/\u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u0438\u0446\u0430,\" not \"\u0443\u0447\u0451\u043d\u044b\u0439/\u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\"; \"\u043b\u0430\u0443\u0440\u0435\u0430\u0442\u043a\u0430/\u043e\u0431\u043b\u0430\u0434\u0430\u0442\u0435\u043b\u044c\u043d\u0438\u0446\u0430,\" not \"\u043b\u0430\u0443\u0440\u0435\u0430\u0442/\u043e\u0431\u043b\u0430\u0434\u0430\u0442\u0435\u043b\u044c\"), even if the masculine form of the word is more common. Check every such noun before finishing.\n\nSYNTAX:\nUse participial and adverbial-participial constructions to add syntactic variety \u2014 but sparingly, no more than one such construction per 2-3 sentences, so the text stays easy to read and doesn't turn into bureaucratic language.\n\nDETAIL:\nFill the text with names, dates, and concrete details (what people wore, what they ate, how old they were, what they said) \u2014 abstractions are not acceptable.\n\nEMOTION:\nConvey not just facts but the heroine's inner state at key moments \u2014 anxiety before a decisive moment, joy at a victory, pain at an injustice. Do this through concrete details and actions (held her breath, hands trembling), not abstract labels (\"she was happy\").\n\nENDING:\nEnd the article with thanks for reading and a gentle invitation to read other articles on the channel \u2014 in the spirit of: \"Thanks for reading to the end! If this story moved you, come back \u2014 we have many more stories like this one that you won't find in textbooks.\"\n\nStick to historical accuracy: if a fact is disputed, say so directly in the text rather than presenting a guess as fact.\n\nIMPORTANT: the article itself must be written in Russian (this channel publishes in Russian, on Yandex Zen). Only these instructions are in English \u2014 the output text is not.\n\nReply with only the article text, no headline (the headline is handled separately) and no markdown formatting."
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "77875ff7-d322-497a-b832-eeb240ff1bbd",
      "name": "LLM: Humanize Draft (Claude Haiku 4.5)",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        3024,
        1488
      ],
      "parameters": {
        "model": "anthropic/claude-haiku-4.5",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "489bf9aa-3583-4eb0-b927-2b99d2523b80",
      "name": "Humanize draft",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        3024,
        1312
      ],
      "parameters": {
        "text": "=Text: {{ $json.draft_text }}",
        "options": {
          "systemMessage": "=You are a style editor for a historical Zen channel. Rewrite the text below, keeping all facts and structure intact (no subheadings or lists \u2014 continuous narrative only), but:\n\n- Break about 20% of the sentences into short, punchy ones (3-6 words) \u2014 this creates the dramatic rhythm typical of good historical prose. Alternate them with longer, more descriptive sentences\n- Replace 2-3 generic/abstract words with more specific, precise synonyms\n- Do NOT add conversational fillers or interjections (\"by the way,\" \"and here's the thing,\" \"surprisingly\") \u2014 the tone should stay literary and dramatic\n- Do NOT add new facts, do NOT change the meaning or chronology of events\n\nPUNCTUATION (strict):\nMaximum ONE dash per paragraph. If a dash is used for a parenthetical explanation inside a sentence (the \"text \u2014 explanation \u2014 text\" construction) \u2014 rewrite it using commas, parentheses, or split it into two separate sentences. This is the single most common AI habit in Russian text, and needs especially close attention.\n\nCHECK FOR AND REMOVE, IF PRESENT (typical AI patterns):\n- \"not just X \u2014 it's/I Y\" and variations\n- \"but the real [X] wasn't Y, it was Z\" style contrast constructions\n- consecutive rhetorical questions\n- \"it's worth noting,\" \"one cannot fail to mention,\" \"it's important to understand,\" \"it's important to note,\" \"in the modern world,\" \"thus\"\n- \"one of the most,\" \"one of the few,\" \"one of the first,\" and similar generic superlatives with no specifics \u2014 either give a precise number/fact instead, or rephrase without the comparison\n- Invented quotes hedged with \"she might have said\" \u2014 if you find this construction, either drop the hedge and keep it as a plain authorial statement, or rewrite without the quote entirely\n\nALSO \u2014 remove structural AI patterns:\n- If several paragraphs in a row start the same way (with the heroine's name, or the same construction) \u2014 vary the paragraph openings\n- Avoid perfectly symmetrical lists (three items of identical length/structure in a row is a clear AI marker) \u2014 make lists uneven in length\n\nCHECK GRAMMATICAL GENDER AGREEMENT: if the article's subject is a woman, every noun and participle referring to her must be in the feminine form in Russian (not \"as an outstanding [male] scholar\" but \"as an outstanding [female] scholar\") \u2014 fix if this is broken.\n\nCHECK TEXT INTEGRITY: make sure there are no stray characters, mixed alphabets, or corrupted encoding inside words (for example, Latin/Arabic/other-script characters wedged into a Russian word) \u2014 fix them to normal spelling if you find any.\n\nIMPORTANT: the rewritten text must remain in Russian \u2014 only these editing instructions are in English.\n\nReply with only the rewritten text, no explanations and no markdown formatting."
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "23c55df9-9f3f-4f87-b3ca-3d12bebc7a42",
      "name": "Merge Humanized With Topic",
      "type": "n8n-nodes-base.code",
      "position": [
        3296,
        1312
      ],
      "parameters": {
        "jsCode": "const topic = $('Merge Draft With Topic').first().json;\nreturn [{ json: {\n  id: topic.id,\n  title: topic.title,\n  format: topic.format,\n  hook: topic.hook,\n  article_text: $json.output\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "bfc4e107-25bd-4f02-84b0-848a2c0435bb",
      "name": "Create Article Doc",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        3632,
        1312
      ],
      "parameters": {
        "title": "={{ $('Merge Humanized With Topic').first().json.title }}",
        "folderId": "={{ $('Create Article Folder').first().json.id }}"
      },
      "typeVersion": 2
    },
    {
      "id": "f89de01e-b8bb-413f-837b-c11ebe91549a",
      "name": "Insert Article Text",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        3792,
        1312
      ],
      "parameters": {
        "actionsUi": {
          "actionFields": [
            {
              "text": "={{ $('Merge Humanized With Topic').item.json.article_text }}",
              "action": "insert"
            }
          ]
        },
        "operation": "update",
        "documentURL": "=https://docs.google.com/document/d/{{ $json.id }}/edit"
      },
      "typeVersion": 2
    },
    {
      "id": "65696e37-084f-4db6-91e9-c818222b01af",
      "name": "Set Doc Permissions",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        3952,
        1312
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.documentId }}"
        },
        "options": {},
        "operation": "share",
        "permissionsUi": {
          "permissionsValues": {
            "role": "reader",
            "type": "anyone"
          }
        }
      },
      "typeVersion": 3
    },
    {
      "id": "36806f36-4389-4af4-b690-47f0f96c5b5b",
      "name": "Attach Doc Link",
      "type": "n8n-nodes-base.code",
      "position": [
        4160,
        1312
      ],
      "parameters": {
        "jsCode": "const topic = $('Merge Humanized With Topic').first().json;\nreturn [{ json: {\n  id: topic.id,\n  title: topic.title,\n  format: topic.format,\n  hook: topic.hook,\n  article_text: topic.article_text,\n  doc_url: 'https://docs.google.com/document/d/' + $('Create Article Doc').first().json.id + '/edit'\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "a2674963-ddfb-4971-877f-af449ae27d00",
      "name": "Merge Draft With Topic",
      "type": "n8n-nodes-base.code",
      "position": [
        2704,
        1312
      ],
      "parameters": {
        "jsCode": "const topic = $('Limit To One Topic').first().json;\nreturn [{ json: {\n  id: topic.id,\n  title: topic.title,\n  format: topic.format,\n  hook: topic.hook,\n  draft_text: $json.output\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "39580791-393e-41d2-aced-99da39429168",
      "name": "Update status & log",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        6800,
        1312
      ],
      "parameters": {
        "columns": {
          "value": {
            "id": "={{ $json.id }}",
            "status": "ready",
            "doc_url": "={{ $('Attach Doc Link').first().json.doc_url }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ]
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Topics"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Search Spreadsheet').first().json.id }}"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "50b9316f-6ed2-4416-8eff-0b2fe2aaed65",
      "name": "Aggregate Image Results",
      "type": "n8n-nodes-base.code",
      "position": [
        6208,
        1312
      ],
      "parameters": {
        "jsCode": "const rows = $input.all();\nconst topicId = rows[0]?.json.article_id || $('Merge Humanized With Topic').first().json.id;\nreturn [{ json: { id: topicId, images_done: rows.length } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "a9aa99bf-0647-46ce-8347-8b2095a68734",
      "name": "Append Image Row",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        5984,
        1312
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "article_id",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "article_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "scene",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "scene",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "image_prompt",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "image_prompt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "file_url",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "file_url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Images"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Search Spreadsheet').first().json.id }}"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "594a41a1-30c8-4aac-9b43-db35a440380b",
      "name": "Prepare Image Row",
      "type": "n8n-nodes-base.code",
      "position": [
        5760,
        1312
      ],
      "parameters": {
        "jsCode": "const prompts = $('Parse Image Prompts').all();\nconst files = $input.all();\nreturn files.map((f, i) => ({ json: {\n  article_id: prompts[i].json.article_id,\n  scene: prompts[i].json.scene,\n  image_prompt: prompts[i].json.image_prompt,\n  file_url: f.json.webViewLink,\n  status: 'done'\n} }));"
      },
      "typeVersion": 2
    },
    {
      "id": "122562d2-e04c-487b-9e4f-85dd633fdaeb",
      "name": "Set File Permissions",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        5536,
        1312
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.id }}"
        },
        "options": {},
        "operation": "share",
        "permissionsUi": {
          "permissionsValues": {
            "role": "reader",
            "type": "anyone"
          }
        }
      },
      "typeVersion": 3
    },
    {
      "id": "ae7caa29-c573-4a1e-a7f7-5035c33ca6a3",
      "name": "Upload to Drive",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        5312,
        1312
      ],
      "parameters": {
        "name": "={{ $json.article_id }}_{{ $json.image_index }}.png",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Create Article Folder').first().json.id }}"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "d56919ad-0e96-472f-8aea-c9b7a3e0d9ce",
      "name": "Decode Base64 To Binary",
      "type": "n8n-nodes-base.code",
      "position": [
        5088,
        1312
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst out = [];\nfor (const item of items) {\n  const img = item.json.images?.[0] || item.json.data?.[0];\n  if (!img) continue;\n  const b64 = (img.b64_json || img.url || '').replace(/^data:image\\/\\w+;base64,/, '');\n  const buffer = Buffer.from(b64, 'base64');\n  out.push({\n    json: item.json,\n    binary: { data: await this.helpers.prepareBinaryData(buffer, `${item.json.article_id}_${item.json.image_index}.png`, 'image/png') }\n  });\n}\nreturn out;"
      },
      "typeVersion": 2
    },
    {
      "id": "eb57bfc1-8f34-4a42-9620-46bc3bf3afe0",
      "name": "Generate images (OpenRouter)",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        4880,
        1312
      ],
      "parameters": {
        "url": "https://openrouter.ai/api/v1/images",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"model\": \"google/gemini-3.1-flash-lite-image\",\n  \"prompt\": {{ JSON.stringify($json.image_prompt) }},\n  \"n\": 1,\n  \"aspect_ratio\": \"1:1\",\n  \"resolution\": \"1K\"\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openRouterApi"
      },
      "typeVersion": 4.5
    },
    {
      "id": "d5558c6c-51a8-47c2-b867-0ad8af955365",
      "name": "Parse Image Prompts",
      "type": "n8n-nodes-base.code",
      "position": [
        4688,
        1312
      ],
      "parameters": {
        "jsCode": "const topic = $('Merge Humanized With Topic').first().json;\nconst raw = $input.first().json.output;\nconst clean = String(raw).replace(/```json|```/g, '').trim();\nconst scenes = JSON.parse(clean);\nreturn scenes.map((s, idx) => ({ json: {\n  article_id: topic.id,\n  article_title: topic.title,\n  scene: s.scene,\n  image_prompt: s.image_prompt,\n  image_index: idx\n} }));"
      },
      "typeVersion": 2
    },
    {
      "id": "5c7b6796-9685-40dd-86e3-72d908177a8a",
      "name": "Generate image prompts",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        4400,
        1312
      ],
      "parameters": {
        "text": "=Article text: {{ $json.article_text }}",
        "options": {
          "systemMessage": "Analyze the article and identify a MAXIMUM of 2 scenes to illustrate \u2014 the most visually rich and dramatic moments in the narrative (not everyday details, but climactic episodes: a specific action, a conflict, a turning point).\n\nIf the article covers several people, pick the 2 most striking episodes as a priority, even if there are more than 2 people (don't try to cover everyone).\n\nFor each scene, write an image-generation prompt in English, in a consistent style \u2014 always append this exact string to the end of every image_prompt: \"warm natural lighting, painterly historical illustration, soft muted color palette, no text overlay\".\n\nReply strictly as a JSON array, with no markdown wrapper: [{\"scene\":\"short description in Russian\",\"image_prompt\":\"detailed prompt in English\"}]\n\nNote: the \"scene\" field must be written in Russian (to match the source article), the \"image_prompt\" field must be in English (as required by the image generation model)."
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "e2320152-0f5e-4f9c-b475-691c2d14ec5e",
      "name": "LLM: Image Prompts (GPT-5)",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        4400,
        1488
      ],
      "parameters": {
        "model": "openai/gpt-5",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f5527aee-d660-455e-aac7-55d14a228ec6",
      "name": "Get Folder IDs",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1056,
        1328
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "State"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Search Spreadsheet').first().json.id }}"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "0200b055-6254-42e1-a4e6-6ea41bbee7cf",
      "name": "Search Spreadsheet",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        624,
        1344
      ],
      "parameters": {
        "filter": {
          "includeTrashed": false
        },
        "options": {},
        "resource": "fileFolder",
        "queryString": "Zen_Content_DB"
      },
      "typeVersion": 3,
      "alwaysOutputData": true
    },
    {
      "id": "4379a4ff-d4c5-4a49-8b4c-328aba4098d7",
      "name": "Validate Folder IDs",
      "type": "n8n-nodes-base.if",
      "position": [
        1264,
        1328
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-articles-folder",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.articles_folder_id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "c0666f24-4e5d-40f4-b383-bdf61ac2fea6",
      "name": "Stop: Missing Folder IDs",
      "type": "n8n-nodes-base.stopAndError",
      "position": [
        1376,
        1600
      ],
      "parameters": {
        "errorMessage": "articles_folder_id is empty in the State sheet. Run the Weekly Topics workflow (its Bootstrap branch) first to create the folder structure and record its ID before running Daily Generation."
      },
      "typeVersion": 1
    },
    {
      "id": "fe55a3d6-0bc6-4d09-9155-30fb559f78cb",
      "name": "Create Article Folder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        3472,
        1312
      ],
      "parameters": {
        "name": "={{ $json.id }} - {{ $json.title }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Get Folder IDs').first().json.articles_folder_id }}"
        },
        "resource": "folder"
      },
      "typeVersion": 3
    },
    {
      "id": "71348148-6bbd-4660-926f-9fb344838919",
      "name": "Has New Topics?",
      "type": "n8n-nodes-base.if",
      "position": [
        1872,
        1328
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "25cfdc3a-680e-4fc1-a5cf-aed9043fde3e",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "a139a6cb-581b-4017-b7af-9e2abf1854fc",
      "name": "Spreadsheet Found?",
      "type": "n8n-nodes-base.if",
      "position": [
        816,
        1344
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "25cfdc3a-680e-4fc1-a5cf-aed9043fde3e",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1fac0c2e-8fd1-4e60-b510-fe57fde5c651",
      "name": "Weekly Schedule (Mon 09:00)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        624,
        2736
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "e4dc0000-760b-43fd-bc5c-aaff294601da",
      "name": "Search files and folders",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        928,
        2736
      ],
      "parameters": {
        "filter": {
          "includeTrashed": false
        },
        "options": {},
        "resource": "fileFolder",
        "queryString": "Zen_Content_DB"
      },
      "typeVersion": 3,
      "alwaysOutputData": true
    },
    {
      "id": "726a32a9-142a-4496-a78d-ed5a4ccfffd9",
      "name": "Set Spreadsheet ID (Found)",
      "type": "n8n-nodes-base.set",
      "position": [
        1280,
        2528
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "sid-1",
              "name": "spreadsheet_id",
              "type": "string",
              "value": "={{ $json.id }}"
            }
          ]
        }
      },
      "typeVersion": 3.5
    },
    {
      "id": "ace58381-7b20-44d2-aeac-89b314bc704d",
      "name": "Create Spreadsheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1264,
        2928
      ],
      "parameters": {
        "title": "Zen_Content_DB",
        "options": {},
        "resource": "spreadsheet",
        "sheetsUi": {
          "sheetValues": [
            {
              "title": "Topics"
            },
            {
              "title": "Published"
            },
            {
              "title": "Images"
            },
            {
              "title": "State"
            }
          ]
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "b1a9e35d-722e-417a-b804-e1100397b1cd",
      "name": "Prepare Topics Header Row",
      "type": "n8n-nodes-base.set",
      "position": [
        1808,
        2848
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "prep-topics-header-f0",
              "name": "id",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-topics-header-f1",
              "name": "title",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-topics-header-f2",
              "name": "format",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-topics-header-f3",
              "name": "hook",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-topics-header-f4",
              "name": "status",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-topics-header-f5",
              "name": "created_date",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-topics-header-f6",
              "name": "doc_url",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.5
    },
    {
      "id": "678af636-b02c-4c1f-9db0-1ffa12f13b0c",
      "name": "Seed Topics Header",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2032,
        2848
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Topics"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Create Spreadsheet').item.json.spreadsheetId }}"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "4eb1b474-6e2b-48a0-9f4d-302be171edeb",
      "name": "Prepare Published Header Row",
      "type": "n8n-nodes-base.set",
      "position": [
        1808,
        3008
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "prep-published-header-f0",
              "name": "id",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-published-header-f1",
              "name": "title",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-published-header-f2",
              "name": "publish_date",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-published-header-f3",
              "name": "link",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-published-header-f4",
              "name": "format",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.5
    },
    {
      "id": "fbdaacee-2c80-4f7c-9766-8806ecd17374",
      "name": "Seed Published Header",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2032,
        3008
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Published"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Create Spreadsheet').item.json.spreadsheetId }}"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "1ba71b3e-c2a2-42ff-abc4-31bb8451e982",
      "name": "Prepare Images Header Row",
      "type": "n8n-nodes-base.set",
      "position": [
        1808,
        3168
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "prep-images-header-f0",
              "name": "article_id",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-images-header-f1",
              "name": "scene",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-images-header-f2",
              "name": "image_prompt",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-images-header-f3",
              "name": "file_url",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-images-header-f4",
              "name": "status",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.5
    },
    {
      "id": "493bd94f-9c05-4798-b83e-4723143ea1f5",
      "name": "Seed Images Header",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2032,
        3168
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Images"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Create Spreadsheet').item.json.spreadsheetId }}"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "40a98f5a-fe37-47ee-8315-baec14710167",
      "name": "Prepare State Row",
      "type": "n8n-nodes-base.set",
      "position": [
        1808,
        3328
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "prep-state-row-f0",
              "name": "state_id",
              "type": "string",
              "value": "1"
            },
            {
              "id": "prep-state-row-f1",
              "name": "last_topic_gen_date",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-state-row-f2",
              "name": "last_content_gen_date",
              "type": "string",
              "value": ""
            },
            {
              "id": "prep-state-row-f3",
              "name": "topics_in_queue",
              "type": "string",
              "value": "==COUNTIF(Topics!E:E;\"new\")"
            },
            {
              "id": "prep-state-row-f4",
              "name": "total_published",
              "type": "string",
              "value": "==COUNTA(Published!A2:A)"
            },
            {
              "id": "a7645707-777e-443c-8665-15a04356aade",
              "name": "articles_folder_id",
              "type": "string",
              "value": "={{ $('Create Articles Folder').item.json.id }}"
            }
          ]
        }
      },
      "typeVersion": 3.5
    },
    {
      "id": "9bf525dd-a2de-4882-96b7-dbe2fe80288b",
      "name": "Seed State Row",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2032,
        3328
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "State"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Create Spreadsheet').item.json.spreadsheetId }}"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "dc8892d9-c9fa-4b21-baaf-b49145f311d7",
      "name": "Wait (Drive index)",
      "type": "n8n-nodes-base.wait",
      "position": [
        2256,
        3328
      ],
      "parameters": {
        "amount": 3
      },
      "typeVersion": 1.1
    },
    {
      "id": "ca898afb-f6c3-454c-8735-9cd6226a65ad",
      "name": "Get Published Titles",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1552,
        2416
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Published"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.spreadsheet_id }}"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "205b51fe-8faf-4c12-a7c8-74876f857a30",
      "name": "Get Existing Queue Titles",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1552,
        2656
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Topics"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.spreadsheet_id }}"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "1d990f1c-c875-4c24-862d-2c25422e8757",
      "name": "Merge/Aggregate Titles",
      "type": "n8n-nodes-base.merge",
      "position": [
        1808,
        2528
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "d3e90c59-7f11-498e-9a7b-72f20030ef94",
      "name": "Build Exclude List",
      "type": "n8n-nodes-base.code",
      "position": [
        2032,
        2528
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst titles = items.map(i => i.json.title).filter(Boolean);\nreturn [{ json: { excludeList: titles.length ? titles.join('; ') : 'no data' } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "7b0b90e5-1933-4b31-8552-8004de015e01",
      "name": "AI: Generate Topics",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2992,
        2528
      ],
      "parameters": {
        "text": "Generate 10 new topics per the instructions in the system message.",
        "options": {
          "systemMessage": "=Here is a list of real historical figures from Wikipedia (may be in English): {{ $json.names }}\n\nYou are the content strategist for a Zen channel about historical events and little-known facts about women. Your task is to choose 10 people FROM THIS LIST ONLY (don't invent others) and craft an article topic for each, in Russian.\n\nSelection criteria among the people offered:\n- Pick the ones whose biographies are the most surprising and least known to a general Russian-speaking audience\n- Prioritize topics with a specific, unexpected fact rather than a generic \"she was the first woman to...\"\n- If there's a standard Russian spelling or translation of the name, use it; otherwise transliterate sensibly\n\nUse a mix of formats:\n- 3 roundups (\"N women who...\", \"N facts about...\")\n- 3 individual stories (one specific woman's fate, dramatic arc)\n- 2 myth-busting/expos\u00e9 pieces (\"the myth of...\", \"why it wasn't actually like that\")\n- 2 comparisons (\"how she's portrayed vs. how it really was\", \"before and after\")\n\nAlready-used topics \u2014 don't pick the same people again and don't repeat the substance (not just the wording): {{ $('Build Exclude List').item.json.excludeList }}\n\nReply strictly as a JSON array, with no markdown wrapper: [{\"title\":\"...\",\"format\":\"...\",\"hook\":\"...\"}]\n\nNote: title, format, and hook must all be written in Russian \u2014 only these instructions are in English."
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "293bda2c-3e41-4c22-b2da-3e2a16eace74",
      "name": "Parse JSON Response",
      "type": "n8n-nodes-base.code",
      "position": [
        3392,
        2528
      ],
      "parameters": {
        "jsCode": "const raw = $input.first().json.output;\nconst clean = String(raw).replace(/```json|```/g, '').trim();\nconst topics = JSON.parse(clean);\nreturn topics.map(t => ({ json: t }));"
      },
      "typeVersion": 2
    },
    {
      "id": "2a388841-e779-4dc0-aac4-9ee1925fc151",
      "name": "Prepare Topic Row",
      "type": "n8n-nodes-base.set",
      "position": [
        3856,
        2528
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "row-id",
              "name": "id",
              "type": "string",
              "value": "={{ $now.toMillis() }}-{{ $itemIndex }}"
            },
            {
              "id": "row-status",
              "name": "status",
              "type": "string",
              "value": "new"
            },
            {
              "id": "row-date",
              "name": "created_date",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.5
    },
    {
      "id": "5a9d8ef0-a864-4bec-82b7-fda193d44d18",
      "name": "Append to Topics Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        4096,
        2528
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "id",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "title",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "format",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "format",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "hook",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "hook",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "created_date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "created_date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [
            "id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Topics"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Spreadsheet ID (Found)').first().json.spreadsheet_id || $('Create Spreadsheet').first().json.spreadsheetId }}"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "2385e4ae-51c3-42c3-91d4-cd83aa0f01fc",
      "name": "Update State Row",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        4208,
        2192
      ],
      "parameters": {
        "columns": {
          "value": {
            "state_id": "1",
            "row_number": 0,
            "last_topic_gen_date": "={{ $now.toISO() }}"
          },
          "schema": [
            {
              "id": "state_id",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "state_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "last_topic_gen_date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "last_topic_gen_date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "last_content_gen_date",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "last_content_gen_date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "topics_in_queue",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "topics_in_queue",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "total_published",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "total_published",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "state_id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "State"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Spreadsheet ID (Found)').first().json.spreadsheet_id || $('Create Spreadsheet').first().json.spreadsheetId }}"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "92fbf07f-99a0-48eb-8402-1ed3078b55d0",
      "name": "Table Exists?1",
      "type": "n8n-nodes-base.if",
      "position": [
        1120,
        2736
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "b777ea30-c1be-4f4a-bbd4-bd3ce016884b",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "866607f1-51f0-4f5d-a31b-29032d959103",
      "name": "Filter Duplicate Topics",
      "type": "n8n-nodes-base.code",
      "position": [
        3632,
        2528
      ],
      "parameters": {
        "jsCode": "// Stop-words to ignore when comparing similarity.\n// NOTE: kept in Russian on purpose \u2014 this filters Russian-language topic titles\n// (the channel publishes in Russian), so the stopword list and the regex charset\n// below must stay Russian for the matching to work correctly.\nconst stopWords = new Set([\n  '\u043a\u0430\u043a', '\u0447\u0442\u043e', '\u044d\u0442\u043e', '\u043e\u043d\u0430', '\u043e\u043d', '\u0435\u0451', '\u0438\u0445', '\u0434\u043b\u044f', '\u043f\u0440\u0438', '\u0438\u043b\u0438',\n  '\u043d\u0430', '\u0438\u0437', '\u0434\u043e', '\u043f\u043e', '\u043d\u0435', '\u043d\u043e', '\u0442\u043e', '\u0430', '\u0438', '\u0432', '\u0441', '\u0443',\n  '\u0436\u0435\u043d\u0449\u0438\u043d', '\u0436\u0435\u043d\u0449\u0438\u043d\u0430', '\u0436\u0435\u043d\u0449\u0438\u043d\u044b', '\u0438\u0441\u0442\u043e\u0440\u0438\u044f', '\u043a\u043e\u0442\u043e\u0440\u044b\u0439', '\u043a\u043e\u0442\u043e\u0440\u044b\u0435', '\u043a\u043e\u0442\u043e\u0440\u0430\u044f'\n]);\n\nfunction getKeywords(text) {\n  return text\n    .toLowerCase()\n    .replace(/[^\u0430-\u044f\u0451\\s]/gi, ' ')\n    .split(/\\s+/)\n    .filter(w => w.length > 3 && !stopWords.has(w));\n}\n\nfunction similarity(words1, words2) {\n  const set1 = new Set(words1);\n  const set2 = new Set(words2);\n  const intersection = [...set1].filter(w => set2.has(w));\n  const union = new Set([...set1, ...set2]);\n  return union.size ? intersection.length / union.size : 0;\n}\n\n// Pull the already-used topics from the upstream node\nconst excludeRaw = $('Build Exclude List').first().json.excludeList;\nconst existingTitles = excludeRaw === 'no data' ? [] : excludeRaw.split('; ').filter(Boolean);\nconst existingKeywords = existingTitles.map(getKeywords);\n\nconst items = $input.all();\nconst seenInThisBatch = [];\nconst result = [];\n\nfor (const item of items) {\n  const title = item.json.title || '';\n  const kw = getKeywords(title);\n\n  // Compare against both already-published/queued topics and topics from this same batch\n  const isDuplicate = [...existingKeywords, ...seenInThisBatch].some(\n    existing => similarity(kw, existing) > 0.4\n  );\n\n  if (!isDuplicate) {\n    seenInThisBatch.push(kw);\n    result.push(item);\n  }\n}\n\nreturn result;"
      },
      "typeVersion": 2
    },
    {
      "id": "75fb99fa-c322-4024-8617-bbc57a351ea9",
      "name": "Pick Random Category",
      "type": "n8n-nodes-base.code",
      "position": [
        2256,
        2528
      ],
      "parameters": {
        "jsCode": "const categories = [\n  '16th-century women rulers',           // 66\n  '16th-century women scientists',       // 17\n  '17th-century women scientists',       // 10\n  '18th-century English women writers',  // 229\n  '18th-century French women writers',   // 109\n  '18th-century English women artists',  // 79\n  '18th-century women rulers',           // 61\n  '18th-century women regents',          // 41\n  '19th-century women physicians',       // 27\n  '19th-century women inventors',        // 35\n  '19th-century American women scientists', // 106\n  '19th-century British women scientists',  // 52\n  '19th-century American women physicians', // 165\n  '19th-century American women inventors'   // 57\n];\nconst picked = categories[Math.floor(Math.random() * categories.length)];\nreturn [{ json: { category: picked } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "b5ffa143-6bc4-4d7e-8a41-7ade118b9c14",
      "name": "Get Category Members",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2496,
        2528
      ],
      "parameters": {
        "url": "={{ 'https://en.wikipedia.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:' + encodeURIComponent($json.category) + '&cmlimit=100&format=json' }}",
        "options": {}
      },
      "typeVersion": 4.5
    },
    {
      "id": "c616ce4e-3153-4e92-be57-ac534d09f525",
      "name": "Pick Random Batch + Filter Excluded",
      "type": "n8n-nodes-base.code",
      "position": [
        2736,
        2528
      ],
      "parameters": {
        "jsCode": "const members = $json.query.categorymembers.map(m => m.title);\nconst excludeRaw = $('Build Exclude List').first().json.excludeList;\nconst excluded = excludeRaw === 'no data' ? [] : excludeRaw.toLowerCase();\n\nconst fresh = members.filter(name => !excluded.includes(name.toLowerCase().split(' ')[0]));\nconst shuffled = fresh.sort(() => Math.random() - 0.5).slice(0, 15);\n\nreturn [{ json: { names: shuffled.join('; ') } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "b046ec57-9c91-4939-ab2e-a066f5688f1e",
      "name": "Create Articles Folder",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        1520,
        2928
      ],
      "parameters": {
        "name": "Zen_Content_Articles",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "root",
          "cachedResultName": "/ (Root folder)"
        },
        "resource": "folder"
      },
      "typeVersion": 3
    },
    {
      "id": "ed1f48af-07ff-4960-9bcc-08d84d1aafad",
      "name": "LLM: Generate Topics (Claude Sonnet 4.5)",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        2992,
        2704
      ],
      "parameters": {
        "model": "anthropic/claude-sonnet-4.5",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "fdddfb5e-903b-4c11-a7ba-9b22b6be9c29",
      "name": "Prepare Published Row",
      "type": "n8n-nodes-base.set",
      "position": [
        6400,
        1312
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "pub-id",
              "name": "id",
              "type": "string",
              "value": "={{ $('Merge Humanized With Topic').first().json.id }}"
            },
            {
              "id": "pub-title",
              "name": "title",
              "type": "string",
              "value": "={{ $('Merge Humanized With Topic').first().json.title }}"
            },
            {
              "id": "pub-date",
              "name": "publish_date",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            },
            {
              "id": "pub-link",
              "name": "link",
              "type": "string",
              "value": "={{ $('Attach Doc Link').first().json.doc_url }}"
            },
            {
              "id": "pub-format",
              "name": "format",
              "type": "string",
              "value": "={{ $('Merge Humanized With Topic').first().json.format }}"
            }
          ]
        }
      },
      "typeVersion": 3.5
    },
    {
      "id": "86bc19b4-cd5e-4272-90dd-522b06fcf38d",
      "name": "Append to Published Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        6608,
        1312
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "autoMapInputData",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Published"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Search Spreadsheet').first().json.id }}"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "e90b50d0-1961-43b2-96e0-826f7c98a57d",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        576,
        864
      ],
      "parameters": {
        "color": "#F59E0B",
        "width": 560,
        "height": 384,
        "content": "\ud83d\udd0d INFRASTRUCTURE CHECK\n\nWe look up the Zen_Content_DB spreadsheet by name (the \"Include Trashed Items\" filter is OFF \u2014 otherwise you can hit a 404 on a deleted copy sitting in Trash).\n\nSpreadsheet Found?\n  true  \u2192 read the folder ID from the State sheet\n  false \u2192 go to the Bootstrap branch (Search files and folders / Table Exists?1), which creates the spreadsheet + sheets + folder from scratch\n\nValidate Folder IDs checks that articles_folder_id is actually filled in on State \u2014 if it's empty, we stop with a clear error (Stop: Missing Folder IDs) instead of failing later, deeper in the chain, on Upload to Drive / Create Article Doc."
      },
      "typeVersion": 1
    },
    {
      "id": "c9d949af-7130-4b96-9464-0b89e75de3e6",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1280,
        864
      ],
      "parameters": {
        "color": "#F59E0B",
        "width": 544,
        "height": 336,
        "content": "\ud83d\udccb TOPIC QUEUE\n\nRead all topics from Topics, filter by status=new.\n\nHas New Topics?\n  true  \u2192 take the first topic (Limit To One Topic) and write the article\n  false \u2192 DELIBERATE design choice: falls through to Search files and folders, i.e. it automatically triggers new-topic generation (the Weekly logic) right here, without waiting for Monday. If the queue is empty, fresh topics get generated and the first one is picked up immediately."
      },
      "typeVersion": 1
    },
    {
      "id": "dedf59b2-a202-45a7-9968-840acf93432f",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2048,
        864
      ],
      "parameters": {
        "color": "#3B82F6",
        "width": 576,
        "height": 352,
        "content": "\u270d\ufe0f DRAFT GENERATION\n\nTake exactly 1 topic from the queue (Limit To One Topic \u2014 otherwise Daily would write articles for every remaining topic in one run).\n\nDraft article writes to a prompt that requires:\n- at least 5-6 scenes, no invented quotes\n- correct feminine grammatical gender throughout\n- banned AI phrasing (\"not just X \u2014 it's Y\" etc.)\n- length follows from depth of coverage, not the other way around\n\nModel: Claude Haiku 4.5 (via the LLM node right below) \u2014 fast and cheap, but still reliable at following detailed instructions."
      },
      "typeVersion": 1
    },
    {
      "id": "81379fbf-e160-429a-8a58-4d911d6567ba",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2704,
        864
      ],
      "parameters": {
        "color": "#3B82F6",
        "width": 544,
        "height": 384,
        "content": "\ud83c\udfad \"HUMANIZING\" (2nd pass)\n\nMerge Draft With Topic \u2014 a technical glue step: the Agent node only outputs {output}, dropping id/title/hook. This Code node re-attaches them, otherwise Update status & log later has nothing to match on.\n\nHumanize draft rewrites the text:\n- rhythm (short/long sentences mixed together)\n- max 1 dash per paragraph (a very common AI tell)\n- removes stock superlatives (\"one of the most...\")\n- final check for gender agreement + character/encoding integrity\n\nModel: Claude Haiku 4.5. Verified on dzen.guru/detector: holds a stable 3-7% AI score."
      },
      "typeVersion": 1
    },
    {
      "id": "dc02fd0a-293d-4368-92f4-542c9145f056",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3472,
        608
      ],
      "parameters": {
        "color": "#EC4899",
        "width": 576,
        "height": 640,
        "content": "\ud83d\udcc1 FOLDER + DOC CREATION\n\nMerge Humanized With Topic \u2014 same glue pattern as after Draft article: collects id/title/format/hook plus the final article_text into one item.\n\nCreate Article Folder \u2192 creates a subfolder \"{id} - {title}\" inside Zen_Content_Articles \u2014 this is where both the Doc and all of this article's images will live together (not split into separate Images/Articles roots).\n\nCreate Article Doc \u2192 the title comes from the topic's title field. \u26a0\ufe0f The Title field must be set to Expression mode, not Fixed \u2014 otherwise the doc gets created as \"Untitled\" (this bug has bitten us once already).\n\nInsert Article Text \u2192 the field is called \"documentURL\" but it needs a full URL (https://docs.google.com/document/d/{id}/edit), not a bare ID.\n\nSet Doc Permissions \u2192 shares the file via link. \u26a0\ufe0f On this step the document ID field is called \"documentId\" (not \"id\" \u2014 different Google Docs API operations return the ID under different field names; always check the real input, don't rely on memory).\n\nAttach Doc Link \u2192 assembles the final Doc URL, which flows into Generate image prompts and finally into Published/Topics."
      },
      "typeVersion": 1
    },
    {
      "id": "5cfc8a1b-87bc-4029-a5e9-0a80a0cda87c",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4384,
        608
      ],
      "parameters": {
        "color": "#3B82F6",
        "width": 608,
        "height": 640,
        "content": "\ud83c\udfa8 IMAGE GENERATION\n\nGenerate image prompts \u2014 picks a MAXIMUM of 2 scenes from the text (test mode). Priority goes to the most vivid/dramatic moments, not mechanically \"one per person\".\n\nEvery image_prompt always ends with a fixed style string: \"warm natural lighting, painterly historical illustration, soft muted color palette, no text overlay\" \u2014 this keeps the channel's visual style consistent.\n\nParse Image Prompts \u2192 splits the JSON array into separate items (1 scene = 1 item), each of which then flows through the rest of the chain independently.\n\nGenerate images (OpenRouter) \u2192 POST /api/v1/images, model: google/gemini-3.1-flash-lite-image (\"Nano Banana 2 Lite\"). The response is base64 inside JSON, not a ready-made file (see the next node \u2014 Decode Base64 To Binary \u2014 without it, Upload to Drive won't accept the data).\n\n\u26a0\ufe0f Raising the scene limit (currently 2) also raises generation spend accordingly \u2014 scale the number up with your budget in mind."
      },
      "typeVersion": 1
    },
    {
      "id": "3ff8502e-3e65-43c2-a4aa-8eeb70bf18ca",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5184,
        848
      ],
      "parameters": {
        "color": "#EC4899",
        "width": 560,
        "height": 432,
        "content": "\ud83d\udcbe SAVING THE IMAGES\n\nDecode Base64 To Binary \u2192 OpenRouter returns the image as base64 inside a JSON payload; this node decodes it into an actual binary file (without this step, Google Drive refuses the data).\n\nUpload to Drive \u2192 uploads into the article's own folder (Create Article Folder), not a separate Images folder \u2014 the whole \"article + images\" structure lives in one place.\n\nSet File Permissions \u2192 shares \"anyone with the link\" (reader), so the file is downloadable at publish time.\n\nPrepare Image Row / Append Image Row \u2192 writes to the Images sheet (article_id, scene, image_prompt, file_url, status) \u2014 one row per image, run once per item (2-3 times per run, depending on how many images were generated)."
      },
      "typeVersion": 1
    },
    {
      "id": "edb2e2a6-27dc-4a34-89c2-8f710d3084a9",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6160,
        848
      ],
      "parameters": {
        "color": "#EC4899",
        "width": 608,
        "height": 432,
        "content": "\u2705 FINISHING THE ARTICLE\n\nAggregate Image Results \u2192 collapses N image items back into a single item (needed because the next writes go to Topics/Published, which should get exactly 1 row per article, not N).\n\nPrepare Published Row / Append to Published Sheet \u2192 added later in development (originally missing \u2014 articles only ever showed up in Images, and Published stayed empty). Writes id/title/publish_date/link/format \u2014 the real data source behind =COUNTA(Published!A2:A) in State.\n\nUpdate status & log \u2192 the final step: sets Topics.status \u2192 \"ready\" and writes doc_url. Matched strictly by id (matchingColumns).\n\nThis is the end of the chain \u2014 execution stops here; the next run starts over from Get next topic."
      },
      "typeVersion": 1
    },
    {
      "id": "926acc73-194d-4f3a-a07d-6e0e77aaca85",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        256,
        2928
      ],
      "parameters": {
        "color": "#F59E0B",
        "width": 576,
        "height": 432,
        "content": "\ud83d\udd0e SPREADSHEET LOOKUP (Weekly path)\n\nOnce a week (Mon 09:00) we check whether Zen_Content_DB exists.\n\nSearch files and folders \u2192 the \"Include Trashed Items\" filter is OFF (mandatory! without it, the search can pick up a deleted copy from Trash \u2192 404 further down the chain \u2014 we've hit this error more than once).\n\nTable Exists?1\n  true  \u2192 the spreadsheet exists, go straight to Set Spreadsheet ID (Found)\n  false \u2192 build everything from scratch (right-hand branch)\n\nThis duplicates Search Spreadsheet in the Daily workflow \u2014 kept on purpose as a second, independent entry point (two different triggers, each with its own check)."
      },
      "typeVersion": 1
    },
    {
      "id": "c5bafb8e-3c41-440e-af15-1cbf716b5336",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2416,
        2864
      ],
      "parameters": {
        "color": "#10B981",
        "width": 576,
        "height": 672,
        "content": "\ud83c\udfd7\ufe0f BUILD FROM SCRATCH (false branch)\n\nOnly fires if Zen_Content_DB wasn't found.\n\nCreate Spreadsheet \u2192 creates the file with all 4 sheets at once (Topics/Published/Images/State).\n\nCreate Articles Folder \u2192 ONE root folder on Drive (used to be two \u2014 Images + Articles; simplified to one: per-article subfolders get created inside it, holding both the Doc and the images together).\n\nPrepare/Seed [Topics|Published|Images] Header Row \u2192 write the column headers into each sheet, row by row.\n\nPrepare State Row \u2192 default values + live formulas (=COUNTIF/=COUNTA \u2014 Value Input Mode on Seed State Row MUST be set to \"User Entered,\" otherwise the formulas get written as plain text) + articles_folder_id.\n\nWait (Drive index) \u2192 a short pause before searching again (we've hit a race condition here with too short a delay \u2014 if you get a 404 right after creation again, increase this).\n\nAt the end, this loops back to Search files and folders to find the spreadsheet that was just created and take the true branch."
      },
      "typeVersion": 1
    },
    {
      "id": "7c9a18b4-1ea7-4f2f-b658-4f9574e5e878",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1520,
        1760
      ],
      "parameters": {
        "color": "#F59E0B",
        "width": 608,
        "height": 592,
        "content": "\ud83d\udcd6 READING EXISTING TOPICS\n\nSet Spreadsheet ID (Found) \u2192 a single convergence point: works whether the spreadsheet was just found or just created (dual fallback for the two different entry paths).\n\nGet Published Titles / Get Existing Queue Titles \u2192 read already-published topics plus topics already sitting in the queue \u2014 needed to prevent duplicates before generating new ones.\n\n\u26a0\ufe0f Sheet is selected \"By Name,\" NOT \"From list\" \u2014 \"From list\" mode caches a numeric gid, which breaks the moment the spreadsheet gets recreated (a different file has a different gid). \"By Name\" resolves it fresh every run.\n\nMerge/Aggregate Titles \u2192 simply combines both lists into items.\n\nBuild Exclude List \u2192 collapses everything into one item's excludeList field (a \"; \"-joined string) \u2014 this is what actually goes into the AI agent's prompt as \"don't repeat these topics.\"\n\nNext comes the Wikipedia grounding step (Pick Random Category onward) \u2014 without it the AI kept looping on the same 5 \"viral\" historical figures."
      },
      "typeVersion": 1
    },
    {
      "id": "1a0a0f83-4aa4-4877-b365-cc370c56e2cd",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2384,
        1760
      ],
      "parameters": {
        "color": "#F59E0B",
        "width": 512,
        "height": 592,
        "content": "\ud83c\udf10 WIKIPEDIA REQUEST\n\nGet Category Members \u2192 GET to en.wikipedia.org/w/api.php, using the category chosen in the previous step (Pick Random Category). \u26a0\ufe0f The URL is built with encodeURIComponent \u2014 categories like \"18th-century English women writers\" contain spaces.\n\nMany \"women\" categories are umbrella containers with very few articles directly in them (most of the real content lives in narrower century+country subcategories). The category list in Pick Random Category is already calibrated against genuinely populated options (10-200+ articles) \u2014 don't pick new categories by guesswork, it's easy to land on an empty one (happened with \"Women explorers\").\n\nPick Random Batch + Filter Excluded \u2192 grabs ~15 random names from the API response, strikes out any already in excludeList. This is the filter ON THE WAY IN \u2014 the model never even sees already-used people among the candidates."
      },
      "typeVersion": 1
    },
    {
      "id": "a8b4bffd-f5c0-405b-9cdf-a00c7bd1fb2d",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3008,
        1760
      ],
      "parameters": {
        "color": "#3B82F6",
        "width": 512,
        "height": 592,
        "content": "\ud83e\udd16 TOPIC GENERATION + FILTERING\n\nAI: Generate Topics \u2192 the prompt explicitly requires picking FROM the Wikipedia pool (names), not making things up. \u26a0\ufe0f System Message must be in Expression mode, not Fixed \u2014 otherwise {{ $json.names }} goes to the model as literal text (we've hit this bug before; the model then ignored the pool entirely and wrote the same worn-out stories \u2014 Cleopatra/Wu Zetian on repeat).\n\nParse JSON Response \u2192 parses the agent's response (the output field, not content[0].text \u2014 those are different shapes; an agent node isn't the same as a raw API call).\n\nFilter Duplicate Topics \u2192 the SECOND line of defense against duplicates (after the Wikipedia randomization on the input side). Jaccard similarity on title keywords, threshold 0.4. Filters out some topics \u2014 the output isn't guaranteed to be 10, just however many pass the filter (that's fine, more honest than letting duplicates through)."
      },
      "typeVersion": 1
    },
    {
      "id": "a5d431a0-faad-4a17-bd46-c03f5f039271",
      "name": "Sticky Note13",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3632,
        1760
      ],
      "parameters": {
        "color": "#EC4899",
        "width": 512,
        "height": 592,
        "content": "\ud83d\udcbe SAVING NEW TOPICS\n\nPrepare Topic Row \u2192 adds housekeeping fields to each topic: id (timestamp+index), status=\"new\", created_date.\n\nAppend to Topics Sheet \u2192 writes every topic that passed the filter into Topics in one append call.\n\nUpdate State Row \u2192 updates ONLY last_topic_gen_date. \u26a0\ufe0f Does NOT touch topics_in_queue or total_published \u2014 those are now live formulas (=COUNTIF/=COUNTA) right in the sheet; if Update tried to overwrite them, the formula would turn into a static number and stop updating itself.\n\nAfter this it loops back to Search Spreadsheet (see the note at the start of the Daily graph): this is the \"if the queue runs dry, refill it right away\" behavior \u2014 after generation, one of the fresh topics is automatically picked up and drafted."
      },
      "typeVersion": 1
    },
    {
      "id": "7e9a556c-7867-450b-b64b-aca959d8f733",
      "name": "README",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -528,
        256
      ],
      "parameters": {
        "width": 700,
        "height": 796,
        "content": "# \ud83d\uddde\ufe0f Zen Content Automation\n### AI-powered article + image pipeline for a Yandex Zen channel\n\n**What this does:** two connected workflows that keep a Zen channel supplied with ready-to-publish historical articles (about women's history, easily adapted to any niche) plus matching illustrations \u2014 with duplicate-topic protection and AI-detector resistance built in.\n\n**Weekly (Monday 09:00):** searches Wikipedia by category for real historical figures, asks an LLM to turn a curated batch into 10 topic ideas, filters out anything too similar to what's already published or queued, and appends the survivors to a Google Sheet.\n\n**Daily:** picks the oldest queued topic, drafts an article, runs a second \"humanizing\" pass to remove AI-sounding phrasing, saves it as a Google Doc, generates 1-2 illustrations, uploads everything into one per-article Drive folder, and logs the result.\n\n**You still publish manually** \u2014 Yandex Zen has no public posting API, so the output is a finished Doc + images folder, ready to copy into the Zen editor.\n\n**Setup checklist:**\n1. Connect Google Sheets, Google Drive, Google Docs, and OpenRouter credentials\n2. Run the workflow once manually \u2014 it bootstraps the spreadsheet, sheets, and Drive folder automatically\n3. Adjust the niche/topic prompt in \"LLM: Generate Topics\" and \"LLM: Draft Article\" to fit your channel\n4. Turn on the Weekly schedule trigger once you're happy with a few test runs\n\nSee the color-coded legend note for how the canvas is organized."
      },
      "typeVersion": 1
    },
    {
      "id": "3a46a236-9e1e-4b58-ac6d-7c1e3087758a",
      "name": "Color Legend",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -528,
        1152
      ],
      "parameters": {
        "width": 700,
        "height": 460,
        "content": "## \ud83c\udfa8 Note Color Legend\n\n\ud83d\udd35 **Blue \u2014 AI generation**\nSteps where an LLM writes or transforms content (drafting, humanizing, generating topics or image prompts)\n\n\ud83d\udfe0 **Orange \u2014 Search / Read**\nLooking things up: finding the spreadsheet, reading existing topics, querying the Wikipedia API\n\n\ud83e\ude77 **Pink \u2014 Write / Save**\nPersisting results: appending rows, updating status, uploading files to Drive, saving documents\n\n\ud83d\udfe2 **Green \u2014 Infrastructure / Setup**\nOne-time bootstrap logic: creating the spreadsheet, sheets, and folder structure from scratch if they don't exist yet"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "45a9e653-b2ee-42c7-aaf0-ffd0ababe728",
  "nodeGroups": [],
  "connections": {
    "Draft article": {
      "main": [
        [
          {
            "node": "Merge Draft With Topic",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Folder IDs": {
      "main": [
        [
          {
            "node": "Validate Folder IDs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get next topic": {
      "main": [
        [
          {
            "node": "Filter New Topics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Humanize draft": {
      "main": [
        [
          {
            "node": "Merge Humanized With Topic",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Seed State Row": {
      "main": [
        [
          {
            "node": "Wait (Drive index)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Table Exists?1": {
      "main": [
        [
          {
            "node": "Set Spreadsheet ID (Found)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Spreadsheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Attach Doc Link": {
      "main": [
        [
          {
            "node": "Generate image prompts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has New Topics?": {
      "main": [
        [
          {
            "node": "Limit To One Topic",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Search files and folders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload to Drive": {
      "main": [
        [
          {
            "node": "Set File Permissions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append Image Row": {
      "main": [
        [
          {
            "node": "Aggregate Image Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update State Row": {
      "main": [
        [
          {
            "node": "Search Spreadsheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter New Topics": {
      "main": [
        [
          {
            "node": "Has New Topics?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Image Row": {
      "main": [
        [
          {
            "node": "Append Image Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare State Row": {
      "main": [
        [
          {
            "node": "Seed State Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Topic Row": {
      "main": [
        [
          {
            "node": "Append to Topics Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Exclude List": {
      "main": [
        [
          {
            "node": "Pick Random Category",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Article Doc": {
      "main": [
        [
          {
            "node": "Insert Article Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Spreadsheet": {
      "main": [
        [
          {
            "node": "Create Articles Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Limit To One Topic": {
      "main": [
        [
          {
            "node": "Draft article",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Spreadsheet": {
      "main": [
        [
          {
            "node": "Spreadsheet Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Seed Images Header": {
      "main": [
        [
          {
            "node": "Prepare State Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Seed Topics Header": {
      "main": [
        [
          {
            "node": "Prepare Published Header Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Spreadsheet Found?": {
      "main": [
        [
          {
            "node": "Get Folder IDs",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Search files and folders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait (Drive index)": {
      "main": [
        [
          {
            "node": "Search files and folders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI: Generate Topics": {
      "main": [
        [
          {
            "node": "Parse JSON Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Article Text": {
      "main": [
        [
          {
            "node": "Set Doc Permissions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Image Prompts": {
      "main": [
        [
          {
            "node": "Generate images (OpenRouter)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse JSON Response": {
      "main": [
        [
          {
            "node": "Filter Duplicate Topics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Doc Permissions": {
      "main": [
        [
          {
            "node": "Attach Doc Link",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Folder IDs": {
      "main": [
        [
          {
            "node": "Get next topic",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Stop: Missing Folder IDs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Category Members": {
      "main": [
        [
          {
            "node": "Pick Random Batch + Filter Excluded",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Published Titles": {
      "main": [
        [
          {
            "node": "Merge/Aggregate Titles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pick Random Category": {
      "main": [
        [
          {
            "node": "Get Category Members",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set File Permissions": {
      "main": [
        [
          {
            "node": "Prepare Image Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Article Folder": {
      "main": [
        [
          {
            "node": "Create Article Doc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Published Row": {
      "main": [
        [
          {
            "node": "Append to Published Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Seed Published Header": {
      "main": [
        [
          {
            "node": "Prepare Images Header Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append to Topics Sheet": {
      "main": [
        [
          {
            "node": "Update State Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Articles Folder": {
      "main": [
        [
          {
            "node": "Prepare Topics Header Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate image prompts": {
      "main": [
        [
          {
            "node": "Parse Image Prompts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Draft With Topic": {
      "main": [
        [
          {
            "node": "Humanize draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge/Aggregate Titles": {
      "main": [
        [
          {
            "node": "Build Exclude List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Image Results": {
      "main": [
        [
          {
            "node": "Prepare Published Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decode Base64 To Binary": {
      "main": [
        [
          {
            "node": "Upload to Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Duplicate Topics": {
      "main": [
        [
          {
            "node": "Prepare Topic Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search files and folders": {
      "main": [
        [
          {
            "node": "Table Exists?1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append to Published Sheet": {
      "main": [
        [
          {
            "node": "Update status & log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Existing Queue Titles": {
      "main": [
        [
          {
            "node": "Merge/Aggregate Titles",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Prepare Images Header Row": {
      "main": [
        [
          {
            "node": "Seed Images Header",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Topics Header Row": {
      "main": [
        [
          {
            "node": "Seed Topics Header",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM: Image Prompts (GPT-5)": {
      "ai_languageModel": [
        [
          {
            "node": "Generate image prompts",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Merge Humanized With Topic": {
      "main": [
        [
          {
            "node": "Create Article Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Spreadsheet ID (Found)": {
      "main": [
        [
          {
            "node": "Get Published Titles",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Existing Queue Titles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Weekly Schedule (Mon 09:00)": {
      "main": [
        [
          {
            "node": "Search files and folders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate images (OpenRouter)": {
      "main": [
        [
          {
            "node": "Decode Base64 To Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Published Header Row": {
      "main": [
        [
          {
            "node": "Seed Published Header",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking 'Execute workflow'": {
      "main": [
        [
          {
            "node": "Search Spreadsheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pick Random Batch + Filter Excluded": {
      "main": [
        [
          {
            "node": "AI: Generate Topics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM: Draft Article (Claude Haiku 4.5)": {
      "ai_languageModel": [
        [
          {
            "node": "Draft article",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "LLM: Humanize Draft (Claude Haiku 4.5)": {
      "ai_languageModel": [
        [
          {
            "node": "Humanize draft",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "LLM: Generate Topics (Claude Sonnet 4.5)": {
      "ai_languageModel": [
        [
          {
            "node": "AI: Generate Topics",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  }
}