Commit Graph

12 Commits

Author SHA1 Message Date
debont80 da37690b42 feat: Phase N power-user surface (terminal, palette, per-item, sort, url-templates)
- Built-in yt-dlp terminal: src/main/terminal.ts streams raw yt-dlp runs
  (binary fixed, gated on customCommandEnabled), new TerminalView + sidebar tab
- Command palette (Ctrl/Cmd+K), portal-free CommandPalette.tsx wired in App
- Per-playlist-item editing: per-row video/audio toggle + All video/All audio
  batch; addPlaylist enqueues via addMany with per-item kind
- Weighted format sorting: DownloadOptions.formatSort -> raw -S (overrides codec)
- URL-regex template auto-matching: CommandTemplate.urlPattern + selectExtraArgs
  matchesUrl, threaded through resolveExtraArgs; field in TemplateManager

typecheck + test (192) + electron-vite build all clean. Roadmap: Phase N COMPLETE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 11:10:22 -04:00
debont80 9ac11ceb6c feat: queue UX (Phase M) + media editing (split-chapters, trim)
Phase L (media editing):
- Split by chapters (--split-chapters) as a DownloadOptions toggle
- Trim/cut by time range: parseTrimSections -> --download-sections "*A-B"
  + --force-keyframes-at-cuts (deduped vs SponsorBlock), per-download Trim panel

Phase M (queue & daily-use UX), all 7:
- Pause/resume: main-side killTree + paused flag (silent close), download:pause
  IPC, store pause/resume + paused status, QueueItem controls
- Reorder via "Download next" (prioritize)
- Aggregate progress strip (pure summarizeQueue) + combined speed/ETA
- Drag-and-drop links / .url files onto the download card
- Retry all failed
- Duplicate detection (sameVideo) with "Download anyway" guard
- Per-download scheduling (datetime-local) + save-for-later (saved status,
  15s promotion ticker); session-only (queue not persisted)

New pure modules unit-tested (queueStats); buildArgs trim/split tested.
typecheck + test green (178 passed). Roadmap updated: Phase M COMPLETE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 10:55:43 -04:00
debont80 4854fcc947 feat: self-managing yt-dlp + ffmpeg version display
yt-dlp is now a managed, auto-updating binary rather than a static bundled
one. On launch AeroFetch seeds a writable copy under userData from the
bundled seed, self-heals it if it goes missing, and — throttled to once a
day — self-updates it to the configured channel (default: nightly). This
keeps the binary current so YouTube changes don't silently cause 403s, and
an app reinstall (or portable re-extraction) can no longer roll it back.
Settings shows an auto-update toggle, the live version, last-checked time,
and the channel selector.

Also surface the bundled ffmpeg/ffprobe versions in Settings (display only:
they have no self-update path and, unlike yt-dlp, don't break as sites
change, so there is no auto-update for them).

- binaries: split the read-only bundled seed from the managed userData copy
- ytdlp: ensureManagedYtdlp (seed + self-heal), startup auto-update runner
- ytdlpPolicy: pure once-a-day throttle decision (unit-tested)
- ffmpeg: parse ffmpeg/ffprobe -version for the Settings panel
- settings/ipc/preload/renderer: new settings, IPC channels, types, and UI

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 07:27:43 -04:00
debont80 fa92383ef4 Verify update installer SHA-256; harden download teardown
Integrity + defence-in-depth for the in-app updater:

- Require and verify a SHA-256 checksum before running an installer. Each
  release must attach `<installer>.sha256`; downloadAppUpdate fetches it from
  the host-pinned origin (deriving the URL itself, not trusting the renderer),
  hashes the stream as it writes, and refuses to run on mismatch/missing/
  unparseable. Fails closed. NB: same-host hash is integrity, not protection
  against a fully compromised host — only Authenticode signing covers that.
- fetchTrustedText helper GETs the checksum with the same per-hop host
  re-validation as the installer download, plus a size cap and timeout.
- finish() is now the single teardown point and aborts the request on failure,
  so a write error can't leave Electron pulling bytes into a dead stream;
  removed three now-redundant explicit abort() calls.
- Clear the idle/stall timer once the body is fully received.
- Export isTrustedDownloadUrl/compareVersions, extract extractSha256, and add
  14 unit tests (host pin incl. userinfo spoof + wrong port, checksum parsing
  incl. sha512 non-slice, prerelease never outranking its release).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 05:38:02 -04:00
debont80 3536626a8a security: harden command exec, IPC, deep-link, cookies, and persistence
Seven-tier security audit of the main process, each finding fixed with a
regression test. Typecheck (node + web) clean; unit tests 106 -> 140.

- Tier 1 (command exec/argv): allowlist the yt-dlp --update-to channel
  (blocks arbitrary-binary-install RCE); gate per-download extraArgs behind
  the customCommandEnabled consent flag in main (blocks --exec RCE); resolve
  taskkill/schtasks by absolute System32 path; validate per-download
  outputDir; normalize the URL in assertHttpUrl and use it at every spawn.
- Tier 2 (input validation): fix isSafeFilenameTemplate drive-relative
  ('C:foo') and Windows dotted-'..' traversal bypasses; percent-encode the
  untrusted id in entryUrl; catch reserved device names with extensions in
  sanitizeDirSegment.
- Tier 3 (fs/backup): drop malformed template rows in importBackup;
  normalize deep-link URLs via assertHttpUrl.
- Tier 4 (cookies): confine the sign-in window's navigations/popups to web
  URLs (recursively) and deny all web permissions on its session.
- Tier 5 (deep-link/argv): bound the .url file read to 64 KB; match the
  aerofetch:// scheme case-insensitively.
- Tier 6 (Electron window): deny camera/mic/geolocation/USB/HID/serial/
  Bluetooth permissions on the app window.
- Tier 7 (network/persistence): restrict the watched-source RSS fetch to
  youtube.com feed URLs (SSRF guard); complete isValidSource validation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 08:04:19 -04:00
debont80 47da3e6746 Add Pinchflat-style media manager: index channels into playlist folders
Index an entire YouTube channel or playlist once, then download it into
organized <Channel>/<Playlist>/<NNN> - <Title> folders, with incremental
re-sync. Built in six rebuild-gated phases (F-K); see ROADMAP-PINCHFLAT.md.

- F: channel-walk indexer (/playlists + /videos) -> persisted Source +
  MediaItem JSON stores; pure classify/dedup logic in indexerCore.ts
- G: Windows-safe folder paths + dir sanitizer (collectionOutputTemplate)
- H: Library tab + source tree + "Download pending" into the existing queue
- I: state-preserving re-index merge + persist-downloaded-on-complete
- J: watched sources, RSS fast-check, Task Scheduler + --sync launch
  (the OS-level scheduling/RSS need a real-install smoke test)
- K: .info.json / thumbnail / .description sidecars for media servers

Also gitignore .gitea-token. 106 unit tests pass; typecheck + build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 20:50:13 -04:00
debont80 18e1d9a24d Bundle ffprobe.exe and complete the real-download smoke-test pass
A live smoke test of buildArgs' argv against the bundled yt-dlp + ffmpeg found
that ffprobe.exe was never bundled (resources/bin/ shipped only ffmpeg.exe).
yt-dlp resolves both from --ffmpeg-location, so duration-aware post-processing
failed at runtime for every user with "ffprobe not found": --sponsorblock-remove,
--force-keyframes-at-cuts, and --split-chapters (CODE-AUDIT C1).

- Bundle ffprobe.exe (matching n8.1.2 LGPL build, sha256-verified against the
  already-bundled ffmpeg.exe) and document it in resources/bin/README.md as a
  required binary; correct the stale "not committed to git" note.
- Guard startDownload against a missing ffmpeg.exe/ffprobe.exe up front so the
  failure is a clear AeroFetch error, not a cryptic yt-dlp postprocessing one
  (new getFfprobePath() in binaries.ts).
- Expand test/real-download.integration.test.ts from 2 to 8 live cases: crop,
  sponsorblock-remove, audio opus re-encode, mkv+vp9 merge, subtitle+chapter
  embed, restrict-filenames, download-archive skip, and Phase C extra-args.
  All 8 pass against live yt-dlp + ffmpeg.
- ROADMAP: mark the Phase A/B/C smoke-test caveats done. CODE-AUDIT: add C1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 10:57:12 -04:00
debont80 b08d76a986 Apply all CODE-AUDIT.md findings (S1-S5, P1-P3, M1-M3)
Security:
- S1 (backup.ts): require explicit confirmation before enabling custom-command
  templates from a backup file; import templates in a disabled state if declined
- S2 (cookies.ts): deny non-http/https popups in cookie login window, matching
  the main window's setWindowOpenHandler defence
- S3 (download.ts): main-process concurrency cap — startDownload now rejects
  spawns when active.size >= maxConcurrent (defence-in-depth; renderer already
  enforces this but should not be the only gate)
- S4 (settings.ts, validation.ts): reject filenameTemplate values containing
  path traversal (.. segments or absolute paths); validate outputDir is absolute
- S5 (history.ts, errorlog.ts, templates.ts, validation.ts): per-field
  validation on JSON reads — invalid entries dropped rather than blindly trusted

Performance:
- P1 (settings.ts): getSettings() now only writes to disk when sanitization
  actually changed a value; removes synchronous file I/O on every hot-path read
- P2 (ipc.ts, download.ts, downloads.ts): renderer forwards its pre-probed
  metadata (title/channel/duration) via StartDownloadOptions.meta; main uses
  it directly instead of spawning a redundant second yt-dlp probe

Maintainability:
- M1 (log.ts, download.ts, probe.ts): extract duplicated cleanError() to
  src/main/log.ts; both callers import from the shared module
- M2 (buildArgs.ts): document parseExtraArgs escape-sequence limitations
- M3 (electron-builder.yml): clarify icon TODO as a pre-release action
- P3 (downloads.ts): document that lowering maxConcurrent mid-flight does
  not pause active downloads (intended behaviour, now written down)

Tests:
- Add test/validation.test.ts covering isSafeFilenameTemplate, isSafeOutputDir,
  isValidHistoryEntry, isValidErrorLogEntry, isTemplateLike (76 tests pass)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 10:22:43 -04:00
debont80 fa78b13cac Complete Phase E theming, onboarding, and Windows share integration
Adds theme presets (Toffee/Slate/Evergreen/Lavender) with a "follow system"
mode and high-contrast awareness, a first-run welcome screen, and the
Windows analog of Android's share sheet for a Win32 app: an aerofetch://
protocol handler plus an Explorer "Send to AeroFetch" entry, both routed
through a single-instance lock so a second launch hands its link to the
already-running window instead of opening a duplicate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 08:45:09 -04:00
debont80 a822a2bb52 Complete Phase C (custom commands & yt-dlp updater) and Phase D (library, UX & notifications)
Phase C had been implemented in the working tree but never committed:
named custom-command templates (CRUD + per-download override), command
preview, and the in-app yt-dlp self-updater.

Phase D adds: history search/kind-filter/multi-select/bulk-delete/
re-download; native OS notifications on completion/failure; a private/
incognito download mode that skips history; settings+template backup/
restore via JSON; and a persistent error log surfaced as a Diagnostics
report in Settings.

See ROADMAP.md for the full per-item breakdown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 06:19:45 -04:00
debont80 67133f13b5 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>
2026-06-22 20:53:18 -04:00
debont80 26cd7fc7b0 Add a pure, unit-tested buildArgs() module
Extracts yt-dlp argv construction out of download.ts into its own
electron-free module (src/main/buildArgs.ts) so it can be exercised by
vitest without spinning up Electron, plus a real-download integration
test (skipped by default; opt in with AEROFETCH_REAL_DOWNLOAD=1) that
spawns the bundled yt-dlp.exe/ffmpeg.exe against its argv. download.ts
isn't wired up to it yet — that lands with the next commit.

This was done in an earlier local session but never actually committed,
even though download.ts has imported from it since the Phase A commit
(bb5dd6c) — the build had been relying on an uncommitted local file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 20:52:23 -04:00