ratproto 0.2.0 → 0.3.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 +14 -0
- data/lib/ratproto/cli.rb +2 -0
- data/lib/ratproto/commands/base.rb +5 -1
- data/lib/ratproto/commands/stream.rb +97 -34
- data/lib/ratproto/commands/stream_labels.rb +127 -0
- data/lib/ratproto/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dd51fed594f086af2d560047baf82a7adfe1f29483421d8dd1c4e0ffef8e00f4
|
|
4
|
+
data.tar.gz: b91f3eaac638d75e04d3030fb9cf59f70e6d8589f223fe0c1a846837ffa1336e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5cb31e38a0d44e863da64ccbd826258d48c8ae6451e63279e577213fe482ed4122c084a338264c8dcb118369a3fd8e571678a02bf070537368b89b5d4011b91e
|
|
7
|
+
data.tar.gz: 85cd4e59791b9f09a987dbe1ac2e8ebfe2d4d991b7033d851d7d26e8b56f21b19816fb6fb763c9272491378d98c2e3fe7e4acdb0a7925e17a15ac31eba8efa59
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [0.3.0] - 2026-07-30
|
|
2
|
+
|
|
3
|
+
* `stream`:
|
|
4
|
+
- added option `--print-cursor` (`-s`)
|
|
5
|
+
- print account status change messages
|
|
6
|
+
- added option `--[no-]account` (`-a`) to enable/disable account messages
|
|
7
|
+
- print the timestamp of the last message on stop
|
|
8
|
+
- allow passing handles with `@` to `-d` option
|
|
9
|
+
- status messages are printed to stderr
|
|
10
|
+
- suggest that `-j` might be missing on connection error
|
|
11
|
+
* added `stream-labels` command which works similarly to `stream`, but streams labels from a labeller
|
|
12
|
+
- pass labeller service as a hostname or a DID/handle of an account
|
|
13
|
+
- filter labels by type (value) or by pattern in the labelled URI
|
|
14
|
+
|
|
1
15
|
## [0.2.0] - 2026-07-28
|
|
2
16
|
|
|
3
17
|
- converted the codebase from a single script in `exe/rat` to per-command classes using Clamp framework
|
data/lib/ratproto/cli.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative 'commands/base'
|
|
|
7
7
|
require_relative 'commands/fetch'
|
|
8
8
|
require_relative 'commands/resolve'
|
|
9
9
|
require_relative 'commands/stream'
|
|
10
|
+
require_relative 'commands/stream_labels'
|
|
10
11
|
|
|
11
12
|
Clamp.allow_options_after_parameters = true
|
|
12
13
|
|
|
@@ -50,6 +51,7 @@ module RatProto
|
|
|
50
51
|
|
|
51
52
|
subcommand 'fetch', 'fetch a single record given its at:// URI', Commands::Fetch
|
|
52
53
|
subcommand 'stream', 'stream events from a relay or PDS firehose', Commands::Stream
|
|
54
|
+
subcommand 'stream-labels', 'stream labels a labeller firehose', Commands::StreamLabels
|
|
53
55
|
subcommand 'resolve', 'resolve a DID or @handle', Commands::Resolve
|
|
54
56
|
subcommand 'help', 'show help', Commands::Help
|
|
55
57
|
subcommand 'version', 'show version', Commands::Version
|
|
@@ -11,6 +11,10 @@ module RatProto
|
|
|
11
11
|
|
|
12
12
|
private
|
|
13
13
|
|
|
14
|
+
def log(text = nil)
|
|
15
|
+
$stderr.puts text
|
|
16
|
+
end
|
|
17
|
+
|
|
14
18
|
def parse_at_uri(value)
|
|
15
19
|
signal_usage_error "not an at:// URI: #{value.inspect}" unless value.start_with?('at://')
|
|
16
20
|
|
|
@@ -33,7 +37,7 @@ module RatProto
|
|
|
33
37
|
def map_argument_to_did(did)
|
|
34
38
|
if did =~ DID_REGEXP
|
|
35
39
|
did
|
|
36
|
-
elsif did =~ HANDLE_REGEXP
|
|
40
|
+
elsif did.gsub(/\A@/, '') =~ HANDLE_REGEXP
|
|
37
41
|
if resolved_did = DID.resolve_handle(did)
|
|
38
42
|
resolved_did.to_s
|
|
39
43
|
else
|
|
@@ -15,9 +15,11 @@ module RatProto
|
|
|
15
15
|
|
|
16
16
|
option ['-j', '--jetstream'], :flag, 'use a Jetstream source'
|
|
17
17
|
|
|
18
|
-
option
|
|
18
|
+
option(
|
|
19
|
+
['-r', '--cursor'], 'CURSOR',
|
|
20
|
+
'start from a given cursor (pass 0 to read from the beginning of the playback window)'
|
|
21
|
+
) do |value|
|
|
19
22
|
raise ArgumentError, 'must be a decimal integer' unless value =~ /\A\d+\z/
|
|
20
|
-
|
|
21
23
|
value.to_i
|
|
22
24
|
end
|
|
23
25
|
|
|
@@ -27,35 +29,48 @@ module RatProto
|
|
|
27
29
|
option ['-c', '--collection'], 'NSID[,NSID...]', 'filter events by collection (comma-separated or repeated)',
|
|
28
30
|
multivalued: true, attribute_name: :collection_values
|
|
29
31
|
|
|
32
|
+
option ['-a', '--[no-]account'], :flag, 'print account status events (default: enabled unless filtering by collection)'
|
|
33
|
+
option ['-s', '--print-cursor'], :flag, 'only print current cursor (seq)'
|
|
34
|
+
|
|
35
|
+
|
|
30
36
|
def execute
|
|
31
37
|
require 'skyfall'
|
|
32
38
|
|
|
33
|
-
dids = comma_separated(did_values, '-d/--did')
|
|
34
|
-
collections = comma_separated(collection_values, '-c/--collection')
|
|
39
|
+
@dids = comma_separated(did_values, '-d/--did')
|
|
40
|
+
@collections = comma_separated(collection_values, '-c/--collection')
|
|
41
|
+
|
|
42
|
+
@dids = (@dids.length > 0) ? @dids.map { |did| map_argument_to_did(did) } : nil
|
|
43
|
+
@collections = (@collections.length > 0) ? @collections.map { |collection| validate_nsid(collection) } : nil
|
|
35
44
|
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
@sky = build_stream
|
|
46
|
+
configure_interrupt_handler
|
|
38
47
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
48
|
+
if print_cursor?
|
|
49
|
+
@sky.on_message do |msg|
|
|
50
|
+
print_cursor_and_stop(msg)
|
|
51
|
+
next
|
|
52
|
+
end
|
|
42
53
|
|
|
43
|
-
|
|
54
|
+
@sky.connect
|
|
55
|
+
else
|
|
56
|
+
configure_callbacks
|
|
57
|
+
@sky.connect
|
|
44
58
|
|
|
45
|
-
|
|
59
|
+
log "Stopped stream at cursor #{@sky.cursor}" + (@last_timestamp ? " (#{@last_timestamp})" : "")
|
|
60
|
+
end
|
|
46
61
|
end
|
|
47
62
|
|
|
48
63
|
|
|
49
64
|
private
|
|
50
65
|
|
|
51
|
-
def build_stream
|
|
66
|
+
def build_stream
|
|
52
67
|
if jetstream?
|
|
53
68
|
options = {}
|
|
54
69
|
options[:cursor] = cursor if cursor
|
|
55
70
|
|
|
56
71
|
# pass DID/collection filters to Jetstream to filter events server-side
|
|
57
|
-
options[:wanted_dids] = dids if dids
|
|
58
|
-
options[:wanted_collections] = collections if collections
|
|
72
|
+
options[:wanted_dids] = @dids if @dids
|
|
73
|
+
options[:wanted_collections] = @collections if @collections
|
|
59
74
|
|
|
60
75
|
Skyfall::Jetstream.new(service, options)
|
|
61
76
|
else
|
|
@@ -63,36 +78,84 @@ module RatProto
|
|
|
63
78
|
end
|
|
64
79
|
end
|
|
65
80
|
|
|
66
|
-
def configure_callbacks
|
|
67
|
-
sky.on_connecting { |url|
|
|
68
|
-
sky.on_connect {
|
|
69
|
-
sky.on_disconnect {
|
|
70
|
-
sky.on_reconnect {
|
|
71
|
-
sky.on_timeout {
|
|
72
|
-
sky.on_error { |e|
|
|
81
|
+
def configure_callbacks
|
|
82
|
+
@sky.on_connecting { |url| log "Connecting to #{url}..." }
|
|
83
|
+
@sky.on_connect { log "Connected" }
|
|
84
|
+
@sky.on_disconnect { log "Disconnected" }
|
|
85
|
+
@sky.on_reconnect { log "Connection lost, trying to reconnect..." }
|
|
86
|
+
@sky.on_timeout { log "Connection stalled, triggering a reconnect..." }
|
|
87
|
+
@sky.on_error { |e| report_error(e) }
|
|
73
88
|
|
|
74
|
-
sky.on_message
|
|
75
|
-
|
|
76
|
-
next if dids && !dids.include?(msg.repo)
|
|
89
|
+
@sky.on_message { |msg| process_message(msg) }
|
|
90
|
+
end
|
|
77
91
|
|
|
78
|
-
|
|
92
|
+
def report_error(error)
|
|
93
|
+
log "ERROR: #{error.message}"
|
|
79
94
|
|
|
80
|
-
|
|
81
|
-
|
|
95
|
+
if !jetstream? && error.message.include?('Unexpected response code')
|
|
96
|
+
log "If this is a Jetstream server, retry with -j / --jetstream."
|
|
97
|
+
end
|
|
98
|
+
end
|
|
82
99
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
100
|
+
def process_message(msg)
|
|
101
|
+
case msg.type
|
|
102
|
+
when :commit
|
|
103
|
+
print_commit_message(msg)
|
|
104
|
+
when :account
|
|
105
|
+
print_account_message(msg) if show_account_messages?
|
|
86
106
|
end
|
|
87
107
|
end
|
|
88
108
|
|
|
89
|
-
def
|
|
109
|
+
def print_commit_message(msg)
|
|
110
|
+
return if @dids && !@dids.include?(msg.repo)
|
|
111
|
+
|
|
112
|
+
time = msg.time.getlocal.iso8601
|
|
113
|
+
@last_timestamp = time
|
|
114
|
+
|
|
115
|
+
msg.operations.each do |op|
|
|
116
|
+
next if @collections && !@collections.include?(op.collection)
|
|
117
|
+
|
|
118
|
+
json = op.raw_record && JSON.generate(op.raw_record.except('$type'))
|
|
119
|
+
puts "[#{time}] (#{msg.seq}) #{msg.repo} #{op.action.inspect} #{op.collection} #{op.rkey} #{json}"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def print_account_message(msg)
|
|
124
|
+
return if @dids && !@dids.include?(msg.repo)
|
|
125
|
+
|
|
126
|
+
time = msg.time.getlocal.iso8601
|
|
127
|
+
@last_timestamp = time
|
|
128
|
+
|
|
129
|
+
status = (msg.active?) ? :active : msg.status
|
|
130
|
+
|
|
131
|
+
puts "[#{time}] (#{msg.seq}) #{msg.repo} #account (status = #{status.inspect})"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def show_account_messages?
|
|
135
|
+
if account? == nil
|
|
136
|
+
# enabled by default if -c is not used
|
|
137
|
+
@collections.nil?
|
|
138
|
+
else
|
|
139
|
+
# if enabled/disabled explicitly, follow user's choice
|
|
140
|
+
account?
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def configure_interrupt_handler
|
|
90
145
|
trap('SIGINT') do
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
sky.disconnect
|
|
146
|
+
log
|
|
147
|
+
log 'Disconnecting...'
|
|
148
|
+
@sky.disconnect
|
|
94
149
|
end
|
|
95
150
|
end
|
|
151
|
+
|
|
152
|
+
def print_cursor_and_stop(msg)
|
|
153
|
+
return if @disconnecting
|
|
154
|
+
|
|
155
|
+
puts msg.seq
|
|
156
|
+
@sky.disconnect
|
|
157
|
+
@disconnecting = true
|
|
158
|
+
end
|
|
96
159
|
end
|
|
97
160
|
end
|
|
98
161
|
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'didkit'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'time'
|
|
6
|
+
|
|
7
|
+
require_relative 'base'
|
|
8
|
+
|
|
9
|
+
module RatProto
|
|
10
|
+
module Commands
|
|
11
|
+
class StreamLabels < Base
|
|
12
|
+
banner 'Stream labels from a labeller firehose'
|
|
13
|
+
|
|
14
|
+
parameter 'SERVICE', 'labeller hostname, or a @handle/DID of the labeller account (handle must start with @)'
|
|
15
|
+
|
|
16
|
+
option ['-r', '--cursor'], 'CURSOR', 'start from a given cursor' do |value|
|
|
17
|
+
raise ArgumentError, 'must be a decimal integer' unless value =~ /\A\d+\z/
|
|
18
|
+
value.to_i
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
option ['-l', '--labels'], 'LABEL[,LABEL...]', 'filter events by label type (comma-separated or repeated)',
|
|
22
|
+
multivalued: true, attribute_name: :label_types
|
|
23
|
+
|
|
24
|
+
option ['-t', '--target'], 'PATTERN', "filter by labelled DID/URI (can be a pattern with '*' as a wildcard; repeatable)",
|
|
25
|
+
multivalued: true, attribute_name: :target_patterns
|
|
26
|
+
|
|
27
|
+
option ['-s', '--print-cursor'], :flag, 'only print current cursor (seq)'
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def execute
|
|
31
|
+
require 'skyfall'
|
|
32
|
+
|
|
33
|
+
@labels = (label_types.length > 0) ? comma_separated(label_types, '-l/--labels') : nil
|
|
34
|
+
@targets = (target_patterns.length > 0) ? target_patterns.map { |pattern| target_regexp(pattern) } : nil
|
|
35
|
+
@service = determine_hostname(service)
|
|
36
|
+
|
|
37
|
+
@sky = Skyfall::Firehose.new(@service, :subscribe_labels, cursor)
|
|
38
|
+
configure_interrupt_handler
|
|
39
|
+
|
|
40
|
+
if print_cursor?
|
|
41
|
+
@sky.on_message do |msg|
|
|
42
|
+
print_cursor_and_stop(msg)
|
|
43
|
+
next
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@sky.connect
|
|
47
|
+
else
|
|
48
|
+
configure_callbacks
|
|
49
|
+
@sky.connect
|
|
50
|
+
|
|
51
|
+
log "Stopped stream at cursor #{@sky.cursor}" + (@last_timestamp ? " (#{@last_timestamp})" : "")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def target_regexp(pattern)
|
|
59
|
+
source = Regexp.escape(pattern).gsub('\*', '.*')
|
|
60
|
+
Regexp.new("\\A#{source}\\z")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def determine_hostname(service)
|
|
64
|
+
if service =~ /\A(https?:\/\/)?[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)+(:[0-9]+)?\z/
|
|
65
|
+
return service
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
if service =~ DID_REGEXP
|
|
69
|
+
did = DID.new(service)
|
|
70
|
+
else
|
|
71
|
+
handle = service.gsub(/\A@/, '')
|
|
72
|
+
|
|
73
|
+
if handle =~ HANDLE_REGEXP
|
|
74
|
+
if resolved_did = DID.resolve_handle(handle)
|
|
75
|
+
did = resolved_did
|
|
76
|
+
else
|
|
77
|
+
signal_error "couldn't resolve handle @#{handle}"
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
signal_usage_error "#{service.inspect} is not a valid hostname, DID or handle"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
did.document.labeller_host
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def configure_callbacks
|
|
88
|
+
@sky.on_connecting { |url| log "Connecting to #{url}..." }
|
|
89
|
+
@sky.on_connect { log "Connected" }
|
|
90
|
+
@sky.on_disconnect { log "Disconnected" }
|
|
91
|
+
@sky.on_reconnect { log "Connection lost, trying to reconnect..." }
|
|
92
|
+
@sky.on_timeout { log "Connection stalled, triggering a reconnect..." }
|
|
93
|
+
@sky.on_error { |e| log "ERROR: #{e}" }
|
|
94
|
+
|
|
95
|
+
@sky.on_message do |msg|
|
|
96
|
+
next unless msg.type == :labels
|
|
97
|
+
|
|
98
|
+
msg.labels.each do |label|
|
|
99
|
+
next if @labels && !@labels.include?(label.value)
|
|
100
|
+
next if @targets && @targets.none? { |pattern| pattern.match?(label.subject) }
|
|
101
|
+
|
|
102
|
+
@last_timestamp = label.created_at
|
|
103
|
+
|
|
104
|
+
json = JSON.generate(label.data.except('cid', 'cts', 'sig', 'src', 'ver'))
|
|
105
|
+
puts "[#{label.created_at}] (#{msg.seq}) #{json}"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def configure_interrupt_handler
|
|
111
|
+
trap('SIGINT') do
|
|
112
|
+
log
|
|
113
|
+
log 'Disconnecting...'
|
|
114
|
+
@sky.disconnect
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def print_cursor_and_stop(msg)
|
|
119
|
+
return if @disconnecting
|
|
120
|
+
|
|
121
|
+
puts msg.seq
|
|
122
|
+
@sky.disconnect
|
|
123
|
+
@disconnecting = true
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
data/lib/ratproto/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ratproto
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kuba Suder
|
|
@@ -106,6 +106,7 @@ files:
|
|
|
106
106
|
- lib/ratproto/commands/fetch.rb
|
|
107
107
|
- lib/ratproto/commands/resolve.rb
|
|
108
108
|
- lib/ratproto/commands/stream.rb
|
|
109
|
+
- lib/ratproto/commands/stream_labels.rb
|
|
109
110
|
- lib/ratproto/version.rb
|
|
110
111
|
- sig/ratproto.rbs
|
|
111
112
|
homepage: https://ruby.sdk.blue
|