mirror of
https://github.com/taigrr/wtf
synced 2025-01-18 04:03:14 -08:00
402 lines
12 KiB
Go
402 lines
12 KiB
Go
package spotify
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// User contains the basic, publicly available information about a Spotify user.
|
|
type User struct {
|
|
// The name displayed on the user's profile.
|
|
// Note: Spotify currently fails to populate
|
|
// this field when querying for a playlist.
|
|
DisplayName string `json:"display_name"`
|
|
// Known public external URLs for the user.
|
|
ExternalURLs map[string]string `json:"external_urls"`
|
|
// Information about followers of the user.
|
|
Followers Followers `json:"followers"`
|
|
// A link to the Web API endpoint for this user.
|
|
Endpoint string `json:"href"`
|
|
// The Spotify user ID for the user.
|
|
ID string `json:"id"`
|
|
// The user's profile image.
|
|
Images []Image `json:"images"`
|
|
// The Spotify URI for the user.
|
|
URI URI `json:"uri"`
|
|
}
|
|
|
|
// PrivateUser contains additional information about a user.
|
|
// This data is private and requires user authentication.
|
|
type PrivateUser struct {
|
|
User
|
|
// The country of the user, as set in the user's account profile.
|
|
// An ISO 3166-1 alpha-2 country code. This field is only available when the
|
|
// current user has granted acess to the ScopeUserReadPrivate scope.
|
|
Country string `json:"country"`
|
|
// The user's email address, as entered by the user when creating their account.
|
|
// Note: this email is UNVERIFIED - there is no proof that it actually
|
|
// belongs to the user. This field is only available when the current user
|
|
// has granted access to the ScopeUserReadEmail scope.
|
|
Email string `json:"email"`
|
|
// The user's Spotify subscription level: "premium", "free", etc.
|
|
// The subscription level "open" can be considered the same as "free".
|
|
// This field is only available when the current user has granted access to
|
|
// the ScopeUserReadPrivate scope.
|
|
Product string `json:"product"`
|
|
// The user's date of birth, in the format 'YYYY-MM-DD'. You can use
|
|
// the DateLayout constant to convert this to a time.Time value.
|
|
// This field is only available when the current user has granted
|
|
// access to the ScopeUserReadBirthdate scope.
|
|
Birthdate string `json:"birthdate"`
|
|
}
|
|
|
|
// GetUsersPublicProfile gets public profile information about a
|
|
// Spotify User. It does not require authentication.
|
|
func (c *Client) GetUsersPublicProfile(userID ID) (*User, error) {
|
|
spotifyURL := c.baseURL + "users/" + string(userID)
|
|
|
|
var user User
|
|
|
|
err := c.get(spotifyURL, &user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
// CurrentUser gets detailed profile information about the
|
|
// current user.
|
|
//
|
|
// Reading the user's email address requires that the application
|
|
// has the ScopeUserReadEmail scope. Reading the country, display
|
|
// name, profile images, and product subscription level requires
|
|
// that the application has the ScopeUserReadPrivate scope.
|
|
//
|
|
// Warning: The email address in the response will be the address
|
|
// that was entered when the user created their spotify account.
|
|
// This email address is unverified - do not assume that Spotify has
|
|
// checked that the email address actually belongs to the user.
|
|
func (c *Client) CurrentUser() (*PrivateUser, error) {
|
|
var result PrivateUser
|
|
|
|
err := c.get(c.baseURL+"me", &result)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// CurrentUsersTracks gets a list of songs saved in the current
|
|
// Spotify user's "Your Music" library.
|
|
func (c *Client) CurrentUsersTracks() (*SavedTrackPage, error) {
|
|
return c.CurrentUsersTracksOpt(nil)
|
|
}
|
|
|
|
// CurrentUsersTracksOpt is like CurrentUsersTracks, but it accepts additional
|
|
// options for sorting and filtering the results.
|
|
func (c *Client) CurrentUsersTracksOpt(opt *Options) (*SavedTrackPage, error) {
|
|
spotifyURL := c.baseURL + "me/tracks"
|
|
if opt != nil {
|
|
v := url.Values{}
|
|
if opt.Country != nil {
|
|
v.Set("country", *opt.Country)
|
|
}
|
|
if opt.Limit != nil {
|
|
v.Set("limit", strconv.Itoa(*opt.Limit))
|
|
}
|
|
if opt.Offset != nil {
|
|
v.Set("offset", strconv.Itoa(*opt.Offset))
|
|
}
|
|
if params := v.Encode(); params != "" {
|
|
spotifyURL += "?" + params
|
|
}
|
|
}
|
|
|
|
var result SavedTrackPage
|
|
|
|
err := c.get(spotifyURL, &result)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// FollowUser adds the current user as a follower of one or more
|
|
// spotify users, identified by their Spotify IDs.
|
|
//
|
|
// Modifying the lists of artists or users the current user follows
|
|
// requires that the application has the ScopeUserFollowModify scope.
|
|
func (c *Client) FollowUser(ids ...ID) error {
|
|
return c.modifyFollowers("user", true, ids...)
|
|
}
|
|
|
|
// FollowArtist adds the current user as a follower of one or more
|
|
// spotify artists, identified by their Spotify IDs.
|
|
//
|
|
// Modifying the lists of artists or users the current user follows
|
|
// requires that the application has the ScopeUserFollowModify scope.
|
|
func (c *Client) FollowArtist(ids ...ID) error {
|
|
return c.modifyFollowers("artist", true, ids...)
|
|
}
|
|
|
|
// UnfollowUser removes the current user as a follower of one or more
|
|
// Spotify users.
|
|
//
|
|
// Modifying the lists of artists or users the current user follows
|
|
// requires that the application has the ScopeUserFollowModify scope.
|
|
func (c *Client) UnfollowUser(ids ...ID) error {
|
|
return c.modifyFollowers("user", false, ids...)
|
|
}
|
|
|
|
// UnfollowArtist removes the current user as a follower of one or more
|
|
// Spotify artists.
|
|
//
|
|
// Modifying the lists of artists or users the current user follows
|
|
// requires that the application has the ScopeUserFollowModify scope.
|
|
func (c *Client) UnfollowArtist(ids ...ID) error {
|
|
return c.modifyFollowers("artist", false, ids...)
|
|
}
|
|
|
|
// CurrentUserFollows checks to see if the current user is following
|
|
// one or more artists or other Spotify Users. This call requires
|
|
// ScopeUserFollowRead.
|
|
//
|
|
// The t argument indicates the type of the IDs, and must be either
|
|
// "user" or "artist".
|
|
//
|
|
// The result is returned as a slice of bool values in the same order
|
|
// in which the IDs were specified.
|
|
func (c *Client) CurrentUserFollows(t string, ids ...ID) ([]bool, error) {
|
|
if l := len(ids); l == 0 || l > 50 {
|
|
return nil, errors.New("spotify: UserFollows supports 1 to 50 IDs")
|
|
}
|
|
if t != "artist" && t != "user" {
|
|
return nil, errors.New("spotify: t must be 'artist' or 'user'")
|
|
}
|
|
spotifyURL := fmt.Sprintf("%sme/following/contains?type=%s&ids=%s",
|
|
c.baseURL, t, strings.Join(toStringSlice(ids), ","))
|
|
|
|
var result []bool
|
|
|
|
err := c.get(spotifyURL, &result)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (c *Client) modifyFollowers(usertype string, follow bool, ids ...ID) error {
|
|
if l := len(ids); l == 0 || l > 50 {
|
|
return errors.New("spotify: Follow/Unfollow supports 1 to 50 IDs")
|
|
}
|
|
v := url.Values{}
|
|
v.Add("type", usertype)
|
|
v.Add("ids", strings.Join(toStringSlice(ids), ","))
|
|
spotifyURL := c.baseURL + "me/following?" + v.Encode()
|
|
method := "PUT"
|
|
if !follow {
|
|
method = "DELETE"
|
|
}
|
|
req, err := http.NewRequest(method, spotifyURL, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = c.execute(req, nil, http.StatusNoContent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CurrentUsersFollowedArtists gets the current user's followed artists.
|
|
// This call requires that the user has granted the ScopeUserFollowRead scope.
|
|
func (c *Client) CurrentUsersFollowedArtists() (*FullArtistCursorPage, error) {
|
|
return c.CurrentUsersFollowedArtistsOpt(-1, "")
|
|
}
|
|
|
|
// CurrentUsersFollowedArtistsOpt is like CurrentUsersFollowedArtists,
|
|
// but it accept the optional arguments limit and after. Limit is the
|
|
// maximum number of items to return (1 <= limit <= 50), and after is
|
|
// the last artist ID retrieved from the previous request. If you don't
|
|
// wish to specify either of the parameters, use -1 for limit and the empty
|
|
// string for after.
|
|
func (c *Client) CurrentUsersFollowedArtistsOpt(limit int, after string) (*FullArtistCursorPage, error) {
|
|
spotifyURL := c.baseURL + "me/following"
|
|
v := url.Values{}
|
|
v.Set("type", "artist")
|
|
if limit != -1 {
|
|
v.Set("limit", strconv.Itoa(limit))
|
|
}
|
|
if after != "" {
|
|
v.Set("after", after)
|
|
}
|
|
if params := v.Encode(); params != "" {
|
|
spotifyURL += "?" + params
|
|
}
|
|
|
|
var result struct {
|
|
A FullArtistCursorPage `json:"artists"`
|
|
}
|
|
|
|
err := c.get(spotifyURL, &result)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result.A, nil
|
|
}
|
|
|
|
// CurrentUsersAlbums gets a list of albums saved in the current
|
|
// Spotify user's "Your Music" library.
|
|
func (c *Client) CurrentUsersAlbums() (*SavedAlbumPage, error) {
|
|
return c.CurrentUsersAlbumsOpt(nil)
|
|
}
|
|
|
|
// CurrentUsersAlbumsOpt is like CurrentUsersAlbums, but it accepts additional
|
|
// options for sorting and filtering the results.
|
|
func (c *Client) CurrentUsersAlbumsOpt(opt *Options) (*SavedAlbumPage, error) {
|
|
spotifyURL := c.baseURL + "me/albums"
|
|
if opt != nil {
|
|
v := url.Values{}
|
|
if opt.Country != nil {
|
|
v.Set("market", *opt.Country)
|
|
}
|
|
if opt.Limit != nil {
|
|
v.Set("limit", strconv.Itoa(*opt.Limit))
|
|
}
|
|
if opt.Offset != nil {
|
|
v.Set("offset", strconv.Itoa(*opt.Offset))
|
|
}
|
|
if params := v.Encode(); params != "" {
|
|
spotifyURL += "?" + params
|
|
}
|
|
}
|
|
|
|
var result SavedAlbumPage
|
|
|
|
err := c.get(spotifyURL, &result)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// CurrentUsersPlaylists gets a list of the playlists owned or followed by
|
|
// the current spotify user.
|
|
//
|
|
// Private playlists require the ScopePlaylistReadPrivate scope. Note that
|
|
// this scope alone will not return collaborative playlists, even though
|
|
// they are always private. In order to retrieve collaborative playlists
|
|
// the user must authorize the ScopePlaylistReadCollaborative scope.
|
|
func (c *Client) CurrentUsersPlaylists() (*SimplePlaylistPage, error) {
|
|
return c.CurrentUsersPlaylistsOpt(nil)
|
|
}
|
|
|
|
// CurrentUsersPlaylistsOpt is like CurrentUsersPlaylists, but it accepts
|
|
// additional options for sorting and filtering the results.
|
|
func (c *Client) CurrentUsersPlaylistsOpt(opt *Options) (*SimplePlaylistPage, error) {
|
|
spotifyURL := c.baseURL + "me/playlists"
|
|
if opt != nil {
|
|
v := url.Values{}
|
|
if opt.Limit != nil {
|
|
v.Set("limit", strconv.Itoa(*opt.Limit))
|
|
}
|
|
if opt.Offset != nil {
|
|
v.Set("offset", strconv.Itoa(*opt.Offset))
|
|
}
|
|
if params := v.Encode(); params != "" {
|
|
spotifyURL += "?" + params
|
|
}
|
|
}
|
|
|
|
var result SimplePlaylistPage
|
|
|
|
err := c.get(spotifyURL, &result)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// CurrentUsersTopArtistsOpt gets a list of the top played artists in a given time
|
|
// range of the current Spotify user. It supports up to 50 artists in a single
|
|
// call. This call requires ScopeUserTopRead.
|
|
func (c *Client) CurrentUsersTopArtistsOpt(opt *Options) (*FullArtistPage, error) {
|
|
spotifyURL := c.baseURL + "me/top/artists"
|
|
if opt != nil {
|
|
v := url.Values{}
|
|
if opt.Limit != nil {
|
|
v.Set("limit", strconv.Itoa(*opt.Limit))
|
|
}
|
|
if opt.Timerange != nil {
|
|
v.Set("time_range", *opt.Timerange+"_term")
|
|
}
|
|
if params := v.Encode(); params != "" {
|
|
spotifyURL += "?" + params
|
|
}
|
|
}
|
|
|
|
var result FullArtistPage
|
|
|
|
err := c.get(spotifyURL, &result)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// CurrentUsersTopArtists is like CurrentUsersTopArtistsOpt but with
|
|
// sensible defaults. The default limit is 20 and the default timerange
|
|
// is medium_term.
|
|
func (c *Client) CurrentUsersTopArtists() (*FullArtistPage, error) {
|
|
return c.CurrentUsersTopArtistsOpt(nil)
|
|
}
|
|
|
|
// CurrentUsersTopTracksOpt gets a list of the top played tracks in a given time
|
|
// range of the current Spotify user. It supports up to 50 tracks in a single
|
|
// call. This call requires ScopeUserTopRead.
|
|
func (c *Client) CurrentUsersTopTracksOpt(opt *Options) (*FullTrackPage, error) {
|
|
spotifyURL := c.baseURL + "me/top/tracks"
|
|
if opt != nil {
|
|
v := url.Values{}
|
|
if opt.Limit != nil {
|
|
v.Set("limit", strconv.Itoa(*opt.Limit))
|
|
}
|
|
if opt.Timerange != nil {
|
|
v.Set("time_range", *opt.Timerange+"_term")
|
|
}
|
|
if opt.Offset != nil {
|
|
v.Set("offset", strconv.Itoa(*opt.Offset))
|
|
}
|
|
if params := v.Encode(); params != "" {
|
|
spotifyURL += "?" + params
|
|
}
|
|
}
|
|
|
|
var result FullTrackPage
|
|
|
|
err := c.get(spotifyURL, &result)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// CurrentUsersTopTracks is like CurrentUsersTopTracksOpt but with
|
|
// sensible defaults. The default limit is 20 and the default timerange
|
|
// is medium_term.
|
|
func (c *Client) CurrentUsersTopTracks() (*FullTrackPage, error) {
|
|
return c.CurrentUsersTopTracksOpt(nil)
|
|
} |