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:
2026-06-26 11:25:57 -04:00
parent 20ee913394
commit 24c4c807a3
11 changed files with 238 additions and 21 deletions
+10
View File
@@ -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
}
+3 -1
View File
@@ -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)
+4
View File
@@ -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
}