AutomationFlowsWeb Scraping › Kodi Torrent Episode Fetcher

Kodi Torrent Episode Fetcher

Kodi-Torrent-Episode-Fetcher. Uses httpRequest, @n-octo-n/n8n-nodes-curl. Event-driven trigger; 27 nodes.

Event trigger★★★★☆ complexity27 nodesHTTP Request@N Octo N/N8N Nodes Curl
Web Scraping Trigger: Event Nodes: 27 Complexity: ★★★★☆ Added:

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Kodi-Torrent-Episode-Fetcher",
  "nodes": [
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -720,
        -376
      ],
      "id": "37852d33-e170-4284-9d0b-f60df99e2b73",
      "name": "When clicking \u2018Execute workflow\u2019"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('parameters').first().json.kodi.api_endpoint }}",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "TVShowCollection"
            }
          ]
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json, text/javascript, */*; q=0.01"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "{\n\t\"jsonrpc\": \"2.0\",\n\t\"method\": \"VideoLibrary.GetTVShows\",\n\t\"id\": \"1755435622719\",\n\t\"params\": {\n\t\t\"properties\": [\n\t\t\t\"title\",\n\t\t\t\"year\",\n\t\t\t\"mpaa\",\n\t\t\t\"playcount\",\n\t\t\t\"episode\",\n\t\t\t\"premiered\",\n\t\t\t\"lastplayed\",\n\t\t\t\"file\",\n\t\t\t\"originaltitle\",\n\t\t\t\"sorttitle\",\n\t\t\t\"season\",\n\t\t\t\"watchedepisodes\",\n\t\t\t\"dateadded\",\n\t\t\t\"runtime\",\n\t\t\t\"uniqueid\"\n\t\t],\n\t\t\"limits\": {\n\t\t\t\"start\": 0\n\t\t},\n\t\t\"sort\": {\n\t\t\t\"method\": \"title\",\n\t\t\t\"order\": \"ascending\",\n\t\t\t\"ignorearticle\": true\n\t\t}\n\t}\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -272,
        -376
      ],
      "id": "87857c62-265e-45a1-ac90-b03efe2b5280",
      "name": "GetKodiTVShowCollection"
    },
    {
      "parameters": {
        "jsCode": "/**\n *  n8n Function node \u2013 Return only the tvshows whose `episode` > 0\n *\n *  Input example (single item):\n *  [\n *    {\n *      \"id\": \"...\",\n *      \"jsonrpc\": \"...\",\n *      \"result\": {\n *        \"limits\": { ... },\n *        \"tvshows\": [ \u2026 ]\n *      }\n *    }\n *  ]\n *\n *  Output: a single item containing\n *          { tvshows: [ ...filtered array... ] }\n */\n\nlet shows = [];\n\n// Grab the TV\u2011show list \u2013 safe guard against missing fields\ntry {\n    // The first (and only) item should carry the data\n    const incoming = items[0].json.result;\n    if (incoming && Array.isArray(incoming.tvshows)) {\n        shows = incoming.tvshows;\n    }\n} catch (err) {\n    // In case of unexpected structure, throw a clear error\n    throw new Error('Expected format: items[0].json.result.tvshows array');\n}\n\n// Filter only those where episode number is greater than 0\nconst filteredShows = shows.filter(show => {\n    // Some entries may have missing or non\u2011numeric episode values\n    const ep = Number(show.episode);\n    return !isNaN(ep) && ep > 0;\n});\n\n// Return a single item with the filtered array\nreturn filteredShows;\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -48,
        -376
      ],
      "id": "0d9cc63c-6f01-455a-953b-aba530f713e4",
      "name": "getKodiTvShows"
    },
    {
      "parameters": {
        "url": "={{ $('parameters').first().json.tvmaze.api_endpoint }}",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "imdb",
              "value": "={{ $('getKodiTvShows').item.json.uniqueid.imdb }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        400,
        -280
      ],
      "id": "bb0c3c5d-d57c-493c-850a-2bb650949de0",
      "name": "GetStatusTVShow",
      "notes": "api at https://www.tvmaze.com/api"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('parameters').first().json.kodi.api_endpoint }}",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "EpisodeCollection"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n\t\"jsonrpc\": \"2.0\",\n\t\"method\": \"VideoLibrary.GetEpisodes\",\n\t\"id\": \"1755448022596\",\n\t\"params\": {\n\t\t\"tvshowid\": {{ $json.tvshowid }},\n\t\t\"season\": {{ $json.season }},\n\t\t\"properties\": [\n\t\t\t\"title\",\n\t\t\t\"playcount\",\n\t\t\t\"lastplayed\",\n\t\t\t\"dateadded\",\n\t\t\t\"episode\",\n\t\t\t\"season\",\n\t\t\t\"rating\",\n\t\t\t\"file\",\n\t\t\t\"showtitle\",\n\t\t\t\"tvshowid\",\n\t\t\t\"uniqueid\",\n\t\t\t\"resume\",\n\t\t\t\"firstaired\"\n\t\t],\n\t\t\"limits\": {\n\t\t\t\"start\": 0\n\t\t},\n\t\t\"sort\": {\n\t\t\t\"method\": \"episode\",\n\t\t\t\"order\": \"ascending\",\n\t\t\t\"ignorearticle\": true\n\t\t}\n\t}\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        624,
        -496
      ],
      "id": "d812a347-efd7-4566-b58a-bf887ef2f667",
      "name": "getTVShowEpisodes"
    },
    {
      "parameters": {
        "jsCode": "function getShowPath(tvshowid = 9) {\n  for (const show of $('getKodiTvShows').all()) {\n    if (show.json.tvshowid == tvshowid) {\n      return show;\n    }\n  }\n}\n\nfunction getShowStatus(imdb){\n  for (const s of $('GetStatusTVShow1').all()) {\n    if (s.json.imdb == imdb) {\n      return s.json.status;\n    }\n  }\n}\n\nconst rows = [];\n\n$('getTVShowEpisodes').all().forEach(item => {\n  //\u00a0Acessa o array \u201cepisodes\u201d dentro do objeto result\n  const episodes = item.json.result?.episodes;\n  if (Array.isArray(episodes)) {\n    episodes.forEach(ep => {\n      rows.push({\n        json: {\n          tvshowid   : ep.tvshowid,\n          season     : ep.season,\n          episode     : ep.episode,\n          episode_unique_id    : getShowPath(ep.tvshowid).json.uniqueid.imdb+\"_S\"+String(ep.season).padStart(2, '0')+\"E\"+String(ep.episode).padStart(2, '0'),   \n          filePath : getShowPath(ep.tvshowid).json.file,\n          title : getShowPath(ep.tvshowid).json.title,\n          imdb : getShowPath(ep.tvshowid).json.uniqueid.imdb,\n          status : getShowStatus(getShowPath(ep.tvshowid).json.uniqueid.imdb)\n        }\n      });\n    });\n  }\n});\n\nreturn rows;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1072,
        -376
      ],
      "id": "b2d0a8ed-5d96-40c6-a7ef-b8717b1b359c",
      "name": "getTVShowEpisodes1",
      "executeOnce": false
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('parameters').first().json.kodi.api_endpoint }}",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "SeasonCollection"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n\t\"jsonrpc\": \"2.0\",\n\t\"method\": \"VideoLibrary.GetSeasons\",\n\t\"id\": \"1755447909146\",\n\t\"params\": {\n\t\t\"tvshowid\": {{ $('getKodiTvShows').item.json.tvshowid }},\n\t\t\"properties\": [\n\t\t\t\"season\",\n\t\t\t\"tvshowid\",\n\t\t\t\"episode\",\n\t\t\t\"watchedepisodes\"\n\t\t]\n\t}\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        176,
        -496
      ],
      "id": "230cf273-e596-46e7-a9a3-c763c982f53a",
      "name": "GetKodiSeasons"
    },
    {
      "parameters": {
        "jsCode": "/**\n * n8n Code node \u2013 collect tvshowid & season from every season entry\n *\n * Input:  items (array of objects, each with json.result.seasons)\n * Output: array of { tvshowid, season }\n */\n\nconst result = [];\n\n// loop over every incoming item\nfor (const input of items) {\n  // safely get the seasons array (may be undefined)\n  const seasons = input.json?.result?.seasons || [];\n\n  // each season \u2192 push a new object to the output\n  for (const s of seasons) {\n    result.push({\n      json: {\n        tvshowid: s.tvshowid,\n        season:   s.season\n      }\n    });\n  }\n}\n\nreturn result;\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        400,
        -496
      ],
      "id": "f7eacf95-a2b2-412a-b700-4e1c0a7ad08e",
      "name": "GetKodiSeasons1"
    },
    {
      "parameters": {
        "jsCode": "const output = [];\n\n$('GetStatusTVShow').all().forEach(\n  item => {\n    output.push(\n      {\n        imdb:item.json.externals.imdb, \n        status: item.json.status\n      }\n    )\n  }\n)\n\nreturn output;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        624,
        -280
      ],
      "id": "d74fc468-ccd1-4b9b-836a-52cd6df3353e",
      "name": "GetStatusTVShow1"
    },
    {
      "parameters": {
        "url": "={{ $('parameters').first().json.eztvx.api_endpoint }}",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "imdb_id",
              "value": "={{ $json.imdb.replace('tt','') }}"
            },
            {
              "name": "limit",
              "value": "100"
            }
          ]
        },
        "options": {
          "pagination": {
            "pagination": {
              "parameters": {
                "parameters": [
                  {
                    "name": "page",
                    "value": "={{ $response.body.page + 1 }}"
                  }
                ]
              },
              "paginationCompleteWhen": "other",
              "completeExpression": "={{ $response.body.page == Math.ceil($response.body.torrents_count / $response.body.limit) || $response.body.torrents_count == 0 }}"
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1744,
        -496
      ],
      "id": "07a8ff4c-aa03-4f55-a37d-26a9509d79af",
      "name": "eztvx api",
      "executeOnce": false
    },
    {
      "parameters": {
        "jsCode": "const allTorrents = [];\n\n// Percorre todas as p\u00e1ginas recebidas\nfor (const page of $('eztvx api').all()) {\n  const tts = page.json.torrents;\n  if (Array.isArray(tts)) {\n    allTorrents.push(...tts);\n  }\n}\n\nreturn allTorrents;\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1968,
        -496
      ],
      "id": "e654e18d-18f7-4540-ae08-c0a60853e68b",
      "name": "Merge-Pages"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "eaac5d32-6ff7-40a3-a123-c2e2f616a262",
              "leftValue": "={{ $json.status }}",
              "rightValue": "Running",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        1296,
        -496
      ],
      "id": "3656c94f-e894-4416-95db-beaf4077b2a5",
      "name": "getTVShowEpisodesRunning"
    },
    {
      "parameters": {
        "jsCode": "/* 1\ufe0f\u20e3  Recebemos o \u201citems\u201d que \u00e9 um array de objetos.\n   Cada objeto cont\u00e9m a estrutura `json` com os campos do seu JSON acima. */\n\nconst uniquePairs = new Map();        // usaremos um Map para garantir unicidade\n\nitems.forEach(item => {\n  const imdb      = item.json.imdb;          // valor do campo `imdb`\n  const filePath  = item.json.filePath;      // valor do campo `filePath`\n\n  // a chave ser\u00e1 uma string \u00fanica composta pelos dois valores\n  const key = `${imdb}|${filePath}`;\n\n  // se a chave ainda n\u00e3o existir, adicionamos ao Map\n  if (!uniquePairs.has(key)) {\n    uniquePairs.set(key, { imdb, filePath });\n  }\n});\n\n/* 2\ufe0f\u20e3  Convertendo o Map de volta para um array que n8n entende. \n   Cada elemento do array deve ter a forma `{ json: {...} }`. */\nconst result = Array.from(uniquePairs.values()).map(obj => ({ json: obj }));\n\nreturn result;   // \u2190 este valor ser\u00e1 passado para o pr\u00f3ximo node\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1520,
        -496
      ],
      "id": "955b41b2-3518-4243-b538-f8da5960de48",
      "name": "uniqueTVShows"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cdbe82a2-5b90-4af2-93a6-4325fda64c9f",
              "name": "torrent_url",
              "value": "={{ $json.torrent_url }}",
              "type": "string"
            },
            {
              "id": "c2c89369-cd03-4f55-b475-b54e3f9a89ab",
              "name": "episode",
              "value": "=S{{ String($json.season).padStart(2, '0') }}E{{ String($json.episode).padStart(2, '0') }}",
              "type": "string"
            },
            {
              "id": "eb0acba6-aae0-4bb5-900f-dae3ced803b7",
              "name": "seeds",
              "value": "={{ $json.seeds }}",
              "type": "number"
            },
            {
              "id": "cd845989-040b-4927-b005-4657604a241b",
              "name": "imdb_id",
              "value": "={{ $json.imdb_id }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2640,
        -496
      ],
      "id": "223d026a-42f0-4ac2-88c5-c8a874d7b1ae",
      "name": "clean fields"
    },
    {
      "parameters": {
        "jsCode": "const data = []\n  $(\"getTVShowEpisodes1\").all().forEach(\n    item => data.push(item.json.episode_unique_id)\n  )\n\nreturn {data}"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2640,
        -280
      ],
      "id": "18afd060-c129-4370-95e8-015041930e98",
      "name": "arrayAllEpisodes"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "33d5aea4-cb7f-4467-a44d-48cb6af320f5",
              "leftValue": "={{ $('arrayAllEpisodes').first().json.data }}",
              "rightValue": "={{ 'tt'+$json.imdb_id+'_'+ $json.episode}}",
              "operator": {
                "type": "array",
                "operation": "notContains",
                "rightType": "any"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        3088,
        -376
      ],
      "id": "4eb5b0f6-b067-4bce-9c3c-f2f8225b5e7e",
      "name": "removeEpisodesDownloaded",
      "executeOnce": false
    },
    {
      "parameters": {
        "jsCode": "const bestByEpisode = {};\n\nfor (const item of items) {\n  const { episode, seeds } = item.json;\n\n  // If there's no record for this episode yet,\n  // or if this torrent has more seeds than the recorded ones,\n  // then we save it as \"the best\" so far.\n  if (!bestByEpisode[episode] || seeds > bestByEpisode[episode].seeds) {\n    bestByEpisode[episode] = item.json;   // guarda todo o objeto\n  }\n}\n\nconst resultItems = Object.values(bestByEpisode).map(torrent => ({ json: torrent }));\n\nreturn resultItems;\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3312,
        -376
      ],
      "id": "d865b8ef-7e3e-410c-8be3-0bfe2aa6b859",
      "name": "better-seeds-filter",
      "executeOnce": false
    },
    {
      "parameters": {
        "jsCode": "const env = {\n  biglybt: {\n    url: \"http://192.168.1.82:9093/transmission/rpc\",\n    paused: true,\n  },\n  eztvx: {\n    api_endpoint: \"https://eztvx.to/api/get-torrents\",\n    quality: [\"1080p\",\"2160p\"],\n  },\n  tvmaze:{\n    api_endpoint: \"https://api.tvmaze.com/lookup/shows\"\n  },\n  kodi: {\n    api_endpoint: \"http://192.168.1.82:8080/jsonrpc\"\n  }\n};\n\nreturn env;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -496,
        -376
      ],
      "id": "c9b1ff99-d7be-4dbb-a8b5-bb59491e6742",
      "name": "parameters"
    },
    {
      "parameters": {
        "mode": "chooseBranch"
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        848,
        -448
      ],
      "id": "4c4f319a-2dea-4a22-ac3c-a2170b948041",
      "name": "Merge"
    },
    {
      "parameters": {
        "mode": "chooseBranch"
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        2864,
        -448
      ],
      "id": "1dfa94d6-d713-4020-bfe9-706dafad2705",
      "name": "Merge1"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "092f5fb2-6046-4cfe-ac26-6cd5f717d7b9",
              "leftValue": "={{ $json.episode }}",
              "rightValue": "=0",
              "operator": {
                "type": "dateTime",
                "operation": "notEquals"
              }
            },
            {
              "id": "69af5142-eefe-4e1a-bc15-20dbbb3ce678",
              "leftValue": "={{ $('parameters').first().json.eztvx.quality }}",
              "rightValue": "={{ $json.quality }}",
              "operator": {
                "type": "array",
                "operation": "contains",
                "rightType": "any"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        2416,
        -496
      ],
      "id": "eeaa674b-0422-4771-950d-8886f9abdcbe",
      "name": "Filter Season Pack and Quality"
    },
    {
      "parameters": {
        "jsCode": "/**\n *  n8n Function node \u2013 merge torrents with episode filePath\n *\n *  Input nodes:\n *      - better-seeds-filter   \u2192 list of torrents\n *      - getTVShowEpisodes1    \u2192 list of episodes\n *\n *  Output: same format as the torrents input, but each object will contain a\n *          filePath property when a matching episode is found.\n */\n\n/* ---------- 1. Load data from the two input nodes --------------------- */\nconst torrentsNode   = $(\"better-seeds-filter\").all();  // array of items\nconst episodesNode   = $(\"getTVShowEpisodes1\").all();   // array of items\n\nfor (const torrent of torrentsNode) {\n  for (const episode of episodesNode) {\n    if (episode.json.imdb == torrent.json.imdb_id) {\n      \n    }\n  }\n}\n\n// Extract only the JSON payload from each item\nconst torrents = torrentsNode.map(item => item.json);\nconst episodes = episodesNode.map(item => item.json);\n\n/* ---------- 2. Build a lookup table: imdbId \u2192 filePath ------------ */\nconst imdbMap = episodes.reduce((acc, ep) => {\n  // ep.imdb is like \"tt1234567\" \u2192 strip the leading 'tt'\n  const id = ep.imdb.replace(/^tt/, '');\n  if (id) acc[id] = ep.filePath;\n  return acc;\n}, {});\n\n/* ---------- 3. Merge the filePath into each torrent ----------------- */\nconst merged = torrents.map(torrent => {\n  const path = imdbMap[torrent.imdb_id];          // may be undefined\n  if (path) {\n    return { json: { ...torrent, filePath: path } };\n  }\n  // if no match, return the original torrent unchanged\n  return { json: torrent };\n});\n\n/* ---------- 4. Return the result ----------------------------------- */\nreturn merged;\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3760,
        -376
      ],
      "id": "5267f4e7-bbc0-4307-ba98-5724c0a2a1e3",
      "name": "Add Save File Path",
      "executeOnce": false
    },
    {
      "parameters": {
        "cmdline": "= -I {{ $('parameters').first().json.biglybt.url }}"
      },
      "type": "@n-octo-n/n8n-nodes-curl.cURL",
      "typeVersion": 1,
      "position": [
        3536,
        -376
      ],
      "id": "df935974-a91c-4701-9e81-847fe687dab9",
      "name": "Get-Transmission-Session-Id"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('parameters').first().json.biglybt.url }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Transmission-Session-Id",
              "value": "={{ $('Get-Transmission-Session-Id').item.json.headers['x-transmission-session-id'] }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"method\": \"torrent-add\",\n  \"arguments\": {\n    \"paused\": {{ $('parameters').first().json.biglybt.paused }},\n    \"download-dir\": \"{{ $json.filePath }}\",\n    \"filename\": \"{{ $json.torrent_url }}\"\n  }\n} ",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3984,
        -376
      ],
      "id": "80d0c00f-b4b4-405e-8271-f7dee2db46ea",
      "name": "add-torrent",
      "alwaysOutputData": false,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "jsCode": "// Para cada item no input\nfor (const item of $input.all()) {\n  // Extrai a qualidade do t\u00edtulo usando express\u00e3o regular\n  const qualityMatch = item.json.title.match(/(480p|720p|1080p|2160p)/i);\n  \n  // Se encontrou uma correspond\u00eancia, adiciona ao item\n  if (qualityMatch && qualityMatch[0]) {\n    item.json.quality = qualityMatch[0].toLowerCase(); // Padroniza para min\u00fasculas\n  } else {\n    // Se n\u00e3o encontrar, define como desconhecido ou vazio\n    item.json.quality = 'unknown';\n  }\n}\n\n// Retorna os itens processados\nreturn items;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2192,
        -496
      ],
      "id": "d16c8078-32b3-4cf6-8165-be05e2c954fc",
      "name": "setQuality"
    },
    {
      "parameters": {
        "content": "## n8n Workflow: Auto Episode Fetcher\n\n### Description\nThis workflow automatically finds and downloads missing episodes for your active TV shows. It queries your Kodi library to see what you have, checks TVMaze to see which shows are still \"Running,\" searches EZTVx for the best torrents of missing episodes, and adds them to your BiglyBT/Transmission client.\n\n### How It Works\n1.  **Kodi Library Scan:** Fetches your entire TV show and episode list from Kodi.\n2.  **Status Check:** Uses the TVMaze API to filter only shows with a \"Running\" status.\n3.  **Torrent Search:** For each active show, it searches EZTVx for available torrents.\n4.  **Smart Filtering:** Removes episodes you already have, filters by desired quality (e.g., 1080p), and selects the torrent with the most seeds.\n5.  **Download:** Sends the best torrent for each missing episode to your torrent client.\n\n### Important Notes\n*   This workflow is designed for individual episodes, not season packs.\n*   Ensure your torrent client (BiglyBT/Transmission) has its RPC interface enabled and accessible.\n*   Ensure Kodi has \"Allow control via HTTP\" and \"Allow remote control via HTTP\" enabled in its settings.\n*   The workflow filters out episodes that are already present in your Kodi library.",
        "height": 544,
        "width": 1008,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -960,
        -1040
      ],
      "typeVersion": 1,
      "id": "8ad2e95a-fcf7-4229-89f3-6f1af54d8ae6",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "### Configuration (Parameters Node)\nBefore running, you **must** adjust the `parameters` node to match your environment:\n**Key Adjustments:**\n*   `biglybt.url`: Update the IP address, port, and path to match your torrent client's RPC endpoint.\n*   `kodi.api_endpoint`: Update the IP address and port to match your Kodi server.\n*   `eztvx.quality`: Modify the array to include your preferred video qualities (e.g., `[\"720p\"]`).\n",
        "height": 256,
        "width": 400
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -624,
        -224
      ],
      "typeVersion": 1,
      "id": "c5021a8b-76c5-48e2-89f2-978ff33e1521",
      "name": "Sticky Note1"
    }
  ],
  "connections": {
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GetKodiTVShowCollection": {
      "main": [
        [
          {
            "node": "getKodiTvShows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "getKodiTvShows": {
      "main": [
        [
          {
            "node": "GetKodiSeasons",
            "type": "main",
            "index": 0
          },
          {
            "node": "GetStatusTVShow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GetStatusTVShow": {
      "main": [
        [
          {
            "node": "GetStatusTVShow1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "getTVShowEpisodes": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GetKodiSeasons": {
      "main": [
        [
          {
            "node": "GetKodiSeasons1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GetKodiSeasons1": {
      "main": [
        [
          {
            "node": "getTVShowEpisodes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GetStatusTVShow1": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          },
          {
            "node": "getTVShowEpisodes1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "eztvx api": {
      "main": [
        [
          {
            "node": "Merge-Pages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "getTVShowEpisodes1": {
      "main": [
        [
          {
            "node": "getTVShowEpisodesRunning",
            "type": "main",
            "index": 0
          },
          {
            "node": "arrayAllEpisodes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "getTVShowEpisodesRunning": {
      "main": [
        [
          {
            "node": "uniqueTVShows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "uniqueTVShows": {
      "main": [
        [
          {
            "node": "eztvx api",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge-Pages": {
      "main": [
        [
          {
            "node": "setQuality",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "clean fields": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "arrayAllEpisodes": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 1
          },
          {
            "node": "removeEpisodesDownloaded",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "removeEpisodesDownloaded": {
      "main": [
        [
          {
            "node": "better-seeds-filter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "parameters": {
      "main": [
        [
          {
            "node": "GetKodiTVShowCollection",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "getTVShowEpisodes1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge1": {
      "main": [
        [
          {
            "node": "removeEpisodesDownloaded",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Season Pack and Quality": {
      "main": [
        [
          {
            "node": "clean fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "better-seeds-filter": {
      "main": [
        [
          {
            "node": "Get-Transmission-Session-Id",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get-Transmission-Session-Id": {
      "main": [
        [
          {
            "node": "Add Save File Path",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add Save File Path": {
      "main": [
        [
          {
            "node": "add-torrent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "setQuality": {
      "main": [
        [
          {
            "node": "Filter Season Pack and Quality",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "d8e6c8f3-e989-4b47-9e43-ee09477201af",
  "id": "Rs3f79aNYikpTEDw",
  "tags": []
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Kodi-Torrent-Episode-Fetcher. Uses httpRequest, @n-octo-n/n8n-nodes-curl. Event-driven trigger; 27 nodes.

Source: https://gist.github.com/harrypython/3ee43c39f82f82725c247c0d30bc5470 — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

This workflow allows you to import any workflow from a file or another n8n instance and map the credentials easily. A multi-form setup guides you through the entire process At the beginning you have t

Execute Command, Read Write File, HTTP Request +3
Web Scraping

[n8n] Advanced URL Parsing and Shortening Workflow - Switchy.io Integration. Uses splitInBatches, stickyNote, httpRequest, html. Event-driven trigger; 56 nodes.

HTTP Request, GitHub, Stop And Error +1
Web Scraping

[](https://youtu.be/c7yCZhmMjtI)

HTTP Request, GitHub, Stop And Error +1
Web Scraping

This automation organizes your n8n workflows files into categorizes (Active, Template, Done, Archived) and uploads them directly to a categorized Google Drive folders. It is designed to help users man

Google Drive, HTTP Request, Time Saved
Web Scraping

Create Animated Stories using GPT-4o-mini, Midjourney, Kling and Creatomate API. Uses httpRequest. Event-driven trigger; 51 nodes.

HTTP Request