feat: optional direct Bandcamp/Apple/YouTube links via Odesli (flag-gated) (closes task/odesli-direct-cross-platform-links)
Source: task/odesli-direct-cross-platform-links (plan/improvement-backlog) — bandcamp was only a Google search; Odesli gives direct cross-platform links for Spotify-resolved names.
- service/odesli.js: makeOdesli({fetch,baseUrl}) -> getLinks(spotifyUrl); pure parseOdesli core; bounded AbortSignal.timeout; non-200/timeout/garbage -> {} (graceful).
- resolver.js: opt-in ODESLI / ODESLI_URL flag (default OFF). When ON, enrich each Spotify-resolved result with additive bandcamp/appleMusic/youtube fields (isHttpUrl-validated), cached per Spotify url. OFF -> no call, byte-identical behavior. /health + startup log surface the flag.
- extension: a tiny additive "bc" link to a direct Bandcamp page when result.bandcamp is present (reuses isSafeHttpUrl); primary artist/album/spotify/google rendering unchanged. styles.css: bandcamp-teal secondary link.
- odesli.test.js + odesli.fixture.json: parseOdesli unit-tested against a real captured Odesli response (Blood Incantation album -> direct bandcamp; absent apple/youtube omitted; junk -> {}); getLinks 200/non-200/throw/parse-error paths.
- docs: .env.example, README, AGENTS.md note the opt-in flag.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,9 @@ is tracked in the **towl** work-item store under project **`reddit-spotify-linke
|
||||
`node:test`. The only music API is Spotify (MusicBrainz removed). The resolver confirms **both artists
|
||||
and albums**: one `type=artist,album` Spotify search per candidate, exact
|
||||
case-insensitive name match against either; each result carries
|
||||
`kind: "artist" | "album"`.
|
||||
`kind: "artist" | "album"`. `odesli.js` = the optional Odesli (song.link) enrichment client
|
||||
(injected `fetch`, pure `parseOdesli` core, HTTP-only — no new deps); `odesli.test.js` +
|
||||
`odesli.fixture.json` (a captured real response) cover it.
|
||||
- `extension/` — Firefox **MV2** content script (`content.js` + `extract.js` +
|
||||
`manifest.json` + `styles.css`). No build step. `extract.js` is the pure candidate
|
||||
extractor (Title-Case runs + cue phrases for artists; quoted strings + album cue
|
||||
@@ -94,6 +96,17 @@ fixes small things directly.
|
||||
to classify which candidates are real artists/albums. It stays **zero-NPM-dep**:
|
||||
HTTP only, no in-process model runtime. The heuristic + Spotify exact-match is the
|
||||
zero-dep default when no LLM is configured.
|
||||
- **Optional Odesli (song.link) enrichment** is opt-in, default OFF (`ODESLI=1`, or `ODESLI_URL`
|
||||
to override the endpoint — mirrors the ollama opt-in). When ON, an **album** result that already
|
||||
carries a DIRECT Spotify url is enriched with the album's direct Bandcamp (+ Apple/YouTube) links
|
||||
as ADDITIVE fields (`bandcamp`/`appleMusic`/`youtube`) — the existing `name`/`kind`/`spotify`/
|
||||
`google`/`primary` shape is unchanged, so off the behavior is byte-identical and an older
|
||||
extension keeps rendering. The gate is `shouldEnrich` (pure, unit-tested): **album-kind results
|
||||
only** — Odesli rejects ARTIST urls (405 UNSUPPORTED_URL), so artist results are skipped entirely
|
||||
(no wasted round-trip on the common case). Only the Spotify-creds path produces a url to enrich;
|
||||
the no-creds Google fallback is untouched. Bounded (`AbortSignal.timeout`) + cached per Spotify
|
||||
url + graceful (Odesli down/garbage keeps the current result). The input is our own validated
|
||||
Spotify album url against the fixed host (no SSRF).
|
||||
- Detection precision lever (zero-dep default): a candidate links **only** when
|
||||
Spotify returns an **artist or album** whose name matches it case-insensitively. A
|
||||
confirmed name links to its Spotify artist/album page if available, else a
|
||||
|
||||
Reference in New Issue
Block a user