diff --git a/.zscripts/dev.pid b/.zscripts/dev.pid index 6e64a91..1e3b283 100755 --- a/.zscripts/dev.pid +++ b/.zscripts/dev.pid @@ -1 +1 @@ -1173 +1195 diff --git a/agent-ctx/2-a-music-services-framework.md b/agent-ctx/2-a-music-services-framework.md new file mode 100755 index 0000000..c5c2eb1 --- /dev/null +++ b/agent-ctx/2-a-music-services-framework.md @@ -0,0 +1,105 @@ +# Task 2-a — v6.7 part A (server): resolve anti-spam semaphore + music-service framework + +Task ID: 2-a +Agent: full-stack-developer +Status: COMPLETE — lint clean, tsc clean on all four files, verified live +against the dev server (curl) and at the process level (ps snapshots). + +## Scope (ONLY these files — nothing else touched) + +1. `src/lib/music-services.ts` (modified) +2. `src/lib/server/music-providers.ts` (NEW) +3. `src/app/api/resolve/route.ts` (modified) +4. `src/components/ava/settings-tab.tsx` (modified — Music services chips only) + +## What was built + +### 1. `src/lib/music-services.ts` +- `export type StreamStatus = 'live' | 'coming-soon'` +- `ServiceDef` gained required `streamStatus: StreamStatus` + optional + `comingSoonNote?: string` (required when coming-soon). +- youtube / soundcloud → `'live'`; spotify / apple → `'coming-soon'` with + notes like: "Spotify playback is coming in a later update — your saved + Spotify login will be used automatically the moment it ships. Meanwhile, + YouTube, SoundCloud and direct-file links work today." +- All pre-existing fields/exports intact (`SERVICES`, `serviceDef`, + `serviceForUrl`, `isServiceId`, `ServiceId`). + +### 2. `src/lib/server/music-providers.ts` — exact exports +```ts +export type ProviderStatus = 'live' | 'coming-soon' + +export interface ProviderAdapter { + id: ServiceId | 'generic' + label: string + status: ProviderStatus + comingSoonNote: string + handlesUrl(url: string): boolean + // commented v6.8+ extension points: + // resolveTrack?(url, auth) / listPlaylist?(url, auth) +} + +export const YTDLP_PROVIDER: ProviderAdapter // id 'generic', live, handlesUrl → true +export const SPOTIFY_PROVIDER: ProviderAdapter // id 'spotify', coming-soon +export const APPLE_PROVIDER: ProviderAdapter // id 'apple', coming-soon +export function providerForUrl(url: string): ProviderAdapter +``` +- Domain matching mirrors `serviceForUrl` (host/subdomain, "www." ignored) + scoped to the one service. +- `providerForUrl` is status-agnostic: flip an adapter to 'live' and it + still owns its URLs at the route's dispatch point. +- Big "HOW TO SHIP A NEW SERVICE (v6.8+)" header comment: 3-step recipe + (implement resolveTrack/listPlaylist with the exact payload shapes → + flip streamStatus in music-services.ts + status here → extend the marked + dispatch spot in the route). + +### 3. `src/app/api/resolve/route.ts` +- **Coming-soon dispatch** right after `assertPublicHttpUrl`, BEFORE + cache/direct-audio/yt-dlp: + `{ ok: false, code: 'coming-soon', service: provider.id, error: provider.comingSoonNote }` (422). + Spotify/Apple links never spawn yt-dlp. Generic "That site is not + supported" 422 kept but without the DRM parenthetical. +- **Semaphore**: `YTDLP_MAX_CONCURRENCY = 2`, `YTDLP_QUEUE_TIMEOUT_MS = 40_000`, + class `YtdlpSemaphore` on globalThis (`__avaYtdlpSemaphore`). FIFO + waiters, direct slot handoff in `release()` (active count unchanged). + Acquire wrapped around `runYtDlp` with a `held` flag → release exactly + once in the finally; busy → 503 "The resolver is busy right now — try + again in a moment."; timed-out waiter that was granted a slot in the same + tick does NOT double-release (queue membership is the single source of + truth). Cache hits + direct-file links bypass the gate. +- Fixed a pre-existing strict-type bug in `buildCookieParts` + (`pairs: string[]` receiving `[string,string]` tuples → now + `[string, string][]`; no runtime change). + +### 4. `src/components/ava/settings-tab.tsx` +- Coming-soon service chips get an amber "soon" badge: + `soon` + (placed between name and the check/— marker). Nothing else changed; + login buttons fully functional for coming-soon services. + +## Verification +- `bun run lint` clean; `bunx tsc --noEmit` clean for all four files. +- Live: spotify track/playlist + apple album → 422 coming-soon in ~6ms + (zero yt-dlp); SSRF gate still first (localhost → 400); direct MP3 → 200; + spotify+cookies → coming-soon before cookie staging; real YouTube resolve + still passes through (bot-wall 422 as before). +- End-to-end anti-spam: 6 concurrent resolves of the same YouTube link — + ps snapshots show ≤2 top-level `bin/yt-dlp` processes at any moment + (extra PIDs = the onefile binary's own self-exec children, not extra + extractions); FIFO waves of 2; zero 503s; dev.log error-free. +- Scratch semaphore tests (cap/FIFO/timeout/handoff/no-leak) all pass; + scratch dir deleted. + +## Notes for the lead agent +- **apple.com is broad**: the apple ServiceDef domains include `apple.com`, + so ALL apple.com links (e.g. podcasts.apple.com, which yt-dlp's + ApplePodcasts extractor could handle) now get the Apple Music coming-soon + answer. Consistent with client-side matching; say the word to scope the + adapter to music.apple.com only. +- Worklog has no v6.6 section (the v6.6 music-login files were committed + inside the v6.5 commit 8fa0eb9); my entry follows v6.5 directly. +- Not-mine tsc errors (untouched): examples/websocket/* (missing deps), + api/rust-scaffold (Uint8Array vs BodyInit), ava/audio-source.ts ('never' + narrowing — the concurrent client agent's WIP). +- Did NOT commit; did NOT touch version.ts / page.tsx / audio.ts / queue + files / link-dialog / audio-source / queue-bar. diff --git a/bin/deno b/bin/deno new file mode 100755 index 0000000..09ccfb7 Binary files /dev/null and b/bin/deno differ diff --git a/bin/yt-dlp b/bin/yt-dlp index a385dc1..83a0d2c 100755 Binary files a/bin/yt-dlp and b/bin/yt-dlp differ diff --git a/db/custom.db b/db/custom.db index aed7f07..f498a50 100755 Binary files a/db/custom.db and b/db/custom.db differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100755 index 0000000..802a6f1 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/demo-track.mp3 b/public/demo-track.mp3 new file mode 100755 index 0000000..dd14078 Binary files /dev/null and b/public/demo-track.mp3 differ diff --git a/public/favicon-16.png b/public/favicon-16.png new file mode 100755 index 0000000..de81975 Binary files /dev/null and b/public/favicon-16.png differ diff --git a/public/favicon-32.png b/public/favicon-32.png new file mode 100755 index 0000000..ea6148a Binary files /dev/null and b/public/favicon-32.png differ diff --git a/public/logo.svg b/public/logo.svg index d93ac8f..00e6cb3 100755 --- a/public/logo.svg +++ b/public/logo.svg @@ -1,29 +1,12 @@ - - - - - - - - - - - - - - + + OVE — Open Visualization Engine + + + diff --git a/public/og-image.jpg b/public/og-image.jpg new file mode 100755 index 0000000..ed0e8a6 Binary files /dev/null and b/public/og-image.jpg differ diff --git a/secrets/house-youtube-cookies.txt b/secrets/house-youtube-cookies.txt new file mode 100755 index 0000000..f924ca6 --- /dev/null +++ b/secrets/house-youtube-cookies.txt @@ -0,0 +1,49 @@ +# Netscape HTTP Cookie File +# This file is generated by yt-dlp. Do not edit. + +www.google.com FALSE /recaptcha TRUE 1808687573 _GRECAPTCHA 09AKhCRwiT1-eLwOwaLcFwkJlDCOvrfctf5tsleUgIESjud7kzj-VQQ1lebmDMm57NTJHMlFVl3pt8HEhKvP-HrKM +.youtube.com TRUE / TRUE 1808809698 __Secure-YNID CvABCAUSogEBDB6PDNM4lSTL1fNjvHhlJr6rUsMazeqGK4GnVIGw8Y9-1IaaTzG4xMYqDTp7ZQDCiOU4ja7PJFkLs5dyGICIh4mtR1-7hYM0p6tgJLIJS59qkDH2n4kctfCpEU8yIPqRRFbnf_ASyhnp358K7yiogbiukJK2GCIHcBwD3EPJpLjqSXNvW43WZ1JGO34LV1fLDuOkYdVSAyNQ1jcYqGsFkqIoBTJFARxY9Wp8CqmEo-J1q5StRZXXkrwaf3R8rL5BNxRjv52YkTuQF9qrnbw3B01oxyNluUkk8LkcIHPTrFA7VKfXgEBNaM8C +.youtube.com TRUE / TRUE 0 YSC wJcQpiA7MFI +.youtube.com TRUE / TRUE 1808809757 VISITOR_INFO1_LIVE xwM2uudtLPY +.youtube.com TRUE / TRUE 1808809757 VISITOR_PRIVACY_METADATA CgJVUxIEGgAgEg%3D%3D +.youtube.com TRUE / FALSE 0 PREF f4=4000000&f6=40000000&tz=UTC&hl=en +.youtube.com TRUE / TRUE 1824671657 __Secure-1PSIDTS sidts-CjQBkldj_8-zeoTpyxBBXFMCgRR9qjPtqXfR-AAPv8VdkpM_1x1QaswtzUbaALHmihy8PR6VEAA +.youtube.com TRUE / TRUE 1824671657 __Secure-3PSIDTS sidts-CjQBkldj_8-zeoTpyxBBXFMCgRR9qjPtqXfR-AAPv8VdkpM_1x1QaswtzUbaALHmihy8PR6VEAA +.youtube.com TRUE / FALSE 1827695657 HSID AHlWy3lOE0sa6Py0E +.youtube.com TRUE / TRUE 1827695657 SSID AUIZ5QzBYjVQUn6tB +.youtube.com TRUE / FALSE 1827695657 APISID PrsWDyK9QoAfY-Fv/A_DU6Ntj541EzC59t +.youtube.com TRUE / TRUE 1827695657 SAPISID qShyTwi4D6RAWqup/A9cVfuudF_AUU8fCi +.youtube.com TRUE / TRUE 1827695657 __Secure-1PAPISID qShyTwi4D6RAWqup/A9cVfuudF_AUU8fCi +.youtube.com TRUE / TRUE 1827695657 __Secure-3PAPISID qShyTwi4D6RAWqup/A9cVfuudF_AUU8fCi +.youtube.com TRUE / FALSE 1827695657 SID g.a000EAmiU2hM7FuD6v63bTop3LlKx764pFf1t4tnmCs-R8ZiKwUtjiMkG7YgtJw5b2BBETp3ZQACgYKAUcSARcSFQHGX2MiFNswjWtYxSdRiBvSpq9YRxoVAUF8yKqHjReqCyQLK_r14tW462do0076 +.youtube.com TRUE / TRUE 1827695657 __Secure-1PSID g.a000EAmiU2hM7FuD6v63bTop3LlKx764pFf1t4tnmCs-R8ZiKwUt-OO1YThhWv8hLVazSJioKAACgYKAf8SARcSFQHGX2Mi7vLX1X-44U7MbuYdvaP85RoVAUF8yKrCkxk0H23OSKpdpS4Idwfp0076 +.youtube.com TRUE / TRUE 1827695657 __Secure-3PSID g.a000EAmiU2hM7FuD6v63bTop3LlKx764pFf1t4tnmCs-R8ZiKwUt-5dt3UN1zw2Vbd0ITxZ-RQACgYKAT8SARcSFQHGX2Miyerzf0aNf4CZ1gHaVcOvJRoVAUF8yKpZFiPbafwxuLK3XngvGkL80076 +.youtube.com TRUE / TRUE 1827695657 LOGIN_INFO AFmmF2swRgIhANuOmQ72_1tpBj__Z89LskTVp9i130w2AnoFIeQ5GFw2AiEA99-nsLcdID44W7O0ha1XNs3kBGM9Pj1H2jrIWVOJtaM:QUQ3MjNmd2ZLb2g2Mi1pSi1DMVpHOUZuMTRUY1ZDS2xVQ2Q2Q3p6OF9vQVdKQ054OEJyd3dfdjVvdWhURzUyZFZRcExVbG04RWpMY21LUDV4OENhYlU3MnJKZV9lN3lGYzZSekpLWmJYZ0d0ZTA3b3dzR1Q0bEhZQmpISDV5MWRlZy1rcVZNRnI0UHl2YmE4MG9OeXhjNHdwWExOUGxTMTh3 +.youtube.com TRUE / FALSE 1824793757 SIDCC APD_SKGFb6CzpAR5B4SQ5RYAOPYcbkVN3Z8WfzBrTgrxPLk2NYzxEYjXPcv0wiBT0aS5V47e +.youtube.com TRUE / TRUE 1824793757 __Secure-1PSIDCC APD_SKEuV2_zW-UZAToXz6oqw0lD8JzfM3Iyw4UAwRFVChaTOtJqmtH5JWx8nzCKWA9ZZm0pmQ +.youtube.com TRUE / TRUE 1824793757 __Secure-3PSIDCC APD_SKFWJyPSvgrzM4pyuJwCIWvE1wXocR3mi5wjmltaSdqnFj9BXIx4YsQnFCO6RMJe-lQhQQ +.youtube.com TRUE / TRUE 1808809698 __Secure-ROLLOUT_TOKEN CKq_r_Sk4IqwXxDlhvalkduXAxjFuIbB1d6XAw%3D%3D +.youtube.com TRUE / TRUE 1856208374 __Secure-YT_TVFAS t=498092&s=2 +.youtube.com TRUE / TRUE 1808688374 DEVICE_INFO ChxOemN3TVRRMk1UazVPVFkwTVRRek5USXdNQT09EPathNcGGOKthNcG +.youtube.com TRUE /tv TRUE 1825968374 __Secure-YT_DERP CL211MHsAw%3D%3D +accounts.google.com FALSE / TRUE 1827695629 __Host-GAPS 1:0bBQMEQYLfI-4i87W5sPl2-rwRCMAWDpibPT0RQX25Ax8PoQN7jB6BAHp2GwEpIjNcMgOxXFBNdF6K4_vtRAbowFRgpkmA:52xBJrI_Z8BTd8dv +accounts.google.com FALSE / TRUE 1795723741 OTZ 8847129_76_80_104160_76_446820 +accounts.google.com FALSE / TRUE 1827695657 LSID s.youtube:g.a000EAmiU7I0LBJQ9_IHxDfrsX9jO_gwDhI1oTR_ms40z3BgSsg3AWRpNZCGhA_kasTpATnOJQACgYKAbgSARcSFQHGX2Miq8IIUQluFbKEs_pBJCyZTxoVAUF8yKqHDmmhHNl2x2myYlW-AESI0076 +accounts.google.com FALSE / TRUE 1827695657 __Host-1PLSID s.youtube:g.a000EAmiU7I0LBJQ9_IHxDfrsX9jO_gwDhI1oTR_ms40z3BgSsg3BRd7bBsDLNhEqSDuRpabYgACgYKATASARcSFQHGX2MiqP1cqrb_2zyytLYEdSz6_RoVAUF8yKqWz5CnyOe2YDSEAgBDfEyN0076 +accounts.google.com FALSE / TRUE 1827695657 __Host-3PLSID s.youtube:g.a000EAmiU7I0LBJQ9_IHxDfrsX9jO_gwDhI1oTR_ms40z3BgSsg3ygc1yL3ad8eqjqFqou3nOgACgYKAYgSARcSFQHGX2Miio-7XF4Ixdh8OiCXAn6OShoVAUF8yKqKMxTErwEagIKXiwg1qB7-0076 +accounts.google.com FALSE / TRUE 1827695629 ACCOUNT_CHOOSER AFx_qI7Td1SxByP4v9j-bilk4PQMOT-H0XQbziFNmveYVwWyyvOLuamF3OBpbWmGTZ1E0AAnKV7P_HkWGuMOVV8LMpJfBa41uwzpnaiDjFTu7vpsDWpJ3luNRokBVolDLMNsiw1NpnETQ5aTWY8VYDe2OJeT-5kXeQgVL8WHc7-w4hb8YQVi2EM +.google.com TRUE / TRUE 1808942959 NID CsMCCAES9QEBOxGDSEw5ZNTUJ8oyGjiLG6tJvqph6S_TNQrpbDO2JKCuQ1GbQ2HeHYJZpg5DwuarhAlOHDyhHEpOCw-KLWAdk7CUEH1ywdnnZCq6pT7v_gzKyMLdAfTTpISS4pdKis7pSmwQJ7wkyd9SwaZvFwgXXHyRufVvwGS7DEYinDM2QiZKastEaemeZ3uafFQUJOqPPm9xUPo9A8evqrXeX2u_WH3GKuzLv00vIVCgTHp89n8CPYW_ZoUAUkAatS1yiXWCHnUbmsIcGOFvwJyh15bIfZSWWJy3g6NurLtsSgGMSCmM4g_vzr8R5E104WqT6vlRERe9nigBMkUB0qwHz3PwnbA_0m1Ia0mrG7DrMyc4TPe1rd4r701mtL1r6VYeNrIP7MlhSYXD5dMaoLDJyek9UJNtO9yAVcmRr3b9www +.google.com TRUE / FALSE 1827695629 SID g.a000EAmiU2hM7FuD6v63bTop3LlKx764pFf1t4tnmCs-R8ZiKwUtjiMkG7YgtJw5b2BBETp3ZQACgYKAUcSARcSFQHGX2MiFNswjWtYxSdRiBvSpq9YRxoVAUF8yKqHjReqCyQLK_r14tW462do0076 +.google.com TRUE / TRUE 1827695629 __Secure-1PSID g.a000EAmiU2hM7FuD6v63bTop3LlKx764pFf1t4tnmCs-R8ZiKwUt-OO1YThhWv8hLVazSJioKAACgYKAf8SARcSFQHGX2Mi7vLX1X-44U7MbuYdvaP85RoVAUF8yKrCkxk0H23OSKpdpS4Idwfp0076 +.google.com TRUE / TRUE 1827695629 __Secure-3PSID g.a000EAmiU2hM7FuD6v63bTop3LlKx764pFf1t4tnmCs-R8ZiKwUt-5dt3UN1zw2Vbd0ITxZ-RQACgYKAT8SARcSFQHGX2Miyerzf0aNf4CZ1gHaVcOvJRoVAUF8yKpZFiPbafwxuLK3XngvGkL80076 +.google.com TRUE / FALSE 1827695629 HSID AvG4uuuFtl-8-qhUH +.google.com TRUE / TRUE 1827695629 SSID A_oeh4tFJB7iymCHx +.google.com TRUE / FALSE 1827695629 APISID PrsWDyK9QoAfY-Fv/A_DU6Ntj541EzC59t +.google.com TRUE / TRUE 1827695629 SAPISID qShyTwi4D6RAWqup/A9cVfuudF_AUU8fCi +.google.com TRUE / TRUE 1827695629 __Secure-1PAPISID qShyTwi4D6RAWqup/A9cVfuudF_AUU8fCi +.google.com TRUE / TRUE 1827695629 __Secure-3PAPISID qShyTwi4D6RAWqup/A9cVfuudF_AUU8fCi +.google.com TRUE / FALSE 1824671657 SIDCC APD_SKH6ArSxVZJ-tZd13uSOlQxmI6fFkjfQ871Qwbtszlfjhq5LNC3HLR69O8hy1SiokjE29w +.google.com TRUE / TRUE 1824671657 __Secure-1PSIDCC APD_SKHHaq35TM1Xz_P1bEspG8lIaoGEN3R0xN2vKFb4ArQ1yHTMCsbU3vfg2YVZd0C53FKZUA +.google.com TRUE / TRUE 1824671657 __Secure-3PSIDCC APD_SKEr9tfGBVIpEzBSuQKMLPZEMm60YNuNpExMCMvB5kEvzSOSTLQ65r-ty64yKNyawYpM7A +workspace.google.com FALSE / TRUE 1795723760 OTZ 8847129_76_80_104160_76_446820 +gds.google.com FALSE / TRUE 1795727630 OTZ 8847194_76_80_104160_76_446820 diff --git a/src/app/api/audio/stream/route.ts b/src/app/api/audio/stream/route.ts index 177c674..94c881e 100755 --- a/src/app/api/audio/stream/route.ts +++ b/src/app/api/audio/stream/route.ts @@ -1,3 +1,6 @@ +import { createReadStream } from 'node:fs' +import { stat } from 'node:fs/promises' +import { Readable } from 'node:stream' import { NextRequest, NextResponse } from 'next/server' import { assertPublicHttpUrl } from '@/lib/server/ssrf' import { getStreamEntry, refreshStreamEntry } from '@/lib/server/stream-store' @@ -7,10 +10,18 @@ export const dynamic = 'force-dynamic' /** * GET /api/audio/stream?token= - * Same-origin byte proxy for a resolved upstream audio URL. Same-origin is - * what keeps the Web Audio analyser untainted (a cross-origin - {/* class strategy: next-themes puts "dark" (default) or "light" on - pre-paint; globals.css remaps the palettes under .light */} +