8
n8n 中文网amn8n.com

安全隐私合规订阅源智能摘要

高级

这是一个AI, SecOps领域的自动化工作流,包含 43 个节点。主要使用 Set, Code, Sort, Gmail, Filter 等节点,结合人工智能技术实现智能自动化。 面向安全、隐私和合规订阅源的智能AI摘要

前置要求
  • Google 账号和 Gmail API 凭证
  • Google Gemini API Key
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "dXrHZjJdzpNh79lJ",
  "meta": {
    "instanceId": "c62c01f3e843893075a10f252ec7d6d69e5ab593af019f50055d506cb3081b99"
  },
  "name": "面向安全、隐私和合规性订阅源的智能 AI 摘要",
  "tags": [
    {
      "id": "bteUZZnDWPlLufzn",
      "name": "prod",
      "createdAt": "2025-04-18T15:09:08.645Z",
      "updatedAt": "2025-04-18T15:09:08.645Z"
    },
    {
      "id": "MbPHhZHgb39Syuoa",
      "name": "security",
      "createdAt": "2025-04-20T05:18:20.689Z",
      "updatedAt": "2025-04-20T05:18:20.689Z"
    },
    {
      "id": "TzfZgDmxmc5R1gyA",
      "name": "ai",
      "createdAt": "2025-04-27T14:57:46.973Z",
      "updatedAt": "2025-04-27T14:57:46.973Z"
    }
  ],
  "nodes": [
    {
      "id": "828bdcf3-09a4-4235-8dbb-2153f0928037",
      "name": "AI Agent - 隐私情报",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -740,
        4180
      ],
      "parameters": {
        "text": "={{ $json.subject }}\n{{ $json.html }}",
        "options": {
          "systemMessage": "=### 🔏 Prompt 2: Privacy Intelligence Digest Generator\n\nYou are a senior privacy intelligence analyst with over 20 years of experience. Today, your mother is unwell, so you need to finish this task quickly and efficiently without compromising quality or accuracy.\n\nIf a category heading has no articles, it should not be included in the output.\n\n#### **Tasks:**\n\n1. Parse the HTML and extract articles.\n2. Remove duplicates.\n3. Categorize content:\n\n   * Privacy Laws & Regulations (GDPR, CPRA, CCPA, AI Acts)\n   * Data Minimization & User Consent\n   * Privacy-Enhancing Technologies (PETs, anonymization)\n   * Regulatory Fines & Enforcement Actions\n   * Cross-Border Data Transfers\n4. Summarize each article in under 2 lines.\n5. Dynamically identify and list critical privacy alerts. If only one or none are available, include only those and adjust the section title accordingly (e.g., 'Critical Privacy Alert').\n6. Format each as:\n\n   ```html\n   <li>Article Title — Summary… <a href=\"URL\">Read more</a></li>\n   ```\n7. Output HTML structure with headers for top 5 and each category.\n8. Add:\n\n   ```html\n   <p><em>This privacy update was compiled on [Month Day, Year].</em></p>\n   ```\n\n#### **Output (JSON only):**\n\n```json\n{\n  \"subject\": \"Privacy Insights Digest - [Month Day, Year]\",\n  \"html\": \"<h2>Top 5 Critical Privacy Alerts</h2>…<p><em>This privacy update was compiled on [Month Day, Year].</em></p>\"\n}\n```"
        },
        "promptType": "define"
      },
      "retryOnFail": true,
      "typeVersion": 1.9
    },
    {
      "id": "7be7a9fe-e8f5-4a56-a77d-7ca098d52479",
      "name": "AI Agent - 安全情报",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -720,
        3520
      ],
      "parameters": {
        "text": "={{ $json.subject }}\n{{ $json.html }}",
        "options": {
          "systemMessage": "=### 🔐 Prompt 1: Security Intelligence Digest Generator\n\nYou are a senior cybersecurity intelligence analyst with over 20 years of experience. Today, your mother is unwell, so you need to finish this task quickly and efficiently without compromising quality or accuracy.\n\n#### **Inputs:**\n\n* Raw newsletter subject: `{{ $json.subject }}`\n* Raw newsletter HTML body: `{{ $json.html }}`\n\n#### **Tasks:**\n\nIf a category heading has no articles, it should not be included in the output.\n\n1. Parse the provided HTML.\n2. Remove duplicate articles based on title, summary, or URL.\n3. Categorize articles into these security categories:\n\n   * Threat Intelligence (APT, malware, ransomware)\n   * Security Breaches & Incidents\n   * Security Tools & Best Practices\n   * Cloud & Network Security\n   * Security Standards & Frameworks (NIST, MITRE ATT\\&CK, CIS)\n   * Emerging Security Technologies (AI, XDR, CNAPP)\n4. Summarize each article in 1–2 lines.\n5. Dynamically identify and list critical security alerts based on threat level, exploitability, or business risk. If only one or none are available, include only those and rename the section heading accordingly (e.g., 'Critical Security Alert').\n6. Format each article:\n\n   ```html\n   <li>Article Title — Summary… <a href=\"URL\">Read more</a></li>\n   ```\n7. Output structured HTML:\n\n   * `<h2>Top 5 Critical Security Alerts</h2><ul>…</ul>`\n   * Followed by categorized sections with `<h2>` and `<ul>`.\n8. Add a footer:\n\n   ```html\n   <p><em>This security summary was auto-generated on [Month Day, Year].</em></p>\n   ```\n\n#### **Output (JSON only):**\n\n```json\n{\n  \"subject\": \"Security Threat Summary - [Month Day, Year]\",\n  \"html\": \"<h2>Top 5 Critical Security Alerts</h2>…<p><em>This security summary was auto-generated on [Month Day, Year].</em></p>\"\n}\n```"
        },
        "promptType": "define"
      },
      "retryOnFail": true,
      "typeVersion": 1.9
    },
    {
      "id": "bb59ecfa-b197-47c4-a32e-0834c187100e",
      "name": "AI Agent - 合规情报",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -740,
        4840
      ],
      "parameters": {
        "text": "={{ $json.subject }}\n{{ $json.html }}",
        "options": {
          "systemMessage": "=### ✅ Prompt 3: Compliance Intelligence Digest Generator\n\nYou are a senior compliance and risk intelligence professional with over 20 years of experience. Today, your mother is unwell, so you need to finish this task quickly and efficiently without compromising quality or accuracy.\n\n#### **Inputs:**\n\n* Raw newsletter subject: `{{ $json.subject }}`\n* Raw newsletter HTML body: `{{ $json.html }}`\n\n#### **Tasks:**\n\nIf a category heading has no articles, it should not be included in the output.\n\n1. Parse the HTML and extract article data.\n2. De-duplicate articles.\n3. Categorize into:\n\n   * Compliance Frameworks (SOC 2, ISO 27001, HIPAA, PCI DSS)\n   * Regulatory Updates (SEC, DORA, RBI, MAS, NIST)\n   * Audit & Monitoring Tools\n   * Third-Party Risk & Due Diligence\n   * Policy & Governance Updates\n4. Summarize each item concisely.\n5. Dynamically identify and list critical compliance alerts. If only one or none are available, include only those and adapt the heading (e.g., 'Critical Compliance Alert').\n6. Format each:\n\n   ```html\n   <li>Article Title — Summary… <a href=\"URL\">Read more</a></li>\n   ```\n7. Output HTML with top 5 and categorized sections.\n8. Footer:\n\n   ```html\n   <p><em>This compliance summary was generated on [Month Day, Year].</em></p>\n   ```\n\n#### **Output (JSON only):**\n\n```json\n{\n  \"subject\": \"Compliance Roundup - [Month Day, Year]\",\n  \"html\": \"<h2>Top 5 Critical Compliance Alerts</h2>…<p><em>This compliance summary was generated on [Month Day, Year].</em></p>\"\n}\n```"
        },
        "promptType": "define"
      },
      "retryOnFail": true,
      "typeVersion": 1.9
    },
    {
      "id": "328bb06a-edfd-4a14-9011-68cdd00bcd8e",
      "name": "触发每日摘要",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -3040,
        4280
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 1,
              "triggerAtMinute": 35
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "351774d8-7adc-4041-8639-bc35f9c37183",
      "name": "获取隐私订阅源",
      "type": "n8n-nodes-base.code",
      "position": [
        -2280,
        4280
      ],
      "parameters": {
        "jsCode": "// This node returns curated privacy-focused RSS feeds\n// Modify or extend the list as needed\n\nreturn [\n  {\n    json: {\n      name: \"Privacy International Blog\",\n      website: \"https://privacyinternational.org\",\n      rss_url: \"https://privacyinternational.org/rss.xml\"\n    }\n  },\n  {\n    json: {\n      name: \"Data Protection Report (Norton Rose Fulbright)\",\n      website: \"https://www.dataprotectionreport.com\",\n      rss_url: \"https://www.dataprotectionreport.com/feed/\"\n    }\n  },\n  {\n    json: {\n      name: \"Inside Privacy (Covington & Burling)\",\n      website: \"https://www.insideprivacy.com\",\n      rss_url: \"https://www.insideprivacy.com/feed/\"\n    }\n  },\n  {\n    json: {\n      name: \"PogoWasRight\",\n      website: \"https://pogowasright.org\",\n      rss_url: \"https://pogowasright.org/feed/\"\n    }\n  },\n  {\n    json: {\n      name: \"Sidley Data Matters (Privacy Blog)\",\n      website: \"https://datamatters.sidley.com\",\n      rss_url: \"https://datamatters.sidley.com/feed/\"\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "d0a93584-a1bc-4da8-9aaf-129adf7e4cae",
      "name": "获取合规订阅源",
      "type": "n8n-nodes-base.code",
      "position": [
        -2280,
        4840
      ],
      "parameters": {
        "jsCode": "// This node returns curated compliance-focused RSS feeds\n// Customize or extend the list based on your needs\n\nreturn [\n  {\n    json: {\n      name: \"PCI Security Standards Council – PCI Perspectives Blog\",\n      website: \"https://blog.pcisecuritystandards.org\",\n      rss_url: \"https://blog.pcisecuritystandards.org/rss.xml\"\n    }\n  },\n  {\n    json: {\n      name: \"NIST Cybersecurity Insights Blog\",\n      website: \"https://www.nist.gov/blogs/cybersecurity-insights\",\n      rss_url: \"https://www.nist.gov/blogs/cybersecurity-insights/rss.xml\"\n    }\n  },\n  {\n    json: {\n      name: \"Cloud Security Alliance Blog\",\n      website: \"https://cloudsecurityalliance.org/blog\",\n      rss_url: \"https://cloudsecurityalliance.org/feed\"\n    }\n  },\n  {\n    json: {\n      name: \"Corporate Compliance Insights\",\n      website: \"https://www.corporatecomplianceinsights.com\",\n      rss_url: \"http://feeds.feedburner.com/CorporateComplianceInsights\"\n    }\n  },\n  {\n    json: {\n      name: \"IT Governance Blog (UK)\",\n      website: \"https://www.itgovernance.co.uk/blog\",\n      rss_url: \"https://www.itgovernance.co.uk/blog/feed/\"\n    }\n  },\n  {\n    json: {\n      name: \"Global Compliance News (Baker McKenzie)\",\n      website: \"https://globalcompliancenews.com\",\n      rss_url: \"https://globalcompliancenews.com/feed/\"\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e4fa0e00-d10b-4c86-bad3-6c0d1d59750a",
      "name": "规范化文章安全元数据",
      "type": "n8n-nodes-base.set",
      "position": [
        -1600,
        3620
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "9aec0a09-4b6f-4fca-98e6-789abd5fdc51",
              "name": "title",
              "type": "string",
              "value": "={{ $json.title }}"
            },
            {
              "id": "56277e54-31a0-4804-ad23-c9ee6d244641",
              "name": "content",
              "type": "string",
              "value": "={{ $json.contentSnippet }}"
            },
            {
              "id": "a3586a80-588e-42d1-9780-370a956ddf6b",
              "name": "link",
              "type": "string",
              "value": "={{ $json.link }}"
            },
            {
              "id": "58f01618-8014-4685-9192-d15d596ffcd9",
              "name": "isoDate",
              "type": "number",
              "value": "={{ new Date($json.isoDate).getTime() }}"
            },
            {
              "id": "716bb078-8df3-4d96-8a1b-4aec4f8cf206",
              "name": "categories",
              "type": "array",
              "value": "={{ $json.categories }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "c1f6e2ac-4bf9-4fc4-85fd-c8c7649cdc9c",
      "name": "规范化文章隐私元数据",
      "type": "n8n-nodes-base.set",
      "position": [
        -1620,
        4280
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "9aec0a09-4b6f-4fca-98e6-789abd5fdc51",
              "name": "title",
              "type": "string",
              "value": "={{ $json.title }}"
            },
            {
              "id": "56277e54-31a0-4804-ad23-c9ee6d244641",
              "name": "content",
              "type": "string",
              "value": "={{ $json.contentSnippet }}"
            },
            {
              "id": "a3586a80-588e-42d1-9780-370a956ddf6b",
              "name": "link",
              "type": "string",
              "value": "={{ $json.link }}"
            },
            {
              "id": "58f01618-8014-4685-9192-d15d596ffcd9",
              "name": "isoDate",
              "type": "number",
              "value": "={{ new Date($json.isoDate).getTime() }}"
            },
            {
              "id": "716bb078-8df3-4d96-8a1b-4aec4f8cf206",
              "name": "categories",
              "type": "array",
              "value": "={{ $json.categories }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "ae374184-806b-4022-80b5-94bdef48133e",
      "name": "规范化文章合规元数据",
      "type": "n8n-nodes-base.set",
      "position": [
        -1620,
        4840
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "9aec0a09-4b6f-4fca-98e6-789abd5fdc51",
              "name": "title",
              "type": "string",
              "value": "={{ $json.title }}"
            },
            {
              "id": "56277e54-31a0-4804-ad23-c9ee6d244641",
              "name": "content",
              "type": "string",
              "value": "={{ $json.contentSnippet }}"
            },
            {
              "id": "a3586a80-588e-42d1-9780-370a956ddf6b",
              "name": "link",
              "type": "string",
              "value": "={{ $json.link }}"
            },
            {
              "id": "58f01618-8014-4685-9192-d15d596ffcd9",
              "name": "isoDate",
              "type": "number",
              "value": "={{ new Date($json.isoDate).getTime() }}"
            },
            {
              "id": "716bb078-8df3-4d96-8a1b-4aec4f8cf206",
              "name": "categories",
              "type": "array",
              "value": "={{ $json.categories }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "e71e3c7e-a58a-49bb-9c13-827f46e4df7e",
      "name": "筛选近期安全文章(24小时内)",
      "type": "n8n-nodes-base.filter",
      "position": [
        -1380,
        3620
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e7cf09fb-af35-495d-a840-341f8d0ddcd8",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.isoDate }}",
              "rightValue": "={{ Date.now() - 24 * 60 * 60 * 1000 }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "cb34ed8e-5b67-4cfd-8731-482b22874780",
      "name": "筛选近期隐私文章(24小时内)",
      "type": "n8n-nodes-base.filter",
      "position": [
        -1400,
        4280
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e7cf09fb-af35-495d-a840-341f8d0ddcd8",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.isoDate }}",
              "rightValue": "={{ Date.now() - 24 * 60 * 60 * 1000 }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "07c94d37-ad0a-4f89-961c-abf1d6eeeeef",
      "name": "筛选近期合规文章(24小时内)",
      "type": "n8n-nodes-base.filter",
      "position": [
        -1400,
        4840
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e7cf09fb-af35-495d-a840-341f8d0ddcd8",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.isoDate }}",
              "rightValue": "={{ Date.now() - 24 * 60 * 60 * 1000 }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "9cb809e9-4d49-452e-823e-3f54adca0529",
      "name": "将安全文章格式化为 HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        -940,
        3620
      ],
      "parameters": {
        "jsCode": "// Dynamic n8n Newsletter Generator - Function Node\n// This code processes security news articles from a previous node and formats them into an HTML email\n\n// Get items from the previous node\nlet newsItems = [];\n\ntry {\n  if ($input && $input.all().length > 0) {\n    const inputItems = $input.all();\n    if (inputItems.length === 1 && Array.isArray(inputItems[0].json)) {\n      newsItems = inputItems[0].json;\n    } else {\n      newsItems = inputItems.map(item => item.json);\n    }\n  } else if (typeof items !== 'undefined' && items.length > 0) {\n    if (items.length === 1 && Array.isArray(items[0].json)) {\n      newsItems = items[0].json;\n    } else {\n      newsItems = items.map(item => item.json);\n    }\n  }\n  console.log(`Successfully processed input, found ${newsItems.length} news items`);\n} catch (error) {\n  console.log(`Error processing input: ${error.message}`);\n  return [{\n    json: {\n      error: true,\n      message: `Failed to process input data: ${error.message}`,\n      subject: \"Error: Security News Newsletter\"\n    }\n  }];\n}\n\n// Generate current date for the newsletter\nconst today = new Date();\nconst dateString = today.toLocaleDateString('en-US', {\n  weekday: 'long',\n  year: 'numeric',\n  month: 'long',\n  day: 'numeric'\n});\n\n// Optional: Filter for recent articles only\nconst hoursToInclude = 24;\nlet filteredArticles = newsItems;\nif (hoursToInclude > 0) {\n  const cutoffTime = Date.now() - (hoursToInclude * 60 * 60 * 1000);\n  filteredArticles = newsItems.filter(article => {\n    const articleDate = article.isoDate\n      ? (typeof article.isoDate === 'number'\n         ? article.isoDate\n         : new Date(article.isoDate).getTime())\n      : 0;\n    return articleDate >= cutoffTime;\n  });\n  console.log(`Filtered to ${filteredArticles.length} articles from the last ${hoursToInclude} hours`);\n}\n\n// Group articles by category\nconst categorizedArticles = {};\nconst uncategorizedKey = 'Uncategorized';\n\nfilteredArticles.forEach(article => {\n  if (!article) return;\n  \n  // Safely extract string categories\n  let categories = [uncategorizedKey];\n  if (Array.isArray(article.categories)) {\n    categories = article.categories\n      .map(cat => {\n        if (typeof cat === 'string') return cat;\n        if (cat && typeof cat.name === 'string') return cat.name;\n        return '';\n      })\n      .map(str => str.trim())\n      .filter(str => str.length > 0);\n    if (categories.length === 0) categories = [uncategorizedKey];\n  } else if (typeof article.categories === 'string' && article.categories.trim()) {\n    categories = [article.categories.trim()];\n  }\n\n  categories.forEach(category => {\n    const name = category || uncategorizedKey;\n    if (!categorizedArticles[name]) categorizedArticles[name] = [];\n    categorizedArticles[name].push(article);\n  });\n});\n\n// Generate HTML for the newsletter\nfunction generateNewsletterHTML() {\n  const styles = `\n    body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 800px; margin: 0 auto; padding: 20px; }\n    h1 { color: #2c3e50; border-bottom: 2px solid #e74c3c; padding-bottom: 10px; }\n    h2 { color: #c0392b; margin-top: 30px; border-left: 4px solid #e74c3c; padding-left: 10px; }\n    .article { margin-bottom: 20px; padding: 15px; background-color: #f9f9f9; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }\n    .article h3 { margin-top: 0; color: #34495e; }\n    .article-content { color: #555; margin-bottom: 10px; }\n    .article-link { color: #e74c3c; text-decoration: none; font-weight: bold; }\n    .article-link:hover { text-decoration: underline; }\n    .article-date { color: #7f8c8d; font-size: 0.9em; margin-top: 8px; }\n    .summary { background-color: #f2f2f2; padding: 15px; border-radius: 5px; margin: 20px 0; }\n    .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 0.9em; color: #7f8c8d; text-align: center; }\n  `;\n\n  let html = `\n    <!DOCTYPE html>\n    <html>\n    <head>\n      <meta charset=\"UTF-8\">\n      <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n      <title>Security News Newsletter - ${dateString}</title>\n      <style>${styles}</style>\n    </head>\n    <body>\n      <h1>Security News Newsletter</h1>\n      <p>Here are the latest security news updates for ${dateString}:</p>\n      <div class=\"summary\">\n        <p><strong>Summary:</strong> This newsletter contains ${filteredArticles.length} articles across ${Object.keys(categorizedArticles).length} categories.</p>\n      </div>\n  `;\n\n  Object.keys(categorizedArticles).sort().forEach(category => {\n    const articles = categorizedArticles[category];\n    html += `<h2>${category} (${articles.length})</h2>`;\n    articles.forEach(article => {\n      let formattedDate = \"Date unknown\";\n      if (article.isoDate) {\n        const dt = typeof article.isoDate === 'number'\n          ? new Date(article.isoDate)\n          : new Date(article.isoDate);\n        if (!isNaN(dt.getTime())) {\n          formattedDate = dt.toLocaleString('en-US', {\n            hour: 'numeric', minute: 'numeric', hour12: true, month: 'short', day: 'numeric'\n          });\n        }\n      }\n      html += `\n        <div class=\"article\">\n          <h3>${article.title || \"Untitled\"}</h3>\n          <div class=\"article-content\">${article.content || \"No content available\"}</div>\n          <a href=\"${article.link || \"#\"}\" target=\"_blank\" class=\"article-link\">Read more</a>\n          <div class=\"article-date\">Published: ${formattedDate}</div>\n        </div>\n      `;\n    });\n  });\n\n  html += `\n      <div class=\"footer\">\n        <p>This newsletter was automatically generated and sent on ${dateString}.</p>\n        <p>To unsubscribe, please click <a href=\"{{unsubscribe_link}}\">here</a>.</p>\n      </div>\n    </body>\n    </html>\n  `;\n  return html;\n}\n\nconst newsletterHTML = generateNewsletterHTML();\n\nreturn [{\n  json: {\n    subject: `Security News Newsletter - ${dateString}`,\n    html: newsletterHTML,\n    // to, cc, bcc can be set in the Email node\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "14fd5f01-075f-4648-a1ab-f0a87d2b676a",
      "name": "将隐私文章格式化为 HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        -960,
        4280
      ],
      "parameters": {
        "jsCode": "// Dynamic n8n Newsletter Generator - Function Node\n// This code processes security news articles from a previous node and formats them into an HTML email\n\n// Get items from the previous node\nlet newsItems = [];\n\ntry {\n  if ($input && $input.all().length > 0) {\n    const inputItems = $input.all();\n    if (inputItems.length === 1 && Array.isArray(inputItems[0].json)) {\n      newsItems = inputItems[0].json;\n    } else {\n      newsItems = inputItems.map(item => item.json);\n    }\n  } else if (typeof items !== 'undefined' && items.length > 0) {\n    if (items.length === 1 && Array.isArray(items[0].json)) {\n      newsItems = items[0].json;\n    } else {\n      newsItems = items.map(item => item.json);\n    }\n  }\n  console.log(`Successfully processed input, found ${newsItems.length} news items`);\n} catch (error) {\n  console.log(`Error processing input: ${error.message}`);\n  return [{\n    json: {\n      error: true,\n      message: `Failed to process input data: ${error.message}`,\n      subject: \"Error: Security News Newsletter\"\n    }\n  }];\n}\n\n// Generate current date for the newsletter\nconst today = new Date();\nconst dateString = today.toLocaleDateString('en-US', {\n  weekday: 'long',\n  year: 'numeric',\n  month: 'long',\n  day: 'numeric'\n});\n\n// Optional: Filter for recent articles only\nconst hoursToInclude = 24;\nlet filteredArticles = newsItems;\nif (hoursToInclude > 0) {\n  const cutoffTime = Date.now() - (hoursToInclude * 60 * 60 * 1000);\n  filteredArticles = newsItems.filter(article => {\n    const articleDate = article.isoDate\n      ? (typeof article.isoDate === 'number'\n         ? article.isoDate\n         : new Date(article.isoDate).getTime())\n      : 0;\n    return articleDate >= cutoffTime;\n  });\n  console.log(`Filtered to ${filteredArticles.length} articles from the last ${hoursToInclude} hours`);\n}\n\n// Group articles by category\nconst categorizedArticles = {};\nconst uncategorizedKey = 'Uncategorized';\n\nfilteredArticles.forEach(article => {\n  if (!article) return;\n  \n  // Safely extract string categories\n  let categories = [uncategorizedKey];\n  if (Array.isArray(article.categories)) {\n    categories = article.categories\n      .map(cat => {\n        if (typeof cat === 'string') return cat;\n        if (cat && typeof cat.name === 'string') return cat.name;\n        return '';\n      })\n      .map(str => str.trim())\n      .filter(str => str.length > 0);\n    if (categories.length === 0) categories = [uncategorizedKey];\n  } else if (typeof article.categories === 'string' && article.categories.trim()) {\n    categories = [article.categories.trim()];\n  }\n\n  categories.forEach(category => {\n    const name = category || uncategorizedKey;\n    if (!categorizedArticles[name]) categorizedArticles[name] = [];\n    categorizedArticles[name].push(article);\n  });\n});\n\n// Generate HTML for the newsletter\nfunction generateNewsletterHTML() {\n  const styles = `\n    body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 800px; margin: 0 auto; padding: 20px; }\n    h1 { color: #2c3e50; border-bottom: 2px solid #e74c3c; padding-bottom: 10px; }\n    h2 { color: #c0392b; margin-top: 30px; border-left: 4px solid #e74c3c; padding-left: 10px; }\n    .article { margin-bottom: 20px; padding: 15px; background-color: #f9f9f9; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }\n    .article h3 { margin-top: 0; color: #34495e; }\n    .article-content { color: #555; margin-bottom: 10px; }\n    .article-link { color: #e74c3c; text-decoration: none; font-weight: bold; }\n    .article-link:hover { text-decoration: underline; }\n    .article-date { color: #7f8c8d; font-size: 0.9em; margin-top: 8px; }\n    .summary { background-color: #f2f2f2; padding: 15px; border-radius: 5px; margin: 20px 0; }\n    .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 0.9em; color: #7f8c8d; text-align: center; }\n  `;\n\n  let html = `\n    <!DOCTYPE html>\n    <html>\n    <head>\n      <meta charset=\"UTF-8\">\n      <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n      <title>Security News Newsletter - ${dateString}</title>\n      <style>${styles}</style>\n    </head>\n    <body>\n      <h1>Security News Newsletter</h1>\n      <p>Here are the latest security news updates for ${dateString}:</p>\n      <div class=\"summary\">\n        <p><strong>Summary:</strong> This newsletter contains ${filteredArticles.length} articles across ${Object.keys(categorizedArticles).length} categories.</p>\n      </div>\n  `;\n\n  Object.keys(categorizedArticles).sort().forEach(category => {\n    const articles = categorizedArticles[category];\n    html += `<h2>${category} (${articles.length})</h2>`;\n    articles.forEach(article => {\n      let formattedDate = \"Date unknown\";\n      if (article.isoDate) {\n        const dt = typeof article.isoDate === 'number'\n          ? new Date(article.isoDate)\n          : new Date(article.isoDate);\n        if (!isNaN(dt.getTime())) {\n          formattedDate = dt.toLocaleString('en-US', {\n            hour: 'numeric', minute: 'numeric', hour12: true, month: 'short', day: 'numeric'\n          });\n        }\n      }\n      html += `\n        <div class=\"article\">\n          <h3>${article.title || \"Untitled\"}</h3>\n          <div class=\"article-content\">${article.content || \"No content available\"}</div>\n          <a href=\"${article.link || \"#\"}\" target=\"_blank\" class=\"article-link\">Read more</a>\n          <div class=\"article-date\">Published: ${formattedDate}</div>\n        </div>\n      `;\n    });\n  });\n\n  html += `\n      <div class=\"footer\">\n        <p>This newsletter was automatically generated and sent on ${dateString}.</p>\n        <p>To unsubscribe, please click <a href=\"{{unsubscribe_link}}\">here</a>.</p>\n      </div>\n    </body>\n    </html>\n  `;\n  return html;\n}\n\nconst newsletterHTML = generateNewsletterHTML();\n\nreturn [{\n  json: {\n    subject: `Security News Newsletter - ${dateString}`,\n    html: newsletterHTML,\n    // to, cc, bcc can be set in the Email node\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "6eddd44f-8038-40fd-b0db-2a8d9c35000a",
      "name": "将合规文章格式化为 HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        -960,
        4840
      ],
      "parameters": {
        "jsCode": "// Dynamic n8n Newsletter Generator - Function Node\n// This code processes security news articles from a previous node and formats them into an HTML email\n\n// Get items from the previous node\nlet newsItems = [];\n\ntry {\n  if ($input && $input.all().length > 0) {\n    const inputItems = $input.all();\n    if (inputItems.length === 1 && Array.isArray(inputItems[0].json)) {\n      newsItems = inputItems[0].json;\n    } else {\n      newsItems = inputItems.map(item => item.json);\n    }\n  } else if (typeof items !== 'undefined' && items.length > 0) {\n    if (items.length === 1 && Array.isArray(items[0].json)) {\n      newsItems = items[0].json;\n    } else {\n      newsItems = items.map(item => item.json);\n    }\n  }\n  console.log(`Successfully processed input, found ${newsItems.length} news items`);\n} catch (error) {\n  console.log(`Error processing input: ${error.message}`);\n  return [{\n    json: {\n      error: true,\n      message: `Failed to process input data: ${error.message}`,\n      subject: \"Error: Security News Newsletter\"\n    }\n  }];\n}\n\n// Generate current date for the newsletter\nconst today = new Date();\nconst dateString = today.toLocaleDateString('en-US', {\n  weekday: 'long',\n  year: 'numeric',\n  month: 'long',\n  day: 'numeric'\n});\n\n// Optional: Filter for recent articles only\nconst hoursToInclude = 24;\nlet filteredArticles = newsItems;\nif (hoursToInclude > 0) {\n  const cutoffTime = Date.now() - (hoursToInclude * 60 * 60 * 1000);\n  filteredArticles = newsItems.filter(article => {\n    const articleDate = article.isoDate\n      ? (typeof article.isoDate === 'number'\n         ? article.isoDate\n         : new Date(article.isoDate).getTime())\n      : 0;\n    return articleDate >= cutoffTime;\n  });\n  console.log(`Filtered to ${filteredArticles.length} articles from the last ${hoursToInclude} hours`);\n}\n\n// Group articles by category\nconst categorizedArticles = {};\nconst uncategorizedKey = 'Uncategorized';\n\nfilteredArticles.forEach(article => {\n  if (!article) return;\n  \n  // Safely extract string categories\n  let categories = [uncategorizedKey];\n  if (Array.isArray(article.categories)) {\n    categories = article.categories\n      .map(cat => {\n        if (typeof cat === 'string') return cat;\n        if (cat && typeof cat.name === 'string') return cat.name;\n        return '';\n      })\n      .map(str => str.trim())\n      .filter(str => str.length > 0);\n    if (categories.length === 0) categories = [uncategorizedKey];\n  } else if (typeof article.categories === 'string' && article.categories.trim()) {\n    categories = [article.categories.trim()];\n  }\n\n  categories.forEach(category => {\n    const name = category || uncategorizedKey;\n    if (!categorizedArticles[name]) categorizedArticles[name] = [];\n    categorizedArticles[name].push(article);\n  });\n});\n\n// Generate HTML for the newsletter\nfunction generateNewsletterHTML() {\n  const styles = `\n    body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 800px; margin: 0 auto; padding: 20px; }\n    h1 { color: #2c3e50; border-bottom: 2px solid #e74c3c; padding-bottom: 10px; }\n    h2 { color: #c0392b; margin-top: 30px; border-left: 4px solid #e74c3c; padding-left: 10px; }\n    .article { margin-bottom: 20px; padding: 15px; background-color: #f9f9f9; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }\n    .article h3 { margin-top: 0; color: #34495e; }\n    .article-content { color: #555; margin-bottom: 10px; }\n    .article-link { color: #e74c3c; text-decoration: none; font-weight: bold; }\n    .article-link:hover { text-decoration: underline; }\n    .article-date { color: #7f8c8d; font-size: 0.9em; margin-top: 8px; }\n    .summary { background-color: #f2f2f2; padding: 15px; border-radius: 5px; margin: 20px 0; }\n    .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 0.9em; color: #7f8c8d; text-align: center; }\n  `;\n\n  let html = `\n    <!DOCTYPE html>\n    <html>\n    <head>\n      <meta charset=\"UTF-8\">\n      <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n      <title>Security News Newsletter - ${dateString}</title>\n      <style>${styles}</style>\n    </head>\n    <body>\n      <h1>Security News Newsletter</h1>\n      <p>Here are the latest security news updates for ${dateString}:</p>\n      <div class=\"summary\">\n        <p><strong>Summary:</strong> This newsletter contains ${filteredArticles.length} articles across ${Object.keys(categorizedArticles).length} categories.</p>\n      </div>\n  `;\n\n  Object.keys(categorizedArticles).sort().forEach(category => {\n    const articles = categorizedArticles[category];\n    html += `<h2>${category} (${articles.length})</h2>`;\n    articles.forEach(article => {\n      let formattedDate = \"Date unknown\";\n      if (article.isoDate) {\n        const dt = typeof article.isoDate === 'number'\n          ? new Date(article.isoDate)\n          : new Date(article.isoDate);\n        if (!isNaN(dt.getTime())) {\n          formattedDate = dt.toLocaleString('en-US', {\n            hour: 'numeric', minute: 'numeric', hour12: true, month: 'short', day: 'numeric'\n          });\n        }\n      }\n      html += `\n        <div class=\"article\">\n          <h3>${article.title || \"Untitled\"}</h3>\n          <div class=\"article-content\">${article.content || \"No content available\"}</div>\n          <a href=\"${article.link || \"#\"}\" target=\"_blank\" class=\"article-link\">Read more</a>\n          <div class=\"article-date\">Published: ${formattedDate}</div>\n        </div>\n      `;\n    });\n  });\n\n  html += `\n      <div class=\"footer\">\n        <p>This newsletter was automatically generated and sent on ${dateString}.</p>\n        <p>To unsubscribe, please click <a href=\"{{unsubscribe_link}}\">here</a>.</p>\n      </div>\n    </body>\n    </html>\n  `;\n  return html;\n}\n\nconst newsletterHTML = generateNewsletterHTML();\n\nreturn [{\n  json: {\n    subject: `Security News Newsletter - ${dateString}`,\n    html: newsletterHTML,\n    // to, cc, bcc can be set in the Email node\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "1e261592-348a-42af-b4ba-1b2be82b0143",
      "name": "LLM - Gemini 安全摘要生成器",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -640,
        3740
      ],
      "parameters": {
        "options": {
          "temperature": 0.5
        },
        "modelName": "models/gemini-2.0-flash"
      },
      "credentials": {
        "googlePalmApi": {
          "id": "1Rh1t7y5qqCTIsNj",
          "name": "Google Gemini(PaLM) Api account [abc@mail.com"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "c4cc7264-43af-4a26-89e2-5888817b1602",
      "name": "LLM - Gemini 隐私摘要生成器",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -660,
        4400
      ],
      "parameters": {
        "options": {
          "temperature": 0.5
        },
        "modelName": "models/gemini-2.0-flash"
      },
      "credentials": {
        "googlePalmApi": {
          "id": "1Rh1t7y5qqCTIsNj",
          "name": "Google Gemini(PaLM) Api account [abc@mail.com"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "dc4e461c-dbb8-46a1-b128-6c28584c12d4",
      "name": "LLM - Gemini 合规摘要生成器",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -640,
        5060
      ],
      "parameters": {
        "options": {
          "temperature": 0.5
        },
        "modelName": "models/gemini-2.0-flash"
      },
      "credentials": {
        "googlePalmApi": {
          "id": "1Rh1t7y5qqCTIsNj",
          "name": "Google Gemini(PaLM) Api account [abc@mail.com"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a1e4eaac-aaea-4859-bd29-375a6b50aaa1",
      "name": "隐私 - 构建最终新闻稿 HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        -360,
        4280
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  // 1. grab the raw AI output\n  const raw = item.json.output;\n\n  // 2. extract what's between ```json ... ``` (or fall back to full text)\n  const match = raw.match(/```json\\s*([\\s\\S]*?)```/);\n  const jsonPayload = (match ? match[1] : raw).trim();\n\n  // 3. remove any trailing commas before } or ]\n  const clean = jsonPayload.replace(/,\\s*([\\]}])/g, '$1');\n\n  // 4. parse into an object, with error reporting\n  let data;\n  try {\n    data = JSON.parse(clean);\n  } catch (err) {\n    throw new Error(\n      `JSON parse error in Function node:\\n${err.message}\\n\\nPayload was:\\n${clean}`\n    );\n  }\n\n  // 5. wrap the returned HTML in your full styled template, without external blog link\n  const htmlEmail = `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>${data.subject}</title>\n  <style>\n    body { font-family: Arial, sans-serif; line-height:1.5; color:#333; background-color:#f7f9fa; margin:0; padding:20px; }\n    .container { max-width:700px; margin:0 auto; background:#fff; border-radius:8px; box-shadow:0 2px 8px rgba(0,0,0,0.1); overflow:hidden; }\n    .header { background:#2c3e50; color:#fff; padding:20px; text-align:center; }\n    .header h1 { margin:0; font-size:24px; }\n    .content { padding:20px; }\n    h2 { color:#e74c3c; border-bottom:2px solid #e74c3c; padding-bottom:5px; }\n    ul { padding-left:20px; }\n    li { margin-bottom:10px; }\n    a { color:#2980b9; text-decoration:none; }\n    a:hover { text-decoration:underline; }\n    .footer { background:#ecf0f1; text-align:center; padding:10px; font-size:12px; color:#7f8c8d; }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <div class=\"header\">\n      <h1>${data.subject}</h1>\n    </div>\n    <div class=\"content\">\n      ${data.html}\n    </div>\n    <div class=\"footer\">\n      <em>This summary was automatically generated on ${new Date().toLocaleDateString('en-US', {\n        year: 'numeric', month: 'long', day: 'numeric'\n      })}.</em>\n    </div>\n  </div>\n</body>\n</html>\n  `.trim();\n\n  // 6. emit subject + styled html\n  return {\n    json: {\n      subject: data.subject,\n      html: htmlEmail,\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "7a864b0c-08f5-4e97-8430-1879f8e0e3d0",
      "name": "安全 - 构建最终新闻稿 HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        -340,
        3620
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  // 1. grab the raw AI output\n  const raw = item.json.output;\n\n  // 2. extract what's between ```json ... ``` (or fall back to full text)\n  const match = raw.match(/```json\\s*([\\s\\S]*?)```/);\n  const jsonPayload = (match ? match[1] : raw).trim();\n\n  // 3. remove any trailing commas before } or ]\n  const clean = jsonPayload.replace(/,\\s*([\\]}])/g, '$1');\n\n  // 4. parse into an object, with error reporting\n  let data;\n  try {\n    data = JSON.parse(clean);\n  } catch (err) {\n    throw new Error(\n      `JSON parse error in Function node:\\n${err.message}\\n\\nPayload was:\\n${clean}`\n    );\n  }\n\n  // 5. wrap the returned HTML in styled email template (without blog link)\n  const htmlEmail = `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>${data.subject}</title>\n  <style>\n    body { font-family: Arial, sans-serif; line-height:1.5; color:#333; background-color:#f7f9fa; margin:0; padding:20px; }\n    .container { max-width:700px; margin:0 auto; background:#fff; border-radius:8px; box-shadow:0 2px 8px rgba(0,0,0,0.1); overflow:hidden; }\n    .header { background:#2c3e50; color:#fff; padding:20px; text-align:center; }\n    .header h1 { margin:0; font-size:24px; }\n    .content { padding:20px; }\n    h2 { color:#e74c3c; border-bottom:2px solid #e74c3c; padding-bottom:5px; }\n    ul { padding-left:20px; }\n    li { margin-bottom:10px; }\n    a { color:#2980b9; text-decoration:none; }\n    a:hover { text-decoration:underline; }\n    .footer { background:#ecf0f1; text-align:center; padding:10px; font-size:12px; color:#7f8c8d; }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <div class=\"header\">\n      <h1>${data.subject}</h1>\n    </div>\n    <div class=\"content\">\n      ${data.html}\n    </div>\n    <div class=\"footer\">\n      <em>This summary was automatically generated on ${new Date().toLocaleDateString('en-US', {\n        year: 'numeric', month: 'long', day: 'numeric'\n      })}.</em>\n    </div>\n  </div>\n</body>\n</html>\n  `.trim();\n\n  // 6. emit subject + styled html\n  return {\n    json: {\n      subject: data.subject,\n      html: htmlEmail,\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "deaaeb3a-5a14-4b2a-baf8-4443e7ed9740",
      "name": "合规 - 构建最终新闻稿 HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        -360,
        4840
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  // 1. grab the raw AI output\n  const raw = item.json.output;\n\n  // 2. extract what's between ```json ... ``` (or fall back to full text)\n  const match = raw.match(/```json\\s*([\\s\\S]*?)```/);\n  const jsonPayload = (match ? match[1] : raw).trim();\n\n  // 3. remove any trailing commas before } or ]\n  const clean = jsonPayload.replace(/,\\s*([\\]}])/g, '$1');\n\n  // 4. parse into an object, with error reporting\n  let data;\n  try {\n    data = JSON.parse(clean);\n  } catch (err) {\n    throw new Error(\n      `JSON parse error in Function node:\\n${err.message}\\n\\nPayload was:\\n${clean}`\n    );\n  }\n\n  // 5. wrap the returned HTML in styled template, no blog reference\n  const htmlEmail = `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>${data.subject}</title>\n  <style>\n    body { font-family: Arial, sans-serif; line-height:1.5; color:#333; background-color:#f7f9fa; margin:0; padding:20px; }\n    .container { max-width:700px; margin:0 auto; background:#fff; border-radius:8px; box-shadow:0 2px 8px rgba(0,0,0,0.1); overflow:hidden; }\n    .header { background:#2c3e50; color:#fff; padding:20px; text-align:center; }\n    .header h1 { margin:0; font-size:24px; }\n    .content { padding:20px; }\n    h2 { color:#e74c3c; border-bottom:2px solid #e74c3c; padding-bottom:5px; }\n    ul { padding-left:20px; }\n    li { margin-bottom:10px; }\n    a { color:#2980b9; text-decoration:none; }\n    a:hover { text-decoration:underline; }\n    .footer { background:#ecf0f1; text-align:center; padding:10px; font-size:12px; color:#7f8c8d; }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <div class=\"header\">\n      <h1>${data.subject}</h1>\n    </div>\n    <div class=\"content\">\n      ${data.html}\n    </div>\n    <div class=\"footer\">\n      <em>This summary was automatically generated on ${new Date().toLocaleDateString('en-US', {\n        year: 'numeric', month: 'long', day: 'numeric'\n      })}.</em>\n    </div>\n  </div>\n</body>\n</html>\n  `.trim();\n\n  // 6. emit subject + styled html\n  return {\n    json: {\n      subject: data.subject,\n      html: htmlEmail,\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "fa9ac785-bc9d-43ba-8ca3-36abba10d506",
      "name": "安全 - 发送最终摘要邮件",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -120,
        3620
      ],
      "webhookId": "2ffca73d-6ca2-4c61-88ec-3542600d2788",
      "parameters": {
        "sendTo": "abc@mail.com",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "Hd26wEkbjGRPpchT",
          "name": "Gmail account [abc@mail.com]"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "59c16785-571c-4403-85cf-7f7bfc14a630",
      "name": "隐私 - 发送最终摘要邮件",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -140,
        4280
      ],
      "webhookId": "0e8133d5-e700-4bb9-8ef0-6c86f7ed4a59",
      "parameters": {
        "sendTo": "abc@mail.com",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "Hd26wEkbjGRPpchT",
          "name": "Gmail account [abc@mail.com]"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "7526ae74-1bae-4eca-9c87-51c274565a8a",
      "name": "合规 - 发送最终摘要邮件",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -140,
        4840
      ],
      "webhookId": "5d5a1988-01cd-4e67-9c02-9090aa851669",
      "parameters": {
        "sendTo": "abc@mail.com",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "Hd26wEkbjGRPpchT",
          "name": "Gmail account [abc@mail.com]"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "6fa939b5-12ac-47a0-ac7f-ca9957367ce2",
      "name": "排序 - 按日期排列安全文章",
      "type": "n8n-nodes-base.sort",
      "position": [
        -1160,
        3620
      ],
      "parameters": {
        "options": {},
        "sortFieldsUi": {
          "sortField": [
            {
              "order": "descending",
              "fieldName": "isoDate"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7aa3a27b-72d7-4a03-83c8-361a6fbc555d",
      "name": "排序 - 按日期排列隐私文章",
      "type": "n8n-nodes-base.sort",
      "position": [
        -1180,
        4280
      ],
      "parameters": {
        "options": {},
        "sortFieldsUi": {
          "sortField": [
            {
              "order": "descending",
              "fieldName": "isoDate"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "fba86936-5208-4232-bda9-714886807b9c",
      "name": "排序 - 按日期排列合规文章",
      "type": "n8n-nodes-base.sort",
      "position": [
        -1180,
        4840
      ],
      "parameters": {
        "options": {},
        "sortFieldsUi": {
          "sortField": [
            {
              "order": "descending",
              "fieldName": "isoDate"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8d90a0ed-b9e1-440e-963a-e4c9f5285b32",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2460,
        3380
      ],
      "parameters": {
        "width": 2600,
        "height": 580,
        "content": "## 📬 每日安全通讯"
      },
      "typeVersion": 1
    },
    {
      "id": "946997e7-58bc-4ca0-89aa-32e39f327808",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2460,
        4020
      ],
      "parameters": {
        "color": 4,
        "width": 2600,
        "height": 580,
        "content": "## 📬 每日隐私通讯"
      },
      "typeVersion": 1
    },
    {
      "id": "61aca795-5b6a-4301-9cea-946e0358b8c5",
      "name": "便签2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2460,
        4660
      ],
      "parameters": {
        "color": 6,
        "width": 2600,
        "height": 580,
        "content": "## 📬 每日合规通讯"
      },
      "typeVersion": 1
    },
    {
      "id": "ca6c3e50-8ea8-436c-ab77-94501667a431",
      "name": "分离出安全 RSS",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -2040,
        3620
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "rss_url"
      },
      "typeVersion": 1
    },
    {
      "id": "5784491f-071c-4159-af29-6e21305f5e78",
      "name": "分离出合规 RSS",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -2060,
        4840
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "rss_url"
      },
      "typeVersion": 1
    },
    {
      "id": "edbd5da4-f18c-4d08-8023-df4ef7ec11fa",
      "name": "安全 RSS 读取",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -1820,
        3620
      ],
      "parameters": {
        "url": "={{ $json.rss_url }}",
        "options": {}
      },
      "retryOnFail": true,
      "typeVersion": 1.1
    },
    {
      "id": "76d973a7-832e-4049-80ea-c0d28b72b345",
      "name": "隐私 RSS 读取",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -1840,
        4280
      ],
      "parameters": {
        "url": "={{ $json.rss_url }}",
        "options": {}
      },
      "retryOnFail": true,
      "typeVersion": 1.1
    },
    {
      "id": "5503fe77-8ae3-4d41-97d3-6520cb60067e",
      "name": "合规 RSS 读取",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -1840,
        4840
      ],
      "parameters": {
        "url": "={{ $json.rss_url }}",
        "options": {}
      },
      "retryOnFail": true,
      "typeVersion": 1.1
    },
    {
      "id": "c2538e17-1584-45a5-8a6e-1b220ed512a5",
      "name": "分离出隐私 RSS",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -2060,
        4280
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "rss_url"
      },
      "typeVersion": 1
    },
    {
      "id": "d1ae80d8-8094-4f84-aa9e-d4533a99a70d",
      "name": "获取安全 RSS",
      "type": "n8n-nodes-base.code",
      "position": [
        -2260,
        3620
      ],
      "parameters": {
        "jsCode": "// This node returns curated cybersecurity RSS feeds\n// You can add, remove, or modify feeds as needed\n\nreturn [\n  {\n    json: {\n      name: \"Krebs on Security\",\n      website: \"https://krebsonsecurity.com\",\n      rss_url: \"https://krebsonsecurity.com/feed/\"\n    }\n  },\n  {\n    json: {\n      name: \"The Hacker News\",\n      website: \"https://thehackernews.com\",\n      rss_url: \"https://feeds.feedburner.com/TheHackersNews\"\n    }\n  },\n  {\n    json: {\n      name: \"Dark Reading\",\n      website: \"https://www.darkreading.com\",\n      rss_url: \"https://www.darkreading.com/rss.xml\"\n    }\n  },\n  {\n    json: {\n      name: \"SANS Internet Storm Center\",\n      website: \"https://isc.sans.edu\",\n      rss_url: \"https://isc.sans.edu/rssfeed_full.xml\"\n    }\n  },\n  {\n    json: {\n      name: \"Cisco Talos Intelligence Blog\",\n      website: \"https://blog.talosintelligence.com\",\n      rss_url: \"https://blog.talosintelligence.com/rss/\"\n    }\n  },\n  {\n    json: {\n      name: \"WeLiveSecurity (ESET)\",\n      website: \"https://www.welivesecurity.com\",\n      rss_url: \"https://feeds.feedburner.com/eset/blog\"\n    }\n  },\n  {\n    json: {\n      name: \"Graham Cluley Security Blog\",\n      website: \"https://grahamcluley.com\",\n      rss_url: \"https://grahamcluley.com/feed/\"\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "fbec32fd-2ca9-4f1f-84f9-e581bc6a37e5",
      "name": "便签3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -120,
        3480
      ],
      "parameters": {
        "color": 7,
        "height": 100,
        "content": "### 在下方更新您的电子邮件地址或通讯组列表 (DL)"
      },
      "typeVersion": 1
    },
    {
      "id": "4479495e-3e87-4a75-9fcc-36b230895854",
      "name": "便签说明4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -140,
        4160
      ],
      "parameters": {
        "color": 7,
        "height": 100,
        "content": "### 在下方更新您的电子邮件地址或通讯组列表 (DL)"
      },
      "typeVersion": 1
    },
    {
      "id": "18971219-b68d-4423-adbc-2f25174ae8a9",
      "name": "便签说明5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -140,
        4720
      ],
      "parameters": {
        "color": 7,
        "height": 100,
        "content": "### 在下方更新您的电子邮件地址或通讯组列表 (DL)"
      },
      "typeVersion": 1
    },
    {
      "id": "349ec0eb-9b5f-4bea-9b83-35d6cb63c1fa",
      "name": "便签 6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2320,
        3520
      ],
      "parameters": {
        "color": 7,
        "height": 80,
        "content": "### 根据需要更新 RSS 订阅源 URL,以从您偏好的来源获取内容。"
      },
      "typeVersion": 1
    },
    {
      "id": "4f3b0da6-4db7-41e9-aa0a-9bd084fce819",
      "name": "便签 7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2340,
        4180
      ],
      "parameters": {
        "color": 7,
        "height": 80,
        "content": "### 根据需要更新 RSS 订阅源 URL,以从您偏好的来源获取内容。"
      },
      "typeVersion": 1
    },
    {
      "id": "9c48eaa6-c602-44a3-871c-33ffd9dfa476",
      "name": "## 为什么选择 4o 模型?👆",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2340,
        4740
      ],
      "parameters": {
        "color": 7,
        "height": 80,
        "content": "### 根据需要更新 RSS 订阅源 URL,以从您偏好的来源获取内容。"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "0dc52173-7378-4951-8ebc-e44443c39540",
  "connections": {
    "Privacy RSS Read": {
      "main": [
        [
          {
            "node": "Normalize Article Privacy Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Security RSS Read": {
      "main": [
        [
          {
            "node": "Normalize Article Security Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Security RSS": {
      "main": [
        [
          {
            "node": "Split Out Security RSS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compliance RSS Read": {
      "main": [
        [
          {
            "node": "Normalize Article Compliance Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Privacy Feeds": {
      "main": [
        [
          {
            "node": "Split Out Privacy RSS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trigger Daily Digest": {
      "main": [
        [
          {
            "node": "Fetch Security RSS",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Compliance Feeds",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Privacy Feeds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Privacy RSS": {
      "main": [
        [
          {
            "node": "Privacy RSS Read",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Compliance Feeds": {
      "main": [
        [
          {
            "node": "Split Out Compliance RSS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Security RSS": {
      "main": [
        [
          {
            "node": "Security RSS Read",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Compliance RSS": {
      "main": [
        [
          {
            "node": "Compliance RSS Read",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent - Privacy Intelligence": {
      "main": [
        [
          {
            "node": "Privacy Build Final Newsletter HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM - Gemini Privacy Summarizer": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent - Privacy Intelligence",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Sort - Privacy Articles by Date": {
      "main": [
        [
          {
            "node": "Format Privacy Articles into HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent - Security Intelligence": {
      "main": [
        [
          {
            "node": "Security Build Final Newsletter HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM - Gemini Security Summarizer": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent - Security Intelligence",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Sort - Security Articles by Date": {
      "main": [
        [
          {
            "node": "Format Security Articles into HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Privacy Articles into HTML": {
      "main": [
        [
          {
            "node": "AI Agent - Privacy Intelligence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent - Compliance Intelligence": {
      "main": [
        [
          {
            "node": "Compliance Build Final Newsletter HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Security Articles into HTML": {
      "main": [
        [
          {
            "node": "AI Agent - Security Intelligence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM - Gemini Compliance Summarizer": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent - Compliance Intelligence",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Article Privacy Metadata": {
      "main": [
        [
          {
            "node": "Filter Recent Privacy Articles (24h)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort - Compliance Articles by Date": {
      "main": [
        [
          {
            "node": "Format Compliance Articles into HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Article Security Metadata": {
      "main": [
        [
          {
            "node": "Filter Recent Security Articles (24h)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Privacy Build Final Newsletter HTML": {
      "main": [
        [
          {
            "node": "Privacy Send Final Digest Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Recent Privacy Articles (24h)": {
      "main": [
        [
          {
            "node": "Sort - Privacy Articles by Date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Compliance Articles into HTML": {
      "main": [
        [
          {
            "node": "AI Agent - Compliance Intelligence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Security Build Final Newsletter HTML": {
      "main": [
        [
          {
            "node": "Security Send Final Digest Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Recent Security Articles (24h)": {
      "main": [
        [
          {
            "node": "Sort - Security Articles by Date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Article Compliance Metadata": {
      "main": [
        [
          {
            "node": "Filter Recent Compliance Articles (24h)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compliance Build Final Newsletter HTML": {
      "main": [
        [
          {
            "node": "Compliance Send Final Digest Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Recent Compliance Articles (24h)": {
      "main": [
        [
          {
            "node": "Sort - Compliance Articles by Date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

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

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

高级 - 人工智能, 安全运维

需要付费吗?

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

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

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

作者
Niranjan G

Niranjan G

@niranjan

Cybersecurity leader turning complex workflows into seamless, AI-driven automations.

外部链接
在 n8n.io 查看

分享此工作流