{
  "name": "YouTube Ingestion",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 6
            }
          ]
        }
      },
      "id": "schedule-trigger",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "mode": "json",
        "jsonOutput": "[\n  {\"channel_id\": \"UCupvZG-5ko_eiXAupbDfxWw\", \"name\": \"CNN\", \"bias\": -0.6},\n  {\"channel_id\": \"UCXIJgqnII2ZOINSWNOGFThA\", \"name\": \"Fox News\", \"bias\": 0.6},\n  {\"channel_id\": \"UCeY0bbntWzzVIaj2z3QigXg\", \"name\": \"NBC News\", \"bias\": -0.3},\n  {\"channel_id\": \"UCBi2mrWuNuyYy4gbM6fU2Eg\", \"name\": \"ABC News\", \"bias\": -0.2},\n  {\"channel_id\": \"UC16niRr50-MSBwiO3YDb3RA\", \"name\": \"BBC News\", \"bias\": 0.0},\n  {\"channel_id\": \"UCYfdidRxbB8Qhf0Nx7ioOYw\", \"name\": \"Newsmax\", \"bias\": 0.8},\n  {\"channel_id\": \"UCVgO639RlClg9xZa3TSbIwA\", \"name\": \"MSNBC\", \"bias\": -0.7},\n  {\"channel_id\": \"UCaXg93e976WSpkJYNyqo2WA\", \"name\": \"Reuters\", \"bias\": 0.0},\n  {\"channel_id\": \"UCIRYagvkLYaLam7XqMq2gGg\", \"name\": \"Associated Press\", \"bias\": 0.0},\n  {\"channel_id\": \"UCvixJtaXuNdMPUGdOPcY8Ag\", \"name\": \"The Young Turks\", \"bias\": -0.8},\n  {\"channel_id\": \"UCnQC_G5Xsjhp9VbJKyQWRUg\", \"name\": \"Ben Shapiro\", \"bias\": 0.8},\n  {\"channel_id\": \"UCOt1K85e9rN3SfJz6n2vKjg\", \"name\": \"C-Span\", \"bias\": 0.0}\n]",
        "options": {}
      },
      "id": "channel-list",
      "name": "Channel List",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.3,
      "position": [
        500,
        300
      ]
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {}
      },
      "id": "split-channels",
      "name": "Split Channels",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        700,
        300
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://www.youtube.com/feeds/videos.xml?channel_id={{ $json.channel_id }}",
        "options": {}
      },
      "id": "fetch-rss-feed",
      "name": "Fetch Channel RSS Feed",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        900,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "const xml = items[0].json.body || items[0].json;\nconst entries = xml.match(/<entry>([\\s\\S]*?)<\\/entry>/g) || [];\n\nconst results = entries.map(entry => {\n  const videoId = (entry.match(/<yt:videoId>([^<]+)<\\/yt:videoId>/) || [])[1];\n  const title = (entry.match(/<title>([^<]+)<\\/title>/) || [])[1];\n  const published = (entry.match(/<published>([^<]+)<\\/published>/) || [])[1];\n  const link = (entry.match(/<link[^>]+href=\"([^\"]+)\"/i) || [])[1];\n  \n  return {\n    video_id: videoId,\n    title: title,\n    published_at: published,\n    video_url: link,\n    channel_id: $input.first().json.channel_id,\n    channel_name: $input.first().json.name,\n    channel_bias: $input.first().json.bias\n  };\n}).filter(v => v.video_id);\n\nreturn results.map(r => ({ json: r }));"
      },
      "id": "parse-entries",
      "name": "Parse Video Entries",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1100,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.video_id }}",
              "operation": "isNotEmpty"
            }
          ]
        }
      },
      "id": "filter-valid",
      "name": "Filter Valid Videos",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 1,
      "position": [
        1300,
        300
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ $env.DATABASE_URL ? 'http://localhost:3002' : 'http://api:8000' }}/api/youtube/check",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "video_id",
              "value": "={{ $json.video_id }}"
            }
          ]
        },
        "options": {}
      },
      "id": "check-duplicate",
      "name": "Check If Already Stored",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1500,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.exists }}",
              "value2": false
            }
          ]
        }
      },
      "id": "filter-new",
      "name": "Filter New Videos",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 1,
      "position": [
        1700,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.SCRAPER_URL || 'http://localhost:3002' }}/api/youtube/transcript",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "video_id",
              "value": "={{ $json.video_id }}"
            },
            {
              "name": "title",
              "value": "={{ $json.title }}"
            },
            {
              "name": "channel_id",
              "value": "={{ $json.channel_id }}"
            },
            {
              "name": "channel_name",
              "value": "={{ $json.channel_name }}"
            },
            {
              "name": "published_at",
              "value": "={{ $json.published_at }}"
            }
          ]
        },
        "options": {}
      },
      "id": "extract-transcript",
      "name": "Extract Transcript",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1900,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.DATABASE_URL ? 'http://localhost:3002' : 'http://api:8000' }}/api/youtube/store",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "video_id",
              "value": "={{ $json.video_id }}"
            },
            {
              "name": "title",
              "value": "={{ $json.title }}"
            },
            {
              "name": "channel_id",
              "value": "={{ $json.channel_id }}"
            },
            {
              "name": "channel_name",
              "value": "={{ $json.channel_name }}"
            },
            {
              "name": "transcript_text",
              "value": "={{ $json.transcript_text }}"
            },
            {
              "name": "transcript_segments",
              "value": "={{ JSON.stringify($json.transcript_segments || []) }}"
            },
            {
              "name": "topic",
              "value": "={{ $json.topic || 'general' }}"
            },
            {
              "name": "published_at",
              "value": "={{ $json.published_at }}"
            }
          ]
        },
        "options": {}
      },
      "id": "store-transcript",
      "name": "Store Transcript",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2100,
        300
      ]
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Channel List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Channel List": {
      "main": [
        [
          {
            "node": "Split Channels",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Channels": {
      "main": [
        [
          {
            "node": "Fetch Channel RSS Feed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Channel RSS Feed": {
      "main": [
        [
          {
            "node": "Parse Video Entries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Video Entries": {
      "main": [
        [
          {
            "node": "Filter Valid Videos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Valid Videos": {
      "main": [
        [
          {
            "node": "Check If Already Stored",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check If Already Stored": {
      "main": [
        [
          {
            "node": "Filter New Videos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter New Videos": {
      "main": [
        [
          {
            "node": "Extract Transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Transcript": {
      "main": [
        [
          {
            "node": "Store Transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner"
  },
  "triggerCount": 1,
  "updatedAt": "2026-04-03T00:00:00.000Z",
  "versionId": "1"
}