From 57ca4b58784bad07003cbe986b4636db087dad45 Mon Sep 17 00:00:00 2001 From: ayrisdev Date: Thu, 3 Sep 2026 00:11:05 +0300 Subject: [PATCH] =?UTF-8?q?perf:=20her=20kanal=C4=B1=20Oxylabs=20ISP=20hav?= =?UTF-8?q?uzundaki=20ayr=C4=B1=20bir=20statik=20IP'ye=20sabitle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dashboard'dan doğrulandı: isp.oxylabs.io:8001-8010 her biri farklı, statik (rotasyon yapmayan) bir IP'ye karşılık geliyor. Ama PROXY_URL'in portu hep sabitti — 7-8 kanalın tüm poll+capture trafiği tek bir IP'den (8001) geçiyordu. Artık her kanal, DB id'sinden deterministik bir hash ile havuzdaki 10 porttan birine sabitleniyor (aynı kanal hep aynı IP'yi kullanıyor, farklı kanallar havuza yayılıyor) — hem YouTube'un tek IP üzerindeki yük algısını azaltıyor hem de bant genişliğini paylaştırıyor. Co-Authored-By: Claude Sonnet 5 --- apps/api-daemon/src/capture/streamIngest.ts | 4 +-- apps/api-daemon/src/youtubePolling.ts | 2 +- apps/api-daemon/src/ytdlpCookies.ts | 29 +++++++++++++++++++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/apps/api-daemon/src/capture/streamIngest.ts b/apps/api-daemon/src/capture/streamIngest.ts index 873d7e8..b83897a 100644 --- a/apps/api-daemon/src/capture/streamIngest.ts +++ b/apps/api-daemon/src/capture/streamIngest.ts @@ -96,7 +96,7 @@ async function processCompletedSegments( } async function runCapture(job: Job): Promise { - const { sessionId, youtubeUrl, segmentTimeSec } = job.data; + const { sessionId, youtubeUrl, segmentTimeSec, channelDbId } = job.data; const outDir = path.join(env.sharedMediaRoot, "raw", sessionId); await mkdir(outDir, { recursive: true }); @@ -108,7 +108,7 @@ async function runCapture(job: Job): Promise { "yt-dlp", [ "--js-runtimes", "node", - ...(await ytdlpAntiBotArgs()), + ...(await ytdlpAntiBotArgs(channelDbId)), "-f", "bestvideo+bestaudio/best", "-o", "-", youtubeUrl, ], { stdio: ["ignore", "pipe", "pipe"] }, diff --git a/apps/api-daemon/src/youtubePolling.ts b/apps/api-daemon/src/youtubePolling.ts index c59bc65..73ecc94 100644 --- a/apps/api-daemon/src/youtubePolling.ts +++ b/apps/api-daemon/src/youtubePolling.ts @@ -76,7 +76,7 @@ async function findLiveVideoId(channelId: string, channelName: string, channelDb "--no-warnings", "--js-runtimes", "node", - ...(await ytdlpAntiBotArgs()), + ...(await ytdlpAntiBotArgs(channelDbId)), "--print", "%(id)s", liveUrl, diff --git a/apps/api-daemon/src/ytdlpCookies.ts b/apps/api-daemon/src/ytdlpCookies.ts index fe469a5..0eb3380 100644 --- a/apps/api-daemon/src/ytdlpCookies.ts +++ b/apps/api-daemon/src/ytdlpCookies.ts @@ -5,6 +5,31 @@ import { env } from "./env"; const COOKIES_PATH = "/tmp/yt-cookies.txt"; const 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 -> +// 10 distinct IPs). PROXY_URL's own port is the base; funneling every +// channel through that single port meant 7-8 simultaneous channels' worth +// of YouTube-facing traffic all looked like it came from one IP. +const PROXY_PORT_POOL_SIZE = 10; + +function simpleHash(input: string): number { + let hash = 0; + for (let i = 0; i < input.length; i++) { + hash = (hash * 31 + input.charCodeAt(i)) >>> 0; + } + return hash; +} + +/** Deterministically pins a channel to one of the pool's ports — same channel always gets the same IP (so it isn't mid-session IP-mismatched against a live URL signed for a different one), different channels spread across the pool. */ +function proxyUrlForChannel(baseProxyUrl: string, channelKey: string): string { + const match = baseProxyUrl.match(/^(.*:)(\d+)$/); + if (!match) return baseProxyUrl; + const [, prefix, basePortStr] = match; + const basePort = Number(basePortStr); + const offset = simpleHash(channelKey) % PROXY_PORT_POOL_SIZE; + return `${prefix}${basePort + offset}`; +} + /** * 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 @@ -21,7 +46,7 @@ const COOKIES_SETTING_KEY = "ytdlp_cookies"; * current value and rewrites the temp file, so a panel update takes effect * on the very next yt-dlp invocation without a redeploy. */ -export async function ytdlpAntiBotArgs(): Promise { +export async function ytdlpAntiBotArgs(channelKey: string): Promise { const args: string[] = []; const setting = await prisma.appSetting.findUnique({ where: { key: COOKIES_SETTING_KEY } }); @@ -42,7 +67,7 @@ export async function ytdlpAntiBotArgs(): Promise { args.push("--extractor-args", "youtube:player_client=web,mweb,android"); if (env.proxyUrl) { - args.push("--proxy", env.proxyUrl); + args.push("--proxy", proxyUrlForChannel(env.proxyUrl, channelKey)); } return args;