// Client-safe cookie-policy resolution. No node-only imports — the editor's // settings/channel forms are "use client" files that pull the mode constants // in. Mirrors availability.ts in that respect. // // One browser-cookie spec (yt-dlp `--cookies-from-browser`) and one MODE decide // how every yt-dlp spawn uses cookies: // // "always" — pass cookies on every invocation (enumeration, metadata // prefetch, availability probes, downloads). // "when-required" — (default; the historical behavior) cookies only to retry // an attempt that failed with an auth/age error. Covers the // download auth-retry AND the metadata-prefetch retry. // "defer" — never use cookies in normal runs. Auth-gated videos are // recorded (needs_auth), excluded from subsequent batch // runs, and collected into the per-channel "Needs cookies" // bucket whose button re-runs them with cookies forced on. // // The VALUE resolves channel-over-global (non-empty channel spec wins; empty = // inherit). The MODE resolves the same way (absent channel mode = inherit). // With no value configured, "always"/"when-required" degrade to the no-cookie // behavior; "defer" still defers (the bucket run then warns that no cookie // value is configured). export type CookieMode = "always" | "when-required" | "defer"; export const COOKIE_MODE_VALUES: ReadonlyArray = [ "always", "when-required", "defer", ]; export const DEFAULT_COOKIE_MODE: CookieMode = "when-required"; export function isCookieMode(v: unknown): v is CookieMode { return v === "always" || v === "when-required" || v === "defer"; } export type ResolvedCookiePolicy = { // The resolved browser spec, or undefined when neither the channel nor the // global settings carry a non-empty one. cookies: string | undefined; mode: CookieMode; }; // The subset of SiteSettings / ChannelConfig this module reads. Structural, so // neither settings.ts nor channelConfig.ts needs to be imported here (both // import CookieMode from this file). export type CookiePolicyInputs = { cookiesFromBrowser?: string; cookieMode?: CookieMode; }; export function resolveCookiePolicy( settings: CookiePolicyInputs, channelConfig?: CookiePolicyInputs | null, ): ResolvedCookiePolicy { const channelValue = channelConfig?.cookiesFromBrowser?.trim() ?? ""; const globalValue = settings.cookiesFromBrowser?.trim() ?? ""; const cookies = channelValue || globalValue || undefined; const mode = isCookieMode(channelConfig?.cookieMode) ? channelConfig!.cookieMode! : isCookieMode(settings.cookieMode) ? settings.cookieMode! : DEFAULT_COOKIE_MODE; return { cookies, mode }; } // Cookies for an ordinary (non-retry) invocation: only "always" mode passes // them prophylactically. Undefined in every other mode — including "defer", // whose whole point is that normal runs stay cookie-free. export function alwaysCookies(p: ResolvedCookiePolicy): string | undefined { return p.mode === "always" ? p.cookies : undefined; } // Cookies for retrying an attempt that failed with an auth/age error: // available in "always" and "when-required", never in "defer" (the failure is // recorded instead, feeding the Needs-cookies bucket). export function authRetryCookies(p: ResolvedCookiePolicy): string | undefined { return p.mode === "defer" ? undefined : p.cookies; } // The argv fragment for a resolved cookie spec. Empty when there is none, so // spawn sites can unconditionally spread it. export function cookieArgs(cookies: string | undefined): string[] { return cookies ? ["--cookies-from-browser", cookies] : []; }