spotifyrb 0.1.0
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.
- checksums.yaml +7 -0
- data/.env.example +3 -0
- data/.github/FUNDING.yml +5 -0
- data/.github/dependabot.yml +11 -0
- data/.github/workflows/ci.yml +42 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +8 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +124 -0
- data/LICENSE.txt +21 -0
- data/README.md +574 -0
- data/Rakefile +10 -0
- data/bin/console +19 -0
- data/bin/setup +8 -0
- data/lib/spotify/client.rb +56 -0
- data/lib/spotify/collection.rb +82 -0
- data/lib/spotify/error.rb +4 -0
- data/lib/spotify/error_generator.rb +128 -0
- data/lib/spotify/oauth.rb +61 -0
- data/lib/spotify/object.rb +19 -0
- data/lib/spotify/objects/album.rb +4 -0
- data/lib/spotify/objects/artist.rb +4 -0
- data/lib/spotify/objects/audiobook.rb +4 -0
- data/lib/spotify/objects/device.rb +4 -0
- data/lib/spotify/objects/episode.rb +4 -0
- data/lib/spotify/objects/player.rb +4 -0
- data/lib/spotify/objects/playlist.rb +4 -0
- data/lib/spotify/objects/show.rb +4 -0
- data/lib/spotify/objects/snapshot.rb +4 -0
- data/lib/spotify/objects/track.rb +4 -0
- data/lib/spotify/objects/user.rb +4 -0
- data/lib/spotify/resource.rb +64 -0
- data/lib/spotify/resources/albums.rb +16 -0
- data/lib/spotify/resources/artists.rb +20 -0
- data/lib/spotify/resources/me.rb +13 -0
- data/lib/spotify/resources/player.rb +46 -0
- data/lib/spotify/resources/playlists.rb +37 -0
- data/lib/spotify/resources/search.rb +10 -0
- data/lib/spotify/resources/users.rb +13 -0
- data/lib/spotify/version.rb +3 -0
- data/lib/spotify.rb +36 -0
- data/lib/spotifyrb.rb +1 -0
- data/spotifyrb.gemspec +29 -0
- metadata +111 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Spotify
|
|
2
|
+
class Collection
|
|
3
|
+
attr_reader :data, :total
|
|
4
|
+
|
|
5
|
+
def self.from_response(response, type:, key: nil)
|
|
6
|
+
body = response.body
|
|
7
|
+
|
|
8
|
+
if key.is_a?(String)
|
|
9
|
+
data = body[key].map { |attrs| type.new(attrs) }
|
|
10
|
+
else
|
|
11
|
+
data = body.map { |attrs| type.new(attrs) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
new(
|
|
15
|
+
data: data,
|
|
16
|
+
total: data.count,
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.from_search_response(response)
|
|
21
|
+
body = response.body
|
|
22
|
+
|
|
23
|
+
data = []
|
|
24
|
+
|
|
25
|
+
puts body["tracks"].count
|
|
26
|
+
|
|
27
|
+
if body["tracks"] && body["tracks"]["items"]
|
|
28
|
+
data.concat(body["tracks"]["items"].map { |attrs| Track.new(attrs) })
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if body["artists"] && body["artists"]["items"]
|
|
32
|
+
data.concat(body["artists"]["items"].map { |attrs| Artist.new(attrs) })
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if body["albums"] && body["albums"]["items"]
|
|
36
|
+
data.concat(body["albums"]["items"].map { |attrs| Album.new(attrs) })
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if body["playlists"] && body["playlists"]["items"]
|
|
40
|
+
data.concat(body["playlists"]["items"].map { |attrs| Playlist.new(attrs) })
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if body["shows"] && body["shows"]["items"]
|
|
44
|
+
data.concat(body["shows"]["items"].map { |attrs| Show.new(attrs) })
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if body["episodes"] && body["episodes"]["items"]
|
|
48
|
+
data.concat(body["episodes"]["items"].map { |attrs| Episode.new(attrs) })
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if body["audiobooks"] && body["audiobooks"]["items"]
|
|
52
|
+
data.concat(body["audiobooks"]["items"].map { |attrs| Audiobook.new(attrs) })
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
new(
|
|
56
|
+
data: data,
|
|
57
|
+
total: data.count,
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def initialize(data:, total:)
|
|
62
|
+
@data = data
|
|
63
|
+
@total = total
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def each(&block)
|
|
67
|
+
data.each(&block)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def first
|
|
71
|
+
data.first
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def last
|
|
75
|
+
data.last
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def count
|
|
79
|
+
data.count
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
module Spotify
|
|
2
|
+
class ErrorGenerator < StandardError
|
|
3
|
+
attr_reader :http_status_code
|
|
4
|
+
attr_reader :spotify_error_code
|
|
5
|
+
attr_reader :spotify_error_message
|
|
6
|
+
|
|
7
|
+
def initialize(response_body, http_status_code)
|
|
8
|
+
@response_body = response_body
|
|
9
|
+
@http_status_code = http_status_code
|
|
10
|
+
set_spotify_error_values
|
|
11
|
+
super(build_message)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def set_spotify_error_values
|
|
17
|
+
@spotify_error_code = @response_body["error"]["status"]
|
|
18
|
+
@spotify_error_message = @response_body["error"]["message"]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def error_message
|
|
22
|
+
@spotify_error_message || @response_body["error"]["message"]
|
|
23
|
+
rescue NoMethodError
|
|
24
|
+
"An unknown error occurred."
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def build_message
|
|
28
|
+
if spotify_error_code.nil?
|
|
29
|
+
return "Error #{@http_status_code}: #{error_message}"
|
|
30
|
+
end
|
|
31
|
+
"Error #{@http_status_code}: #{error_message} '#{spotify_error_message}'"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module Errors
|
|
36
|
+
class BadRequestError < ErrorGenerator
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def error_message
|
|
40
|
+
"Your request was malformed."
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class AuthenticationMissingError < ErrorGenerator
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def error_message
|
|
48
|
+
"You did not supply valid authentication credentials."
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class ForbiddenError < ErrorGenerator
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def error_message
|
|
56
|
+
"You are not allowed to perform that action."
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class EntityNotFoundError < ErrorGenerator
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def error_message
|
|
64
|
+
"No results were found for your request."
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class ConflictError < ErrorGenerator
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def error_message
|
|
72
|
+
"Your request was a conflict."
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class TooManyRequestsError < ErrorGenerator
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def error_message
|
|
80
|
+
"Your request exceeded the API rate limit."
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class InternalError < ErrorGenerator
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def error_message
|
|
88
|
+
"We were unable to perform the request due to server-side problems."
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
class ServiceUnavailableError < ErrorGenerator
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def error_message
|
|
96
|
+
"You have been rate limited for sending more than 20 requests per second."
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class NotImplementedError < ErrorGenerator
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def error_message
|
|
104
|
+
"This resource has not been implemented."
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
class ErrorFactory
|
|
110
|
+
HTTP_ERROR_MAP = {
|
|
111
|
+
400 => Errors::BadRequestError,
|
|
112
|
+
401 => Errors::AuthenticationMissingError,
|
|
113
|
+
403 => Errors::ForbiddenError,
|
|
114
|
+
404 => Errors::EntityNotFoundError,
|
|
115
|
+
409 => Errors::ConflictError,
|
|
116
|
+
429 => Errors::TooManyRequestsError,
|
|
117
|
+
500 => Errors::InternalError,
|
|
118
|
+
503 => Errors::ServiceUnavailableError,
|
|
119
|
+
501 => Errors::NotImplementedError
|
|
120
|
+
}.freeze
|
|
121
|
+
|
|
122
|
+
def self.create(response_body, http_status_code)
|
|
123
|
+
status = http_status_code
|
|
124
|
+
error_class = HTTP_ERROR_MAP[status] || ErrorGenerator
|
|
125
|
+
error_class.new(response_body, http_status_code) if error_class
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Spotify
|
|
2
|
+
class OAuth
|
|
3
|
+
attr_reader :client_id, :client_secret
|
|
4
|
+
|
|
5
|
+
def initialize(client_id:, client_secret:)
|
|
6
|
+
@client_id = client_id
|
|
7
|
+
@client_secret = client_secret
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create(grant_type:, scope: nil)
|
|
11
|
+
send_request(url: "https://id.spotify.tv/oauth2/token", body: {
|
|
12
|
+
client_id: client_id,
|
|
13
|
+
client_secret: client_secret,
|
|
14
|
+
grant_type: grant_type,
|
|
15
|
+
scope: scope
|
|
16
|
+
})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def refresh(refresh_token:)
|
|
20
|
+
send_request(url: "https://id.spotify.tv/oauth2/token", body: {
|
|
21
|
+
client_id: client_id,
|
|
22
|
+
client_secret: client_secret,
|
|
23
|
+
grant_type: "refresh_token",
|
|
24
|
+
refresh_token: refresh_token
|
|
25
|
+
})
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def device(scopes:)
|
|
29
|
+
send_request(url: "https://id.spotify.tv/oauth2/device", body: { client_id: client_id, scope: scopes })
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def validate(token:)
|
|
33
|
+
response = Faraday.get("https://id.spotify.tv/oauth2/validate", nil, { "Authorization" => "OAuth #{token}" })
|
|
34
|
+
|
|
35
|
+
return false if response.status != 200
|
|
36
|
+
|
|
37
|
+
JSON.parse(response.body, object_class: OpenStruct)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def revoke(token:)
|
|
41
|
+
response = Faraday.post("https://id.spotify.tv/oauth2/revoke", {
|
|
42
|
+
client_id: client_id,
|
|
43
|
+
token: token
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
JSON.parse(response.body, object_class: OpenStruct) if response.status != 200
|
|
47
|
+
|
|
48
|
+
true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def send_request(url:, body:)
|
|
54
|
+
response = Faraday.post(url, body)
|
|
55
|
+
|
|
56
|
+
return false if response.status != 200
|
|
57
|
+
|
|
58
|
+
JSON.parse(response.body, object_class: OpenStruct)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require "ostruct"
|
|
2
|
+
|
|
3
|
+
module Spotify
|
|
4
|
+
class Object < OpenStruct
|
|
5
|
+
def initialize(attributes)
|
|
6
|
+
super to_ostruct(attributes)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def to_ostruct(obj)
|
|
10
|
+
if obj.is_a?(Hash)
|
|
11
|
+
OpenStruct.new(obj.map { |key, val| [ key, to_ostruct(val) ] }.to_h)
|
|
12
|
+
elsif obj.is_a?(Array)
|
|
13
|
+
obj.map { |o| to_ostruct(o) }
|
|
14
|
+
else # Assumed to be a primitive value
|
|
15
|
+
obj
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Spotify
|
|
2
|
+
class Resource
|
|
3
|
+
attr_reader :client
|
|
4
|
+
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def get_request(url, params: {}, headers: {})
|
|
12
|
+
handle_response client.connection.get(url, params, headers)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def post_request(url, body:, headers: {})
|
|
16
|
+
handle_response client.connection.post(url, body, headers)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def patch_request(url, body:, headers: {})
|
|
20
|
+
handle_response client.connection.patch(url, body, headers)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def put_request(url, body:, headers: {})
|
|
24
|
+
handle_response client.connection.put(url, body, headers)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# def delete_request(url, params: {}, headers: {})
|
|
28
|
+
# handle_response client.connection.delete(url, params, headers)
|
|
29
|
+
# end
|
|
30
|
+
|
|
31
|
+
def delete_request(url, body: {}, headers: {})
|
|
32
|
+
response = client.connection.delete(url) do |req|
|
|
33
|
+
req.headers["Content-Type"] = "application/json"
|
|
34
|
+
req.body = body.to_json
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
handle_response response
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def handle_response(response)
|
|
41
|
+
return true if response.status == 204 && (response.body.nil? || response.body.empty?)
|
|
42
|
+
return true if response.status == 200 && (response.body.nil? || response.body.empty?)
|
|
43
|
+
return response unless error?(response)
|
|
44
|
+
|
|
45
|
+
raise_error(response)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def error?(response)
|
|
49
|
+
[ 400, 401, 403, 404, 409, 429, 500, 501, 503 ].include?(response.status) ||
|
|
50
|
+
begin
|
|
51
|
+
body = response.body
|
|
52
|
+
body = JSON.parse(body) if body.is_a?(String) && body.strip.start_with?("{")
|
|
53
|
+
body.respond_to?(:key?) && body.key?("error")
|
|
54
|
+
rescue JSON::ParserError
|
|
55
|
+
false
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def raise_error(response)
|
|
60
|
+
error = Spotify::ErrorFactory.create(response.body, response.status)
|
|
61
|
+
raise error if error
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Spotify
|
|
2
|
+
class AlbumsResource < Resource
|
|
3
|
+
def list(ids:, market: nil)
|
|
4
|
+
response = get_request("albums", params: { ids: ids, market: market })
|
|
5
|
+
Collection.from_response(response, type: Album, key: "albums")
|
|
6
|
+
end
|
|
7
|
+
def get(id:, market: nil)
|
|
8
|
+
response = get_request("albums/#{id}", params: { market: market })
|
|
9
|
+
Album.new response.body
|
|
10
|
+
end
|
|
11
|
+
def tracks(id:, **params)
|
|
12
|
+
response = get_request("albums/#{id}/tracks", params: params)
|
|
13
|
+
Collection.from_response(response, type: Track, key: "items")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Spotify
|
|
2
|
+
class ArtistsResource < Resource
|
|
3
|
+
def list(ids:)
|
|
4
|
+
response = get_request("artists", params: { ids: ids })
|
|
5
|
+
Collection.from_response(response, type: Artist, key: "artists")
|
|
6
|
+
end
|
|
7
|
+
def get(id:, market: nil)
|
|
8
|
+
response = get_request("artists/#{id}", params: { market: market })
|
|
9
|
+
Artist.new response.body
|
|
10
|
+
end
|
|
11
|
+
def albums(id:, **params)
|
|
12
|
+
response = get_request("artists/#{id}/albums", params: params)
|
|
13
|
+
Collection.from_response(response, type: Album, key: "items")
|
|
14
|
+
end
|
|
15
|
+
def top_tracks(id:, market: nil)
|
|
16
|
+
response = get_request("artists/#{id}/top-tracks", params: { market: market })
|
|
17
|
+
Collection.from_response(response, type: Track, key: "tracks")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Spotify
|
|
2
|
+
class MeResource < Resource
|
|
3
|
+
def me
|
|
4
|
+
response = get_request("me")
|
|
5
|
+
User.new response.body
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def playlists(**params)
|
|
9
|
+
response = get_request("me/playlists", params: params)
|
|
10
|
+
Collection.from_response(response, type: Playlist, key: "items")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Spotify
|
|
2
|
+
class PlayerResource < Resource
|
|
3
|
+
def state(**params)
|
|
4
|
+
response = get_request("me/player", params: params)
|
|
5
|
+
if response == true
|
|
6
|
+
nil
|
|
7
|
+
else
|
|
8
|
+
Player.new response.body
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def devices
|
|
13
|
+
response = get_request("me/player/devices")
|
|
14
|
+
Collection.from_response(response, type: Device, key: "devices")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def playing(**params)
|
|
18
|
+
response = get_request("me/player/currently-playing", params: params)
|
|
19
|
+
if response == true
|
|
20
|
+
nil
|
|
21
|
+
else
|
|
22
|
+
Player.new response.body
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def play(device: nil)
|
|
27
|
+
response = put_request("me/player/play", body: { device_id: device }.compact)
|
|
28
|
+
response.success?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def pause(device: nil)
|
|
32
|
+
response = put_request("me/player/pause", body: { device_id: device }.compact)
|
|
33
|
+
response.success?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def next(device: nil)
|
|
37
|
+
response = post_request("me/player/next", body: { device_id: device }.compact)
|
|
38
|
+
response.success?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def previous(device: nil)
|
|
42
|
+
response = post_request("me/player/previous", body: { device_id: device }.compact)
|
|
43
|
+
response.success?
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Spotify
|
|
2
|
+
class PlaylistsResource < Resource
|
|
3
|
+
def get(id:, **params)
|
|
4
|
+
response = get_request("playlists/#{id}", params: params)
|
|
5
|
+
Playlist.new response.body
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def update(id:, **attrs)
|
|
9
|
+
put_request("playlists/#{id}", body: attrs)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def tracks(id:, **params)
|
|
13
|
+
response = get_request("playlists/#{id}/tracks", params: params)
|
|
14
|
+
Collection.from_response(response, type: Track, key: "items")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add_tracks(id:, uris:, position: nil)
|
|
18
|
+
body = { uris: uris.split(",") }
|
|
19
|
+
body[:position] = position if position
|
|
20
|
+
response = post_request("playlists/#{id}/tracks", body: body)
|
|
21
|
+
Snapshot.new response.body
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def remove_tracks(id:, uris:, snapshot_id: nil)
|
|
25
|
+
body = { tracks: uris.split(",").map { |uri| { uri: uri } } }
|
|
26
|
+
body[:snapshot_id] = snapshot_id if snapshot_id
|
|
27
|
+
response = delete_request("playlists/#{id}/tracks", body: body)
|
|
28
|
+
Snapshot.new response.body
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create(user:, name:, **params)
|
|
32
|
+
attrs = { name: name }
|
|
33
|
+
response = post_request("users/#{user}/playlists", body: attrs.merge(params))
|
|
34
|
+
Playlist.new response.body
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Spotify
|
|
2
|
+
class SearchResource < Resource
|
|
3
|
+
def search(query:, type:, market: nil, limit: nil, offset: nil)
|
|
4
|
+
params = { q: query, type: type, market: market, limit: limit, offset: offset }
|
|
5
|
+
response = get_request("search", params: params)
|
|
6
|
+
|
|
7
|
+
Collection.from_search_response(response)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Spotify
|
|
2
|
+
class UsersResource < Resource
|
|
3
|
+
def get(id:)
|
|
4
|
+
response = get_request("users/#{id}")
|
|
5
|
+
User.new response.body
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def playlists(id:, **params)
|
|
9
|
+
response = get_request("users/#{id}/playlists", params: params)
|
|
10
|
+
Collection.from_response(response, type: Playlist, key: "items")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/spotify.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require "faraday"
|
|
2
|
+
require "json"
|
|
3
|
+
require "ostruct"
|
|
4
|
+
require "spotify/version"
|
|
5
|
+
|
|
6
|
+
module Spotify
|
|
7
|
+
autoload :Error, "spotify/error"
|
|
8
|
+
autoload :Errors, "spotify/error_generator"
|
|
9
|
+
autoload :ErrorGenerator, "spotify/error_generator"
|
|
10
|
+
autoload :ErrorFactory, "spotify/error_generator"
|
|
11
|
+
|
|
12
|
+
autoload :Client, "spotify/client"
|
|
13
|
+
autoload :Collection, "spotify/collection"
|
|
14
|
+
autoload :Resource, "spotify/resource"
|
|
15
|
+
autoload :Object, "spotify/object"
|
|
16
|
+
|
|
17
|
+
autoload :MeResource, "spotify/resources/me"
|
|
18
|
+
autoload :SearchResource, "spotify/resources/search"
|
|
19
|
+
autoload :PlayerResource, "spotify/resources/player"
|
|
20
|
+
autoload :UsersResource, "spotify/resources/users"
|
|
21
|
+
autoload :AlbumsResource, "spotify/resources/albums"
|
|
22
|
+
autoload :ArtistsResource, "spotify/resources/artists"
|
|
23
|
+
autoload :PlaylistsResource, "spotify/resources/playlists"
|
|
24
|
+
|
|
25
|
+
autoload :User, "spotify/objects/user"
|
|
26
|
+
autoload :Album, "spotify/objects/album"
|
|
27
|
+
autoload :Track, "spotify/objects/track"
|
|
28
|
+
autoload :Artist, "spotify/objects/artist"
|
|
29
|
+
autoload :Audiobook, "spotify/objects/audiobook"
|
|
30
|
+
autoload :Episode, "spotify/objects/episode"
|
|
31
|
+
autoload :Playlist, "spotify/objects/playlist"
|
|
32
|
+
autoload :Show, "spotify/objects/show"
|
|
33
|
+
autoload :Snapshot, "spotify/objects/snapshot"
|
|
34
|
+
autoload :Player, "spotify/objects/player"
|
|
35
|
+
autoload :Device, "spotify/objects/device"
|
|
36
|
+
end
|
data/lib/spotifyrb.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "spotify"
|
data/spotifyrb.gemspec
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require_relative 'lib/spotify/version'
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = "spotifyrb"
|
|
5
|
+
spec.version = Spotify::VERSION
|
|
6
|
+
spec.authors = [ "Dean Perry" ]
|
|
7
|
+
spec.email = [ "dean@deanpcmad.com" ]
|
|
8
|
+
|
|
9
|
+
spec.summary = "A Ruby library for interacting with the Spotify API"
|
|
10
|
+
spec.homepage = "https://deanpcmad.com"
|
|
11
|
+
spec.license = "MIT"
|
|
12
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
|
13
|
+
|
|
14
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
15
|
+
spec.metadata["source_code_uri"] = "https://github.com/deanpcmad/spotifyrb"
|
|
16
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
|
17
|
+
|
|
18
|
+
# Specify which files should be added to the gem when it is released.
|
|
19
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
20
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
22
|
+
end
|
|
23
|
+
spec.bindir = "exe"
|
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
25
|
+
spec.require_paths = [ "lib" ]
|
|
26
|
+
|
|
27
|
+
spec.add_dependency "faraday", "~> 2.11"
|
|
28
|
+
spec.add_dependency "ostruct", "~> 0.6.0"
|
|
29
|
+
end
|