{
  "id": "UNFe3hdtDsylgfrT",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Save key RSS articles to Notion and notify users via Telegram",
  "tags": [],
  "nodes": [
    {
      "id": "3b526441-b43c-479b-bb3d-a7554c753ea0",
      "name": "Main Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1584,
        736
      ],
      "parameters": {
        "width": 440,
        "height": 919,
        "content": "## \ud83d\udcf0 Save key RSS articles to Notion and notify users via Telegram\n\n**Automatically collect, analyze, and organize RSS feed articles using AI**\n\n### How it works\n\n1. **Collect**: Fetches articles from multiple RSS feeds (TechCrunch, Dev.to, The Verge)\n2. **Deduplicate**: Removes duplicate articles across feeds\n3. **Filter**: Checks against existing Notion database to avoid re-processing\n4. **Analyze**: Uses OpenAI to analyze content, extract insights, and assign priority scores\n5. **Save**: Stores high-priority articles (\u226560 score) in Notion database\n6. **Notify**: Sends Telegram alerts for important articles\n7. **Cleanup**: Optionally archives articles older than 30 days\n\n### Setup\n\n1. **Connect RSS Feeds**: Modify RSS nodes with your preferred feeds\n2. **Configure Notion**: Set your Notion database ID in all Notion nodes\n3. **Add OpenAI Key**: Configure OpenAI credentials for AI analysis\n4. **Set Telegram Bot**: Add your Telegram chat ID for notifications\n5. **Schedule**: Replace Manual Trigger with Schedule Trigger for automation\n\n### Customization\n\n\u2022 Adjust priority threshold in Priority Filter node (default: \u226560)\n\u2022 Modify AI analysis prompt for different categorization\n\u2022 Change retention period in cleanup section (default: 30 days)\n\u2022 Add more RSS feeds by duplicating RSS nodes"
      },
      "typeVersion": 1
    },
    {
      "id": "ac25fbbe-2180-42b1-a3c5-4338ce879d0a",
      "name": "Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -640,
        832
      ],
      "parameters": {
        "color": 7,
        "width": 314,
        "height": 720,
        "content": "## 1. Collect RSS Feeds\n\nFetches articles from configured RSS feeds in parallel"
      },
      "typeVersion": 1
    },
    {
      "id": "4e5ba07c-d6eb-4e11-9039-bf083557ce38",
      "name": "Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -304,
        1040
      ],
      "parameters": {
        "color": 6,
        "width": 394,
        "height": 336,
        "content": "## 2. Process & Deduplicate\n\nMerges all feeds and removes duplicate articles based on URL/GUID"
      },
      "typeVersion": 1
    },
    {
      "id": "0e4d8181-a7b9-48ca-a948-0c7c93244a8c",
      "name": "Section 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        128,
        1040
      ],
      "parameters": {
        "color": 7,
        "width": 442,
        "height": 352,
        "content": "## 3. Filter New Content\n\nCompares against Notion database to find only new articles"
      },
      "typeVersion": 1
    },
    {
      "id": "5c7737c1-b454-4af7-bad2-8896fe7b6746",
      "name": "Warning AI",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        688,
        784
      ],
      "parameters": {
        "color": 3,
        "width": 280,
        "height": 237.6,
        "content": "## \u26a0\ufe0f REQUIRED: OpenAI API Key\n\nAdd your OpenAI API credentials to this node before running the workflow"
      },
      "typeVersion": 1
    },
    {
      "id": "d1886030-c6f2-495a-a0ef-8a7d662046d7",
      "name": "Section 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        592,
        1040
      ],
      "parameters": {
        "color": 6,
        "width": 474,
        "height": 352,
        "content": "## 4. AI Analysis\n\nUses OpenAI to analyze content, extract summary, categorize, and assign priority scores"
      },
      "typeVersion": 1
    },
    {
      "id": "1b658bf5-0899-4bbf-9245-a44d411d631a",
      "name": "Section 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1104,
        1040
      ],
      "parameters": {
        "color": 7,
        "width": 778,
        "height": 352,
        "content": "## 5. Save & Notify\n\nFilters high-priority articles, saves to Notion, and sends Telegram notifications"
      },
      "typeVersion": 1
    },
    {
      "id": "89e3217f-f255-4653-bf6c-892cc6febb09",
      "name": "Section 6 Optional",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -640,
        1600
      ],
      "parameters": {
        "color": 6,
        "width": 682,
        "height": 320,
        "content": "## 6. Cleanup (Optional)\n\nAutomatically archives articles older than 30 days. Enable these nodes if desired."
      },
      "typeVersion": 1
    },
    {
      "id": "6957834b-fe08-4f4c-8bff-882f690f9f9a",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -1072,
        1504
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "5390a8c7-0e10-4cb5-be32-9226d1637ad8",
      "name": "RSS TechCrunch",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -544,
        976
      ],
      "parameters": {
        "url": "https://techcrunch.com/feed/",
        "options": {
          "ignoreSSL": true
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "89578d2f-9cfe-4d7d-b1ed-ec68cb23df71",
      "name": "RSS Dev.to",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -544,
        1184
      ],
      "parameters": {
        "url": "https://dev.to/feed",
        "options": {
          "ignoreSSL": true
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "fc658ec5-df7d-4740-b6d5-d009f28dd5a4",
      "name": "RSS The Verge",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        -544,
        1376
      ],
      "parameters": {
        "url": "https://www.theverge.com/rss/index.xml",
        "options": {
          "ignoreSSL": true
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "b64390db-e13b-4f68-a609-f7785310c09c",
      "name": "Remove Duplicates",
      "type": "n8n-nodes-base.code",
      "position": [
        -64,
        1184
      ],
      "parameters": {
        "jsCode": "// Get all merged items from RSS feeds\nconst allItems = items;\n\n// Create a Set to track unique article URLs/GUIDs\nconst seenUrls = new Set();\nconst uniqueArticles = [];\n\n// Filter duplicates based on GUID or link\nfor (const item of allItems) {\n  const identifier = item.json.guid || item.json.link;\n  \n  if (!seenUrls.has(identifier)) {\n    seenUrls.add(identifier);\n    uniqueArticles.push(item);\n  }\n}\n\nconsole.log(`Total articles: ${allItems.length}`);\nconsole.log(`Unique articles: ${uniqueArticles.length}`);\nconsole.log(`Duplicates removed: ${allItems.length - uniqueArticles.length}`);\n\nreturn uniqueArticles;"
      },
      "typeVersion": 2
    },
    {
      "id": "d26dcdce-d1ee-473b-83e9-6f4c37c34dc5",
      "name": "Get Existing Articles",
      "type": "n8n-nodes-base.notion",
      "position": [
        192,
        1184
      ],
      "parameters": {
        "options": {},
        "resource": "databasePage",
        "operation": "getAll",
        "returnAll": true,
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_NOTION_DATABASE_ID",
          "cachedResultUrl": "https://www.notion.so/YOUR_NOTION_DATABASE_ID",
          "cachedResultName": "Your Notion Database"
        }
      },
      "executeOnce": true,
      "typeVersion": 2.2,
      "alwaysOutputData": true
    },
    {
      "id": "eafc68d5-630e-4e9f-8551-da1f11a4600c",
      "name": "Filter New Articles",
      "type": "n8n-nodes-base.code",
      "position": [
        432,
        1184
      ],
      "parameters": {
        "jsCode": "// Get existing Notion articles\nconst notionItems = $(\"Get Existing Articles\").all() || [];\n\nconsole.log('=== FILTER NEW ARTICLES DEBUG ===');\nconsole.log('Total items in Notion:', notionItems.length);\n\n// Extract existing URLs from Notion - try different possible locations\nconst existingUrls = [];\n\nfor (const item of notionItems) {\n  let url = null;\n  \n  // Try different possible URL locations in Notion response\n  if (item.json.properties?.URL?.url) {\n    url = item.json.properties.URL.url;\n  } else if (item.json.properties?.URL?.rich_text?.[0]?.plain_text) {\n    url = item.json.properties.URL.rich_text[0].plain_text;\n  } else if (item.json.property_url) {\n    url = item.json.property_url;\n  } else if (item.json.url) {\n    url = item.json.url;\n  }\n  \n  if (url) {\n    existingUrls.push(url);\n  }\n}\n\nconsole.log('URLs extracted from Notion:', existingUrls.length);\nconsole.log('First 3 URLs:', existingUrls.slice(0, 3));\n\n// Get all RSS articles\nconst allRssItems = $(\"Remove Duplicates\").all();\nconsole.log('Total unique RSS articles:', allRssItems.length);\n\n// Filter only new articles\nconst newArticles = allRssItems.filter(item => {\n  const articleUrl = item.json.guid || item.json.link;\n  const isNew = !existingUrls.includes(articleUrl);\n  \n  if (!isNew) {\n    console.log('DUPLICATE FOUND:', articleUrl);\n  }\n  \n  return isNew;\n});\n\nconsole.log('New articles found:', newArticles.length);\nconsole.log('Duplicates filtered:', allRssItems.length - newArticles.length);\n\nif (newArticles.length > 0) {\n  return newArticles;\n} else {\n  console.log('No new articles to process');\n  return [];\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "39667e4e-a7bf-4e26-b66b-de329943fd3d",
      "name": "AI Content Analysis",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        672,
        1184
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ {\n  \"model\": \"gpt-4o-mini\",\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are an expert content analyst. Analyze articles and return ONLY valid JSON with these fields: summary (max 3 sentences), category (Technology/AI/Business/DevOps/Other), tags (array of 3-5 keywords), sentiment (Positive/Neutral/Negative), priority_score (0-100 based on relevance and quality). Be concise and accurate.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"Analyze this article:\\n\\nTitle: \" + $json.title.replace(/\"/g, \"'\").replace(/\\n/g, \" \") + \"\\n\\nContent: \" + ($json.contentSnippet || $json.description || \"\").substring(0, 500).replace(/\"/g, \"'\").replace(/\\n/g, \" \") + \"\\n\\nProvide analysis in JSON format.\"\n    }\n  ],\n  \"max_tokens\": 300,\n  \"temperature\": 0.3\n} }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "bac96659-952a-431b-865f-23d4585c3555",
      "name": "Parse AI Analysis",
      "type": "n8n-nodes-base.code",
      "position": [
        912,
        1184
      ],
      "parameters": {
        "jsCode": "// Process ALL items from AI Content Analysis\nconst results = [];\n\nfor (let i = 0; i < items.length; i++) {\n  const aiResponse = items[i].json;\n  \n  // Parse AI response\n  let aiAnalysis;\n  try {\n    const content = aiResponse.choices[0].message.content;\n    \n    // Try to extract JSON from markdown code blocks if present\n    const jsonMatch = content.match(/```json\\s*([\\s\\S]*?)```/) || content.match(/({[\\s\\S]*})/);\n    const jsonStr = jsonMatch ? jsonMatch[1] : content;\n    \n    aiAnalysis = JSON.parse(jsonStr.trim());\n  } catch (error) {\n    console.error('Error parsing AI response for item', i, ':', error);\n    // Fallback values if parsing fails\n    aiAnalysis = {\n      summary: 'AI analysis failed',\n      category: 'Other',\n      tags: ['uncategorized'],\n      sentiment: 'Neutral',\n      priority_score: 50\n    };\n  }\n\n  // Get original article data from the same index in Filter New Articles\n  const filterNewArticles = $(\"Filter New Articles\").all();\n  const article = filterNewArticles[i].json;\n\n  // Combine all data\n  results.push({\n    json: {\n      title: article.title,\n      url: article.guid || article.link,\n      summary: aiAnalysis.summary,\n      category: aiAnalysis.category,\n      tags: Array.isArray(aiAnalysis.tags) ? aiAnalysis.tags.join(', ') : aiAnalysis.tags,\n      sentiment: aiAnalysis.sentiment,\n      priority_score: parseInt(aiAnalysis.priority_score) || 50,\n      source: article.creator || article['dc:creator'] || 'Unknown',\n      published_date: article.pubDate || article.isoDate || new Date().toISOString(),\n      original_content: article.contentSnippet || article.description || ''\n    }\n  });\n}\n\nconsole.log('Total items processed:', results.length);\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "acc6df03-dfb3-41bf-b94f-ea1ba13b9e35",
      "name": "Priority Filter (\u226560)",
      "type": "n8n-nodes-base.if",
      "position": [
        1152,
        1184
      ],
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ $json.priority_score }}",
              "value2": 60,
              "operation": "largerEqual"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "61bbfc47-34f2-4511-96df-91d44b48f3f5",
      "name": "Telegram Notification",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1648,
        1168
      ],
      "parameters": {
        "text": "=\ud83d\udd25 **High Priority Article**\n\n\ud83d\udcf0 **{{ $('Parse AI Analysis').item.json.title }}**\n\n\ud83d\udcca Priority: {{ $('Parse AI Analysis').item.json.priority_score }}/100\n\ud83d\udcc2 Category: {{ $('Parse AI Analysis').item.json.category }}\n\ud83d\ude0a Sentiment: {{ $('Parse AI Analysis').item.json.sentiment }}\n\ud83c\udff7\ufe0f Tags: {{ $('Parse AI Analysis').item.json.tags }}\n\n\ud83d\udcdd Summary:\n{{ $('Parse AI Analysis').item.json.summary }}\n\n\ud83d\udd17 [Read Article]({{ $('Parse AI Analysis').item.json.url }})",
        "chatId": "YOUR_TELEGRAM_CHAT_ID",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "6e0989b7-abdd-4367-91e3-323d8ebff4d1",
      "name": "Get Old Articles (>30 days)",
      "type": "n8n-nodes-base.notion",
      "notes": "OPTIONAL: Enable and add filter in Code node to archive old articles",
      "position": [
        -592,
        1728
      ],
      "parameters": {
        "options": {},
        "resource": "databasePage",
        "operation": "getAll",
        "returnAll": true,
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_NOTION_DATABASE_ID",
          "cachedResultUrl": "https://www.notion.so/YOUR_NOTION_DATABASE_ID",
          "cachedResultName": "Your Notion Database"
        }
      },
      "executeOnce": true,
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "ecdd49ce-0af1-48e2-b144-7568d8816c4b",
      "name": "Archive Old Articles",
      "type": "n8n-nodes-base.notion",
      "notes": "OPTIONAL: Enable along with Get Old Articles node for auto-cleanup",
      "position": [
        -144,
        1728
      ],
      "parameters": {
        "pageId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.id }}"
        },
        "options": {},
        "resource": "databasePage",
        "operation": "update",
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "Status|select",
              "selectValue": "Archived"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "3f9cd696-8aea-4530-8ba7-8d8e8ec9c3af",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        -256,
        1168
      ],
      "parameters": {
        "numberInputs": 3
      },
      "typeVersion": 3.2
    },
    {
      "id": "02cde6a5-696c-45e3-be2b-8f27e6b35295",
      "name": "Save to Notion",
      "type": "n8n-nodes-base.notion",
      "position": [
        1424,
        1168
      ],
      "parameters": {
        "simple": false,
        "options": {},
        "resource": "databasePage",
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_NOTION_DATABASE_ID",
          "cachedResultUrl": "https://www.notion.so/YOUR_NOTION_DATABASE_ID",
          "cachedResultName": "Your Notion Database"
        },
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "Title|rich_text",
              "textContent": "={{ $('Parse AI Analysis').item.json.title }}"
            },
            {
              "key": "URL|url",
              "urlValue": "={{ $('Parse AI Analysis').item.json.url }}"
            },
            {
              "key": "Summary|rich_text",
              "textContent": "={{ $('Parse AI Analysis').item.json.summary }}"
            },
            {
              "key": "Category|select",
              "selectValue": "={{ $('Parse AI Analysis').item.json.category }}"
            },
            {
              "key": "Tags|rich_text",
              "textContent": "={{ $('Parse AI Analysis').item.json.tags }}"
            },
            {
              "key": "Sentiment|select",
              "selectValue": "={{ $json.sentiment }}"
            },
            {
              "key": "Priority|number",
              "numberValue": "={{ $json.priority_score }}"
            },
            {
              "key": "Source|rich_text",
              "textContent": "={{ $json.source }}"
            },
            {
              "key": "Published|date",
              "date": "={{ $json.published_date }}",
              "includeTime": false
            },
            {
              "key": "Added Date|date",
              "date": "={{ $now }}"
            },
            {
              "key": "Status|select",
              "selectValue": "New"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "aadd4d51-68ff-4034-a9ce-cafeb971eeab",
      "name": "Filter by Date (>30 days)",
      "type": "n8n-nodes-base.code",
      "position": [
        -384,
        1728
      ],
      "parameters": {
        "jsCode": "const allArticles = $input.all();\n\n// Calculate date 30 days ago\nconst thirtyDaysAgo = new Date();\nthirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);\n\n// Filter articles older than 30 days\nconst oldArticles = allArticles.filter(item => {\n  const dateString = item.json.property_added_date?.start;\n  \n  if (!dateString) {\n    return false;\n  }\n  \n  const articleDate = new Date(dateString);\n  return articleDate < thirtyDaysAgo;\n});\n\nif (oldArticles.length > 0) {\n  return oldArticles;\n} else {\n  return [];\n}"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "e2ead020-6e26-4b4b-94e5-4d3b0df0035a",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Remove Duplicates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RSS Dev.to": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "RSS The Verge": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "RSS TechCrunch",
            "type": "main",
            "index": 0
          },
          {
            "node": "RSS Dev.to",
            "type": "main",
            "index": 0
          },
          {
            "node": "RSS The Verge",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Old Articles (>30 days)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RSS TechCrunch": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save to Notion": {
      "main": [
        [
          {
            "node": "Telegram Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Analysis": {
      "main": [
        [
          {
            "node": "Priority Filter (\u226560)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicates": {
      "main": [
        [
          {
            "node": "Get Existing Articles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Content Analysis": {
      "main": [
        [
          {
            "node": "Parse AI Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter New Articles": {
      "main": [
        [
          {
            "node": "AI Content Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Existing Articles": {
      "main": [
        [
          {
            "node": "Filter New Articles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Priority Filter (\u226560)": {
      "main": [
        [
          {
            "node": "Save to Notion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter by Date (>30 days)": {
      "main": [
        [
          {
            "node": "Archive Old Articles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Old Articles (>30 days)": {
      "main": [
        [
          {
            "node": "Filter by Date (>30 days)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}