AutomationFlowsAI & RAG › Generate Zen Articles and Images with Openrouter, Google Sheets, and Drive

Generate Zen Articles and Images with Openrouter, Google Sheets, and Drive

ByRenegaDe @renegade on n8n.io

This workflow manages a Yandex Zen content pipeline by generating weekly article topics from Wikipedia categories, then drafting a daily Russian article and up to two illustrations via OpenRouter, saving everything to Google Docs/Drive and tracking status in a Google Sheets…

Event trigger★★★★★ complexityAI-powered79 nodesGoogle SheetsOpenRouter ChatAgentGoogle DocsGoogle DriveHTTP RequestStop And Error
AI & RAG Trigger: Event Nodes: 79 Complexity: ★★★★★ AI nodes: yes Added:

This workflow corresponds to n8n.io template #18236 — we link there as the canonical source.

This workflow follows the Agent → Google Docs recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "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

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow manages a Yandex Zen content pipeline by generating weekly article topics from Wikipedia categories, then drafting a daily Russian article and up to two illustrations via OpenRouter, saving everything to Google Docs/Drive and tracking status in a Google Sheets…

Source: https://n8n.io/workflows/18236/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Arvifund - Supabase (Fixed v5). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 91 nodes.

HTTP Request, Telegram, Google Sheets +9
AI & RAG

Arvifund - Supabase. Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.

HTTP Request, Telegram, Google Sheets +8
AI & RAG

Arvifund - Supabase (Fixed v2). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.

HTTP Request, Telegram, Google Sheets +9
AI & RAG

Arvifund - Supabase (Fixed v4). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.

HTTP Request, Telegram, Google Sheets +9
AI & RAG

Arvifund - Supabase (Fixed v3). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.

HTTP Request, Telegram, Google Sheets +9