spotted 0.32.0 → 0.33.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 +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +3 -4
- data/lib/spotted/client.rb +6 -62
- data/lib/spotted/version.rb +1 -1
- data/lib/spotted.rb +0 -2
- data/rbi/spotted/client.rbi +1 -27
- data/sig/spotted/client.rbs +1 -14
- metadata +2 -5
- data/lib/spotted/internal/oauth2.rb +0 -87
- data/rbi/spotted/internal/oauth2.rbi +0 -34
- data/sig/spotted/internal/oauth2.rbs +0 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6b3243cac7dcf0803cbd0bff07b62b0394ac29dfd62c143bc7c1334157f7f9a9
|
|
4
|
+
data.tar.gz: b59e7369c5fe8604894b0995a0dbdd2ff26d7392d04ca8373ab6b4c1d73e6a4c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf009a7418ffbe4bbcbc04eb49c2fc60916b0d0d45013e134673a146852b79d77aad84e79e9a500c8383e3d6ccae1cd5f0ff35a650d39573871854067c07ff18
|
|
7
|
+
data.tar.gz: 38c5fe4312c8b96e2125aa365064f18580fd144874c0c19b7114d8cea47b1cde3ed501046c963042f89bdd627a6c423646b9b2a4fc879857cfa380fc8160107f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.33.0 (2026-01-15)
|
|
4
|
+
|
|
5
|
+
Full Changelog: [v0.32.0...v0.33.0](https://github.com/cjavdev/spotted/compare/v0.32.0...v0.33.0)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* **api:** manual updates ([4a0240e](https://github.com/cjavdev/spotted/commit/4a0240e10435cc9af7425885c72fa9e361d29613))
|
|
10
|
+
* **api:** manual updates ([c937e8f](https://github.com/cjavdev/spotted/commit/c937e8f8f5ac33270ae32920640f28706df98965))
|
|
11
|
+
* **api:** turn off oauth ([88abcd6](https://github.com/cjavdev/spotted/commit/88abcd62763cadfd6036026af2efd7ee0f2de678))
|
|
12
|
+
|
|
3
13
|
## 0.32.0 (2026-01-14)
|
|
4
14
|
|
|
5
15
|
Full Changelog: [v0.31.1...v0.32.0](https://github.com/cjavdev/spotted/compare/v0.31.1...v0.32.0)
|
data/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Use the Spotted MCP Server to enable AI assistants to interact with this API, al
|
|
|
17
17
|
|
|
18
18
|
Documentation for releases of this gem can be found [on RubyDoc](https://gemdocs.org/gems/spotted).
|
|
19
19
|
|
|
20
|
-
The REST API documentation can be found on [spotted.
|
|
20
|
+
The REST API documentation can be found on [spotted.cjav.dev](https://spotted.cjav.dev).
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
@@ -26,7 +26,7 @@ To use this gem, install via Bundler by adding the following to your application
|
|
|
26
26
|
<!-- x-release-please-start-version -->
|
|
27
27
|
|
|
28
28
|
```ruby
|
|
29
|
-
gem "spotted", "~> 0.
|
|
29
|
+
gem "spotted", "~> 0.33.0"
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
<!-- x-release-please-end -->
|
|
@@ -38,8 +38,7 @@ require "bundler/setup"
|
|
|
38
38
|
require "spotted"
|
|
39
39
|
|
|
40
40
|
spotted = Spotted::Client.new(
|
|
41
|
-
|
|
42
|
-
client_secret: ENV["SPOTIFY_CLIENT_SECRET"] # This is the default and can be omitted
|
|
41
|
+
access_token: ENV["SPOTIFY_ACCESS_TOKEN"] # This is the default and can be omitted
|
|
43
42
|
)
|
|
44
43
|
|
|
45
44
|
album = spotted.albums.retrieve("4aawyAB9vmqN3uQ7FjRGTy")
|
data/lib/spotted/client.rb
CHANGED
|
@@ -15,13 +15,7 @@ module Spotted
|
|
|
15
15
|
# Default max retry delay in seconds.
|
|
16
16
|
DEFAULT_MAX_RETRY_DELAY = 8.0
|
|
17
17
|
|
|
18
|
-
# @return [String
|
|
19
|
-
attr_reader :client_id
|
|
20
|
-
|
|
21
|
-
# @return [String, nil]
|
|
22
|
-
attr_reader :client_secret
|
|
23
|
-
|
|
24
|
-
# @return [String, nil]
|
|
18
|
+
# @return [String]
|
|
25
19
|
attr_reader :access_token
|
|
26
20
|
|
|
27
21
|
# @return [Spotted::Resources::Albums]
|
|
@@ -76,63 +70,13 @@ module Spotted
|
|
|
76
70
|
#
|
|
77
71
|
# @return [Hash{String=>String}]
|
|
78
72
|
private def auth_headers
|
|
79
|
-
unless @access_token.nil?
|
|
80
|
-
return {**bearer_auth}
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
if @client_id && @client_secret
|
|
84
|
-
return {**oauth_2_0}
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
{}
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# @api private
|
|
91
|
-
#
|
|
92
|
-
# @return [Hash{String=>String}]
|
|
93
|
-
private def bearer_auth
|
|
94
73
|
return {} if @access_token.nil?
|
|
95
74
|
|
|
96
75
|
{"authorization" => "Bearer #{@access_token}"}
|
|
97
76
|
end
|
|
98
77
|
|
|
99
|
-
# @api private
|
|
100
|
-
# @return [Spotted::Internal::OAuth2ClientCredentials]
|
|
101
|
-
attr_reader :oauth_2_0_state
|
|
102
|
-
|
|
103
|
-
# @api private
|
|
104
|
-
#
|
|
105
|
-
# @return [Hash{String=>String}]
|
|
106
|
-
private def oauth_2_0
|
|
107
|
-
return @oauth_2_0_state.auth_headers if @oauth_2_0_state
|
|
108
|
-
|
|
109
|
-
return {} unless @client_id && @client_secret
|
|
110
|
-
|
|
111
|
-
path = Spotted::Internal::Util.interpolate_path("https://accounts.spotify.com/api/token")
|
|
112
|
-
token_url = Spotted::Internal::Util.join_parsed_uri(
|
|
113
|
-
@base_url_components,
|
|
114
|
-
{
|
|
115
|
-
path: path,
|
|
116
|
-
query: {grant_type: "client_credentials"}
|
|
117
|
-
}
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
@oauth_2_0_state = Spotted::Internal::OAuth2ClientCredentials.new(
|
|
121
|
-
token_url: token_url.to_s,
|
|
122
|
-
client_id: @client_id,
|
|
123
|
-
client_secret: @client_secret,
|
|
124
|
-
timeout: @timeout,
|
|
125
|
-
client: self
|
|
126
|
-
)
|
|
127
|
-
@oauth_2_0_state.auth_headers
|
|
128
|
-
end
|
|
129
|
-
|
|
130
78
|
# Creates and returns a new client for interacting with the API.
|
|
131
79
|
#
|
|
132
|
-
# @param client_id [String, nil] Defaults to `ENV["SPOTIFY_CLIENT_ID"]`
|
|
133
|
-
#
|
|
134
|
-
# @param client_secret [String, nil] Defaults to `ENV["SPOTIFY_CLIENT_SECRET"]`
|
|
135
|
-
#
|
|
136
80
|
# @param access_token [String, nil] Defaults to `ENV["SPOTIFY_ACCESS_TOKEN"]`
|
|
137
81
|
#
|
|
138
82
|
# @param base_url [String, nil] Override the default base URL for the API, e.g.,
|
|
@@ -146,8 +90,6 @@ module Spotted
|
|
|
146
90
|
#
|
|
147
91
|
# @param max_retry_delay [Float]
|
|
148
92
|
def initialize(
|
|
149
|
-
client_id: ENV["SPOTIFY_CLIENT_ID"],
|
|
150
|
-
client_secret: ENV["SPOTIFY_CLIENT_SECRET"],
|
|
151
93
|
access_token: ENV["SPOTIFY_ACCESS_TOKEN"],
|
|
152
94
|
base_url: ENV["SPOTTED_BASE_URL"],
|
|
153
95
|
max_retries: self.class::DEFAULT_MAX_RETRIES,
|
|
@@ -157,9 +99,11 @@ module Spotted
|
|
|
157
99
|
)
|
|
158
100
|
base_url ||= "https://api.spotify.com/v1"
|
|
159
101
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
102
|
+
if access_token.nil?
|
|
103
|
+
raise ArgumentError.new("access_token is required, and can be set via environ: \"SPOTIFY_ACCESS_TOKEN\"")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
@access_token = access_token.to_s
|
|
163
107
|
|
|
164
108
|
super(
|
|
165
109
|
base_url: base_url,
|
data/lib/spotted/version.rb
CHANGED
data/lib/spotted.rb
CHANGED
|
@@ -16,7 +16,6 @@ require "rbconfig"
|
|
|
16
16
|
require "securerandom"
|
|
17
17
|
require "set"
|
|
18
18
|
require "stringio"
|
|
19
|
-
require "thread"
|
|
20
19
|
require "time"
|
|
21
20
|
require "uri"
|
|
22
21
|
# rubocop:enable Lint/RedundantRequireStatement
|
|
@@ -53,7 +52,6 @@ require_relative "spotted/errors"
|
|
|
53
52
|
require_relative "spotted/internal/transport/base_client"
|
|
54
53
|
require_relative "spotted/internal/transport/pooled_net_requester"
|
|
55
54
|
require_relative "spotted/client"
|
|
56
|
-
require_relative "spotted/internal/oauth2"
|
|
57
55
|
require_relative "spotted/internal/cursor_url_page"
|
|
58
56
|
require_relative "spotted/models/audiobook_base"
|
|
59
57
|
require_relative "spotted/models/playlist_user_object"
|
data/rbi/spotted/client.rbi
CHANGED
|
@@ -10,13 +10,7 @@ module Spotted
|
|
|
10
10
|
|
|
11
11
|
DEFAULT_MAX_RETRY_DELAY = T.let(8.0, Float)
|
|
12
12
|
|
|
13
|
-
sig { returns(
|
|
14
|
-
attr_reader :client_id
|
|
15
|
-
|
|
16
|
-
sig { returns(T.nilable(String)) }
|
|
17
|
-
attr_reader :client_secret
|
|
18
|
-
|
|
19
|
-
sig { returns(T.nilable(String)) }
|
|
13
|
+
sig { returns(String) }
|
|
20
14
|
attr_reader :access_token
|
|
21
15
|
|
|
22
16
|
sig { returns(Spotted::Resources::Albums) }
|
|
@@ -72,25 +66,9 @@ module Spotted
|
|
|
72
66
|
private def auth_headers
|
|
73
67
|
end
|
|
74
68
|
|
|
75
|
-
# @api private
|
|
76
|
-
sig { returns(T::Hash[String, String]) }
|
|
77
|
-
private def bearer_auth
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# @api private
|
|
81
|
-
sig { returns(Spotted::Internal::OAuth2ClientCredentials) }
|
|
82
|
-
attr_reader :oauth_2_0_state
|
|
83
|
-
|
|
84
|
-
# @api private
|
|
85
|
-
sig { returns(T::Hash[String, String]) }
|
|
86
|
-
private def oauth_2_0
|
|
87
|
-
end
|
|
88
|
-
|
|
89
69
|
# Creates and returns a new client for interacting with the API.
|
|
90
70
|
sig do
|
|
91
71
|
params(
|
|
92
|
-
client_id: T.nilable(String),
|
|
93
|
-
client_secret: T.nilable(String),
|
|
94
72
|
access_token: T.nilable(String),
|
|
95
73
|
base_url: T.nilable(String),
|
|
96
74
|
max_retries: Integer,
|
|
@@ -100,10 +78,6 @@ module Spotted
|
|
|
100
78
|
).returns(T.attached_class)
|
|
101
79
|
end
|
|
102
80
|
def self.new(
|
|
103
|
-
# Defaults to `ENV["SPOTIFY_CLIENT_ID"]`
|
|
104
|
-
client_id: ENV["SPOTIFY_CLIENT_ID"],
|
|
105
|
-
# Defaults to `ENV["SPOTIFY_CLIENT_SECRET"]`
|
|
106
|
-
client_secret: ENV["SPOTIFY_CLIENT_SECRET"],
|
|
107
81
|
# Defaults to `ENV["SPOTIFY_ACCESS_TOKEN"]`
|
|
108
82
|
access_token: ENV["SPOTIFY_ACCESS_TOKEN"],
|
|
109
83
|
# Override the default base URL for the API, e.g.,
|
data/sig/spotted/client.rbs
CHANGED
|
@@ -8,11 +8,7 @@ module Spotted
|
|
|
8
8
|
|
|
9
9
|
DEFAULT_MAX_RETRY_DELAY: Float
|
|
10
10
|
|
|
11
|
-
attr_reader
|
|
12
|
-
|
|
13
|
-
attr_reader client_secret: String?
|
|
14
|
-
|
|
15
|
-
attr_reader access_token: String?
|
|
11
|
+
attr_reader access_token: String
|
|
16
12
|
|
|
17
13
|
attr_reader albums: Spotted::Resources::Albums
|
|
18
14
|
|
|
@@ -48,16 +44,7 @@ module Spotted
|
|
|
48
44
|
|
|
49
45
|
private def auth_headers: -> ::Hash[String, String]
|
|
50
46
|
|
|
51
|
-
private def bearer_auth: -> ::Hash[String, String]
|
|
52
|
-
|
|
53
|
-
# @api private
|
|
54
|
-
attr_reader oauth_2_0_state: Spotted::Internal::OAuth2ClientCredentials
|
|
55
|
-
|
|
56
|
-
private def oauth_2_0: -> ::Hash[String, String]
|
|
57
|
-
|
|
58
47
|
def initialize: (
|
|
59
|
-
?client_id: String?,
|
|
60
|
-
?client_secret: String?,
|
|
61
48
|
?access_token: String?,
|
|
62
49
|
?base_url: String?,
|
|
63
50
|
?max_retries: Integer,
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spotted
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.33.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Spotted
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-01-
|
|
11
|
+
date: 2026-01-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: cgi
|
|
@@ -55,7 +55,6 @@ files:
|
|
|
55
55
|
- lib/spotted/file_part.rb
|
|
56
56
|
- lib/spotted/internal.rb
|
|
57
57
|
- lib/spotted/internal/cursor_url_page.rb
|
|
58
|
-
- lib/spotted/internal/oauth2.rb
|
|
59
58
|
- lib/spotted/internal/transport/base_client.rb
|
|
60
59
|
- lib/spotted/internal/transport/pooled_net_requester.rb
|
|
61
60
|
- lib/spotted/internal/type/array_of.rb
|
|
@@ -281,7 +280,6 @@ files:
|
|
|
281
280
|
- rbi/spotted/file_part.rbi
|
|
282
281
|
- rbi/spotted/internal.rbi
|
|
283
282
|
- rbi/spotted/internal/cursor_url_page.rbi
|
|
284
|
-
- rbi/spotted/internal/oauth2.rbi
|
|
285
283
|
- rbi/spotted/internal/transport/base_client.rbi
|
|
286
284
|
- rbi/spotted/internal/transport/pooled_net_requester.rbi
|
|
287
285
|
- rbi/spotted/internal/type/array_of.rbi
|
|
@@ -506,7 +504,6 @@ files:
|
|
|
506
504
|
- sig/spotted/file_part.rbs
|
|
507
505
|
- sig/spotted/internal.rbs
|
|
508
506
|
- sig/spotted/internal/cursor_url_page.rbs
|
|
509
|
-
- sig/spotted/internal/oauth2.rbs
|
|
510
507
|
- sig/spotted/internal/transport/base_client.rbs
|
|
511
508
|
- sig/spotted/internal/transport/pooled_net_requester.rbs
|
|
512
509
|
- sig/spotted/internal/type/array_of.rbs
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Spotted
|
|
4
|
-
module Internal
|
|
5
|
-
class OAuth2ClientCredentials
|
|
6
|
-
# @param token_url [String]
|
|
7
|
-
# @param client_id [String]
|
|
8
|
-
# @param client_secret [String]
|
|
9
|
-
# @param timeout [Integer]
|
|
10
|
-
# @param client [Object]
|
|
11
|
-
def initialize(token_url:, client_id:, client_secret:, timeout:, client:)
|
|
12
|
-
@token_url = token_url
|
|
13
|
-
@client_id = client_id
|
|
14
|
-
@client_secret = client_secret
|
|
15
|
-
@client = client
|
|
16
|
-
@timeout = timeout
|
|
17
|
-
@token = nil
|
|
18
|
-
@token_expires_at = nil
|
|
19
|
-
@mutex = Thread::Mutex.new
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# @api private
|
|
23
|
-
#
|
|
24
|
-
# @return [Hash{String=>String}]
|
|
25
|
-
def auth_headers
|
|
26
|
-
@mutex.synchronize do
|
|
27
|
-
if @token && !token_expired?
|
|
28
|
-
return {"Authorization" => "Bearer #{@token}"}
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
@token = nil
|
|
32
|
-
@token_expires_at = nil
|
|
33
|
-
|
|
34
|
-
token_response = fetch_token
|
|
35
|
-
if token_response
|
|
36
|
-
@token = token_response[:access_token]
|
|
37
|
-
@token_expires_at = Time.now + token_response[:expires_in]
|
|
38
|
-
|
|
39
|
-
return {"Authorization" => "Bearer #{@token}"}
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
{}
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# @api private
|
|
47
|
-
#
|
|
48
|
-
# @return [Boolean]
|
|
49
|
-
private def token_expired?
|
|
50
|
-
return true if @token_expires_at.nil? || @token.nil?
|
|
51
|
-
|
|
52
|
-
# Consider token expired if it expires within 10 seconds
|
|
53
|
-
Time.now > (@token_expires_at - 10)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# @api private
|
|
57
|
-
#
|
|
58
|
-
# @return [Object]
|
|
59
|
-
private def fetch_token
|
|
60
|
-
body = URI.encode_www_form(
|
|
61
|
-
{
|
|
62
|
-
grant_type: "client_credentials",
|
|
63
|
-
client_id: @client_id,
|
|
64
|
-
client_secret: @client_secret
|
|
65
|
-
}
|
|
66
|
-
)
|
|
67
|
-
request = {
|
|
68
|
-
method: :post,
|
|
69
|
-
url: URI(@token_url),
|
|
70
|
-
headers: {
|
|
71
|
-
"Content-Type" => "application/x-www-form-urlencoded"
|
|
72
|
-
},
|
|
73
|
-
body: body,
|
|
74
|
-
max_retries: @client.max_retries,
|
|
75
|
-
timeout: @timeout
|
|
76
|
-
}
|
|
77
|
-
_status, response, stream = @client.send_request(
|
|
78
|
-
request,
|
|
79
|
-
redirect_count: 0,
|
|
80
|
-
retry_count: 0,
|
|
81
|
-
send_retry_header: true
|
|
82
|
-
)
|
|
83
|
-
Spotted::Internal::Util.decode_content(response, stream: stream)
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# typed: strong
|
|
2
|
-
|
|
3
|
-
module Spotted
|
|
4
|
-
module Internal
|
|
5
|
-
class OAuth2ClientCredentials
|
|
6
|
-
sig do
|
|
7
|
-
params(
|
|
8
|
-
token_url: String,
|
|
9
|
-
client_id: String,
|
|
10
|
-
client_secret: String,
|
|
11
|
-
timeout: Integer,
|
|
12
|
-
client: T.anything
|
|
13
|
-
).void
|
|
14
|
-
end
|
|
15
|
-
def initialize(token_url:, client_id:, client_secret:, timeout:, client:)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# @api private
|
|
19
|
-
sig { returns(T::Hash[String, String]) }
|
|
20
|
-
def auth_headers
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# @api private
|
|
24
|
-
sig { returns(T::Boolean) }
|
|
25
|
-
private def token_expired?
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# @api private
|
|
29
|
-
sig { returns(T.anything) }
|
|
30
|
-
private def fetch_token
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
module Spotted
|
|
2
|
-
module Internal
|
|
3
|
-
class OAuth2ClientCredentials
|
|
4
|
-
def initialize: (
|
|
5
|
-
token_url: String,
|
|
6
|
-
client_id: String,
|
|
7
|
-
client_secret: String,
|
|
8
|
-
timeout: Integer,
|
|
9
|
-
client: top
|
|
10
|
-
) -> void
|
|
11
|
-
|
|
12
|
-
def auth_headers: -> ::Hash[String, String]
|
|
13
|
-
|
|
14
|
-
private def token_expired?: -> bool
|
|
15
|
-
|
|
16
|
-
private def fetch_token: -> top
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|