AutomationFlowsAI & RAG › Youtube to Telegram Summary Bot with Decodo & Gemini AI

Youtube to Telegram Summary Bot with Decodo & Gemini AI

ByKhairul Muhtadin @khmuhtadin on n8n.io

Stop wasting hours watching long videos. This n8n workflow acts as your personal "TL;DW" (Too Long; Didn't Watch) assistant. It automatically pulls YouTube transcripts using Decodo, analyzes them with Google Gemini, and sends a detailed summary straight to your Telegram.

Event trigger★★★★☆ complexityAI-powered26 nodesHTTP RequestTelegramTelegram TriggerChain LlmError TriggerGoogle Gemini Chat
AI & RAG Trigger: Event Nodes: 26 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #11145 — we link there as the canonical source.

This workflow follows the Chainllm → HTTP Request recipe pattern — see all workflows that pair these two integrations.

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
{
  "nodes": [
    {
      "id": "c6a91e50-fa97-4c99-b630-2ade0b370785",
      "name": "Decodo Youtube Transcript Scrapper",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1712,
        272
      ],
      "parameters": {
        "url": "https://scraper-api.decodo.com/v2/scrape",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "target",
              "value": "youtube_transcript"
            },
            {
              "name": "query",
              "value": "={{ $json.videoId }}"
            },
            {
              "name": "language_code",
              "value": "={{ $json.languageCode }}"
            }
          ]
        },
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "8aae3665-3aa7-44a2-9336-40b35e34af83",
      "name": "Extract Transcript",
      "type": "n8n-nodes-base.code",
      "position": [
        1936,
        272
      ],
      "parameters": {
        "jsCode": "const texts = [];\n\n// Ambil data dari input pertama\nconst inputData = $input.first().json;\n\n// Cek struktur data\nif (inputData && inputData.results && inputData.results[0]) {\n  const content = inputData.results[0].content;\n  \n  // Loop content\n  for (const contentItem of content) {\n    if (contentItem.transcriptSegmentRenderer) {\n      const runs = contentItem.transcriptSegmentRenderer.snippet.runs;\n      for (const run of runs) {\n        texts.push(run.text);\n      }\n    }\n  }\n}\n\nreturn {\n  transcript: texts.join(' '),\n  textArray: texts,\n  totalSegments: texts.length\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "926e6c2c-66bd-4666-b757-58232118d181",
      "name": "Decodo Youtube Metadata Scrapper",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1712,
        496
      ],
      "parameters": {
        "url": "https://scraper-api.decodo.com/v2/scrape",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "target",
              "value": "youtube_metadata"
            },
            {
              "name": "query",
              "value": "={{ $json.videoId }}"
            }
          ]
        },
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "0cb552e1-ff95-44b9-9cd6-b9f580c113ed",
      "name": "Parse Metadata",
      "type": "n8n-nodes-base.code",
      "position": [
        2224,
        496
      ],
      "parameters": {
        "jsCode": "// Code Node: Parse YouTube Metadata\nconst inputData = $input.first().json;\n\n// Helper function: Format duration (seconds to mm:ss or hh:mm:ss)\nfunction formatDuration(seconds) {\n  const hours = Math.floor(seconds / 3600);\n  const minutes = Math.floor((seconds % 3600) / 60);\n  const secs = seconds % 60;\n  \n  if (hours > 0) {\n    return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\n  }\n  return `${minutes}:${secs.toString().padStart(2, '0')}`;\n}\n\n// Helper function: Format number with commas\nfunction formatNumber(num) {\n  return num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\n}\n\n// Helper function: Format date (YYYYMMDD to readable format)\nfunction formatDate(dateStr) {\n  if (!dateStr) return null;\n  const year = dateStr.substring(0, 4);\n  const month = dateStr.substring(4, 6);\n  const day = dateStr.substring(6, 8);\n  return `${year}-${month}-${day}`;\n}\n\n// Extract metadata\nlet metadata = {};\n\nif (inputData && inputData.results && inputData.results[0]) {\n  const content = inputData.results[0].content.results;\n  \n  // Basic Info\n  metadata.videoId = content.video_id || null;\n  metadata.title = content.title || null;\n  metadata.channel = content.uploader || null;\n  metadata.channelId = content.channel_id || null;\n  metadata.channelUrl = content.channel_url || null;\n  \n  // Duration & Date\n  metadata.duration = content.duration || 0;\n  metadata.durationFormatted = content.duration ? formatDuration(content.duration) : null;\n  metadata.uploadDate = formatDate(content.upload_date);\n  metadata.uploadDateRaw = content.upload_date || null;\n  \n  // Engagement Stats\n  metadata.viewCount = content.view_count || 0;\n  metadata.viewCountFormatted = content.view_count ? formatNumber(content.view_count) : \"0\";\n  metadata.likeCount = content.like_count || 0;\n  metadata.likeCountFormatted = content.like_count ? formatNumber(content.like_count) : \"0\";\n  metadata.commentCount = content.comment_count || 0;\n  metadata.commentCountFormatted = content.comment_count ? formatNumber(content.comment_count) : \"0\";\n  \n  // Content Context\n  metadata.tags = content.tags || [];\n  metadata.categories = content.categories || [];\n  metadata.category = content.categories && content.categories.length > 0 ? content.categories[0] : null;\n  \n  // Chapters (filter out \"Ad\")\n  metadata.allChapters = content.chapters || [];\n  metadata.chapters = (content.chapters || [])\n    .filter(chapter => chapter.title.toLowerCase() !== 'ad')\n    .map(chapter => ({\n      title: chapter.title,\n      startTime: chapter.start_time,\n      startTimeFormatted: formatDuration(chapter.start_time)\n    }));\n  \n  metadata.chapterTitles = metadata.chapters.map(ch => ch.title);\n  \n  // Description\n  metadata.description = content.description || null;\n  metadata.descriptionShort = content.description \n    ? content.description.substring(0, 200) + '...' \n    : null;\n  \n  // Thumbnail (highest quality)\n  metadata.thumbnails = content.thumbnails || [];\n  metadata.thumbnail = content.thumbnails && content.thumbnails.length > 0\n    ? content.thumbnails[content.thumbnails.length - 1].url\n    : null;\n  \n  // Additional Info\n  metadata.isLive = content.is_live || false;\n  metadata.ageLimit = content.age_limit || 0;\n  metadata.isTranscriptAvailable = content.is_transcript_available || false;\n  \n  // Video URL\n  metadata.videoUrl = `https://www.youtube.com/watch?v=${content.video_id}`;\n}\n\n// Return parsed metadata\nreturn {\n  videoId: metadata.videoId,\n  title: metadata.title,\n  channel: metadata.channel,\n  channelUrl: metadata.channelUrl,\n  duration: metadata.durationFormatted,\n  durationSeconds: metadata.duration,\n  uploadDate: metadata.uploadDate,\n  views: metadata.viewCountFormatted,\n  viewsRaw: metadata.viewCount,\n  likes: metadata.likeCountFormatted,\n  likesRaw: metadata.likeCount,\n  comments: metadata.commentCountFormatted,\n  commentsRaw: metadata.commentCount,\n  tags: metadata.tags,\n  category: metadata.category,\n  chapters: metadata.chapters,\n  chapterTitles: metadata.chapterTitles,\n  description: metadata.description,\n  descriptionShort: metadata.descriptionShort,\n  thumbnail: metadata.thumbnail,\n  videoUrl: metadata.videoUrl,\n  isLive: metadata.isLive,\n  isTranscriptAvailable: metadata.isTranscriptAvailable,\n  \n  // Formatted summary for AI context\n  summaryForAI: {\n    basic: `\"${metadata.title}\" by ${metadata.channel}`,\n    stats: `${metadata.viewCountFormatted} views \u2022 ${metadata.likeCountFormatted} likes \u2022 ${metadata.durationFormatted}`,\n    content: `Category: ${metadata.category} | Tags: ${metadata.tags.slice(0, 5).join(', ')}`,\n    structure: `Chapters: ${metadata.chapterTitles.join(' \u2192 ')}`\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "c6597425-57f9-4145-9850-06971302d5d6",
      "name": "Filter: YouTube URL?",
      "type": "n8n-nodes-base.if",
      "position": [
        1040,
        480
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "d4907238-4696-42dc-bf38-5d70f331deed",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "leftValue": "={{ $json.message.text }}",
              "rightValue": "youtube."
            },
            {
              "id": "f4014e6e-75a9-4247-bae9-2031b892c4f4",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "leftValue": "={{ $json.message.text }}",
              "rightValue": "youtu.be"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d06863bd-55d9-4b5f-b019-82494d8dd625",
      "name": "Send \"Invalid URL\"",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1264,
        576
      ],
      "parameters": {
        "text": "not a youtube url",
        "chatId": "={{ $('On New Message').item.json.message.chat.id }}",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "2a6c36f4-b200-4de3-afad-78f2b8773743",
      "name": "Send \"Processing...\"",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1264,
        384
      ],
      "parameters": {
        "text": "<i>Processing your input...</i>",
        "chatId": "={{ $('On New Message').item.json.message.chat.id }}",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "34d42b11-c468-4f47-b5fe-6d6a9b26a74c",
      "name": "On New Message",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        816,
        480
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "ed900824-c2ea-4e9d-989f-4b60ec63876c",
      "name": "Set: Video ID & Config",
      "type": "n8n-nodes-base.set",
      "position": [
        1488,
        384
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "7edca446-a550-4ee5-8024-a099637ea517",
              "name": "videoId",
              "type": "string",
              "value": "={{ $('On New Message').item.json.message.text.match(/youtu\\.be\\/([^?]+)/)[1]  }}"
            },
            {
              "id": "1c222d04-c342-458d-8079-eda8d63f2fcf",
              "name": "languageCode",
              "type": "string",
              "value": "en"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "01adb94f-daae-45c2-b92a-91f0c5044bb1",
      "name": "Generate TLDR",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        2160,
        272
      ],
      "parameters": {
        "text": "={{ $json.transcript }}",
        "batching": {},
        "messages": {
          "messageValues": [
            {
              "message": "You are a professional content summarizer. Create a comprehensive TLDR (Too Long; Didn't Read) summary of this YouTube video.\n\n1. ONE-LINE SUMMARY (max 150 characters)\nA punchy one-liner that captures the essence of the video.\n\n2. KEY POINTS (3-5 bullet points)\nThe main takeaways from the video. Each point should be:\n- Clear and specific\n- Action-oriented when possible\n- Include important details (names, tools, concepts)\n\n3. MAIN TOPICS COVERED\nBreak down the video into major sections based on the chapters. For each section:\n- Title\n- Brief description (1-2 sentences)\n- Key information\n\n4. TOOLS/RESOURCES MENTIONED\nList all tools, software, websites, or resources mentioned with brief descriptions.\n\n5. TARGET AUDIENCE\nWho would benefit most from watching this video?\n\n6. PRACTICAL TAKEAWAYS\nWhat can viewers do after watching? (actionable steps)\n\n7. NOTABLE QUOTES (optional)\n1-2 memorable or impactful quotes from the video."
            }
          ]
        },
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "9f6b145c-f736-4bbc-ba86-e4a97912a531",
      "name": "Merge: Data + Summary",
      "type": "n8n-nodes-base.merge",
      "position": [
        2512,
        384
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineAll"
      },
      "typeVersion": 3.2
    },
    {
      "id": "41ad2d7b-0401-410b-95d5-b9c2a1216a2a",
      "name": "Alert Admin",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1216,
        864
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "chatId": "YOUR_CHAT_ID",
        "additionalFields": {
          "parse_mode": "HTML"
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "4046c68d-44e1-4189-8ae9-9ffed8132381",
      "name": "Format Error Log",
      "type": "n8n-nodes-base.code",
      "position": [
        1040,
        864
      ],
      "parameters": {
        "jsCode": "// Optimized error handler - clean & informative\nconst errorData = $input.first().json;\n\nfunction getNestedValue(obj, path, defaultValue = 'Unknown') {\n  return path.split('.').reduce((curr, prop) => \n    curr?.[prop], obj) || defaultValue;\n}\n\nconst workflowName = getNestedValue(errorData, 'workflow.name', 'Unknown Workflow');\nconst nodeName = getNestedValue(errorData, 'execution.error.node.name', 'Unknown Node');\nconst errorMessage = getNestedValue(errorData, 'execution.error.message', 'No error message');\nconst timestamp = getNestedValue(errorData, 'execution.startedAt', new Date().toISOString());\nconst executionId = getNestedValue(errorData, 'execution.id', 'Unknown');\n\n// Tangkap error details\nconst errorObj = errorData.execution?.error || {};\nconst errorDescription = errorObj.description || '';\nconst errorHttpCode = errorObj.httpCode || '';\n\n// Stack trace - ambil HANYA baris pertama yang paling penting\nconst errorStack = errorObj.stack || '';\nconst mainError = errorStack.split('\\n')[0] || '';\n\n// Format detail yang clean\nlet errorDetails = [];\nif (errorDescription && errorDescription !== errorMessage) {\n  errorDetails.push(errorDescription);\n}\nif (errorHttpCode) {\n  errorDetails.push(`HTTP ${errorHttpCode}`);\n}\n\nconst detailText = errorDetails.length > 0 \n  ? errorDetails.join(' | ') \n  : 'No additional details';\n\n// Main error dari stack (tanpa \"NodeApiError: \" prefix)\nconst cleanMainError = mainError.replace(/^(NodeApiError|Error):\\s*/, '').trim();\n\n// Build message - COMPACT VERSION\nconst message = `\ud83d\udea8 <b>WORKFLOW ERROR</b>\\n\\n` +\n  `\ud83d\udccb <b>${workflowName}</b>\\n` +\n  `\u2699\ufe0f Node: <code>${nodeName}</code>\\n\\n` +\n  `\u274c <b>${errorMessage}</b>\\n` +\n  `\ud83d\udca1 ${detailText}\\n\\n` +\n  `\ud83d\udd50 ${new Date(timestamp).toLocaleString('id-ID', { \n    day: '2-digit', \n    month: 'short', \n    hour: '2-digit', \n    minute: '2-digit' \n  })}\\n` +\n  `\ud83d\udd17 Exec: <code>${executionId}</code>`;\n\nreturn {\n  json: {\n    message: message\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "a43fe0fb-7a2b-45c8-9c2c-633d9af0e486",
      "name": "On Error",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        848,
        864
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "c0f1efbf-3152-4481-b5aa-c3921775a6f2",
      "name": "Split & Format HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        2736,
        480
      ],
      "parameters": {
        "jsCode": "// Code Node: Split Long TLDR into Telegram Messages (HTML Format)\n\nconst inputData = $input.first().json;\nconst fullText = inputData.text;\nconst metadata = inputData;\n\nconst maxCharsPerChunk = 4000;\n\n// Helper: Escape HTML special characters\nfunction escapeHtml(text) {\n  return text\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;');\n}\n\n// Helper: Convert Markdown-style formatting to HTML\nfunction convertMarkdownToHtml(text) {\n  let html = escapeHtml(text);\n  \n  // Bold: **text** atau *text* (hanya yang standalone)\n  html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<b>$1</b>');\n  html = html.replace(/\\*([^\\*\\n]+?)\\*/g, '<b>$1</b>');\n  \n  // Italic: _text_\n  html = html.replace(/_([^_\\n]+?)_/g, '<i>$1</i>');\n  \n  // Code: `code`\n  html = html.replace(/`([^`]+?)`/g, '<code>$1</code>');\n  \n  // Links: [text](url)\n  html = html.replace(/\\[([^\\]]+)\\]\\(([^\\)]+)\\)/g, '<a href=\"$2\">$1</a>');\n  \n  // Headers (convert to bold)\n  html = html.replace(/^#+\\s+(.+)$/gm, '<b>$1</b>');\n  \n  // Bullet points: * item or - item\n  html = html.replace(/^[\\*\\-]\\s+(.+)$/gm, '  \u2022 $1');\n  \n  // Numbered lists: 1. item\n  html = html.replace(/^\\d+\\.\\s+(.+)$/gm, (match, p1, offset, string) => {\n    // Ambil nomor dari beginning of match\n    const num = match.match(/^(\\d+)\\./)[1];\n    return `  ${num}. ${p1}`;\n  });\n  \n  return html;\n}\n\n// Helper: Add extra spacing after bullet points\nfunction formatTextWithBulletSpacing(text) {\n  return text\n    .replace(/(\\*\\s+[^\\n]+)\\n(?!\\n)/g, '$1\\n\\n')\n    .replace(/(\\d+\\.\\s+[^\\n]+)\\n(?!\\n)/g, '$1\\n\\n')\n    .replace(/(-\\s+[^\\n]+)\\n(?!\\n)/g, '$1\\n\\n')\n    .replace(/\\n{3,}/g, '\\n\\n');\n}\n\n// Helper: Split text intelligently\nfunction splitTextIntoChunks(text, maxLength) {\n  const chunks = [];\n  const formattedText = formatTextWithBulletSpacing(text).trim();\n  const paragraphs = formattedText.split('\\n\\n');\n  \n  let currentChunk = '';\n  \n  for (const paragraph of paragraphs) {\n    const trimmedParagraph = paragraph.trim();\n    if (!trimmedParagraph) continue;\n    \n    // Convert to HTML sebelum test length\n    const htmlParagraph = convertMarkdownToHtml(trimmedParagraph);\n    \n    const testChunk = currentChunk \n      ? currentChunk + '\\n\\n' + htmlParagraph \n      : htmlParagraph;\n    \n    if (testChunk.length > maxLength) {\n      if (currentChunk) {\n        chunks.push(currentChunk);\n      }\n      \n      if (htmlParagraph.length > maxLength) {\n        const lines = trimmedParagraph.split('\\n');\n        let tempChunk = '';\n        \n        for (const line of lines) {\n          const trimmedLine = line.trim();\n          if (!trimmedLine) continue;\n          \n          const htmlLine = convertMarkdownToHtml(trimmedLine);\n          const testLine = tempChunk \n            ? tempChunk + '\\n' + htmlLine \n            : htmlLine;\n          \n          if (testLine.length > maxLength) {\n            if (tempChunk) chunks.push(tempChunk);\n            tempChunk = htmlLine;\n          } else {\n            tempChunk = testLine;\n          }\n        }\n        \n        if (tempChunk) {\n          currentChunk = tempChunk;\n        }\n      } else {\n        currentChunk = htmlParagraph;\n      }\n    } else {\n      currentChunk = testChunk;\n    }\n  }\n  \n  if (currentChunk) {\n    chunks.push(currentChunk);\n  }\n  \n  return chunks;\n}\n\n// Split text\nconst textChunks = splitTextIntoChunks(fullText, maxCharsPerChunk);\n\n// Create output items\nconst outputItems = textChunks.map((chunk, index) => {\n  const isFirst = index === 0;\n  const isLast = index === textChunks.length - 1;\n  \n  let messageText = '';\n  \n  // Header dengan HTML formatting\n  if (isFirst) {\n    messageText = `\ud83d\udcfa <b>TLDR Summary</b>\\n\\n` +\n                  `<b>${escapeHtml(metadata.title)}</b>\\n\\n` +\n                  `\ud83d\udc64 ${escapeHtml(metadata.channel)}\\n` +\n                  `\u23f1\ufe0f ${metadata.duration} | \ud83d\udc41\ufe0f ${metadata.views} views\\n\\n` +\n                  `\ud83d\udcc4 Part ${index + 1}/${textChunks.length}\\n` +\n                  `${'\u2500'.repeat(40)}\\n\\n`;\n  } else {\n    messageText = `\ud83d\udcc4 Part ${index + 1}/${textChunks.length}\\n` +\n                  `${'\u2500'.repeat(40)}\\n\\n`;\n  }\n  \n  // Add content (sudah dalam format HTML)\n  messageText += chunk;\n  \n  // Footer\n  if (isLast) {\n    messageText += `\\n\\n${'\u2500'.repeat(40)}\\n\\n` +\n                   `\u2705 End of Summary\\n\\n` +\n                   `\ud83d\udd17 <a href=\"${metadata.videoUrl}\">Watch Video</a>`;\n  }\n  \n  return {\n    json: {\n      messageNumber: index + 1,\n      totalMessages: textChunks.length,\n      text: messageText,\n      charCount: messageText.length,\n      chatId: $('On New Message').item.json.message.chat.id,\n      parseMode: 'HTML', // Tambahkan ini\n      \n      videoId: metadata.videoId,\n      title: metadata.title,\n      videoUrl: metadata.videoUrl\n    }\n  };\n});\n\nreturn outputItems;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "12d5526f-2aa9-4caf-9200-9959e213d42a",
      "name": "Loop: Message Batches",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        2960,
        480
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "8acd0552-7966-409f-b31f-2ad88e5373e7",
      "name": "Send Summary Part",
      "type": "n8n-nodes-base.telegram",
      "position": [
        3216,
        496
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "chatId": "={{ $('On New Message').item.json.message.chat.id }}",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "9e7870e2-8d31-4950-8f53-1a93218350b1",
      "name": "Wait: Rate Limiter",
      "type": "n8n-nodes-base.wait",
      "position": [
        3408,
        496
      ],
      "parameters": {
        "amount": 1
      },
      "typeVersion": 1.1
    },
    {
      "id": "a61550ee-68a6-4b55-aed8-5a1c118d3922",
      "name": "Send Video Info",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2736,
        288
      ],
      "parameters": {
        "text": "=\ud83d\udcfa Youtube Video Analyze of **{{ $json.title }}** by **{{ $json.channel }}**\n\n\u23f1\ufe0f Video Duration: {{ $json.duration }}\n{{ $json.summaryForAI.content }}\nStats: {{ $json.summaryForAI.stats }}\n\nChapters (if applicable)\n{{ $json.summaryForAI.structure }}",
        "chatId": "={{ $('On New Message').item.json.message.chat.id }}",
        "additionalFields": {
          "parse_mode": "Markdown",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "e083caee-4c26-4d3a-9a93-86cf9b35aadc",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        208
      ],
      "parameters": {
        "width": 672,
        "height": 672,
        "content": "# YouTube Summary Generator via Telegram\n\nThis workflow acts as your personal \"TL;DV\" (Too Long; Didn't View) assistant directly inside Telegram. Just drop a YouTube link to your bot, and it instantly gets to work turning a long watch into a quick read.\n\n### How it works\nOnce the bot detects a valid YouTube URL, it sends a quick \"Processing...\" note so you know it\u2019s working. Behind the scenes, it uses the **Decodo API** to grab the full transcript and video details (like view counts and chapters). It then feeds that raw text into **Google\u2019s Gemini Flash model**. The AI analyzes the content and structures a smart summary\u2014pulling out key takeaways, main topics, and specific tools mentioned. Finally, the workflow formats this info into clean, readable chunks and texts them back to you.\n\n### What you\u2019ll need to set it up\nTo get this running, you just need three specific credentials:\n* **Telegram API:** For your bot to receive links and send messages.\n* **Decodo Scraper API:** To fetch the video data and transcripts.\n* **Google Gemini Chat Model:** To power the AI summarization.\n\n### Make it your own\nYou can easily customize this tool! Change the `languageCode` setting to get summaries in other languages (like `id` for Indonesian), tweak the AI prompt to change how the summary looks, or set the admin alert to ping your personal ID if anything goes wrong. It\u2019s a powerful way to digest content without the watch time."
      },
      "typeVersion": 1
    },
    {
      "id": "fee6ec95-0788-434e-9a09-5027a81fcb49",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        784,
        736
      ],
      "parameters": {
        "color": 3,
        "width": 640,
        "height": 288,
        "content": "## Error Handling\n\nCatches workflow failures, formats error details, sends Telegram alerts to admin."
      },
      "typeVersion": 1
    },
    {
      "id": "db9892f8-7324-42b6-9f36-ecf8830d262c",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1440,
        736
      ],
      "parameters": {
        "color": 6,
        "width": 528,
        "height": 288,
        "content": "## Create Decodo Credentials\nService: Decodo Maps Scrapper\nNode: HTTP Request\nURL: https://scraper-api.decodo.com/v2/scrape\nCredential Type: HTTP Header Auth\nHeader Name: Authorization\nHeader Value: Basic [YOUR_DECODO_API_KEY]\n\nGet API key: https://dashboard.decodo.com/web-scraping-api/scraper?target=google_maps\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "aafb217d-11f0-47ae-8db4-880bc38b870f",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1440,
        208
      ],
      "parameters": {
        "color": 5,
        "width": 1216,
        "height": 512,
        "content": "## Extract and Process Video"
      },
      "typeVersion": 1
    },
    {
      "id": "0c438165-4d92-472c-a020-1208859ffaef",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2672,
        208
      ],
      "parameters": {
        "color": 5,
        "width": 944,
        "height": 512,
        "content": "## Send Result to Telegram"
      },
      "typeVersion": 1
    },
    {
      "id": "b3293925-ab2a-404e-9442-e43d7b5d500e",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        784,
        320
      ],
      "parameters": {
        "color": 5,
        "width": 640,
        "height": 400,
        "content": "## Input Validation"
      },
      "typeVersion": 1
    },
    {
      "id": "92636a99-c100-4705-a273-5c6e37c2c06a",
      "name": "Gemini 3.0",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1968,
        496
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-3-pro-preview"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "On Error": {
      "main": [
        [
          {
            "node": "Format Error Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini 3.0": {
      "ai_languageModel": [
        [
          {
            "node": "Generate TLDR",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Generate TLDR": {
      "main": [
        [
          {
            "node": "Merge: Data + Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On New Message": {
      "main": [
        [
          {
            "node": "Filter: YouTube URL?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Metadata": {
      "main": [
        [
          {
            "node": "Merge: Data + Summary",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Format Error Log": {
      "main": [
        [
          {
            "node": "Alert Admin",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Summary Part": {
      "main": [
        [
          {
            "node": "Wait: Rate Limiter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Transcript": {
      "main": [
        [
          {
            "node": "Generate TLDR",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait: Rate Limiter": {
      "main": [
        [
          {
            "node": "Loop: Message Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split & Format HTML": {
      "main": [
        [
          {
            "node": "Loop: Message Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter: YouTube URL?": {
      "main": [
        [
          {
            "node": "Send \"Processing...\"",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send \"Invalid URL\"",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send \"Processing...\"": {
      "main": [
        [
          {
            "node": "Set: Video ID & Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop: Message Batches": {
      "main": [
        [],
        [
          {
            "node": "Send Summary Part",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge: Data + Summary": {
      "main": [
        [
          {
            "node": "Send Video Info",
            "type": "main",
            "index": 0
          },
          {
            "node": "Split & Format HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set: Video ID & Config": {
      "main": [
        [
          {
            "node": "Decodo Youtube Transcript Scrapper",
            "type": "main",
            "index": 0
          },
          {
            "node": "Decodo Youtube Metadata Scrapper",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decodo Youtube Metadata Scrapper": {
      "main": [
        [
          {
            "node": "Parse Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decodo Youtube Transcript Scrapper": {
      "main": [
        [
          {
            "node": "Extract Transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

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

About this workflow

Stop wasting hours watching long videos. This n8n workflow acts as your personal "TL;DW" (Too Long; Didn't Watch) assistant. It automatically pulls YouTube transcripts using Decodo, analyzes them with Google Gemini, and sends a detailed summary straight to your Telegram.

Source: https://n8n.io/workflows/11145/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

This automated TLDW (Too Long; Didn't Watch) generator using Decodo's scraping API to extract complete video transcripts and metadata, then uses Google Gemini 3 to create intelligent summaries with ke

HTTP Request, Telegram, Telegram Trigger +3
AI & RAG

This workflow creates a multi-talented AI assistant named Simran that interacts with users via Telegram. It can handle text and voice messages, understand the user's intent, and perform various tasks.

MongoDB, Chain Llm, Google Gemini Chat +11
AI & RAG

A Telegram bot that converts natural-language work descriptions into detailed cost estimates using AI parsing, vector search, and the open-source DDC CWICR database with 55,000+ construction work item

HTTP Request, Telegram, Telegram Trigger +6
AI & RAG

This workflow turns any URL sent to a Telegram bot into ready-to-publish social posts: Trigger: Telegram message (checks if it contains a URL). Fetch & parse: Downloads the page and extracts readable

Telegram Trigger, HTTP Request, Google Sheets +5
AI & RAG

Auto-Generate Social Media Posts From Urls With Ai Telegram Multi-Platform Posting. Uses telegramTrigger, httpRequest, googleSheets, openAi. Event-driven trigger; 42 nodes.

Telegram Trigger, HTTP Request, Google Sheets +5