8
n8n 中文网amn8n.com

使用Gemini AI为Discord生成精选本地新闻摘要

高级

这是一个Content Creation, Multimodal AI领域的自动化工作流,包含 20 个节点。主要使用 Code, Sort, Limit, Merge, Discord 等节点。 使用Gemini AI为Discord生成精选本地新闻摘要

前置要求
  • Discord Bot Token 或 Webhook
  • 可能需要目标 API 的认证凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "meta": {
    "instanceId": "105694f414213a0eca348284005921253960bd1b0223294a4970522d0da53055",
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "e5629011-82f2-4107-967f-91659ee7455f",
      "name": "每日 8 点触发器",
      "type": "n8n-nodes-base.scheduleTrigger",
      "notes": "Change the cron expression to adjust schedule. Current: 8AM daily",
      "position": [
        -400,
        48
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "fb7df187-44df-4e20-bc35-64ef353e6ee0",
      "name": "RSS Phoenix New Times",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -192,
        -256
      ],
      "parameters": {
        "url": "https://www.phoenixnewtimes.com/phoenix/Rss.xml",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "9d60cd62-960a-451b-bde1-5543eaef63ae",
      "name": "RSS AZ Free News",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -192,
        -96
      ],
      "parameters": {
        "url": "https://azfreenews.com/feed/",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "6dbf6940-9f45-48be-87f2-1d03727c2f71",
      "name": "RSS Reddit Phoenix",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -192,
        48
      ],
      "parameters": {
        "url": "https://www.reddit.com/r/Phoenix/.rss",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "6633710a-8742-45f6-b21b-07c800067043",
      "name": "RSS Reddit Scottsdale",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -192,
        192
      ],
      "parameters": {
        "url": "https://www.reddit.com/r/Scottsdale/.rss",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "b5248dc3-2541-4ee1-8ae8-342c363963f6",
      "name": "RSS Reddit Arizona",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -192,
        336
      ],
      "parameters": {
        "url": "https://www.reddit.com/r/Arizona/.rss",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "834fa6ed-9b2d-433f-b121-f58ff9dba58b",
      "name": "合并所有 RSS 源",
      "type": "n8n-nodes-base.merge",
      "position": [
        16,
        48
      ],
      "parameters": {
        "numberInputs": 5
      },
      "typeVersion": 3
    },
    {
      "id": "588b18cf-44a7-42b8-940e-219bb5176cfb",
      "name": "去重与准备",
      "type": "n8n-nodes-base.code",
      "position": [
        208,
        48
      ],
      "parameters": {
        "jsCode": "// Deduplicate articles based on URL and prepare for Gemini scoring\nconst items = $input.all();\nconst uniqueArticles = new Map();\nconst locations = ['Phoenix', 'Scottsdale', 'Paradise Valley', 'Arizona', 'Tempe', 'Mesa', 'Chandler'];\n\n// Process and deduplicate\nfor (const item of items) {\n  const url = item.json.link || item.json.url || '';\n  const title = item.json.title || '';\n  const description = item.json.description || item.json.content || item.json.summary || '';\n  const source = item.json.source || 'Unknown';\n  const pubDate = item.json.pubDate || item.json.published || new Date().toISOString();\n  \n  // Create unique key based on URL or title\n  const key = url || title;\n  \n  if (key && !uniqueArticles.has(key)) {\n    // Check if article mentions any of our locations\n    const fullText = `${title} ${description}`.toLowerCase();\n    const mentionsLocation = locations.some(loc => fullText.includes(loc.toLowerCase()));\n    \n    uniqueArticles.set(key, {\n      title: title.substring(0, 200),\n      url: url,\n      description: description.substring(0, 500),\n      source: source,\n      pubDate: pubDate,\n      mentionsLocation: mentionsLocation\n    });\n  }\n}\n\n// Convert to array and return\nconst deduplicatedArticles = Array.from(uniqueArticles.values());\n\n// Sort by date (newest first) and filter to most recent 50 for processing\nconst sortedArticles = deduplicatedArticles\n  .sort((a, b) => new Date(b.pubDate) - new Date(a.pubDate))\n  .slice(0, 50);\n\nreturn sortedArticles.map(article => ({ json: article }));"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "add1c53d-bfb6-4ae7-898d-3205404f96b6",
      "name": "为 Gemini 准备",
      "type": "n8n-nodes-base.code",
      "position": [
        416,
        48
      ],
      "parameters": {
        "jsCode": "// Prepare batch of articles for Gemini scoring - ONLY TITLES for efficiency\nconst articles = $input.all().map(item => item.json);\n\n// Create a text summary for Gemini to score - ONLY TITLES to save tokens\nconst titlesText = articles.map((article, index) => {\n  return `${index + 1}. ${article.title}`;\n}).join('\\n');\n\n// Return both the articles and the formatted text\nreturn [{\n  json: {\n    articles: articles,\n    titlesText: titlesText,\n    articlesCount: articles.length\n  }\n}];"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "8e245ef2-dada-4bdf-b0cf-82f36732ae7e",
      "name": "Gemini 相关性评分",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "REPLACE YOUR_GEMINI_API_KEY_HERE with your actual Gemini API key",
      "position": [
        608,
        48
      ],
      "parameters": {
        "url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite:generateContent?key=YOUR_KEY_HERE",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({\n  \"contents\": [{\n    \"parts\": [{\n      \"text\": \"You are a local news relevance scorer for Phoenix, Scottsdale, and Paradise Valley area. Score the following \" + $json.articlesCount + \" article TITLES based on their local relevance.\\n\\nScore each from 0-100 based on:\\n- Local relevance (50%): Is it about Phoenix/Scottsdale/Paradise Valley?\\n- Community impact (30%): Will locals care?\\n- News value (20%): Is it newsworthy?\\n\\nArticle titles:\\n\" + $json.titlesText + \"\\n\\nReturn ONLY a JSON array with scores in the same order. Example: [85, 72, 45, 90, 33]\\nBe strict - only highly local news should score above 70.\"\n    }]\n  }],\n  \"generationConfig\": {\n    \"temperature\": 0.3,\n    \"topK\": 40,\n    \"topP\": 0.95,\n    \"maxOutputTokens\": 512\n  }\n}) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "executeOnce": true,
      "typeVersion": 4.2
    },
    {
      "id": "8d17b168-d628-403e-8d02-7c28ef911b04",
      "name": "处理分数",
      "type": "n8n-nodes-base.code",
      "position": [
        816,
        48
      ],
      "parameters": {
        "jsCode": "// Process Gemini response and add scores to articles\nconst geminiResponse = $input.first().json;\nconst preparedData = $('Prepare for Gemini').first().json;\nconst articles = preparedData.articles;\n\n// Extract scores from Gemini response\nlet scores = [];\ntry {\n  // Parse the response from Gemini\n  const responseText = geminiResponse.candidates[0].content.parts[0].text;\n  \n  // Try to extract JSON array from the response\n  const jsonMatch = responseText.match(/\\[.*\\]/s);\n  if (jsonMatch) {\n    scores = JSON.parse(jsonMatch[0]);\n  } else {\n    // Fallback: assign default scores based on location mentions\n    scores = articles.map(article => article.mentionsLocation ? 75 : 50);\n  }\n} catch (error) {\n  console.error('Error parsing Gemini response:', error);\n  // Fallback scoring based on location mentions\n  scores = articles.map(article => article.mentionsLocation ? 75 : 50);\n}\n\n// Combine articles with scores\nconst scoredArticles = articles.map((article, index) => ({\n  ...article,\n  relevanceScore: scores[index] || 50\n}));\n\nreturn scoredArticles.map(article => ({ json: article }));"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "339e1098-65ba-4e89-85e1-9edcd6049ec9",
      "name": "按相关性排序",
      "type": "n8n-nodes-base.sort",
      "position": [
        1008,
        48
      ],
      "parameters": {
        "options": {},
        "sortFieldsUi": {
          "sortField": [
            {
              "order": "descending",
              "fieldName": "relevanceScore"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "03a00299-fa97-4cb2-8265-d1b0962344a3",
      "name": "限制为前 5 条",
      "type": "n8n-nodes-base.limit",
      "notes": "Change maxItems to adjust how many articles to send (e.g., 15)",
      "position": [
        1216,
        48
      ],
      "parameters": {
        "maxItems": 5
      },
      "typeVersion": 1
    },
    {
      "id": "1e31fe92-f998-4e72-a8a6-388d0b3db8e1",
      "name": "格式化 Discord 消息",
      "type": "n8n-nodes-base.code",
      "notes": "Using RSS descriptions directly - no additional AI summarization to save tokens",
      "position": [
        1408,
        48
      ],
      "parameters": {
        "jsCode": "// Collect all incoming articles\nconst articles = $input.all().map(item => item.json);\n\n// Get today's date formatted\nconst today = new Date().toLocaleDateString('en-US', { \n  weekday: 'long', \n  year: 'numeric', \n  month: 'long', \n  day: 'numeric' \n});\n\n// Utility: strip HTML tags from text\nfunction stripHtml(html) {\n  return html\n    .replace(/<[^>]*>/g, '')   // remove HTML tags\n    .replace(/\\s+/g, ' ')      // collapse whitespace\n    .trim();\n}\n\n// Build the full digest string\nlet discordMessage = `🌵 **LOCAL NEWS DIGEST** 🌵\\n📅 ${today}\\n${'━'.repeat(40)}\\n\\n`;\n\narticles.forEach((article, index) => {\n  const emoji = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : `${index + 1}.`;\n  \n  const description = stripHtml(article.description || 'No description available');\n\n  discordMessage += `${emoji} **${article.title}**\\n`;\n  discordMessage += `📍 ${description}\\n`;\n  discordMessage += `🔗 ${article.url}\\n\\n`;\n});\n\ndiscordMessage += `${'━'.repeat(40)}\\n`;\ndiscordMessage += `_Powered by n8n automation | Phoenix • Scottsdale • Paradise Valley_`;\n\n// Discord has a 2000 character limit, so split into multiple messages if necessary\nconst maxLength = 1900; // leave buffer for safety\nconst messages = [];\n\nfor (let i = 0; i < discordMessage.length; i += maxLength) {\n  messages.push(discordMessage.slice(i, i + maxLength));\n}\n\n// Return multiple items (Discord node will send each separately)\nreturn messages.map(msg => ({\n  json: { content: msg }\n}));\n"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "6b23ead3-4d35-451f-b0f2-7e5146dd26a1",
      "name": "发送到 Discord",
      "type": "n8n-nodes-base.discord",
      "notes": "REPLACE with your actual Discord webhook URL",
      "position": [
        1616,
        48
      ],
      "webhookId": "5565d2a2-5e59-43c4-8733-d6b1ca51f509",
      "parameters": {
        "content": "={{ $json.content }}",
        "options": {},
        "authentication": "webhook"
      },
      "credentials": {
        "discordWebhookApi": {
          "id": "6S341y8tVCvOIO8r",
          "name": "All Ops Notis Discord Webhook"
        }
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "9983e297-ee82-4fc9-bd87-74f46857afaf",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        528,
        -336
      ],
      "parameters": {
        "height": 304,
        "content": "## 设置您的 Gemini API 密钥。免费获取!"
      },
      "typeVersion": 1
    },
    {
      "id": "0cb7e7cd-9818-40fd-9444-7a040fe054bf",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -256,
        -496
      ],
      "parameters": {
        "height": 208,
        "content": "## 在此处设置您的新闻源"
      },
      "typeVersion": 1
    },
    {
      "id": "5e83db58-6f66-4e74-b355-e7d32ff6649d",
      "name": "便签2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1136,
        272
      ],
      "parameters": {
        "height": 112,
        "content": "## 设置您想要多少篇文章。"
      },
      "typeVersion": 1
    },
    {
      "id": "ef2042de-6862-4981-b195-2dfc11d68df6",
      "name": "便签3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1328,
        -224
      ],
      "parameters": {
        "height": 192,
        "content": "## 对于其他发送位置(如 Telegram、WhatsApp 等)可能需要更改。"
      },
      "typeVersion": 1
    },
    {
      "id": "023096cc-74b9-4c66-91ad-9102f6ba209c",
      "name": "便签4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1584,
        224
      ],
      "parameters": {
        "height": 112,
        "content": "## 接收您的相关新闻文章"
      },
      "typeVersion": 1
    }
  ],
  "pinData": {},
  "connections": {
    "Limit to Top 5": {
      "main": [
        [
          {
            "node": "Format Discord Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Scores": {
      "main": [
        [
          {
            "node": "Sort by Relevance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RSS AZ Free News": {
      "main": [
        [
          {
            "node": "Merge All RSS Feeds",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Daily 8AM Trigger": {
      "main": [
        [
          {
            "node": "RSS Phoenix New Times",
            "type": "main",
            "index": 0
          },
          {
            "node": "RSS AZ Free News",
            "type": "main",
            "index": 0
          },
          {
            "node": "RSS Reddit Phoenix",
            "type": "main",
            "index": 0
          },
          {
            "node": "RSS Reddit Scottsdale",
            "type": "main",
            "index": 0
          },
          {
            "node": "RSS Reddit Arizona",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort by Relevance": {
      "main": [
        [
          {
            "node": "Limit to Top 5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare for Gemini": {
      "main": [
        [
          {
            "node": "Gemini Relevance Scoring",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RSS Reddit Arizona": {
      "main": [
        [
          {
            "node": "Merge All RSS Feeds",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "RSS Reddit Phoenix": {
      "main": [
        [
          {
            "node": "Merge All RSS Feeds",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Merge All RSS Feeds": {
      "main": [
        [
          {
            "node": "Deduplicate & Prepare",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Deduplicate & Prepare": {
      "main": [
        [
          {
            "node": "Prepare for Gemini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RSS Phoenix New Times": {
      "main": [
        [
          {
            "node": "Merge All RSS Feeds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RSS Reddit Scottsdale": {
      "main": [
        [
          {
            "node": "Merge All RSS Feeds",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Format Discord Message": {
      "main": [
        [
          {
            "node": "Send to Discord",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini Relevance Scoring": {
      "main": [
        [
          {
            "node": "Process Scores",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

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

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

高级 - 内容创作, 多模态 AI

需要付费吗?

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

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

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

作者
Kaden Reese

Kaden Reese

@kadenreese

I started automating with Python in 2020 and still use it in workflows when needed, but I’ve recently leaned into n8n for client-facing solutions. Lately I’ve focused on real estate automations, though I also build workflows for email, scraping, and other use cases. Currently Building 👇🏻

外部链接
在 n8n.io 查看

分享此工作流