1package spotify
2
3import (
4	"errors"
5	"fmt"
6	"net/url"
7	"strconv"
8	"strings"
9	"time"
10)
11
12// SimpleAlbum contains basic data about an album.
13type SimpleAlbum struct {
14	// The name of the album.
15	Name string `json:"name"`
16	// A slice of SimpleArtists
17	Artists []SimpleArtist `json:"artists"`
18	// The field is present when getting an artist’s
19	// albums. Possible values are “album”, “single”,
20	// “compilation”, “appears_on”. Compare to album_type
21	// this field represents relationship between the artist
22	// and the album.
23	AlbumGroup string `json:"album_group"`
24	// The type of the album: one of "album",
25	// "single", or "compilation".
26	AlbumType string `json:"album_type"`
27	// The SpotifyID for the album.
28	ID ID `json:"id"`
29	// The SpotifyURI for the album.
30	URI URI `json:"uri"`
31	// The markets in which the album is available,
32	// identified using ISO 3166-1 alpha-2 country
33	// codes.  Note that al album is considered
34	// available in a market when at least 1 of its
35	// tracks is available in that market.
36	AvailableMarkets []string `json:"available_markets"`
37	// A link to the Web API enpoint providing full
38	// details of the album.
39	Endpoint string `json:"href"`
40	// The cover art for the album in various sizes,
41	// widest first.
42	Images []Image `json:"images"`
43	// Known external URLs for this album.
44	ExternalURLs map[string]string `json:"external_urls"`
45	// The date the album was first released.  For example, "1981-12-15".
46	// Depending on the ReleaseDatePrecision, it might be shown as
47	// "1981" or "1981-12". You can use ReleaseDateTime to convert this
48	// to a time.Time value.
49	ReleaseDate string `json:"release_date"`
50	// The precision with which ReleaseDate value is known: "year", "month", or "day"
51	ReleaseDatePrecision string `json:"release_date_precision"`
52}
53
54// Copyright contains the copyright statement associated with an album.
55type Copyright struct {
56	// The copyright text for the album.
57	Text string `json:"text"`
58	// The type of copyright.
59	Type string `json:"type"`
60}
61
62// FullAlbum provides extra album data in addition to the data provided by SimpleAlbum.
63type FullAlbum struct {
64	SimpleAlbum
65	Artists    []SimpleArtist `json:"artists"`
66	Copyrights []Copyright    `json:"copyrights"`
67	Genres     []string       `json:"genres"`
68	// The popularity of the album, represented as an integer between 0 and 100,
69	// with 100 being the most popular.  Popularity of an album is calculated
70	// from the popularify of the album's individual tracks.
71	Popularity int `json:"popularity"`
72	// The date the album was first released.  For example, "1981-12-15".
73	// Depending on the ReleaseDatePrecision, it might be shown as
74	// "1981" or "1981-12". You can use ReleaseDateTime to convert this
75	// to a time.Time value.
76	ReleaseDate string `json:"release_date"`
77	// The precision with which ReleaseDate value is known: "year", "month", or "day"
78	ReleaseDatePrecision string            `json:"release_date_precision"`
79	Tracks               SimpleTrackPage   `json:"tracks"`
80	ExternalIDs          map[string]string `json:"external_ids"`
81}
82
83// SavedAlbum provides info about an album saved to an user's account.
84type SavedAlbum struct {
85	// The date and time the track was saved, represented as an ISO
86	// 8601 UTC timestamp with a zero offset (YYYY-MM-DDTHH:MM:SSZ).
87	// You can use the TimestampLayout constant to convert this to
88	// a time.Time value.
89	AddedAt   string `json:"added_at"`
90	FullAlbum `json:"album"`
91}
92
93// ReleaseDateTime converts the album's ReleaseDate to a time.TimeValue.
94// All of the fields in the result may not be valid.  For example, if
95// f.ReleaseDatePrecision is "month", then only the month and year
96// (but not the day) of the result are valid.
97func (f *FullAlbum) ReleaseDateTime() time.Time {
98	if f.ReleaseDatePrecision == "day" {
99		result, _ := time.Parse(DateLayout, f.ReleaseDate)
100		return result
101	}
102	if f.ReleaseDatePrecision == "month" {
103		ym := strings.Split(f.ReleaseDate, "-")
104		year, _ := strconv.Atoi(ym[0])
105		month, _ := strconv.Atoi(ym[1])
106		return time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
107	}
108	year, _ := strconv.Atoi(f.ReleaseDate)
109	return time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)
110}
111
112// GetAlbum gets Spotify catalog information for a single album, given its Spotify ID.
113func (c *Client) GetAlbum(id ID) (*FullAlbum, error) {
114	spotifyURL := fmt.Sprintf("%salbums/%s", c.baseURL, id)
115
116	var a FullAlbum
117
118	err := c.get(spotifyURL, &a)
119	if err != nil {
120		return nil, err
121	}
122
123	return &a, nil
124}
125
126func toStringSlice(ids []ID) []string {
127	result := make([]string, len(ids))
128	for i, str := range ids {
129		result[i] = str.String()
130	}
131	return result
132}
133
134// GetAlbums gets Spotify Catalog information for multiple albums, given their
135// Spotify IDs.  It supports up to 20 IDs in a single call.  Albums are returned
136// in the order requested.  If an album is not found, that position in the
137// result slice will be nil.
138func (c *Client) GetAlbums(ids ...ID) ([]*FullAlbum, error) {
139	if len(ids) > 20 {
140		return nil, errors.New("spotify: exceeded maximum number of albums")
141	}
142	spotifyURL := fmt.Sprintf("%salbums?ids=%s", c.baseURL, strings.Join(toStringSlice(ids), ","))
143
144	var a struct {
145		Albums []*FullAlbum `json:"albums"`
146	}
147
148	err := c.get(spotifyURL, &a)
149	if err != nil {
150		return nil, err
151	}
152
153	return a.Albums, nil
154}
155
156// AlbumType represents the type of an album. It can be used to filter
157// results when searching for albums.
158type AlbumType int
159
160// AlbumType values that can be used to filter which types of albums are
161// searched for.  These are flags that can be bitwise OR'd together
162// to search for multiple types of albums simultaneously.
163const (
164	AlbumTypeAlbum       AlbumType = 1 << iota
165	AlbumTypeSingle                = 1 << iota
166	AlbummTypeAppearsOn            = 1 << iota
167	AlbumTypeCompilation           = 1 << iota
168)
169
170func (at AlbumType) encode() string {
171	types := []string{}
172	if at&AlbumTypeAlbum != 0 {
173		types = append(types, "album")
174	}
175	if at&AlbumTypeSingle != 0 {
176		types = append(types, "single")
177	}
178	if at&AlbummTypeAppearsOn != 0 {
179		types = append(types, "appears_on")
180	}
181	if at&AlbumTypeCompilation != 0 {
182		types = append(types, "compilation")
183	}
184	return strings.Join(types, ",")
185}
186
187// GetAlbumTracks gets the tracks for a particular album.
188// If you only care about the tracks, this call is more efficient
189// than GetAlbum.
190func (c *Client) GetAlbumTracks(id ID) (*SimpleTrackPage, error) {
191	return c.GetAlbumTracksOpt(id, -1, -1)
192}
193
194// GetAlbumTracksOpt behaves like GetAlbumTracks, with the exception that it
195// allows you to specify extra parameters that limit the number of results returned.
196// The maximum number of results to return is specified by limit.
197// The offset argument can be used to specify the index of the first track to return.
198// It can be used along with limit to reqeust the next set of results.
199func (c *Client) GetAlbumTracksOpt(id ID, limit, offset int) (*SimpleTrackPage, error) {
200	spotifyURL := fmt.Sprintf("%salbums/%s/tracks", c.baseURL, id)
201	v := url.Values{}
202	if limit != -1 {
203		v.Set("limit", strconv.Itoa(limit))
204	}
205	if offset != -1 {
206		v.Set("offset", strconv.Itoa(offset))
207	}
208	optional := v.Encode()
209	if optional != "" {
210		spotifyURL = spotifyURL + "?" + optional
211	}
212
213	var result SimpleTrackPage
214	err := c.get(spotifyURL, &result)
215	if err != nil {
216		return nil, err
217	}
218
219	return &result, nil
220}
221