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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user