Atmosfire 0.0.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 +7 -0
- data/lib/atmosfire/at_uri.rb +73 -0
- data/lib/atmosfire/collection.rb +48 -0
- data/lib/atmosfire/record.rb +84 -0
- data/lib/atmosfire/repo.rb +46 -0
- data/lib/atmosfire/requests.rb +73 -0
- data/lib/atmosfire/session.rb +70 -0
- data/lib/atmosfire/version.rb +3 -0
- data/lib/atmosfire.rb +9 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2e059c19199525a9436b51dc419fa63dd881800b3a2bbb976350742da931a131
|
4
|
+
data.tar.gz: b19e78694cf71cc16a563d0a8f7e6a863c9f216107cee4226efa1f553dd8ba57
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f2abdb67eb28583c69ec45e6d1c8c140dd61b568060fe5dc95f74666cfb4bdb132c9c88fbcad15af46c4bb1f248a5f0a2edc28fd42231b9209909757ec765b6f
|
7
|
+
data.tar.gz: 3073642340513a9c597586258dcb26c0451d085c297776730de66e35870c64b29728ddcad01b871019e9b13421712d93e2af7031b7a9c32eeab0100a232bfede
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
module Atmosfire
|
4
|
+
module RequestUtils
|
5
|
+
extend T::Sig
|
6
|
+
include Kernel
|
7
|
+
|
8
|
+
sig { params(url: String, atp_host: String).returns(T.nilable(AtUri)) }
|
9
|
+
|
10
|
+
def at_uri(url, atp_host = "https://bsky.social")
|
11
|
+
rulesets = [
|
12
|
+
AtUriParser.create_rule(%r{^#{Regexp.escape("https://")}(bsky\.app)/profile/(.+)/post/([\w]+)$}) do |handle, collection, rkey, pds|
|
13
|
+
handle.start_with?("did:") ? did = handle : did = resolve_handle(handle, pds)
|
14
|
+
AtUri.new(did, "app.bsky.feed.post", rkey)
|
15
|
+
end,
|
16
|
+
|
17
|
+
AtUriParser.create_rule(%r{^at://(.+)/(.+)/(\w+)$}) do |handle, collection, rkey, pds|
|
18
|
+
handle.start_with?("did:") ? did = handle : did = resolve_handle(handle, pds)
|
19
|
+
AtUri.new(did, collection, rkey)
|
20
|
+
end,
|
21
|
+
|
22
|
+
]
|
23
|
+
AtUriParser.parse(url, rulesets, pds: atp_host)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module Atmosfire
|
29
|
+
module AtUriParser
|
30
|
+
extend T::Sig
|
31
|
+
Rule = Struct.new(:pattern, :transform)
|
32
|
+
|
33
|
+
sig { params(url: String, rulesets: T::Array[Rule], pds: String).returns(T.nilable(AtUri)) }
|
34
|
+
def self.parse(url, rulesets, pds: "https://bsky.social")
|
35
|
+
rulesets.each do |ruleset|
|
36
|
+
match_data = url.match(ruleset.pattern)
|
37
|
+
next unless match_data
|
38
|
+
|
39
|
+
at_uri = ruleset.transform.call(match_data, pds)
|
40
|
+
return at_uri if at_uri.is_a?(AtUri)
|
41
|
+
end
|
42
|
+
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.create_rule(pattern, &block)
|
47
|
+
transform = Proc.new do |match_data, pds|
|
48
|
+
block.call(*match_data.captures, pds)
|
49
|
+
end
|
50
|
+
Rule.new(pattern, transform)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
AtUri = Struct.new :repo, :collection, :rkey do
|
55
|
+
extend T::Sig
|
56
|
+
|
57
|
+
def initialize(*args)
|
58
|
+
if args.count == 1
|
59
|
+
parts = args[0].split("/")
|
60
|
+
repo, collection, rkey = parts[2], parts[3], parts[4]
|
61
|
+
elsif args.count == 3
|
62
|
+
repo, collection, rkey = args
|
63
|
+
end
|
64
|
+
super(repo, collection, rkey)
|
65
|
+
end
|
66
|
+
|
67
|
+
sig { returns(String) }
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
"at://#{repo}/#{collection}/#{rkey}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Atmosfire
|
3
|
+
class Repo
|
4
|
+
Collection = Struct.new :repo, :collection do
|
5
|
+
include RequestUtils
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { params(repo: Atmosfire::Repo, lexicon_name: String).void }
|
9
|
+
|
10
|
+
def initialize(repo, lexicon_name)
|
11
|
+
super(repo, lexicon_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
sig { params(limit: Integer).returns(T::Array[Atmosfire::Record]) }
|
15
|
+
|
16
|
+
def list_records(limit = 10)
|
17
|
+
self.repo.pds_endpoint
|
18
|
+
.get.com_atproto_repo_listRecords(
|
19
|
+
repo: self.repo.did,
|
20
|
+
collection: self.collection,
|
21
|
+
limit: limit,
|
22
|
+
)["records"]
|
23
|
+
.map { |record|
|
24
|
+
Atmosfire::Record.from_hash(record)
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { returns(String) }
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
"at://#{self.repo.did}/#{self.collection}"
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { params(rkey: String).returns(T.nilable(Atmosfire::Record)) }
|
35
|
+
|
36
|
+
def [](rkey)
|
37
|
+
Atmosfire::Record.from_uri(
|
38
|
+
T.must(
|
39
|
+
at_uri(
|
40
|
+
"at://#{self.repo.did}/#{@collection}/#{rkey}"
|
41
|
+
)
|
42
|
+
),
|
43
|
+
self.repo.pds
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Atmosfire
|
3
|
+
Record = Struct.new :uri, :cid, :timestamp, :content do
|
4
|
+
extend T::Sig
|
5
|
+
class << self
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { params(json_hash: Hash).returns(T.nilable(Atmosfire::Record)) }
|
9
|
+
|
10
|
+
def from_hash(json_hash)
|
11
|
+
timestamp = nil
|
12
|
+
timestamp = Time.parse json_hash["value"]["createdAt"] if json_hash["value"]["createdAt"]
|
13
|
+
raw_content = json_hash["value"]
|
14
|
+
new(AtUri.new(json_hash["uri"]), json_hash["cid"], timestamp, raw_content)
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { params(uri: Atmosfire::AtUri, pds: String).returns(T.nilable(Atmosfire::Record)) }
|
18
|
+
|
19
|
+
def from_uri(uri, pds = "https://bsky.social")
|
20
|
+
self.from_hash XRPC::Client.new(pds).get.com_atproto_repo_getRecord(
|
21
|
+
repo: uri.repo,
|
22
|
+
collection: uri.collection,
|
23
|
+
rkey: uri.rkey,
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
sig { params(content_hash: Hash, session: Atmosfire::Session, rkey: T.nilable(String)).returns(T.nilable(Atmosfire::Record)) }
|
28
|
+
|
29
|
+
def create(content_hash, session, rkey = nil)
|
30
|
+
return nil if content_hash["$type"].nil?
|
31
|
+
if rkey.nil?
|
32
|
+
rec = from_uri(session.xrpc.post.com_atproto_repo_createRecord(
|
33
|
+
repo: session.did,
|
34
|
+
collection: content_hash["$type"],
|
35
|
+
record: content_hash,
|
36
|
+
)["uri"])
|
37
|
+
return rec
|
38
|
+
else rec = from_uri(session.xrpc.post.com_atproto_repo_createRecord(
|
39
|
+
repo: session.did,
|
40
|
+
collection: content_hash["$type"],
|
41
|
+
rkey: rkey,
|
42
|
+
record: content_hash,
|
43
|
+
)["uri"])
|
44
|
+
return rec; end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def refresh(pds = "https://bsky.social")
|
49
|
+
self.class.from_uri(self.uri, pds)
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { params(session: Atmosfire::Session).returns(T.nilable(Atmosfire::Record)) }
|
53
|
+
|
54
|
+
def update(session)
|
55
|
+
self.delete(session)
|
56
|
+
session.xrpc.post.com_atproto_repo_createRecord(
|
57
|
+
repo: session.did,
|
58
|
+
collection: self.uri.collection,
|
59
|
+
rkey: self.uri.rkey,
|
60
|
+
record: self.content,
|
61
|
+
)
|
62
|
+
self.class.from_uri(self.uri, session.pds)
|
63
|
+
end
|
64
|
+
|
65
|
+
def put(session)
|
66
|
+
session.xrpc.post.com_atproto_repo_putRecord(
|
67
|
+
repo: session.did,
|
68
|
+
collection: self.uri.collection,
|
69
|
+
rkey: self.uri.rkey,
|
70
|
+
record: self.content,
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
sig { params(session: Atmosfire::Session).returns(T.nilable(Integer)) }
|
75
|
+
|
76
|
+
def delete(session)
|
77
|
+
session.xrpc.post.com_atproto_repo_deleteRecord(
|
78
|
+
repo: session.did,
|
79
|
+
collection: self.uri.collection,
|
80
|
+
rkey: self.uri.rkey,
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
class Atmosfire::Repo
|
4
|
+
include Atmosfire::RequestUtils
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
sig { params(username: String, pds: String, open: T::Boolean, authenticate: T.nilable(Atmosfire::Session)).void }
|
8
|
+
|
9
|
+
def initialize(username, pds = "https://bsky.social", open: true, authenticate: nil)
|
10
|
+
@pds = pds
|
11
|
+
@pds_endpoint = XRPC::Client.new(pds)
|
12
|
+
if username.start_with?("did:")
|
13
|
+
@did = username
|
14
|
+
else
|
15
|
+
@did = resolve_handle(username, pds)
|
16
|
+
end
|
17
|
+
@record_list = []
|
18
|
+
if open == true
|
19
|
+
open!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def open!
|
24
|
+
@collections = describe_repo["collections"]
|
25
|
+
end
|
26
|
+
|
27
|
+
sig { returns(Hash) }
|
28
|
+
|
29
|
+
def describe_repo
|
30
|
+
@pds_endpoint.get.com_atproto_repo_describeRepo(repo: @did)
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { returns(Hash) }
|
34
|
+
|
35
|
+
def did_document
|
36
|
+
describe_repo()["didDoc"]
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { params(collection: String).returns(Atmosfire::Repo::Collection) }
|
40
|
+
|
41
|
+
def [](collection)
|
42
|
+
Collection.new(self, collection)
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :did, :record_list, :pds, :pds_endpoint
|
46
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Atmosfire
|
2
|
+
class Error < StandardError; end
|
3
|
+
|
4
|
+
class HTTPError < Error; end
|
5
|
+
|
6
|
+
class UnauthorizedError < HTTPError; end
|
7
|
+
|
8
|
+
module RequestUtils # Goal is to replace with pure XRPC eventually
|
9
|
+
def resolve_handle(username, pds = "https://bsky.social")
|
10
|
+
(XRPC::Client.new(pds).get.com_atproto_identity_resolveHandle(handle: username))["did"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def query_obj_to_query_params(q)
|
14
|
+
out = "?"
|
15
|
+
q.to_h.each do |key, value|
|
16
|
+
out += "#{key}=#{value}&" unless value.nil? || (value.class.method_defined?(:empty?) && value.empty?)
|
17
|
+
end
|
18
|
+
out.slice(0...-1)
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_headers
|
22
|
+
{ "Content-Type" => "application/json" }
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_session_uri(pds)
|
26
|
+
"#{pds}/xrpc/com.atproto.server.createSession"
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete_session_uri(pds)
|
30
|
+
"#{pds}/xrpc/com.atproto.server.deleteSession"
|
31
|
+
end
|
32
|
+
|
33
|
+
def refresh_session_uri(pds)
|
34
|
+
"#{pds}/xrpc/com.atproto.server.refreshSession"
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_session_uri(pds)
|
38
|
+
"#{pds}/xrpc/com.atproto.server.getSession"
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_record_uri(pds)
|
42
|
+
"#{pds}/xrpc/com.atproto.repo.createRecord"
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete_record_uri(pds)
|
46
|
+
"#{pds}/xrpc/com.atproto.repo.deleteRecord"
|
47
|
+
end
|
48
|
+
|
49
|
+
def mute_actor_uri(pds)
|
50
|
+
"#{pds}/xrpc/app.bsky.graph.muteActor"
|
51
|
+
end
|
52
|
+
|
53
|
+
def upload_blob_uri(pds)
|
54
|
+
"#{pds}/xrpc/com.atproto.repo.uploadBlob"
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_post_thread_uri(pds, query)
|
58
|
+
"#{pds}/xrpc/app.bsky.feed.getPostThread#{query_obj_to_query_params(query)}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def default_authenticated_headers(session)
|
62
|
+
default_headers.merge({
|
63
|
+
Authorization: "Bearer #{session.access_token}",
|
64
|
+
})
|
65
|
+
end
|
66
|
+
|
67
|
+
def refresh_token_headers(session)
|
68
|
+
default_headers.merge({
|
69
|
+
Authorization: "Bearer #{session.refresh_token}",
|
70
|
+
})
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
require "atmosfire/requests"
|
4
|
+
|
5
|
+
module Atmosfire
|
6
|
+
Credentials = Struct.new :username, :pw, :pds do
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { params(username: String, pw: String, pds: String).void }
|
10
|
+
|
11
|
+
def initialize(username, pw, pds = "https://bsky.social")
|
12
|
+
super
|
13
|
+
self.pds ||= "https://bsky.social"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Session
|
18
|
+
include RequestUtils
|
19
|
+
extend T::Sig
|
20
|
+
|
21
|
+
attr_reader :pds, :access_token, :refresh_token, :did, :xrpc
|
22
|
+
|
23
|
+
sig { params(credentials: Atmosfire::Credentials, should_open: T::Boolean).void }
|
24
|
+
|
25
|
+
def initialize(credentials, should_open = true)
|
26
|
+
@credentials = credentials
|
27
|
+
@pds = credentials.pds
|
28
|
+
open! if should_open
|
29
|
+
end
|
30
|
+
|
31
|
+
def open!
|
32
|
+
@xrpc = XRPC::Client.new(@pds)
|
33
|
+
response = @xrpc.post.com_atproto_server_createSession(identifier: @credentials.username, password: @credentials.pw)
|
34
|
+
|
35
|
+
raise UnauthorizedError if response["accessJwt"].nil?
|
36
|
+
|
37
|
+
@access_token = response["accessJwt"]
|
38
|
+
@refresh_token = response["refreshJwt"]
|
39
|
+
@did = response["did"]
|
40
|
+
|
41
|
+
@xrpc = XRPC::Client.new(@pds, @access_token)
|
42
|
+
@refresher = XRPC::Client.new(@pds, @refresh_token)
|
43
|
+
end
|
44
|
+
|
45
|
+
def refresh!
|
46
|
+
response = @refresher.post.com_atproto_server_refreshSession
|
47
|
+
raise UnauthorizedError if response["accessJwt"].nil?
|
48
|
+
@access_token = response["accessJwt"]
|
49
|
+
@refresh_token = response["refreshJwt"]
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { returns(T.nilable(Hash)) }
|
53
|
+
|
54
|
+
def get_session
|
55
|
+
@xrpc.get.com_atproto_server_getSession
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete!
|
59
|
+
response = HTTParty.post(
|
60
|
+
URI(delete_session_uri(pds)),
|
61
|
+
headers: refresh_token_headers(self),
|
62
|
+
)
|
63
|
+
if response.code == 200
|
64
|
+
{ success: true }
|
65
|
+
else
|
66
|
+
raise UnauthorizedError
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/atmosfire.rb
ADDED
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: Atmosfire
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Shreyan Jain
|
8
|
+
- Tynan Burke
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2023-07-24 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: xrpc
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 0.1.5
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 0.1.5
|
28
|
+
description: A Ruby gem for interacting with atmosfire
|
29
|
+
email:
|
30
|
+
- shreyan.jain.9@outlook.com
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- "./lib/atmosfire.rb"
|
36
|
+
- "./lib/atmosfire/at_uri.rb"
|
37
|
+
- "./lib/atmosfire/collection.rb"
|
38
|
+
- "./lib/atmosfire/record.rb"
|
39
|
+
- "./lib/atmosfire/repo.rb"
|
40
|
+
- "./lib/atmosfire/requests.rb"
|
41
|
+
- "./lib/atmosfire/session.rb"
|
42
|
+
- "./lib/atmosfire/version.rb"
|
43
|
+
homepage: https://github.com/ShreyanJain9/atmosfire
|
44
|
+
licenses:
|
45
|
+
- MIT
|
46
|
+
metadata: {}
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubygems_version: 3.4.15
|
63
|
+
signing_key:
|
64
|
+
specification_version: 4
|
65
|
+
summary: Interact with the AT Protocol using Ruby
|
66
|
+
test_files: []
|