Commit Graph

37 Commits

Author SHA1 Message Date
Mattias Tall
ffd46b5e08 Fix missing view_count column migration on startup
create_all doesn't add columns to existing tables. Add _add_column_if_missing
helper that checks PRAGMA table_info and runs ALTER TABLE if needed, called
on every startup before FTS setup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:20:40 +02:00
Mattias Tall
8221177615 Add view count to videos
- Video model: view_count column (Integer, nullable)
- ytdlp._normalize_video: extract view_count from yt-dlp info
- _VIDEO_SELECT: include v.view_count in all queries
- VideoDetail schema: view_count field
- Watch page: formatViews() helper, show "X.XM views" in meta row
  alongside date and category

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:18:53 +02:00
Mattias Tall
cdf6520fd8 Add lazy comment fetching to watch page
- VideoComment model (video_id, author, text, likes, is_pinned, published_at)
- fetch_video_comments() in ytdlp.py: top 20 comments, no reply threads,
  sorted pinned-first then by likes
- GET /videos/by-yt/{id}/comments — returns cached comments instantly
- POST /videos/by-yt/{id}/comments/refresh — fetches from YouTube, stores, returns
- Watch page: CommentsSection shows "Load comments" button when uncached,
  renders comments with author/likes once loaded; Refresh link to re-fetch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:15:41 +02:00
Mattias Tall
d6dd07e0bd Add delete button to taste profile tags in Stats
- Backend: DELETE /stats/taste/{tag} removes the row from user_tag_affinity
- API: deleteTasteTag(tag) helper
- Stats UI: × button on each tag chip, faint by default, full opacity on hover;
  invalidates stats query so the tag disappears immediately

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:07:32 +02:00
Mattias Tall
a4cd32da4a Add PWA support and Google Takeout subscription import
- manifest.json + icon.svg for installability on mobile (standalone mode)
- index.html: theme-color, apple-mobile-web-app meta tags, manifest link
- Settings: Import CSV section reads Google Takeout subscriptions.csv,
  extracts UC... channel IDs, calls follow-bulk to follow them all at once

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:04:42 +02:00
Mattias Tall
219a388d72 Channel page: overlay avatar/name/buttons on banner
Name, subscriber count, and action buttons sit at the bottom of the
banner with a gradient overlay. Falls back to a plain dark header when
no banner is available. Description moves below the header.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 10:58:31 +02:00
Mattias Tall
426e85c2c9 Mobile nav, channel banners, search channel linking
- Add bottom tab bar (Home/Following/Discover/Downloads/Settings) for mobile
- Fetch and display channel banner images on channel pages
- Fix ChannelCard: channels without a local DB id now follow+navigate on click

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 10:55:25 +02:00
Mattias Tall
ecb246ed99 Add yt-dlp diagnostic button to Settings page
Shows node version, yt-dlp version, cookie args, and raw stderr tail
to diagnose download/metadata failures without needing shell access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 10:45:28 +02:00
Mattias Tall
a33491fee0 Fix login: don't intercept 401 on auth/login itself
A failed login attempt was triggering the global 401 interceptor which
silently redirected back to /login, making the form appear broken.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 10:40:33 +02:00
Mattias Tall
fb2e42051e Fix Watch page crash: import getQueue
getQueue was called on the watch page but missing from the import list,
causing a ReferenceError that broke the entire /watch/:id route.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 10:23:24 +02:00
Mattias Tall
ba03245449 Fix EJS: install yt-dlp[default] and enable --js-runtimes node
yt-dlp's EJS (External JavaScript Solver) needs two things:
1. The solver scripts — only bundled with yt-dlp[default], not bare yt-dlp
2. An explicit --js-runtimes flag — Node.js is not the default (Deno is)

Both are now set: pip installs the [default] extras, and /etc/yt-dlp.conf
sets --js-runtimes node globally so every yt-dlp call uses it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 10:19:00 +02:00
Mattias Tall
050caead54 Install Node.js via nodeenv (pip); use web client
nodeenv is a Python package that downloads a pre-built Node.js binary —
no apt repos, no compilation, guaranteed to work in python:3.12-slim.
The 'node' binary is linked into /usr/local/bin so yt-dlp can find it.

With Node.js available the web client works fully (37 formats) and can
solve YouTube's n-challenge that every other approach was failing on.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 10:16:10 +02:00
Mattias Tall
53ea64ee8a Switch to web_embedded player client
web_embedded: supports cookies, no Node.js/JS runtime needed, 23 video
formats available. android_vr was skipped by yt-dlp when cookies are
present since that client doesn't support cookie auth.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 10:12:32 +02:00
Mattias Tall
b58dc26bd4 Switch to android_vr player client — no Node.js required
android_vr provides pre-signed format URLs that bypass YouTube's
n-challenge and signature JS requirements entirely. Tested: 23 video
formats available without any JavaScript runtime installed.

Reverts Node.js Dockerfile addition (which failed to build anyway).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 10:10:58 +02:00
Mattias Tall
a006bf08bc Add node symlink to Dockerfile; expand ytdlp-test diagnostics
Debian installs nodejs as /usr/bin/nodejs but yt-dlp looks for 'node'.
The symlink ensures yt-dlp can find the runtime.

Diagnostics now report node path/version and yt-dlp version so we can
verify the environment without shelling into the container.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 10:06:53 +02:00
Mattias Tall
299338ff80 Fix double _cookie_args() call in download; fix diagnostic player client
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 10:01:24 +02:00
Mattias Tall
3e439f2d3a Add Node.js to Docker image; use web player client only
Node.js is required by yt-dlp to solve YouTube's n-challenge (format URL
deobfuscation). Without it the web client returns no video formats.

The tv and ios player clients were removed — both require GVS PO tokens
that we don't have, so they only produce warnings and block every request.
The web client with Node.js installed gives 30+ formats and works cleanly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 09:57:02 +02:00
Mattias Tall
98d986cd95 Fix cookie fallback breaking yt-dlp in Docker; add OAuth2 auth flow
- _cookie_args() no longer falls through to --cookies-from-browser when
  cookies_file is configured but missing. Firefox isn't installed in the
  Docker image, so that fallback caused yt-dlp to exit with empty stdout
  and every metadata fetch to return "Video not found on YouTube".
- fetch_video_metadata() now retries without auth args if the first call
  fails, so a broken cookie config can't block public video fetches.
- Add use_oauth2 setting + full device-auth flow (POST /settings/oauth2-init,
  GET /settings/oauth2-status) with OAuth2Section UI in Settings page.
- Add GET /settings/ytdlp-test diagnostics endpoint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 09:53:02 +02:00
inputnoise
b3284b35da Set yt-dlp cache dir to /data/yt-dlp-cache for persistent OAuth tokens
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:56:41 +02:00
inputnoise
32af6c1c49 Try tv player client first to bypass datacenter IP bot detection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:45:05 +02:00
inputnoise
4ab8245a93 Debug: log fetch_video_metadata cookie args and errors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:42:02 +02:00
inputnoise
8c291f5c2d Hardcode download bind mount directly to /mnt/serverdata/youtube
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:31:40 +02:00
inputnoise
9487261e11 Hardcode download volume path to /mnt/serverdata/youtube
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:30:36 +02:00
inputnoise
28441ca726 Use named volume for downloads so DOWNLOAD_PATH from stack.env is respected on every redeploy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:28:47 +02:00
inputnoise
a09f8ac5c2 Use print() for cookie debug log so it shows in container logs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:25:39 +02:00
inputnoise
d50ccf399f Log cookie args on download start for debugging 2026-05-25 21:19:26 +02:00
inputnoise
a2a84d2c04 Use iOS player client to bypass YouTube bot detection (fixes 'only images available') 2026-05-25 21:04:24 +02:00
inputnoise
5f5ca52b95 Add cookies.txt upload UI — drag/drop or click to upload, stored in data volume 2026-05-25 21:01:02 +02:00
inputnoise
56dd5f8360 Add cookies file support for Docker; auto-detect /data/cookies.txt 2026-05-25 20:57:04 +02:00
inputnoise
bcc425b6fb Fix volume permissions: entrypoint chowns /data to uid 1000, run app as non-root 2026-05-25 20:50:10 +02:00
inputnoise
03b10b6f86 Always install latest yt-dlp on build
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:38:25 +02:00
inputnoise
2a2ba0f41c Proxy /files/ through nginx to backend for video playback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:32:16 +02:00
inputnoise
1a5d074de4 Remove exposed backend port — only frontend proxy needed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:27:45 +02:00
inputnoise
a652f6e7a8 Add stack.env for Portainer repository deployment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:26:33 +02:00
inputnoise
a3b73fbf72 Remove unused JELLYFIN_URL config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:25:08 +02:00
inputnoise
7194ec45ec Remove ALLOW_REGISTRATION env var — managed via admin UI instead
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:16:15 +02:00
inputnoise
1827dd6c4e Initial commit — YT Hub
Self-hosted personal YouTube management app.
FastAPI + SQLite backend, React + Vite + Tailwind frontend.
Dockerfiles and compose included for Portainer deployment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:09:04 +02:00