ratproto 0.3.1 → 0.3.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f6ecf6b20c9235a6f792aedbe3d555e8387d788dbe1bb7e8ae65850a8de34b6
4
- data.tar.gz: 9f6b2dd26064ae1bc1ed8e854c2f0fd1c193672556e99c3ec78df5eab9ba50e9
3
+ metadata.gz: a098c33a93bc8b33f32631f733fb3b5d3d1f3b233035d1cdbf8d397071a8d566
4
+ data.tar.gz: ae846bf49bca35fa4ee7fe748e8d0a05eeaf60a44f6cd687d7a0d66f61c4254f
5
5
  SHA512:
6
- metadata.gz: 0e0326c6bba6787261b88668a0a5877f0a68eaff5146bdfd769a404271f050f200decf652546855cadd2f9f8040190f4d26ff65e867fcb1e1bacbd3148237db8
7
- data.tar.gz: fee5cf28d9a8cf5a64dcf06fae08db2f467832c0ed56327010c31a3bb71c2e0159e2ec8cda2f14aa6df4848eba76751d67563d3f3a83e71f38a36cdac9c92c87
6
+ metadata.gz: 9abf0ae89949e3e20b9f89c6b0ed9d09275a4fe55b68104c0579f6c85b6f10b7901c0dc9c884c022b8f269cf28bcfe6e07e00db9519266ab4e00bc5fab5e8b35
7
+ data.tar.gz: 0dfd41610d6faf493475041b065b8e688bae795bdb14c85238945ec0498a80d9ea530e7ce9e1e8fe81d7938f106c9467d100859404925000a4e58927c60a480a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [0.3.3] - 2026-08-17
2
+
3
+ - `stream`: added `--print-time` mode, bug fixes, unified timestamp formats
4
+ - added `status` command that prints account status
5
+
6
+ ## [0.3.2] - 2026-07-30
7
+
8
+ - `stream`: filter by record operation type using `-o`
9
+ - `stream`: disable all commit messages and leave account messages by passing `-c -`
10
+ - `stream` & `stream-labels`: disable status messages using `-q`
11
+
1
12
  ## [0.3.1] - 2026-07-30
2
13
 
3
14
  - added polyfill of `Hash#except` for Ruby 2.6
data/lib/ratproto/cli.rb CHANGED
@@ -5,6 +5,7 @@ require 'clamp'
5
5
  require_relative 'version'
6
6
  require_relative 'ext'
7
7
  require_relative 'commands/base'
8
+ require_relative 'commands/account_status'
8
9
  require_relative 'commands/fetch'
9
10
  require_relative 'commands/resolve'
10
11
  require_relative 'commands/stream'
@@ -51,6 +52,7 @@ module RatProto
51
52
  end
52
53
 
53
54
  subcommand 'fetch', 'fetch a single record given its at:// URI', Commands::Fetch
55
+ subcommand 'status', 'print account status', Commands::AccountStatus
54
56
  subcommand 'stream', 'stream events from a relay or PDS firehose', Commands::Stream
55
57
  subcommand 'stream-labels', 'stream labels a labeller firehose', Commands::StreamLabels
56
58
  subcommand 'resolve', 'resolve a DID or @handle', Commands::Resolve
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'didkit'
4
+
5
+ require_relative 'base'
6
+
7
+ module RatProto
8
+ module Commands
9
+ class AccountStatus < Base
10
+ banner 'Print status of an account on its PDS'
11
+
12
+ parameter 'TARGET', 'DID or handle'
13
+
14
+ option ['-s', '--status'], :flag, 'print only the status without a full message'
15
+
16
+ def execute
17
+ begin
18
+ resolved_did = DID.resolve_handle(target)
19
+ rescue StandardError => e
20
+ signal_error "resolving #{target.inspect}: #{e.class}: #{e.message}"
21
+ end
22
+
23
+ if resolved_did.nil?
24
+ signal_error "couldn't resolve #{target}"
25
+ end
26
+
27
+ result = resolved_did.account_status
28
+
29
+ if status?
30
+ puts (result&.to_s || 'deleted')
31
+ return
32
+ end
33
+
34
+ case result
35
+ when :active
36
+ puts "#{resolved_did} at #{resolved_did.document.pds_host}: account active"
37
+ when nil
38
+ puts "#{resolved_did} at #{resolved_did.document.pds_host}: account not found"
39
+ else
40
+ puts "#{resolved_did} at #{resolved_did.document.pds_host}: account inactive (status = #{result})"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -9,6 +9,8 @@ require_relative 'base'
9
9
  module RatProto
10
10
  module Commands
11
11
  class Stream < Base
12
+ OPERATION_TYPES = %w[create update delete].freeze
13
+
12
14
  banner 'Stream events from a relay or PDS firehose'
13
15
 
14
16
  parameter 'SERVICE', 'relay, PDS or Jetstream hostname'
@@ -26,31 +28,37 @@ module RatProto
26
28
  option ['-d', '--did'], 'DID[,DID...]', 'filter events by DID (comma-separated or repeated)',
27
29
  multivalued: true, attribute_name: :did_values
28
30
 
29
- option ['-c', '--collection'], 'NSID[,NSID...]', 'filter events by collection (comma-separated or repeated)',
31
+ option ['-c', '--collection'], 'NSID[,NSID...]',
32
+ "filter events by collection (comma-separated or repeated; pass '-' to skip all and only print account events)",
30
33
  multivalued: true, attribute_name: :collection_values
31
34
 
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)'
35
+ option ['-o', '--operation'], 'ACTION[,ACTION...]',
36
+ 'filter events by operation (create, update or delete; comma-separated or repeated)',
37
+ multivalued: true, attribute_name: :operation_values
38
+
39
+ option ['-a', '--[no-]account'], :flag,
40
+ 'print account status events (default: enabled unless filtering by collection or operation)'
41
+
42
+ option ['-q', '--quiet'], :flag, 'disable status messages'
43
+ option ['-s', '--print-cursor'], :flag, 'only print cursor (seq) of first event and disconnect'
44
+ option ['--print-time'], :flag, 'only print timestamp of first event and disconnect'
34
45
 
35
46
 
36
47
  def execute
37
48
  require 'skyfall'
38
49
 
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
50
+ prepare_filters
44
51
 
45
52
  @sky = build_stream
46
53
  configure_interrupt_handler
47
54
 
48
- if print_cursor?
55
+ if print_cursor? || print_time?
49
56
  @sky.on_message do |msg|
50
57
  print_cursor_and_stop(msg)
51
58
  next
52
59
  end
53
60
 
61
+ @sky.on_error { |e| report_error(e) }
54
62
  @sky.connect
55
63
  else
56
64
  configure_callbacks
@@ -63,6 +71,40 @@ module RatProto
63
71
 
64
72
  private
65
73
 
74
+ def prepare_filters
75
+ @dids = comma_separated(did_values, '-d/--did')
76
+ @collections = comma_separated(collection_values, '-c/--collection')
77
+ @operations = comma_separated(operation_values, '-o/--operation')
78
+
79
+ if @collections.include?('-')
80
+ if @collections.any? { |collection| collection != '-' }
81
+ signal_usage_error("'-' cannot be combined with collection names")
82
+ end
83
+
84
+ if @operations.length > 0
85
+ signal_usage_error("'-c -' cannot be combined with -o/--operation")
86
+ end
87
+
88
+ @no_collections = true
89
+ @collections = nil
90
+ elsif @collections.length > 0
91
+ @collections = @collections.map { |collection| validate_nsid(collection) }
92
+ else
93
+ @collections = nil
94
+ end
95
+
96
+ @dids = (@dids.length > 0) ? @dids.map { |did| map_argument_to_did(did) } : nil
97
+ @operations = (@operations.length > 0) ? @operations.map { |operation| validate_operation(operation) } : nil
98
+ end
99
+
100
+ def validate_operation(operation)
101
+ if OPERATION_TYPES.include?(operation)
102
+ operation.to_sym
103
+ else
104
+ signal_usage_error("#{operation.inspect} is not a valid operation (expected create, update or delete)")
105
+ end
106
+ end
107
+
66
108
  def build_stream
67
109
  if jetstream?
68
110
  options = {}
@@ -90,10 +132,10 @@ module RatProto
90
132
  end
91
133
 
92
134
  def report_error(error)
93
- log "ERROR: #{error.message}"
135
+ log "ERROR: #{error.message}", true
94
136
 
95
137
  if !jetstream? && error.message.include?('Unexpected response code')
96
- log "If this is a Jetstream server, retry with -j / --jetstream."
138
+ log "If this is a Jetstream server, retry with -j / --jetstream.", true
97
139
  end
98
140
  end
99
141
 
@@ -107,13 +149,15 @@ module RatProto
107
149
  end
108
150
 
109
151
  def print_commit_message(msg)
110
- return if @dids && !@dids.include?(msg.repo)
111
-
112
- time = msg.time.getlocal.iso8601
152
+ time = msg.time.getlocal
113
153
  @last_timestamp = time
114
154
 
155
+ return if @no_collections
156
+ return if @dids && !@dids.include?(msg.repo)
157
+
115
158
  msg.operations.each do |op|
116
159
  next if @collections && !@collections.include?(op.collection)
160
+ next if @operations && !@operations.include?(op.action)
117
161
 
118
162
  json = op.raw_record && JSON.generate(op.raw_record.except('$type'))
119
163
  puts "[#{time}] (#{msg.seq}) #{msg.repo} #{op.action.inspect} #{op.collection} #{op.rkey} #{json}"
@@ -121,11 +165,11 @@ module RatProto
121
165
  end
122
166
 
123
167
  def print_account_message(msg)
124
- return if @dids && !@dids.include?(msg.repo)
125
-
126
- time = msg.time.getlocal.iso8601
168
+ time = msg.time.getlocal
127
169
  @last_timestamp = time
128
170
 
171
+ return if @dids && !@dids.include?(msg.repo)
172
+
129
173
  status = (msg.active?) ? :active : msg.status
130
174
 
131
175
  puts "[#{time}] (#{msg.seq}) #{msg.repo} #account (status = #{status.inspect})"
@@ -133,8 +177,8 @@ module RatProto
133
177
 
134
178
  def show_account_messages?
135
179
  if account? == nil
136
- # enabled by default if -c is not used
137
- @collections.nil?
180
+ # enabled by default if -c or -o is not used, or if commits were explicitly disabled
181
+ @no_collections || (@collections.nil? && @operations.nil?)
138
182
  else
139
183
  # if enabled/disabled explicitly, follow user's choice
140
184
  account?
@@ -143,16 +187,21 @@ module RatProto
143
187
 
144
188
  def configure_interrupt_handler
145
189
  trap('SIGINT') do
146
- log
190
+ log nil, true
147
191
  log 'Disconnecting...'
148
192
  @sky.disconnect
149
193
  end
150
194
  end
151
195
 
196
+ def log(text = nil, force = false)
197
+ super(text) unless quiet? && !force
198
+ end
199
+
152
200
  def print_cursor_and_stop(msg)
153
201
  return if @disconnecting
154
202
 
155
- puts msg.seq
203
+ puts (print_time?) ? msg.time.getlocal : msg.seq
204
+
156
205
  @sky.disconnect
157
206
  @disconnecting = true
158
207
  end
@@ -24,7 +24,9 @@ module RatProto
24
24
  option ['-t', '--target'], 'PATTERN', "filter by labelled DID/URI (can be a pattern with '*' as a wildcard; repeatable)",
25
25
  multivalued: true, attribute_name: :target_patterns
26
26
 
27
- option ['-s', '--print-cursor'], :flag, 'only print current cursor (seq)'
27
+ option ['-q', '--quiet'], :flag, 'disable status messages'
28
+ option ['-s', '--print-cursor'], :flag, 'only print cursor (seq) of first event and disconnect'
29
+ option ['--print-time'], :flag, 'only print timestamp of first event and disconnect'
28
30
 
29
31
 
30
32
  def execute
@@ -37,12 +39,13 @@ module RatProto
37
39
  @sky = Skyfall::Firehose.new(@service, :subscribe_labels, cursor)
38
40
  configure_interrupt_handler
39
41
 
40
- if print_cursor?
42
+ if print_cursor? || print_time?
41
43
  @sky.on_message do |msg|
42
44
  print_cursor_and_stop(msg)
43
45
  next
44
46
  end
45
47
 
48
+ @sky.on_error { |e| log "ERROR: #{e.message}" }
46
49
  @sky.connect
47
50
  else
48
51
  configure_callbacks
@@ -90,35 +93,41 @@ module RatProto
90
93
  @sky.on_disconnect { log "Disconnected" }
91
94
  @sky.on_reconnect { log "Connection lost, trying to reconnect..." }
92
95
  @sky.on_timeout { log "Connection stalled, triggering a reconnect..." }
93
- @sky.on_error { |e| log "ERROR: #{e}" }
96
+ @sky.on_error { |e| log "ERROR: #{e.message}" }
94
97
 
95
98
  @sky.on_message do |msg|
96
99
  next unless msg.type == :labels
97
100
 
98
101
  msg.labels.each do |label|
102
+ time = label.created_at.getlocal
103
+ @last_timestamp = time
104
+
99
105
  next if @labels && !@labels.include?(label.value)
100
106
  next if @targets && @targets.none? { |pattern| pattern.match?(label.subject) }
101
107
 
102
- @last_timestamp = label.created_at
103
-
104
108
  json = JSON.generate(label.data.except('cid', 'cts', 'sig', 'src', 'ver'))
105
- puts "[#{label.created_at}] (#{msg.seq}) #{json}"
109
+ puts "[#{time}] (#{msg.seq}) #{json}"
106
110
  end
107
111
  end
108
112
  end
109
113
 
110
114
  def configure_interrupt_handler
111
115
  trap('SIGINT') do
112
- log
116
+ log nil, true
113
117
  log 'Disconnecting...'
114
118
  @sky.disconnect
115
119
  end
116
120
  end
117
121
 
122
+ def log(text = nil, force = false)
123
+ super(text) unless quiet? && !force
124
+ end
125
+
118
126
  def print_cursor_and_stop(msg)
119
127
  return if @disconnecting
120
128
 
121
- puts msg.seq
129
+ puts (print_time?) ? msg.labels.first.created_at.getlocal : msg.seq
130
+
122
131
  @sky.disconnect
123
132
  @disconnecting = true
124
133
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RatProto
4
- VERSION = "0.3.1"
4
+ VERSION = "0.3.3"
5
5
  end
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.3.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kuba Suder
@@ -102,6 +102,7 @@ files:
102
102
  - exe/rat
103
103
  - lib/ratproto.rb
104
104
  - lib/ratproto/cli.rb
105
+ - lib/ratproto/commands/account_status.rb
105
106
  - lib/ratproto/commands/base.rb
106
107
  - lib/ratproto/commands/fetch.rb
107
108
  - lib/ratproto/commands/resolve.rb
@@ -117,6 +118,7 @@ metadata:
117
118
  bug_tracker_uri: https://tangled.org/mackuba.eu/ratproto/issues
118
119
  changelog_uri: https://tangled.org/mackuba.eu/ratproto/blob/master/CHANGELOG.md
119
120
  source_code_uri: https://tangled.org/mackuba.eu/ratproto
121
+ rubygems_mfa_required: 'true'
120
122
  rdoc_options: []
121
123
  require_paths:
122
124
  - lib