feat: Phase P reliability (YouTube extractor-args plumbing, signing + smoke-test docs)
- YouTube reliability: Settings.youtubePlayerClient + youtubePoToken -> one --extractor-args "youtube:player_client=...;po_token=..." group (accessArgs, unit-tested); advanced fields in Settings -> Network. Automatic WebView PO-token minting deferred (documented). - docs/SMOKE-TEST.md: release-gate checklist for all shipped-but-untested OS wiring (cookies window, protocol/Send-to, scheduled sync, aria2c, proxy, tray/taskbar/pause-resume/terminal) — needs a human to run. - docs/SIGNING.md: code-signing guide (CSC_LINK/CSC_KEY_PASSWORD, OV/EV, CI, HSM hook, verification). Build is signing-ready; needs a cert. - i18n remains deferred (not faked). typecheck + test (195) + build all clean. Roadmap: Phase P code shipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -100,6 +100,10 @@ export interface AccessOptions {
|
||||
restrictFilenames: boolean
|
||||
/** absolute path to a --download-archive file; omit to disable archive tracking */
|
||||
downloadArchivePath?: string
|
||||
/** YouTube extraction client override (e.g. 'web_safari'); empty/omitted = default */
|
||||
youtubePlayerClient?: string
|
||||
/** manually-supplied YouTube Proof-of-Origin token; empty/omitted = none */
|
||||
youtubePoToken?: string
|
||||
}
|
||||
|
||||
// Tuned for typical CDN-served video: 16 connections split across 16 segments,
|
||||
@@ -278,6 +282,12 @@ function accessArgs(a: AccessOptions): string[] {
|
||||
else if (a.cookiesFile) args.push('--cookies', a.cookiesFile)
|
||||
if (a.restrictFilenames) args.push('--restrict-filenames')
|
||||
if (a.downloadArchivePath) args.push('--download-archive', a.downloadArchivePath)
|
||||
// YouTube reliability: combine the player-client + PO-token overrides into one
|
||||
// `youtube:` extractor-args group (semicolon-separated, as yt-dlp expects).
|
||||
const yt: string[] = []
|
||||
if (a.youtubePlayerClient?.trim()) yt.push(`player_client=${a.youtubePlayerClient.trim()}`)
|
||||
if (a.youtubePoToken?.trim()) yt.push(`po_token=${a.youtubePoToken.trim()}`)
|
||||
if (yt.length > 0) args.push('--extractor-args', `youtube:${yt.join(';')}`)
|
||||
return args
|
||||
}
|
||||
|
||||
|
||||
@@ -214,7 +214,9 @@ export function buildCommand(opts: StartDownloadOptions): string[] {
|
||||
cookiesFromBrowser: settings.cookieSource === 'browser' ? settings.cookiesBrowser : undefined,
|
||||
cookiesFile,
|
||||
restrictFilenames: settings.restrictFilenames,
|
||||
downloadArchivePath: settings.downloadArchive ? getDownloadArchivePath() : undefined
|
||||
downloadArchivePath: settings.downloadArchive ? getDownloadArchivePath() : undefined,
|
||||
youtubePlayerClient: settings.youtubePlayerClient,
|
||||
youtubePoToken: settings.youtubePoToken
|
||||
}
|
||||
const extraArgs = resolveExtraArgs(opts, settings)
|
||||
return buildArgs(opts, outputTemplate, options, getBinDir(), access, extraArgs)
|
||||
|
||||
@@ -36,6 +36,8 @@ const DEFAULTS: Settings = {
|
||||
useAria2c: false,
|
||||
cookieSource: 'none',
|
||||
cookiesBrowser: 'chrome',
|
||||
youtubePlayerClient: '',
|
||||
youtubePoToken: '',
|
||||
restrictFilenames: false,
|
||||
downloadArchive: false,
|
||||
// yt-dlp is self-managed: a writable copy under userData, auto-updated on the
|
||||
@@ -256,6 +258,8 @@ export function setSettings(partial: Partial<Settings>): Settings {
|
||||
// and exported by exportBackup as-is — documented, not masked.
|
||||
case 'proxy':
|
||||
case 'rateLimit':
|
||||
case 'youtubePlayerClient':
|
||||
case 'youtubePoToken':
|
||||
if (typeof value === 'string') s.set(key, value)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -226,6 +226,8 @@ export function SettingsView(): React.JSX.Element {
|
||||
const proxy = useSettings((s) => s.proxy)
|
||||
const rateLimit = useSettings((s) => s.rateLimit)
|
||||
const useAria2c = useSettings((s) => s.useAria2c)
|
||||
const youtubePlayerClient = useSettings((s) => s.youtubePlayerClient)
|
||||
const youtubePoToken = useSettings((s) => s.youtubePoToken)
|
||||
const cookieSource = useSettings((s) => s.cookieSource)
|
||||
const cookiesBrowser = useSettings((s) => s.cookiesBrowser)
|
||||
const restrictFilenames = useSettings((s) => s.restrictFilenames)
|
||||
@@ -675,6 +677,28 @@ export function SettingsView(): React.JSX.Element {
|
||||
label={useAria2c ? 'On' : 'Off'}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label="YouTube client (advanced)"
|
||||
hint="Override the YouTube extraction client when downloads start failing the bot check. Try web_safari, tv, or mweb. Leave blank for yt-dlp's default."
|
||||
>
|
||||
<Input
|
||||
value={youtubePlayerClient}
|
||||
placeholder="web_safari"
|
||||
onChange={(_, d) => update({ youtubePlayerClient: d.value })}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label="YouTube PO token (advanced)"
|
||||
hint="A manually-obtained Proof-of-Origin token for YouTube's bot check, sent via --extractor-args. Usually cookies (above) are the easier fix; automatic minting is planned."
|
||||
>
|
||||
<Input
|
||||
value={youtubePoToken}
|
||||
placeholder="(none)"
|
||||
onChange={(_, d) => update({ youtubePoToken: d.value })}
|
||||
/>
|
||||
</Field>
|
||||
</Card>
|
||||
|
||||
<Card className={styles.card}>
|
||||
|
||||
@@ -27,6 +27,8 @@ if (import.meta.env.DEV && !window.api) {
|
||||
useAria2c: false,
|
||||
cookieSource: 'none',
|
||||
cookiesBrowser: 'chrome',
|
||||
youtubePlayerClient: '',
|
||||
youtubePoToken: '',
|
||||
restrictFilenames: false,
|
||||
downloadArchive: false,
|
||||
autoUpdateYtdlp: true,
|
||||
|
||||
@@ -21,6 +21,8 @@ const FALLBACK: Settings = {
|
||||
useAria2c: false,
|
||||
cookieSource: 'none',
|
||||
cookiesBrowser: 'chrome',
|
||||
youtubePlayerClient: '',
|
||||
youtubePoToken: '',
|
||||
restrictFilenames: false,
|
||||
downloadArchive: false,
|
||||
autoUpdateYtdlp: true,
|
||||
|
||||
@@ -492,6 +492,17 @@ export interface Settings {
|
||||
cookieSource: CookieSource
|
||||
/** which browser to read cookies from when cookieSource is 'browser' */
|
||||
cookiesBrowser: CookieBrowser
|
||||
/**
|
||||
* YouTube reliability (Phase P). When set, emitted as
|
||||
* `--extractor-args "youtube:player_client=…;po_token=…"`:
|
||||
* - playerClient: an alternate extraction client (e.g. 'web_safari', 'tv',
|
||||
* 'mweb') — a common workaround for YouTube throttling/extraction breakage.
|
||||
* - poToken: a manually-supplied Proof-of-Origin token for the bot check.
|
||||
* Both empty = yt-dlp's defaults. (Automatic WebView token minting is deferred;
|
||||
* this is the argv plumbing it would feed.)
|
||||
*/
|
||||
youtubePlayerClient: string
|
||||
youtubePoToken: string
|
||||
/** sanitize output filenames to a portable ASCII-only subset (--restrict-filenames) */
|
||||
restrictFilenames: boolean
|
||||
/** record completed downloads in a yt-dlp --download-archive file to skip repeats */
|
||||
|
||||
Reference in New Issue
Block a user