fix: yt-dlp'ye YouTube cookie desteği ekle (bot-check bypass)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -19,3 +19,10 @@ API_DAEMON_PORT=4001
|
|||||||
# capturing immediately — useful for testing the pipeline without waiting
|
# capturing immediately — useful for testing the pipeline without waiting
|
||||||
# for a real scheduled stream.
|
# for a real scheduled stream.
|
||||||
FORCE_LIVE_URL=
|
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=
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { prisma } from "@streamclipper/db";
|
|||||||
import { env } from "../env";
|
import { env } from "../env";
|
||||||
import { QUEUE_NAMES, signalDetectionQueue, type StreamIngestJob } from "../queues";
|
import { QUEUE_NAMES, signalDetectionQueue, type StreamIngestJob } from "../queues";
|
||||||
import { redisConnection } from "../redis";
|
import { redisConnection } from "../redis";
|
||||||
|
import { ytdlpCookieArgs } from "../ytdlpCookies";
|
||||||
|
|
||||||
const SEGMENT_LIST_POLL_MS = 5_000;
|
const SEGMENT_LIST_POLL_MS = 5_000;
|
||||||
|
|
||||||
@@ -73,7 +74,11 @@ async function runCapture(job: Job<StreamIngestJob>): Promise<void> {
|
|||||||
|
|
||||||
const ytdlp = spawn(
|
const ytdlp = spawn(
|
||||||
"yt-dlp",
|
"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"] },
|
{ stdio: ["ignore", "pipe", "pipe"] },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ export const env = {
|
|||||||
segmentTimeSec: Number(process.env.SEGMENT_TIME_SEC ?? 900),
|
segmentTimeSec: Number(process.env.SEGMENT_TIME_SEC ?? 900),
|
||||||
port: Number(process.env.API_DAEMON_PORT ?? 4001),
|
port: Number(process.env.API_DAEMON_PORT ?? 4001),
|
||||||
forceLiveUrl: process.env.FORCE_LIVE_URL ?? "",
|
forceLiveUrl: process.env.FORCE_LIVE_URL ?? "",
|
||||||
|
ytdlpCookiesB64: process.env.YTDLP_COOKIES_B64 ?? "",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { prisma } from "@streamclipper/db";
|
|||||||
import { env } from "./env";
|
import { env } from "./env";
|
||||||
import { streamIngestQueue } from "./queues";
|
import { streamIngestQueue } from "./queues";
|
||||||
import { sendTelegramMessage } from "./telegram";
|
import { sendTelegramMessage } from "./telegram";
|
||||||
|
import { ytdlpCookieArgs } from "./ytdlpCookies";
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile);
|
const execFileAsync = promisify(execFile);
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ async function findLiveVideoId(channelId: string): Promise<string | null> {
|
|||||||
"--simulate",
|
"--simulate",
|
||||||
"--no-warnings",
|
"--no-warnings",
|
||||||
"--extractor-args", "youtube:player_client=android",
|
"--extractor-args", "youtube:player_client=android",
|
||||||
|
...ytdlpCookieArgs(),
|
||||||
"--print", "%(id)s",
|
"--print", "%(id)s",
|
||||||
liveUrl,
|
liveUrl,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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] : [];
|
||||||
|
}
|
||||||
@@ -34,6 +34,7 @@ services:
|
|||||||
FORCE_LIVE_URL: ${FORCE_LIVE_URL:-}
|
FORCE_LIVE_URL: ${FORCE_LIVE_URL:-}
|
||||||
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
|
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
|
||||||
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-}
|
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-}
|
||||||
|
YTDLP_COOKIES_B64: ${YTDLP_COOKIES_B64:-}
|
||||||
volumes:
|
volumes:
|
||||||
- shared-media:/shared-media
|
- shared-media:/shared-media
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
Reference in New Issue
Block a user