* fix: split html sanitization from plaintext handling
Add a dedicated SanitizeHTML helper for HTML-rendered values so entity-encoded markup is decoded before bluemonday sanitization. Use the new helper for the login welcome message and artist biographies while preserving SanitizeText semantics for lyrics and other plaintext callers. Add regression coverage for both helpers and the serveIndex welcomeMessage path.
* docs: add SanitizeText and SanitizeHTML godoc
Signed-off-by: Deluan <deluan@navidrome.org>
* fix: preserve plain text in artist biographies
Revert artist biography storage to SanitizeText so entity-encoded plain text remains decoded for Subsonic consumers. This avoids double-escaping values like R&B in XML responses while keeping the new welcomeMessage HTML sanitization in place, and adds a regression test covering the biography storage behavior.
---------
Signed-off-by: Deluan <deluan@navidrome.org>
* refactor: extract matchSongsToLibrary to core/matcher package
Move the song-to-library matching algorithm from core/external into its own
core/matcher package. The Matcher struct exposes a single public method
MatchSongsToLibrary that implements a multi-phase matching algorithm
(ID > MBID > ISRC > fuzzy title+artist). Includes pre-sanitization
optimization for the fuzzy matching loop.
No behavioral changes — the algorithm is identical to the version in
core/external/provider_matching.go.
* refactor: inject matcher.Matcher via Wire instead of creating it inline
Add *matcher.Matcher as a dependency of external.NewProvider, wired via
Google Wire. Update all provider test files to pass matcher.New(ds).
This eliminates tight coupling so future consumers can reuse the matcher
without depending on the external package.
* refactor: remove old provider_matching files
Delete core/external/provider_matching.go and its tests. All matching
logic now lives in core/matcher/.
* test(matcher): restore test coverage lost in extraction
Port back 23 specs that existed in the old provider_matching_test.go
but were dropped during the extraction. Covers specificity levels,
fuzzy matching thresholds, fuzzy album matching, duration matching,
and deduplication edge cases.
* test(matcher): extract matchFieldInAnd/matchFieldInEq helpers
The four inline mock.MatchedBy closures in setupAllPhaseExpectations
all followed the same squirrel.And -> squirrel.Eq -> field-name-check
pattern. Extract into two small helpers to reduce duplication and
make the setup functions read as a concise list of phase expectations.
* refactor(matcher): address PR #5348 review feedback
- sanitizedTrack now holds *model.MediaFile instead of a value copy. Since
MediaFile is a large struct (~74 fields), this avoids the per-track copy
into sanitized[] and a second copy when findBestMatch assigns the winner.
loadTracksByTitleAndArtist updated to iterate by index and pass &tracks[i].
- loadTracksByISRC now sorts results (starred desc, rating desc, year asc,
compilation asc) so that when multiple library tracks share an ISRC the
most relevant one is picked deterministically, matching the sort order
already used by loadTracksByTitleAndArtist.
- Restored the four worked examples (MBID Priority, ISRC Priority,
Specificity Ranking, Fuzzy Title Matching) in the MatchSongsToLibrary
godoc that were dropped during the extraction.
- matcher_test.go: tests now enforce expectations via AssertExpectations in
a DeferCleanup. The old setupAllPhaseExpectations helper was replaced
with per-phase helpers (expectIDPhase/expectMBIDPhase/expectISRCPhase +
allowOtherPhases) so each test deterministically verifies which matching
phases fire. This surfaced (and fixes) a latent issue copilot flagged:
the old .Once() expectations were not actually asserted, so tests would
silently pass even when phases short-circuited unexpectedly.
Add ID field to Artist and Song structs in the agents package. When resolving
similar artists and top songs, the provider now uses a three-phase lookup:
1. Direct ID match (if agent returns internal Navidrome IDs)
2. MBID exact match (if MusicBrainz ID is available)
3. Fuzzy name/title match (existing behavior)
This enables agents to return more precise matches when they have access to
internal database IDs, while maintaining backward compatibility with
name-based matching.