tighten lastfm parsing and locale url handling

This commit is contained in:
2026-04-21 19:21:32 +02:00
parent ba97fe85fe
commit 4c7e6f5792
4 changed files with 135 additions and 26 deletions

View File

@@ -1422,9 +1422,9 @@ type resolvedLastFMTrack struct {
}
var (
lastFMTitleTagsRe = regexp.MustCompile(`<a\s+href="[^"]+"\s+title="([^"]+)"`)
lastFMTitleTagsRe = regexp.MustCompile(`<a\b[^>]*\btitle=(?:"([^"]+)"|'([^']+)')`)
lastFMTotalTracksRe = regexp.MustCompile(`data-playlisting-entry-count="(\d+)"`)
lastFMPlaylistTitleRe = regexp.MustCompile(`<h1 class="playlisting-playlist-header-title">([^<]+)</h1>`)
lastFMPlaylistTitleRe = regexp.MustCompile(`<h1[^>]*class="[^"]*playlisting-playlist-header-title[^"]*"[^>]*>([^<]+)</h1>`)
lastFMMirrorTitleRe = regexp.MustCompile(`^Title:\s*(.+?)\s+\|`)
lastFMMirrorLinkTextRe = regexp.MustCompile(`\[([^\]]+)\]\(`)
errLastFMInvalidSource = "unsupported source"
@@ -1662,7 +1662,11 @@ func isValidLastFMPlaylistURL(raw string) bool {
return false
}
h := strings.ToLower(strings.TrimPrefix(strings.TrimSpace(u.Host), "www."))
return h == "last.fm" || strings.HasSuffix(h, ".last.fm")
if h != "last.fm" && !strings.HasSuffix(h, ".last.fm") {
return false
}
p := strings.ToLower(strings.TrimSpace(u.Path))
return strings.Contains(p, "/playlists/")
}
func fetchLastFMPlaylist(ctx context.Context, verifySSL bool, playlistURL string) (string, []lastFMTrack, error) {
@@ -1827,8 +1831,10 @@ func extractLastFMTitleArtistPairs(page string) []lastFMTrack {
titles := lastFMTitleTagsRe.FindAllStringSubmatch(page, -1)
out := make([]lastFMTrack, 0, len(titles)/2)
for i := 0; i+1 < len(titles); i += 2 {
title := html.UnescapeString(strings.TrimSpace(titles[i][1]))
artist := html.UnescapeString(strings.TrimSpace(titles[i+1][1]))
titleRaw := strings.TrimSpace(firstNonEmpty(titles[i][1], titles[i][2]))
artistRaw := strings.TrimSpace(firstNonEmpty(titles[i+1][1], titles[i+1][2]))
title := html.UnescapeString(titleRaw)
artist := html.UnescapeString(artistRaw)
if title == "" || artist == "" {
continue
}
@@ -1837,6 +1843,15 @@ func extractLastFMTitleArtistPairs(page string) []lastFMTrack {
return out
}
func firstNonEmpty(items ...string) string {
for _, item := range items {
if strings.TrimSpace(item) != "" {
return strings.TrimSpace(item)
}
}
return ""
}
func extractLastFMTracksFromMirrorMarkdown(md string) (string, []lastFMTrack) {
lines := strings.Split(strings.ReplaceAll(md, "\r\n", "\n"), "\n")
title := ""
@@ -2316,6 +2331,18 @@ func promptSearchInteractive(defaultLimit int) (string, string, searchOptions, e
func normalizeSearchResults(source, mediaType string, pages []map[string]any) []searchResult {
results := make([]searchResult, 0)
seen := map[string]struct{}{}
appendUnique := func(r searchResult) {
if strings.TrimSpace(r.ID) == "" || strings.TrimSpace(r.Title) == "" {
return
}
key := r.ID
if _, ok := seen[key]; ok {
return
}
seen[key] = struct{}{}
results = append(results, r)
}
for _, page := range pages {
switch source {
case "qobuz":
@@ -2351,9 +2378,7 @@ func normalizeSearchResults(source, mediaType string, pages []map[string]any) []
trackCount = searchInt(itm["track_count"])
}
explicit := searchBool(itm["parental_warning"])
if id != "" && title != "" {
results = append(results, searchResult{ID: id, Title: title, Artist: artist, Album: album, TrackCount: trackCount, Explicit: explicit})
}
appendUnique(searchResult{ID: id, Title: title, Artist: artist, Album: album, TrackCount: trackCount, Explicit: explicit})
}
case "tidal":
items, ok := page["items"].([]any)
@@ -2387,9 +2412,7 @@ func normalizeSearchResults(source, mediaType string, pages []map[string]any) []
trackCount = searchInt(itm["tracks_count"])
}
explicit := searchBool(itm["explicit"])
if id != "" && title != "" {
results = append(results, searchResult{ID: id, Title: title, Artist: artist, Album: album, TrackCount: trackCount, Explicit: explicit})
}
appendUnique(searchResult{ID: id, Title: title, Artist: artist, Album: album, TrackCount: trackCount, Explicit: explicit})
}
case "deezer":
key := mediaType + "s"
@@ -2415,9 +2438,7 @@ func normalizeSearchResults(source, mediaType string, pages []map[string]any) []
album := nestedSearchString(itm, "album", "title")
trackCount := searchInt(itm["nb_tracks"])
explicit := searchBool(itm["explicit_lyrics"])
if id != "" && title != "" {
results = append(results, searchResult{ID: id, Title: title, Artist: artist, Album: album, TrackCount: trackCount, Explicit: explicit})
}
appendUnique(searchResult{ID: id, Title: title, Artist: artist, Album: album, TrackCount: trackCount, Explicit: explicit})
}
case "soundcloud":
items, ok := page["items"].([]any)
@@ -2433,9 +2454,7 @@ func normalizeSearchResults(source, mediaType string, pages []map[string]any) []
title := asString(itm["title"])
artist := nestedSearchString(itm, "artist", "name")
trackCount := searchInt(itm["tracks_count"])
if id != "" && title != "" {
results = append(results, searchResult{ID: id, Title: title, Artist: artist, TrackCount: trackCount})
}
appendUnique(searchResult{ID: id, Title: title, Artist: artist, TrackCount: trackCount})
}
}
}