Polls a YouTube channel’s uploads playlist hourly, fetches the transcript and chapter data for any new video, summarizes it via a LibreChat-backed LLM, and posts the result as a Discord embed.
Quick Reference
| Item | Value |
|---|---|
| n8n workflow ID | jnnu9FRYbuMQBznt |
| Workflow name | Youtube Video Summary |
| Trigger type | Schedule — hourly (scheduleTrigger) |
| YouTube playlist polled | UUeeFfhMcJa1kjtfZAGskOCA (TechLinked uploads) |
| Transcript sub-workflow | 3whIdBOOMHMbWScI — “Extract transcripts from external YouTube Videos using YouTube Transcript API” |
| LLM backend | LibreChat agent agent_6CfnFJu-bUVcOjMMHONaH via lmChatOpenAi |
| LLM prompt (git) | mikayla/scripts:prompts/youtube-summary.md |
| Output destination | Discord webhook → “TechLinked- Homelab Server” channel |
| Error workflow | TX2y7dUINVnLM3ze — “n8n Error Notification” |
| Status | ACTIVE |
Architecture
Single execution path — no split-response; the whole chain runs serially per video.
Schedule (hourly) (fires every hour; also has a Manual Test Trigger sibling)
↓
YouTube Data API: playlistItems (HTTP GET /v3/playlistItems — fetches last 10 uploads from hardcoded playlist)
↓
Split Out items (explodes the items array into individual video records)
↓
Skip dedupe? (IF: Manual Test Trigger was the entry point → skip dedupe; else → Dedupe)
↓ [true branch] ↓ [false branch]
YouTube Video Trigger Dedupe across runs (removeDuplicates: keyed on contentDetails.videoId)
↑_________________________↑
↓
YouTube Video Trigger (Code node: shapes item into {id: "yt:video:<id>", title, link, pubDate})
↓
Download Youtube HTML (HTTP GET watch page — extracts ytInitialData + ytInitialPlayerResponse)
↓
Raw HTML Parse: Chapters (Code node: parses chapter renderer tree + picks best thumbnail; strips chapter 0)
↓
Call 'Extract transcripts…' (executeWorkflow → sub-workflow 3whIdBOOMHMbWScI; passes videoId + apiToken)
↓
Summarize transcript (chainLlm: prompt = transcript text + chapters JSON; retries on fail, 5s wait)
↓ [ai_languageModel]
LibreChat (lmChatOpenAi sub-node wired to Summarize transcript; presencePenalty 0.5, timeout 10min)
↓
Discord (webhook embed: title=video title, description=$json.text, thumbnail from parse node)
Schedule Trigger Spec
- Type:
scheduleTrigger— no webhook URL, no external call needed to fire it. - Interval: Every 1 hour (field:
hours, no specific minute/offset configured — fires on the hour per n8n instance clock). - Manual testing: A
Manual Test Triggernode sits in parallel. When that path is taken, theSkip dedupe?IF node routes directly toYouTube Video Trigger, bypassingDedupe across runs. This means manual test runs will reprocess already-seen videos — intentional, but don’t run it manually in production if you care about duplicate Discord posts.
LLM Summarization
- Node:
Summarize transcript(chainLlm, typeVersion 1.4) - Sub-node:
LibreChat(lmChatOpenAi, typeVersion 1.2) wired viaai_languageModelport - Credential:
LibreChat- Youtube Summary(openAiApi, idE3JKwhS3rOMyn72x) - Model:
agent_6CfnFJu-bUVcOjMMHONaH(LibreChat agent — model backing it is configured inside LibreChat, not in n8n) - LLM options:
presencePenalty: 0.5,timeout: 600000ms(10 min),maxRetries: 3 - Prompt shape:
Below is the youtube transcript: ` ` ` {{ $json.text }} ` ` ` The chapters in this video are as follows: ` ` ` {{ $('Raw HTML Parse: Chapters').item.json.chapters.toJsonString() }} ` ` ` - Output:
$json.text— the raw LLM response string, passed directly into the Discord embeddescriptionfield. - No structured output schema — it’s a free-text summary, not a parsed object. What the LLM returns is what Discord gets.
Credentials Needed
YouTube account 2(youTubeOAuth2Api) — OAuth2 for YouTube Data API v3. Used byYouTube Data API: playlistItemsnode.LibreChat- Youtube Summary(openAiApi) — OpenAI-compatible API key pointing at the LibreChat instance. Used by theLibreChatLLM sub-node.TechLinked- Homelab Server(discordWebhookApi) — Discord incoming webhook URL. Used by theDiscordnode.
The transcript sub-workflow (3whIdBOOMHMbWScI) manages its own credentials independently — check that workflow for what it needs.
Gotchas
-
The playlist ID is hardcoded into the
YouTube Data API: playlistItemsnode (UUeeFfhMcJa1kjtfZAGskOCA). This is the TechLinked uploads playlist. If you want to monitor a different channel, you can’t just swap a config variable — you have to edit the node parameters directly. Easy to miss when you’re cloning this for another channel. -
The transcript sub-workflow receives a plaintext
apiTokenfield in the workflow inputs — it’s not a credential reference, it’s a literal string passed as a workflow input. If that token rotates, you have to update it inside theCall 'Extract transcripts…'node’sworkflowInputsvalue map. It will not surface in the credentials panel. -
Raw HTML Parse: Chaptersdeliberately drops the first chapter (.slice(1)) — this is intentional because YouTube’s chapter 0 is typically an intro/title card that duplicates the video title. If a video has only one chapter,chapterswill be an empty array and the prompt will receive[]as the chapters JSON. The LLM handles this fine, but it’s confusing to debug if you’re looking at the parsed output and wondering where chapter 1 went. -
The HTML scraper depends on
ytInitialDatabeing present in the watch page response — YouTube occasionally serves bot-detection pages or consent walls instead of the full page HTML. When this happens,chapterswill be[]andthumbnailfalls back tohttps://i.ytimg.com/vi/<videoId>/maxresdefault.jpg. There’s no explicit error node wired after this Code node; the failure is silent and the summary still posts, just without accurate chapter context. -
Dedupe across runsuses n8n’s built-in deduplication store (removeItemsSeenInPreviousExecutions). This state is stored in the n8n database. If you migrate the n8n instance or reset execution history, the deduplication state resets and all recent videos will be reprocessed and re-posted to Discord on the next hourly run. -
LLM timeout is 10 minutes — if the LibreChat instance is down or slow, this node will block for the full timeout before failing. Combined with
maxRetries: 3, a worst case is a 30-minute hung execution before the error workflow fires.
Disabling
n8n UI → workflow jnnu9FRYbuMQBznt → toggle Active off. The schedule will stop firing. The Manual Test Trigger path is unaffected by active status — you can still run the workflow manually from the editor when inactive.
To also suppress Discord output while leaving the workflow active (e.g., during LLM credential changes), disable the Discord node directly.
Related
- Transcript sub-workflow: n8n workflow
3whIdBOOMHMbWScI— “Extract transcripts from external YouTube Videos using YouTube Transcript API”. This workflow is the actual transcript fetcher; the YouTube Summary workflow calls it as a sub-workflow. Changes to transcript API credentials or logic happen there, not here. - Error notification workflow: n8n workflow
TX2y7dUINVnLM3ze— “n8n Error Notification”. Fires on any execution error from this workflow. - LLM prompt:
mikayla/scripts:prompts/youtube-summary.md(versioned). LibreChat agent UI (agent_6CfnFJu-bUVcOjMMHONaH) is still the runtime source of truth — keep them in sync by hand. - Discord target: “TechLinked- Homelab Server” webhook — posts land in whatever channel that webhook is wired to on the Discord server.