spotify-ruby-kev 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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