-
Notifications
You must be signed in to change notification settings - Fork 0
/
album.go
309 lines (248 loc) · 8.58 KB
/
album.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
package gotidal
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
)
// Album represents an individual release.
type Album struct {
AlbumResource `json:"resource"`
}
type AlbumResource struct {
ID string `json:"id"`
BarcodeID string `json:"barcodeID"`
Title string `json:"title"`
Artists []artistResource `json:"artists"`
Duration int `json:"duration"`
ReleaseDate string `json:"releaseDate"`
ImageCover []Image `json:"imageCover"`
VideoCover []Image `json:"videoCover"`
NumberOfVolumes int `json:"numberOfVolumes"`
NumberOfTracks int `json:"numberOfTracks"`
NumberOfVideos int `json:"numberOfVideos"`
Type string `json:"type"`
Copyright string `json:"copyright"`
MediaMetaData MediaMetaData `json:"mediaMetadata"`
Properties AlbumProperties `json:"properties"`
TidalURL string `json:"tidalUrl"`
ProviderInfo ProviderInfo `json:"providerInfo"`
}
// MediaMetaData represents the metadata of an album.
type MediaMetaData struct {
Tags []string `json:"tags"`
}
// AlbumProperties represents the properties of an album.
type AlbumProperties struct {
Content []string `json:"content"`
}
// ProviderInfo represents the provider of an album.
type ProviderInfo struct {
ID string `json:"providerId"`
Name string `json:"providerName"`
}
// Track represents an individual track on an album.
type Track struct {
trackResource `json:"resource"`
}
type trackResource struct {
ID string `json:"id"`
ArtifactType string `json:"artifactType"`
Title string `json:"title"`
ISRC string `json:"isrc"`
Copyright string `json:"copyright"`
Version string `json:"version"`
Artists []artistResource `json:"artists"`
Album AlbumResource `json:"album"`
TrackNumber int `json:"trackNumber"`
VolumeNumber int `json:"volumeNumber"`
MediaMetaData MediaMetaData `json:"mediaMetadata"`
Properties AlbumProperties `json:"properties"`
TidalURL string `json:"tidalUrl"`
ProviderInfo ProviderInfo `json:"providerInfo"`
}
type albumResults struct {
Data []Album `json:"data"`
}
type ItemMetaData struct {
Total int `json:"total"`
}
type idListParams struct {
ids string
}
// GetSingleAlbum returns an album that matches an ID.
func (c *Client) GetSingleAlbum(ctx context.Context, id string) (*Album, error) {
if id == "" {
return nil, ErrMissingRequiredParameters
}
response, err := c.request(ctx, http.MethodGet, concat("/albums/", id), nil)
if err != nil {
return nil, fmt.Errorf("failed to connect to the albums endpoint: %w", err)
}
var result Album
err = json.Unmarshal(response, &result)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal the albums response body: %w", err)
}
return &result, nil
}
type trackResults struct {
Data []Track `json:"data"`
MetaData ItemMetaData `json:"metadata"`
}
const paginationLimit = 100
// GetAlbumTracks returns a list of album tracks.
//
// The items endpoint is paginated so we set a fairly high limit (100 items) in the hope to catch most cases in
// one round-trip. If the metadata reports a higher total then we make susequent API calls until all the tracks are
// returned.
//
// This endpoint also supports videos but it was hard to find any examples of this, so for the moment this is tracks
// only.
func (c *Client) GetAlbumTracks(ctx context.Context, id string) ([]Track, error) {
if id == "" {
return nil, ErrMissingRequiredParameters
}
params := PaginationParams{
Limit: paginationLimit,
Offset: 0,
}
total := 0
runningTotal := 0
var tracks []Track
for total >= runningTotal {
response, err := c.request(ctx, http.MethodGet, concat("/albums/", id, "/items"), params)
if err != nil {
return nil, fmt.Errorf("failed to connect to the albums endpoint: %w", err)
}
var results trackResults
err = json.Unmarshal(response, &results)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal the albums response body: %w", err)
}
tracks = append(tracks, results.Data...)
params.Offset += params.Limit
runningTotal += params.Limit
total = results.MetaData.Total
}
return tracks, nil
}
// GetAlbumByBarcodeID returns a list of albums that match a barcode ID.
func (c *Client) GetAlbumByBarcodeID(ctx context.Context, barcodeID string) ([]Album, error) {
if barcodeID == "" {
return nil, ErrMissingRequiredParameters
}
type barcodeParams struct {
barcodeId string // nolint:revive // This variable is directly referenced in the query string.
}
params := barcodeParams{
barcodeId: barcodeID,
}
response, err := c.request(ctx, http.MethodGet, "/albums/byBarcodeId", params)
if err != nil {
return nil, fmt.Errorf("failed to connect to the albums endpoint: %w", err)
}
var results albumResults
err = json.Unmarshal(response, &results)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal the albums response body: %w", err)
}
return results.Data, nil
}
// GetMultipleAlbums returns a list of albums filtered by their IDs.
func (c *Client) GetMultipleAlbums(ctx context.Context, ids []string) ([]Album, error) {
params := idListParams{
ids: strings.Join(ids, ","),
}
response, err := c.request(ctx, http.MethodGet, "/albums/byIds", params)
if err != nil {
return nil, fmt.Errorf("failed to connect to the multiple albums endpoint: %w", err)
}
var results albumResults
err = json.Unmarshal(response, &results)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal the multiple albums response body: %w", err)
}
return results.Data, nil
}
type similarAlbum struct {
Resource struct {
ID string `json:"id"`
}
}
type similarAlbumResults struct {
Data []similarAlbum `json:"data"`
MetaData ItemMetaData `json:"metadata"`
}
// GetSimilarAlbums returns a slice of album IDs that can be used as a parameter in the GetMultipleAlbums function.
func (c *Client) GetSimilarAlbums(ctx context.Context, id string, params PaginationParams) ([]string, error) {
response, err := c.request(ctx, http.MethodGet, concat("/albums/", id, "/similar"), params)
if err != nil {
return nil, fmt.Errorf("failed to connect to the similar albums endpoint: %w", err)
}
var results similarAlbumResults
err = json.Unmarshal(response, &results)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal the similar albums response body: %w", err)
}
var albumIDs []string
for _, albumID := range results.Data {
albumIDs = append(albumIDs, albumID.Resource.ID)
}
return albumIDs, nil
}
// GetAlbumsByArtist returns a list of albums that match an artist ID.
func (c *Client) GetSingleTrack(ctx context.Context, id string) (*Track, error) {
response, err := c.request(ctx, http.MethodGet, concat("/tracks/", id), nil)
if err != nil {
return nil, fmt.Errorf("failed to connect to the tracks endpoint: %w", err)
}
var result Track
err = json.Unmarshal(response, &result)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal the tracks response body: %w", err)
}
return &result, nil
}
// GetTracksByISRC returns a list of tracks that match an ISRC.
//
// ISRC lookup can be found here. This is a useful tool for finding ISRCs for testing purposes:
// https://isrcsearch.ifpi.org/
func (c *Client) GetTracksByISRC(ctx context.Context, isrc string, params PaginationParams) ([]Track, error) {
type isrcParams struct {
isrc string
Limit int
Offset int
}
response, err := c.request(ctx, http.MethodGet, "/tracks/byIsrc", isrcParams{
isrc: isrc,
Limit: params.Limit,
Offset: params.Offset,
})
if err != nil {
return nil, fmt.Errorf("failed to connect to the tracks endpoint: %w", err)
}
var result trackResults
err = json.Unmarshal(response, &result)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal the tracks response body: %w", err)
}
return result.Data, nil
}
// GetMultipleTracks returns a list of tracks filtered by their IDs.
func (c *Client) GetMultipleTracks(ctx context.Context, ids []string) ([]Track, error) {
params := idListParams{
ids: strings.Join(ids, ","),
}
response, err := c.request(ctx, http.MethodGet, "/tracks", params)
if err != nil {
return nil, fmt.Errorf("failed to connect to the multiple tracks endpoint: %w", err)
}
var results trackResults
err = json.Unmarshal(response, &results)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal the multiple tracks response body: %w", err)
}
return results.Data, nil
}