skyfall 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -2
- data/LICENSE.txt +1 -1
- data/README.md +28 -1
- data/example/follower_tracker.rb +84 -0
- 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: db537537fcd4e38f184c6cdea3f51ba1b8554848d4c3cd1de20f63ca2d956fc2
|
4
|
+
data.tar.gz: 34c51f6c8c0152589562bf0a3b3c5246cd8a1e95664f1128c86bd17c6a313d59
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11477293a1bc0377ef5d9eaa68cc2d374f76ac4d942006572e8b9e1668cb8f54a582abdeda81b65c1d72f6452ba2cad1153c403027ba5d7efbfa093624713105
|
7
|
+
data.tar.gz: 8d86b1f71a4fd5fa7f01077fefe8d8e292dc6b5007b79ee4d1ed1343f887c4104c0480b297e7ce4be61db0cce65211a568b642411fa5c3e7f62c4c91a4fe0f7b
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
## [
|
1
|
+
## [0.4.0] - 2024-09-23
|
2
2
|
|
3
|
-
- added
|
3
|
+
- (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)
|
4
|
+
- added a way to set the user agent sent when connecting using the `user_agent` field (default is `"Skyfall/#{version}"`)
|
5
|
+
- added `app.bsky.feed.postgate` record type
|
4
6
|
|
5
7
|
## [0.3.1] - 2024-06-28
|
6
8
|
|
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/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.0
|
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-09-23 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
|