From 32c09de896e8af992bd3405a442f2aae0c778d18 Mon Sep 17 00:00:00 2001 From: ayrisdev Date: Sun, 30 Aug 2026 19:50:55 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20canl=C4=B1-tespit=20fetch'ine=20cookie?= =?UTF-8?q?=20ekle=20=E2=80=94=20AB=20consent=20duvar=C4=B1n=C4=B1=20a?= =?UTF-8?q?=C5=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Coolify sunucusu AB'de; oturumsuz istek YouTube'un interaktif consent sayfasına düşüyor (o sayfada videoDetails yok), bu da gerçekten canlı bir kanalı "değil" gibi gösteriyordu (doğrulandı: @ertemsener canlıydı, sistem "değil" dedi). yt-dlp için zaten sakladığımız oturum açık cookie'leri aynı fetch'e Cookie header'ı olarak eklendi — giriş yapılmış hesaplar consent duvarını hiç görmüyor. Co-Authored-By: Claude Sonnet 5 --- apps/api-daemon/src/youtubePolling.ts | 13 ++++++++++++- apps/api-daemon/src/ytdlpCookies.ts | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/apps/api-daemon/src/youtubePolling.ts b/apps/api-daemon/src/youtubePolling.ts index 77a82cf..477961b 100644 --- a/apps/api-daemon/src/youtubePolling.ts +++ b/apps/api-daemon/src/youtubePolling.ts @@ -2,6 +2,7 @@ import { prisma } from "@streamclipper/db"; import { env } from "./env"; import { streamIngestQueue } from "./queues"; import { sendTelegramMessage } from "./telegram"; +import { getCookieHeader } from "./ytdlpCookies"; const BROWSER_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36"; @@ -30,12 +31,22 @@ export const lastPollErrors = new Map(); * only when a channel really is live — verified empty for an offline * channel). Scoping to that one object (and cross-checking channelId) * avoids both the wrong-video bug and yt-dlp's bot-check entirely. + * + * The Coolify server is EU-geolocated, where an unauthenticated request + * hits YouTube's interactive consent wall (no `videoDetails` on that page, + * so a genuinely live channel would misreport as offline) — passing the + * same logged-in cookies used for yt-dlp skips it, since consent is already + * implied by the account. */ async function findLiveVideoId(channelId: string): Promise { const liveUrl = `https://www.youtube.com/channel/${channelId}/live`; try { - const res = await fetch(liveUrl, { headers: { "User-Agent": BROWSER_USER_AGENT } }); + const cookieHeader = await getCookieHeader(); + const headers: Record = { "User-Agent": BROWSER_USER_AGENT }; + if (cookieHeader) headers["Cookie"] = cookieHeader; + + const res = await fetch(liveUrl, { headers }); if (!res.ok) { lastPollErrors.set(channelId, `HTTP ${res.status} fetching ${liveUrl}`); return null; diff --git a/apps/api-daemon/src/ytdlpCookies.ts b/apps/api-daemon/src/ytdlpCookies.ts index 3717558..0b728b7 100644 --- a/apps/api-daemon/src/ytdlpCookies.ts +++ b/apps/api-daemon/src/ytdlpCookies.ts @@ -33,3 +33,25 @@ export async function ytdlpAntiBotArgs(): Promise { return args; } + +/** + * Same cookies, formatted as a `Cookie:` header for plain fetch() calls + * (youtubePolling.ts's live-check). Logged-in cookies also skip YouTube's + * interactive EU consent wall, which an unauthenticated fetch from an + * EU-geolocated server IP otherwise hits — that page has no `videoDetails`, + * so the check would misreport a genuinely live channel as offline. + */ +export async function getCookieHeader(): Promise { + const setting = await prisma.appSetting.findUnique({ where: { key: COOKIES_SETTING_KEY } }); + if (!setting?.value) return null; + + const pairs = setting.value + .split("\n") + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith("#")) + .map((line) => line.split("\t")) + .filter((fields) => fields.length >= 7) + .map(([, , , , , name, value]) => `${name}=${value}`); + + return pairs.length > 0 ? pairs.join("; ") : null; +}