.refine artist fetch all tracks
This commit is contained in:
@@ -142,12 +142,27 @@ func (a *App) GetSpotifyMetadata(req SpotifyMetadataRequest) (string, error) {
|
||||
defer cancel()
|
||||
|
||||
settings, err := a.LoadSettings()
|
||||
separator := req.Separator
|
||||
if separator == "" {
|
||||
separator = ", "
|
||||
if err == nil && settings != nil {
|
||||
if sep, ok := settings["separator"].(string); ok {
|
||||
if sep == "semicolon" {
|
||||
separator = "; "
|
||||
} else if sep == "comma" {
|
||||
separator = ", "
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil && settings != nil {
|
||||
if useAPI, ok := settings["useSpotFetchAPI"].(bool); ok && useAPI {
|
||||
if apiURL, ok := settings["spotFetchAPIUrl"].(string); ok && apiURL != "" {
|
||||
|
||||
data, err := backend.GetSpotifyDataWithAPI(ctx, req.URL, true, apiURL, req.Batch, time.Duration(req.Delay*float64(time.Second)))
|
||||
data, err := backend.GetSpotifyDataWithAPI(ctx, req.URL, true, apiURL, req.Batch, time.Duration(req.Delay*float64(time.Second)), separator, func(tracks interface{}) {
|
||||
runtime.EventsEmit(a.ctx, "metadata-stream", tracks)
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to fetch metadata from API: %v", err)
|
||||
}
|
||||
@@ -162,7 +177,9 @@ func (a *App) GetSpotifyMetadata(req SpotifyMetadataRequest) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
data, err := backend.GetFilteredSpotifyData(ctx, req.URL, req.Batch, time.Duration(req.Delay*float64(time.Second)))
|
||||
data, err := backend.GetFilteredSpotifyData(ctx, req.URL, req.Batch, time.Duration(req.Delay*float64(time.Second)), separator, func(tracks interface{}) {
|
||||
runtime.EventsEmit(a.ctx, "metadata-stream", tracks)
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to fetch metadata: %v", err)
|
||||
}
|
||||
@@ -283,7 +300,21 @@ func (a *App) DownloadTrack(req DownloadRequest) (DownloadResponse, error) {
|
||||
defer cancel()
|
||||
|
||||
trackURL := fmt.Sprintf("https://open.spotify.com/track/%s", req.SpotifyID)
|
||||
trackData, err := backend.GetFilteredSpotifyData(ctx, trackURL, false, 0)
|
||||
metadataSeparator := req.Separator
|
||||
if metadataSeparator == "" {
|
||||
metadataSeparator = ", "
|
||||
metadataSettings, _ := a.LoadSettings()
|
||||
if metadataSettings != nil {
|
||||
if sep, ok := metadataSettings["separator"].(string); ok {
|
||||
if sep == "semicolon" {
|
||||
metadataSeparator = "; "
|
||||
} else if sep == "comma" {
|
||||
metadataSeparator = ", "
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
trackData, err := backend.GetFilteredSpotifyData(ctx, trackURL, false, 0, metadataSeparator, nil)
|
||||
if err == nil {
|
||||
|
||||
var trackResp struct {
|
||||
|
||||
+14
-14
@@ -485,7 +485,7 @@ func extractDuration(ms float64) map[string]interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
func FilterTrack(data map[string]interface{}, albumFetchData ...map[string]interface{}) map[string]interface{} {
|
||||
func FilterTrack(data map[string]interface{}, separator string, albumFetchData ...map[string]interface{}) map[string]interface{} {
|
||||
dataMap := getMap(data, "data")
|
||||
trackData := getMap(dataMap, "trackUnion")
|
||||
if len(trackData) == 0 {
|
||||
@@ -665,7 +665,7 @@ func FilterTrack(data map[string]interface{}, albumFetchData ...map[string]inter
|
||||
for _, artist := range albumArtists {
|
||||
albumArtistNames = append(albumArtistNames, getString(artist, "name"))
|
||||
}
|
||||
albumArtistsString = strings.Join(albumArtistNames, GetSeparator())
|
||||
albumArtistsString = strings.Join(albumArtistNames, separator)
|
||||
}
|
||||
if albumArtistsString == "" {
|
||||
albumArtistsString = getString(albumUnionData, "artists")
|
||||
@@ -681,7 +681,7 @@ func FilterTrack(data map[string]interface{}, albumFetchData ...map[string]inter
|
||||
for _, artist := range albumArtists {
|
||||
albumArtistNames = append(albumArtistNames, getString(artist, "name"))
|
||||
}
|
||||
albumArtistsString = strings.Join(albumArtistNames, GetSeparator())
|
||||
albumArtistsString = strings.Join(albumArtistNames, separator)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -715,7 +715,7 @@ func FilterTrack(data map[string]interface{}, albumFetchData ...map[string]inter
|
||||
for _, artist := range artists {
|
||||
artistNames = append(artistNames, getString(artist, "name"))
|
||||
}
|
||||
artistsString := strings.Join(artistNames, GetSeparator())
|
||||
artistsString := strings.Join(artistNames, separator)
|
||||
|
||||
copyrightTexts := []string{}
|
||||
for _, item := range copyrightInfo {
|
||||
@@ -802,7 +802,7 @@ func FilterTrack(data map[string]interface{}, albumFetchData ...map[string]inter
|
||||
return filtered
|
||||
}
|
||||
|
||||
func FilterAlbum(data map[string]interface{}) map[string]interface{} {
|
||||
func FilterAlbum(data map[string]interface{}, separator string) map[string]interface{} {
|
||||
dataMap := getMap(data, "data")
|
||||
albumData := getMap(dataMap, "albumUnion")
|
||||
if len(albumData) == 0 {
|
||||
@@ -814,7 +814,7 @@ func FilterAlbum(data map[string]interface{}) map[string]interface{} {
|
||||
for _, artist := range artists {
|
||||
artistNames = append(artistNames, getString(artist, "name"))
|
||||
}
|
||||
albumArtistsString := strings.Join(artistNames, GetSeparator())
|
||||
albumArtistsString := strings.Join(artistNames, separator)
|
||||
|
||||
coverObj := extractCoverImage(getMap(albumData, "coverArt"))
|
||||
var cover interface{}
|
||||
@@ -875,7 +875,7 @@ func FilterAlbum(data map[string]interface{}) map[string]interface{} {
|
||||
for _, artist := range trackArtists {
|
||||
trackArtistNames = append(trackArtistNames, getString(artist, "name"))
|
||||
}
|
||||
trackArtistsString := strings.Join(trackArtistNames, GetSeparator())
|
||||
trackArtistsString := strings.Join(trackArtistNames, separator)
|
||||
|
||||
trackURI := getString(track, "uri")
|
||||
trackID := ""
|
||||
@@ -943,7 +943,7 @@ func FilterAlbum(data map[string]interface{}) map[string]interface{} {
|
||||
return filtered
|
||||
}
|
||||
|
||||
func FilterPlaylist(data map[string]interface{}) map[string]interface{} {
|
||||
func FilterPlaylist(data map[string]interface{}, separator string) map[string]interface{} {
|
||||
dataMap := getMap(data, "data")
|
||||
playlistData := getMap(dataMap, "playlistV2")
|
||||
if len(playlistData) == 0 {
|
||||
@@ -1075,7 +1075,7 @@ func FilterPlaylist(data map[string]interface{}) map[string]interface{} {
|
||||
for _, artist := range trackArtists {
|
||||
trackArtistNames = append(trackArtistNames, getString(artist, "name"))
|
||||
}
|
||||
artistsString := strings.Join(trackArtistNames, GetSeparator())
|
||||
artistsString := strings.Join(trackArtistNames, separator)
|
||||
|
||||
trackDurationMs := getFloat64(getMap(trackData, "trackDuration"), "totalMilliseconds")
|
||||
durationObj := extractDuration(trackDurationMs)
|
||||
@@ -1121,7 +1121,7 @@ func FilterPlaylist(data map[string]interface{}) map[string]interface{} {
|
||||
for _, artist := range albumArtists {
|
||||
albumArtistNames = append(albumArtistNames, getString(artist, "name"))
|
||||
}
|
||||
albumArtistsString = strings.Join(albumArtistNames, GetSeparator())
|
||||
albumArtistsString = strings.Join(albumArtistNames, separator)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1295,7 +1295,7 @@ func stripHTMLTags(s string) string {
|
||||
return re.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
func FilterArtist(data map[string]interface{}) map[string]interface{} {
|
||||
func FilterArtist(data map[string]interface{}, separator string) map[string]interface{} {
|
||||
dataMap := getMap(data, "data")
|
||||
artistData := getMap(dataMap, "artistUnion")
|
||||
if len(artistData) == 0 {
|
||||
@@ -1424,7 +1424,7 @@ func FilterArtist(data map[string]interface{}) map[string]interface{} {
|
||||
return filtered
|
||||
}
|
||||
|
||||
func FilterSearch(data map[string]interface{}) map[string]interface{} {
|
||||
func FilterSearch(data map[string]interface{}, separator string) map[string]interface{} {
|
||||
dataMap := getMap(data, "data")
|
||||
searchData := getMap(dataMap, "searchV2")
|
||||
if len(searchData) == 0 {
|
||||
@@ -1514,7 +1514,7 @@ func FilterSearch(data map[string]interface{}) map[string]interface{} {
|
||||
for _, artist := range trackArtists {
|
||||
trackArtistNames = append(trackArtistNames, getString(artist, "name"))
|
||||
}
|
||||
trackArtistsString := strings.Join(trackArtistNames, GetSeparator())
|
||||
trackArtistsString := strings.Join(trackArtistNames, separator)
|
||||
|
||||
durationString := getString(trackDuration, "formatted")
|
||||
|
||||
@@ -1586,7 +1586,7 @@ func FilterSearch(data map[string]interface{}) map[string]interface{} {
|
||||
for _, artist := range albumArtists {
|
||||
albumArtistNames = append(albumArtistNames, getString(artist, "name"))
|
||||
}
|
||||
albumArtistsString := strings.Join(albumArtistNames, GetSeparator())
|
||||
albumArtistsString := strings.Join(albumArtistNames, separator)
|
||||
|
||||
dateInfo := getMap(album, "date")
|
||||
var year interface{}
|
||||
|
||||
@@ -11,10 +11,9 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetSpotifyDataWithAPI(ctx context.Context, spotifyURL string, useAPI bool, apiBaseURL string, batch bool, delay time.Duration) (interface{}, error) {
|
||||
func GetSpotifyDataWithAPI(ctx context.Context, spotifyURL string, useAPI bool, apiBaseURL string, batch bool, delay time.Duration, separator string, callback MetadataCallback) (interface{}, error) {
|
||||
if !useAPI || apiBaseURL == "" {
|
||||
|
||||
return GetFilteredSpotifyData(ctx, spotifyURL, batch, delay)
|
||||
return GetFilteredSpotifyData(ctx, spotifyURL, batch, delay, separator, callback)
|
||||
}
|
||||
|
||||
spotifyType, id := parseSpotifyURLToTypeAndID(spotifyURL)
|
||||
@@ -79,6 +78,43 @@ func GetSpotifyDataWithAPI(ctx context.Context, spotifyURL string, useAPI bool,
|
||||
return nil, fmt.Errorf("unsupported Spotify type: %s", spotifyType)
|
||||
}
|
||||
|
||||
if callback != nil {
|
||||
switch payload := data.(type) {
|
||||
case *AlbumResponsePayload:
|
||||
if len(payload.TrackList) > 0 {
|
||||
callback(payload.TrackList)
|
||||
}
|
||||
case PlaylistResponsePayload:
|
||||
if len(payload.TrackList) > 0 {
|
||||
callback(payload.TrackList)
|
||||
}
|
||||
case *ArtistDiscographyPayload:
|
||||
if len(payload.TrackList) > 0 {
|
||||
callback(payload.TrackList)
|
||||
}
|
||||
case TrackResponse:
|
||||
t := payload.Track
|
||||
callback([]AlbumTrackMetadata{{
|
||||
SpotifyID: t.SpotifyID,
|
||||
Artists: t.Artists,
|
||||
Name: t.Name,
|
||||
AlbumName: t.AlbumName,
|
||||
AlbumArtist: t.AlbumArtist,
|
||||
DurationMS: t.DurationMS,
|
||||
Images: t.Images,
|
||||
ReleaseDate: t.ReleaseDate,
|
||||
TrackNumber: t.TrackNumber,
|
||||
TotalTracks: t.TotalTracks,
|
||||
DiscNumber: t.DiscNumber,
|
||||
TotalDiscs: t.TotalDiscs,
|
||||
ExternalURL: t.ExternalURL,
|
||||
Plays: t.Plays,
|
||||
PreviewURL: t.PreviewURL,
|
||||
IsExplicit: t.IsExplicit,
|
||||
}})
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
|
||||
+100
-30
@@ -18,13 +18,17 @@ var (
|
||||
errInvalidSpotifyURL = errors.New("invalid or unsupported Spotify URL")
|
||||
)
|
||||
|
||||
type MetadataCallback func(data interface{})
|
||||
|
||||
type SpotifyMetadataClient struct {
|
||||
httpClient *http.Client
|
||||
Separator string
|
||||
}
|
||||
|
||||
func NewSpotifyMetadataClient() *SpotifyMetadataClient {
|
||||
return &SpotifyMetadataClient{
|
||||
httpClient: &http.Client{Timeout: 30 * time.Second},
|
||||
Separator: ", ",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,54 +346,57 @@ type SearchResponse struct {
|
||||
Playlists []SearchResult `json:"playlists"`
|
||||
}
|
||||
|
||||
func GetFilteredSpotifyData(ctx context.Context, spotifyURL string, batch bool, delay time.Duration) (interface{}, error) {
|
||||
func GetFilteredSpotifyData(ctx context.Context, spotifyURL string, batch bool, delay time.Duration, separator string, callback MetadataCallback) (interface{}, error) {
|
||||
client := NewSpotifyMetadataClient()
|
||||
return client.GetFilteredData(ctx, spotifyURL, batch, delay)
|
||||
if separator != "" {
|
||||
client.Separator = separator
|
||||
}
|
||||
return client.GetFilteredData(ctx, spotifyURL, batch, delay, callback)
|
||||
}
|
||||
|
||||
func (c *SpotifyMetadataClient) GetFilteredData(ctx context.Context, spotifyURL string, batch bool, delay time.Duration) (interface{}, error) {
|
||||
func (c *SpotifyMetadataClient) GetFilteredData(ctx context.Context, spotifyURL string, batch bool, delay time.Duration, callback MetadataCallback) (interface{}, error) {
|
||||
parsed, err := parseSpotifyURI(spotifyURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw, err := c.getRawSpotifyData(ctx, parsed, batch, delay)
|
||||
raw, err := c.getRawSpotifyData(ctx, parsed, batch, delay, callback)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.processSpotifyData(ctx, raw)
|
||||
return c.processSpotifyData(ctx, raw, callback)
|
||||
}
|
||||
|
||||
func (c *SpotifyMetadataClient) getRawSpotifyData(ctx context.Context, parsed spotifyURI, batch bool, delay time.Duration) (interface{}, error) {
|
||||
func (c *SpotifyMetadataClient) getRawSpotifyData(ctx context.Context, parsed spotifyURI, batch bool, delay time.Duration, callback MetadataCallback) (interface{}, error) {
|
||||
switch parsed.Type {
|
||||
case "playlist":
|
||||
return c.fetchPlaylist(ctx, parsed.ID)
|
||||
return c.fetchPlaylist(ctx, parsed.ID, callback)
|
||||
case "album":
|
||||
return c.fetchAlbum(ctx, parsed.ID)
|
||||
return c.fetchAlbum(ctx, parsed.ID, callback)
|
||||
case "track":
|
||||
return c.fetchTrack(ctx, parsed.ID)
|
||||
case "artist_discography":
|
||||
return c.fetchArtistDiscography(ctx, parsed)
|
||||
return c.fetchArtistDiscography(ctx, parsed, callback)
|
||||
case "artist":
|
||||
|
||||
discographyParsed := spotifyURI{Type: "artist_discography", ID: parsed.ID, DiscographyGroup: "all"}
|
||||
return c.fetchArtistDiscography(ctx, discographyParsed)
|
||||
return c.fetchArtistDiscography(ctx, discographyParsed, callback)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported Spotify type: %s", parsed.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SpotifyMetadataClient) processSpotifyData(ctx context.Context, raw interface{}) (interface{}, error) {
|
||||
func (c *SpotifyMetadataClient) processSpotifyData(ctx context.Context, raw interface{}, callback MetadataCallback) (interface{}, error) {
|
||||
switch payload := raw.(type) {
|
||||
case *apiPlaylistResponse:
|
||||
return c.formatPlaylistData(payload), nil
|
||||
return c.formatPlaylistData(payload, callback), nil
|
||||
case *apiAlbumResponse:
|
||||
return c.formatAlbumData(payload)
|
||||
return c.formatAlbumData(payload, callback)
|
||||
case *apiTrackResponse:
|
||||
return c.formatTrackData(payload), nil
|
||||
case *apiArtistResponse:
|
||||
return c.formatArtistDiscographyData(ctx, payload)
|
||||
return c.formatArtistDiscographyData(ctx, payload, callback)
|
||||
default:
|
||||
return nil, errors.New("unknown raw payload type")
|
||||
}
|
||||
@@ -437,7 +444,7 @@ func (c *SpotifyMetadataClient) fetchTrack(ctx context.Context, trackID string)
|
||||
|
||||
if albumID != "" {
|
||||
|
||||
albumResponse, err := c.fetchAlbumWithClient(ctx, client, albumID)
|
||||
albumResponse, err := c.fetchAlbumWithClient(ctx, client, albumID, nil)
|
||||
if err == nil && albumResponse != nil {
|
||||
|
||||
albumJSON, _ := json.Marshal(albumResponse)
|
||||
@@ -482,7 +489,7 @@ func (c *SpotifyMetadataClient) fetchTrack(ctx context.Context, trackID string)
|
||||
}
|
||||
}
|
||||
|
||||
filteredData := FilterTrack(data, albumFetchData)
|
||||
filteredData := FilterTrack(data, c.Separator, albumFetchData)
|
||||
|
||||
jsonData, err := json.Marshal(filteredData)
|
||||
if err != nil {
|
||||
@@ -497,15 +504,15 @@ func (c *SpotifyMetadataClient) fetchTrack(ctx context.Context, trackID string)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (c *SpotifyMetadataClient) fetchAlbum(ctx context.Context, albumID string) (*apiAlbumResponse, error) {
|
||||
func (c *SpotifyMetadataClient) fetchAlbum(ctx context.Context, albumID string, callback MetadataCallback) (*apiAlbumResponse, error) {
|
||||
client := NewSpotifyClient()
|
||||
if err := client.Initialize(); err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize spotify client: %w", err)
|
||||
}
|
||||
return c.fetchAlbumWithClient(ctx, client, albumID)
|
||||
return c.fetchAlbumWithClient(ctx, client, albumID, callback)
|
||||
}
|
||||
|
||||
func (c *SpotifyMetadataClient) fetchAlbumWithClient(ctx context.Context, client *SpotifyClient, albumID string) (*apiAlbumResponse, error) {
|
||||
func (c *SpotifyMetadataClient) fetchAlbumWithClient(ctx context.Context, client *SpotifyClient, albumID string, callback MetadataCallback) (*apiAlbumResponse, error) {
|
||||
|
||||
allItems := []interface{}{}
|
||||
offset := 0
|
||||
@@ -537,6 +544,15 @@ func (c *SpotifyMetadataClient) fetchAlbumWithClient(ctx context.Context, client
|
||||
|
||||
if data == nil {
|
||||
data = response
|
||||
if callback != nil {
|
||||
filtered := FilterAlbum(data, c.Separator)
|
||||
jsonData, _ := json.Marshal(filtered)
|
||||
var result apiAlbumResponse
|
||||
if json.Unmarshal(jsonData, &result) == nil {
|
||||
formatted, _ := c.formatAlbumData(&result, nil)
|
||||
callback(formatted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
albumData := getMap(getMap(response, "data"), "albumUnion")
|
||||
@@ -579,7 +595,7 @@ func (c *SpotifyMetadataClient) fetchAlbumWithClient(ctx context.Context, client
|
||||
tracksV2["totalCount"] = len(allItems)
|
||||
}
|
||||
|
||||
filteredData := FilterAlbum(data)
|
||||
filteredData := FilterAlbum(data, c.Separator)
|
||||
|
||||
jsonData, err := json.Marshal(filteredData)
|
||||
if err != nil {
|
||||
@@ -594,7 +610,7 @@ func (c *SpotifyMetadataClient) fetchAlbumWithClient(ctx context.Context, client
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (c *SpotifyMetadataClient) fetchPlaylist(ctx context.Context, playlistID string) (*apiPlaylistResponse, error) {
|
||||
func (c *SpotifyMetadataClient) fetchPlaylist(ctx context.Context, playlistID string, callback MetadataCallback) (*apiPlaylistResponse, error) {
|
||||
client := NewSpotifyClient()
|
||||
if err := client.Initialize(); err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize spotify client: %w", err)
|
||||
@@ -630,6 +646,15 @@ func (c *SpotifyMetadataClient) fetchPlaylist(ctx context.Context, playlistID st
|
||||
|
||||
if data == nil {
|
||||
data = response
|
||||
if callback != nil {
|
||||
filtered := FilterPlaylist(data, c.Separator)
|
||||
jsonData, _ := json.Marshal(filtered)
|
||||
var result apiPlaylistResponse
|
||||
if json.Unmarshal(jsonData, &result) == nil {
|
||||
formatted := c.formatPlaylistData(&result, nil)
|
||||
callback(formatted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playlistData := getMap(getMap(response, "data"), "playlistV2")
|
||||
@@ -672,7 +697,7 @@ func (c *SpotifyMetadataClient) fetchPlaylist(ctx context.Context, playlistID st
|
||||
content["totalCount"] = len(allItems)
|
||||
}
|
||||
|
||||
filteredData := FilterPlaylist(data)
|
||||
filteredData := FilterPlaylist(data, c.Separator)
|
||||
|
||||
jsonData, err := json.Marshal(filteredData)
|
||||
if err != nil {
|
||||
@@ -687,7 +712,7 @@ func (c *SpotifyMetadataClient) fetchPlaylist(ctx context.Context, playlistID st
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (c *SpotifyMetadataClient) fetchArtistDiscography(ctx context.Context, parsed spotifyURI) (*apiArtistResponse, error) {
|
||||
func (c *SpotifyMetadataClient) fetchArtistDiscography(ctx context.Context, parsed spotifyURI, callback MetadataCallback) (*apiArtistResponse, error) {
|
||||
client := NewSpotifyClient()
|
||||
if err := client.Initialize(); err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize spotify client: %w", err)
|
||||
@@ -712,6 +737,16 @@ func (c *SpotifyMetadataClient) fetchArtistDiscography(ctx context.Context, pars
|
||||
return nil, fmt.Errorf("failed to query artist overview: %w", err)
|
||||
}
|
||||
|
||||
if callback != nil {
|
||||
filtered := FilterArtist(data, c.Separator)
|
||||
jsonData, _ := json.Marshal(filtered)
|
||||
var result apiArtistResponse
|
||||
if json.Unmarshal(jsonData, &result) == nil {
|
||||
formatted, _ := c.formatArtistDiscographyData(ctx, &result, nil)
|
||||
callback(formatted)
|
||||
}
|
||||
}
|
||||
|
||||
allDiscographyItems := []interface{}{}
|
||||
offset := 0
|
||||
limit := 50
|
||||
@@ -841,7 +876,7 @@ func (c *SpotifyMetadataClient) fetchArtistDiscography(ctx context.Context, pars
|
||||
}
|
||||
}
|
||||
|
||||
filteredData := FilterArtist(data)
|
||||
filteredData := FilterArtist(data, c.Separator)
|
||||
|
||||
jsonData, err := json.Marshal(filteredData)
|
||||
if err != nil {
|
||||
@@ -898,7 +933,7 @@ func (c *SpotifyMetadataClient) formatTrackData(raw *apiTrackResponse) TrackResp
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SpotifyMetadataClient) formatAlbumData(raw *apiAlbumResponse) (*AlbumResponsePayload, error) {
|
||||
func (c *SpotifyMetadataClient) formatAlbumData(raw *apiAlbumResponse, callback MetadataCallback) (*AlbumResponsePayload, error) {
|
||||
var artistID, artistURL string
|
||||
|
||||
info := AlbumInfoMetadata{
|
||||
@@ -911,6 +946,13 @@ func (c *SpotifyMetadataClient) formatAlbumData(raw *apiAlbumResponse) (*AlbumRe
|
||||
ArtistURL: artistURL,
|
||||
}
|
||||
|
||||
if callback != nil {
|
||||
callback(AlbumResponsePayload{
|
||||
AlbumInfo: info,
|
||||
TrackList: []AlbumTrackMetadata{},
|
||||
})
|
||||
}
|
||||
|
||||
tracks := make([]AlbumTrackMetadata, 0, len(raw.Tracks))
|
||||
for idx, item := range raw.Tracks {
|
||||
durationMS := parseDuration(item.Duration)
|
||||
@@ -955,13 +997,17 @@ func (c *SpotifyMetadataClient) formatAlbumData(raw *apiAlbumResponse) (*AlbumRe
|
||||
})
|
||||
}
|
||||
|
||||
if callback != nil {
|
||||
callback(tracks)
|
||||
}
|
||||
|
||||
return &AlbumResponsePayload{
|
||||
AlbumInfo: info,
|
||||
TrackList: tracks,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *SpotifyMetadataClient) formatPlaylistData(raw *apiPlaylistResponse) PlaylistResponsePayload {
|
||||
func (c *SpotifyMetadataClient) formatPlaylistData(raw *apiPlaylistResponse, callback MetadataCallback) PlaylistResponsePayload {
|
||||
var info PlaylistInfoMetadata
|
||||
info.Tracks.Total = raw.Count
|
||||
info.Followers.Total = raw.Followers
|
||||
@@ -971,6 +1017,13 @@ func (c *SpotifyMetadataClient) formatPlaylistData(raw *apiPlaylistResponse) Pla
|
||||
info.Cover = raw.Cover
|
||||
info.Description = raw.Description
|
||||
|
||||
if callback != nil {
|
||||
callback(PlaylistResponsePayload{
|
||||
PlaylistInfo: info,
|
||||
TrackList: []AlbumTrackMetadata{},
|
||||
})
|
||||
}
|
||||
|
||||
tracks := make([]AlbumTrackMetadata, 0, len(raw.Tracks))
|
||||
for _, item := range raw.Tracks {
|
||||
durationMS := parseDuration(item.Duration)
|
||||
@@ -1015,13 +1068,17 @@ func (c *SpotifyMetadataClient) formatPlaylistData(raw *apiPlaylistResponse) Pla
|
||||
})
|
||||
}
|
||||
|
||||
if callback != nil {
|
||||
callback(tracks)
|
||||
}
|
||||
|
||||
return PlaylistResponsePayload{
|
||||
PlaylistInfo: info,
|
||||
TrackList: tracks,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SpotifyMetadataClient) formatArtistDiscographyData(ctx context.Context, raw *apiArtistResponse) (*ArtistDiscographyPayload, error) {
|
||||
func (c *SpotifyMetadataClient) formatArtistDiscographyData(ctx context.Context, raw *apiArtistResponse, callback MetadataCallback) (*ArtistDiscographyPayload, error) {
|
||||
discType := "all"
|
||||
|
||||
info := ArtistInfoMetadata{
|
||||
@@ -1067,7 +1124,17 @@ func (c *SpotifyMetadataClient) formatArtistDiscographyData(ctx context.Context,
|
||||
Images: alb.Cover,
|
||||
ExternalURL: fmt.Sprintf("https://open.spotify.com/album/%s", alb.ID),
|
||||
})
|
||||
}
|
||||
|
||||
if callback != nil {
|
||||
callback(ArtistDiscographyPayload{
|
||||
ArtistInfo: info,
|
||||
AlbumList: albumList,
|
||||
TrackList: []AlbumTrackMetadata{},
|
||||
})
|
||||
}
|
||||
|
||||
for _, alb := range raw.Discography.All {
|
||||
go func(albumID string, albumName string) {
|
||||
sem <- struct{}{}
|
||||
|
||||
@@ -1081,7 +1148,7 @@ func (c *SpotifyMetadataClient) formatArtistDiscographyData(ctx context.Context,
|
||||
default:
|
||||
}
|
||||
|
||||
albumData, err := c.fetchAlbumWithClient(ctx, sharedClient, albumID)
|
||||
albumData, err := c.fetchAlbumWithClient(ctx, sharedClient, albumID, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting tracks for album %s: %v\n", albumName, err)
|
||||
resultsChan <- fetchResult{tracks: []AlbumTrackMetadata{}}
|
||||
@@ -1131,6 +1198,9 @@ func (c *SpotifyMetadataClient) formatArtistDiscographyData(ctx context.Context,
|
||||
IsExplicit: tr.IsExplicit,
|
||||
})
|
||||
}
|
||||
if callback != nil {
|
||||
callback(tracks)
|
||||
}
|
||||
resultsChan <- fetchResult{tracks: tracks}
|
||||
}(alb.ID, alb.Name)
|
||||
}
|
||||
@@ -1290,7 +1360,7 @@ func (c *SpotifyMetadataClient) Search(ctx context.Context, query string, limit
|
||||
return nil, fmt.Errorf("failed to query search: %w", err)
|
||||
}
|
||||
|
||||
filteredData := FilterSearch(data)
|
||||
filteredData := FilterSearch(data, c.Separator)
|
||||
|
||||
jsonData, err := json.Marshal(filteredData)
|
||||
if err != nil {
|
||||
@@ -1407,7 +1477,7 @@ func (c *SpotifyMetadataClient) SearchByType(ctx context.Context, query string,
|
||||
return nil, fmt.Errorf("failed to query search: %w", err)
|
||||
}
|
||||
|
||||
filteredData := FilterSearch(data)
|
||||
filteredData := FilterSearch(data, c.Separator)
|
||||
|
||||
jsonData, err := json.Marshal(filteredData)
|
||||
if err != nil {
|
||||
|
||||
@@ -21,6 +21,7 @@ interface ArtistInfoProps {
|
||||
header?: string;
|
||||
gallery?: string[];
|
||||
followers: number;
|
||||
total_albums?: number;
|
||||
genres: string[];
|
||||
biography?: string;
|
||||
verified?: boolean;
|
||||
@@ -99,6 +100,7 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
|
||||
const [downloadingGalleryIndex, setDownloadingGalleryIndex] = useState<number | null>(null);
|
||||
const [downloadingAllGallery, setDownloadingAllGallery] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<"albums" | "tracks" | "gallery">("albums");
|
||||
const displayedAlbumCount = artistInfo.total_albums || albumList.length;
|
||||
const filteredAlbumGroups = useMemo(() => {
|
||||
const albumTypeMap = new Map(albumList.map(a => [a.name, a.album_type]));
|
||||
const albumGroups = trackList.reduce((acc, track) => {
|
||||
@@ -330,9 +332,9 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
|
||||
</>)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm flex-wrap text-white/90">
|
||||
<span>{albumList.length} {albumList.length === 1 ? "album" : "albums"}</span>
|
||||
<span>{displayedAlbumCount.toLocaleString()} {displayedAlbumCount === 1 ? "album" : "albums"}</span>
|
||||
<span>•</span>
|
||||
<span>{trackList.length} {trackList.length === 1 ? "track" : "tracks"}</span>
|
||||
<span>{trackList.length.toLocaleString()} {trackList.length === 1 ? "track" : "tracks"}</span>
|
||||
{artistInfo.genres.length > 0 && (<>
|
||||
<span>•</span>
|
||||
<span>{artistInfo.genres.join(", ")}</span>
|
||||
@@ -383,9 +385,9 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
|
||||
</>)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm flex-wrap">
|
||||
<span>{albumList.length} {albumList.length === 1 ? "album" : "albums"}</span>
|
||||
<span>{displayedAlbumCount.toLocaleString()} {displayedAlbumCount === 1 ? "album" : "albums"}</span>
|
||||
<span>•</span>
|
||||
<span>{trackList.length} {trackList.length === 1 ? "track" : "tracks"}</span>
|
||||
<span>{trackList.length.toLocaleString()} {trackList.length === 1 ? "track" : "tracks"}</span>
|
||||
{artistInfo.genres.length > 0 && (<>
|
||||
<span>•</span>
|
||||
<span>{artistInfo.genres.join(", ")}</span>
|
||||
@@ -412,7 +414,7 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
|
||||
|
||||
{activeTab === "gallery" && hasGallery && (<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-2xl font-bold">Gallery ({artistInfo.gallery!.length})</h3>
|
||||
<h3 className="text-2xl font-bold">Gallery ({artistInfo.gallery!.length.toLocaleString()})</h3>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button onClick={handleDownloadAllGallery} size="sm" variant="outline" disabled={downloadingAllGallery}>
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { getSettings } from "@/lib/settings";
|
||||
import { fetchSpotifyMetadata } from "@/lib/api";
|
||||
import { toastWithSound as toast } from "@/lib/toast-with-sound";
|
||||
import { logger } from "@/lib/logger";
|
||||
import { AddFetchHistory } from "../../wailsjs/go/main/App";
|
||||
import { EventsOff, EventsOn } from "../../wailsjs/runtime/runtime";
|
||||
import type { SpotifyMetadataResponse } from "@/types/api";
|
||||
export function useMetadata() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [metadata, setMetadata] = useState<SpotifyMetadataResponse | null>(null);
|
||||
const loadingToastId = useRef<string | number | null>(null);
|
||||
const fetchedCount = useRef(0);
|
||||
const currentName = useRef("");
|
||||
const [showApiModal, setShowApiModal] = useState(false);
|
||||
const [showAlbumDialog, setShowAlbumDialog] = useState(false);
|
||||
const [selectedAlbum, setSelectedAlbum] = useState<{
|
||||
@@ -16,6 +20,73 @@ export function useMetadata() {
|
||||
external_urls: string;
|
||||
} | null>(null);
|
||||
const [pendingArtistName, setPendingArtistName] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
fetchedCount.current = 0;
|
||||
currentName.current = "";
|
||||
loadingToastId.current = toast.silentInfo("fetching metadata...", {
|
||||
duration: Infinity,
|
||||
description: "please wait while we retrieve the information"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (loadingToastId.current) {
|
||||
toast.dismiss(loadingToastId.current);
|
||||
loadingToastId.current = null;
|
||||
}
|
||||
}, [loading]);
|
||||
useEffect(() => {
|
||||
const handler = (data: any) => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(data)) {
|
||||
fetchedCount.current += data.length;
|
||||
if (loadingToastId.current && currentName.current) {
|
||||
toast.silentInfo(`fetching tracks for ${currentName.current.toLowerCase()}...`, {
|
||||
id: loadingToastId.current,
|
||||
description: `${fetchedCount.current.toLocaleString()} tracks fetched`
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
const baseInfo = data;
|
||||
const name = "artist_info" in baseInfo ? baseInfo.artist_info.name :
|
||||
"album_info" in baseInfo ? baseInfo.album_info.name :
|
||||
"playlist_info" in baseInfo ? (baseInfo.playlist_info.name || baseInfo.playlist_info.owner.name) : "";
|
||||
if (name) {
|
||||
currentName.current = name;
|
||||
if (loadingToastId.current) {
|
||||
toast.silentInfo(`fetching tracks for ${name.toLowerCase()}...`, {
|
||||
id: loadingToastId.current,
|
||||
description: `${fetchedCount.current.toLocaleString()} tracks fetched`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
setMetadata(prev => {
|
||||
if (Array.isArray(data)) {
|
||||
if (!prev || !("track_list" in prev)) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
track_list: [...prev.track_list, ...data]
|
||||
};
|
||||
}
|
||||
if (prev && "track_list" in prev && prev.track_list.length > 0) {
|
||||
return prev;
|
||||
}
|
||||
const baseInfo = data;
|
||||
if (!("track_list" in baseInfo)) {
|
||||
baseInfo.track_list = [];
|
||||
}
|
||||
return baseInfo;
|
||||
});
|
||||
};
|
||||
EventsOn("metadata-stream", handler);
|
||||
return () => EventsOff("metadata-stream");
|
||||
}, []);
|
||||
const getUrlType = (url: string): string => {
|
||||
if (url.includes("/track/"))
|
||||
return "track";
|
||||
|
||||
@@ -5,41 +5,49 @@ import { getSettings } from "./settings";
|
||||
const toastStyle = {
|
||||
className: "font-mono lowercase",
|
||||
};
|
||||
type ToastData = Parameters<typeof toast.success>[1];
|
||||
const isSfxEnabled = () => getSettings().sfxEnabled;
|
||||
export const toastWithSound = {
|
||||
success: (message: string, data?: any) => {
|
||||
success: (message: string, data?: ToastData) => {
|
||||
const msg = message.toLowerCase();
|
||||
logger.success(msg);
|
||||
if (isSfxEnabled())
|
||||
playSuccessSound();
|
||||
return toast.success(msg, { ...toastStyle, ...data });
|
||||
},
|
||||
error: (message: string, data?: any) => {
|
||||
error: (message: string, data?: ToastData) => {
|
||||
const msg = message.toLowerCase();
|
||||
logger.error(msg);
|
||||
if (isSfxEnabled())
|
||||
playErrorSound();
|
||||
return toast.error(msg, { ...toastStyle, ...data });
|
||||
},
|
||||
warning: (message: string, data?: any) => {
|
||||
warning: (message: string, data?: ToastData) => {
|
||||
const msg = message.toLowerCase();
|
||||
logger.warning(msg);
|
||||
if (isSfxEnabled())
|
||||
playWarningSound();
|
||||
return toast.warning(msg, { ...toastStyle, ...data });
|
||||
},
|
||||
info: (message: string, data?: any) => {
|
||||
info: (message: string, data?: ToastData) => {
|
||||
const msg = message.toLowerCase();
|
||||
logger.info(msg);
|
||||
if (isSfxEnabled())
|
||||
playInfoSound();
|
||||
return toast.info(msg, { ...toastStyle, ...data });
|
||||
},
|
||||
message: (message: string, data?: any) => {
|
||||
message: (message: string, data?: ToastData) => {
|
||||
const msg = message.toLowerCase();
|
||||
logger.info(msg);
|
||||
if (isSfxEnabled())
|
||||
playInfoSound();
|
||||
return toast(msg, { ...toastStyle, ...data });
|
||||
},
|
||||
silentInfo: (message: string, data?: ToastData) => {
|
||||
const msg = message.toLowerCase();
|
||||
logger.info(msg);
|
||||
return toast.info(msg, { ...toastStyle, ...data });
|
||||
},
|
||||
dismiss: (id?: string | number) => toast.dismiss(id),
|
||||
toast: toast,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user