{ "id": "4Tq5HZBdETVe7jEb", "meta": { "instanceId": "workflow-4b03b82a", "versionId": "1.0.0", "createdAt": "2025-09-29T07:07:47.576435", "updatedAt": "2025-09-29T07:07:47.576451", "owner": "n8n-user", "license": "MIT", "category": "automation", "status": "active", "priority": "high", "environment": "production" }, "name": "⚡AI-Powered YouTube Playlist & Video Summarization and Analysis v2", "tags": [ "automation", "n8n", "production-ready", "excellent", "optimized" ], "nodes": [ { "id": "trigger-ac330b23", "name": "Manual Trigger", "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [ 100, 100 ], "parameters": {} }, { "id": "505077d1-a2e4-4b0d-99d6-756940022c3d", "name": "Google Gemini Chat Model1", "type": "n8n-nodes-base.noOp", "position": [ -440, -40 ], "parameters": { "options": {}, "modelName": "models/gemini-2.0-pro-exp" }, "credentials": { "googlePalmApi": { "id": "2zwuT5znDglBrUCO", "name": "Google Gemini(PaLM) Api account" } }, "typeVersion": 1, "notes": "This lmChatGoogleGemini node performs automated tasks as part of the workflow." }, { "id": "5da369db-b801-4653-888d-0e6042620298", "name": "Handle Queries", "type": "n8n-nodes-base.noOp", "position": [ -160, -280 ], "parameters": { "text": "={{ $('Chat').item.json.chatInput }}", "options": { "systemMessage": "=You are an intelligent assistant that can respond to queries related to the content of a Youtube Playlist or a single Video.\n\n# YOUR TASK\nDecide if the user has provided the info required and reply accordingly. If there is no url in context you have to suggest the user to provide one. \n\n\n1. If the user provided a YouTube Playlist or Video URL: reply in structured markdown format to the user based on the formulated questions and context. Assume the user is here because they don't won't / have time to watch such videos so:\n- Use the tool called `chat_playlist_data`, which can analyze YouTube videos. Use this tool effectively to process video content and generate structured summaries.\n- Your answers needs to be exhaustive and minimise bullet points\n- Be verbose in your response\n\n2. 1. If the user provided a YouTube Playlist or Video URL:\n- Do not ask for more specific details - always try to summarize the videos with the data from the tool `chat_playlist_data`\n- Never reply \"already provided a detailed summary\" - always try to summarize again the videos with more info from the tool `chat_playlist_data` - even if you have already provided the data before (so repeat yourself).\n\n3. If the user HAS NOT provided a YouTube Playlist URL or a invaid URL: gently invite the user to provide the URL so you can process it. \n\n# Rules\n\n## YouTube Playlist URL Definition\n- A URL from www.youtube.com or youtube.com.\n- Contains the query parameter `list=` followed by a playlist ID.\n- Example: {{ $env.WEBHOOK_URL }} (where PLXXXXXX is the playlist ID).\n\n## YouTube Video URL Definition\n- A URL from www.youtube.com, youtube.com, or youtu.be.\n- For www.youtube.com or youtube.com, it contains the query parameter `v=` followed by a video ID.\n- For youtu.be, it follows the format {{ $env.WEBHOOK_URL }}\n- Examples:\n - {{ $env.WEBHOOK_URL }} (where VIDEO_ID is the video ID).\n - {{ $env.WEBHOOK_URL }}\n- Does NOT have a query parameter `list=`\n\n# Context\n{\n \"intent\": {{ $('Default Intent').item.json.output?.intent || \"NONE\" }},\n \"url\": {{ $('Default Intent').item.json.output?.url || \"\" }},\n \"id\": {{ $('Default Intent').item.json.output?.id || \"\" }},\n \"limit\": {{ $('Default Intent').item.json.output?.limit || 0 }},\n \"status\": {{ $('Default Intent').item.json.output?.status || 'PENDING' }}\n}" }, "promptType": "define" }, "typeVersion": 1.7, "notes": "This agent node performs automated tasks as part of the workflow." }, { "id": "866bf387-3482-4615-94d5-fd72d5db21da", "name": "Split Out", "type": "n8n-nodes-base.splitOut", "position": [ 1380, -200 ], "parameters": { "include": "selectedOtherFields", "options": {}, "fieldToSplitOut": "transcript", "fieldsToInclude": "youtubeId" }, "typeVersion": 1, "notes": "This splitOut node performs automated tasks as part of the workflow." }, { "id": "359404ce-4bc9-4e4d-9a26-22b9f9b176c9", "name": "Summarize & Analyze Transcript", "type": "n8n-nodes-base.noOp", "position": [ 660, 540 ], "parameters": { "text": "=Please analyze the given text and create a structured summary following these guidelines:\n\n1. Break down the content into main topics using Level 2 headers (##)\n2. Under each header:\n - List only the most essential concepts and key points\n - Use bullet points for clarity\n - Keep explanations concise\n - Preserve technical accuracy\n - Highlight key terms in bold\n3. Format requirements:\n - Use markdown formatting\n - Keep bullet points simple (no nesting)\n - Bold important terms using **term**\n - Use tables for comparisons\n - Include relevant technical details\n\nPlease provide a clear, structured summary that captures the core concepts while maintaining technical accuracy.\n\n**Make sure the summary is 300-400 max characters long.**\n\nInclude metadata such as video number, id, and title in the summary.\n\n**Here is the text**\n\nVideo number: {{ $json.video_number }}\nTitle: {{ $json.title }}\nYoutube ID: {{ $json.youtubeId }}\nTranscript:\n{{ $json.transcript_text }}", "promptType": "define" }, "typeVersion": 1.4, "notes": "This chainLlm node performs automated tasks as part of the workflow." }, { "id": "036765df-6da4-4430-bcea-af4066fb7c24", "name": "Concatenate", "type": "n8n-nodes-base.summarize", "position": [ 1700, -200 ], "parameters": { "options": {}, "fieldsToSplitBy": "youtubeId", "fieldsToSummarize": { "values": [ { "field": "transcript.text", "separateBy": " ", "aggregation": "concatenate" } ] } }, "typeVersion": 1, "notes": "This summarize node performs automated tasks as part of the workflow." }, { "id": "9152725c-15ca-41a0-8f98-108834e0c8be", "name": "Split Out1", "type": "n8n-nodes-base.splitOut", "position": [ 660, 40 ], "parameters": { "options": {}, "fieldToSplitOut": "videos" }, "typeVersion": 1, "notes": "This splitOut node performs automated tasks as part of the workflow." }, { "id": "fee0e045-614a-41f0-ac75-051dff773e77", "name": "Limit", "type": "n8n-nodes-base.limit", "position": [ 860, 40 ], "parameters": { "maxItems": "={{ $('Update Context Intent').item.json.output.limit }}" }, "typeVersion": 1, "notes": "This limit node performs automated tasks as part of the workflow." }, { "id": "a691c1c7-d8c4-4eab-861b-f7cfcbeb0fc8", "name": "Qdrant Vector Store", "type": "n8n-nodes-base.noOp", "position": [ 1680, 480 ], "parameters": { "mode": "insert", "options": {}, "qdrantCollection": { "__rl": true, "mode": "id", "value": "={{ $('Update Context Intent').first().json.output.id }}" } }, "credentials": { "qdrantApi": { "id": "mb8rw8tmUeP6aPJm", "name": "QdrantApi account" } }, "typeVersion": 1, "notes": "This vectorStoreQdrant node performs automated tasks as part of the workflow." }, { "id": "c3949e8f-0deb-4106-aaa5-e64403024243", "name": "Recursive Character Text Splitter", "type": "n8n-nodes-base.noOp", "position": [ 1800, 860 ], "parameters": { "options": {}, "chunkSize": 1200, "chunkOverlap": 200 }, "typeVersion": 1, "notes": "This textSplitterRecursiveCharacterTextSplitter node performs automated tasks as part of the workflow." }, { "id": "a3b23c6b-14c0-4805-8220-7c6166268276", "name": "Embeddings Google Gemini", "type": "n8n-nodes-base.noOp", "position": [ 1660, 700 ], "parameters": { "modelName": "models/text-embedding-004" }, "credentials": { "googlePalmApi": { "id": "2zwuT5znDglBrUCO", "name": "Google Gemini(PaLM) Api account" } }, "typeVersion": 1, "notes": "This embeddingsGoogleGemini node performs automated tasks as part of the workflow." }, { "id": "6f4ee00d-dcdc-4468-9713-115912c1e571", "name": "Google Gemini Chat Model2", "type": "n8n-nodes-base.noOp", "position": [ 640, 740 ], "parameters": { "options": {}, "modelName": "models/gemini-2.0-flash" }, "credentials": { "googlePalmApi": { "id": "2zwuT5znDglBrUCO", "name": "Google Gemini(PaLM) Api account" } }, "typeVersion": 1, "notes": "This lmChatGoogleGemini node performs automated tasks as part of the workflow." }, { "id": "75442631-512e-4b0e-a5a8-ed3e3a3e1f94", "name": "Embeddings Google Gemini1", "type": "n8n-nodes-base.noOp", "position": [ -120, 320 ], "parameters": { "modelName": "models/text-embedding-004" }, "credentials": { "googlePalmApi": { "id": "2zwuT5znDglBrUCO", "name": "Google Gemini(PaLM) Api account" } }, "typeVersion": 1, "notes": "This embeddingsGoogleGemini node performs automated tasks as part of the workflow." }, { "id": "c33ceeca-9bf0-4dd2-b8df-fc2a1ccdf512", "name": "Chat", "type": "n8n-nodes-base.noOp", "position": [ -2780, -460 ], "webhookId": "e66183cc-1eed-4968-b34b-bcecf1bb55e8", "parameters": { "public": true, "options": { "loadPreviousSession": "notSupported" }, "initialMessages": "Hi there! 👋\nPlease provide a URL of a Youtube playlist you would like me to analise." }, "typeVersion": 1.1, "notes": "This chatTrigger node performs automated tasks as part of the workflow." }, { "id": "06bb2dfd-027c-4902-9559-2de080f6c145", "name": "Video Titles", "type": "n8n-nodes-base.splitOut", "position": [ 1160, 40 ], "parameters": { "options": {}, "fieldToSplitOut": "id,title" }, "typeVersion": 1, "notes": "This splitOut node performs automated tasks as part of the workflow." }, { "id": "d9bbdda2-ef26-4b6c-94d0-7542f88f1530", "name": "Merge", "type": "n8n-nodes-base.merge", "position": [ 1700, 20 ], "parameters": { "mode": "combineBySql", "query": "SELECT \n ROW_NUMBER() AS video_number,\n input1.youtubeId, input2.title, input1.concatenated_transcript_text as transcript_text FROM input1 LEFT JOIN input2 ON input1.youtubeId = input2.id" }, "typeVersion": 3, "notes": "This merge node performs automated tasks as part of the workflow." }, { "id": "e6febdb9-370b-478d-b470-d6c2d3314d7b", "name": "Edit Fields", "type": "n8n-nodes-base.set", "position": [ 980, 540 ], "parameters": { "options": {}, "assignments": { "assignments": [ { "id": "b5e935c5-4973-40a3-adb9-fa76904d2ed9", "name": "video_number", "type": "number", "value": "={{ $('Merge').item.json.video_number }}" }, { "id": "e98f417d-123f-4a85-b2f7-64430e7b0250", "name": "youtubeId", "type": "string", "value": "={{ $('Merge').item.json.youtubeId }}" }, { "id": "d0ced7fd-c9a3-4a09-bf09-e4b5e45dd03d", "name": "title", "type": "string", "value": "={{ $('Merge').item.json.title }}" }, { "id": "31a80e6d-9b02-4d21-b888-1a00c036a04b", "name": "summary", "type": "string", "value": "={{ $json.text }}" }, { "id": "ef12f3f2-3d63-4e78-835f-7da004393a07", "name": "transcript_text", "type": "string", "value": "={{ $('Merge').item.json.transcript_text }}" }, { "id": "afa841e2-6f8b-4b27-9f28-10368ee32c2e", "name": "playlistId", "type": "string", "value": "={{ $('Update Context Intent').first().json.output.id }}" } ] } }, "typeVersion": 3.4, "notes": "This set node performs automated tasks as part of the workflow." }, { "id": "2736a1e6-a982-4f60-aa8d-658e9a3e9193", "name": "AI Agent", "type": "n8n-nodes-base.noOp", "position": [ 2720, 480 ], "parameters": { "text": "=Please analyze the given \"Transcript summary\" and create a full summary overview, following the below guidelines.\n\n1. Provide a full descriptive break down of the content of each video. Assume the user does not won't or have time to watch such videos, so:\n- Your summary needs to be exhaustive, descriptive, and minimise bullet points\n- Your summary needs to captures all the core concepts while maintaining technical accuracy\n- Your summary will be verbose\n\n2. Consider that the intent of the user is not to watch the videos but rather have all the content required and summaries from on the \"Transcript summary\".\n\n3. Use the tool called `chat_playlist_data`, which can analyze YouTube videos. Use this tool effectively to process video content and generate structured summaries.\n\nUser message:\n{{ $('Chat').item.json.chatInput }}\n\nTranscript summary:\n{{ $('Full Summary').item.json.concatenated_summary }}", "agent": "conversationalAgent", "options": {}, "promptType": "define" }, "executeOnce": true, "typeVersion": 1.7, "notes": "This agent node performs automated tasks as part of the workflow." }, { "id": "cf1e5c50-9c89-465f-beca-482cbd9affba", "name": "Google Gemini Chat Model4", "type": "n8n-nodes-base.noOp", "position": [ 2520, 720 ], "parameters": { "options": {}, "modelName": "models/gemini-2.0-flash" }, "credentials": { "googlePalmApi": { "id": "2zwuT5znDglBrUCO", "name": "Google Gemini(PaLM) Api account" } }, "typeVersion": 1, "notes": "This lmChatGoogleGemini node performs automated tasks as part of the workflow." }, { "id": "1cbcec5e-b58c-4fce-b944-08c47a7385db", "name": "Delete Collection", "type": "n8n-nodes-base.httpRequest", "onError": "continueRegularOutput", "position": [ 1340, 480 ], "parameters": { "url": "{{ $env.BASE_URL }}", "method": "POST", "options": {}, "jsonBody": "{\n \"filter\": {}\n}", "sendBody": true, "sendHeaders": true, "specifyBody": "json", "authentication": "{{ $credentials.predefinedCredentialType }}", "headerParameters": { "parameters": [ { "name": "Content-Type", "value": "application/json" } ] }, "nodeCredentialType": "YOUR_CREDENTIAL_HERE" }, "credentials": { "qdrantApi": { "id": "mb8rw8tmUeP6aPJm", "name": "QdrantApi account" } }, "executeOnce": true, "typeVersion": 4.2, "notes": "This httpRequest node performs automated tasks as part of the workflow." }, { "id": "16d7eaec-1d98-42dd-89aa-2681a9d1697d", "name": "Default Data Loader", "type": "n8n-nodes-base.noOp", "position": [ 1820, 700 ], "parameters": { "options": { "metadata": { "metadataValues": [ { "name": "video_number", "value": "={{ $input.item.json.video_number }}" }, { "name": "=youtubeId", "value": "={{ $input.item.json.youtubeId }}" }, { "name": "summary", "value": "={{ $input.item.json.summary }}" }, { "name": "title", "value": "={{ $input.item.json.title }}" }, { "name": "playlistId", "value": "={{ $input.item.json.playlistId }}" } ] } } }, "typeVersion": 1, "notes": "This documentDefaultDataLoader node performs automated tasks as part of the workflow." }, { "id": "d3bc9db2-7618-46fd-b825-2cc0ad45fc22", "name": "Chat Buffer Memory", "type": "n8n-nodes-base.noOp", "position": [ -260, -40 ], "parameters": { "sessionKey": "YOUR_CREDENTIAL_HERE", "sessionIdType": "customKey", "contextWindowLength": 10 }, "typeVersion": 1.3, "notes": "This memoryBufferWindow node performs automated tasks as part of the workflow." }, { "id": "fb55b13f-880f-404c-9446-9f8a238c8a5c", "name": "Full Summary", "type": "n8n-nodes-base.summarize", "position": [ 2520, 480 ], "parameters": { "options": {}, "fieldsToSummarize": { "values": [ { "field": "summary", "separateBy": "\n", "aggregation": "concatenate" } ] } }, "typeVersion": 1.1, "notes": "This summarize node performs automated tasks as part of the workflow." }, { "id": "efc2fc99-c738-425e-9ee1-669e378e197f", "name": "Sticky Note2", "type": "n8n-nodes-base.stickyNote", "position": [ -560, -400 ], "parameters": { "color": 7, "width": 1080, "height": 900, "content": "## RAG & Reply to User Query\n- Retrieves and provides answers to user queries combining retrieval-augmented generation.\n- Processes messages without specific routing rules.\n " }, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow." }, { "id": "0040adf6-2cfc-4b79-ba81-b76740bfd158", "name": "Sticky Note3", "type": "n8n-nodes-base.stickyNote", "position": [ 560, -400 ], "parameters": { "color": 7, "width": 1380, "height": 700, "content": "## Fetch and prepare Playlist video transcripts data for processing \n- Collects and organizes playlist video transcripts.\n- Prepares data for analysis and summarization." }, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow." }, { "id": "59b29fc5-5d88-40c0-a273-9dc71d9f009e", "name": "Chat Buffer Memory1", "type": "n8n-nodes-base.noOp", "position": [ 2700, 720 ], "parameters": { "sessionKey": "YOUR_CREDENTIAL_HERE", "sessionIdType": "customKey", "contextWindowLength": 10 }, "typeVersion": 1.3, "notes": "This memoryBufferWindow node performs automated tasks as part of the workflow." }, { "id": "afb6f6be-d299-402e-a9c6-2c60fbf4974e", "name": "YouTube Transcript", "type": "n8n-nodes-youtube-transcription-dmr.youtubeTranscripter", "position": [ 1160, -200 ], "parameters": { "videoId": "={{ $json.id }}", "continueOnFail": true }, "typeVersion": 1, "notes": "This youtubeTranscripter node performs automated tasks as part of the workflow." }, { "id": "9a43eb4a-9d4c-4fce-b19c-94485aa8af76", "name": "Sticky Note4", "type": "n8n-nodes-base.stickyNote", "position": [ 560, 340 ], "parameters": { "color": 7, "width": 640, "height": 700, "content": "## Summarize & Analyze Transcript\n- Creates summarized data from transcripts." }, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow." }, { "id": "676677f0-3dae-4873-a449-92930ced534e", "name": "Sticky Note5", "type": "n8n-nodes-base.stickyNote", "position": [ 1240, 340 ], "parameters": { "color": 7, "width": 980, "height": 700, "content": "## Store Embeddings\n- Saves embedded data for future use.\n- Updates current context to maintain the flow of the conversation. " }, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow." }, { "id": "9ecd4d0d-b8bd-4a5d-981f-49fa515c17be", "name": "Sticky Note6", "type": "n8n-nodes-base.stickyNote", "position": [ 2260, 340 ], "parameters": { "color": 7, "width": 940, "height": 880, "content": "## First Summary Analysis\n- Conducts initial analysis of summarized data.\n- Return to the user insights from processed transcripts." }, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow." }, { "id": "45ae8377-0848-45fb-a7a3-aba8d44e3d35", "name": "Message Intent", "type": "n8n-nodes-base.noOp", "onError": "continueRegularOutput", "position": [ -2260, -460 ], "parameters": { "text": "= {{ $('Chat').item.json.chatInput }}", "options": { "systemMessage": "=**# YOUR TASK:**\nPlease analyze the user's message and decide if the user has provided the info required - and ALWAYS reply using the **Output format** defined below.\n\n# Output format\nYou use the following JSON structure to reply, don't include anything else, and alway inlude all the fields:\n```\n{\n \"intent\": PLAYLIST|VIDEO|NONE,\n \"url\": Youtube Playlist or Video URL or empty string,\n \"id\": Youtube Playlist or Video ID or empty string,\n \"limit\": number, default 0,\n \"status\": Previous context status `{{ $json.context_intent?.status }}` or \"PENDING\"\n}\n```\n\n## INTENT field GUIDELINES:\n\n**Respond with \"PLAYLIST\" if:**\n- The messsage contains a valid Youtube Playlist URL\n\n**Respond with \"VIDEO\" if:**\n- The messsage contains a valid Youtube Video URL\n\n**Respond with \"NONE\" if:**\n- The messsage does not contains a valid Youtube Playlist or Video URL\n\n## LIMIT field GUIDELINE:\nIf the \"Previous Context\" intent or the current intent is a Playlist: Based on current or most recent user message, check if there is an indication of how many videos to process, otherwise default to 0.\n\n## STATUS field GUIDELINE\nIf intent is a Playlist or Video and _different_ from the the \"Previous Context\" then use \"PENDING\" since the user intent is to run a new process. Otherwise use the \"Previous Context\" status value.\n\n\n# Rules for Playlist and Video\n\n## YouTube Playlist URL Definition\n- A URL from www.youtube.com or youtube.com.\n- Contains the query parameter `&list=` followed by a playlist ID.\n- Example: {{ $env.WEBHOOK_URL }} (where PLXXXXXX is the playlist ID).\n\n## YouTube Video URL Definition\n- A URL from www.youtube.com, youtube.com, or youtu.be.\n- For www.youtube.com or youtube.com, it contains the query parameter `v=` followed by a video ID.\n- For youtu.be, it follows the format {{ $env.WEBHOOK_URL }}\n- Examples:\n - {{ $env.WEBHOOK_URL }} (where VIDEO_ID is the video ID).\n - {{ $env.WEBHOOK_URL }}\n- IMPORTANT: YouTube Video URL **Does NOT have a query parameter `list=`**\n\n\n# Previous Context\n{{ JSON.stringify($json.context_intent) }}\n" }, "promptType": "define", "hasOutputParser": true }, "retryOnFail": true, "typeVersion": 1.7, "alwaysOutputData": true, "notes": "This agent node performs automated tasks as part of the workflow." }, { "id": "a29403f0-ed5c-465c-a43d-84e8dee69662", "name": "Structured Output Parser1", "type": "n8n-nodes-base.noOp", "position": [ -2020, -220 ], "parameters": { "jsonSchemaExample": "{\n \"intent\": \"PLAYLIST|VIDEO|NONE\",\n \"url\": \"Youtube Playlist or Video URL or empty string,\",\n \"id\": \"Youtube Playlist or Video ID or empty string,\",\n \"limit\": \"number of playlist videos to process or 0\",\n \"status\": \"PENDING|READY|DONE\"\n}" }, "typeVersion": 1.2, "notes": "This outputParserStructured node performs automated tasks as part of the workflow." }, { "id": "b3f43325-aa7b-47f3-8bbb-e27fa0c44a1e", "name": "Update Context Intent", "type": "n8n-nodes-base.redis", "position": [ -1160, -640 ], "parameters": { "key": "YOUR_CREDENTIAL_HERE", "value": "=intent {{ $('Process Status').item.json.output?.intent || null }} url {{ $('Process Status').item.json.output?.url || \"\" }} id {{ $('Process Status').item.json.output?.id || \"\" }} limit {{ $('Process Status').item.json.output?.limit || 0 }} status {{ $('Process Status').item.json.output?.status || 'PENDING' }}", "keyType": "YOUR_CREDENTIAL_HERE", "operation": "set", "valueIsJSON": false }, "credentials": { "redis": { "id": "mA0f9F1ROUThyrRW", "name": "Redis account" } }, "typeVersion": 1, "notes": "This redis node performs automated tasks as part of the workflow." }, { "id": "9c296501-049c-44b5-ae8d-8dda2c523278", "name": "Get Previous Context Intent", "type": "n8n-nodes-base.redis", "onError": "continueRegularOutput", "position": [ -2440, -460 ], "parameters": { "key": "YOUR_CREDENTIAL_HERE", "keyType": "YOUR_CREDENTIAL_HERE", "options": { "dotNotation": false }, "operation": "get", "valueIsJSON": false, "propertyName": "context_intent" }, "credentials": { "redis": { "id": "mA0f9F1ROUThyrRW", "name": "Redis account" } }, "typeVersion": 1, "alwaysOutputData": true, "notes": "This redis node performs automated tasks as part of the workflow." }, { "id": "9499d9f2-7d6d-432b-bdde-b4b98b90b224", "name": "Route Message Intent", "type": "n8n-nodes-base.switch", "position": [ -1700, -460 ], "parameters": { "rules": { "values": [ { "outputKey": "YOUR_CREDENTIAL_HERE", "conditions": { "options": { "version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict" }, "combinator": "and", "conditions": [ { "id": "44e008af-4a1a-429d-adb6-039e74b643a6", "operator": { "type": "boolean", "operation": "true", "singleValue": true }, "leftValue": "={{ \n ($json.output.intent == \"VIDEO\" || $json.output.intent == \"PLAYLIST\")\n && $json.output.status != \"DONE\" \n}}", "rightValue": "/PLAYLIST|VIDEO/" } ] }, "renameOutput": true }, { "outputKey": "YOUR_CREDENTIAL_HERE", "conditions": { "options": { "version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict" }, "combinator": "and", "conditions": [ { "id": "12641857-945d-4470-968e-f3f805bfe1cd", "operator": { "type": "boolean", "operation": "true", "singleValue": true }, "leftValue": "={{ \n $json.output.intent == \"NONE\" || $json.output.status == \"DONE\"\n}}", "rightValue": "NONE" } ] }, "renameOutput": true } ] }, "options": { "fallbackOutput": "extra" } }, "typeVersion": 3.2, "notes": "This switch node performs automated tasks as part of the workflow." }, { "id": "dfd329e8-98b1-4ae8-8cc1-a1a5215b1c09", "name": "Process Status", "type": "n8n-nodes-base.code", "position": [ -1340, -640 ], "parameters": { "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nif ($input.last().json.output.intent == 'VIDEO') {\n $input.last().json.output.status = 'READY'\n}\n\nelse if ($input.last().json.output.intent == 'PLAYLIST' && parseInt($input.last().json.output.limit) > 0) {\n $input.last().json.output.status = 'READY'\n}\n\nelse {\n $input.last().json.output = {\n intent: $('Default Intent').first().json.output.intent,\n url: $('Default Intent').first().json.output.url,\n id: $('Default Intent').first().json.output.id,\n limit: $('Default Intent').first().json.output.limit,\n status: 'PENDING',\n }\n}\n\n\nreturn $input.all();" }, "typeVersion": 2, "notes": "This code node performs automated tasks as part of the workflow." }, { "id": "c84c21c6-0948-4628-b7a8-7cd2a5602cdc", "name": "Simple Memory", "type": "n8n-nodes-base.noOp", "position": [ -2180, -220 ], "parameters": { "sessionKey": "YOUR_CREDENTIAL_HERE", "sessionIdType": "customKey" }, "typeVersion": 1.3, "notes": "This memoryBufferWindow node performs automated tasks as part of the workflow." }, { "id": "8ad2e1d1-b657-47cd-afa5-4fc49dc7e0e6", "name": "Simple Memory3", "type": "n8n-nodes-base.noOp", "position": [ 1040, -1180 ], "parameters": { "sessionKey": "YOUR_CREDENTIAL_HERE", "sessionIdType": "customKey" }, "typeVersion": 1.3, "notes": "This memoryBufferWindow node performs automated tasks as part of the workflow." }, { "id": "cf4d7c73-d9b1-42da-9ccc-6abccde4110c", "name": "Sticky Note7", "type": "n8n-nodes-base.stickyNote", "position": [ -2540, -620 ], "parameters": { "color": 7, "width": 1080, "height": 580, "content": "## Message intent routing\n- Retrieves the previous context for continuity.\n- Ensures data integrity before processing.\n- Routes incoming messages based on intent.\n " }, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow." }, { "id": "c0ecf826-b803-421a-aa2a-cb713131fbb7", "name": "Google Gemini Chat Model6", "type": "n8n-nodes-base.noOp", "position": [ -2340, -220 ], "parameters": { "options": {}, "modelName": "models/gemini-2.0-flash-lite" }, "credentials": { "googlePalmApi": { "id": "2zwuT5znDglBrUCO", "name": "Google Gemini(PaLM) Api account" } }, "typeVersion": 1, "notes": "This lmChatGoogleGemini node performs automated tasks as part of the workflow." }, { "id": "513a8c71-e982-47df-87bd-1e3d3ae9c613", "name": "Sticky Note9", "type": "n8n-nodes-base.stickyNote", "position": [ -1420, -1020 ], "parameters": { "color": 7, "width": 460, "height": 580, "content": "## Update Context\n- Updates any issues detected in the context.\n- Prepares data for workflow progression.\n " }, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow." }, { "id": "d4a9d7aa-dfee-418f-900a-9649f0405861", "name": "Sticky Note10", "type": "n8n-nodes-base.stickyNote", "position": [ 800, -1480 ], "parameters": { "color": 7, "width": 480, "height": 460, "content": "## Ask number of Playlist videos to process" }, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow." }, { "id": "f89481a4-64a2-4895-821f-effe48f7d331", "name": "Numb of Videos", "type": "n8n-nodes-base.noOp", "position": [ 900, -1380 ], "parameters": { "text": "={{ $('Chat').item.json.chatInput }}", "options": { "systemMessage": "=**Objective:**\n\nWe are here because the user wants to analyse a playlist in context, but we are missing how many videos he would like to process. Please reply to the user asking user to provide a number.\n\n## Context\n{{ JSON.stringify($json.output) }}" }, "promptType": "define" }, "typeVersion": 1.7, "notes": "This agent node performs automated tasks as part of the workflow." }, { "id": "bb8fa898-4953-4148-8339-2d205b86fc91", "name": "Default Intent", "type": "n8n-nodes-base.code", "position": [ -1920, -460 ], "parameters": { "jsCode": "if(\n ($('Message Intent').first().json?.output?.intent == 'NONE' \n || Object.keys($('Message Intent').first().json?.output || {}).length == 0)\n && Object.keys($('Get Previous Context Intent').first().json.context_intent).length > 0\n) {\n //use prev context intent\n if(!$input.first().json.output) {\n $input.first().json.output = {}\n }\n $input.first().json.output.intent = $('Get Previous Context Intent').first().json.context_intent?.intent || \"NONE\";\n $input.first().json.output.url = $('Get Previous Context Intent').first().json.context_intent?.url || \"\";\n $input.first().json.output.id = $('Get Previous Context Intent').first().json.context_intent?.id || \"\";\n $input.first().json.output.limit = $('Get Previous Context Intent').first().json.context_intent?.limit || 0;\n $input.first().json.output.status = $('Get Previous Context Intent').first().json.context_intent?.status || \"PENDING\";\n} else {\n // $input.first().json.output.intent = $('Message Intent').first().json.context_intent?.intent || \"NONE\";\n // $input.first().json.output.url = $('Message Intent').first().json.context_intent?.url || \"\";\n // $input.first().json.output.id = $('Message Intent').first().json.context_intent?.id || \"\";\n // $input.first().json.output.limit = $('Message Intent').first().json.context_intent?.limit || 0;\n // $input.first().json.output.status = $('Message Intent').first().json.context_intent?.status || \"PENDING\";\n}\n\n// else use message intent\n\nreturn $input.all();" }, "typeVersion": 2, "notes": "This code node performs automated tasks as part of the workflow." }, { "id": "0f1d1bb6-22c0-4b65-beaf-eaf4401d7550", "name": "Playlist Limit", "type": "n8n-nodes-base.if", "position": [ 160, -860 ], "parameters": { "options": {}, "conditions": { "options": { "version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict" }, "combinator": "and", "conditions": [ { "id": "6ee01f0a-9533-4fb3-b023-cfcc422d9011", "operator": { "name": "filter.operator.equals", "type": "string", "operation": "equals" }, "leftValue": "={{ $('Process Status').item.json.output.intent }}", "rightValue": "PLAYLIST" }, { "id": "e9575bb2-3c60-498b-b2c7-436b62e5195c", "operator": { "type": "number", "operation": "lte" }, "leftValue": "={{ parseInt($('Process Status').item.json.output.limit) }}", "rightValue": 0 } ] } }, "typeVersion": 2.2, "notes": "This if node performs automated tasks as part of the workflow." }, { "id": "083c17aa-de83-4f3c-8d71-f665903ce3d5", "name": "Playlist or Video", "type": "n8n-nodes-base.switch", "position": [ 160, -640 ], "parameters": { "rules": { "values": [ { "outputKey": "YOUR_CREDENTIAL_HERE", "conditions": { "options": { "version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict" }, "combinator": "and", "conditions": [ { "id": "cc3ec644-7c3d-4d9f-b7a7-89b85824e3e3", "operator": { "name": "filter.operator.equals", "type": "string", "operation": "equals" }, "leftValue": "={{ $('Route Message Intent').item.json.output.intent }}", "rightValue": "VIDEO" } ] }, "renameOutput": true }, { "outputKey": "YOUR_CREDENTIAL_HERE", "conditions": { "options": { "version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict" }, "combinator": "and", "conditions": [ { "id": "33beac83-b96b-4e76-9d18-e22df163ea4d", "operator": { "type": "string", "operation": "equals" }, "leftValue": "={{ $('Route Message Intent').item.json.output.intent }}", "rightValue": "PLAYLIST" } ] }, "renameOutput": true } ] }, "options": {} }, "typeVersion": 3.2, "notes": "This switch node performs automated tasks as part of the workflow." }, { "id": "0626bc2d-9e3a-4a4f-a331-62e8b6c69840", "name": "Get Fields for Summary", "type": "n8n-nodes-base.code", "position": [ 2340, 480 ], "parameters": { "jsCode": "return $('Edit Fields').all();" }, "typeVersion": 2, "notes": "This code node performs automated tasks as part of the workflow." }, { "id": "4aaaed18-ce90-4296-aba2-b5fa9492655d", "name": "Update Context Process Done1", "type": "n8n-nodes-base.redis", "position": [ 2040, 480 ], "parameters": { "key": "YOUR_CREDENTIAL_HERE", "value": "=intent {{ $('Process Status').first().json.output?.intent || null }} url {{ $('Process Status').first().json.output?.url || \"\" }} id {{ $('Process Status').first().json.output?.id || \"\" }} limit {{ $('Process Status').first().json.output?.limit || 0 }} status DONE", "keyType": "YOUR_CREDENTIAL_HERE", "operation": "set", "valueIsJSON": false }, "credentials": { "redis": { "id": "mA0f9F1ROUThyrRW", "name": "Redis account" } }, "executeOnce": true, "typeVersion": 1, "notes": "This redis node performs automated tasks as part of the workflow." }, { "id": "d3f71d59-a158-43e2-bfbd-a3bec20dea5b", "name": "Google Gemini Chat Model8", "type": "n8n-nodes-base.noOp", "position": [ 880, -1180 ], "parameters": { "options": {}, "modelName": "models/gemini-2.0-flash-thinking-exp" }, "credentials": { "googlePalmApi": { "id": "2zwuT5znDglBrUCO", "name": "Google Gemini(PaLM) Api account" } }, "typeVersion": 1, "notes": "This lmChatGoogleGemini node performs automated tasks as part of the workflow." }, { "id": "fa6b8a79-436e-4742-9732-cd5c1b2d3c88", "name": "Playlist HTTP Request", "type": "n8n-nodes-base.httpRequest", "position": [ 660, -200 ], "parameters": { "url": "{{ $env.BASE_URL }}", "options": {} }, "typeVersion": 4.2, "notes": "This httpRequest node performs automated tasks as part of the workflow." }, { "id": "99d6da96-8983-4e7d-8343-b9ac975f5d20", "name": "YouTube Transcript1", "type": "n8n-nodes-youtube-transcription-dmr.youtubeTranscripter", "position": [ 1200, -860 ], "parameters": { "videoId": "={{ $('Update Context Intent').item.json.output.id }}", "continueOnFail": true }, "typeVersion": 1, "notes": "This youtubeTranscripter node performs automated tasks as part of the workflow." }, { "id": "41a07802-3fba-42fa-8482-f526a7e1b173", "name": "Video HTTP Request", "type": "n8n-nodes-base.httpRequest", "position": [ 900, -860 ], "parameters": { "url": "{{ $env.BASE_URL }}", "options": {} }, "typeVersion": 4.2, "notes": "This httpRequest node performs automated tasks as part of the workflow." }, { "id": "02103114-9c22-49b1-9dee-746b84cdef66", "name": "Get Title and Desc", "type": "n8n-nodes-base.code", "position": [ 1200, -640 ], "parameters": { "jsCode": "/**\n * This code node contains a modified version of play-dl,\n * which is licensed under the GNU General Public License Version 3 (GPLv3).\n *\n * Original Library Name: play-dl\n * Original Library Source: {{ $env.WEBHOOK_URL }}\n * Original Library License: GNU General Public License Version 3 (GPLv3)\n * (See: {{ $env.WEBHOOK_URL }}\n *\n * Modifications were made to the original library for use within this N8N workflow.\n * These modifications are also licensed under the GNU General Public License Version 3 (GPLv3).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <{{ $env.WEBHOOK_URL }}>.\n */\n\n\n/**\n * Basic function to get specific data (title, description, duration)\n * from pre-fetched HTML body data of a YouTube video page.\n * Assumes the HTML body is passed as the first argument.\n *\n * @param {string} body HTML body data of the YouTube video page.\n * @param {string} video_id YouTube video ID.\n * @param {string} url YouTube video URL.\n * @returns {Promise<{title: string, description: string, duration: number}>} Video Basic Info.\n * @throws {Error} If video ID cannot be extracted, captcha is detected,\n * or necessary data cannot be parsed.\n */\nasync function video_basic_info(body, video_id, url) {\n // --- Input Validation ---\n if (typeof body !== 'string') {\n throw new Error('body parameter must be a string of HTML');\n }\n if (typeof video_id !== 'string' || !video_id.trim()) {\n throw new Error('video_id parameter must be a non-empty string');\n }\n if (typeof url !== 'string' || !url.trim()) {\n throw new Error('url parameter must be a non-empty URL string');\n }\n\n // --- Captcha Check ---\n // Added check for consent page as well\n if (body.includes('Our systems have detected unusual traffic') || body.includes('consent.google.com')) {\n throw new Error('Captcha or Consent page encountered: YouTube likely requires interaction or detected bot-like activity.');\n }\n\n // --- Extract Player Data ---\n let player_data;\n try {\n // More robust regex to find ytInitialPlayerResponse, stopping at the next semicolon\n const player_data_match = body.match(/var ytInitialPlayerResponse\\s*=\\s*({.+?});\\s*(?:var |<\\/script)/);\n if (!player_data_match || !player_data_match[1]) {\n // Fallback attempt with simpler split (less reliable)\n const split_data = body.split('var ytInitialPlayerResponse = ');\n player_data = split_data?.[1]?.split(';')?.[0];\n } else {\n player_data = player_data_match[1];\n }\n\n if (!player_data) {\n // Check for common failure indicators in the HTML if data isn't found\n if (body.includes('