spotify-ruby-kev 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotify
4
+ class SDK
5
+ class Connect
6
+ class PlaybackState < Model
7
+ ##
8
+ # Get the device the current playback is on.
9
+ #
10
+ # @example
11
+ # device = @sdk.connect.devices[0]
12
+ # device.playback.device
13
+ #
14
+ # @return [Spotify::SDK::Connect::Device] self Return the device object.
15
+ #
16
+ def device
17
+ Spotify::SDK::Connect::Device.new(super, parent)
18
+ end
19
+
20
+ ##
21
+ # Is the current user playing a track?
22
+ #
23
+ # @example
24
+ # playback = @sdk.connect.playback
25
+ # playback.playing?
26
+ #
27
+ # @return [FalseClass,TrueClass] is_playing True if user is currently performing playback.
28
+ #
29
+ alias_attribute :playing?, :is_playing
30
+
31
+ ##
32
+ # Is the current playback set to shuffle?
33
+ #
34
+ # @example
35
+ # playback = @sdk.connect.playback
36
+ # playback.shuffling?
37
+ #
38
+ # @return [FalseClass,TrueClass] is_shuffling True if shuffle is set.
39
+ #
40
+ alias_attribute :shuffling?, :shuffle_state
41
+
42
+ ##
43
+ # What repeat mode is the current playback set to?
44
+ #
45
+ # Options:
46
+ # :off => This means no repeat is set.
47
+ # :context => This means it will repeat within the same context.
48
+ # :track => This will repeat the same track.
49
+ #
50
+ # @example
51
+ # playback = @sdk.connect.playback
52
+ # playback.repeat # :off, :context, or :track
53
+ #
54
+ # @return [Symbol] repeat_mode Either :off, :context, or :track
55
+ #
56
+ def repeat_mode
57
+ repeat_state.to_sym
58
+ end
59
+
60
+ ##
61
+ # The current timestamp of the playback state
62
+ #
63
+ # @example
64
+ # playback = @sdk.connect.playback
65
+ # playback.time
66
+ #
67
+ # @return [Time] time The accuracy time of the playback state.
68
+ #
69
+ def time
70
+ Time.at(timestamp / 1000)
71
+ end
72
+
73
+ ##
74
+ # What is the current position of the track?
75
+ #
76
+ # @example
77
+ # playback = @sdk.connect.playback
78
+ # playback.position
79
+ #
80
+ # @return [Integer] position_ms In milliseconds, the position of the track.
81
+ #
82
+ alias_attribute :position, :progress_ms
83
+
84
+ ##
85
+ # How much percentage of the track is the position currently in?
86
+ #
87
+ # @example
88
+ # playback = @sdk.connect.playback
89
+ # playback.position_percentage # => 7.30
90
+ # playback.position_percentage(4) # => 7.3039
91
+ #
92
+ # @param [Integer] decimal_points How many decimal points to return
93
+ # @return [Float] percentage Completion percentage. Rounded to 2 decimal places.
94
+ #
95
+ def position_percentage(decimal_points=2)
96
+ return nil if position.nil?
97
+
98
+ ((position.to_f / item.duration.to_f) * 100).ceil(decimal_points)
99
+ end
100
+
101
+ ##
102
+ # Get the artists for the currently playing track.
103
+ #
104
+ # @example
105
+ # @sdk.connect.playback.artists
106
+ #
107
+ # @return [Array] artists An array of artists wrapped in Spotify::SDK::Artist
108
+ #
109
+ def artists
110
+ item[:artists].map do |artist|
111
+ Spotify::SDK::Artist.new(artist, parent)
112
+ end
113
+ end
114
+
115
+ ##
116
+ # Get the main artist for the currently playing track.
117
+ #
118
+ # @example
119
+ # @sdk.connect.playback.artist
120
+ #
121
+ # @return [Spotify::SDK::Artist] artist The main artist of the track.
122
+ #
123
+ def artist
124
+ artists.first
125
+ end
126
+
127
+ ##
128
+ # Get the item for the currently playing track.
129
+ #
130
+ # @example
131
+ # @sdk.connect.playback.item
132
+ #
133
+ # @return [Spotify::SDK::Item] item The currently playing track, wrapped in Spotify::SDK::Item
134
+ #
135
+ def item
136
+ raise "Playback information is not available if user has a private session enabled" if device.private_session?
137
+
138
+ Spotify::SDK::Item.new(to_h, parent)
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotify
4
+ class SDK
5
+ class Image < Model
6
+ ##
7
+ # Get the ID of the image.
8
+ #
9
+ # @example
10
+ # artist = @sdk.connect.playback.artist
11
+ # artist.images[0].id # => "941223d904f006c4d998598272d43d94"
12
+ #
13
+ # @return [String] image_id The image ID generated from Spotify.
14
+ #
15
+ def id
16
+ url.match(/[a-z0-9]+$/i)[0]
17
+ end
18
+
19
+ ##
20
+ # Get the mobile-related link for the image. Designed for offline mobile apps.
21
+ #
22
+ # @example
23
+ # artist = @sdk.connect.playback.artist
24
+ # artist.images[0].spotify_uri # => "spoitfy:image:..."
25
+ #
26
+ # @return [String] spotify_uri The mobile-embeddable image for the item.
27
+ #
28
+ def spotify_uri
29
+ "spotify:image:%s" % id
30
+ end
31
+
32
+ ##
33
+ # Get the HTTP link for the image. Designed for web apps.
34
+ #
35
+ # @example
36
+ # artist = @sdk.connect.playback.artist
37
+ # artist.images[0].spotify_url # => "https://i.scdn.co/image/..."
38
+ #
39
+ # @return [String] spotify_url The web-embeddable HTTP image for the item.
40
+ #
41
+ alias_attribute :spotify_url, :url
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotify
4
+ class SDK
5
+ class Item < Model
6
+ ##
7
+ # Let's transform the item object into better for us.
8
+ # Before: { track: ..., played_at: ..., context: ... }
9
+ # After: { track_properties..., played_at: ..., context: ... }
10
+ #
11
+ # :nodoc:
12
+ def initialize(payload, parent)
13
+ track = payload.delete(:track) || payload.delete(:item)
14
+ properties = payload.except(:parent, :device, :repeat_state, :shuffle_state)
15
+ super(track.merge(properties: properties), parent)
16
+ end
17
+
18
+ ##
19
+ # Get the album for this item.
20
+ #
21
+ # @example
22
+ # @sdk.connect.playback.item.album
23
+ #
24
+ # @return [Spotify::SDK::Album] album The album object, wrapped in Spotify::SDK::Album
25
+ #
26
+ def album
27
+ Spotify::SDK::Album.new(super, parent)
28
+ end
29
+
30
+ ##
31
+ # Get the artists/creators for this item.
32
+ #
33
+ # @example
34
+ # @sdk.connect.playback.item.artists
35
+ #
36
+ # @return [Array] artists A list of artists, wrapped in Spotify::SDK::Artist
37
+ #
38
+ def artists
39
+ super.map do |artist|
40
+ Spotify::SDK::Artist.new(artist, parent)
41
+ end
42
+ end
43
+
44
+ ##
45
+ # Get the primary artist/creator for this item.
46
+ #
47
+ # @example
48
+ # @sdk.connect.playback.item.artist
49
+ #
50
+ # @return [Spotify::SDK::Artist] artist The primary artist, wrapped in Spotify::SDK::Artist
51
+ #
52
+ def artist
53
+ artists.first
54
+ end
55
+
56
+ ##
57
+ # Get the context.
58
+ #
59
+ # @example
60
+ # @sdk.connect.playback.item.context
61
+ # @sdk.me.history[0].context
62
+ #
63
+ # @return [Hash] context Information about the user's context.
64
+ #
65
+ alias_attribute :context, "properties.context"
66
+
67
+ ##
68
+ # Get the duration.
69
+ # Alias to self.duration_ms
70
+ #
71
+ # @example
72
+ # @sdk.connect.playback.item.duration # => 10331
73
+ #
74
+ # @return [Integer] duration_ms In milliseconds, how long the item is.
75
+ #
76
+ alias_attribute :duration, :duration_ms
77
+
78
+ ##
79
+ # Is this track explicit?
80
+ # Alias to self.explicit
81
+ #
82
+ # @example
83
+ # @sdk.connect.playback.item.explicit? # => true
84
+ #
85
+ # @return [TrueClass,FalseClass] is_explicit Returns true if item contains explicit content.
86
+ #
87
+ alias_attribute :explicit?, :explicit
88
+
89
+ ##
90
+ # Is this a local track, not a Spotify track?
91
+ # Alias to self.is_local
92
+ #
93
+ # @example
94
+ # @sdk.connect.playback.item.local? # => false
95
+ #
96
+ # @return [TrueClass,FalseClass] is_local Returns true if item is local to the user.
97
+ #
98
+ alias_attribute :local?, :is_local
99
+
100
+ ##
101
+ # Is this a playable track?
102
+ # Alias to self.is_playable
103
+ #
104
+ # @example
105
+ # @sdk.connect.playback.item.playable? # => false
106
+ #
107
+ # @return [TrueClass,FalseClass] is_playable Returns true if item is playable.
108
+ #
109
+ alias_attribute :playable?, :is_playable
110
+
111
+ ##
112
+ # Is this a track?
113
+ # Alias to self.type == "track"
114
+ #
115
+ # @example
116
+ # @sdk.connect.playback.item.track? # => true
117
+ #
118
+ # @return [TrueClass,FalseClass] is_track Returns true if item is an music track.
119
+ #
120
+ def track?
121
+ type == "track"
122
+ end
123
+
124
+ ##
125
+ # Get the Spotify URI for this item.
126
+ # Alias to self.uri
127
+ #
128
+ # @example
129
+ # @sdk.connect.playback.item.spotify_uri # => "spotify:track:..."
130
+ #
131
+ # @return [String] spotify_uri The direct URI to this Spotify resource.
132
+ #
133
+ alias_attribute :spotify_uri, :uri
134
+
135
+ ##
136
+ # Get the Spotify HTTP URL for this item.
137
+ # Alias to self.external_urls[:spotify]
138
+ #
139
+ # @example
140
+ # @sdk.connect.playback.item.spotify_url # => "https://open.spotify.com/..."
141
+ #
142
+ # @return [String] spotify_url The direct HTTP URL to this Spotify resource.
143
+ #
144
+ alias_attribute :spotify_url, "external_urls.spotify"
145
+
146
+ ##
147
+ # Get the ISRC for this track.
148
+ #
149
+ # @example
150
+ # @sdk.connect.playback.item.isrc # => "USUM00000000"
151
+ #
152
+ # @return [String] isrc The ISRC string for this track.
153
+ #
154
+ alias_attribute :isrc, "external_ids.isrc"
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotify
4
+ class SDK
5
+ class Me < Base
6
+ ##
7
+ # Get the current user's information.
8
+ # Respective information requires the `user-read-private user-read-email user-read-birthdate` scopes.
9
+ # GET /v1/me
10
+ #
11
+ # @example
12
+ # me = @sdk.me.info
13
+ #
14
+ # @see https://developer.spotify.com/console/get-current-user/
15
+ # @see https://developer.spotify.com/documentation/web-api/reference/users-profile/get-current-users-profile/
16
+ #
17
+ # @param [Hash] override_opts Custom options for HTTParty.
18
+ # @return [Spotify::SDK::Me::Info] user_info Return the user's information.
19
+ #
20
+ def info(override_opts={})
21
+ me_info = send_http_request(:get, "/v1/me", override_opts)
22
+ Spotify::SDK::Me::Info.new(me_info, self)
23
+ end
24
+
25
+ ##
26
+ # Check what tracks a user has recently played.
27
+ #
28
+ # @example
29
+ # @sdk.me.history
30
+ # @sdk.me.history(20)
31
+ #
32
+ # @param [Integer] limit How many results to request. Defaults to 10.
33
+ # @param [Hash] override_opts Custom options for HTTParty.
34
+ # @return [Array] response List of recently played tracked, in chronological order.
35
+ #
36
+ def history(limit=10, override_opts={})
37
+ request = {
38
+ method: :get,
39
+ http_path: "/v1/me/player/recently-played",
40
+ keys: %i[items],
41
+ limit: limit
42
+ }
43
+
44
+ send_multiple_http_requests(request, override_opts).map do |item|
45
+ Spotify::SDK::Item.new(item, self)
46
+ end
47
+ end
48
+
49
+ ##
50
+ # Check if the current user is following N users.
51
+ #
52
+ # @example
53
+ # artists = %w(3q7HBObVc0L8jNeTe5Gofh 0NbfKEOTQCcwd6o7wSDOHI 3TVXtAsR1Inumwj472S9r4)
54
+ # @sdk.me.following?(artists, :artist)
55
+ # # => {"3q7HBObVc0L8jNeTe5Gofh" => false, "0NbfKEOTQCcwd6o7wSDOHI" => false, ...}
56
+ #
57
+ # users = %w(3q7HBObVc0L8jNeTe5Gofh 0NbfKEOTQCcwd6o7wSDOHI 3TVXtAsR1Inumwj472S9r4)
58
+ # @sdk.me.following?(users, :user)
59
+ # # => {"3q7HBObVc0L8jNeTe5Gofh" => false, "0NbfKEOTQCcwd6o7wSDOHI" => false, ...}
60
+ #
61
+ # @param [Array] list List of Spotify user/artist IDs. Cannot mix user and artist IDs in single request.
62
+ # @param [Symbol] type Either :user or :artist. Checks if follows respective type of account.
63
+ # @param [Hash] override_opts Custom options for HTTParty.
64
+ # @return [Hash] hash A hash containing a key with the ID, and a value that equals is_following (boolean).
65
+ #
66
+ def following?(list, type=:artist, override_opts={})
67
+ raise "Must contain an array" unless list.is_a?(Array)
68
+ raise "Must contain an array of String or Spotify::SDK::Artist" if any_of?(list, [String, Spotify::SDK::Artist])
69
+ raise "type must be either 'artist' or 'user'" unless %i[artist user].include?(type)
70
+
71
+ send_is_following_http_requests(list.map {|id| id.try(:id) || id }, type, override_opts)
72
+ end
73
+
74
+ def following_artists?(list, override_opts={})
75
+ following?(list, :artist, override_opts)
76
+ end
77
+
78
+ def following_users?(list, override_opts={})
79
+ following?(list, :user, override_opts)
80
+ end
81
+
82
+ ##
83
+ # Get the current user's followed artists. Requires the `user-read-follow` scope.
84
+ # GET /v1/me/following
85
+ #
86
+ # @example
87
+ # @sdk.me.following
88
+ #
89
+ # @param [Integer] n Number of results to return.
90
+ # @param [Hash] override_opts Custom options for HTTParty.
91
+ # @return [Array] artists A list of followed artists, wrapped in Spotify::SDK::Artist
92
+ #
93
+ def following(limit=50, override_opts={})
94
+ request = {
95
+ method: :get,
96
+ # TODO: Spotify API bug - `limit={n}` returns n-1 artists.
97
+ # ^ Example: `limit=5` returns 4 artists.
98
+ # TODO: Support `type=users` as well as `type=artists`.
99
+ http_path: "/v1/me/following?type=artist&limit=#{[limit, 50].min}",
100
+ keys: %i[artists items],
101
+ limit: limit
102
+ }
103
+
104
+ send_multiple_http_requests(request, override_opts).map do |artist|
105
+ Spotify::SDK::Artist.new(artist, self)
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ def any_of?(array, klasses) # :nodoc:
112
+ (array.map(&:class) - klasses).any?
113
+ end
114
+
115
+ def send_multiple_http_requests(opts, override_opts) # :nodoc:
116
+ response = send_http_request(opts[:method], opts[:http_path], override_opts)
117
+ responses, next_request = hash_deep_lookup(response, opts[:keys].dup)
118
+ if next_request && responses.size < opts[:limit]
119
+ responses += send_multiple_http_requests(opts.merge(http_path: next_request), override_opts)
120
+ end
121
+ responses.first(opts[:limit])
122
+ end
123
+
124
+ def hash_deep_lookup(response, keys) # :nodoc:
125
+ error_message = "Cannot find '%s' key in Spotify::SDK::Me#hash_deep_lookup"
126
+ while keys.any?
127
+ next_request ||= response[:next]
128
+ next_key = keys.shift
129
+ response = next_key ? response[next_key] : raise(error_message % next_key)
130
+ end
131
+ [response, next_request ? next_request[23..-1] : nil]
132
+ end
133
+
134
+ # TODO: Migrate this into the abstracted send_multiple_http_requests
135
+ def send_is_following_http_requests(list, type, override_opts) # :nodoc:
136
+ max_ids = list.first(50)
137
+ remaining_ids = list - max_ids
138
+
139
+ ids = max_ids.map {|id| {id.strip => nil} }.inject(&:merge)
140
+ following = send_http_request(
141
+ :get,
142
+ "/v1/me/following/contains?type=%s&ids=%s" % [type, ids.keys.join(",")],
143
+ override_opts
144
+ )
145
+ ids.each_key {|id| ids[id] = following.shift }
146
+
147
+ # rubocop:disable Style/IfUnlessModifier
148
+ if remaining_ids.any?
149
+ ids.merge(send_is_following_http_requests(remaining_ids, type, override_opts))
150
+ end || ids
151
+ # rubocop:enable Style/IfUnlessModifier
152
+ end
153
+ end
154
+ end
155
+ end