refactor(go): consolidate duplicated track maps, selectors, and small helpers

This commit is contained in:
zarzet
2026-07-26 18:57:44 +07:00
parent cb8064a317
commit 69830f6d46
11 changed files with 86 additions and 257 deletions
+2 -12
View File
@@ -333,7 +333,7 @@ func resolveCollectionShareURL(ext *loadedExtension, itemType string, track *Ext
if url := urlFromExternalLinks(track.ExternalLinks, "album"); url != "" {
return url
}
if url := templateShareURL(ext, "album", firstNonEmptyString(track.AlbumID, collectionID(*track, "album"), track.AlbumURL)); url != "" {
if url := templateShareURL(ext, "album", firstNonEmptyTrimmed(track.AlbumID, collectionID(*track, "album"), track.AlbumURL)); url != "" {
return url
}
return ""
@@ -350,7 +350,7 @@ func resolveCollectionShareURL(ext *loadedExtension, itemType string, track *Ext
if url := urlFromExternalLinks(track.ExternalLinks, "artist"); url != "" {
return url
}
if url := templateShareURL(ext, "artist", firstNonEmptyString(track.ArtistID, collectionID(*track, "artist"))); url != "" {
if url := templateShareURL(ext, "artist", firstNonEmptyTrimmed(track.ArtistID, collectionID(*track, "artist"))); url != "" {
return url
}
return ""
@@ -430,13 +430,3 @@ func stripProviderPrefix(id string) string {
}
return id
}
func firstNonEmptyString(values ...string) string {
for _, value := range values {
trimmed := strings.TrimSpace(value)
if trimmed != "" {
return trimmed
}
}
return ""
}
+4 -83
View File
@@ -783,29 +783,7 @@ func CustomSearchWithExtensionJSONWithRequestID(extensionID, query string, optio
result := make([]map[string]any, len(tracks))
for i, track := range tracks {
result[i] = map[string]any{
"id": track.ID,
"name": track.Name,
"artists": track.Artists,
"album_name": track.AlbumName,
"album_artist": track.AlbumArtist,
"duration_ms": track.DurationMS,
"images": track.ResolvedCoverURL(),
"preview_url": track.PreviewURL,
"release_date": track.ReleaseDate,
"track_number": track.TrackNumber,
"total_tracks": track.TotalTracks,
"disc_number": track.DiscNumber,
"total_discs": track.TotalDiscs,
"isrc": track.ISRC,
"provider_id": track.ProviderID,
"item_type": track.ItemType,
"album_type": track.AlbumType,
"composer": track.Composer,
"audio_quality": track.AudioQuality,
"audio_modes": track.AudioModes,
"explicit": track.Explicit,
}
result[i] = normalizeExtensionTrackMetadataMap(track, "", 0)
}
return marshalJSONString(result)
@@ -835,51 +813,13 @@ func HandleURLWithExtensionJSON(url string) (string, error) {
}
if result.Track != nil {
response["track"] = map[string]any{
"id": result.Track.ID,
"name": result.Track.Name,
"artists": result.Track.Artists,
"album_name": result.Track.AlbumName,
"album_artist": result.Track.AlbumArtist,
"duration_ms": result.Track.DurationMS,
"images": result.Track.ResolvedCoverURL(),
"preview_url": result.Track.PreviewURL,
"release_date": result.Track.ReleaseDate,
"track_number": result.Track.TrackNumber,
"total_tracks": result.Track.TotalTracks,
"disc_number": result.Track.DiscNumber,
"total_discs": result.Track.TotalDiscs,
"isrc": result.Track.ISRC,
"provider_id": result.Track.ProviderID,
"composer": result.Track.Composer,
"explicit": result.Track.Explicit,
}
response["track"] = normalizeExtensionTrackMetadataMap(*result.Track, "", 0)
}
if len(result.Tracks) > 0 {
tracks := make([]map[string]any, len(result.Tracks))
for i, track := range result.Tracks {
tracks[i] = map[string]any{
"id": track.ID,
"name": track.Name,
"artists": track.Artists,
"album_name": track.AlbumName,
"album_artist": track.AlbumArtist,
"duration_ms": track.DurationMS,
"images": track.ResolvedCoverURL(),
"preview_url": track.PreviewURL,
"release_date": track.ReleaseDate,
"track_number": track.TrackNumber,
"total_tracks": track.TotalTracks,
"disc_number": track.DiscNumber,
"total_discs": track.TotalDiscs,
"isrc": track.ISRC,
"provider_id": track.ProviderID,
"item_type": track.ItemType,
"album_type": track.AlbumType,
"composer": track.Composer,
"explicit": track.Explicit,
}
tracks[i] = normalizeExtensionTrackMetadataMap(track, "", 0)
}
response["tracks"] = tracks
}
@@ -958,26 +898,7 @@ func HandleURLWithExtensionJSON(url string) (string, error) {
if len(result.Artist.TopTracks) > 0 {
topTracks := make([]map[string]any, len(result.Artist.TopTracks))
for i, track := range result.Artist.TopTracks {
topTracks[i] = map[string]any{
"id": track.ID,
"name": track.Name,
"artists": track.Artists,
"album_name": track.AlbumName,
"album_artist": track.AlbumArtist,
"duration_ms": track.DurationMS,
"images": track.ResolvedCoverURL(),
"preview_url": track.PreviewURL,
"release_date": track.ReleaseDate,
"track_number": track.TrackNumber,
"total_tracks": track.TotalTracks,
"disc_number": track.DiscNumber,
"total_discs": track.TotalDiscs,
"isrc": track.ISRC,
"provider_id": track.ProviderID,
"spotify_id": track.SpotifyID,
"composer": track.Composer,
"explicit": track.Explicit,
}
topTracks[i] = normalizeExtensionTrackMetadataMap(track, "", 0)
}
artistResponse["top_tracks"] = topTracks
}
+2 -2
View File
@@ -879,7 +879,7 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
return &DownloadResponse{
Success: false,
Error: "Download failed: " + lastErr.Error(),
ErrorType: firstNonEmptyString(lastErrType, "extension_error"),
ErrorType: firstNonEmptyTrimmed(lastErrType, "extension_error"),
RetryAfterSeconds: lastRetryAfterSeconds,
Service: req.Source,
}, nil
@@ -1013,7 +1013,7 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
}
if lastErr != nil {
errorType := firstNonEmptyString(lastErrType, classifyDownloadErrorType(lastErr.Error()))
errorType := firstNonEmptyTrimmed(lastErrType, classifyDownloadErrorType(lastErr.Error()))
if errorType == "unknown" {
errorType = "not_found"
}
+6 -6
View File
@@ -151,11 +151,15 @@ func invokeExtensionMethod(vm *goja.Runtime, method string, args ...any) (goja.V
return goja.Null(), nil
}
return callable(extensionObject, gojaArgumentValues(vm, args)...)
}
func gojaArgumentValues(vm *goja.Runtime, args []any) []goja.Value {
values := make([]goja.Value, len(args))
for i, arg := range args {
values[i] = gojaArgumentValue(vm, arg)
}
return callable(extensionObject, values...)
return values
}
func gojaArgumentValue(vm *goja.Runtime, value any) goja.Value {
@@ -214,11 +218,7 @@ func invokeExtensionOrGlobal(vm *goja.Runtime, method string, args ...any) (goja
if !ok {
return goja.Null(), nil
}
values := make([]goja.Value, len(args))
for i, arg := range args {
values[i] = gojaArgumentValue(vm, arg)
}
return callable(vm.GlobalObject(), values...)
return callable(vm.GlobalObject(), gojaArgumentValues(vm, args)...)
}
func extensionTrackInput(track *ExtTrackMetadata) map[string]any {
+1 -10
View File
@@ -77,7 +77,7 @@ func (e *repoExtension) getMinAppVersion() string {
}
func (e *repoExtension) getRawSHA256() string {
return firstNonEmpty(e.SHA256, e.ChecksumSHA256, e.ChecksumAlt)
return firstNonEmptyTrimmed(e.SHA256, e.ChecksumSHA256, e.ChecksumAlt)
}
func (e *repoExtension) getSHA256() string {
@@ -443,15 +443,6 @@ func (s *extensionRepo) downloadExtension(extensionID string, destPath string) e
const maxExtensionPackageBytes int64 = 64 * 1024 * 1024
func firstNonEmpty(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return value
}
}
return ""
}
func normalizeSHA256(value string) string {
normalized := strings.ToLower(strings.TrimSpace(value))
normalized = strings.TrimPrefix(normalized, "sha256:")
-4
View File
@@ -508,10 +508,6 @@ func (c *LyricsClient) FetchLyricsWithMetadata(artist, track string) (*LyricsRes
return c.parseLRCLibResponse(&lrcResp), nil
}
func (c *LyricsClient) FetchLyricsFromLRCLibSearch(query string, durationSec float64) (*LyricsResponse, error) {
return c.fetchLyricsFromLRCLibSearch(query, "", "", durationSec)
}
func (c *LyricsClient) fetchLyricsFromLRCLibSearch(query, trackName, artistName string, durationSec float64) (*LyricsResponse, error) {
params := url.Values{}
params.Set("q", query)
+6 -27
View File
@@ -99,36 +99,15 @@ func appleMusicSearchResultMatches(result appleMusicSearchResult, trackName, art
}
func selectBestAppleMusicSearchResult(results []appleMusicSearchResult, trackName, artistName string, durationSec float64) *appleMusicSearchResult {
if len(results) == 0 {
return nil
}
bestIndex := -1
bestScore := -1
for i := range results {
best := selectBestLyricsCandidate(len(results), trackName, artistName, durationSec, func(i int) (string, string, float64, bool) {
result := &results[i]
if !appleMusicSearchResultMatches(*result, trackName, artistName, durationSec) {
continue
}
score := scoreLyricsSearchCandidate(
result.SongName,
result.ArtistName,
float64(result.Duration)/1000.0,
trackName,
artistName,
durationSec,
)
if score > bestScore {
bestScore = score
bestIndex = i
}
}
if bestIndex < 0 {
ok := appleMusicSearchResultMatches(*result, trackName, artistName, durationSec)
return result.SongName, result.ArtistName, float64(result.Duration) / 1000.0, ok
})
if best < 0 {
return nil
}
return &results[bestIndex]
return &results[best]
}
func (c *AppleMusicClient) getAppleMusicToken() (string, error) {
+7 -15
View File
@@ -114,9 +114,7 @@ func (c *NeteaseClient) SearchSong(trackName, artistName string) (int64, error)
}
func selectBestNeteaseSearchResult(results []neteaseSearchSong, trackName, artistName string) *neteaseSearchSong {
bestIndex := -1
bestScore := -1
for i := range results {
best := selectBestLyricsCandidate(len(results), trackName, artistName, 0, func(i int) (string, string, float64, bool) {
result := &results[i]
artists := make([]string, 0, len(result.Artists))
for _, artist := range result.Artists {
@@ -125,20 +123,14 @@ func selectBestNeteaseSearchResult(results []neteaseSearchSong, trackName, artis
}
}
candidateArtist := strings.Join(artists, ", ")
if !lyricsSearchTitlesMatch(result.Name, trackName, false) ||
!lyricsSearchArtistsMatch(candidateArtist, artistName) {
continue
}
score := scoreLyricsSearchCandidate(result.Name, candidateArtist, 0, trackName, artistName, 0)
if score > bestScore {
bestIndex = i
bestScore = score
}
}
if bestIndex < 0 {
ok := lyricsSearchTitlesMatch(result.Name, trackName, false) &&
lyricsSearchArtistsMatch(candidateArtist, artistName)
return result.Name, candidateArtist, 0, ok
})
if best < 0 {
return nil
}
return &results[bestIndex]
return &results[best]
}
func (c *NeteaseClient) FetchLyricsByID(songID int64, includeTranslation, includeRomanization bool) (string, error) {
+39 -55
View File
@@ -265,31 +265,39 @@ func (c *SpotifyLyricsClient) SearchSong(trackName, artistName string, durationS
return strings.TrimSpace(best.TrackID), nil
}
func selectBestSpotifyLyricsSearchResult(results []spotifyLyricsSearchResult, trackName, artistName string, durationSec float64) *spotifyLyricsSearchResult {
if len(results) == 0 {
return nil
}
// selectBestLyricsCandidate returns the index of the highest-scoring candidate
// whose provider-specific gate passes; candidate returns
// (name, artist, durationSec, gatePassed). Returns -1 when nothing matches.
func selectBestLyricsCandidate(n int, trackName, artistName string, durationSec float64, candidate func(i int) (string, string, float64, bool)) int {
bestIndex := -1
bestScore := -1
for i := range results {
result := &results[i]
candidateDuration := parseClockDuration(result.Duration)
if !lyricsSearchTitlesMatch(result.Name, trackName, false) ||
!lyricsSearchArtistsMatch(result.ArtistName, artistName) ||
!lyricsSearchDurationMatches(candidateDuration, durationSec) {
for i := 0; i < n; i++ {
name, artist, duration, ok := candidate(i)
if !ok {
continue
}
score := scoreLyricsSearchCandidate(result.Name, result.ArtistName, candidateDuration, trackName, artistName, durationSec)
score := scoreLyricsSearchCandidate(name, artist, duration, trackName, artistName, durationSec)
if score > bestScore {
bestIndex = i
bestScore = score
}
}
if bestIndex < 0 {
return bestIndex
}
func selectBestSpotifyLyricsSearchResult(results []spotifyLyricsSearchResult, trackName, artistName string, durationSec float64) *spotifyLyricsSearchResult {
best := selectBestLyricsCandidate(len(results), trackName, artistName, durationSec, func(i int) (string, string, float64, bool) {
result := &results[i]
duration := parseClockDuration(result.Duration)
ok := lyricsSearchTitlesMatch(result.Name, trackName, false) &&
lyricsSearchArtistsMatch(result.ArtistName, artistName) &&
lyricsSearchDurationMatches(duration, durationSec)
return result.Name, result.ArtistName, duration, ok
})
if best < 0 {
return nil
}
return &results[bestIndex]
return &results[best]
}
func (c *SpotifyLyricsClient) FetchLyricsByID(trackID string) (*LyricsResponse, error) {
@@ -383,32 +391,20 @@ func (c *YouTubeLyricsClient) SearchSong(trackName, artistName string, durationS
}
func selectBestYouTubeLyricsSearchResult(results []youtubeLyricsSearchResult, trackName, artistName string, durationSec float64) *youtubeLyricsSearchResult {
if len(results) == 0 {
return nil
}
bestIndex := -1
bestScore := -1
for i := range results {
best := selectBestLyricsCandidate(len(results), trackName, artistName, durationSec, func(i int) (string, string, float64, bool) {
result := &results[i]
candidateDuration := parseClockDuration(result.Duration)
duration := parseClockDuration(result.Duration)
artistMatches := lyricsSearchArtistsMatch(result.Author, artistName) ||
lyricsSearchArtistAppearsInTitle(result.Title, artistName)
if !lyricsSearchTitlesMatch(result.Title, trackName, true) ||
!artistMatches ||
!lyricsSearchDurationMatches(candidateDuration, durationSec) {
continue
}
score := scoreLyricsSearchCandidate(result.Title, result.Author, candidateDuration, trackName, artistName, durationSec)
if score > bestScore {
bestIndex = i
bestScore = score
}
}
if bestIndex < 0 {
ok := lyricsSearchTitlesMatch(result.Title, trackName, true) &&
artistMatches &&
lyricsSearchDurationMatches(duration, durationSec)
return result.Title, result.Author, duration, ok
})
if best < 0 {
return nil
}
return &results[bestIndex]
return &results[best]
}
func (c *YouTubeLyricsClient) FetchLyrics(trackName, artistName string, durationSec float64) (*LyricsResponse, error) {
@@ -451,29 +447,17 @@ func (c *KugouLyricsClient) SearchSong(trackName, artistName string, durationSec
}
func selectBestKugouLyricsSearchResult(results []kugouLyricsSearchResult, trackName, artistName string, durationSec float64) *kugouLyricsSearchResult {
if len(results) == 0 {
return nil
}
bestIndex := -1
bestScore := -1
for i := range results {
best := selectBestLyricsCandidate(len(results), trackName, artistName, durationSec, func(i int) (string, string, float64, bool) {
result := &results[i]
if !lyricsSearchTitlesMatch(result.Title, trackName, false) ||
!lyricsSearchArtistsMatch(result.Artist, artistName) ||
!lyricsSearchDurationMatches(result.Duration, durationSec) {
continue
}
score := scoreLyricsSearchCandidate(result.Title, result.Artist, result.Duration, trackName, artistName, durationSec)
if score > bestScore {
bestIndex = i
bestScore = score
}
}
if bestIndex < 0 {
ok := lyricsSearchTitlesMatch(result.Title, trackName, false) &&
lyricsSearchArtistsMatch(result.Artist, artistName) &&
lyricsSearchDurationMatches(result.Duration, durationSec)
return result.Title, result.Artist, result.Duration, ok
})
if best < 0 {
return nil
}
return &results[bestIndex]
return &results[best]
}
func (c *KugouLyricsClient) FetchLyrics(trackName, artistName string, durationSec float64) (*LyricsResponse, error) {
+2 -2
View File
@@ -123,9 +123,9 @@ func TestLyricsCacheParsingAndLRCLibClient(t *testing.T) {
if err != nil || got.SyncType != "LINE_SYNCED" || len(got.Lines) != 1 {
t.Fatalf("FetchLyricsWithMetadata = %#v/%v", got, err)
}
search, err := client.FetchLyricsFromLRCLibSearch("Artist Song", 180)
search, err := client.fetchLyricsFromLRCLibSearch("Artist Song", "", "", 180)
if err != nil || len(search.Lines) == 0 {
t.Fatalf("FetchLyricsFromLRCLibSearch = %#v/%v", search, err)
t.Fatalf("fetchLyricsFromLRCLibSearch = %#v/%v", search, err)
}
if best := client.findBestLRCLibSearchMatch(
[]LRCLibResponse{
+17 -41
View File
@@ -1661,30 +1661,27 @@ type AudioQuality struct {
Codec string `json:"codec,omitempty"`
}
func flacAudioQualityFromStreamInfo(streamInfo []byte) AudioQuality {
bitDepth, sampleRate, totalSamples := parseFLACStreamInfoQuality(streamInfo)
duration := 0
if sampleRate > 0 && totalSamples > 0 {
duration = int(totalSamples / int64(sampleRate))
}
return AudioQuality{
BitDepth: bitDepth,
SampleRate: sampleRate,
TotalSamples: totalSamples,
Duration: duration,
Codec: "flac",
}
}
func audioQualityFromParsedFlac(f *flac.File) (AudioQuality, error) {
for _, meta := range f.Meta {
if meta.Type != flac.StreamInfo || len(meta.Data) < 18 {
continue
}
streamInfo := meta.Data
sampleRate := (int(streamInfo[10]) << 12) | (int(streamInfo[11]) << 4) | (int(streamInfo[12]) >> 4)
bitsPerSample := ((int(streamInfo[12]) & 0x01) << 4) | (int(streamInfo[13]) >> 4) + 1
totalSamples := int64(streamInfo[13]&0x0F)<<32 |
int64(streamInfo[14])<<24 |
int64(streamInfo[15])<<16 |
int64(streamInfo[16])<<8 |
int64(streamInfo[17])
duration := 0
if sampleRate > 0 && totalSamples > 0 {
duration = int(totalSamples / int64(sampleRate))
}
return AudioQuality{
BitDepth: bitsPerSample,
SampleRate: sampleRate,
TotalSamples: totalSamples,
Duration: duration,
Codec: "flac",
}, nil
return flacAudioQualityFromStreamInfo(meta.Data), nil
}
return AudioQuality{}, fmt.Errorf("FLAC STREAMINFO block not found")
}
@@ -1717,28 +1714,7 @@ func GetAudioQuality(filePath string) (AudioQuality, error) {
return AudioQuality{}, fmt.Errorf("failed to read STREAMINFO: %w", err)
}
sampleRate := (int(streamInfo[10]) << 12) | (int(streamInfo[11]) << 4) | (int(streamInfo[12]) >> 4)
bitsPerSample := ((int(streamInfo[12]) & 0x01) << 4) | (int(streamInfo[13]) >> 4) + 1
totalSamples := int64(streamInfo[13]&0x0F)<<32 |
int64(streamInfo[14])<<24 |
int64(streamInfo[15])<<16 |
int64(streamInfo[16])<<8 |
int64(streamInfo[17])
duration := 0
if sampleRate > 0 && totalSamples > 0 {
duration = int(totalSamples / int64(sampleRate))
}
return AudioQuality{
BitDepth: bitsPerSample,
SampleRate: sampleRate,
TotalSamples: totalSamples,
Duration: duration,
Codec: "flac",
}, nil
return flacAudioQualityFromStreamInfo(streamInfo), nil
}
file.Seek(0, 0)