1package spotify 2 3import ( 4 "fmt" 5 "net/url" 6 "strconv" 7 "strings" 8) 9 10// Seeds contains IDs of artists, genres and/or tracks 11// to be used as seeds for recommendations 12type Seeds struct { 13 Artists []ID 14 Tracks []ID 15 Genres []string 16} 17 18// count returns the total number of seeds contained in s 19func (s Seeds) count() int { 20 return len(s.Artists) + len(s.Tracks) + len(s.Genres) 21} 22 23// Recommendations contains a list of recommended tracks based on seeds 24type Recommendations struct { 25 Seeds []RecommendationSeed `json:"seeds"` 26 Tracks []SimpleTrack `json:"tracks"` 27} 28 29// RecommendationSeed represents a recommendation seed after 30// being processed by the Spotify API 31type RecommendationSeed struct { 32 AfterFilteringSize int `json:"afterFilteringSize"` 33 AfterRelinkingSize int `json:"afterRelinkingSize"` 34 Endpoint string `json:"href"` 35 ID ID `json:"id"` 36 InitialPoolSize int `json:"initialPoolSize"` 37 Type string `json:"type"` 38} 39 40// MaxNumberOfSeeds allowed by Spotify for a recommendation request 41const MaxNumberOfSeeds = 5 42 43// setSeedValues sets url values into v for each seed in seeds 44func setSeedValues(seeds Seeds, v url.Values) { 45 if len(seeds.Artists) != 0 { 46 v.Set("seed_artists", strings.Join(toStringSlice(seeds.Artists), ",")) 47 } 48 if len(seeds.Tracks) != 0 { 49 v.Set("seed_tracks", strings.Join(toStringSlice(seeds.Tracks), ",")) 50 } 51 if len(seeds.Genres) != 0 { 52 v.Set("seed_genres", strings.Join(seeds.Genres, ",")) 53 } 54} 55 56// setTrackAttributesValues sets track attributes values to the given url values 57func setTrackAttributesValues(trackAttributes *TrackAttributes, values url.Values) { 58 if trackAttributes == nil { 59 return 60 } 61 for attr, val := range trackAttributes.intAttributes { 62 values.Set(attr, strconv.Itoa(val)) 63 } 64 for attr, val := range trackAttributes.floatAttributes { 65 values.Set(attr, strconv.FormatFloat(val, 'f', -1, 64)) 66 } 67} 68 69// GetRecommendations returns a list of recommended tracks based on the given seeds. 70// Recommendations are generated based on the available information for a given seed entity 71// and matched against similar artists and tracks. If there is sufficient information 72// about the provided seeds, a list of tracks will be returned together with pool size details. 73// For artists and tracks that are very new or obscure 74// there might not be enough data to generate a list of tracks. 75func (c *Client) GetRecommendations(seeds Seeds, trackAttributes *TrackAttributes, opt *Options) (*Recommendations, error) { 76 v := url.Values{} 77 78 if seeds.count() == 0 { 79 return nil, fmt.Errorf("spotify: at least one seed is required") 80 } 81 if seeds.count() > MaxNumberOfSeeds { 82 return nil, fmt.Errorf("spotify: exceeded maximum of %d seeds", MaxNumberOfSeeds) 83 } 84 85 setSeedValues(seeds, v) 86 setTrackAttributesValues(trackAttributes, v) 87 88 if opt != nil { 89 if opt.Limit != nil { 90 v.Set("limit", strconv.Itoa(*opt.Limit)) 91 } 92 if opt.Country != nil { 93 v.Set("market", *opt.Country) 94 } 95 } 96 97 spotifyURL := c.baseURL + "recommendations?" + v.Encode() 98 99 var recommendations Recommendations 100 err := c.get(spotifyURL, &recommendations) 101 if err != nil { 102 return nil, err 103 } 104 105 return &recommendations, err 106} 107 108// GetAvailableGenreSeeds retrieves a list of available genres seed parameter values for 109// recommendations. 110func (c *Client) GetAvailableGenreSeeds() ([]string, error) { 111 spotifyURL := c.baseURL + "recommendations/available-genre-seeds" 112 113 genreSeeds := make(map[string][]string) 114 115 err := c.get(spotifyURL, &genreSeeds) 116 if err != nil { 117 return nil, err 118 } 119 120 return genreSeeds["genres"], nil 121} 122