Restore the previous fixed-tag classification flow, including startup backfill for existing videos and the 91porn spider tag.
Also commit the current drive scanning, preview scheduling, and admin drive-control updates present in the workspace.
- Promote systemd to the primary 运行 method (production / long-running)
- Provide complete unit files for backend (compiled binary) and frontend (vite preview)
- Add daily ops cheatsheet: status / restart / journalctl / rebuild flows
- Keep start.sh as a local-dev / fallback path (方式 B)
- Warn against mixing start.sh with systemd to avoid port/process contention
When PikPak's cached captcha_token expires, Init() and runtime API
calls used to fail permanently with error_code=4002, leaving the drive
un-attached and blocking spider91 -> PikPak migration.
- refreshCaptchaToken: on 4002, clear cached token and retry once with
empty captcha_token so the server issues a fresh one. Covers the
driver-attach path during server startup.
- requestOnce: extend captcha-refresh-and-retry path from case 9 to also
cover case 4002, clearing cache before refresh to avoid sending the
same expired token again. Covers per-API-call recovery at runtime.
- Add captcha_recovery_test.go covering: recovery on 4002, no-loop
guard when token already empty, request-level recovery, and
single-retry boundary.
OpenList's upstream PikPak driver does not currently handle 4002 either,
so this is a strict improvement.
Previously corsMiddleware reflected any Origin back into
Access-Control-Allow-Origin while emitting Allow-Credentials: true.
Combined with no CSRF token, this let any third-party site read or
write authenticated APIs cross-origin (full session takeover via
chained requests to /admin/api/drives etc).
Changes:
- config.Server.AllowedOrigins []string (default empty = same-origin only)
- corsMiddleware now only emits CORS headers for whitelisted Origins;
unknown origins receive no Allow-Origin and 403 on preflight
- '*' entries are silently dropped to prevent regression
- Always set Vary: Origin to keep caches honest
- Drop the originOr() helper, no longer needed
- Add cors_test.go covering allow / reject / preflight / wildcard cases
Same-origin deployments (nginx fronting / and /api on the same domain)
keep working with no config change. Cross-origin deployments must add
their frontend Origin to server.allowed_origins.
VideoPlayer:
- Long-press the video for >=400ms to enter 2x playback rate; release/pause/leave/src change restores 1x
- Block native context menu, iOS long-press callout, and download UI on the player
Shorts page (/shorts) reachable from the main nav burger menu:
- Vertical scroll-snap feed (one video per 100svh slide)
- IntersectionObserver picks the active slide; only active +/- 1 mounts a real <video>
- Default muted autoplay with mute toggle; tap toggles play/pause
- Long-press 2x carries over from the detail player
- Per-slide TikTok-style scrub bar: hidden line by default, drag from the bottom hit area to seek with live MM:SS readout
- Auto-fullscreen on first user pointer (Android Chrome/Firefox/Edge); falls back gracefully on iOS Safari which doesn't support element-level Fullscreen API
- Body overflow lock + dynamic theme-color=#000 while on the page
- Like via double-tap or the heart action button; tap again on the heart to unlike, count synced with the existing detail-page likes
- Heart-burst animation; right-rail action stack ready for future buttons
Backend:
- POST /api/shorts/next: client posts { seenIds, count }, server returns up to N videos that aren't in seenIds, picked via SQLite ORDER BY RANDOM(); roundComplete=true tells client to clear local seen list and start a new round
- DELETE /api/video/:id/like: decrement likes (clamped at 0) for unlike
- catalog.RandomVideosExcluding / CountVisibleVideos / DecrementLike with unit tests
Misc:
- index.html viewport gains viewport-fit=cover for safe-area handling
Final decision after evaluating three approaches:
- VLC external player with vlc:// scheme: poor UX, protocol unreliable
- ffprobe + smart remux/transcode: 2-core box gets pinned by ffmpeg
- All-302: simplest and least resource intensive
Removed:
- /p/transcode/{id} routes and full ffmpeg pipeline
- /api/play-token + /p/play VLC bridge
- Server.FFmpegPath/FFprobePath/transcodeJobs fields
- needsBrowserTranscode helper
- VLC button + modal in VideoActions, related CSS
- VideoPlayer transcode polling
videoSource now returns:
- /p/upload/<id> for local uploads
- /p/stream/<driveID>/<fileID> for everything else (302 to CDN)
Trade-off: mkv/avi can no longer be played natively in <video>;
documented in plan section 14.7/14.8 as known limitation.
- Add a global site-wide theme that switches between two palettes:
- dark + warm orange (existing visual, default)
- cream white + sakura pink (new, soft cream bg + pink accent +
deep mauve text + soft pink shadows)
- All colors live in tokens.css under [data-theme="dark"] and
[data-theme="pink"]; component CSS is unchanged
- Backend: persist theme in SQLite settings table (key=ui.theme),
expose via Admin Settings PUT/GET and a new public read-only
endpoint GET /api/settings/theme so the login page can pick up
the right theme before the user authenticates
- Frontend: inline script in index.html applies cached theme from
localStorage before React mounts to prevent first-paint flash;
main.tsx fires syncThemeFromServer() in parallel to align with
server value; theme.ts validates input and ignores unknown values
to be robust against an old backend not returning the theme field
- Admin: new /admin/theme page with two large preview cards (mini
page mock-ups locked to each palette via data-preview), Palette
icon entry in the sidebar; clicking a card applies locally first,
then PUTs to the backend with rollback on failure
- README + plan section 14.6 updated