fix: canlı-tespit fetch'ine cookie ekle — AB consent duvarını aş
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 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,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 { getCookieHeader } from "./ytdlpCookies";
|
||||||
|
|
||||||
const BROWSER_USER_AGENT =
|
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";
|
"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<string, string>();
|
|||||||
* only when a channel really is live — verified empty for an offline
|
* only when a channel really is live — verified empty for an offline
|
||||||
* channel). Scoping to that one object (and cross-checking channelId)
|
* channel). Scoping to that one object (and cross-checking channelId)
|
||||||
* avoids both the wrong-video bug and yt-dlp's bot-check entirely.
|
* 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<string | null> {
|
async function findLiveVideoId(channelId: string): Promise<string | null> {
|
||||||
const liveUrl = `https://www.youtube.com/channel/${channelId}/live`;
|
const liveUrl = `https://www.youtube.com/channel/${channelId}/live`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(liveUrl, { headers: { "User-Agent": BROWSER_USER_AGENT } });
|
const cookieHeader = await getCookieHeader();
|
||||||
|
const headers: Record<string, string> = { "User-Agent": BROWSER_USER_AGENT };
|
||||||
|
if (cookieHeader) headers["Cookie"] = cookieHeader;
|
||||||
|
|
||||||
|
const res = await fetch(liveUrl, { headers });
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
lastPollErrors.set(channelId, `HTTP ${res.status} fetching ${liveUrl}`);
|
lastPollErrors.set(channelId, `HTTP ${res.status} fetching ${liveUrl}`);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -33,3 +33,25 @@ export async function ytdlpAntiBotArgs(): Promise<string[]> {
|
|||||||
|
|
||||||
return args;
|
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<string | null> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user