bskyrb 0.4 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|