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>
This commit is contained in:
@@ -8,15 +8,19 @@ 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 checks each one against a real music database and only
|
||||
links the names that actually match an artist.
|
||||
**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 (direct links)
|
||||
or
|
||||
<a> links <──rewrite── confirmed links <───────────────── MusicBrainz (zero-setup)
|
||||
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
|
||||
@@ -42,18 +46,18 @@ there are no dependencies.
|
||||
```sh
|
||||
cd service
|
||||
node resolver.js
|
||||
# resolver listening on http://localhost:8787 (provider: musicbrainz)
|
||||
# resolver listening on http://localhost:8787 (spotify: off, llm: off)
|
||||
```
|
||||
|
||||
That's it. With no configuration the resolver validates names against
|
||||
**MusicBrainz** (no account required). When MusicBrainz knows an artist's official
|
||||
links (its `url-relations`), you get **direct** Spotify and Bandcamp artist pages;
|
||||
otherwise it falls back to Spotify/Bandcamp **search** URLs.
|
||||
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.
|
||||
|
||||
### Optional: direct Spotify links
|
||||
### Spotify credentials (one of the two gates)
|
||||
|
||||
For **direct artist links** (instead of Spotify search), give the service Spotify
|
||||
API credentials:
|
||||
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**.
|
||||
@@ -63,10 +67,11 @@ API credentials:
|
||||
SPOTIFY_CLIENT_ID=your_id
|
||||
SPOTIFY_CLIENT_SECRET=your_secret
|
||||
```
|
||||
3. Restart the service. It will report `provider: spotify` on startup.
|
||||
3. Restart the service. Startup reports `(spotify: on, llm: off)`.
|
||||
|
||||
(Bandcamp has no public API; direct Bandcamp links come from MusicBrainz
|
||||
`url-relations` when present, else a Bandcamp search URL.)
|
||||
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:
|
||||
|
||||
@@ -77,6 +82,37 @@ curl -s localhost:8787/resolve \
|
||||
# {"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**:
|
||||
@@ -104,7 +140,9 @@ with `xpinstall.signatures.required = false`.)
|
||||
If nothing gets linked:
|
||||
|
||||
1. Confirm the resolver is running — `curl localhost:8787/health` should return
|
||||
`{"ok":true,...}`.
|
||||
`{"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
|
||||
@@ -117,7 +155,7 @@ If nothing gets linked:
|
||||
## Configuration
|
||||
|
||||
The resolver URL is the constant `RESOLVER_URL` at the top of
|
||||
`extension/content.js` (default `http://localhost:8787`). If you change the host
|
||||
`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).
|
||||
@@ -128,12 +166,14 @@ let the content script reach it).
|
||||
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 when MusicBrainz has the artist's `url-relations`** (official
|
||||
Bandcamp / Spotify pages); otherwise they fall back to a search URL. (Each
|
||||
confirmed artist costs one extra throttled MusicBrainz lookup, cached after.)
|
||||
- **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.
|
||||
- Without Spotify credentials the MusicBrainz path is rate-limited to ~1 lookup/
|
||||
second, so the first scan of a large thread fills in gradually (cached after).
|
||||
- Built and tested as a Firefox MV2 extension.
|
||||
|
||||
## License
|
||||
|
||||
Reference in New Issue
Block a user