Problem Statement
OpenKara Online Sources cover two origins only: NetEase Cloud Music as a Streaming Source and YouTube as a Video Source. A user whose catalog lives on Apple Music, Spotify, or Deezer cannot search that catalog inside OpenKara. The same user cannot import a track they already have the right to play. Lyrics Acquisition also misses confident matches that public iTunes, Deezer, or Spotify metadata would provide.
Solution
Add new Online Sources in the SpotiFLAC shape: the host stays clean and each source arrives as an adapter behind the existing opt-in switch. Ship metadata-only adapters first (iTunes Search, Deezer public API, song.link cross-platform mapping). Ship full-audio adapters second as bring-your-own-credential adapters (Deezer ARL, Qobuz user token). Add provider priority with ISRC-first dedup so one search fans out across enabled sources and the best match wins. Never bundle third-party credentials or grey-service URLs in the default build.
User Stories
- As a listener with an Apple Music library, I want to search that catalog inside OpenKara, so that I can find a track without leaving the app.
- As a listener with a Spotify library, I want to search that catalog inside OpenKara, so that I can locate a track I already know.
- As a listener with a Deezer library, I want to search that catalog inside OpenKara, so that I can confirm track identity before import.
- As a NetEase user, I want Apple Music artwork and genre to enrich an imported song, so that the library looks complete.
- As a NetEase user, I want a Deezer 30-second preview before import, so that I can verify the correct track version.
- As a user who pastes an Apple Music link, I want OpenKara to resolve the equivalent Spotify or Deezer entry, so that the import path can proceed.
- As a user who pastes a Spotify link, I want OpenKara to resolve the equivalent entry on another enabled source, so that a dead source does not block import.
- As a Deezer subscriber, I want to supply my own session credential once, so that OpenKara can import full-length audio I have the right to play.
- As a Qobuz subscriber, I want to supply my own user token once, so that OpenKara can import full-length audio I have the right to play.
- As a privacy-minded user, I want every new source off by default, so that nothing contacts a third party without my consent.
- As a privacy-minded user, I want to see which hosts each source contacts, so that I can judge the exposure before I enable it.
- As a user with several sources enabled, I want to order provider priority, so that my preferred catalog wins ties.
- As a user who enables several sources, I want one search to fan out across them, so that I do not repeat the query per source.
- As a user who imports a playlist, I want ISRC-first dedup across sources, so that the same recording does not enter the library twice.
- As a user who imports a track that two sources both hold, I want the Import Conflict prompt I already know, so that Keep Library Song and Replace Library Song keep working.
- As a user whose track has no play rights, I want an Import Refusal with the reason, so that the failure list names the cause instead of failing silently.
- As a user with a trial clip only, I want the refusal to stay a refusal, so that a lossy snippet never lands in the library as a full song.
- As a karaoke singer, I want Lyrics Acquisition to use the new metadata for matching, so that more songs gain timed lyrics.
- As a singer, I want the winning lyrics source recorded as today, so that Word-timed Upgrade rules keep applying.
- As a settings user, I want per-source sign-out that clears stored credentials, so that I can revoke access from inside the app.
- As a settings user, I want credential fields to never echo stored secrets into logs or diagnostics, so that a bug report cannot leak my session.
- As an overseas user, I want the NetEase China-client-address behavior unchanged, so that this change does not regress permitted imports.
Implementation Decisions
- Base the work on branch
feat/418-online-sources. It already owns the Streaming Source adapter seam, the gated opt-in registry, and the shared streaming import path. If that branch already merged, use the catalog module on the default branch instead. - Use the Streaming Source adapter seam as the single seam for every new source. A metadata-only adapter implements the search subset. A full-audio adapter implements search plus resolve-to-file. No new import pipeline.
- Reuse the shared streaming import path unchanged. Identity stays source plus stable track id. Same identity at a new quality stays one library song. Same identity with a different file raises the existing Import Conflict.
- Reuse the gated registry seam for enablement. Each new source is off by default and reports capabilities through the existing snapshot. The host never contacts a source host until the user enables that source.
- Add a provider-priority list in settings. Search fans out in priority order. A source error never stops the chain. The chain continues with the next source. Dedup key order is ISRC, then Spotify id, then source plus track id, then normalized title plus artist. A mismatched download candidate is discarded and the chain continues. Every search result carries cover URL, preview URL when the source provides one, and ISRC when the source provides one.
- Split source kinds three ways: Streaming Source (account-backed audio, NetEase pattern), Video Source (public-link playback, YouTube pattern, unchanged), Metadata Source (search, preview, and enrichment only, never imports audio). Metadata Source is a new taxonomy term. Add it to the domain glossary and to the new ADR. A Metadata Source never returns an importable file.
- P0 ships iTunes Search, Deezer public search plus track lookup, and song.link mapping. All three use public endpoints with no user credential: the iTunes Search API, the Deezer public API, and the song.link links API. Spotify metadata uses the public web-player token endpoint with no user login. Apple Music page scraping, if added, is metadata-only and degrades to iTunes or Deezer on parse failure.
- P1 ships bring-your-own-credential audio adapters. The user types the credential in settings. The app stores it in the existing credential vault beside the NetEase session. The app ships no credential, no shared key, and no grey-service URL in the default build.
- Reimplement audio-fetch principles from scratch. Do not copy code from AGPL or GPL download engines. The Deezer path sends the user's session credential to the Deezer track API, receives a CDN URL, fetches the payload, and decrypts it in-process with a Blowfish key derived from the track id. The Qobuz path sends the user's token to the Qobuz file-URL endpoint with the requested quality id, then fetches the returned URL. Settings shows the contacted hosts for each source before the user enables it.
- Pin any external package the same way the model bootstrap pins artifacts: HTTPS-only registry, recorded SHA-256, verify before install, keep the previously installed package on mismatch.
- Feed the new metadata into the existing lyrics lookup query (enriched artist, title, album, duration, ISRC). The acquisition chain and the upgrade rules do not change.
- Update the catalog IPC contract in the same change: extend the online-source id union, the capabilities shape, and the error model. Add one ADR that extends the ADR 0031 taxonomy with Metadata Source, reuses the ADR 0032 shared import path, and respects the ADR 0033 no-replacement-source boundary. Follow the product-standards route for settings UI, IPC, and credential storage, and put the required evidence in the pull request.
- Prototype reference for the resolve outcome shape (from the existing contract, kept here because it encodes the decision): resolve returns either a file (path plus optional title, artist, album) or a refusal (reason plus title plus artist). Adapters never write library rows.
Testing Decisions
- Test external behavior only, not implementation detail. A good test drives the adapter seam or the IPC command and asserts the snapshot, the search result set, the import outcome, or the refusal reason.
- Cover the new adapters with the existing fake-source pattern: fixed track fixtures, forced refusals, expired sessions, and disabled-source gating.
- Cover priority fan-out and ISRC-first dedup with multi-source fixtures, including a wrong-track candidate that must be discarded.
- Cover credential handling with negative tests: stored secrets never appear in logs, diagnostics, or error strings; sign-out clears the vault.
- Cover contract parity the way the existing IPC contract test pins the lyrics source enum: pin the online-source id union and the capabilities shape.
- Prior art: the catalog suite for the streaming trait, the registry default-off tests, the import conflict tests, the settings section tests, and the catalog store tests.
Out of Scope
- A JavaScript extension sandbox or third-party extension store. Adapters are native and reviewed in-tree.
- Bundled credentials, shared keys, or hard-coded third-party download-service URLs in the default build.
- Spotify-audio or YouTube-audio import. That path needs download tooling declined in ADR 0033, so it needs its own proposal.
- UNM-style replacement sourcing for grey songs and trial clips. Refusals stay refusals.
- Scrobblers. A scrobbler is not an Online Source.
- Lyrics-provider changes beyond consuming the new metadata for matching. The acquisition chain and upgrade rules do not change.
Further Notes
- A mature reference host survived by keeping infringing logic out of the host: decentralized community extensions carry the source risk, the host carries none. This spec copies that boundary, not any downloader implementation. Reference repos:
github.com/spotiflacapp/SpotiFLAC-Mobile(host runtime, extension manifest, provider priority, fallback chain; see its extension development guide),github.com/spotiflacapp/SpotiFLAC-Extension(registry with SHA-256 package pinning),github.com/EduAlexxis/ByeTunes(source-layer prior art, open revisionf296f25). - Prior art and capability equivalence: ByeTunes V2.2 (open revision
f296f25) implements the same source set this spec targets, and this spec reaches parity with it in two phases. P0 covers its metadata layer: iTunes Search, Deezer public search plus track lookup, song.link cross-platform mapping, and metadata-only Apple Music page parsing with fallback. P1 covers its full-audio layer under bring-your-own-credential rules: Deezer session-credential fetch plus in-process CDN decrypt, and Qobuz user-token file-URL fetch at the requested quality id. Deliberately not carried over: Tidal public-instance fallback and Spotify-audio via YouTube tooling, both out of scope above. ByeTunes later converged these sources into one private proxy endpoint; this spec does not copy that proxy and does not reuse its closed backend. - No DMCA notice received is not evidence of lawfulness. The safety comes from no bundled secrets, explicit opt-in, user-supplied credentials, and per-source host disclosure.
- License hygiene: reimplementation only. No code from AGPL or GPL music downloaders enters the tree.