minisky 0.3.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a753ae5a6f7d2dd3089ee9b6a637e578051261c615fedd28a44f63ac1774b4d
4
- data.tar.gz: 479240c065cb3ba705aa4a9d07efa8c4904ec2aca2e6ed0e847d391ab8cd62fe
3
+ metadata.gz: 4ed55493afc5d821a8e67ea83bcdc8aa29a7d2c9c3afe40e178441a499516a2d
4
+ data.tar.gz: 351119843f56206f7205872a26cbf66758273dfd35fe54b064a346ebb31d8bcb
5
5
  SHA512:
6
- metadata.gz: f87814325757d1b19cee4e403d6e13afbf84b1353b932eedbce23faa6cb1abfe772d0cf2c459b21bb45ea08fa34173457dea3fdb266f32f1f4334e2e90d22692
7
- data.tar.gz: c4bea17a09982db0462e15e092fdf43c454fd94163d04099b119104fde555387621c5c1b8d51e1a1247ad2cece0b5fd9258b1c98555b0ffb7eca4b96107dd83a
6
+ metadata.gz: bc684d8ffe0105e8c8d198acf35c6e9ca4a4856e7a74961adaab548d6471e140bb3d4e42c23ef710caab5449c12136f72c08522ddc68b244b99ca9019a8be832
7
+ data.tar.gz: 196a97d055b3cb6714df1379cbe5b64c8798e5e09e432bdf8ec652374f500cc8e6a34d39d60c976f2fc373c547d90f367fe54fe791e8d9d3eea98d618c96651a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## [0.5.0] - 2024-12-27 🎄
2
+
3
+ * `host` param in the initializer can be passed with a `https://` prefix (useful if you're passing it directly from a DID document, e.g. using DIDKit)
4
+ * added validation of the `method` parameter in request calls: it needs to be either a proper NSID, or a full URL as a string or a URI object
5
+ * added new optional `params` keyword argument in `post_request`, which lets you append query parameters to the URL if a POST endpoint requires passing them this way
6
+ * `default_progress` is set by default to show progress using dots (`.`) if Minisky is loaded inside an IRB or Pry context
7
+ * when experimenting with Minisky in the console, you can now skip the `field:` parameter to `fetch_all` if you don't remember the expected key name in the response, and the method will make a request and return an error which tells you the list of available keys
8
+ * added `access_token_expired?` helper method
9
+ * moved `token_expiration_date` to public methods
10
+ * `check_access` now returns a result symbol: `:logged_in`, `:refreshed` or `:ok`
11
+ * fixed `method_missing` setter on `User`
12
+
13
+ ## [0.4.0] - 2024-03-31 🐣
14
+
15
+ * allow passing non-JSON body to requests (e.g. when uploading blobs)
16
+ * allow passing custom headers to requests, including overriding `Content-Type`
17
+ * fixed error when the response is success but not JSON (e.g. an empty body like in deleteRecord)
18
+ * allow passing options to the client in the initializer
19
+ * aliased `default_progress` setting as `progress`
20
+ * added `base64` dependency explicitly to the gemspec - fixes a warning in Ruby 3.3, since it will be extracted as an optional gem in 3.4
21
+
1
22
  ## [0.3.1] - 2023-10-10
2
23
 
3
24
  * fixed Minisky not working on Ruby 2.x
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The zlib License
2
2
 
3
- Copyright (c) 2023 Jakub Suder
3
+ Copyright (c) 2023-2024 Jakub Suder
4
4
 
5
5
  This software is provided 'as-is', without any express or implied
6
6
  warranty. In no event will the authors be held liable for any damages
data/README.md CHANGED
@@ -1,7 +1,12 @@
1
- # Minisky
1
+ # Minisky 🌤
2
2
 
3
3
  Minisky is a minimal client of the Bluesky (ATProto) API. It provides a simple API client class that you can use to log in to the Bluesky API and make any GET and POST requests there. It's meant to be an easy way to start playing and experimenting with the AT Protocol API.
4
4
 
5
+ This is designed as a low-level XRPC client library - it purposefully does not include any convenience methods like "get posts" or "get profile" etc., it only provides base components that you could use to build a higher level API.
6
+
7
+ > [!NOTE]
8
+ > ATProto Ruby gems collection: [skyfall](https://github.com/mackuba/skyfall) | [blue_factory](https://github.com/mackuba/blue_factory) | [minisky](https://github.com/mackuba/minisky) | [didkit](https://github.com/mackuba/didkit)
9
+
5
10
 
6
11
  ## Installation
7
12
 
@@ -13,7 +18,7 @@ To install the Minisky gem, run the command:
13
18
 
14
19
  Or alternatively, add it to the `Gemfile` file for Bundler:
15
20
 
16
- gem 'minisky', '~> 0.3'
21
+ gem 'minisky', '~> 0.5'
17
22
 
18
23
 
19
24
  ## Usage
@@ -31,14 +36,21 @@ This allows you to do things like:
31
36
  - look up profile information about any account
32
37
  - load complete threads or users' profile feeds from the AppView
33
38
 
34
- To use Minisky this way, create a `Minisky` instance passing the API hostname string (at the moment there is only one server at `bsky.social`, but there will be more once federation support goes live) and `nil` as the configuration in the arguments:
39
+ To use Minisky this way, create a `Minisky` instance, passing the API hostname string and `nil` as the configuration in the arguments. Use the hostname `api.bsky.app` or `public.api.bsky.app` for the AppView, or a PDS hostname for the `com.atproto.*` raw data endpoints:
35
40
 
36
41
  ```rb
37
42
  require 'minisky'
38
43
 
39
- bsky = Minisky.new('bsky.social', nil)
44
+ bsky = Minisky.new('api.bsky.app', nil)
40
45
  ```
41
46
 
47
+ > [!NOTE]
48
+ > To call PDS endpoints like `getRecord` or `listRecords`, you need to connect to the PDS of the user whose data you're loading, not to yours (unless it's the same one). Alternatively, you can use the `bsky.social` "entryway" PDS hostname for any Bluesky-hosted accounts, but this will not work for self-hosted accounts.
49
+ >
50
+ > To look up the PDS hostname of a user given their handle or DID, you can use the [didkit](https://github.com/mackuba/didkit) library.
51
+ >
52
+ > For the AppView, `api.bsky.app` connects directly to Bluesky's AppView, and `public.api.bsky.app` to a version with extra caching that will usually be faster.
53
+
42
54
 
43
55
  ### Authenticated access
44
56
 
@@ -53,9 +65,12 @@ pass: very-secret-password
53
65
 
54
66
  The `id` can be either your handle, or your DID, or the email you've used to sign up. It's recommended that you use the "app password" that you can create in the settings instead of your main account password.
55
67
 
68
+ > [!NOTE]
69
+ > Bluesky has recently implemented OAuth, but Minisky doesn't support it yet - it will be added in a future version. App passwords should still be supported for a fairly long time.
70
+
56
71
  After you log in, this file will also be used to store your access & request tokens and DID. The data in the config file can be accessed through a `user` wrapper property that exposes them as methods, e.g. the password is available as `user.pass` and the DID as `user.did`.
57
72
 
58
- Next, create the Minisky client instance, passing the server name and the config file name:
73
+ Next, create the Minisky client instance, passing your PDS hostname (for Bluesky-hosted PDSes, you can use either `bsky.social` or your specific PDS like `amanita.us-east.host.bsky.network`) and the name of the config file:
59
74
 
60
75
  ```rb
61
76
  require 'minisky'
@@ -93,7 +108,7 @@ bsky.post_request('com.atproto.repo.createRecord', {
93
108
 
94
109
  In authenticated mode, the requests use the saved access token for auth headers automatically. You can also pass `auth: false` or `auth: nil` to not send any authentication headers for a given request, or `auth: sometoken` to use a specific other token. In unauthenticated mode, sending of auth headers is disabled.
95
110
 
96
- The third useful method you can use is `#fetch_all`, which loads multiple paginated responses and collects all returned items on a single list (you need to pass the name of the field that contains the items in the response). Optionally, you can also specify a limit of pages to load as `max_pages: n`, or a break condition `break_when` to stop fetching when any item matches it. You can use it to e.g. to fetch all of your posts from the last 30 days, but not earlier:
111
+ The third useful method you can use is `#fetch_all`, which loads multiple paginated responses and collects all returned items on a single list (you need to pass the name of the field that contains the items in the response). Optionally, you can also specify a limit of pages to load as `max_pages: n`, or a break condition `break_when` to stop fetching when any item matches it. You can use it to e.g. to fetch all of your posts from the last 30 days but not earlier:
97
112
 
98
113
  ```rb
99
114
  time_limit = Time.now - 86400 * 30
@@ -127,7 +142,7 @@ You can find more examples in the [example](https://github.com/mackuba/minisky/t
127
142
 
128
143
  The `Minisky` client currently supports such configuration options:
129
144
 
130
- - `default_progress` - a progress character to automatically use for `#fetch_all` calls (default: `nil`)
145
+ - `default_progress` - a progress character to automatically use for `#fetch_all` calls (default: `.` when in an interactive console, `nil` otherwise)
131
146
  - `send_auth_headers` - whether auth headers should be added by default (default: `true` in authenticated mode)
132
147
  - `auto_manage_tokens` - whether access tokens should be generated and refreshed automatically when needed (default: `true` in authenticated mode)
133
148
 
@@ -177,7 +192,7 @@ The class needs to provide:
177
192
 
178
193
  ## Credits
179
194
 
180
- Copyright © 2023 Kuba Suder ([@mackuba.eu](https://bsky.app/profile/mackuba.eu)).
195
+ Copyright © 2024 Kuba Suder ([@mackuba.eu](https://bsky.app/profile/mackuba.eu)).
181
196
 
182
197
  The code is available under the terms of the [zlib license](https://choosealicense.com/licenses/zlib/) (permissive, similar to MIT).
183
198
 
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Example: print 10 latest posts from the user's home feed.
4
+ #
5
+ # Instead of using a config file to read & store authentication info, this example uses a customized client class
6
+ # which reads the password from the console and creates a throwaway access token.
7
+ #
8
+ # This approach makes sense for one-off scripts, but it shouldn't be used for things that need to be done repeatedly
9
+ # and often (the authentication-related endpoints have lower rate limits than others).
10
+
11
+ # load minisky from a local folder - you normally won't need this
12
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
13
+
14
+ require 'io/console'
15
+ require 'minisky'
16
+
17
+ class TransientClient
18
+ include Minisky::Requests
19
+
20
+ attr_reader :config, :host
21
+
22
+ def initialize(host, user)
23
+ @host = host
24
+ @config = { 'id' => user.gsub(/^@/, '') }
25
+ end
26
+
27
+ def ask_for_password
28
+ print "Enter password for @#{config['id']}: "
29
+ @config['pass'] = STDIN.noecho(&:gets).chomp
30
+ puts
31
+ end
32
+
33
+ def save_config
34
+ # ignore
35
+ end
36
+ end
37
+
38
+ host, handle = ARGV
39
+
40
+ unless host && handle
41
+ puts "Usage: #{$PROGRAM_NAME} <pds_hostname> <handle>"
42
+ exit 1
43
+ end
44
+
45
+ # create a client instance & read password
46
+ bsky = TransientClient.new(host, handle)
47
+ bsky.ask_for_password
48
+
49
+ # fetch 10 posts from the user's home feed
50
+ result = bsky.get_request('app.bsky.feed.getTimeline', { limit: 10 })
51
+
52
+ result['feed'].each do |r|
53
+ reason = r['reason']
54
+ reply = r['reply']
55
+ post = r['post']
56
+
57
+ if reason && reason['$type'] == 'app.bsky.feed.defs#reasonRepost'
58
+ puts "[Reposted by @#{reason['by']['handle']}]"
59
+ end
60
+
61
+ handle = post['author']['handle']
62
+ timestamp = Time.parse(post['record']['createdAt']).getlocal
63
+
64
+ puts "@#{handle} • #{timestamp}"
65
+ puts
66
+
67
+ if reply
68
+ puts "[in reply to @#{reply['parent']['author']['handle']}]"
69
+ puts
70
+ end
71
+
72
+ puts post['record']['text']
73
+ puts
74
+ puts "=" * 120
75
+ puts
76
+ end
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Example: fetch the list of accounts followed by a given user and check which of them have been deleted / deactivated.
4
+
5
+ # load minisky from a local folder - you normally won't need this
6
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
7
+
8
+ require 'didkit'
9
+ require 'minisky'
10
+
11
+ handle = ARGV[0].to_s.gsub(/^@/, '')
12
+ if handle.empty?
13
+ puts "Usage: #{$PROGRAM_NAME} <handle>"
14
+ exit 1
15
+ end
16
+
17
+ pds_host = DID.resolve_handle(handle).get_document.pds_endpoint
18
+ pds = Minisky.new(pds_host, nil, progress: '.')
19
+
20
+ print "Fetching all follows of @#{handle} from #{pds_host}: "
21
+
22
+ follows = pds.fetch_all('com.atproto.repo.listRecords',
23
+ { repo: handle, collection: 'app.bsky.graph.follow', limit: 100 }, field: 'records')
24
+
25
+ puts
26
+ puts "Found #{follows.length} follows"
27
+
28
+ appview = Minisky.new('api.bsky.app', nil)
29
+
30
+ profiles = []
31
+ i = 0
32
+
33
+ puts
34
+ print "Fetching profiles of all followed accounts: "
35
+
36
+ # getProfiles lets us load multiple profiles in one request, but only up to 25 in one batch
37
+
38
+ while i < follows.length
39
+ batch = follows[i...i+25]
40
+ dids = batch.map { |x| x['value']['subject'] }
41
+ print '.'
42
+ result = appview.get_request('app.bsky.actor.getProfiles', { actors: dids })
43
+ profiles += result['profiles']
44
+ i += 25
45
+ end
46
+
47
+ # these are DIDs that are on the follows list, but aren't being returned from getProfiles
48
+ missing = follows.map { |x| x['value']['subject'] } - profiles.map { |x| x['did'] }
49
+
50
+ puts
51
+ puts "#{missing.length} followed accounts are missing:"
52
+ puts
53
+
54
+ missing.each do |did|
55
+ begin
56
+ doc = DID.new(did).get_document
57
+ rescue OpenURI::HTTPError
58
+ puts "#{did} (?) => DID not found"
59
+ next
60
+ end
61
+
62
+ # check account status on their assigned PDS
63
+ pds = Minisky.new(doc.pds_endpoint.gsub('http://', ''), nil)
64
+ status = pds.get_request('com.atproto.sync.getRepoStatus', { did: did }).slice('status', 'active') rescue 'deleted'
65
+
66
+ puts "#{did} (@#{doc.handles.first}) => #{status}"
67
+ end
@@ -52,4 +52,13 @@ class Minisky
52
52
  @location = location
53
53
  end
54
54
  end
55
+
56
+ class FieldNotSetError < Error
57
+ attr_reader :fields
58
+
59
+ def initialize(fields)
60
+ @fields = fields
61
+ super("Field parameter not provided; available fields: #{@fields.inspect}")
62
+ end
63
+ end
55
64
  end
@@ -3,7 +3,7 @@ require 'yaml'
3
3
  class Minisky
4
4
  attr_reader :host, :config
5
5
 
6
- def initialize(host, config_file)
6
+ def initialize(host, config_file, options = {})
7
7
  @host = host
8
8
  @config_file = config_file
9
9
 
@@ -18,6 +18,22 @@ class Minisky
18
18
  @send_auth_headers = false
19
19
  @auto_manage_tokens = false
20
20
  end
21
+
22
+ if active_repl?
23
+ @default_progress = '.'
24
+ end
25
+
26
+ if options
27
+ options.each do |k, v|
28
+ self.send("#{k}=", v)
29
+ end
30
+ end
31
+ end
32
+
33
+ def active_repl?
34
+ return true if defined?(IRB) && IRB.respond_to?(:CurrentContext) && IRB.CurrentContext
35
+ return true if defined?(Pry) && Pry.respond_to?(:cli) && Pry.cli
36
+ false
21
37
  end
22
38
 
23
39
  def save_config
@@ -18,7 +18,7 @@ class Minisky
18
18
  end
19
19
 
20
20
  def method_missing(name, *args)
21
- if name.end_with?('=')
21
+ if name.to_s.end_with?('=')
22
22
  @config[name.to_s.chop] = args[0]
23
23
  else
24
24
  @config[name.to_s]
@@ -26,6 +26,8 @@ class Minisky
26
26
  end
27
27
  end
28
28
 
29
+ NSID_REGEXP = /^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z]{0,61}[a-zA-Z])?)$/
30
+
29
31
  module Requests
30
32
  attr_accessor :default_progress
31
33
  attr_writer :send_auth_headers
@@ -39,19 +41,26 @@ class Minisky
39
41
  instance_variable_defined?('@auto_manage_tokens') ? @auto_manage_tokens : true
40
42
  end
41
43
 
44
+ alias progress default_progress
45
+ alias progress= default_progress=
46
+
42
47
  def base_url
43
- @base_url ||= "https://#{host}/xrpc"
48
+ if host.include?('://')
49
+ host.chomp('/') + '/xrpc'
50
+ else
51
+ "https://#{host}/xrpc"
52
+ end
44
53
  end
45
54
 
46
55
  def user
47
56
  @user ||= User.new(config)
48
57
  end
49
58
 
50
- def get_request(method, params = nil, auth: default_auth_mode)
59
+ def get_request(method, params = nil, auth: default_auth_mode, headers: nil)
51
60
  check_access if auto_manage_tokens && auth == true
52
61
 
53
- headers = authentication_header(auth)
54
- url = URI("#{base_url}/#{method}")
62
+ headers = authentication_header(auth).merge(headers || {})
63
+ url = build_request_uri(method)
55
64
 
56
65
  if params && !params.empty?
57
66
  url.query = URI.encode_www_form(params)
@@ -63,18 +72,30 @@ class Minisky
63
72
  handle_response(response)
64
73
  end
65
74
 
66
- def post_request(method, params = nil, auth: default_auth_mode)
75
+ def post_request(method, data = nil, auth: default_auth_mode, headers: nil, params: nil)
67
76
  check_access if auto_manage_tokens && auth == true
68
77
 
69
- headers = authentication_header(auth).merge({ "Content-Type" => "application/json" })
70
- body = params ? params.to_json : ''
78
+ headers = authentication_header(auth).merge(headers || {})
79
+ headers["Content-Type"] = "application/json" unless headers.keys.any? { |k| k.to_s.downcase == 'content-type' }
80
+
81
+ body = if data.is_a?(String) || data.nil?
82
+ data.to_s
83
+ else
84
+ data.to_json
85
+ end
86
+
87
+ url = build_request_uri(method)
71
88
 
72
- response = Net::HTTP.post(URI("#{base_url}/#{method}"), body, headers)
89
+ if params && !params.empty?
90
+ url.query = URI.encode_www_form(params)
91
+ end
92
+
93
+ response = Net::HTTP.post(url, body, headers)
73
94
  handle_response(response)
74
95
  end
75
96
 
76
- def fetch_all(method, params = nil, field:,
77
- auth: default_auth_mode, break_when: nil, max_pages: nil, progress: @default_progress)
97
+ def fetch_all(method, params = nil, auth: default_auth_mode,
98
+ field: nil, break_when: nil, max_pages: nil, headers: nil, progress: @default_progress)
78
99
  data = []
79
100
  params = {} if params.nil?
80
101
  pages = 0
@@ -82,7 +103,12 @@ class Minisky
82
103
  loop do
83
104
  print(progress) if progress
84
105
 
85
- response = get_request(method, params, auth: auth)
106
+ response = get_request(method, params, auth: auth, headers: headers)
107
+
108
+ if field.nil?
109
+ raise FieldNotSetError, response.keys.select { |f| response[f].is_a?(Array) }
110
+ end
111
+
86
112
  records = response[field]
87
113
  cursor = response['cursor']
88
114
 
@@ -101,8 +127,12 @@ class Minisky
101
127
  def check_access
102
128
  if !user.logged_in?
103
129
  log_in
104
- elsif token_expiration_date(user.access_token) < Time.now + 60
130
+ :logged_in
131
+ elsif access_token_expired?
105
132
  perform_token_refresh
133
+ :refreshed
134
+ else
135
+ :ok
106
136
  end
107
137
  end
108
138
 
@@ -140,6 +170,26 @@ class Minisky
140
170
  json
141
171
  end
142
172
 
173
+ def token_expiration_date(token)
174
+ parts = token.split('.')
175
+ raise AuthError, "Invalid access token format" unless parts.length == 3
176
+
177
+ begin
178
+ payload = JSON.parse(Base64.decode64(parts[1]))
179
+ rescue JSON::ParserError
180
+ raise AuthError, "Couldn't decode payload from access token"
181
+ end
182
+
183
+ exp = payload['exp']
184
+ raise AuthError, "Invalid token expiry data" unless exp.is_a?(Numeric) && exp > 0
185
+
186
+ Time.at(exp)
187
+ end
188
+
189
+ def access_token_expired?
190
+ token_expiration_date(user.access_token) < Time.now + 60
191
+ end
192
+
143
193
  def reset_tokens
144
194
  user.access_token = nil
145
195
  user.refresh_token = nil
@@ -152,14 +202,14 @@ class Minisky
152
202
  alias_method :do_post_request, :post_request
153
203
  private :do_get_request, :do_post_request
154
204
 
155
- def get_request(method, params = nil, auth: default_auth_mode, **kwargs)
205
+ def get_request(method, params = nil, auth: default_auth_mode, headers: nil, **kwargs)
156
206
  params ||= kwargs unless kwargs.empty?
157
- do_get_request(method, params, auth: auth)
207
+ do_get_request(method, params, auth: auth, headers: headers)
158
208
  end
159
209
 
160
- def post_request(method, params = nil, auth: default_auth_mode, **kwargs)
210
+ def post_request(method, params = nil, auth: default_auth_mode, headers: nil, **kwargs)
161
211
  params ||= kwargs unless kwargs.empty?
162
- do_post_request(method, params, auth: auth)
212
+ do_post_request(method, params, auth: auth, headers: headers)
163
213
  end
164
214
  end
165
215
 
@@ -173,6 +223,18 @@ class Minisky
173
223
  end
174
224
  end
175
225
 
226
+ def build_request_uri(method)
227
+ if method.is_a?(URI)
228
+ method
229
+ elsif method.include?('://')
230
+ URI(method)
231
+ elsif method =~ NSID_REGEXP
232
+ URI("#{base_url}/#{method}")
233
+ else
234
+ raise ArgumentError, "Invalid method name #{method.inspect} (should be an NSID, URL or an URI object)"
235
+ end
236
+ end
237
+
176
238
  def default_auth_mode
177
239
  !!send_auth_headers
178
240
  end
@@ -191,35 +253,18 @@ class Minisky
191
253
  end
192
254
  end
193
255
 
194
- def token_expiration_date(token)
195
- parts = token.split('.')
196
- raise AuthError, "Invalid access token format" unless parts.length == 3
197
-
198
- begin
199
- payload = JSON.parse(Base64.decode64(parts[1]))
200
- rescue JSON::ParserError
201
- raise AuthError, "Couldn't decode payload from access token"
202
- end
203
-
204
- exp = payload['exp']
205
- raise AuthError, "Invalid token expiry data" unless exp.is_a?(Numeric) && exp > 0
206
-
207
- Time.at(exp)
208
- end
209
-
210
256
  def handle_response(response)
211
257
  status = response.code.to_i
212
258
  message = response.message
259
+ response_body = (response.content_type == 'application/json') ? JSON.parse(response.body) : response.body
213
260
 
214
261
  case response
215
262
  when Net::HTTPSuccess
216
- JSON.parse(response.body)
263
+ response_body
217
264
  when Net::HTTPRedirection
218
265
  raise UnexpectedRedirect.new(status, message, response['location'])
219
266
  else
220
- data = (response.content_type == 'application/json') ? JSON.parse(response.body) : response.body
221
-
222
- error_class = if data.is_a?(Hash) && data['error'] == 'ExpiredToken'
267
+ error_class = if response_body.is_a?(Hash) && response_body['error'] == 'ExpiredToken'
223
268
  ExpiredTokenError
224
269
  elsif response.is_a?(Net::HTTPClientError)
225
270
  ClientErrorResponse
@@ -229,7 +274,7 @@ class Minisky
229
274
  BadResponse
230
275
  end
231
276
 
232
- raise error_class.new(status, message, data)
277
+ raise error_class.new(status, message, response_body)
233
278
  end
234
279
  end
235
280
  end
@@ -1,5 +1,5 @@
1
1
  require_relative 'minisky'
2
2
 
3
3
  class Minisky
4
- VERSION = "0.3.1"
4
+ VERSION = "0.5.0"
5
5
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minisky
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kuba Suder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-10 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-12-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.1'
13
27
  description: A very simple client class that lets you log in to the Bluesky API and
14
28
  make any requests there.
15
29
  email:
@@ -21,8 +35,10 @@ files:
21
35
  - CHANGELOG.md
22
36
  - LICENSE.txt
23
37
  - README.md
38
+ - example/ask_password.rb
24
39
  - example/fetch_my_posts.rb
25
40
  - example/fetch_profile.rb
41
+ - example/find_missing_follows.rb
26
42
  - example/post_skeet.rb
27
43
  - example/science_feed.rb
28
44
  - lib/minisky.rb