skyfall 0.6.0 → 0.7.0
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/CHANGELOG.md +60 -10
- data/LICENSE.txt +1 -1
- data/README.md +10 -11
- data/lib/skyfall/car_archive.rb +42 -6
- data/lib/skyfall/cid.rb +2 -0
- data/lib/skyfall/collection.rb +23 -1
- data/lib/skyfall/errors.rb +58 -4
- data/lib/skyfall/events.rb +19 -0
- data/lib/skyfall/extensions.rb +4 -0
- data/lib/skyfall/firehose/account_message.rb +32 -4
- data/lib/skyfall/firehose/commit_message.rb +45 -5
- data/lib/skyfall/firehose/identity_message.rb +30 -2
- data/lib/skyfall/firehose/info_message.rb +35 -1
- data/lib/skyfall/firehose/labels_message.rb +28 -10
- data/lib/skyfall/firehose/message.rb +133 -24
- data/lib/skyfall/firehose/operation.rb +57 -5
- data/lib/skyfall/firehose/sync_message.rb +31 -0
- data/lib/skyfall/firehose/unknown_message.rb +8 -0
- data/lib/skyfall/firehose.rb +103 -9
- data/lib/skyfall/jetstream/account_message.rb +21 -1
- data/lib/skyfall/jetstream/commit_message.rb +35 -2
- data/lib/skyfall/jetstream/identity_message.rb +22 -1
- data/lib/skyfall/jetstream/message.rb +94 -7
- data/lib/skyfall/jetstream/operation.rb +56 -4
- data/lib/skyfall/jetstream/unknown_message.rb +8 -0
- data/lib/skyfall/jetstream.rb +95 -13
- data/lib/skyfall/label.rb +33 -0
- data/lib/skyfall/stream.rb +304 -53
- data/lib/skyfall/version.rb +1 -1
- metadata +9 -14
- data/example/block_tracker.rb +0 -84
- data/example/jet_monitor_phrases.rb +0 -54
- data/example/print_all_posts.rb +0 -34
- data/example/push_notifications.rb +0 -262
- data/lib/skyfall/firehose/handle_message.rb +0 -14
- data/lib/skyfall/firehose/tombstone_message.rb +0 -11
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
# Example: send push notifications to a client app about interactions with a given account.
|
|
4
|
-
|
|
5
|
-
# load skyfall from a local folder - you normally won't need this
|
|
6
|
-
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
|
7
|
-
|
|
8
|
-
require 'json'
|
|
9
|
-
require 'open-uri'
|
|
10
|
-
require 'skyfall'
|
|
11
|
-
|
|
12
|
-
monitored_did = ARGV[0]
|
|
13
|
-
|
|
14
|
-
if monitored_did.to_s.empty?
|
|
15
|
-
puts "Usage: #{$PROGRAM_NAME} <monitored_did>"
|
|
16
|
-
exit 1
|
|
17
|
-
elsif monitored_did !~ /^did:plc:[a-z0-9]{24}$/
|
|
18
|
-
puts "Not a valid DID: #{monitored_did}"
|
|
19
|
-
exit 1
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
class InvalidURIException < StandardError
|
|
23
|
-
def initialize(uri)
|
|
24
|
-
super("Invalid AT URI: #{uri}")
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
class AtURI
|
|
29
|
-
attr_reader :did, :collection, :rkey
|
|
30
|
-
|
|
31
|
-
def initialize(uri)
|
|
32
|
-
if uri =~ /\Aat:\/\/(did:[\w]+:[\w\.\-]+)\/([\w\.]+)\/([\w\-]+)\z/
|
|
33
|
-
@did = $1
|
|
34
|
-
@collection = $2
|
|
35
|
-
@rkey = $3
|
|
36
|
-
else
|
|
37
|
-
raise InvalidURIException, uri
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
class NotificationEngine
|
|
43
|
-
def initialize(user_did)
|
|
44
|
-
@user_did = user_did
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def connect
|
|
48
|
-
@sky = Skyfall::Firehose.new('bsky.network', :subscribe_repos)
|
|
49
|
-
|
|
50
|
-
@sky.on_connect { puts "Connected, monitoring #{@user_did}" }
|
|
51
|
-
@sky.on_disconnect { puts "Disconnected" }
|
|
52
|
-
@sky.on_reconnect { puts "Reconnecting..." }
|
|
53
|
-
@sky.on_error { |e| puts "ERROR: #{e}" }
|
|
54
|
-
|
|
55
|
-
@sky.on_message do |msg|
|
|
56
|
-
process_message(msg)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
@sky.connect
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def disconnect
|
|
63
|
-
@sky.disconnect
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def process_message(msg)
|
|
67
|
-
# we're only interested in repo commit messages
|
|
68
|
-
return if msg.type != :commit
|
|
69
|
-
|
|
70
|
-
# ignore user's own actions
|
|
71
|
-
return if msg.repo == @user_did
|
|
72
|
-
|
|
73
|
-
msg.operations.each do |op|
|
|
74
|
-
next if op.action != :create
|
|
75
|
-
|
|
76
|
-
begin
|
|
77
|
-
case op.type
|
|
78
|
-
when :bsky_post
|
|
79
|
-
process_post(msg, op)
|
|
80
|
-
when :bsky_like
|
|
81
|
-
process_like(msg, op)
|
|
82
|
-
when :bsky_repost
|
|
83
|
-
process_repost(msg, op)
|
|
84
|
-
when :bsky_follow
|
|
85
|
-
process_follow(msg, op)
|
|
86
|
-
end
|
|
87
|
-
rescue StandardError => e
|
|
88
|
-
puts "Error: #{e}"
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# posts
|
|
95
|
-
|
|
96
|
-
def process_post(msg, op)
|
|
97
|
-
data = op.raw_record
|
|
98
|
-
|
|
99
|
-
if reply = data['reply']
|
|
100
|
-
# check for replies (direct only)
|
|
101
|
-
if reply['parent'] && reply['parent']['uri']
|
|
102
|
-
parent_uri = AtURI.new(reply['parent']['uri'])
|
|
103
|
-
|
|
104
|
-
if parent_uri.did == @user_did
|
|
105
|
-
send_reply_notification(msg, op)
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
if embed = data['embed']
|
|
111
|
-
# check for quotes
|
|
112
|
-
if embed['record'] && embed['record']['uri']
|
|
113
|
-
quoted_uri = AtURI.new(embed['record']['uri'])
|
|
114
|
-
|
|
115
|
-
if quoted_uri.did == @user_did
|
|
116
|
-
send_quote_notification(msg, op)
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# second type of quote (recordWithMedia)
|
|
121
|
-
if embed['record'] && embed['record']['record'] && embed['record']['record']['uri']
|
|
122
|
-
quoted_uri = AtURI.new(embed['record']['record']['uri'])
|
|
123
|
-
|
|
124
|
-
if quoted_uri.did == @user_did
|
|
125
|
-
send_quote_notification(msg, op)
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
if facets = data['facets']
|
|
131
|
-
# check for mentions
|
|
132
|
-
if facets.any? { |f| f['features'] && f['features'].any? { |x| x['did'] == @user_did }}
|
|
133
|
-
send_mention_notification(msg, op)
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def send_reply_notification(msg, op)
|
|
139
|
-
handle = get_user_handle(msg.repo)
|
|
140
|
-
|
|
141
|
-
send_push("@#{handle} replied:", op.raw_record)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def send_quote_notification(msg, op)
|
|
145
|
-
handle = get_user_handle(msg.repo)
|
|
146
|
-
|
|
147
|
-
send_push("@#{handle} quoted you:", op.raw_record)
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def send_mention_notification(msg, op)
|
|
151
|
-
handle = get_user_handle(msg.repo)
|
|
152
|
-
|
|
153
|
-
send_push("@#{handle} mentioned you:", op.raw_record)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
# likes
|
|
158
|
-
|
|
159
|
-
def process_like(msg, op)
|
|
160
|
-
data = op.raw_record
|
|
161
|
-
|
|
162
|
-
if data['subject'] && data['subject']['uri']
|
|
163
|
-
liked_uri = AtURI.new(data['subject']['uri'])
|
|
164
|
-
|
|
165
|
-
if liked_uri.did == @user_did
|
|
166
|
-
case liked_uri.collection
|
|
167
|
-
when 'app.bsky.feed.post'
|
|
168
|
-
send_post_like_notification(msg, liked_uri)
|
|
169
|
-
when 'app.bsky.feed.generator'
|
|
170
|
-
send_feed_like_notification(msg, liked_uri)
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def send_post_like_notification(msg, uri)
|
|
177
|
-
handle = get_user_handle(msg.repo)
|
|
178
|
-
post = get_record(uri)
|
|
179
|
-
|
|
180
|
-
send_push("@#{handle} liked your post", post)
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def send_feed_like_notification(msg, uri)
|
|
184
|
-
handle = get_user_handle(msg.repo)
|
|
185
|
-
feed = get_record(uri)
|
|
186
|
-
|
|
187
|
-
send_push("@#{handle} liked your feed", feed)
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
# reposts
|
|
192
|
-
|
|
193
|
-
def process_repost(msg, op)
|
|
194
|
-
data = op.raw_record
|
|
195
|
-
|
|
196
|
-
if data['subject'] && data['subject']['uri']
|
|
197
|
-
reposted_uri = AtURI.new(data['subject']['uri'])
|
|
198
|
-
|
|
199
|
-
if reposted_uri.did == @user_did && reposted_uri.collection == 'app.bsky.feed.post'
|
|
200
|
-
send_repost_notification(msg, reposted_uri)
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
def send_repost_notification(msg, uri)
|
|
206
|
-
handle = get_user_handle(msg.repo)
|
|
207
|
-
post = get_record(uri)
|
|
208
|
-
|
|
209
|
-
send_push("@#{handle} reposted your post", post)
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
# follows
|
|
214
|
-
|
|
215
|
-
def process_follow(msg, op)
|
|
216
|
-
if op.raw_record['subject'] == @user_did
|
|
217
|
-
send_follow_notification(msg)
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
def send_follow_notification(msg)
|
|
222
|
-
handle = get_user_handle(msg.repo)
|
|
223
|
-
|
|
224
|
-
send_push("@#{handle} followed you", msg.repo)
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
#
|
|
229
|
-
# Note: in this example, we're calling the Bluesky AppView to get details about the person interacting with the user
|
|
230
|
-
# and the post/feed that was liked/reposted etc. In a real app, you might run into rate limits if you do that,
|
|
231
|
-
# because these requests will all be sent from the server's IP.
|
|
232
|
-
#
|
|
233
|
-
# So you might need to take a different route and send just the info that you have here in the push notification data
|
|
234
|
-
# (the AT URI / DID) and fetch the details on the client side, e.g. in a Notification Service Extension on iOS.
|
|
235
|
-
#
|
|
236
|
-
|
|
237
|
-
def get_user_handle(did)
|
|
238
|
-
url = "https://api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=#{did}"
|
|
239
|
-
json = JSON.parse(URI.open(url).read)
|
|
240
|
-
json['handle']
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
def get_record(uri)
|
|
244
|
-
url = "https://api.bsky.app/xrpc/com.atproto.repo.getRecord?" +
|
|
245
|
-
"repo=#{uri.did}&collection=#{uri.collection}&rkey=#{uri.rkey}"
|
|
246
|
-
json = JSON.parse(URI.open(url).read)
|
|
247
|
-
json['value']
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
def send_push(message, data = nil)
|
|
251
|
-
# send the message to APNS/FCM here
|
|
252
|
-
puts
|
|
253
|
-
puts "[#{Time.now}] #{message} #{data&.inspect}"
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
engine = NotificationEngine.new(monitored_did)
|
|
258
|
-
|
|
259
|
-
# close the connection cleanly on Ctrl+C
|
|
260
|
-
trap("SIGINT") { engine.disconnect }
|
|
261
|
-
|
|
262
|
-
engine.connect
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
require_relative '../firehose'
|
|
2
|
-
|
|
3
|
-
module Skyfall
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
# Note: this event type is deprecated and will stop being emitted at some point.
|
|
7
|
-
# You should instead listen for 'identity' events (Skyfall::Firehose::IdentityMessage).
|
|
8
|
-
#
|
|
9
|
-
class Firehose::HandleMessage < Firehose::Message
|
|
10
|
-
def handle
|
|
11
|
-
@data_object['handle']
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
require_relative '../firehose'
|
|
2
|
-
|
|
3
|
-
module Skyfall
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
# Note: this event type is deprecated and will stop being emitted at some point.
|
|
7
|
-
# You should instead listen for 'account' events (Skyfall::Firehose::AccountMessage).
|
|
8
|
-
#
|
|
9
|
-
class Firehose::TombstoneMessage < Firehose::Message
|
|
10
|
-
end
|
|
11
|
-
end
|