bskyrb 0.4 → 0.5.1
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/lib/bskyrb/codegen.rb +212 -0
- data/lib/bskyrb/error.rb +10 -0
- data/lib/bskyrb/firehose.rb +37 -0
- data/lib/bskyrb/generated_classes.rb +6137 -0
- data/lib/bskyrb/records.rb +190 -0
- data/lib/bskyrb/session.rb +126 -0
- data/lib/bskyrb/version.rb +6 -0
- data/lib/bskyrb.rb +8 -0
- metadata +13 -18
@@ -0,0 +1,190 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Bskyrb
|
3
|
+
class RecordManager
|
4
|
+
include RequestUtils
|
5
|
+
attr_reader :session
|
6
|
+
|
7
|
+
def initialize(session)
|
8
|
+
@session = session
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_post_by_url(url, depth = 10)
|
12
|
+
# e.g. "https://staging.bsky.app/profile/naia.bsky.social/post/3jszsrnruws27"
|
13
|
+
# or "at://did:plc:scx5mrfxxrqlfzkjcpbt3xfr/app.bsky.feed.post/3jszsrnruws27"
|
14
|
+
# regex by chatgpt:
|
15
|
+
query = Bskyrb::AppBskyFeedGetpostthread::GetPostThread::Input.new.tap do |q|
|
16
|
+
q.uri = at_post_link(session.pds, url)
|
17
|
+
q.depth = depth
|
18
|
+
end
|
19
|
+
res = HTTParty.get(
|
20
|
+
get_post_thread_uri(session.pds, query),
|
21
|
+
headers: default_authenticated_headers(session),
|
22
|
+
)
|
23
|
+
Bskyrb::AppBskyFeedDefs::PostView.from_hash res["thread"]["post"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def upload_blob(blob_path, content_type)
|
27
|
+
# only images?
|
28
|
+
image_bytes = File.binread(blob_path)
|
29
|
+
HTTParty.post(
|
30
|
+
upload_blob_uri(session.pds),
|
31
|
+
body: image_bytes,
|
32
|
+
headers: default_authenticated_headers(session),
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_record(input)
|
37
|
+
unless input.is_a?(Hash) || input.class.name.include?("Input")
|
38
|
+
raise "`create_record` takes an Input class or a hash"
|
39
|
+
end
|
40
|
+
HTTParty.post(
|
41
|
+
create_record_uri(session.pds),
|
42
|
+
body: input.to_h.compact.to_json,
|
43
|
+
headers: default_authenticated_headers(session),
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def delete_record(collection, rkey)
|
48
|
+
data = { collection: collection, repo: session.did, rkey: rkey }
|
49
|
+
HTTParty.post(
|
50
|
+
delete_record_uri(session),
|
51
|
+
body: data.to_json,
|
52
|
+
headers: default_authenticated_headers(session),
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def create_post(text)
|
57
|
+
input = Bskyrb::ComAtprotoRepoCreaterecord::CreateRecord::Input.from_hash({
|
58
|
+
"collection" => "app.bsky.feed.post",
|
59
|
+
"$type" => "app.bsky.feed.post",
|
60
|
+
"repo" => session.did,
|
61
|
+
"record" => {
|
62
|
+
"$type" => "app.bsky.feed.post",
|
63
|
+
"createdAt" => DateTime.now.iso8601(3),
|
64
|
+
"text" => text,
|
65
|
+
},
|
66
|
+
})
|
67
|
+
create_record(input)
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_reply(replylink, text)
|
71
|
+
reply_to = get_post_by_url(replylink)
|
72
|
+
input = Bskyrb::ComAtprotoRepoCreaterecord::CreateRecord::Input.from_hash({
|
73
|
+
"collection" => "app.bsky.feed.post",
|
74
|
+
"$type" => "app.bsky.feed.post",
|
75
|
+
"repo" => session.did,
|
76
|
+
|
77
|
+
"record" => {
|
78
|
+
"reply" => {
|
79
|
+
"parent" => {
|
80
|
+
"uri" => reply_to.uri,
|
81
|
+
"cid" => reply_to.cid,
|
82
|
+
},
|
83
|
+
"root" => {
|
84
|
+
"uri" => reply_to.uri,
|
85
|
+
"cid" => reply_to.cid,
|
86
|
+
},
|
87
|
+
},
|
88
|
+
"$type" => "app.bsky.feed.post",
|
89
|
+
"createdAt" => DateTime.now.iso8601(3),
|
90
|
+
"text" => text,
|
91
|
+
},
|
92
|
+
})
|
93
|
+
create_record(input)
|
94
|
+
end
|
95
|
+
|
96
|
+
def profile_action(username, type)
|
97
|
+
input = Bskyrb::ComAtprotoRepoCreaterecord::CreateRecord::Input.from_hash({
|
98
|
+
"collection" => type,
|
99
|
+
"repo" => session.did,
|
100
|
+
"record" => {
|
101
|
+
"subject" => resolve_handle(session.pds, username)["did"],
|
102
|
+
"createdAt" => DateTime.now.iso8601(3),
|
103
|
+
"$type" => type,
|
104
|
+
},
|
105
|
+
})
|
106
|
+
create_record(input)
|
107
|
+
end
|
108
|
+
|
109
|
+
def post_action(post, action_type)
|
110
|
+
data = {
|
111
|
+
collection: action_type,
|
112
|
+
repo: session.did,
|
113
|
+
record: {
|
114
|
+
subject: {
|
115
|
+
uri: post.uri,
|
116
|
+
cid: post.cid,
|
117
|
+
},
|
118
|
+
createdAt: DateTime.now.iso8601(3),
|
119
|
+
"$type": action_type,
|
120
|
+
},
|
121
|
+
}
|
122
|
+
create_record(data)
|
123
|
+
end
|
124
|
+
|
125
|
+
def like(post_url)
|
126
|
+
post = get_post_by_url(post_url)
|
127
|
+
post_action(post, "app.bsky.feed.like")
|
128
|
+
end
|
129
|
+
|
130
|
+
def repost(post_url)
|
131
|
+
post = get_post_by_url(post_url)
|
132
|
+
post_action(post, "app.bsky.feed.repost")
|
133
|
+
end
|
134
|
+
|
135
|
+
def follow(username)
|
136
|
+
profile_action(username, "app.bsky.graph.follow")
|
137
|
+
end
|
138
|
+
|
139
|
+
def block(username)
|
140
|
+
profile_action(username, "app.bsky.graph.block")
|
141
|
+
end
|
142
|
+
|
143
|
+
def get_latest_post(username)
|
144
|
+
feed = get_latest_n_posts(username, 1)
|
145
|
+
feed.feed.first
|
146
|
+
end
|
147
|
+
|
148
|
+
def get_latest_n_posts(username, n)
|
149
|
+
query = Bskyrb::AppBskyFeedGetauthorfeed::GetAuthorFeed::Input.new.tap do |q|
|
150
|
+
q.actor = username
|
151
|
+
q.limit = n
|
152
|
+
end
|
153
|
+
hydrate_feed HTTParty.get(
|
154
|
+
get_author_feed_uri(session.pds, query),
|
155
|
+
headers: default_authenticated_headers(session),
|
156
|
+
), Bskyrb::AppBskyFeedGetauthorfeed::GetAuthorFeed::Output
|
157
|
+
end
|
158
|
+
|
159
|
+
def get_skyline(n)
|
160
|
+
query = Bskyrb::AppBskyFeedGettimeline::GetTimeline::Input.new.tap do |q|
|
161
|
+
q.limit = n
|
162
|
+
end
|
163
|
+
hydrate_feed HTTParty.get(
|
164
|
+
get_timeline_uri(session.pds, query),
|
165
|
+
headers: default_authenticated_headers(session),
|
166
|
+
), Bskyrb::AppBskyFeedGettimeline::GetTimeline::Output
|
167
|
+
end
|
168
|
+
|
169
|
+
def get_popular(n)
|
170
|
+
query = Bskyrb::AppBskyUnspeccedGetpopular::GetPopular::Input.new.tap do |q|
|
171
|
+
q.limit = n
|
172
|
+
end
|
173
|
+
hydrate_feed HTTParty.get(
|
174
|
+
get_popular_uri(session.pds, query),
|
175
|
+
headers: default_authenticated_headers(session),
|
176
|
+
), Bskyrb::AppBskyUnspeccedGetpopular::GetPopular::Output
|
177
|
+
end
|
178
|
+
|
179
|
+
def hydrate_feed(response_hash, klass)
|
180
|
+
klass.from_hash(response_hash).tap do |feed|
|
181
|
+
feed.feed = response_hash["feed"].map do |h|
|
182
|
+
Bskyrb::AppBskyFeedDefs::FeedViewPost.from_hash(h).tap do |obj|
|
183
|
+
obj.post = Bskyrb::AppBskyFeedDefs::PostView.from_hash h["post"]
|
184
|
+
obj.reply = Bskyrb::AppBskyFeedDefs::ReplyRef.from_hash h["reply"] if h["reply"]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# typed: false
|
2
|
+
require "uri"
|
3
|
+
require "httparty"
|
4
|
+
|
5
|
+
module Bskyrb
|
6
|
+
module RequestUtils
|
7
|
+
def resolve_handle(pds, username)
|
8
|
+
resolveHandle = XRPC::Endpoint.new(pds, "com.atproto.identity.resolveHandle", :handle)
|
9
|
+
resolveHandle.get(handle: username)
|
10
|
+
end
|
11
|
+
|
12
|
+
def query_obj_to_query_params(q)
|
13
|
+
out = "?"
|
14
|
+
q.to_h.each do |key, value|
|
15
|
+
out += "#{key}=#{value}&" unless value.nil? || (value.class.method_defined?(:empty?) && value.empty?)
|
16
|
+
end
|
17
|
+
out.slice(0...-1)
|
18
|
+
end
|
19
|
+
|
20
|
+
def default_headers
|
21
|
+
{ "Content-Type" => "application/json" }
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_record_uri(pds)
|
25
|
+
"#{pds}/xrpc/com.atproto.repo.createRecord"
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete_record_uri(pds)
|
29
|
+
"#{pds}/xrpc/com.atproto.repo.deleteRecord"
|
30
|
+
end
|
31
|
+
|
32
|
+
def upload_blob_uri(pds)
|
33
|
+
"#{pds}/xrpc/com.atproto.repo.uploadBlob"
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_post_thread_uri(pds, query)
|
37
|
+
"#{pds}/xrpc/app.bsky.feed.getPostThread#{query_obj_to_query_params(query)}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_author_feed_uri(pds, query)
|
41
|
+
"#{pds}/xrpc/app.bsky.feed.getAuthorFeed#{query_obj_to_query_params(query)}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_timeline_uri(pds, query)
|
45
|
+
"#{pds}/xrpc/app.bsky.feed.getTimeline#{query_obj_to_query_params(query)}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_popular_uri(pds, query)
|
49
|
+
"#{pds}/xrpc/app.bsky.unspecced.getPopular#{query_obj_to_query_params(query)}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def default_authenticated_headers(session)
|
53
|
+
default_headers.merge({
|
54
|
+
Authorization: "Bearer #{session.access_token}",
|
55
|
+
})
|
56
|
+
end
|
57
|
+
|
58
|
+
def at_post_link(pds, url)
|
59
|
+
url = url.to_s
|
60
|
+
|
61
|
+
# Check if the URL is already in AT format
|
62
|
+
if url.start_with?("at://")
|
63
|
+
# Validate the username and post ID in the URL
|
64
|
+
parts = url.split("/")
|
65
|
+
if parts.length == 5 && parts[3] == "app.bsky.feed.post"
|
66
|
+
username = parts[2]
|
67
|
+
post_id = parts[4]
|
68
|
+
if post_id
|
69
|
+
return url
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# If the URL is not valid, raise an error
|
74
|
+
raise "The provided URL #{url} is not a valid AT URL"
|
75
|
+
end
|
76
|
+
|
77
|
+
# Validate the format of the regular URL and extract the username and post ID
|
78
|
+
regex = /https:\/\/[a-zA-Z0-9.-]+\/profile\/[a-zA-Z0-9.-]+\/post\/[a-zA-Z0-9.-]+/
|
79
|
+
raise "The provided URL #{url} does not match the expected schema" unless regex.match?(url)
|
80
|
+
username = url.split("/")[-3]
|
81
|
+
post_id = url.split("/")[-1]
|
82
|
+
|
83
|
+
# Validate the username and post ID in the AT URL
|
84
|
+
did = resolve_handle(pds, username)["did"]
|
85
|
+
|
86
|
+
# Construct the AT URL
|
87
|
+
"at://#{did}/app.bsky.feed.post/#{post_id}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class Credentials
|
92
|
+
attr_reader :username, :pw
|
93
|
+
|
94
|
+
def initialize(username, pw)
|
95
|
+
@username = username
|
96
|
+
@pw = pw
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Session
|
101
|
+
include RequestUtils
|
102
|
+
|
103
|
+
attr_reader :credentials, :pds, :access_token, :refresh_token, :did
|
104
|
+
|
105
|
+
def initialize(credentials, pds, should_open = true)
|
106
|
+
@credentials = credentials
|
107
|
+
@pds = pds
|
108
|
+
open! if should_open
|
109
|
+
end
|
110
|
+
|
111
|
+
def open!
|
112
|
+
uri = URI("#{pds}/xrpc/com.atproto.server.createSession")
|
113
|
+
response = HTTParty.post(
|
114
|
+
uri,
|
115
|
+
body: { identifier: credentials.username, password: credentials.pw }.to_json,
|
116
|
+
headers: default_headers,
|
117
|
+
)
|
118
|
+
|
119
|
+
raise UnauthorizedError if response.code == 401
|
120
|
+
|
121
|
+
@access_token = response["accessJwt"]
|
122
|
+
@refresh_token = response["refreshJwt"]
|
123
|
+
@did = response["did"]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/lib/bskyrb.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bskyrb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shreyan Jain
|
8
|
+
- Tynan Burke
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
12
|
+
date: 2023-05-15 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: json
|
@@ -24,20 +25,6 @@ dependencies:
|
|
24
25
|
- - ">="
|
25
26
|
- !ruby/object:Gem::Version
|
26
27
|
version: '2.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: net-http
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
28
|
- !ruby/object:Gem::Dependency
|
42
29
|
name: date
|
43
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,13 +53,21 @@ dependencies:
|
|
66
53
|
- - ">="
|
67
54
|
- !ruby/object:Gem::Version
|
68
55
|
version: '0'
|
69
|
-
description: A
|
56
|
+
description: A Ruby gem for interacting with bsky/atproto
|
70
57
|
email:
|
71
58
|
- shreyan.jain.9@outlook.com
|
72
59
|
executables: []
|
73
60
|
extensions: []
|
74
61
|
extra_rdoc_files: []
|
75
|
-
files:
|
62
|
+
files:
|
63
|
+
- "./lib/bskyrb.rb"
|
64
|
+
- "./lib/bskyrb/codegen.rb"
|
65
|
+
- "./lib/bskyrb/error.rb"
|
66
|
+
- "./lib/bskyrb/firehose.rb"
|
67
|
+
- "./lib/bskyrb/generated_classes.rb"
|
68
|
+
- "./lib/bskyrb/records.rb"
|
69
|
+
- "./lib/bskyrb/session.rb"
|
70
|
+
- "./lib/bskyrb/version.rb"
|
76
71
|
homepage: https://github.com/ShreyanJain9/bskyrb
|
77
72
|
licenses:
|
78
73
|
- MIT
|