Mi Flujo de Trabajo
Este es unContent Creation, Multimodal AIflujo de automatización del dominio deautomatización que contiene 15 nodos.Utiliza principalmente nodos como Code, Merge, Slack, Switch, RssFeedRead. Usar Gemini AI y fuentes RSS para generar contenido de blog de extremo a extremo desde Slack
- •Bot Token de Slack o URL de Webhook
Nodos utilizados (15)
Categoría
{
"id": "sbb4vrO0WsNieVjJ",
"meta": {
"instanceId": "5ac88c773760ec0a08b5bc88c77b1c991cfe4d8cb09ca2faa12842e1fb3ba445",
"templateCredsSetupCompleted": true
},
"name": "My workflow",
"tags": [],
"nodes": [
{
"id": "7506a331-0426-43ca-86ee-c912e10312c7",
"name": "RSS Read",
"type": "n8n-nodes-base.rssFeedRead",
"position": [
384,
176
],
"parameters": {
"url": "https://skift.com/feed/",
"options": {}
},
"typeVersion": 1.2
},
{
"id": "f15bc30b-b95c-4a15-8a1d-b249b15713de",
"name": "RSS Read1",
"type": "n8n-nodes-base.rssFeedRead",
"position": [
368,
-16
],
"parameters": {
"url": "https://news.google.com/rss/search?q=hotel+technology+OR+hospitality+tech+when:7d&hl=en-US&gl=US&ceid=US:en",
"options": {}
},
"typeVersion": 1.2
},
{
"id": "02bacaaa-a811-4362-9618-85e5818a7fc7",
"name": "RSS Read2",
"type": "n8n-nodes-base.rssFeedRead",
"position": [
368,
-240
],
"parameters": {
"url": "https://news.google.com/rss/search?q=hotel+marketing+OR+hospitality+marketing+when:7d&hl=en-US&gl=US&ceid=US:en",
"options": {}
},
"typeVersion": 1.2
},
{
"id": "c2385ad6-4fac-4643-9d41-28ae2f5d4730",
"name": "Slack: Incoming messages",
"type": "n8n-nodes-base.slackTrigger",
"position": [
-400,
-16
],
"webhookId": "6b2d0c0e-bb63-4518-9f3a-f7e0acf2b3f4",
"parameters": {
"options": {},
"trigger": [
"message"
],
"channelId": {
"__rl": true,
"mode": "id",
"value": "C09C3Q4QCF3"
}
},
"credentials": {
"slackApi": {
"id": "wPuW91Painl3dyIS",
"name": "Slack Bot"
}
},
"typeVersion": 1
},
{
"id": "ff12ac88-4c97-4503-b6b3-c5f76ca10962",
"name": "Código: parse_slack_command",
"type": "n8n-nodes-base.code",
"position": [
-128,
-16
],
"parameters": {
"jsCode": "// --- Slack Event Normalizer ---\n// Works with Slack Trigger (test) or Production webhook body\n\nconst ev =\n $json.event ??\n $json.body?.event ?? // prod webhook\n $json; // fallback (test)\n\nif (!ev) {\n console.log('No Slack event in payload:', $json);\n return [];\n}\n\n// Ignore bot/system messages and edits\nif (ev.bot_id || ev.subtype === 'bot_message' || ev.subtype === 'message_changed') {\n return [];\n}\n\n// Strip a leading @mention like \"<@U123...> \"\nlet text = (ev.text || '').replace(/^<@[^>]+>\\s*/g, '').trim();\nconst lower = text.toLowerCase();\n\nlet cmd = null, pick = null, notes = null;\n\n// start/stop/done/revise\nif (lower === 'start') {\n cmd = 'start';\n} else if (lower === 'stop') {\n cmd = 'stop';\n} else if (lower === 'done') {\n cmd = 'done';\n} else if (/^revise\\[(.*)\\]$/i.test(text)) {\n cmd = 'revise';\n notes = text.match(/^revise\\[(.*)\\]$/i)[1].trim();\n}\n\n// gen (supports \"2\" or \"gen 2\")\nconst m = lower.match(/^gen\\s*(\\d+)$/i);\nif (!cmd && m) {\n cmd = 'gen';\n pick = parseInt(m[1], 10);\n} else if (!cmd && /^\\d+$/.test(lower)) {\n cmd = 'gen';\n pick = parseInt(lower, 10);\n}\n\nreturn [{\n json: {\n cmd,\n pick,\n notes,\n channel: ev.channel || $json.channel,\n ts: ev.ts || $json.ts,\n thread_ts: ev.thread_ts || ev.ts || $json.thread_ts || $json.ts,\n user: ev.user || $json.user,\n text,\n }\n}];"
},
"typeVersion": 2
},
{
"id": "c10ea145-13b8-4f8f-bc68-301dbbb3353e",
"name": "Interruptor: route_by_command",
"type": "n8n-nodes-base.switch",
"position": [
80,
-32
],
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "92dd4e01-59ac-4266-985c-34b05f044bd6",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{$json.cmd}}",
"rightValue": "start"
}
]
}
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "fa91281f-0b2f-4ac3-99b5-c35fa856ee96",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{$json.cmd}}",
"rightValue": "gen"
}
]
}
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1814a166-2e90-45cf-8fbd-acaadfc0a1fd",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{$json.cmd}}",
"rightValue": "revise"
}
]
}
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f004378f-f978-45dd-b507-337132ec4aaf",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{$json.cmd}}",
"rightValue": "done"
}
]
}
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "348766c0-42ad-4f96-888b-8ef20bd2261b",
"name": "Fusionar: all_feeds",
"type": "n8n-nodes-base.merge",
"position": [
656,
0
],
"parameters": {
"numberInputs": 3
},
"typeVersion": 3.2
},
{
"id": "a516595a-76ae-45b9-b654-7e547a31af61",
"name": "Código: make_headline_payload",
"type": "n8n-nodes-base.code",
"position": [
864,
0
],
"parameters": {
"jsCode": "/**\n * Code1 — after Merge (Append)\n * Build clean, deduped article list and a compact text block for Gemini.\n */\n\n// ====== configuration ======\nconst MAX_HEADLINES = 60; // <= how many headlines to pass to Gemini (try 50–80)\n\n// ====== helpers ======\nfunction stripHtml(html = '') {\n // Remove tags\n const noTags = String(html).replace(/<[^>]*>/g, ' ');\n // Collapse whitespace\n return noTags.replace(/\\s+/g, ' ').trim();\n}\n\nfunction truncate(str = '', n = 140) {\n const s = String(str).trim();\n return s.length > n ? s.slice(0, n) + '…' : s;\n}\n\nfunction pickFirst(obj, keys) {\n for (const k of keys) {\n if (obj?.[k]) return obj[k];\n }\n return undefined;\n}\n\n// ====== collect & normalize ======\nconst items = $input.all(); // all merged items\nconst seen = new Set(); // dedupe by *normalized title*\nconst articles = [];\n\nfor (const item of items) {\n const j = item.json || {};\n\n // Try common RSS/Atom/HTTP node fields\n const rawTitle = pickFirst(j, [\n 'title', 'headline', 'name'\n ]);\n\n // If no title, skip (we need a key to dedupe and display)\n if (!rawTitle) continue;\n\n // For descriptions, try several fields and strip HTML\n const rawDesc = pickFirst(j, [\n 'contentSnippet', 'description', 'summary', 'content', 'content:encoded'\n ]);\n const description = rawDesc ? stripHtml(rawDesc) : '';\n\n // Links/guid\n const link = pickFirst(j, [\n 'link', 'url', 'guid'\n ]);\n\n // Deduplicate by normalized title\n const title = String(rawTitle).trim();\n const key = title.toLowerCase();\n if (seen.has(key)) continue;\n seen.add(key);\n\n articles.push({\n title,\n description,\n link: link ? String(link).trim() : ''\n });\n}\n\n// ====== choose how much to send to Gemini ======\nconst limited = articles.slice(0, MAX_HEADLINES);\n\n// Build compact arrays for Gemini + debugging\nconst headlines = limited.map(a => a.title);\n\n// If you want titles + a short blurb in the prompt, use this:\nconst articleText = limited\n .map((a, i) => `${i + 1}. ${a.title}${a.description ? ` — ${truncate(a.description)}` : ''}`)\n .join('\\n');\n\n// ====== output ======\nreturn [{\n json: {\n articles: limited, // array of normalized items\n headlines, // array of titles only\n articleText, // numbered list for Gemini\n articleCount: limited.length,\n }\n}];"
},
"typeVersion": 2
},
{
"id": "4d5de947-36b0-4d5b-9bdc-708ad24ab6aa",
"name": "Gemini: cluster_to_topics",
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"position": [
1168,
0
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "models/gemini-1.5-flash",
"cachedResultName": "models/gemini-1.5-flash"
},
"options": {},
"messages": {
"values": [
{
"content": "=You are a hotel-industry editor.\n\nYou are given recent hospitality headlines & snippets:\n{{$json.articleText}}\n\nTask:\n- Select 10–30 of the most relevant, diverse themes.\n- For each theme, write a short, **paraphrased headline** in your own words (do NOT copy any headline verbatim).\n- Keep each headline concise and specific (≤ 120 characters).\n- No emojis, no hashtags, no numbering, no quotes.\n\nReturn STRICT JSON ONLY, exactly in this shape:\n{\n \"topics\": [\n { \"topic\": \"Paraphrased headline 1\" },\n { \"topic\": \"Paraphrased headline 2\" }\n ]\n}"
}
]
},
"jsonOutput": true
},
"credentials": {
"googlePalmApi": {
"id": "QjyJeJMPhmy8nZA4",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "e226a282-4ac1-4e7d-86c5-a2c69bd3736a",
"name": "Código: parse_or_pass_topics",
"type": "n8n-nodes-base.code",
"position": [
1568,
0
],
"parameters": {
"jsCode": "// Code2 — normalize Gemini topics JSON for Slack\n\nconst raw =\n $json.text ??\n $json.content?.parts?.[0]?.text ??\n '';\n\nlet out = { topics: [] };\n\ntry {\n const cleaned = String(raw).replace(/```json|```/gi, '').trim();\n\n // try full first, then last {...}\n try {\n out = JSON.parse(cleaned);\n } catch {\n const m = cleaned.match(/\\{[\\s\\S]*\\}$/);\n if (m) out = JSON.parse(m[0]);\n }\n} catch (e) {\n console.log('Failed to parse Gemini output:', e, String(raw).slice(0, 300));\n out = { topics: [] };\n}\n\n// --- normalize to [{topic: \"...\"}]\nlet topics = out?.topics ?? [];\nif (!Array.isArray(topics)) topics = [];\n\ntopics = topics\n .map(t => {\n if (typeof t === 'string') return { topic: t.trim() };\n if (t && typeof t === 'object') {\n return { topic: String(t.topic ?? t.headline ?? t.title ?? '').trim() };\n }\n return { topic: '' };\n })\n .filter(t => t.topic);\n\n// guardrail: de-dup & cap (optional)\nconst seen = new Set();\ntopics = topics.filter(t => {\n const key = t.topic.toLowerCase();\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n});\n\n// output to next nodes\nreturn [{\n json: {\n mode: 'topics',\n topics\n }\n}];"
},
"typeVersion": 2
},
{
"id": "a84fa8a6-231d-443b-bbcd-72360a014980",
"name": "Slack: post_topic_list",
"type": "n8n-nodes-base.slack",
"position": [
2112,
-96
],
"webhookId": "fb748920-aef3-42d9-bf9a-30c180d82876",
"parameters": {
"text": "={{ (function () {\n const t = $json.topics || [];\n if (!Array.isArray(t) || !t.length) {\n return 'No topics found. Try `start` again.';\n }\n\n // Show up to 30 items (or all)\n const lines = t.map((x, i) => `${i + 1}) ${x.topic}`);\n const body = lines.join('\\n');\n\n return `Trending hotel topics (reply with a number, e.g. 2):\\n\\n${body}\\n\\nThen: \\`revise[ your notes ]\\` or \\`done\\`.\\nAutomated with this n8n workflow`;\n})() }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "C09C3Q4QCF3"
},
"otherOptions": {
"thread_ts": {
"replyValues": {
"thread_ts": "={{$node[\"Code: parse_slack_command\"].json.thread_ts || $node[\"Code: parse_slack_command\"].json.ts}}"
}
}
}
},
"credentials": {
"slackApi": {
"id": "wPuW91Painl3dyIS",
"name": "Slack Bot"
}
},
"typeVersion": 2.3
},
{
"id": "56c5667f-18d7-401b-b300-cf7429eef2a3",
"name": "Slack: fetch_thread_replies",
"type": "n8n-nodes-base.slack",
"position": [
432,
416
],
"webhookId": "63d1b928-fe46-45d3-81b1-f4d740b3f184",
"parameters": {
"ts": "={{$node[\"Code: parse_slack_command\"].json.thread_ts || $node[\"Code: parse_slack_command\"].json.ts}}",
"filters": {},
"resource": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "C09C3Q4QCF3"
},
"operation": "replies"
},
"credentials": {
"slackApi": {
"id": "wPuW91Painl3dyIS",
"name": "Slack Bot"
}
},
"typeVersion": 2.3
},
{
"id": "36d792cc-bdc2-4de8-aa3e-fca5d7d9750a",
"name": "Código: pick_topic_from_thread",
"type": "n8n-nodes-base.code",
"position": [
1056,
432
],
"parameters": {
"jsCode": "// post gen — map user number to the chosen headline from the thread\n\n// 1) get the user's pick from anywhere we can\nfunction num(x) {\n const n = Number(x);\n return Number.isFinite(n) ? n : undefined;\n}\n\nlet pick =\n num($json.pick) ??\n num($node[\"Code: parse_slack_command\"].json?.pick) ?? // from your normalizer Code\n num(($node[\"Code: parse_slack_command\"].json?.text || \"\").match(/\\d+/)?.[0]); // last resort\n\nif (!pick) {\n return [{ json: { error: 'no_pick', msg: \"Reply with a number like 2 or 'gen 2'.\" } }];\n}\n\n// 2) normalize Slack replies shape to an array of message objects\n// - Return All OFF: slack node gives { messages: [...] }\n// - Return All ON: slack node gives many items, one per message\nlet msgs = [];\nif (Array.isArray($json.messages)) {\n msgs = $json.messages;\n} else if (Array.isArray($items().map(i => i.json))) {\n // Return All ON case: collect all incoming items as messages\n msgs = $items().map(i => i.json);\n}\n\n// 3) find the bot message that listed the topics\n// Adapt the marker below to the exact first line you print in Slack\nconst LIST_MARKER = 'Trending hotel topics';\nlet listMsg = null;\n\n// try newest-first if provided by Slack, else do a defensive search\nfor (const m of msgs) {\n if (typeof m.text === 'string' && m.text.includes(LIST_MARKER)) {\n listMsg = m;\n break;\n }\n}\nif (!listMsg) {\n for (let i = msgs.length - 1; i >= 0; i--) {\n const m = msgs[i];\n if (typeof m.text === 'string' && m.text.includes(LIST_MARKER)) {\n listMsg = m;\n break;\n }\n }\n}\nif (!listMsg) {\n return [{ json: { error: 'no_list', msg: 'Could not find the topics list message in this thread.' } }];\n}\n\n// 4) parse numbered lines from the list message\n// We accept both \"1) Title (12)\" and \"1) Title\" styles\nconst lines = (listMsg.text || '').split('\\n');\nconst topics = [];\nfor (const line of lines) {\n // capture: number ) <headline> (optional (count))\n const m = line.match(/^\\s*\\d+\\)\\s+(.+?)(?:\\s+\\(\\d+\\))?\\s*$/);\n if (m) topics.push(m[1].trim());\n}\n\nif (!topics.length) {\n return [{ json: { error: 'no_topics', msg: 'Could not parse any topics from the list message.' } }];\n}\n\n// clamp pick to available range\nconst idx = Math.max(1, Math.min(pick, topics.length)) - 1;\nconst topic = topics[idx];\n\n// 5) pass forward for Gemini post generation\nreturn [{\n json: {\n topic,\n pick: idx + 1,\n topicsCount: topics.length,\n channel: $node[\"Code: parse_slack_command\"].json?.channel,\n thread_ts: $node[\"Code: parse_slack_command\"].json?.thread_ts || $node[\"Code: parse_slack_command\"].json?.ts\n }\n}];"
},
"typeVersion": 2
},
{
"id": "bf46b6cb-b1a2-4c48-8095-0a0297b9dddb",
"name": "Gemini: write_draft",
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"position": [
1504,
336
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "models/gemini-1.5-flash",
"cachedResultName": "models/gemini-1.5-flash"
},
"options": {},
"messages": {
"values": [
{
"content": "=You are a hotel-industry content writer.\nWrite a 250–350 word LinkedIn-style post about:\n\n“{{$json.topic}}”\n\nUse insights from these recent headlines (signal, not quotes):\n{{$json.articleText || ''}}\n\nRequirements:\n- Start with a strong hook (1–2 sentences).\n- Include 3–5 concise bullets with specific, non-generic observations.\n- End with a one-sentence takeaway + a soft CTA.\n- Confident, analytical tone. No hashtags. No emojis.\n- Plain text only."
}
]
}
},
"credentials": {
"googlePalmApi": {
"id": "QjyJeJMPhmy8nZA4",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "94761e71-40c5-4cfd-a127-3508489d331d",
"name": "Slack: post_draft",
"type": "n8n-nodes-base.slack",
"position": [
2112,
160
],
"webhookId": "fb748920-aef3-42d9-bf9a-30c180d82876",
"parameters": {
"text": "={{$json.text || $json.content?.parts?.[0]?.text || 'No content generated.'}}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "C09C3Q4QCF3"
},
"otherOptions": {
"thread_ts": {
"replyValues": {
"thread_ts": "={{$node[\"Code: parse_slack_command\"].json.thread_ts || $node[\"Code: parse_slack_command\"].json.ts}}"
}
}
}
},
"credentials": {
"slackApi": {
"id": "wPuW91Painl3dyIS",
"name": "Slack Bot"
}
},
"typeVersion": 2.3
}
],
"active": true,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "bb356e18-0314-47a2-bbf5-eb471741b079",
"connections": {
"7506a331-0426-43ca-86ee-c912e10312c7": {
"main": [
[
{
"node": "Merge: all_feeds",
"type": "main",
"index": 2
}
]
]
},
"f15bc30b-b95c-4a15-8a1d-b249b15713de": {
"main": [
[
{
"node": "Merge: all_feeds",
"type": "main",
"index": 1
}
]
]
},
"02bacaaa-a811-4362-9618-85e5818a7fc7": {
"main": [
[
{
"node": "Merge: all_feeds",
"type": "main",
"index": 0
}
]
]
},
"Merge: all_feeds": {
"main": [
[
{
"node": "Code: make_headline_payload",
"type": "main",
"index": 0
}
]
]
},
"bf46b6cb-b1a2-4c48-8095-0a0297b9dddb": {
"main": [
[
{
"node": "94761e71-40c5-4cfd-a127-3508489d331d",
"type": "main",
"index": 0
}
]
]
},
"a84fa8a6-231d-443b-bbcd-72360a014980": {
"main": [
[]
]
},
"c2385ad6-4fac-4643-9d41-28ae2f5d4730": {
"main": [
[
{
"node": "Code: parse_slack_command",
"type": "main",
"index": 0
}
]
]
},
"Switch: route_by_command": {
"main": [
[
{
"node": "02bacaaa-a811-4362-9618-85e5818a7fc7",
"type": "main",
"index": 0
},
{
"node": "f15bc30b-b95c-4a15-8a1d-b249b15713de",
"type": "main",
"index": 0
},
{
"node": "7506a331-0426-43ca-86ee-c912e10312c7",
"type": "main",
"index": 0
}
],
[
{
"node": "56c5667f-18d7-401b-b300-cf7429eef2a3",
"type": "main",
"index": 0
}
]
]
},
"Code: parse_slack_command": {
"main": [
[
{
"node": "Switch: route_by_command",
"type": "main",
"index": 0
}
]
]
},
"4d5de947-36b0-4d5b-9bdc-708ad24ab6aa": {
"main": [
[
{
"node": "Code: parse_or_pass_topics",
"type": "main",
"index": 0
}
]
]
},
"Code: parse_or_pass_topics": {
"main": [
[
{
"node": "a84fa8a6-231d-443b-bbcd-72360a014980",
"type": "main",
"index": 0
}
]
]
},
"Code: make_headline_payload": {
"main": [
[
{
"node": "4d5de947-36b0-4d5b-9bdc-708ad24ab6aa",
"type": "main",
"index": 0
}
]
]
},
"56c5667f-18d7-401b-b300-cf7429eef2a3": {
"main": [
[
{
"node": "Code: pick_topic_from_thread",
"type": "main",
"index": 0
}
]
]
},
"Code: pick_topic_from_thread": {
"main": [
[
{
"node": "bf46b6cb-b1a2-4c48-8095-0a0297b9dddb",
"type": "main",
"index": 0
}
]
]
}
}
}¿Cómo usar este flujo de trabajo?
Copie el código de configuración JSON de arriba, cree un nuevo flujo de trabajo en su instancia de n8n y seleccione "Importar desde JSON", pegue la configuración y luego modifique la configuración de credenciales según sea necesario.
¿En qué escenarios es adecuado este flujo de trabajo?
Intermedio - Creación de contenido, IA Multimodal
¿Es de pago?
Este flujo de trabajo es completamente gratuito, puede importarlo y usarlo directamente. Sin embargo, tenga en cuenta que los servicios de terceros utilizados en el flujo de trabajo (como la API de OpenAI) pueden requerir un pago por su cuenta.
Flujos de trabajo relacionados recomendados
Nijan
@nijanIndie SaaS Builder @ Voicelyst AI | Product (UI/UX) Designer helping SaaS teams grow with UX & AI. 📈 🚀
Compartir este flujo de trabajo