{
  "name": "Vorlux AI | RSS Feed to Blog Pipeline (Deduplicated)",
  "nodes": [
    {
      "id": "6b1df1ce-fe8e-457c-908a-62b0141bd3da",
      "name": "Every 2 Hours",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        220,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 2
            }
          ]
        }
      }
    },
    {
      "id": "a37ff197-dc21-4719-aa96-7294b947a34d",
      "name": "Fetch Feeds",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        460,
        300
      ],
      "parameters": {
        "method": "GET",
        "url": "={{$env.VORLUX_HUB_URL}}/api/content/rss/feeds?status=active",
        "options": {
          "timeout": 15000
        }
      }
    },
    {
      "id": "e83a9ce8-7774-4691-b44f-23a1b7974c87",
      "name": "Score Topics",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        700,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const feeds=$input.first().json.data||[];const topics=[];for(const f of feeds){for(const a of (f.articles||[]).slice(0,5)){const r=(a.title||'').match(/ai|stream|gpu|code|react|next|rust|vtuber/i)?8:5;topics.push({title:a.title,url:a.link,source:f.name,relevance:r,summary:(a.description||'').substring(0,500)});}};return [{json:{topics:topics.sort((a,b)=>b.relevance-a.relevance).slice(0,10),count:topics.length}}];"
      }
    },
    {
      "id": "b670e466-8e62-4f6b-92ba-4904f46eeac6",
      "name": "Save Topics",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        940,
        200
      ],
      "parameters": {
        "method": "POST",
        "url": "={{$env.VORLUX_HUB_URL}}/api/content/topics/batch",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({topics:$json.topics}) }}",
        "options": {
          "timeout": 15000
        }
      }
    },
    {
      "id": "4050e1ed-3a12-46c4-bb87-fb6417384a5f",
      "name": "Notify",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        940,
        400
      ],
      "parameters": {
        "method": "POST",
        "url": "={{$env.DISCORD_CONTENT_WEBHOOK}}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\"embeds\":[{\"title\":\"RSS: {{ $json.count }} articles\",\"color\":5793266}]}",
        "options": {
          "timeout": 10000
        }
      }
    },
    {
      "id": "b55dde8c-09d4-4389-91a2-ebc00b3240fa",
      "name": "Deduplicate",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        700,
        160
      ],
      "notes": "Check existing topics and blog posts to prevent duplicates",
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const topics = $input.first().json.topics || [];\n// Fetch existing titles from Hub\nlet existing = new Set();\ntry {\n  const hubUrl = $env.VORLUX_HUB_URL || 'http://localhost:3010';\n  const [topicRes, blogRes] = await Promise.all([\n    fetch(hubUrl + '/api/content/topics?limit=200').then(r => r.json()).catch(() => ({data:[]})),\n    fetch(hubUrl + '/api/blog/posts?limit=200').then(r => r.json()).catch(() => ({data:[]}))\n  ]);\n  for (const t of (topicRes.data || [])) existing.add((t.title || '').toLowerCase().trim());\n  for (const b of (blogRes.data || [])) existing.add((b.title || '').toLowerCase().trim());\n} catch {}\n\n// Filter out duplicates\nconst unique = topics.filter(t => {\n  const title = (t.title || '').toLowerCase().trim();\n  if (existing.has(title)) return false;\n  // Also check for similar titles (80% word overlap)\n  const words = new Set(title.split(/\\s+/));\n  for (const e of existing) {\n    const eWords = new Set(e.split(/\\s+/));\n    const overlap = [...words].filter(w => eWords.has(w)).length;\n    if (overlap / Math.max(words.size, eWords.size) > 0.8) return false;\n  }\n  existing.add(title);\n  return true;\n});\n\nreturn [{json: {topics: unique, originalCount: topics.length, uniqueCount: unique.length, duplicatesRemoved: topics.length - unique.length}}];"
      }
    }
  ],
  "connections": {
    "Every 2 Hours": {
      "main": [
        [
          {
            "node": "Fetch Feeds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Feeds": {
      "main": [
        [
          {
            "node": "Score Topics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Score Topics": {
      "main": [
        [
          {
            "node": "Deduplicate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Deduplicate": {
      "main": [
        [
          {
            "node": "Save Topics",
            "type": "main",
            "index": 0
          },
          {
            "node": "Notify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "saveExecutionProgress": true
  }
}