From 09b958ec00d2f81134cbc1173e28a0a69dc7f1a1 Mon Sep 17 00:00:00 2001 From: ayrisdev Date: Sun, 30 Aug 2026 16:14:46 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20yt-dlp'ye=20YouTube=20cookie=20deste?= =?UTF-8?q?=C4=9Fi=20ekle=20(bot-check=20bypass)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit android player_client YouTube'un "Sign in to confirm you're not a bot" duvarını aşmadı — Coolify sunucusunun IP'si datacenter olarak işaretlenmiş. Kimlik doğrulanmış bir tarayıcı oturumunun cookies.txt'i YTDLP_COOKIES_B64 env değişkeni (Coolify'da ayarlandı, repo'da yok) üzerinden container'a taşınıp /tmp'e yazılıyor ve her yt-dlp çağrısına --cookies olarak veriliyor. Co-Authored-By: Claude Sonnet 5 --- .env.example | 7 +++++++ apps/api-daemon/src/capture/streamIngest.ts | 7 ++++++- apps/api-daemon/src/env.ts | 1 + apps/api-daemon/src/youtubePolling.ts | 2 ++ apps/api-daemon/src/ytdlpCookies.ts | 22 +++++++++++++++++++++ docker-compose.yml | 1 + 6 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 apps/api-daemon/src/ytdlpCookies.ts diff --git a/.env.example b/.env.example index e764b35..1f549ee 100644 --- a/.env.example +++ b/.env.example @@ -19,3 +19,10 @@ API_DAEMON_PORT=4001 # capturing immediately — useful for testing the pipeline without waiting # for a real scheduled stream. FORCE_LIVE_URL= + +# base64-encoded Netscape-format cookies.txt from a logged-in YouTube session. +# Datacenter server IPs increasingly get YouTube's "Sign in to confirm +# you're not a bot" bot-check on yt-dlp requests; authenticated cookies +# avoid it. Use a throwaway/secondary Google account, not your main one — +# this file is a live session credential. Never commit the raw cookies.txt. +YTDLP_COOKIES_B64= diff --git a/apps/api-daemon/src/capture/streamIngest.ts b/apps/api-daemon/src/capture/streamIngest.ts index bf1ca15..ff8f2b9 100644 --- a/apps/api-daemon/src/capture/streamIngest.ts +++ b/apps/api-daemon/src/capture/streamIngest.ts @@ -6,6 +6,7 @@ import { prisma } from "@streamclipper/db"; import { env } from "../env"; import { QUEUE_NAMES, signalDetectionQueue, type StreamIngestJob } from "../queues"; import { redisConnection } from "../redis"; +import { ytdlpCookieArgs } from "../ytdlpCookies"; const SEGMENT_LIST_POLL_MS = 5_000; @@ -73,7 +74,11 @@ async function runCapture(job: Job): Promise { const ytdlp = spawn( "yt-dlp", - ["--extractor-args", "youtube:player_client=android", "-f", "best", "-o", "-", youtubeUrl], + [ + "--extractor-args", "youtube:player_client=android", + ...ytdlpCookieArgs(), + "-f", "best", "-o", "-", youtubeUrl, + ], { stdio: ["ignore", "pipe", "pipe"] }, ); diff --git a/apps/api-daemon/src/env.ts b/apps/api-daemon/src/env.ts index 37a7bac..246c891 100644 --- a/apps/api-daemon/src/env.ts +++ b/apps/api-daemon/src/env.ts @@ -7,4 +7,5 @@ export const env = { segmentTimeSec: Number(process.env.SEGMENT_TIME_SEC ?? 900), port: Number(process.env.API_DAEMON_PORT ?? 4001), forceLiveUrl: process.env.FORCE_LIVE_URL ?? "", + ytdlpCookiesB64: process.env.YTDLP_COOKIES_B64 ?? "", }; diff --git a/apps/api-daemon/src/youtubePolling.ts b/apps/api-daemon/src/youtubePolling.ts index 1f401f2..2c5585a 100644 --- a/apps/api-daemon/src/youtubePolling.ts +++ b/apps/api-daemon/src/youtubePolling.ts @@ -4,6 +4,7 @@ import { prisma } from "@streamclipper/db"; import { env } from "./env"; import { streamIngestQueue } from "./queues"; import { sendTelegramMessage } from "./telegram"; +import { ytdlpCookieArgs } from "./ytdlpCookies"; const execFileAsync = promisify(execFile); @@ -32,6 +33,7 @@ async function findLiveVideoId(channelId: string): Promise { "--simulate", "--no-warnings", "--extractor-args", "youtube:player_client=android", + ...ytdlpCookieArgs(), "--print", "%(id)s", liveUrl, ], diff --git a/apps/api-daemon/src/ytdlpCookies.ts b/apps/api-daemon/src/ytdlpCookies.ts new file mode 100644 index 0000000..4314409 --- /dev/null +++ b/apps/api-daemon/src/ytdlpCookies.ts @@ -0,0 +1,22 @@ +import { writeFileSync } from "node:fs"; +import { env } from "./env"; + +const COOKIES_PATH = "/tmp/yt-cookies.txt"; + +/** + * YouTube increasingly bot-checks yt-dlp requests from datacenter IPs + * ("Sign in to confirm you're not a bot"), which no --extractor-args + * combination reliably bypasses. An authenticated session's cookies avoid + * the check. The cookies file is never committed — it's decoded once at + * startup from a Coolify-managed env var into a local temp file. + */ +const cookiesFilePath: string | null = env.ytdlpCookiesB64 + ? (() => { + writeFileSync(COOKIES_PATH, Buffer.from(env.ytdlpCookiesB64, "base64"), { mode: 0o600 }); + return COOKIES_PATH; + })() + : null; + +export function ytdlpCookieArgs(): string[] { + return cookiesFilePath ? ["--cookies", cookiesFilePath] : []; +} diff --git a/docker-compose.yml b/docker-compose.yml index 4c670d0..445d2f2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,6 +34,7 @@ services: FORCE_LIVE_URL: ${FORCE_LIVE_URL:-} TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-} TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-} + YTDLP_COOKIES_B64: ${YTDLP_COOKIES_B64:-} volumes: - shared-media:/shared-media ports: