minisky 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +17 -15
- data/example/fetch_my_posts.rb +46 -0
- data/example/post_skeet.rb +34 -0
- data/example/science_feed.rb +52 -0
- data/lib/minisky/errors.rb +55 -0
- data/lib/minisky/minisky.rb +14 -5
- data/lib/minisky/requests.rb +102 -22
- data/lib/minisky/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f4836f5b8d8de9c5f9af0368144fe60f274e1b61176d1c919b70d3fd645a3f3
|
4
|
+
data.tar.gz: 8c87cdbb362b19651e848804aa6c551c88abc759bbdd3df6e609b62b94a7da4b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 84052665f10f23db1513a5135dcb9032f0fbc4c5ae55fc7bbeb15e26d85904acda3ac55170e58d8901e5549ceaaafa34aa038ec8987da9dfdded5349080f12d2
|
7
|
+
data.tar.gz: 5557b97bb51b8feb757a2d16dccfefd7dfd31655a833b379681d89cc924717e234baa19523cda89368f6610481cf11c0dd9494355539d4c7364c9913a05c2ca1
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
## [0.3.0] - 2023-10-05
|
2
|
+
|
3
|
+
* authentication improvements & changes:
|
4
|
+
- Minisky now automatically manages access tokens, calling `check_access` manually is not necessary (set `auto_manage_tokens` to `false` to disable this)
|
5
|
+
- `check_access` now just checks token's expiry time instead of making a request to `getSession`
|
6
|
+
- added `send_auth_headers` option - set to `false` to not set auth header automatically, which is the default
|
7
|
+
- removed default config file name - explicit file name is now required
|
8
|
+
- Minisky can now be used in unauthenticated mode - pass `nil` as the config file name
|
9
|
+
- added `reset_tokens` helper method
|
10
|
+
* refactored response handling - typed errors are now raised on non-success response status
|
11
|
+
* `user` wrapper can also be used for writing fields to the config
|
12
|
+
* improved error handling
|
13
|
+
|
1
14
|
## [0.2.0] - 2023-09-02
|
2
15
|
|
3
16
|
* more consistent handling of parameters in the main methods:
|
data/README.md
CHANGED
@@ -13,12 +13,12 @@ To install the Minisky gem, run the command:
|
|
13
13
|
|
14
14
|
Or alternatively, add it to the `Gemfile` file for Bundler:
|
15
15
|
|
16
|
-
gem 'minisky', '~> 0.
|
16
|
+
gem 'minisky', '~> 0.3'
|
17
17
|
|
18
18
|
|
19
19
|
## Usage
|
20
20
|
|
21
|
-
First, you need to create a `.yml` config file
|
21
|
+
First, you need to create a `.yml` config file with the authentication data, e.g. `bluesky.yml`. It should look like this:
|
22
22
|
|
23
23
|
```yaml
|
24
24
|
id: my.bsky.username
|
@@ -29,12 +29,12 @@ The `id` can be either your handle, or your DID, or the email you've used to sig
|
|
29
29
|
|
30
30
|
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`.
|
31
31
|
|
32
|
-
Next, create the Minisky client instance, passing the server name (at the moment there is only one server at `bsky.social`, but there will be more once federation support goes live):
|
32
|
+
Next, create the Minisky client instance, passing the server name and the config file name (at the moment there is only one server at `bsky.social`, but there will be more once federation support goes live):
|
33
33
|
|
34
34
|
```rb
|
35
35
|
require 'minisky'
|
36
36
|
|
37
|
-
bsky = Minisky.new('bsky.social')
|
37
|
+
bsky = Minisky.new('bsky.social', 'bluesky.yml')
|
38
38
|
bsky.check_access
|
39
39
|
```
|
40
40
|
|
@@ -43,17 +43,22 @@ bsky.check_access
|
|
43
43
|
Now, you can make requests to the Bluesky API using `get_request` and `post_request`:
|
44
44
|
|
45
45
|
```rb
|
46
|
-
bsky.get_request('com.atproto.repo.listRecords', {
|
46
|
+
json = bsky.get_request('com.atproto.repo.listRecords', {
|
47
47
|
repo: bsky.user.did,
|
48
48
|
collection: 'app.bsky.feed.like'
|
49
49
|
})
|
50
50
|
|
51
|
+
json['records'].each do |r|
|
52
|
+
puts r['value']['subject']['uri']
|
53
|
+
end
|
54
|
+
|
51
55
|
bsky.post_request('com.atproto.repo.createRecord', {
|
52
56
|
repo: bsky.user.did,
|
53
57
|
collection: 'app.bsky.feed.post',
|
54
58
|
record: {
|
55
59
|
text: "Hello world!",
|
56
|
-
createdAt: Time.now.iso8601
|
60
|
+
createdAt: Time.now.iso8601,
|
61
|
+
langs: ["en"]
|
57
62
|
}
|
58
63
|
})
|
59
64
|
```
|
@@ -65,7 +70,7 @@ The third useful method you can use is `#fetch_all`, which loads multiple pagina
|
|
65
70
|
```rb
|
66
71
|
time_limit = Time.now - 86400 * 30
|
67
72
|
|
68
|
-
bsky.fetch_all('com.atproto.repo.listRecords',
|
73
|
+
posts = bsky.fetch_all('com.atproto.repo.listRecords',
|
69
74
|
{ repo: bsky.user.did, collection: 'app.bsky.feed.post' },
|
70
75
|
field: 'records',
|
71
76
|
max_pages: 10,
|
@@ -75,7 +80,7 @@ bsky.fetch_all('com.atproto.repo.listRecords',
|
|
75
80
|
There is also a `progress` option you can use to print some kind of character for every page load. E.g. pass `progress: '.'` to print dots as the pages are loading:
|
76
81
|
|
77
82
|
```rb
|
78
|
-
bsky.fetch_all('com.atproto.repo.listRecords',
|
83
|
+
likes = bsky.fetch_all('com.atproto.repo.listRecords',
|
79
84
|
{ repo: bsky.user.did, collection: 'app.bsky.feed.like' },
|
80
85
|
field: 'records',
|
81
86
|
progress: '.')
|
@@ -87,19 +92,16 @@ This will output a line like this:
|
|
87
92
|
.................
|
88
93
|
```
|
89
94
|
|
95
|
+
You can find more examples in the [example](https://github.com/mackuba/minisky/tree/master/example) directory.
|
96
|
+
|
97
|
+
|
90
98
|
## Customization
|
91
99
|
|
92
100
|
The `Minisky` client currently supports one configuration option:
|
93
101
|
|
94
102
|
- `default_progress` - a progress character to automatically use for `#fetch_all` calls (default: `nil`)
|
95
103
|
|
96
|
-
|
97
|
-
|
98
|
-
```rb
|
99
|
-
bsky = Minisky.new('bsky.social', 'config/access.yml')
|
100
|
-
```
|
101
|
-
|
102
|
-
Alternatively, instead of using the `Minisky` class, you can make your own class that includes the `Minisky::Requests` module and provides a different way to load & save the config, e.g. from a JSON file:
|
104
|
+
Instead of using the `Minisky` class, you can also make your own class that includes the `Minisky::Requests` module and provides a different way to load & save the config, e.g. from a JSON file:
|
103
105
|
|
104
106
|
```rb
|
105
107
|
class BlueskyClient
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Example: sync all posts from your account (excluding replies and reposts) to a local JSON file. When run again, it
|
4
|
+
# will only fetch new posts since the last time and append them to the file.
|
5
|
+
#
|
6
|
+
# Requires a bluesky.yml config file in the same directory with contents like this:
|
7
|
+
# id: your.handle
|
8
|
+
# pass: secretpass
|
9
|
+
|
10
|
+
# load minisky from a local folder - you normally won't need this
|
11
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
12
|
+
|
13
|
+
require 'minisky'
|
14
|
+
|
15
|
+
CONFIG_FILE = File.join(__dir__, 'bluesky.yml')
|
16
|
+
POSTS_FILE = File.join(__dir__, 'posts.json')
|
17
|
+
|
18
|
+
# create a client instance
|
19
|
+
bsky = Minisky.new('bsky.social', CONFIG_FILE)
|
20
|
+
|
21
|
+
# print progress dots when loading multiple pages
|
22
|
+
bsky.default_progress = '.'
|
23
|
+
|
24
|
+
# load previously saved posts; we will only fetch posts newer than the last saved before
|
25
|
+
posts = File.exist?(POSTS_FILE) ? JSON.parse(File.read(POSTS_FILE)) : []
|
26
|
+
latest_date = posts[0] && posts[0]['indexedAt']
|
27
|
+
|
28
|
+
# fetch all posts from my timeline (without replies) until the target timestamp
|
29
|
+
results = bsky.fetch_all('app.bsky.feed.getAuthorFeed',
|
30
|
+
{ actor: bsky.user.did, filter: 'posts_no_replies', limit: 100 },
|
31
|
+
field: 'feed',
|
32
|
+
break_when: latest_date && proc { |x| x['post']['indexedAt'] <= latest_date }
|
33
|
+
)
|
34
|
+
|
35
|
+
# trim some data to save space
|
36
|
+
new_posts = results.map { |x| x['post'] }
|
37
|
+
.reject { |x| x['author']['did'] != bsky.user.did } # skip reposts
|
38
|
+
.map { |x| x.except('author') } # skip author profile info
|
39
|
+
|
40
|
+
posts = new_posts + posts
|
41
|
+
|
42
|
+
puts
|
43
|
+
puts "Fetched #{new_posts.length} new posts (total = #{posts.length})"
|
44
|
+
|
45
|
+
# save all new and old posts back to the file
|
46
|
+
File.write(POSTS_FILE, JSON.pretty_generate(posts))
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Example: make a new post (aka "skeet") with text passed in the argument to the script.
|
4
|
+
#
|
5
|
+
# Requires a bluesky.yml config file in the same directory with contents like this:
|
6
|
+
# id: your.handle
|
7
|
+
# pass: secretpass
|
8
|
+
|
9
|
+
# load minisky from a local folder - you normally won't need this
|
10
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
11
|
+
|
12
|
+
require 'minisky'
|
13
|
+
|
14
|
+
if ARGV[0].to_s.empty?
|
15
|
+
puts "Usage: #{$PROGRAM_NAME} <text>"
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
|
19
|
+
text = ARGV[0]
|
20
|
+
|
21
|
+
# create a client instance
|
22
|
+
bsky = Minisky.new('bsky.social', File.join(__dir__, 'bluesky.yml'))
|
23
|
+
|
24
|
+
bsky.post_request('com.atproto.repo.createRecord', {
|
25
|
+
repo: bsky.user.did,
|
26
|
+
collection: 'app.bsky.feed.post',
|
27
|
+
record: {
|
28
|
+
text: text,
|
29
|
+
langs: ["en"],
|
30
|
+
createdAt: Time.now.iso8601
|
31
|
+
}
|
32
|
+
})
|
33
|
+
|
34
|
+
puts "Posted ✓"
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Example: load last 10 posts from the "What's Science" feed and print the post text, data and author handle to the
|
4
|
+
# terminal. Does not require any authentication.
|
5
|
+
#
|
6
|
+
# It's definitely not the most efficient way to do this, but it demonstrates how to load single records from the API.
|
7
|
+
|
8
|
+
# load minisky from a local folder - you normally won't need this
|
9
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
10
|
+
|
11
|
+
require 'minisky'
|
12
|
+
require 'time'
|
13
|
+
|
14
|
+
# the "What's Science" custom feed by @bossett.bsky.social
|
15
|
+
# the service host is hardcoded here, ideally you should fetch the feed record and read the hostname from there
|
16
|
+
FEED_HOST = "bs.bossett.io"
|
17
|
+
FEED_URI = "at://did:plc:jfhpnnst6flqway4eaeqzj2a/app.bsky.feed.generator/for-science"
|
18
|
+
|
19
|
+
# fetch the feed from the feed server (getFeedSkeleton returns only a list or URIs of posts)
|
20
|
+
# pass nil as the config file parameter to create an unauthenticated client
|
21
|
+
feed_api = Minisky.new(FEED_HOST, nil)
|
22
|
+
feed = feed_api.get_request('app.bsky.feed.getFeedSkeleton', { feed: FEED_URI, limit: 10 })
|
23
|
+
|
24
|
+
# second client instance for the Bluesky API - again, pass nil to use without authentication
|
25
|
+
bsky = Minisky.new('bsky.social', nil)
|
26
|
+
|
27
|
+
# for each post URI, fetch the post record and the profile of its author
|
28
|
+
entries = feed['feed'].map do |r|
|
29
|
+
# AT URI is always: at://<did>/<collection>/<rkey>
|
30
|
+
did, collection, rkey = r['post'].gsub('at://', '').split('/')
|
31
|
+
|
32
|
+
print '.'
|
33
|
+
post = bsky.get_request('com.atproto.repo.getRecord', { repo: did, collection: collection, rkey: rkey })
|
34
|
+
author = bsky.get_request('com.atproto.repo.describeRepo', { repo: did })
|
35
|
+
|
36
|
+
[post, author]
|
37
|
+
end
|
38
|
+
|
39
|
+
puts
|
40
|
+
|
41
|
+
entries.each do |post, author|
|
42
|
+
handle = author['handle']
|
43
|
+
timestamp = Time.parse(post['value']['createdAt']).getlocal
|
44
|
+
link = "https://bsky.app/profile/#{handle}/post/#{post['uri'].split('/').last}"
|
45
|
+
|
46
|
+
puts "@#{handle} • #{timestamp} • #{link}"
|
47
|
+
puts
|
48
|
+
puts post['value']['text']
|
49
|
+
puts
|
50
|
+
puts "=" * 120
|
51
|
+
puts
|
52
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative 'minisky'
|
2
|
+
|
3
|
+
class Minisky
|
4
|
+
class Error < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class AuthError < Error
|
8
|
+
def initialize(message)
|
9
|
+
super(message)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class BadResponse < Error
|
14
|
+
attr_reader :status, :data
|
15
|
+
|
16
|
+
def initialize(status, status_message, data)
|
17
|
+
@status = status
|
18
|
+
@data = data
|
19
|
+
|
20
|
+
message = if error_message
|
21
|
+
"#{status} #{status_message}: #{error_message}"
|
22
|
+
else
|
23
|
+
"#{status} #{status_message}"
|
24
|
+
end
|
25
|
+
|
26
|
+
super(message)
|
27
|
+
end
|
28
|
+
|
29
|
+
def error_type
|
30
|
+
@data['error'] if @data.is_a?(Hash)
|
31
|
+
end
|
32
|
+
|
33
|
+
def error_message
|
34
|
+
@data['message'] if @data.is_a?(Hash)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class ClientErrorResponse < BadResponse
|
39
|
+
end
|
40
|
+
|
41
|
+
class ServerErrorResponse < BadResponse
|
42
|
+
end
|
43
|
+
|
44
|
+
class ExpiredTokenError < ClientErrorResponse
|
45
|
+
end
|
46
|
+
|
47
|
+
class UnexpectedRedirect < BadResponse
|
48
|
+
attr_reader :location
|
49
|
+
|
50
|
+
def initialize(status, status_message, location)
|
51
|
+
super(status, status_message, { 'message' => "Unexpected redirect: #{location}" })
|
52
|
+
@location = location
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/minisky/minisky.rb
CHANGED
@@ -1,18 +1,27 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
3
|
class Minisky
|
4
|
-
DEFAULT_CONFIG_FILE = 'bluesky.yml'
|
5
|
-
|
6
4
|
attr_reader :host, :config
|
7
5
|
|
8
|
-
def initialize(host, config_file
|
6
|
+
def initialize(host, config_file)
|
9
7
|
@host = host
|
10
8
|
@config_file = config_file
|
11
|
-
|
9
|
+
|
10
|
+
if @config_file
|
11
|
+
@config = YAML.load(File.read(@config_file))
|
12
|
+
|
13
|
+
if user.id.nil? || user.pass.nil?
|
14
|
+
raise AuthError, "Missing user id or password in the config file #{@config_file}"
|
15
|
+
end
|
16
|
+
else
|
17
|
+
@config = {}
|
18
|
+
@send_auth_headers = false
|
19
|
+
@auto_manage_tokens = false
|
20
|
+
end
|
12
21
|
end
|
13
22
|
|
14
23
|
def save_config
|
15
|
-
File.write(@config_file, YAML.dump(@config))
|
24
|
+
File.write(@config_file, YAML.dump(@config)) if @config_file
|
16
25
|
end
|
17
26
|
end
|
18
27
|
|
data/lib/minisky/requests.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require_relative 'minisky'
|
2
|
+
require_relative 'errors'
|
2
3
|
|
4
|
+
require 'base64'
|
3
5
|
require 'json'
|
4
6
|
require 'net/http'
|
5
|
-
require '
|
7
|
+
require 'time'
|
6
8
|
require 'uri'
|
7
9
|
|
8
10
|
class Minisky
|
@@ -15,13 +17,27 @@ class Minisky
|
|
15
17
|
!!(access_token && refresh_token)
|
16
18
|
end
|
17
19
|
|
18
|
-
def method_missing(name)
|
19
|
-
|
20
|
+
def method_missing(name, *args)
|
21
|
+
if name.end_with?('=')
|
22
|
+
@config[name.to_s.chop] = args[0]
|
23
|
+
else
|
24
|
+
@config[name.to_s]
|
25
|
+
end
|
20
26
|
end
|
21
27
|
end
|
22
28
|
|
23
29
|
module Requests
|
24
30
|
attr_accessor :default_progress
|
31
|
+
attr_writer :send_auth_headers
|
32
|
+
attr_writer :auto_manage_tokens
|
33
|
+
|
34
|
+
def send_auth_headers
|
35
|
+
instance_variable_defined?('@send_auth_headers') ? @send_auth_headers : true
|
36
|
+
end
|
37
|
+
|
38
|
+
def auto_manage_tokens
|
39
|
+
instance_variable_defined?('@auto_manage_tokens') ? @auto_manage_tokens : true
|
40
|
+
end
|
25
41
|
|
26
42
|
def base_url
|
27
43
|
@base_url ||= "https://#{host}/xrpc"
|
@@ -31,7 +47,9 @@ class Minisky
|
|
31
47
|
@user ||= User.new(config)
|
32
48
|
end
|
33
49
|
|
34
|
-
def get_request(method, params = nil, auth:
|
50
|
+
def get_request(method, params = nil, auth: default_auth_mode)
|
51
|
+
check_access if auto_manage_tokens && auth == true
|
52
|
+
|
35
53
|
headers = authentication_header(auth)
|
36
54
|
url = URI("#{base_url}/#{method}")
|
37
55
|
|
@@ -39,21 +57,22 @@ class Minisky
|
|
39
57
|
url.query = URI.encode_www_form(params)
|
40
58
|
end
|
41
59
|
|
42
|
-
|
60
|
+
response = Net::HTTP.get_response(url, headers)
|
61
|
+
handle_response(response)
|
43
62
|
end
|
44
63
|
|
45
|
-
def post_request(method, params = nil, auth:
|
64
|
+
def post_request(method, params = nil, auth: default_auth_mode)
|
65
|
+
check_access if auto_manage_tokens && auth == true
|
66
|
+
|
46
67
|
headers = authentication_header(auth).merge({ "Content-Type" => "application/json" })
|
47
68
|
body = params ? params.to_json : ''
|
48
69
|
|
49
70
|
response = Net::HTTP.post(URI("#{base_url}/#{method}"), body, headers)
|
50
|
-
|
51
|
-
|
52
|
-
JSON.parse(response.body)
|
71
|
+
handle_response(response)
|
53
72
|
end
|
54
73
|
|
55
74
|
def fetch_all(method, params = nil, field:,
|
56
|
-
auth:
|
75
|
+
auth: default_auth_mode, break_when: nil, max_pages: nil, progress: @default_progress)
|
57
76
|
data = []
|
58
77
|
params = {} if params.nil?
|
59
78
|
pages = 0
|
@@ -80,16 +99,16 @@ class Minisky
|
|
80
99
|
def check_access
|
81
100
|
if !user.logged_in?
|
82
101
|
log_in
|
83
|
-
|
84
|
-
|
85
|
-
get_request('com.atproto.server.getSession')
|
86
|
-
rescue OpenURI::HTTPError
|
87
|
-
perform_token_refresh
|
88
|
-
end
|
102
|
+
elsif token_expiration_date(user.access_token) < Time.now + 60
|
103
|
+
perform_token_refresh
|
89
104
|
end
|
90
105
|
end
|
91
106
|
|
92
107
|
def log_in
|
108
|
+
if user.id.nil? || user.pass.nil?
|
109
|
+
raise AuthError, "To log in, please provide a user id and password"
|
110
|
+
end
|
111
|
+
|
93
112
|
data = {
|
94
113
|
identifier: user.id,
|
95
114
|
password: user.pass
|
@@ -97,35 +116,96 @@ class Minisky
|
|
97
116
|
|
98
117
|
json = post_request('com.atproto.server.createSession', data, auth: false)
|
99
118
|
|
100
|
-
|
101
|
-
|
102
|
-
|
119
|
+
user.did = json['did']
|
120
|
+
user.access_token = json['accessJwt']
|
121
|
+
user.refresh_token = json['refreshJwt']
|
103
122
|
|
104
123
|
save_config
|
105
124
|
json
|
106
125
|
end
|
107
126
|
|
108
127
|
def perform_token_refresh
|
128
|
+
if user.refresh_token.nil?
|
129
|
+
raise AuthError, "Can't refresh access token - refresh token is missing"
|
130
|
+
end
|
131
|
+
|
109
132
|
json = post_request('com.atproto.server.refreshSession', auth: user.refresh_token)
|
110
133
|
|
111
|
-
|
112
|
-
|
134
|
+
user.access_token = json['accessJwt']
|
135
|
+
user.refresh_token = json['refreshJwt']
|
113
136
|
|
114
137
|
save_config
|
115
138
|
json
|
116
139
|
end
|
117
140
|
|
141
|
+
def reset_tokens
|
142
|
+
user.access_token = nil
|
143
|
+
user.refresh_token = nil
|
144
|
+
save_config
|
145
|
+
nil
|
146
|
+
end
|
147
|
+
|
118
148
|
private
|
119
149
|
|
150
|
+
def default_auth_mode
|
151
|
+
!!send_auth_headers
|
152
|
+
end
|
153
|
+
|
120
154
|
def authentication_header(auth)
|
121
155
|
if auth.is_a?(String)
|
122
156
|
{ 'Authorization' => "Bearer #{auth}" }
|
123
157
|
elsif auth
|
124
|
-
|
158
|
+
if user.access_token
|
159
|
+
{ 'Authorization' => "Bearer #{user.access_token}" }
|
160
|
+
else
|
161
|
+
raise AuthError, "Can't send auth headers, access token is missing"
|
162
|
+
end
|
125
163
|
else
|
126
164
|
{}
|
127
165
|
end
|
128
166
|
end
|
167
|
+
|
168
|
+
def token_expiration_date(token)
|
169
|
+
parts = token.split('.')
|
170
|
+
raise AuthError, "Invalid access token format" unless parts.length == 3
|
171
|
+
|
172
|
+
begin
|
173
|
+
payload = JSON.parse(Base64.decode64(parts[1]))
|
174
|
+
rescue JSON::ParserError
|
175
|
+
raise AuthError, "Couldn't decode payload from access token"
|
176
|
+
end
|
177
|
+
|
178
|
+
exp = payload['exp']
|
179
|
+
raise AuthError, "Invalid token expiry data" unless exp.is_a?(Numeric) && exp > 0
|
180
|
+
|
181
|
+
Time.at(exp)
|
182
|
+
end
|
183
|
+
|
184
|
+
def handle_response(response)
|
185
|
+
status = response.code.to_i
|
186
|
+
message = response.message
|
187
|
+
|
188
|
+
case response
|
189
|
+
when Net::HTTPSuccess
|
190
|
+
JSON.parse(response.body)
|
191
|
+
when Net::HTTPRedirection
|
192
|
+
raise UnexpectedRedirect.new(status, message, response['location'])
|
193
|
+
else
|
194
|
+
data = (response.content_type == 'application/json') ? JSON.parse(response.body) : response.body
|
195
|
+
|
196
|
+
error_class = if data.is_a?(Hash) && data['error'] == 'ExpiredToken'
|
197
|
+
ExpiredTokenError
|
198
|
+
elsif response.is_a?(Net::HTTPClientError)
|
199
|
+
ClientErrorResponse
|
200
|
+
elsif response.is_a?(Net::HTTPServerError)
|
201
|
+
ServerErrorResponse
|
202
|
+
else
|
203
|
+
BadResponse
|
204
|
+
end
|
205
|
+
|
206
|
+
raise error_class.new(status, message, data)
|
207
|
+
end
|
208
|
+
end
|
129
209
|
end
|
130
210
|
|
131
211
|
include Requests
|
data/lib/minisky/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minisky
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.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-
|
11
|
+
date: 2023-10-05 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A very simple client class that lets you log in to the Bluesky API and
|
14
14
|
make any requests there.
|
@@ -21,7 +21,11 @@ files:
|
|
21
21
|
- CHANGELOG.md
|
22
22
|
- LICENSE.txt
|
23
23
|
- README.md
|
24
|
+
- example/fetch_my_posts.rb
|
25
|
+
- example/post_skeet.rb
|
26
|
+
- example/science_feed.rb
|
24
27
|
- lib/minisky.rb
|
28
|
+
- lib/minisky/errors.rb
|
25
29
|
- lib/minisky/minisky.rb
|
26
30
|
- lib/minisky/requests.rb
|
27
31
|
- lib/minisky/version.rb
|