Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 110 additions & 12 deletions internal/spotify/connect_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ func extractSearchItems(payload map[string]any, kind string) ([]Item, int) {
}

func extractItemFromPayload(payload map[string]any, kind string) (Item, bool) {
if kind == "track" {
if m, ok := getMap(payload, "data", "trackUnion"); ok {
if item, ok := extractItem(m, kind); ok {
return item, true
}
}
if m, ok := getMap(payload, "data", "track"); ok {
if item, ok := extractItem(m, kind); ok {
return item, true
}
}
}
items := collectItemsByKind(payload, kind)
if len(items) == 0 {
return Item{}, false
Expand Down Expand Up @@ -99,6 +111,11 @@ func extractItem(value any, kind string) (Item, bool) {
if !ok {
return Item{}, false
}
if kind == "track" {
if inner, ok := m["track"].(map[string]any); ok {
m = inner
}
}
uri := getString(m, "uri")
if uri == "" && kind != "" {
if id := getString(m, "id"); id != "" {
Expand Down Expand Up @@ -217,25 +234,106 @@ func findFirstName(value any) string {

func extractArtistNames(value any) []string {
artists := []string{}
walkMap(value, func(m map[string]any) {
if list, ok := m["artists"].([]any); ok {
for _, entry := range list {
if name := findFirstName(entry); name != "" {
artists = append(artists, name)
}
}
m, ok := value.(map[string]any)
if !ok {
return nil
}
if list, ok := m["artists"].([]any); ok {
appendArtistNames(&artists, list)
}
if group, ok := m["artists"].(map[string]any); ok {
if list, ok := group["items"].([]any); ok {
appendArtistNames(&artists, list)
}
})
if list, ok := group["nodes"].([]any); ok {
appendArtistNames(&artists, list)
}
if list, ok := group["edges"].([]any); ok {
appendArtistNames(&artists, list)
}
}
if group, ok := m["firstArtist"].(map[string]any); ok {
if list, ok := group["items"].([]any); ok {
appendArtistNames(&artists, list)
}
if list, ok := group["nodes"].([]any); ok {
appendArtistNames(&artists, list)
}
if list, ok := group["edges"].([]any); ok {
appendArtistNames(&artists, list)
}
}
if group, ok := m["otherArtists"].(map[string]any); ok {
if list, ok := group["items"].([]any); ok {
appendArtistNames(&artists, list)
}
if list, ok := group["nodes"].([]any); ok {
appendArtistNames(&artists, list)
}
if list, ok := group["edges"].([]any); ok {
appendArtistNames(&artists, list)
}
}
if len(artists) == 0 {
if m, ok := value.(map[string]any); ok {
if name := getString(m, "artistName"); name != "" {
artists = append(artists, name)
}
if name := getString(m, "artistName"); name != "" {
artists = append(artists, name)
}
}
return dedupeStrings(artists)
}

func appendArtistNames(artists *[]string, entries []any) {
for _, entry := range entries {
if name := artistNameFromValue(entry); name != "" {
*artists = append(*artists, name)
continue
}
}
}

func artistNameFromValue(value any) string {
m, ok := value.(map[string]any)
if !ok {
return ""
}
if profile, ok := m["profile"].(map[string]any); ok {
if name := getString(profile, "name"); name != "" {
return name
}
}
if node, ok := m["node"].(map[string]any); ok {
if name := artistNameFromValue(node); name != "" {
return name
}
}
if artist, ok := m["artist"].(map[string]any); ok {
if name := artistNameFromValue(artist); name != "" {
return name
}
}
name := getString(m, "name")
if name == "" {
return ""
}
if len(m) == 1 || isArtistMap(m) || getString(m, "id") != "" {
return name
}
return ""
}

func isArtistMap(m map[string]any) bool {
if uri := getString(m, "uri"); strings.HasPrefix(uri, "spotify:artist:") {
return true
}
if typ := getString(m, "type"); typ == "artist" {
return true
}
if _, ok := m["profile"]; ok {
return true
}
return false
}

func extractAlbumName(value any) string {
var album string
walkMap(value, func(m map[string]any) {
Expand Down
138 changes: 138 additions & 0 deletions internal/spotify/connect_mapping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,144 @@ func TestExtractItemFallbacks(t *testing.T) {
}
}

func TestExtractItemArtistsContainers(t *testing.T) {
raw := map[string]any{
"uri": "spotify:track:abc",
"name": "Song",
"artists": map[string]any{
"items": []any{
map[string]any{"name": "Artist One"},
map[string]any{"name": "Artist Two"},
},
},
}
item, ok := extractItem(raw, "track")
if !ok {
t.Fatalf("expected item")
}
if len(item.Artists) != 2 || item.Artists[0] != "Artist One" || item.Artists[1] != "Artist Two" {
t.Fatalf("unexpected artists: %#v", item.Artists)
}
}

func TestExtractItemArtistsEdges(t *testing.T) {
raw := map[string]any{
"uri": "spotify:track:abc",
"name": "Song",
"artists": map[string]any{
"edges": []any{
map[string]any{"node": map[string]any{"name": "Artist One"}},
map[string]any{"node": map[string]any{"name": "Artist Two"}},
},
},
}
item, ok := extractItem(raw, "track")
if !ok {
t.Fatalf("expected item")
}
if len(item.Artists) != 2 || item.Artists[0] != "Artist One" || item.Artists[1] != "Artist Two" {
t.Fatalf("unexpected artists: %#v", item.Artists)
}
}

func TestExtractItemFirstArtistItems(t *testing.T) {
raw := map[string]any{
"uri": "spotify:track:abc",
"name": "Song",
"firstArtist": map[string]any{
"items": []any{
map[string]any{"profile": map[string]any{"name": "Artist One"}},
},
},
}
item, ok := extractItem(raw, "track")
if !ok {
t.Fatalf("expected item")
}
if len(item.Artists) != 1 || item.Artists[0] != "Artist One" {
t.Fatalf("unexpected artists: %#v", item.Artists)
}
}

func TestExtractItemOtherArtistsItems(t *testing.T) {
raw := map[string]any{
"uri": "spotify:track:abc",
"name": "Song",
"firstArtist": map[string]any{
"items": []any{
map[string]any{"profile": map[string]any{"name": "Artist One"}},
},
},
"otherArtists": map[string]any{
"items": []any{
map[string]any{"profile": map[string]any{"name": "Artist Two"}},
},
},
}
item, ok := extractItem(raw, "track")
if !ok {
t.Fatalf("expected item")
}
if len(item.Artists) != 2 || item.Artists[0] != "Artist One" || item.Artists[1] != "Artist Two" {
t.Fatalf("unexpected artists: %#v", item.Artists)
}
}
func TestExtractItemFromPayloadPrefersTrackUnion(t *testing.T) {
payload := map[string]any{
"data": map[string]any{
"trackUnion": map[string]any{
"uri": "spotify:track:primary",
"name": "Primary",
"artists": []any{
map[string]any{"name": "Main Artist"},
},
},
"track": map[string]any{
"uri": "spotify:track:secondary",
"name": "Secondary",
"artists": []any{
map[string]any{"name": "Wrong Artist"},
},
},
"other": map[string]any{
"items": []any{
map[string]any{
"uri": "spotify:track:secondary",
"name": "Secondary",
"artists": []any{
map[string]any{"name": "Wrong Artist"},
},
},
},
},
},
}
item, ok := extractItemFromPayload(payload, "track")
if !ok {
t.Fatalf("expected item")
}
if item.ID != "primary" || len(item.Artists) != 1 || item.Artists[0] != "Main Artist" {
t.Fatalf("unexpected item: %#v", item)
}
}

func TestExtractItemArtistsIDName(t *testing.T) {
raw := map[string]any{
"uri": "spotify:track:abc",
"name": "Song",
"artists": []any{
map[string]any{"id": "ar1", "name": "Artist One"},
},
}
item, ok := extractItem(raw, "track")
if !ok {
t.Fatalf("expected item")
}
if len(item.Artists) != 1 || item.Artists[0] != "Artist One" {
t.Fatalf("unexpected artists: %#v", item.Artists)
}
}

func TestExtractSearchItemsFallback(t *testing.T) {
payload := map[string]any{
"data": map[string]any{
Expand Down