e621stream 0.0.0 → 0.0.2

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: b16a81259148bd2d44ea56a857d2aeefbf15029660f8bd79d219198e3231711e
4
- data.tar.gz: d24d99e59eb909cbf80f39108c9d90835022f2d886b4f89060245e8c6ceb86c7
3
+ metadata.gz: d44d1f8f30f4f8d3931eb4a5b1f428413191a84297dbf9f879bef61307205b88
4
+ data.tar.gz: 7524d28a0ec987855de8e63c9c5efae8c2bd0bd0d3d07d846d03bcd16eaa8402
5
5
  SHA512:
6
- metadata.gz: d8b860f937490e74e90af663866c043b9923321ef85a6d2a84a7a8ba57571605079c71fcaf9153f28343262583647ceb1bd4b3c659cb1aa0346e8d2581c1cd47
7
- data.tar.gz: aa52fe6a7af0ac4f3bf7e0adff246b800b16b8350a12ea27feb55c4d389c06bdacd54e17c03e62f84d11bc40cbb7c2d49b5b1b62dd9df16b894b5e82fcabd83c
6
+ metadata.gz: f6d4fae99737f6f2b1b57ffe083662ba32b0a64ca6bacf9bc048ced7afe8da4216d0ebe70515757667a1d6ed86fac452310acb2ef500170fea921f91f28ea690
7
+ data.tar.gz: d276a848290525a9ca77d3a5c432ca2d8baf17a34142b1d52f756d07688e69d608a6a47d9da4c3a33a7bed99e9bc6d932c5b2ad571ddca5fec3cd1ff843ffa7b
data/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # e621stream
2
2
 
3
- A Ruby client for [E621Stream](https://e621.stream), a live WebSocket stream of e621 activity. It's built on [async-websocket](https://github.com/socketry/async-websocket) and typed with Sorbet.
3
+ A client for [E621Stream](https://e621.stream), using [async-websocket](https://github.com/socketry/async-websocket) and typed with sorbet.
4
4
 
5
- ```bash
5
+ ```sh
6
6
  gem install e621stream
7
7
  ```
8
8
 
9
9
  ```ruby
10
- require "e621stream"
10
+ require("e621stream")
11
11
 
12
12
  client = E621Stream::Client.new(
13
13
  username: "your_name",
@@ -22,14 +22,12 @@ client.on(:error) { |error| warn(error.message) }
22
22
  client.run
23
23
  ```
24
24
 
25
- - **Events:** `on("<event type>")` for one type, or `on(:dispatch)` for everything. Each is an `E621Stream::Event` with `id`, `type`, `time` and `data`, which is the record as e621's api returns it. See [Events](https://e621.stream/events/) for what each one is.
26
- - **Running:** `run` blocks. Inside a reactor already, it runs as a task, so `client.close` from another task stops it.
27
- - **Reconnecting:** the connection is reopened with backoff when it drops, and resumes from the last event it got. It isn't reopened after a close retrying won't fix, like bad credentials. `reconnect: false` turns it off.
28
- - **Resuming:** pass `cursor:` (an event id, or a millisecond timestamp) to replay from there. `client.cursor` is the last event received.
29
- - **Changing subscriptions:** `client.subscribe([...])`.
30
- - **Private events:** like `dmail.create`, these are polled with your own credentials and have to be subscribed to by name.
31
- - **Other events:** `:ready`, `:replay_complete`, `:subscribed`, `:reconnecting`, `:close` and `:error`.
25
+ `run` blocks, unless you're already in a reactor, then it runs as a task and `client.close` stops it.
32
26
 
33
- ```bash
34
- bundle exec rake # rubocop, zeitwerk, tests and sorbet
27
+ `on("post.create")` listens for one type, `on(:dispatch)` for everything. Events have `id`, `type`, `time` and `data` (the record as the api returns it), see [Events](https://e621.stream/events/). There's also `:ready`, `:replay_complete`, `:subscribed`, `:reconnecting`, `:close` and `:error`.
28
+
29
+ It reconnects when the connection drops (`reconnect: false` turns that off), and picks up from `client.cursor`. Pass `cursor:` to replay from an event id or ms timestamp. `client.subscribe([...])` changes subscriptions. Private events like `dmail.create` have to be subscribed to by name.
30
+
31
+ ```sh
32
+ bundle exec rake # rubocop, zeitwerk, tests & sorbet
35
33
  ```
@@ -18,7 +18,7 @@ module E621Stream
18
18
  # An event type, `<resource>.*`, `*`, or `{ "type" => ..., "filter" => { ... } }`.
19
19
  Subscription = T.type_alias { T.any(String, T::Hash[T.any(String, Symbol), T.untyped]) }
20
20
 
21
- # Opens the real connection, tests swap it for a fake one.
21
+ # Tests swap this out.
22
22
  class Connector
23
23
  extend(T::Sig)
24
24
 
@@ -30,7 +30,6 @@ module E621Stream
30
30
  end
31
31
 
32
32
  HEARTBEAT_SECONDS = 30
33
- # The server answers heartbeats and pings, nothing for this long means the connection is gone without having closed.
34
33
  SILENT_SECONDS = 75
35
34
  MAX_DELAY_SECONDS = 60
36
35
 
@@ -89,9 +88,8 @@ module E621Stream
89
88
  break unless reconnecting
90
89
 
91
90
  @attempt += 1
92
- # a server that's rate limiting this connection wants a longer break
93
- base = code == CloseCodes::RATE_LIMITED ? 30 : 1
94
- delay = [base * (2**(@attempt - 1)), MAX_DELAY_SECONDS].min * rand(0.75..1.25)
91
+ delay = code == CloseCodes::RATE_LIMITED ? 30 : [@attempt**2, MAX_DELAY_SECONDS].min
92
+ delay += rand
95
93
  emit("reconnecting", { attempt: @attempt, delay: delay })
96
94
  sleep(delay)
97
95
  end
@@ -114,10 +112,8 @@ module E621Stream
114
112
 
115
113
  private
116
114
 
117
- # resolves to the close code and reason
118
115
  sig { returns([Integer, String]) }
119
116
  def connect_once
120
- result = T.let([1000, ""], [Integer, String])
121
117
  @connector.connect(options.url) do |connection|
122
118
  @connection = connection
123
119
  @last_message = now
@@ -132,11 +128,11 @@ module E621Stream
132
128
  @connection = nil
133
129
  end
134
130
  end
135
- result
131
+ # anything but a normal close raises out of read
132
+ [1000, ""]
136
133
  rescue Protocol::WebSocket::ClosedError => e
137
134
  [e.code || 1006, e.message]
138
135
  rescue StandardError => e
139
- # closing tears the connection down under the read, which isn't worth reporting
140
136
  return [1000, "closed by client"] if closing?
141
137
 
142
138
  emit("error", e)
@@ -148,7 +144,7 @@ module E621Stream
148
144
  loop do
149
145
  sleep(HEARTBEAT_SECONDS)
150
146
  if now - @last_message > SILENT_SECONDS
151
- connection.close(4999, "no response from the server")
147
+ connection.close(1001, "timed out")
152
148
  break
153
149
  end
154
150
  send_message({ op: "heartbeat" })
@@ -170,7 +166,6 @@ module E621Stream
170
166
  emit("ready", data)
171
167
  when "dispatch"
172
168
  event = Event.new(id: message["id"], type: message["type"], time: message["time"], data: message["data"], private: message["private"] == true)
173
- # private events aren't in the stream, so they can't be resumed from
174
169
  @cursor = event.id unless event.private?
175
170
  emit("dispatch", event)
176
171
  emit(event.type, event)
@@ -197,7 +192,7 @@ module E621Stream
197
192
  @listeners[name]&.each { |listener| listener.call(value) }
198
193
  end
199
194
 
200
- # read through a method, since #close changes it from another fiber while #run is waiting
195
+ # #close changes this from another fiber
201
196
  sig { returns(T::Boolean) }
202
197
  def closing?
203
198
  @closing
@@ -16,8 +16,10 @@ module E621Stream
16
16
  SLOW_CONSUMER = 4009
17
17
  TOO_MANY_CONNECTIONS = 4010
18
18
  AUTHENTICATION_EXPIRED = 4011
19
+ DISCONNECTED = 4012
20
+ BANNED = 4013
19
21
 
20
22
  # Closing for any of these again would just get the same result, so they aren't reconnected from.
21
- FATAL = T.let([UNKNOWN_OP, DECODE_ERROR, AUTHENTICATION_FAILED, ALREADY_IDENTIFIED, INVALID_SUBSCRIPTION, INVALID_CURSOR, AUTHENTICATION_EXPIRED].freeze, T::Array[Integer])
23
+ FATAL = T.let([UNKNOWN_OP, DECODE_ERROR, AUTHENTICATION_FAILED, ALREADY_IDENTIFIED, INVALID_SUBSCRIPTION, INVALID_CURSOR, AUTHENTICATION_EXPIRED, DISCONNECTED, BANNED].freeze, T::Array[Integer])
22
24
  end
23
25
  end
@@ -3,7 +3,7 @@
3
3
 
4
4
  module E621Stream
5
5
  module Events
6
- # Kept identical to the server's src/events.ts, the repository's tests check it.
6
+ # Generated by scripts/clientTypes.ts in the E621Stream repository, from the server's src/events.ts.
7
7
  EVENT_TYPES = %w[
8
8
  artist.create artist.delete artist.update artist_url.create artist_version.create avoid_posting.create
9
9
  avoid_posting.delete avoid_posting.destroy avoid_posting.undelete avoid_posting.update
@@ -12,18 +12,18 @@ module E621Stream
12
12
  forum_post.create forum_post.delete forum_post.hide forum_post.unhide forum_post.update forum_topic.create
13
13
  forum_topic.delete forum_topic.hide forum_topic.unhide forum_topic.update help_page.create help_page.update
14
14
  mod_action.create news_update.create news_update.update note.create note.update note_version.create
15
- pool.add_posts pool.create pool.delete pool.remove_posts pool.update pool_version.create post.create post.update
16
- post_approval.create post_event.create post_flag.create post_flag.update post_replacement.create
17
- post_set.add_posts post_set.create post_set.delete post_set.remove_posts post_set.update post_version.create
18
- post_version.hide post_version.unhide tag.create tag.delete tag.update tag_alias.create tag_alias.update
19
- tag_implication.create tag_implication.update tag_type_version.create takedown.create takedown.delete
20
- takedown.update upload_karma_event.create user.create user.delete user.update user_feedback.create
21
- user_feedback.delete user_feedback.destroy user_feedback.undelete user_feedback.update wiki_page.create
22
- wiki_page.delete wiki_page.update wiki_page_version.create
15
+ pool.add_posts pool.create pool.delete pool.remove_posts pool.update pool_version.create post.create
16
+ post.destroy post.stats post.update post_approval.create post_event.create post_flag.create post_flag.update
17
+ post_replacement.create post_replacement.destroy post_replacement.update post_set.add_posts post_set.create
18
+ post_set.delete post_set.remove_posts post_set.update post_version.create post_version.hide post_version.unhide
19
+ tag.create tag.delete tag.update tag_alias.create tag_alias.update tag_implication.create tag_implication.update
20
+ tag_type_version.create takedown.create takedown.delete takedown.update upload_karma_event.create user.create
21
+ user.delete user.update user_feedback.create user_feedback.delete user_feedback.destroy user_feedback.undelete
22
+ user_feedback.update wiki_page.create wiki_page.delete wiki_page.update wiki_page_version.create
23
23
  ].freeze
24
24
 
25
25
  # Polled with your own credentials and only sent to you. They have to be subscribed to by name, `*` doesn't include
26
26
  # them.
27
- PRIVATE_EVENT_TYPES = %w[dmail.create].freeze
27
+ PRIVATE_EVENT_TYPES = %w[dmail.create private_post_set.add_posts private_post_set.remove_posts].freeze
28
28
  end
29
29
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module E621Stream
4
- VERSION = "0.0.0"
4
+ VERSION = "0.0.2"
5
5
  end
data/lib/e621stream.rb CHANGED
@@ -4,8 +4,7 @@ require("json")
4
4
  require("zeitwerk")
5
5
  require("sorbet-runtime")
6
6
 
7
- # Loaded eagerly (rather than left to Zeitwerk) because the gemspec already requires version.rb to read the version,
8
- # which defines E621Stream before Zeitwerk::Loader#setup runs.
7
+ # required here since the gemspec loads version.rb before zeitwerk is set up
9
8
  require_relative("e621stream/version")
10
9
 
11
10
  loader = Zeitwerk::Loader.for_gem
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: e621stream
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Donovan_DMC
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-11-09 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: async-websocket
@@ -169,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
169
  - !ruby/object:Gem::Version
170
170
  version: '0'
171
171
  requirements: []
172
- rubygems_version: 3.6.2
172
+ rubygems_version: 3.6.9
173
173
  specification_version: 4
174
174
  summary: A client for E621Stream, a live WebSocket stream of e621 activity.
175
175
  test_files: []