bb5dd6c438
Phase A of the Seal feature-parity roadmap: configurable post-processing via a shared DownloadOptions model (src/shared/ipc.ts), editable as persisted defaults in Settings and overridable per-download in the download bar. - audio formats (mp3/m4a/opus/flac/wav/aac), video container (mp4/mkv/webm), preferred-codec sort (-S vcodec) - subtitles (download/embed/langs/auto), SponsorBlock (remove/mark + categories + force-keyframes-at-cuts), embed chapters/metadata/thumbnail, square-crop audio artwork - playlist downloads: probe via `-J --flat-playlist`, inline selection UI (checkbox list, select-all/none, live count); each entry enqueued as its own single-video download, reusing the existing queue/concurrency/history - reusable DownloadOptionsForm shared by Settings and the download bar so the defaults and per-download override never drift - ROADMAP.md tracking full Seal parity (phases A-E) Also includes in-tree security hardening that landed alongside: preload sandbox (CJS preload output), http(s)-only window-open + blocked renderer navigation, argv-injection guard for URLs (src/main/url.ts), extension-allowlisted file open/reveal (src/main/reveal.ts), yt-dlp version-check timeout, and a minimal window.electron presence marker. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
43 lines
1.5 KiB
TypeScript
43 lines
1.5 KiB
TypeScript
import { shell } from 'electron'
|
|
import { existsSync, statSync } from 'fs'
|
|
import { extname, isAbsolute } from 'path'
|
|
|
|
/**
|
|
* Open/reveal helpers used by the shell:* IPC handlers.
|
|
*
|
|
* The renderer supplies the path (it originates from yt-dlp's after-move print),
|
|
* but the IPC boundary must not trust it blindly: a compromised renderer could
|
|
* otherwise call openPath() on an arbitrary executable and have the OS run it.
|
|
* So openPath is confined to existing files with a known media extension —
|
|
* never .exe/.bat/.ps1/etc.
|
|
*/
|
|
const OPENABLE_EXTENSIONS = new Set([
|
|
// video
|
|
'.mp4', '.mkv', '.webm', '.mov', '.avi', '.flv', '.ts', '.m4v', '.3gp', '.ogv',
|
|
// audio
|
|
'.mp3', '.m4a', '.opus', '.ogg', '.oga', '.aac', '.flac', '.wav', '.wma',
|
|
// subtitle sidecars (plain text — safe to open)
|
|
'.vtt', '.srt'
|
|
])
|
|
|
|
/** Open a downloaded media file with its default app. Returns '' on success,
|
|
* or an error string (matching shell.openPath's contract). */
|
|
export async function safeOpenPath(p: unknown): Promise<string> {
|
|
if (typeof p !== 'string' || !isAbsolute(p)) return 'Invalid path.'
|
|
if (!OPENABLE_EXTENSIONS.has(extname(p).toLowerCase())) {
|
|
return 'Refusing to open this file type.'
|
|
}
|
|
try {
|
|
if (!statSync(p).isFile()) return 'Not a file.'
|
|
} catch {
|
|
return 'File not found.'
|
|
}
|
|
return shell.openPath(p)
|
|
}
|
|
|
|
/** Reveal a path in the OS file manager. No-op for missing/invalid paths. */
|
|
export function safeShowInFolder(p: unknown): void {
|
|
if (typeof p !== 'string' || !isAbsolute(p) || !existsSync(p)) return
|
|
shell.showItemInFolder(p)
|
|
}
|