From 84c5c36672744d002d7b6b54b44d3d07eabe6887 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Wed, 12 Aug 2026 17:46:05 +0700 Subject: [PATCH] feat(gateway): YouTube cookies support for yt-dlp screen share + music YouTube now blocks anonymous embeds (403 'Sign in to confirm you're not a bot'). Resolve with account cookies via --cookies. - mediaSource: buildCookieArgs() reads GMW_YT_COOKIES_PATH (default /etc/gmw-discord-gateway/ytcookies.txt) and injects --cookies into resolveMediaUrl + getDirectScreenInput + extractMediaInfo. Falls back to anon if file missing (graceful 403, not crash). - bws-exec now writes cookies file from BWS secret gmw_yt_downloader_cookies on service start (systemd ConfigFile). --- .../modules/voice-recording/mediaSource.ts | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/services/discord-gateway/src/modules/voice-recording/mediaSource.ts b/services/discord-gateway/src/modules/voice-recording/mediaSource.ts index 8529ca5..2c8132b 100644 --- a/services/discord-gateway/src/modules/voice-recording/mediaSource.ts +++ b/services/discord-gateway/src/modules/voice-recording/mediaSource.ts @@ -1,5 +1,5 @@ import { type ChildProcess, spawn } from "node:child_process"; -import { chmodSync, mkdtempSync, rmSync } from "node:fs"; +import { chmodSync, existsSync, mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { PassThrough, type Readable } from "node:stream"; @@ -217,6 +217,28 @@ function buildNotInstalledError(): Error { ); } +/** + * Build the yt-dlp --cookies args. YouTube blocks anonymous embeds with a + * "Sign in to confirm you're not a bot" 403 unless yt-dlp is given a logged- + * in account's cookies. The path is configurable via GMW_YT_COOKIES_PATH + * (default: the BWS-provided file the deploy writes to /etc/.../ytcookies.txt). + * If the file doesn't exist we pass nothing and fall back to anon (YouTube + * may 403 — screen share will fail gracefully, not crash). + */ +function buildCookieArgs(): string[] { + const cookiePath = + process.env.GMW_YT_COOKIES_PATH ?? "/etc/gmw-discord-gateway/ytcookies.txt"; + try { + if (cookiePath && existsSync(cookiePath)) { + logger.info({ cookiePath }, "Using YouTube cookies for yt-dlp"); + return ["--cookies", cookiePath]; + } + } catch { + /* ignore — fallback to anon */ + } + return []; +} + // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- @@ -241,6 +263,7 @@ export function resolveMediaUrl( ): Promise { return new Promise((resolve, reject) => { const format = options?.quality ?? "bestaudio"; + const cookieArgs = buildCookieArgs(); const args = [ "-f", format, @@ -248,6 +271,7 @@ export function resolveMediaUrl( "-", "--no-progress", "--no-warnings", + ...cookieArgs, "--print", "before_dl:title", "--print", @@ -402,6 +426,7 @@ export function getDirectScreenInput(url: string): Promise { const tmpDir = mkdtempSync(join(tmpdir(), "gmw-ytdlp-")); chmodSync(tmpDir, 0o1777); + const cookieArgs = buildCookieArgs(); const args = [ "-f", "bestvideo[protocol^=http]+bestaudio[protocol^=http]/best[protocol^=http]/best", @@ -410,6 +435,7 @@ export function getDirectScreenInput(url: string): Promise { "--no-playlist", "--no-warnings", "--no-progress", + ...cookieArgs, "-P", tmpDir, url,