feat: birden fazla YouTube hesabı arasında cookie'yi dağıt (Faz 6)

Proxy IP'lerini havuza dağıttığımız aynı sorun cookie/hesap seviyesinde de
vardı: tek bir Google hesabı 7-8 kanalı taramaya çalışınca YouTube hesabın
kendisini şüpheli bulup çoğu/tüm kanalda "Sign in to confirm you're not a
bot" veriyordu — proxy IP'den bağımsız bir sinyal, IP dağıtımı bunu
çözemez.

Yeni CookieProfile tablosu: her kanal, DB id'sinden aynı deterministik hash
ile (proxyUrlForChannel'daki gibi) havuzdaki hesaplardan birine sabitlenir.
Her profil kendi ayrı geçici dosyasına yazılıyor (/tmp/yt-cookies-<id>.txt)
— tek paylaşılan dosya, farklı profil kullanan kanallar eşzamanlı
çalışınca birbirinin cookie'sini ezerdi. Hiç profil eklenmemişse eski tekli
ytdlp_cookies ayarına geri düşüyor (geriye dönük uyumlu, hemen kırılmaz).

Ayarlar sayfasında yeni "Cookie Hesapları" kartı: hesap ekle/sil/yenile,
her biri için ayrı bayatlık rozeti. Eski tekli cookie kartı "tekli/eski"
etiketiyle fallback olarak duruyor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 01:20:36 +03:00
co-authored by Claude Sonnet 5
parent eb2a9d28ed
commit ccd922ba37
5 changed files with 215 additions and 26 deletions
+42 -6
View File
@@ -2,8 +2,8 @@ import { writeFileSync } from "node:fs";
import { prisma } from "@streamclipper/db";
import { env } from "./env";
const COOKIES_PATH = "/tmp/yt-cookies.txt";
const COOKIES_SETTING_KEY = "ytdlp_cookies";
const LEGACY_COOKIES_PATH = "/tmp/yt-cookies.txt";
const LEGACY_COOKIES_SETTING_KEY = "ytdlp_cookies";
// Oxylabs' static ISP pool: one dedicated (non-rotating) IP per port,
// confirmed against the dashboard's Proxy list (isp.oxylabs.io:8001..8010 ->
@@ -30,6 +30,43 @@ function proxyUrlForChannel(baseProxyUrl: string, channelKey: string): string {
return `${prefix}${basePort + offset}`;
}
/**
* Picks which cookie profile a channel uses (same deterministic-hash
* pinning as proxyUrlForChannel — same channel always gets the same
* account, different channels spread across the pool) and writes it to a
* profile-specific temp file. Falls back to the old single global
* ytdlp_cookies AppSetting if no profiles have been added yet, so nothing
* breaks before the panel's Settings page is used to add accounts.
*
* A single shared cookie file was a real bottleneck once several channels
* were active at once: it isn't just proxy IP reputation that YouTube can
* flag, it's the *account* — one Google session making requests that look
* like it's scanning many unrelated channels trips its own abuse signal,
* independent of which IP each request came from. Splitting across
* separate throwaway accounts dilutes that per-account signal the same way
* the proxy pool dilutes per-IP signal.
*
* Profile-specific file paths (not one shared /tmp/yt-cookies.txt) matter
* once multiple channels can be using *different* profiles concurrently —
* a shared path would let one channel's capture process read a file another
* channel's poll check just overwrote with a different account's cookies.
*/
async function cookiesFilePathForChannel(channelKey: string): Promise<string | null> {
const profiles = await prisma.cookieProfile.findMany({ orderBy: { createdAt: "asc" } });
if (profiles.length === 0) {
const legacy = await prisma.appSetting.findUnique({ where: { key: LEGACY_COOKIES_SETTING_KEY } });
if (!legacy?.value) return null;
writeFileSync(LEGACY_COOKIES_PATH, legacy.value, { mode: 0o600 });
return LEGACY_COOKIES_PATH;
}
const profile = profiles[simpleHash(channelKey) % profiles.length];
const path = `/tmp/yt-cookies-${profile.id}.txt`;
writeFileSync(path, profile.value, { mode: 0o600 });
return path;
}
/**
* YouTube's anti-bot layer against yt-dlp has three parts, all needed
* together: a "Sign in to confirm you're not a bot" wall (avoided with an
@@ -52,10 +89,9 @@ export async function ytdlpAntiBotArgs(
): Promise<string[]> {
const args: string[] = [];
const setting = await prisma.appSetting.findUnique({ where: { key: COOKIES_SETTING_KEY } });
if (setting?.value) {
writeFileSync(COOKIES_PATH, setting.value, { mode: 0o600 });
args.push("--cookies", COOKIES_PATH);
const cookiesPath = await cookiesFilePathForChannel(channelKey);
if (cookiesPath) {
args.push("--cookies", cookiesPath);
}
if (env.ytdlpPotProviderUrl) {