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

ItemValue
n8n workflow IDjnnu9FRYbuMQBznt
Workflow nameYoutube Video Summary
Trigger typeSchedule — hourly (scheduleTrigger)
YouTube playlist polledUUeeFfhMcJa1kjtfZAGskOCA (TechLinked uploads)
Transcript sub-workflow3whIdBOOMHMbWScI — “Extract transcripts from external YouTube Videos using YouTube Transcript API”
LLM backendLibreChat agent agent_6CfnFJu-bUVcOjMMHONaH via lmChatOpenAi
LLM prompt (git)mikayla/scripts:prompts/youtube-summary.md
Output destinationDiscord webhook → “TechLinked- Homelab Server” channel
Error workflowTX2y7dUINVnLM3ze — “n8n Error Notification”
StatusACTIVE

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 Trigger node sits in parallel. When that path is taken, the Skip dedupe? IF node routes directly to YouTube Video Trigger, bypassing Dedupe 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 via ai_languageModel port
  • Credential: LibreChat- Youtube Summary (openAiApi, id E3JKwhS3rOMyn72x)
  • 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 embed description field.
  • No structured output schema — it’s a free-text summary, not a parsed object. What the LLM returns is what Discord gets.

Credentials Needed

  1. YouTube account 2 (youTubeOAuth2Api) — OAuth2 for YouTube Data API v3. Used by YouTube Data API: playlistItems node.
  2. LibreChat- Youtube Summary (openAiApi) — OpenAI-compatible API key pointing at the LibreChat instance. Used by the LibreChat LLM sub-node.
  3. TechLinked- Homelab Server (discordWebhookApi) — Discord incoming webhook URL. Used by the Discord node.

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: playlistItems node (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 apiToken field 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 the Call 'Extract transcripts…' node’s workflowInputs value map. It will not surface in the credentials panel.

  • Raw HTML Parse: Chapters deliberately 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, chapters will 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 ytInitialData being present in the watch page response — YouTube occasionally serves bot-detection pages or consent walls instead of the full page HTML. When this happens, chapters will be [] and thumbnail falls back to https://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 runs uses 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.

  • 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.