Add Phase B: cookies, aria2c, proxy/rate-limit, restrict-filenames & archive
Implements the remaining Phase B (Access & networking) items from ROADMAP.md: cookie sources (browser cookie-store import, or a built-in sign-in window that exports a Netscape cookie file via a persisted Electron session partition), the aria2c external downloader, proxy/rate-limit, restrict-filenames, and a download-archive to skip repeats. Wires download.ts to the buildArgs() module added in the previous commit (it wasn't hooked up yet) and renames its options param NetworkOptions -> AccessOptions to reflect the wider scope now that cookies and filename/archive flags joined proxy/rate-limit/aria2c. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+96
-3
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { buildArgs, CROP_SQUARE_PPA } from '../src/main/buildArgs'
|
||||
import { buildArgs, CROP_SQUARE_PPA, ARIA2C_ARGS, type AccessOptions } from '../src/main/buildArgs'
|
||||
import {
|
||||
DEFAULT_DOWNLOAD_OPTIONS,
|
||||
type DownloadOptions,
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
const BIN = 'C:/fake/bin'
|
||||
const OUT = 'C:/out/%(title)s.%(ext)s'
|
||||
const URL = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
|
||||
const NO_ACCESS: AccessOptions = { proxy: '', rateLimit: '', restrictFilenames: false }
|
||||
|
||||
function opts(overrides: Partial<StartDownloadOptions> = {}): StartDownloadOptions {
|
||||
return { id: 't', url: URL, kind: 'video', quality: '1080p', ...overrides }
|
||||
@@ -21,8 +22,12 @@ function audio(overrides: Partial<StartDownloadOptions> = {}): StartDownloadOpti
|
||||
function dlo(overrides: Partial<DownloadOptions> = {}): DownloadOptions {
|
||||
return { ...DEFAULT_DOWNLOAD_OPTIONS, ...overrides }
|
||||
}
|
||||
function build(o: StartDownloadOptions, d: DownloadOptions): string[] {
|
||||
return buildArgs(o, OUT, d, BIN)
|
||||
function build(
|
||||
o: StartDownloadOptions,
|
||||
d: DownloadOptions,
|
||||
access: AccessOptions = NO_ACCESS
|
||||
): string[] {
|
||||
return buildArgs(o, OUT, d, BIN, access)
|
||||
}
|
||||
|
||||
/** True when `seq` appears as a contiguous run inside `argv`. */
|
||||
@@ -48,6 +53,94 @@ describe('buildArgs scaffolding', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// --- Network: proxy / rate limit / aria2c -----------------------------------
|
||||
|
||||
describe('network options', () => {
|
||||
it('emits nothing when proxy/rateLimit are empty and aria2c is unset', () => {
|
||||
const argv = build(opts(), dlo(), NO_ACCESS)
|
||||
expect(argv).not.toContain('--proxy')
|
||||
expect(argv).not.toContain('--limit-rate')
|
||||
expect(argv).not.toContain('--downloader')
|
||||
})
|
||||
|
||||
it('emits --proxy with the trimmed value', () => {
|
||||
const argv = build(opts(), dlo(), { ...NO_ACCESS, proxy: ' socks5://127.0.0.1:1080 ' })
|
||||
expect(hasSeq(argv, '--proxy', 'socks5://127.0.0.1:1080')).toBe(true)
|
||||
})
|
||||
|
||||
it('emits --limit-rate with the trimmed value', () => {
|
||||
const argv = build(opts(), dlo(), { ...NO_ACCESS, rateLimit: ' 2M ' })
|
||||
expect(hasSeq(argv, '--limit-rate', '2M')).toBe(true)
|
||||
})
|
||||
|
||||
it('emits --downloader <path> --downloader-args when an aria2c path is given', () => {
|
||||
const argv = build(opts(), dlo(), { ...NO_ACCESS, aria2cPath: 'C:/fake/bin/aria2c.exe' })
|
||||
expect(hasSeq(argv, '--downloader', 'C:/fake/bin/aria2c.exe', '--downloader-args', ARIA2C_ARGS)).toBe(
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('omits --downloader when aria2cPath is not set', () => {
|
||||
const argv = build(opts(), dlo(), NO_ACCESS)
|
||||
expect(argv).not.toContain('--downloader-args')
|
||||
})
|
||||
})
|
||||
|
||||
// --- Cookies: browser store vs. sign-in window's exported file --------------
|
||||
|
||||
describe('cookies', () => {
|
||||
it('emits nothing when no cookie source is configured', () => {
|
||||
const argv = build(opts(), dlo(), NO_ACCESS)
|
||||
expect(argv).not.toContain('--cookies-from-browser')
|
||||
expect(argv).not.toContain('--cookies')
|
||||
})
|
||||
|
||||
it('emits --cookies-from-browser <name> when cookiesFromBrowser is set', () => {
|
||||
const argv = build(opts(), dlo(), { ...NO_ACCESS, cookiesFromBrowser: 'firefox' })
|
||||
expect(hasSeq(argv, '--cookies-from-browser', 'firefox')).toBe(true)
|
||||
expect(argv).not.toContain('--cookies')
|
||||
})
|
||||
|
||||
it('emits --cookies <path> when only cookiesFile is set', () => {
|
||||
const argv = build(opts(), dlo(), { ...NO_ACCESS, cookiesFile: 'C:/userdata/cookies.txt' })
|
||||
expect(hasSeq(argv, '--cookies', 'C:/userdata/cookies.txt')).toBe(true)
|
||||
expect(argv).not.toContain('--cookies-from-browser')
|
||||
})
|
||||
|
||||
it('prefers cookiesFromBrowser over cookiesFile when both are set', () => {
|
||||
const argv = build(opts(), dlo(), {
|
||||
...NO_ACCESS,
|
||||
cookiesFromBrowser: 'chrome',
|
||||
cookiesFile: 'C:/userdata/cookies.txt'
|
||||
})
|
||||
expect(hasSeq(argv, '--cookies-from-browser', 'chrome')).toBe(true)
|
||||
expect(argv).not.toContain('--cookies')
|
||||
})
|
||||
})
|
||||
|
||||
// --- Restrict filenames / download archive -----------------------------------
|
||||
|
||||
describe('restrictFilenames / downloadArchivePath', () => {
|
||||
it('emits --restrict-filenames when set', () => {
|
||||
expect(build(opts(), dlo(), { ...NO_ACCESS, restrictFilenames: true })).toContain(
|
||||
'--restrict-filenames'
|
||||
)
|
||||
})
|
||||
|
||||
it('omits --restrict-filenames by default', () => {
|
||||
expect(build(opts(), dlo(), NO_ACCESS)).not.toContain('--restrict-filenames')
|
||||
})
|
||||
|
||||
it('emits --download-archive <path> when a path is given', () => {
|
||||
const argv = build(opts(), dlo(), { ...NO_ACCESS, downloadArchivePath: 'C:/userdata/archive.txt' })
|
||||
expect(hasSeq(argv, '--download-archive', 'C:/userdata/archive.txt')).toBe(true)
|
||||
})
|
||||
|
||||
it('omits --download-archive when no path is given', () => {
|
||||
expect(build(opts(), dlo(), NO_ACCESS)).not.toContain('--download-archive')
|
||||
})
|
||||
})
|
||||
|
||||
// --- Audio extraction format ------------------------------------------------
|
||||
|
||||
describe('audio: -x --audio-format <fmt>', () => {
|
||||
|
||||
@@ -21,6 +21,7 @@ const RUN = process.env.AEROFETCH_REAL_DOWNLOAD === '1'
|
||||
const BIN_DIR = resolve('resources/bin')
|
||||
const YTDLP = join(BIN_DIR, 'yt-dlp.exe')
|
||||
const FFMPEG = join(BIN_DIR, 'ffmpeg.exe')
|
||||
const NO_ACCESS = { proxy: '', rateLimit: '', restrictFilenames: false }
|
||||
|
||||
interface RunResult {
|
||||
status: number | null
|
||||
@@ -105,7 +106,7 @@ describe.skipIf(!RUN)('real yt-dlp downloads (buildArgs end-to-end)', () => {
|
||||
embedThumbnail: true,
|
||||
cropThumbnail: true
|
||||
}
|
||||
const argv = buildArgs(opts, join(outDir, '%(id)s.%(ext)s'), options, BIN_DIR)
|
||||
const argv = buildArgs(opts, join(outDir, '%(id)s.%(ext)s'), options, BIN_DIR, NO_ACCESS)
|
||||
console.log('[crop] argv:', JSON.stringify(argv))
|
||||
|
||||
const res = runYtdlp(argv)
|
||||
@@ -146,7 +147,7 @@ describe.skipIf(!RUN)('real yt-dlp downloads (buildArgs end-to-end)', () => {
|
||||
sponsorBlockMode: 'remove' as const,
|
||||
sponsorBlockCategories: ['music_offtopic' as const]
|
||||
}
|
||||
const argv = buildArgs(opts, join(outDir, '%(id)s.%(ext)s'), options, BIN_DIR)
|
||||
const argv = buildArgs(opts, join(outDir, '%(id)s.%(ext)s'), options, BIN_DIR, NO_ACCESS)
|
||||
console.log('[sb] argv:', JSON.stringify(argv))
|
||||
|
||||
const res = runYtdlp(argv)
|
||||
|
||||
Reference in New Issue
Block a user