skyfall 0.3.1 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -2
- data/LICENSE.txt +1 -1
- data/README.md +28 -1
- data/example/follower_tracker.rb +84 -0
- data/lib/skyfall/car_archive.rb +13 -12
- data/lib/skyfall/collection.rb +1 -0
- data/lib/skyfall/operation.rb +1 -0
- data/lib/skyfall/stream.rb +47 -3
- data/lib/skyfall/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1fa2648926fdc913472ec8fca4d6bdf3bbac188dbb0d9c82816538ca0c446d23
|
4
|
+
data.tar.gz: f35348c4834fb9dc26544105074bdc4f9ba07c36d595e5c248651d47e5dc188e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df775bdf85f5fe73f5fc5f27868b4ad8755219e98dfae72a32e7a9bb66295b463f65a2ebd9a8c956d74f200f667a9f02c00310e665a506f69a950db03eec4ad6
|
7
|
+
data.tar.gz: 6dfe5acf800a9765d0ad6f3ce78e7a6aae445a2dde4028ef3af39ff81a5fc3947080ecfb0ceb0dcf439a2db3e6d65e17376b187455fa1eaf56889c87d0137be1
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,12 @@
|
|
1
|
-
## [
|
1
|
+
## [0.4.1] - 2024-10-04
|
2
2
|
|
3
|
-
-
|
3
|
+
- performance fix - don't decode CAR sections which aren't needed, which is most of them; this cuts the amount of memory that GC has to free up by about one third, and should speed up processing by around ~10%
|
4
|
+
|
5
|
+
## [0.4.0] - 2024-09-23
|
6
|
+
|
7
|
+
- (re)added a "hearbeat" feature (removed earlier in 0.2.0) to fix the occasional issue when the websocket stops receiving data, but doesn't disconnect (not enabled by default, turn it on by setting `check_heartbeat` to true)
|
8
|
+
- added a way to set the user agent sent when connecting using the `user_agent` field (default is `"Skyfall/#{version}"`)
|
9
|
+
- added `app.bsky.feed.postgate` record type
|
4
10
|
|
5
11
|
## [0.3.1] - 2024-06-28
|
6
12
|
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -123,9 +123,36 @@ When Skyfall receives a message about a record type that's not on the list, whet
|
|
123
123
|
Do not however check if such operations have a `type` equal to `:unknown` first - just ignore the type and only check the `collection` string. The reason is that some next version of Skyfall might start recognizing those records and add a new `type` value for them like e.g. `:skygram_photo`, and then they won't match your condition anymore.
|
124
124
|
|
125
125
|
|
126
|
+
## Configuration
|
127
|
+
|
128
|
+
### User agent
|
129
|
+
|
130
|
+
`Skyfall::Stream` sends a user agent header when making a connection. This is set by default to `"Skyfall/0.x.y"`, but it's recommended that you override it using the `user_agent` field to something that identifies your app and its author – this will let the owner of the server you're connecting to know who to contact in case the client is causing some problems.
|
131
|
+
|
132
|
+
You can also append your user agent info to the default value like this:
|
133
|
+
|
134
|
+
```rb
|
135
|
+
sky.user_agent = "NewsBot (@news.bot) #{sky.default_user_agent}"
|
136
|
+
```
|
137
|
+
|
138
|
+
### Heartbeat and reconnecting
|
139
|
+
|
140
|
+
Occasionally, especially during times of very heavy traffic, the websocket can get into a stuck state where it stops receiving any data, but doesn't disconnect and just hangs like this forever. To work around this, there is a "heartbeat" feature which starts a background timer, which periodically checks how much time has passed since the last received event, and if the time exceeds a set limit, it manually disconnects and reconnects the stream.
|
141
|
+
|
142
|
+
The option is not enabled by default, because there are some firehoses which will not be sending events often, possibly only once in a while – e.g. labellers and independent PDS firehoses – and in this case we don't want any heartbeat since it will be completely normal not to have any events for a long time. It's not really possible to detect easily if we're connecting to a full network relay or one of those, so in order to avoid false alarms, you need to enable this manually using the `check_heartbeat` property.
|
143
|
+
|
144
|
+
You can also change the `heartbeat_interval`, i.e. how often the timer is triggered (default: 10s), and the `heartbeat_timeout`, i.e. the amount of time passed without events when it reconnects (default: 5 min):
|
145
|
+
|
146
|
+
```rb
|
147
|
+
sky.check_heartbeat = true
|
148
|
+
sky.heartbeat_interval = 5
|
149
|
+
sky.heartbeat_timeout = 120
|
150
|
+
```
|
151
|
+
|
152
|
+
|
126
153
|
## Credits
|
127
154
|
|
128
|
-
Copyright ©
|
155
|
+
Copyright © 2024 Kuba Suder ([@mackuba.eu](https://bsky.app/profile/mackuba.eu)).
|
129
156
|
|
130
157
|
The code is available under the terms of the [zlib license](https://choosealicense.com/licenses/zlib/) (permissive, similar to MIT).
|
131
158
|
|
@@ -0,0 +1,84 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Example: track when people follow and unfollow your 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 ARGV[0] !~ /^did:plc:[a-z0-9]{24}$/
|
18
|
+
puts "Not a valid DID: #{$monitored_did}"
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
|
22
|
+
sky = Skyfall::Stream.new('bsky.network', :subscribe_repos)
|
23
|
+
|
24
|
+
sky.on_connect { puts "Connected, monitoring #{$monitored_did}" }
|
25
|
+
sky.on_disconnect { puts "Disconnected" }
|
26
|
+
sky.on_reconnect { puts "Reconnecting..." }
|
27
|
+
sky.on_error { |e| puts "ERROR: #{e}" }
|
28
|
+
|
29
|
+
sky.on_message do |msg|
|
30
|
+
# we're only interested in repo commit messages
|
31
|
+
next if msg.type != :commit
|
32
|
+
|
33
|
+
msg.operations.each do |op|
|
34
|
+
next if op.action != :create
|
35
|
+
|
36
|
+
begin
|
37
|
+
case op.type
|
38
|
+
when :bsky_block
|
39
|
+
process_block(msg, op)
|
40
|
+
when :bsky_listitem
|
41
|
+
process_list_item(msg, op)
|
42
|
+
end
|
43
|
+
rescue StandardError => e
|
44
|
+
puts "Error: #{e}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_block(msg, op)
|
50
|
+
if op.raw_record['subject'] == $monitored_did
|
51
|
+
owner_handle = get_user_handle(op.repo)
|
52
|
+
puts "@#{owner_handle} has blocked you! (#{msg.time.getlocal})"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def process_list_item(msg, op)
|
57
|
+
if op.raw_record['subject'] == $monitored_did
|
58
|
+
owner_handle = get_user_handle(op.repo)
|
59
|
+
|
60
|
+
list_uri = op.raw_record['list']
|
61
|
+
list_name = get_list_name(list_uri)
|
62
|
+
|
63
|
+
puts "@#{owner_handle} has added you to list \"#{list_name}\" (#{msg.time.getlocal})"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_user_handle(did)
|
68
|
+
url = "https://plc.directory/#{did}"
|
69
|
+
json = JSON.parse(URI.open(url).read)
|
70
|
+
json['alsoKnownAs'][0].gsub('at://', '')
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_list_name(list_uri)
|
74
|
+
repo, type, rkey = list_uri.gsub('at://', '').split('/')
|
75
|
+
url = "https://bsky.social/xrpc/com.atproto.repo.getRecord?repo=#{repo}&collection=#{type}&rkey=#{rkey}"
|
76
|
+
|
77
|
+
json = JSON.parse(URI.open(url).read)
|
78
|
+
json['value']['name']
|
79
|
+
end
|
80
|
+
|
81
|
+
# close the connection cleanly on Ctrl+C
|
82
|
+
trap("SIGINT") { sky.disconnect }
|
83
|
+
|
84
|
+
sky.connect
|
data/lib/skyfall/car_archive.rb
CHANGED
@@ -11,11 +11,15 @@ require 'stringio'
|
|
11
11
|
|
12
12
|
module Skyfall
|
13
13
|
class CarSection
|
14
|
-
attr_reader :cid
|
14
|
+
attr_reader :cid
|
15
15
|
|
16
|
-
def initialize(cid,
|
16
|
+
def initialize(cid, body_data)
|
17
17
|
@cid = cid
|
18
|
-
@
|
18
|
+
@body_data = body_data
|
19
|
+
end
|
20
|
+
|
21
|
+
def body
|
22
|
+
@body ||= CarArchive.convert_data(CBOR.decode(@body_data))
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
@@ -37,9 +41,7 @@ module Skyfall
|
|
37
41
|
section && section.body
|
38
42
|
end
|
39
43
|
|
40
|
-
|
41
|
-
|
42
|
-
def convert_data(object)
|
44
|
+
def self.convert_data(object)
|
43
45
|
if object.is_a?(Hash)
|
44
46
|
object.each do |k, v|
|
45
47
|
if v.is_a?(Hash) || v.is_a?(Array)
|
@@ -65,14 +67,16 @@ module Skyfall
|
|
65
67
|
end
|
66
68
|
end
|
67
69
|
|
68
|
-
def make_cid_link(cid)
|
70
|
+
def self.make_cid_link(cid)
|
69
71
|
{ '$link' => CID.from_cbor_tag(cid) }
|
70
72
|
end
|
71
73
|
|
72
|
-
def make_bytes(data)
|
74
|
+
def self.make_bytes(data)
|
73
75
|
{ '$bytes' => Base64.encode64(data).chomp.gsub(/=+$/, '') }
|
74
76
|
end
|
75
77
|
|
78
|
+
private
|
79
|
+
|
76
80
|
def read_header(buffer)
|
77
81
|
len = buffer.read_varint
|
78
82
|
|
@@ -112,10 +116,7 @@ module Skyfall
|
|
112
116
|
cid = CID.new(prefix + cid_data)
|
113
117
|
|
114
118
|
body_data = sbuffer.read
|
115
|
-
|
116
|
-
convert_data(body)
|
117
|
-
|
118
|
-
@sections << CarSection.new(cid, body)
|
119
|
+
@sections << CarSection.new(cid, body_data)
|
119
120
|
end
|
120
121
|
end
|
121
122
|
end
|
data/lib/skyfall/collection.rb
CHANGED
@@ -4,6 +4,7 @@ module Skyfall
|
|
4
4
|
BSKY_FEED = "app.bsky.feed.generator"
|
5
5
|
BSKY_LIKE = "app.bsky.feed.like"
|
6
6
|
BSKY_POST = "app.bsky.feed.post"
|
7
|
+
BSKY_POSTGATE = "app.bsky.feed.postgate"
|
7
8
|
BSKY_REPOST = "app.bsky.feed.repost"
|
8
9
|
BSKY_THREADGATE = "app.bsky.feed.threadgate"
|
9
10
|
BSKY_BLOCK = "app.bsky.graph.block"
|
data/lib/skyfall/operation.rb
CHANGED
@@ -52,6 +52,7 @@ module Skyfall
|
|
52
52
|
when Collection::BSKY_LISTBLOCK then :bsky_listblock
|
53
53
|
when Collection::BSKY_LISTITEM then :bsky_listitem
|
54
54
|
when Collection::BSKY_POST then :bsky_post
|
55
|
+
when Collection::BSKY_POSTGATE then :bsky_postgate
|
55
56
|
when Collection::BSKY_PROFILE then :bsky_profile
|
56
57
|
when Collection::BSKY_REPOST then :bsky_repost
|
57
58
|
when Collection::BSKY_STARTERPACK then :bsky_starterpack
|
data/lib/skyfall/stream.rb
CHANGED
@@ -14,11 +14,12 @@ module Skyfall
|
|
14
14
|
:subscribe_labels => SUBSCRIBE_LABELS
|
15
15
|
}
|
16
16
|
|
17
|
-
EVENTS = %w(message raw_message connecting connect disconnect reconnect error)
|
17
|
+
EVENTS = %w(message raw_message connecting connect disconnect reconnect error timeout)
|
18
18
|
|
19
19
|
MAX_RECONNECT_INTERVAL = 300
|
20
20
|
|
21
|
-
attr_accessor :
|
21
|
+
attr_accessor :cursor, :auto_reconnect, :last_update, :user_agent
|
22
|
+
attr_accessor :heartbeat_timeout, :heartbeat_interval, :check_heartbeat
|
22
23
|
|
23
24
|
def initialize(server, endpoint, cursor = nil)
|
24
25
|
@endpoint = check_endpoint(endpoint)
|
@@ -26,7 +27,12 @@ module Skyfall
|
|
26
27
|
@cursor = check_cursor(cursor)
|
27
28
|
@handlers = {}
|
28
29
|
@auto_reconnect = true
|
30
|
+
@check_heartbeat = false
|
29
31
|
@connection_attempts = 0
|
32
|
+
@heartbeat_interval = 10
|
33
|
+
@heartbeat_timeout = 300
|
34
|
+
@last_update = nil
|
35
|
+
@user_agent = default_user_agent
|
30
36
|
|
31
37
|
@handlers[:error] = proc { |e| puts "ERROR: #{e}" }
|
32
38
|
end
|
@@ -47,15 +53,18 @@ module Skyfall
|
|
47
53
|
@handlers[:error]&.call(e)
|
48
54
|
end
|
49
55
|
|
50
|
-
@ws = Faye::WebSocket::Client.new(url)
|
56
|
+
@ws = Faye::WebSocket::Client.new(url, nil, { headers: { 'User-Agent' => user_agent }})
|
51
57
|
|
52
58
|
@ws.on(:open) do |e|
|
53
59
|
@handlers[:connect]&.call
|
60
|
+
@last_update = Time.now
|
61
|
+
start_heartbeat_timer
|
54
62
|
end
|
55
63
|
|
56
64
|
@ws.on(:message) do |msg|
|
57
65
|
@reconnecting = false
|
58
66
|
@connection_attempts = 0
|
67
|
+
@last_update = Time.now
|
59
68
|
|
60
69
|
data = msg.data.pack('C*')
|
61
70
|
@handlers[:raw_message]&.call(data)
|
@@ -85,6 +94,7 @@ module Skyfall
|
|
85
94
|
connect
|
86
95
|
end
|
87
96
|
else
|
97
|
+
stop_heartbeat_timer
|
88
98
|
@engines_on = false
|
89
99
|
@handlers[:disconnect]&.call
|
90
100
|
EM.stop_event_loop unless @ws
|
@@ -110,6 +120,40 @@ module Skyfall
|
|
110
120
|
|
111
121
|
alias close disconnect
|
112
122
|
|
123
|
+
def default_user_agent
|
124
|
+
"Skyfall/#{Skyfall::VERSION}"
|
125
|
+
end
|
126
|
+
|
127
|
+
def check_heartbeat=(value)
|
128
|
+
@check_heartbeat = value
|
129
|
+
|
130
|
+
if @check_heartbeat && @engines_on && @ws && !@heartbeat_timer
|
131
|
+
start_heartbeat_timer
|
132
|
+
elsif !@check_heartbeat && @heartbeat_timer
|
133
|
+
stop_heartbeat_timer
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def start_heartbeat_timer
|
138
|
+
return if !@check_heartbeat || @heartbeat_interval.to_f <= 0 || @heartbeat_timeout.to_f <= 0
|
139
|
+
return if @heartbeat_timer
|
140
|
+
|
141
|
+
@heartbeat_timer = EM::PeriodicTimer.new(@heartbeat_interval) do
|
142
|
+
next if @ws.nil? || @heartbeat_timeout.to_f <= 0
|
143
|
+
time_passed = Time.now - @last_update
|
144
|
+
|
145
|
+
if time_passed > @heartbeat_timeout
|
146
|
+
@handlers[:timeout]&.call
|
147
|
+
reconnect
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def stop_heartbeat_timer
|
153
|
+
@heartbeat_timer&.cancel
|
154
|
+
@heartbeat_timer = nil
|
155
|
+
end
|
156
|
+
|
113
157
|
EVENTS.each do |event|
|
114
158
|
define_method "on_#{event}" do |&block|
|
115
159
|
@handlers[event.to_sym] = block
|
data/lib/skyfall/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: skyfall
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kuba Suder
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-10-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: base32
|
@@ -114,6 +114,7 @@ files:
|
|
114
114
|
- LICENSE.txt
|
115
115
|
- README.md
|
116
116
|
- example/block_tracker.rb
|
117
|
+
- example/follower_tracker.rb
|
117
118
|
- example/monitor_phrases.rb
|
118
119
|
- example/print_all_posts.rb
|
119
120
|
- example/push_notifications.rb
|