Files
linker/README.md
T
paulandClaude Opus 4.8 ef57761859 feat: spotify-only matching + optional local-LLM gate + google→bandcamp fallback (closes task/drop-musicbrainz-google-fallback, closes task/ollama-classifier)
Source: human-feedback note 01KVDYV3A35B57N15CRBQDXPCH (project/reddit-spotify-linker,
2026-06-18) — "remove the musicbrainz integration and just use spotify"; a google search
for "<name> bandcamp" fallback; and "would a local llm be better able to categorise these
... include instructions for running something suitable via ollama (16GB M2)".

- Remove MusicBrainz entirely (viaMusicBrainz + url-rels + rate-gate; lib relUrls/mbResult).
  Spotify is now the only music API; it both gates by exact-name match and supplies the
  direct artist link.
- Optional ollama LLM gate (OLLAMA_URL enables it, OLLAMA_MODEL default qwen2.5:3b) called
  over plain HTTP -> stays zero npm-dep. It classifies which candidates are real
  artists/albums (the matching-quality lever). Pure prompt/parse logic in new llm.js
  (unit-tested in llm.test.js); the fetch + resolveAll gate wiring live in resolver.js.
- A confirmed name links to its direct Spotify page when available, else a Google search for
  "<name> bandcamp". New "google" primary platform rendered by the extension (styles.css
  blue accent + platform-aware link title in content.js).
- README: drop MusicBrainz; document the two gates + a runnable local-LLM setup for a 16GB
  MacBook M2. AGENTS.md: flip the old "no ML/NER" standing decision per the human override.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:50:07 +00:00

182 lines
7.6 KiB
Markdown

# Reddit → Spotify / Bandcamp linker
A Firefox extension that, while you browse a Reddit thread, finds **artist / band
names** in the comments (and the post body) and turns them into inline links to
**Spotify** and **Bandcamp** — so a recommendation thread like
[this one](https://www.reddit.com/r/SlamDeathMetal/comments/1txh2r1/) becomes a
list of clickable artists instead of names you have to copy-paste into search.
The hard part is *"which words are artists 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. The resolver confirms names two ways (you enable either or both):
**Spotify** (exact-name match → direct artist pages) and an optional **local LLM**
via ollama (classifies which candidates are artists/albums). A confirmed name
links to its Spotify artist page when available, otherwise to a Google search for
"<name> bandcamp".
```
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.
```sh
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 by exact name match) and supplies the **direct** artist-page link. 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:
```sh
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 "<name> 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 "<name> 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.
## 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](https://addons.mozilla.org/developers/) 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 become a single link — to **Spotify** if the artist is on Spotify,
otherwise to **Bandcamp** — colour-coded (green = Spotify, teal = Bandcamp). 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.json` → `permissions` (Firefox needs the host listed there to
let the content script reach it).
## Limitations (MVP)
- **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*.
- **Links are direct to Spotify when Spotify has the artist**; otherwise a confirmed
name links to a Google search for "<name> bandcamp" (which surfaces the artist's
Bandcamp page).
- **Without Spotify creds or the LLM, nothing links** — at least one gate must be
configured.
- 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.