paulandClaude Opus 4.8 e06c983b16 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>
2026-06-24 02:41:30 +00:00

Reddit → Spotify / Bandcamp linker

A Firefox extension that, while you browse a Reddit thread, finds artist / band names and album titles in the comments (and the post body) and turns them into inline links to Spotify and Bandcamp — so a recommendation thread like this one becomes a list of clickable artists and albums instead of names you have to copy-paste into search.

The hard part is "which words are artists/albums and which aren't?". Rather than guess with client-side rules, the extension hands candidate names to a tiny local resolver service that confirms each one and only links the names that are really artists or albums. The resolver confirms names two ways (you enable either or both): Spotify (exact-name match across artists and albums → direct Spotify pages) and an optional local LLM via ollama (classifies which candidates are artists/albums). A confirmed name links to its Spotify artist/album page when available, otherwise to a Google search for " bandcamp". Album links are italicised so you can tell an album from an artist at a glance.

 reddit page                 content script                 resolver service
 ───────────                 ──────────────                 ────────────────
 comment text  ──extract──>  candidate names  ──POST /resolve──>  Spotify API  (exact-match + direct links)
                                                                     and/or
 <a> links     <──rewrite──  confirmed links  <─────────────────  local LLM    (ollama, optional)

Layout

service/      tiny Node.js resolver (one file, zero npm deps)
  resolver.js
  package.json
  .env.example
extension/    Firefox MV2 extension
  manifest.json
  background.js   (does the resolver fetch; talks to the content script)
  content.js
  styles.css
  icon.svg

1. Run the resolver service

Requirements: Node 18+ (uses the built-in fetch). No npm install needed — there are no dependencies.

cd service
node resolver.js
# resolver listening on http://localhost:8787  (spotify: off, llm: off)

For anything to link you need at least one of two gates configured — Spotify credentials or the local LLM (or both). With neither, the resolver warns at startup and nothing links.

Spotify credentials (one of the two gates)

Spotify does two jobs: it gates a name (a candidate links only when Spotify has that artist or album by exact name match) and supplies the direct Spotify page link. A single search call covers both types, so albums confirm and link with no extra setup. To enable it:

  1. Create a free app at https://developer.spotify.com/dashboard and copy its Client ID and Client Secret.

  2. cp .env.example .env and fill them in (the .env file is gitignored):

    SPOTIFY_CLIENT_ID=your_id
    SPOTIFY_CLIENT_SECRET=your_secret
    
  3. Restart the service. Startup reports (spotify: on, llm: off).

With Spotify alone, names that aren't on Spotify don't link — precise, but it misses real artists Spotify doesn't carry. The local LLM gate (below) catches those.

Quick check that it works:

curl -s localhost:8787/resolve \
  -H 'content-type: application/json' \
  -d '{"candidates":["Cerebral Bore","Devourment","Notarealband Xyzzy"]}'
# {"Cerebral Bore":{...},"Devourment":{...},"Notarealband Xyzzy":null}

A confirmed name links to its Spotify artist page when available, otherwise to a Google search for " bandcamp" (which surfaces the artist's Bandcamp page).

Better matching with a local LLM (optional)

The optional LLM gate is the matching-quality lever: it classifies which candidate strings are real artists/albums, so confirmed names that aren't on Spotify still link (via the " bandcamp" Google fallback). The resolver calls ollama over plain HTTP, so it stays zero npm dependencies.

On a 16GB MacBook (M2):

  1. Install ollama — https://ollama.com/download or brew install ollama.

  2. Pull a small model: ollama pull qwen2.5:3b (~2GB; runs comfortably on a 16GB M2; the first resolve call warms the model so it's a little slow once, then fast).

  3. In service/.env, point the resolver at ollama (and optionally override the model):

    OLLAMA_URL=http://localhost:11434
    OLLAMA_MODEL=qwen2.5:3b
    
  4. Restart the resolver. Startup reports (spotify: off, llm: qwen2.5:3b) (or (spotify: on, llm: qwen2.5:3b) if you also set Spotify creds).

  5. Confirm: curl localhost:8787/health should show "llm":"qwen2.5:3b".

With both gates, the LLM decides which names are artists and Spotify supplies the direct links; with the LLM alone, confirmed names get a Spotify direct link if one exists, else the Google fallback.

By default a name that isn't on Spotify links to a Google " bandcamp" search — one click from the real Bandcamp page. With Odesli (the free, no-auth song.link API) enabled, an album result that resolved to a direct Spotify page is also enriched with the album's direct Bandcamp (and, when Odesli has them, Apple Music / YouTube) links, so the extension shows a small bc link straight to Bandcamp beside the Spotify link.

  • Opt-in, default OFF — set ODESLI=1 in service/.env (or ODESLI_URL=<base> to override the endpoint). With it off, no Odesli call is made and behavior is unchanged.
  • Album results only — Odesli resolves album/track entity urls, not artist urls (an artist url 405s), so only album results are enriched; artist results are skipped (no wasted call).
  • Needs the Spotify gate — an album result only carries a direct Spotify url when Spotify confirmed it, so set the Spotify credentials above to have a url to enrich. The no-creds Google fallback path is untouched.
  • Bounded + graceful — one cached Odesli call per Spotify url, with a timeout; if Odesli is down/slow/garbled the current result is kept and nothing breaks.
ODESLI=1 SPOTIFY_CLIENT_ID=SPOTIFY_CLIENT_SECRET=… node resolver.js
# resolver listening on http://localhost:8787  (spotify: on, llm: off, odesli: on)

curl localhost:8787/health then shows "odesli":true.

2. Install the extension in Firefox

The extension is unpacked / unsigned, so load it as a temporary add-on:

  1. Open about:debugging#/runtime/this-firefox.
  2. Load Temporary Add-on…
  3. Select extension/manifest.json.

It's now active until you restart Firefox. (To keep it permanently you'd sign it via AMO or use Firefox Developer/ESR with xpinstall.signatures.required = false.)

3. Use it

  1. Make sure the resolver service is running.
  2. Open any Reddit comments thread (a URL with /comments/ in it).
  3. Artist names and album titles become a single link — to Spotify if it's on Spotify, otherwise to Bandcamp — colour-coded (green = Spotify, blue = Google/Bandcamp); album links are italicised so they read as albums. New comments loaded as you scroll are linked automatically.
  4. The toolbar icon shows the extension is active; its badge shows how many links were added on the current page.

Troubleshooting

If nothing gets linked:

  1. Confirm the resolver is running — curl localhost:8787/health should return {"ok":true,"spotify":<bool>,"llm":<model|false>,"cached":<n>}. If both spotify and llm are off, nothing can link — configure at least one gate above.
  2. On the reddit thread, open the browser console (F12) and look for [rsl] logs: they report the root element, candidate count, and resolver responses on each scan. A resolve failed line means the background script can't reach the resolver (is it on :8787?); candidates found: 0 means the comment text wasn't matched (please report the thread).
  3. The content script reaches the resolver through the extension's background script — a page-context fetch to http://localhost is blocked by reddit's CSP — so make sure the add-on loaded without errors in about:debugging.

Configuration

The resolver URL is the constant RESOLVER_URL at the top of extension/background.js (default http://localhost:8787). If you change the host or port, update both that constant and the matching entry in extension/manifest.jsonpermissions (Firefox needs the host listed there to let the content script reach it).

Limitations (MVP)

  • Artist detection is mostly capitalization-based, with a cue-phrase pass that also catches lowercase names right after "check out / by / fan of / recommend …". Names split across inline formatting (e.g. bold mid-name) can still be missed. The resolver's exact-name match keeps false positives low; the trade-off is some false negatives.
  • Album detection leans on two precise signals — quoted titles ("Reek of Putrefaction") and album cue phrases ("the album X", "X LP/EP"). Album free-text is noisier than artist names, so it deliberately over-generates only from these cues and lets the Spotify exact-album match (and/or the LLM) be the precision filter. A bare album title written with no quotes or cue may be missed.
  • Links are direct to Spotify when Spotify has the artist/album; otherwise a confirmed name links to a Google search for " bandcamp" (which surfaces the Bandcamp page).
  • Without Spotify creds or the LLM, nothing links — at least one gate must be configured.
  • New reddit (shreddit) shadow DOM — the scan roots at the comment tree (shreddit-comment-tree, falling back to old reddit's .commentarea, then main/body) and pierces the open shadow roots of the shreddit-* web components within it, so it links comment bodies whether reddit slots them into light DOM or encapsulates them in a shadow root. On a broad fallback root (no comment tree found) only comment-component shadow roots are descended, so reddit's chrome (nav, sidebar, composer) is never collected. Closed shadow roots are unreachable from a content script and stay unlinked (a browser limitation, not a setting).
  • The optional LLM adds a little latency on the first call (ollama warms the model), then is fast.
  • The resolver caches results in memory; it resets when you restart it.
  • Built and tested as a Firefox MV2 extension.

License

Personal project — do as you like.

S
Description
No description provided
Readme
275 KiB
Languages
JavaScript 98.4%
CSS 0.9%
Shell 0.7%