{
  "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": "92636a99-c100-4705-a273-5c6e37c2c06a",
      "name": "Gemini Flash",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1968,
        496
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "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
    }
  ],
  "connections": {
    "On Error": {
      "main": [
        [
          {
            "node": "Format Error Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini Flash": {
      "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
          }
        ]
      ]
    }
  }
}