discorb 0.19.0 → 0.20.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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build_version.yml +2 -2
  3. data/.rubocop.yml +12 -75
  4. data/Changelog.md +10 -0
  5. data/Rakefile +482 -454
  6. data/lib/discorb/allowed_mentions.rb +68 -72
  7. data/lib/discorb/app_command/command.rb +466 -398
  8. data/lib/discorb/app_command/common.rb +65 -25
  9. data/lib/discorb/app_command/handler.rb +304 -266
  10. data/lib/discorb/app_command.rb +5 -5
  11. data/lib/discorb/application.rb +198 -197
  12. data/lib/discorb/asset.rb +101 -101
  13. data/lib/discorb/attachment.rb +134 -119
  14. data/lib/discorb/audit_logs.rb +412 -385
  15. data/lib/discorb/automod.rb +279 -269
  16. data/lib/discorb/channel/base.rb +107 -108
  17. data/lib/discorb/channel/category.rb +32 -32
  18. data/lib/discorb/channel/container.rb +44 -44
  19. data/lib/discorb/channel/dm.rb +26 -28
  20. data/lib/discorb/channel/guild.rb +311 -246
  21. data/lib/discorb/channel/stage.rb +156 -140
  22. data/lib/discorb/channel/text.rb +430 -336
  23. data/lib/discorb/channel/thread.rb +374 -325
  24. data/lib/discorb/channel/voice.rb +85 -79
  25. data/lib/discorb/channel.rb +5 -5
  26. data/lib/discorb/client.rb +635 -621
  27. data/lib/discorb/color.rb +178 -182
  28. data/lib/discorb/common.rb +168 -164
  29. data/lib/discorb/components/button.rb +107 -106
  30. data/lib/discorb/components/select_menu.rb +157 -145
  31. data/lib/discorb/components/text_input.rb +103 -106
  32. data/lib/discorb/components.rb +68 -66
  33. data/lib/discorb/dictionary.rb +135 -135
  34. data/lib/discorb/embed.rb +404 -398
  35. data/lib/discorb/emoji.rb +309 -302
  36. data/lib/discorb/emoji_table.rb +16099 -8857
  37. data/lib/discorb/error.rb +131 -131
  38. data/lib/discorb/event.rb +360 -314
  39. data/lib/discorb/event_handler.rb +39 -39
  40. data/lib/discorb/exe/about.rb +17 -17
  41. data/lib/discorb/exe/irb.rb +72 -67
  42. data/lib/discorb/exe/new.rb +323 -315
  43. data/lib/discorb/exe/run.rb +69 -68
  44. data/lib/discorb/exe/setup.rb +57 -55
  45. data/lib/discorb/exe/show.rb +12 -12
  46. data/lib/discorb/extend.rb +25 -45
  47. data/lib/discorb/extension.rb +89 -83
  48. data/lib/discorb/flag.rb +126 -128
  49. data/lib/discorb/gateway.rb +984 -804
  50. data/lib/discorb/gateway_events.rb +670 -638
  51. data/lib/discorb/gateway_requests.rb +45 -48
  52. data/lib/discorb/guild.rb +2115 -1626
  53. data/lib/discorb/guild_template.rb +280 -241
  54. data/lib/discorb/http.rb +247 -232
  55. data/lib/discorb/image.rb +42 -42
  56. data/lib/discorb/integration.rb +169 -161
  57. data/lib/discorb/intents.rb +161 -163
  58. data/lib/discorb/interaction/autocomplete.rb +76 -62
  59. data/lib/discorb/interaction/command.rb +279 -224
  60. data/lib/discorb/interaction/components.rb +114 -104
  61. data/lib/discorb/interaction/modal.rb +36 -32
  62. data/lib/discorb/interaction/response.rb +379 -336
  63. data/lib/discorb/interaction/root.rb +271 -257
  64. data/lib/discorb/interaction.rb +5 -5
  65. data/lib/discorb/invite.rb +154 -153
  66. data/lib/discorb/member.rb +344 -311
  67. data/lib/discorb/message.rb +615 -544
  68. data/lib/discorb/message_meta.rb +197 -186
  69. data/lib/discorb/modules.rb +371 -290
  70. data/lib/discorb/permission.rb +305 -291
  71. data/lib/discorb/presence.rb +352 -346
  72. data/lib/discorb/rate_limit.rb +81 -76
  73. data/lib/discorb/reaction.rb +55 -54
  74. data/lib/discorb/role.rb +272 -240
  75. data/lib/discorb/shard.rb +76 -74
  76. data/lib/discorb/sticker.rb +193 -171
  77. data/lib/discorb/user.rb +205 -188
  78. data/lib/discorb/utils/colored_puts.rb +16 -16
  79. data/lib/discorb/utils.rb +12 -16
  80. data/lib/discorb/voice_state.rb +305 -281
  81. data/lib/discorb/webhook.rb +537 -507
  82. data/lib/discorb.rb +62 -56
  83. data/sig/discorb/application.rbs +2 -0
  84. data/sig/discorb/automod.rbs +10 -1
  85. data/sig/discorb/guild.rbs +2 -0
  86. data/sig/discorb/message.rbs +2 -0
  87. data/sig/discorb/user.rbs +22 -20
  88. metadata +2 -2
@@ -1,804 +1,984 @@
1
- # frozen_string_literal: true
2
-
3
- require "async/http"
4
- require "async/websocket"
5
- require "async/barrier"
6
- require "json"
7
- require "zlib"
8
-
9
- module Discorb
10
- #
11
- # A module for Discord Gateway.
12
- # This module is internal use only.
13
- #
14
- module Gateway
15
- #
16
- # A module to handle gateway events.
17
- #
18
- module Handler
19
- # @type instance: Discorb::Client
20
-
21
- private
22
-
23
- def connect_gateway(reconnect)
24
- Async do
25
- @mutex["gateway_#{shard_id}"] ||= Mutex.new
26
- @mutex["gateway_#{shard_id}"].synchronize do
27
- if reconnect
28
- logger.info "Reconnecting to gateway..."
29
- else
30
- logger.info "Connecting to gateway..."
31
- end
32
-
33
- @http = HTTP.new(self)
34
- _, gateway_response = @http.request(Route.new("/gateway", "//gateway", :get)).wait
35
- gateway_url = gateway_response[:url]
36
- gateway_version = if @intents.to_h[:message_content].nil?
37
- unless @message_content_intent_warned
38
- warn "message_content intent not set, using gateway version 9. " \
39
- "You should specify `message_content` intent for preventing unexpected changes in the future."
40
- @message_content_intent_warned = true
41
- end
42
- 9
43
- else
44
- 10
45
- end
46
- endpoint = Async::HTTP::Endpoint.parse(
47
- "#{gateway_url}?v=#{gateway_version}&encoding=json&compress=zlib-stream&_=#{Time.now.to_i}",
48
- alpn_protocols: Async::HTTP::Protocol::HTTP11.names,
49
- )
50
- begin
51
- reconnect_count = 0
52
- begin
53
- self.connection = Async::WebSocket::Client.connect(
54
- endpoint,
55
- headers: [["User-Agent", Discorb::USER_AGENT]],
56
- handler: RawConnection,
57
- )
58
- rescue Async::WebSocket::ProtocolError => e
59
- raise if reconnect_count > 3
60
-
61
- logger.info "Failed to connect to gateway, retrying...: #{e.message}"
62
- reconnect_count += 1
63
- sleep 2 ** reconnect_count
64
- retry
65
- end
66
- con = self.connection
67
- zlib_stream = Zlib::Inflate.new(Zlib::MAX_WBITS)
68
- buffer = +""
69
- begin
70
- while (message = con.read)
71
- buffer << message
72
- if message.end_with?((+"\x00\x00\xff\xff").force_encoding("ASCII-8BIT"))
73
- begin
74
- data = zlib_stream.inflate(buffer)
75
- buffer = +""
76
- message = JSON.parse(data, symbolize_names: true)
77
- rescue JSON::ParserError
78
- buffer = +""
79
- logger.error "Received invalid JSON from gateway."
80
- logger.debug "#{data}"
81
- else
82
- handle_gateway(message, reconnect)
83
- end
84
- end
85
- end
86
- rescue Async::Wrapper::Cancelled,
87
- OpenSSL::SSL::SSLError,
88
- Async::Wrapper::WaitError,
89
- EOFError,
90
- Errno::EPIPE,
91
- Errno::ECONNRESET,
92
- IOError => e
93
- next if @status == :closed
94
-
95
- logger.info "Gateway connection closed, reconnecting: #{e.class}: #{e.message}"
96
- con.force_close
97
- connect_gateway(true)
98
- next
99
- end
100
- rescue Protocol::WebSocket::ClosedError => e
101
- @tasks.map(&:stop)
102
- case e.code
103
- when 4004
104
- raise ClientError.new("Authentication failed"), cause: nil
105
- when 4009
106
- logger.info "Session timed out, reconnecting."
107
- con.force_close
108
- connect_gateway(true)
109
- next
110
- when 4014
111
- raise ClientError.new("Disallowed intents were specified"), cause: nil
112
- when 4001, 4002, 4003, 4005, 4007
113
- raise ClientError.new(<<~ERROR), cause: e
114
- Disconnected from gateway, probably due to library issues.
115
- #{e.message}
116
-
117
- Please report this to the library issue tracker.
118
- https://github.com/discorb-lib/discorb/issues
119
- ERROR
120
- when 1001
121
- logger.info "Gateway closed with code 1001, reconnecting."
122
- con.force_close
123
- connect_gateway(true)
124
- next
125
- else
126
- logger.error "Discord WebSocket closed with code #{e.code}."
127
- logger.debug "#{e.message}"
128
- con.force_close
129
- connect_gateway(false)
130
- next
131
- end
132
- rescue StandardError => e
133
- logger.error "Discord WebSocket error: #{e.full_message}"
134
- con.force_close
135
- connect_gateway(false)
136
- next
137
- end
138
- end
139
- end
140
- end
141
-
142
- def send_gateway(opcode, **value)
143
- if @shards.any? && shard.nil?
144
- @shards.map(&:connection)
145
- else
146
- [connection]
147
- end.each do |con|
148
- con.write({ op: opcode, d: value }.to_json)
149
- con.flush
150
- end
151
- logger.debug "Sent message to fd #{connection.io.fileno}: #{{ op: opcode, d: value }.to_json.gsub(@token,
152
- "[Token]")}"
153
- end
154
-
155
- def handle_gateway(payload, reconnect)
156
- Async do |_task|
157
- data = payload[:d]
158
- @last_s = payload[:s] if payload[:s]
159
- logger.debug "Received message with opcode #{payload[:op]} from gateway."
160
- logger.debug "#{payload.to_json.gsub(@token, "[Token]")}"
161
- case payload[:op]
162
- when 10
163
- @heartbeat_interval = data[:heartbeat_interval]
164
- if reconnect
165
- payload = {
166
- token: @token,
167
- session_id: session_id,
168
- seq: @last_s,
169
- }
170
- send_gateway(6, **payload)
171
- else
172
- payload = {
173
- token: @token,
174
- intents: @intents.value,
175
- compress: false,
176
- properties: { "os" => RUBY_PLATFORM, "browser" => "discorb", "device" => "discorb" },
177
- }
178
- payload[:shard] = [shard_id, @shard_count] if shard_id
179
- payload[:presence] = @identify_presence if @identify_presence
180
- send_gateway(2, **payload)
181
- end
182
- when 7
183
- logger.info "Received opcode 7, stopping tasks"
184
- @tasks.map(&:stop)
185
- when 9
186
- logger.warn "Received opcode 9, closed connection"
187
- @tasks.map(&:stop)
188
- if data
189
- logger.info "Connection is resumable, reconnecting"
190
- connection.force_close
191
- connect_gateway(true)
192
- else
193
- logger.info "Connection is not resumable, reconnecting with opcode 2"
194
- connection.force_close
195
-
196
- sleep(2)
197
- connect_gateway(false)
198
- end
199
- when 11
200
- logger.debug "Received opcode 11"
201
- @ping = Time.now.to_f - @heartbeat_before
202
- when 0
203
- handle_event(payload[:t], data)
204
- end
205
- end
206
- end
207
-
208
- def handle_heartbeat
209
- Async do |_task|
210
- interval = @heartbeat_interval
211
- sleep((interval / 1000.0 - 1) * rand)
212
- loop do
213
- unless connection.closed?
214
- @heartbeat_before = Time.now.to_f
215
- connection.write({ op: 1, d: @last_s }.to_json)
216
- connection.flush
217
- logger.debug "Sent opcode 1."
218
- logger.debug "Waiting for heartbeat."
219
- end
220
- sleep(interval / 1000.0 - 1)
221
- end
222
- end
223
- end
224
-
225
- def handle_event(event_name, data)
226
- return logger.debug "Client isn't ready; event #{event_name} wasn't handled" if @wait_until_ready &&
227
- !@ready &&
228
- !%w[
229
- READY GUILD_CREATE
230
- ].include?(event_name)
231
-
232
- dispatch(:event_receive, event_name, data)
233
- logger.debug "Handling event #{event_name}"
234
- case event_name
235
- when "READY"
236
- @api_version = data[:v]
237
- self.session_id = data[:session_id]
238
- @user = ClientUser.new(self, data[:user])
239
- @uncached_guilds = data[:guilds].map { |g| g[:id] }
240
- ready if (@uncached_guilds == []) || !@intents.guilds
241
- dispatch(:ready)
242
-
243
- @tasks << handle_heartbeat
244
- when "GUILD_CREATE"
245
- if @uncached_guilds.include?(data[:id])
246
- Guild.new(self, data, true)
247
- @uncached_guilds.delete(data[:id])
248
- if @uncached_guilds == []
249
- logger.debug "All guilds cached"
250
- ready
251
- end
252
- elsif @guilds.has?(data[:id])
253
- @guilds[data[:id]].send(:_set_data, data, true)
254
- dispatch(:guild_available, guild)
255
- else
256
- guild = Guild.new(self, data, true)
257
- dispatch(:guild_join, guild)
258
- end
259
- dispatch(:guild_create, @guilds[data[:id]])
260
- when "MESSAGE_CREATE"
261
- message = Message.new(self, data)
262
- dispatch(:message, message)
263
- when "GUILD_UPDATE"
264
- if @guilds.has?(data[:id])
265
- current = @guilds[data[:id]]
266
- before = Guild.new(self, current.instance_variable_get(:@data).merge(no_cache: true), false)
267
- current.send(:_set_data, data, false)
268
- dispatch(:guild_update, before, current)
269
- else
270
- logger.warn "Unknown guild id #{data[:id]}, ignoring"
271
- end
272
- when "GUILD_DELETE"
273
- return logger.warn "Unknown guild id #{data[:id]}, ignoring" unless (guild = @guilds.delete(data[:id]))
274
-
275
- dispatch(:guild_delete, guild)
276
- if data[:unavailable]
277
- dispatch(:guild_destroy, guild)
278
- else
279
- dispatch(:guild_leave, guild)
280
- end
281
- when "GUILD_ROLE_CREATE"
282
- return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
283
-
284
- nr = Role.new(@client, guild, data[:role])
285
- guild.roles[data[:role][:id]] = nr
286
- dispatch(:role_create, nr)
287
- when "GUILD_ROLE_UPDATE"
288
- return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
289
- return logger.warn "Unknown role id #{data[:role][:id]}, ignoring" unless guild.roles.has?(data[:role][:id])
290
-
291
- current = guild.roles[data[:role][:id]]
292
- before = Role.new(@client, guild, current.instance_variable_get(:@data).update({ no_cache: true }))
293
- current.send(:_set_data, data[:role])
294
- dispatch(:role_update, before, current)
295
- when "GUILD_ROLE_DELETE"
296
- return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
297
- unless (role = guild.roles.delete(data[:role_id]))
298
- return logger.warn "Unknown role id #{data[:role_id]}, ignoring"
299
- end
300
-
301
- dispatch(:role_delete, role)
302
- when "CHANNEL_CREATE"
303
- return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
304
-
305
- nc = Channel.make_channel(self, data)
306
- guild.channels[data[:id]] = nc
307
-
308
- dispatch(:channel_create, nc)
309
- when "CHANNEL_UPDATE"
310
- return logger.warn "Unknown channel id #{data[:id]}, ignoring" unless (current = @channels[data[:id]])
311
-
312
- before = Channel.make_channel(self, current.instance_variable_get(:@data), no_cache: true)
313
- current.send(:_set_data, data)
314
- dispatch(:channel_update, before, current)
315
- when "CHANNEL_DELETE"
316
- return logger.warn "Unknown channel id #{data[:id]}, ignoring" unless (channel = @channels.delete(data[:id]))
317
-
318
- @guilds[data[:guild_id]]&.channels&.delete(data[:id])
319
- dispatch(:channel_delete, channel)
320
- when "CHANNEL_PINS_UPDATE"
321
- nil # do in MESSAGE_UPDATE
322
- when "THREAD_CREATE"
323
- thread = Channel.make_channel(self, data)
324
-
325
- dispatch(:thread_create, thread)
326
- if data.key?(:member)
327
- dispatch(:thread_join, thread)
328
- else
329
- dispatch(:thread_new, thread)
330
- end
331
- when "THREAD_UPDATE"
332
- return logger.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels[data[:id]])
333
-
334
- before = Channel.make_channel(self, thread.instance_variable_get(:@data), no_cache: true)
335
- thread.send(:_set_data, data)
336
- dispatch(:thread_update, before, thread)
337
- when "THREAD_DELETE"
338
- return logger.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels.delete(data[:id]))
339
-
340
- @guilds[data[:guild_id]]&.channels&.delete(data[:id])
341
- dispatch(:thread_delete, thread)
342
- when "THREAD_LIST_SYNC"
343
- data[:threads].each do |raw_thread|
344
- thread = Channel.make_channel(self, raw_thread.merge({ member: raw_thread[:members].find do |m|
345
- m[:id] == raw_thread[:id]
346
- end }))
347
- @channels[thread.id] = thread
348
- end
349
- when "THREAD_MEMBER_UPDATE"
350
- return logger.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels[data[:id]])
351
-
352
- if (member = thread.members[data[:id]])
353
- old = ThreadChannel::Member.new(self, member.instance_variable_get(:@data), data[:guild_id])
354
- member.send(:_set_data, data)
355
- else
356
- old = nil
357
- member = ThreadChannel::Member.new(self, data, data[:guild_id])
358
- thread.members[data[:user_id]] = member
359
- end
360
- dispatch(:thread_member_update, thread, old, member)
361
- when "THREAD_MEMBERS_UPDATE"
362
- return logger.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels[data[:id]])
363
-
364
- thread.instance_variable_set(:@member_count, data[:member_count])
365
- members = []
366
- (data[:added_members] || []).each do |raw_member|
367
- member = ThreadChannel::Member.new(self, raw_member, data[:guild_id])
368
- thread.members[member.id] = member
369
- members << member
370
- end
371
- removed_members = []
372
- (data[:removed_member_ids] || []).each do |id|
373
- removed_members << thread.members.delete(id)
374
- end
375
- dispatch(:thread_members_update, thread, members, removed_members)
376
- when "STAGE_INSTANCE_CREATE"
377
- instance = StageInstance.new(self, data)
378
- dispatch(:stage_instance_create, instance)
379
- when "STAGE_INSTANCE_UPDATE"
380
- unless (channel = @channels[data[:channel_id]])
381
- return logger.warn "Unknown channel id #{data[:channel_id]} , ignoring"
382
- end
383
- unless (instance = channel.stage_instances[data[:id]])
384
- return logger.warn "Unknown stage instance id #{data[:id]}, ignoring"
385
- end
386
-
387
- old = StageInstance.new(self, instance.instance_variable_get(:@data), no_cache: true)
388
- current.send(:_set_data, data)
389
- dispatch(:stage_instance_update, old, current)
390
- when "STAGE_INSTANCE_DELETE"
391
- unless (channel = @channels[data[:channel_id]])
392
- return logger.warn "Unknown channel id #{data[:channel_id]} , ignoring"
393
- end
394
- unless (instance = channel.stage_instances.delete(data[:id]))
395
- return logger.warn "Unknown stage instance id #{data[:id]}, ignoring"
396
- end
397
-
398
- dispatch(:stage_instance_delete, instance)
399
- when "GUILD_MEMBER_ADD"
400
- return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
401
-
402
- nm = Member.new(self, data[:guild_id], data[:user].update({ no_cache: true }), data)
403
- guild.members[nm.id] = nm
404
- dispatch(:member_add, nm)
405
- when "GUILD_MEMBER_UPDATE"
406
- return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
407
- unless (nm = guild.members[data[:user][:id]])
408
- return logger.warn "Unknown member id #{data[:user][:id]}, ignoring"
409
- end
410
-
411
- old = Member.new(self, data[:guild_id], data[:user], data.update({ no_cache: true }))
412
- nm.send(:_set_data, data[:user], data)
413
- dispatch(:member_update, old, nm)
414
- when "GUILD_MEMBER_REMOVE"
415
- return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
416
- unless (member = guild.members.delete(data[:user][:id]))
417
- return logger.warn "Unknown member id #{data[:user][:id]}, ignoring"
418
- end
419
-
420
- dispatch(:member_remove, member)
421
- when "GUILD_BAN_ADD"
422
- return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
423
-
424
- user = if @users.has? data[:user][:id]
425
- @users[data[:user][:id]]
426
- else
427
- User.new(self, data[:user].update({ no_cache: true }))
428
- end
429
-
430
- dispatch(:guild_ban_add, guild, user)
431
- when "GUILD_BAN_REMOVE"
432
- return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
433
-
434
- user = if @users.has? data[:user][:id]
435
- @users[data[:user][:id]]
436
- else
437
- User.new(self, data[:user].update({ no_cache: true }))
438
- end
439
-
440
- dispatch(:guild_ban_remove, guild, user)
441
- when "GUILD_EMOJIS_UPDATE"
442
- return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
443
-
444
- before_emojis = guild.emojis.values.map(&:id).to_set
445
- data[:emojis].each do |emoji|
446
- guild.emojis[emoji[:id]] = CustomEmoji.new(self, guild, emoji)
447
- end
448
- deleted_emojis = before_emojis - guild.emojis.values.map(&:id).to_set
449
- deleted_emojis.each do |emoji|
450
- guild.emojis.delete(emoji)
451
- end
452
- when "GUILD_INTEGRATIONS_UPDATE"
453
- dispatch(:guild_integrations_update, @guilds[data[:guild_id]])
454
- when "INTEGRATION_CREATE"
455
- dispatch(:integration_create, Integration.new(self, data, data[:guild_id]))
456
- when "INTEGRATION_UPDATE"
457
- return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
458
-
459
- integration = Integration.new(self, data, data[:guild_id])
460
- dispatch(:integration_update, integration)
461
- when "INTEGRATION_DELETE"
462
- return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
463
-
464
- dispatch(:integration_delete, IntegrationDeleteEvent.new(self, data))
465
- when "WEBHOOKS_UPDATE"
466
- dispatch(:webhooks_update, WebhooksUpdateEvent.new(self, data))
467
- when "INVITE_CREATE"
468
- dispatch(:invite_create, Invite.new(self, data, true))
469
- when "INVITE_DELETE"
470
- dispatch(:invite_delete, InviteDeleteEvent.new(self, data))
471
- when "VOICE_STATE_UPDATE"
472
- return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
473
-
474
- current = guild.voice_states[data[:user_id]]
475
- if current.nil?
476
- old = nil
477
- current = VoiceState.new(self, data)
478
- guild.voice_states[data[:user_id]] = current
479
- else
480
- guild.voice_states.remove(data[:user_id]) if data[:channel_id].nil?
481
- old = VoiceState.new(self, current.instance_variable_get(:@data))
482
- current.send(:_set_data, data)
483
- end
484
- dispatch(:voice_state_update, old, current)
485
- if old&.channel != current&.channel
486
- dispatch(:voice_channel_update, old, current)
487
- case [old&.channel.nil?, current&.channel.nil?]
488
- when [true, false]
489
- dispatch(:voice_channel_connect, current)
490
- when [false, true]
491
- dispatch(:voice_channel_disconnect, old)
492
- when [false, false]
493
- dispatch(:voice_channel_move, old, current)
494
- end
495
- end
496
- if old&.mute? != current&.mute?
497
- dispatch(:voice_mute_update, old, current)
498
- case [old&.mute?, current&.mute?]
499
- when [false, true]
500
- dispatch(:voice_mute_enable, current)
501
- when [true, false]
502
- dispatch(:voice_mute_disable, old)
503
- end
504
- end
505
- if old&.deaf? != current&.deaf?
506
- dispatch(:voice_deaf_update, old, current)
507
- case [old&.deaf?, current&.deaf?]
508
- when [false, true]
509
- dispatch(:voice_deaf_enable, current)
510
- when [true, false]
511
- dispatch(:voice_deaf_disable, old)
512
- end
513
- end
514
- if old&.self_mute? != current&.self_mute?
515
- dispatch(:voice_self_mute_update, old, current)
516
- case [old&.self_mute?, current&.self_mute?]
517
- when [false, true]
518
- dispatch(:voice_self_mute_enable, current)
519
- when [true, false]
520
- dispatch(:voice_self_mute_disable, old)
521
- end
522
- end
523
- if old&.self_deaf? != current&.self_deaf?
524
- dispatch(:voice_self_deaf_update, old, current)
525
- case [old&.self_deaf?, current&.self_deaf?]
526
- when [false, true]
527
- dispatch(:voice_self_deaf_enable, current)
528
- when [true, false]
529
- dispatch(:voice_self_deaf_disable, old)
530
- end
531
- end
532
- if old&.server_mute? != current&.server_mute?
533
- dispatch(:voice_server_mute_update, old, current)
534
- case [old&.server_mute?, current&.server_mute?]
535
- when [false, true]
536
- dispatch(:voice_server_mute_enable, current)
537
- when [true, false]
538
- dispatch(:voice_server_mute_disable, old)
539
- end
540
- end
541
- if old&.server_deaf? != current&.server_deaf?
542
- dispatch(:voice_server_deaf_update, old, current)
543
- case [old&.server_deaf?, current&.server_deaf?]
544
- when [false, true]
545
- dispatch(:voice_server_deaf_enable, current)
546
- when [true, false]
547
- dispatch(:voice_server_deaf_disable, old)
548
- end
549
- end
550
- if old&.video? != current&.video?
551
- dispatch(:voice_video_update, old, current)
552
- case [old&.video?, current&.video?]
553
- when [false, true]
554
- dispatch(:voice_video_start, current)
555
- when [true, false]
556
- dispatch(:voice_video_end, old)
557
- end
558
- end
559
- if old&.stream? != current&.stream?
560
- dispatch(:voice_stream_update, old, current)
561
- case [old&.stream?, current&.stream?]
562
- when [false, true]
563
- dispatch(:voice_stream_start, current)
564
- when [true, false]
565
- dispatch(:voice_stream_end, old)
566
- end
567
- end
568
- when "PRESENCE_UPDATE"
569
- return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
570
-
571
- guild.presences[data[:user][:id]] = Presence.new(self, data)
572
- when "MESSAGE_UPDATE"
573
- if (message = @messages[data[:id]])
574
- before = Message.new(self, message.instance_variable_get(:@data), no_cache: true)
575
- message.send(:_set_data, message.instance_variable_get(:@data).merge(data))
576
- else
577
- before = nil
578
- message = nil
579
- end
580
- if data[:edited_timestamp].nil?
581
- if message.nil?
582
- nil
583
- elsif message.pinned?
584
- message.instance_variable_set(:@pinned, false)
585
- else
586
- message.instance_variable_set(:@pinned, true)
587
- end
588
- dispatch(:message_pin_update, MessagePinEvent.new(self, data, message))
589
- else
590
- dispatch(:message_update, MessageUpdateEvent.new(self, data, before, current))
591
- end
592
- when "MESSAGE_DELETE"
593
- message.instance_variable_set(:@deleted, true) if (message = @messages[data[:id]])
594
-
595
- dispatch(:message_delete_id, Snowflake.new(data[:id]), channels[data[:channel_id]],
596
- data[:guild_id] && guilds[data[:guild_id]])
597
- dispatch(:message_delete, message, channels[data[:channel_id]], data[:guild_id] && guilds[data[:guild_id]])
598
- when "MESSAGE_DELETE_BULK"
599
- messages = []
600
- data[:ids].each do |id|
601
- if (message = @messages[id])
602
- message.instance_variable_set(:@deleted, true)
603
- messages.push(message)
604
- else
605
- messages.push(UnknownDeleteBulkMessage.new(self, id, data))
606
- end
607
- end
608
- dispatch(:message_delete_bulk, messages)
609
- when "MESSAGE_REACTION_ADD"
610
- if (target_message = @messages[data[:message_id]])
611
- if (target_reaction = target_message.reactions.find do |r|
612
- r.emoji.is_a?(UnicodeEmoji) ? r.emoji.value == data[:emoji][:name] : r.emoji.id == data[:emoji][:id]
613
- end)
614
- target_reaction.instance_variable_set(:@count, target_reaction.count + 1)
615
- else
616
- target_message.reactions << Reaction.new(
617
- target_message,
618
- {
619
- count: 1,
620
- me: @user.id == data[:user_id],
621
- emoji: data[:emoji],
622
- }
623
- )
624
- end
625
- end
626
- dispatch(:reaction_add, ReactionEvent.new(self, data))
627
- when "MESSAGE_REACTION_REMOVE"
628
- if (target_message = @messages[data[:message_id]]) &&
629
- (target_reaction = target_message.reactions.find do |r|
630
- data[:emoji][:id].nil? ? r.emoji.name == data[:emoji][:name] : r.emoji.id == data[:emoji][:id]
631
- end)
632
- target_reaction.instance_variable_set(:@count, target_reaction.count - 1)
633
- target_message.reactions.delete(target_reaction) if target_reaction.count.zero?
634
- end
635
- dispatch(:reaction_remove, ReactionEvent.new(self, data))
636
- when "MESSAGE_REACTION_REMOVE_ALL"
637
- if (target_message = @messages[data[:message_id]])
638
- target_message.reactions = []
639
- end
640
- dispatch(:reaction_remove_all, ReactionRemoveAllEvent.new(self, data))
641
- when "MESSAGE_REACTION_REMOVE_EMOJI"
642
- if (target_message = @messages[data[:message_id]]) &&
643
- (target_reaction = target_message.reactions.find do |r|
644
- data[:emoji][:id].nil? ? r.name == data[:emoji][:name] : r.id == data[:emoji][:id]
645
- end)
646
- target_message.reactions.delete(target_reaction)
647
- end
648
- dispatch(:reaction_remove_emoji, ReactionRemoveEmojiEvent.new(self, data))
649
- when "TYPING_START"
650
- dispatch(:typing_start, TypingStartEvent.new(self, data))
651
- when "INTERACTION_CREATE"
652
- interaction = Interaction.make_interaction(self, data)
653
- dispatch(:interaction_create, interaction)
654
-
655
- dispatch(interaction.class.event_name, interaction)
656
- when "RESUMED"
657
- logger.info("Successfully resumed connection")
658
- @tasks << handle_heartbeat
659
- if shard
660
- dispatch(:shard_resumed, shard)
661
- else
662
- dispatch(:resumed)
663
- end
664
- when "GUILD_SCHEDULED_EVENT_CREATE"
665
- logger.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
666
- event = ScheduledEvent.new(self, data)
667
- guild.scheduled_events[data[:id]] = event
668
- dispatch(:scheduled_event_create, event)
669
- when "GUILD_SCHEDULED_EVENT_UPDATE"
670
- logger.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
671
- unless (event = guild.scheduled_events[data[:id]])
672
- logger.warn("Unknown scheduled event id #{data[:id]}, ignoring")
673
- end
674
- old = event.dup
675
- event.send(:_set_data, data)
676
- dispatch(:scheduled_event_update, old, event)
677
- if old.status == event.status
678
- dispatch(:scheduled_event_edit, old, event)
679
- else
680
- case event.status
681
- when :active
682
- dispatch(:scheduled_event_start, event)
683
- when :completed
684
- dispatch(:scheduled_event_end, event)
685
- end
686
- end
687
- when "GUILD_SCHEDULED_EVENT_DELETE"
688
- logger.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
689
- unless (event = guild.scheduled_events[data[:id]])
690
- logger.warn("Unknown scheduled event id #{data[:id]}, ignoring")
691
- end
692
- guild.scheduled_events.remove(data[:id])
693
- dispatch(:scheduled_event_delete, event)
694
- dispatch(:scheduled_event_cancel, event)
695
- when "GUILD_SCHEDULED_EVENT_USER_ADD"
696
- logger.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
697
- dispatch(:scheduled_event_user_add, ScheduledEventUserEvent.new(self, data))
698
- when "GUILD_SCHEDULED_EVENT_USER_REMOVE"
699
- logger.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
700
- dispatch(:scheduled_event_user_remove, ScheduledEventUserEvent.new(self, data))
701
- when "AUTO_MODERATION_ACTION_EXECUTION"
702
- dispatch(:auto_moderation_action_execution, AutoModerationActionExecutionEvent.new(self, data))
703
- when "AUTO_MODERATION_RULE_CREATE"
704
- dispatch(:auto_moderation_rule_create, AutoModRule.new(self, data))
705
- when "AUTO_MODERATION_RULE_UPDATE"
706
- dispatch(:auto_moderation_rule_update, AutoModRule.new(self, data))
707
- when "AUTO_MODERATION_RULE_DELETE"
708
- dispatch(:auto_moderation_rule_delete, AutoModRule.new(self, data))
709
- else
710
- if respond_to?("event_" + event_name.downcase)
711
- __send__("event_" + event_name.downcase, data)
712
- else
713
- logger.debug "Unhandled event: #{event_name}\n#{data.inspect}"
714
- end
715
- end
716
- end
717
-
718
- def ready
719
- Async do
720
- if @fetch_member
721
- logger.debug "Fetching members"
722
- barrier = Async::Barrier.new
723
-
724
- @guilds.each do |guild|
725
- barrier.async(parent: barrier) do
726
- guild.fetch_members
727
- end
728
- end
729
- barrier.wait
730
- end
731
- @ready = true
732
-
733
- if self.shard
734
- logger.info("Shard #{shard_id} is ready!")
735
- self.shard&.tap do |shard|
736
- if shard.next_shard
737
- dispatch(:shard_standby, shard)
738
- shard.next_shard.tap do |next_shard|
739
- logger.debug("Starting shard #{next_shard.id}")
740
- next_shard.start
741
- end
742
- else
743
- logger.info("All shards are ready!")
744
- dispatch(:standby)
745
- end
746
- end
747
- else
748
- logger.info("Client is ready!")
749
- dispatch(:standby)
750
- end
751
- end
752
- end
753
- end
754
-
755
- #
756
- # A class for connecting websocket with raw bytes data.
757
- # @private
758
- #
759
- class RawConnection < Async::WebSocket::Connection
760
- def initialize(*, **)
761
- super
762
- @closed = false
763
- end
764
-
765
- def inspect
766
- "<#{self.class.name} #{io.fileno}>"
767
- end
768
-
769
- def closed?
770
- @closed
771
- end
772
-
773
- def close
774
- super
775
- @closed = true
776
- rescue StandardError
777
- force_close
778
- end
779
-
780
- def force_close
781
- io.close
782
- @closed = true
783
- end
784
-
785
- def io
786
- @framer
787
- .instance_variable_get(:@stream)
788
- .instance_variable_get(:@io)
789
- .instance_variable_get(:@io)
790
- .instance_variable_get(:@io)
791
- end
792
-
793
- def parse(buffer)
794
- # noop
795
- buffer.to_s
796
- end
797
-
798
- def dump(object)
799
- # noop
800
- object.to_s
801
- end
802
- end
803
- end
804
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "async/http"
4
+ require "async/websocket"
5
+ require "async/barrier"
6
+ require "json"
7
+ require "zlib"
8
+
9
+ module Discorb
10
+ #
11
+ # A module for Discord Gateway.
12
+ # This module is internal use only.
13
+ #
14
+ module Gateway
15
+ #
16
+ # A module to handle gateway events.
17
+ #
18
+ module Handler
19
+ # @type instance: Discorb::Client
20
+
21
+ private
22
+
23
+ def connect_gateway(reconnect)
24
+ Async do
25
+ @mutex["gateway_#{shard_id}"] ||= Mutex.new
26
+ @mutex["gateway_#{shard_id}"].synchronize do
27
+ if reconnect
28
+ logger.info "Reconnecting to gateway..."
29
+ else
30
+ logger.info "Connecting to gateway..."
31
+ end
32
+
33
+ @http = HTTP.new(self)
34
+ gateway_url =
35
+ if reconnect
36
+ @resume_gateway_url
37
+ else
38
+ _, gateway_response =
39
+ @http.request(Route.new("/gateway", "//gateway", :get)).wait
40
+ gateway_response[:url]
41
+ end
42
+ gateway_version = 10
43
+ endpoint =
44
+ Async::HTTP::Endpoint.parse(
45
+ "#{gateway_url}?v=#{gateway_version}&encoding=json&compress=zlib-stream&_=#{Time.now.to_i}",
46
+ alpn_protocols: Async::HTTP::Protocol::HTTP11.names
47
+ )
48
+ begin
49
+ reconnect_count = 0
50
+ begin
51
+ self.connection =
52
+ Async::WebSocket::Client.connect(
53
+ endpoint,
54
+ headers: [["User-Agent", Discorb::USER_AGENT]],
55
+ handler: RawConnection
56
+ )
57
+ rescue Async::WebSocket::ProtocolError => e
58
+ raise if reconnect_count > 3
59
+
60
+ logger.info "Failed to connect to gateway, retrying...: #{e.message}"
61
+ reconnect_count += 1
62
+ sleep 2**reconnect_count
63
+ retry
64
+ end
65
+ con = connection
66
+ zlib_stream = Zlib::Inflate.new(Zlib::MAX_WBITS)
67
+ buffer = +""
68
+ begin
69
+ while (message = con.read)
70
+ buffer << message
71
+ unless message.end_with?(
72
+ (+"\x00\x00\xff\xff").force_encoding("ASCII-8BIT")
73
+ )
74
+ next
75
+ end
76
+ begin
77
+ data = zlib_stream.inflate(buffer)
78
+ buffer = +""
79
+ message = JSON.parse(data, symbolize_names: true)
80
+ rescue JSON::ParserError
81
+ buffer = +""
82
+ logger.error "Received invalid JSON from gateway."
83
+ logger.debug data.to_s
84
+ else
85
+ handle_gateway(message, reconnect)
86
+ end
87
+ end
88
+ rescue Async::Wrapper::Cancelled,
89
+ OpenSSL::SSL::SSLError,
90
+ Async::Wrapper::WaitError,
91
+ IOError => e
92
+ next if @status == :closed
93
+
94
+ logger.info "Gateway connection closed, reconnecting: #{e.class}: #{e.message}"
95
+ con.force_close
96
+ connect_gateway(true)
97
+ next
98
+ end
99
+ rescue Protocol::WebSocket::ClosedError => e
100
+ @tasks.map(&:stop)
101
+ case e.code
102
+ when 4004
103
+ raise ClientError.new("Authentication failed"), cause: nil
104
+ when 4009
105
+ logger.info "Session timed out, reconnecting."
106
+ con.force_close
107
+ connect_gateway(true)
108
+ next
109
+ when 4014
110
+ raise ClientError.new("Disallowed intents were specified"),
111
+ cause: nil
112
+ when 4001, 4002, 4003, 4005, 4007
113
+ raise ClientError.new(<<~ERROR), cause: e
114
+ Disconnected from gateway, probably due to library issues.
115
+ #{e.message}
116
+
117
+ Please report this to the library issue tracker.
118
+ https://github.com/discorb-lib/discorb/issues
119
+ ERROR
120
+ when 1001
121
+ logger.info "Gateway closed with code 1001, reconnecting."
122
+ con.force_close
123
+ connect_gateway(true)
124
+ next
125
+ else
126
+ logger.error "Discord WebSocket closed with code #{e.code}."
127
+ logger.debug e.message.to_s
128
+ con.force_close
129
+ connect_gateway(false)
130
+ next
131
+ end
132
+ rescue StandardError => e
133
+ logger.error "Discord WebSocket error: #{e.full_message}"
134
+ con.force_close
135
+ connect_gateway(false)
136
+ next
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ def send_gateway(opcode, **value)
143
+ if @shards.any? && shard.nil?
144
+ @shards.map(&:connection)
145
+ else
146
+ [connection]
147
+ end.each do |con|
148
+ con.write({ op: opcode, d: value }.to_json)
149
+ con.flush
150
+ end
151
+ logger.debug "Sent message to fd #{connection.io.fileno}: #{
152
+ { op: opcode, d: value }.to_json.gsub(@token, "[Token]")
153
+ }"
154
+ end
155
+
156
+ def handle_gateway(payload, reconnect)
157
+ Async do |_task|
158
+ data = payload[:d]
159
+ @last_s = payload[:s] if payload[:s]
160
+ logger.debug "Received message with opcode #{payload[:op]} from gateway."
161
+ logger.debug payload.to_json.gsub(@token, "[Token]").to_s
162
+ case payload[:op]
163
+ when 10
164
+ @heartbeat_interval = data[:heartbeat_interval]
165
+ if reconnect
166
+ payload = { token: @token, session_id: session_id, seq: @last_s }
167
+ send_gateway(6, **payload)
168
+ else
169
+ payload = {
170
+ token: @token,
171
+ intents: @intents.value,
172
+ compress: false,
173
+ properties: {
174
+ "os" => RUBY_PLATFORM,
175
+ "browser" => "discorb",
176
+ "device" => "discorb"
177
+ }
178
+ }
179
+ payload[:shard] = [shard_id, @shard_count] if shard_id
180
+ payload[:presence] = @identify_presence if @identify_presence
181
+ send_gateway(2, **payload)
182
+ end
183
+ when 7
184
+ logger.info "Received opcode 7, stopping tasks"
185
+ @tasks.map(&:stop)
186
+ when 9
187
+ logger.warn "Received opcode 9, closed connection"
188
+ @tasks.map(&:stop)
189
+ if data
190
+ logger.info "Connection is resumable, reconnecting"
191
+ connection.force_close
192
+ connect_gateway(true)
193
+ else
194
+ logger.info "Connection is not resumable, reconnecting with opcode 2"
195
+ connection.force_close
196
+
197
+ sleep(2)
198
+ connect_gateway(false)
199
+ end
200
+ when 11
201
+ logger.debug "Received opcode 11"
202
+ @ping = Time.now.to_f - @heartbeat_before
203
+ when 0
204
+ handle_event(payload[:t], data)
205
+ end
206
+ end
207
+ end
208
+
209
+ def handle_heartbeat
210
+ Async do |_task|
211
+ interval = @heartbeat_interval
212
+ sleep(((interval / 1000.0) - 1) * rand)
213
+ loop do
214
+ unless connection.closed?
215
+ @heartbeat_before = Time.now.to_f
216
+ connection.write({ op: 1, d: @last_s }.to_json)
217
+ connection.flush
218
+ logger.debug "Sent opcode 1."
219
+ logger.debug "Waiting for heartbeat."
220
+ end
221
+ sleep((interval / 1000.0) - 1)
222
+ end
223
+ end
224
+ end
225
+
226
+ def handle_event(event_name, data)
227
+ if @wait_until_ready && !@ready &&
228
+ !%w[READY GUILD_CREATE].include?(event_name)
229
+ return(
230
+ logger.debug "Client isn't ready; event #{event_name} wasn't handled"
231
+ )
232
+ end
233
+
234
+ dispatch(:event_receive, event_name, data)
235
+ logger.debug "Handling event #{event_name}"
236
+ case event_name
237
+ when "READY"
238
+ @api_version = data[:v]
239
+ self.session_id = data[:session_id]
240
+ @user = ClientUser.new(self, data[:user])
241
+ @resume_gateway_url = data[:resume_gateway_url]
242
+ @uncached_guilds = data[:guilds].map { |g| g[:id] }
243
+ ready if (@uncached_guilds == []) || !@intents.guilds
244
+ dispatch(:ready)
245
+
246
+ @tasks << handle_heartbeat
247
+ when "GUILD_CREATE"
248
+ if @uncached_guilds.include?(data[:id])
249
+ Guild.new(self, data, true)
250
+ @uncached_guilds.delete(data[:id])
251
+ if @uncached_guilds == []
252
+ logger.debug "All guilds cached"
253
+ ready
254
+ end
255
+ elsif @guilds.has?(data[:id])
256
+ @guilds[data[:id]].send(:_set_data, data, true)
257
+ dispatch(:guild_available, guild)
258
+ else
259
+ guild = Guild.new(self, data, true)
260
+ dispatch(:guild_join, guild)
261
+ end
262
+ dispatch(:guild_create, @guilds[data[:id]])
263
+ when "MESSAGE_CREATE"
264
+ message = Message.new(self, data)
265
+ dispatch(:message, message)
266
+ when "GUILD_UPDATE"
267
+ if @guilds.has?(data[:id])
268
+ current = @guilds[data[:id]]
269
+ before =
270
+ Guild.new(
271
+ self,
272
+ current.instance_variable_get(:@data).merge(no_cache: true),
273
+ false
274
+ )
275
+ current.send(:_set_data, data, false)
276
+ dispatch(:guild_update, before, current)
277
+ else
278
+ logger.warn "Unknown guild id #{data[:id]}, ignoring"
279
+ end
280
+ when "GUILD_DELETE"
281
+ unless (guild = @guilds.delete(data[:id]))
282
+ return logger.warn "Unknown guild id #{data[:id]}, ignoring"
283
+ end
284
+
285
+ dispatch(:guild_delete, guild)
286
+ if data[:unavailable]
287
+ dispatch(:guild_destroy, guild)
288
+ else
289
+ dispatch(:guild_leave, guild)
290
+ end
291
+ when "GUILD_ROLE_CREATE"
292
+ unless (guild = @guilds[data[:guild_id]])
293
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring"
294
+ end
295
+
296
+ nr = Role.new(@client, guild, data[:role])
297
+ guild.roles[data[:role][:id]] = nr
298
+ dispatch(:role_create, nr)
299
+ when "GUILD_ROLE_UPDATE"
300
+ unless (guild = @guilds[data[:guild_id]])
301
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring"
302
+ end
303
+ unless guild.roles.has?(data[:role][:id])
304
+ return logger.warn "Unknown role id #{data[:role][:id]}, ignoring"
305
+ end
306
+
307
+ current = guild.roles[data[:role][:id]]
308
+ before =
309
+ Role.new(
310
+ @client,
311
+ guild,
312
+ current.instance_variable_get(:@data).update({ no_cache: true })
313
+ )
314
+ current.send(:_set_data, data[:role])
315
+ dispatch(:role_update, before, current)
316
+ when "GUILD_ROLE_DELETE"
317
+ unless (guild = @guilds[data[:guild_id]])
318
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring"
319
+ end
320
+ unless (role = guild.roles.delete(data[:role_id]))
321
+ return logger.warn "Unknown role id #{data[:role_id]}, ignoring"
322
+ end
323
+
324
+ dispatch(:role_delete, role)
325
+ when "CHANNEL_CREATE"
326
+ unless (guild = @guilds[data[:guild_id]])
327
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring"
328
+ end
329
+
330
+ nc = Channel.make_channel(self, data)
331
+ guild.channels[data[:id]] = nc
332
+
333
+ dispatch(:channel_create, nc)
334
+ when "CHANNEL_UPDATE"
335
+ unless (current = @channels[data[:id]])
336
+ return logger.warn "Unknown channel id #{data[:id]}, ignoring"
337
+ end
338
+
339
+ before =
340
+ Channel.make_channel(
341
+ self,
342
+ current.instance_variable_get(:@data),
343
+ no_cache: true
344
+ )
345
+ current.send(:_set_data, data)
346
+ dispatch(:channel_update, before, current)
347
+ when "CHANNEL_DELETE"
348
+ unless (channel = @channels.delete(data[:id]))
349
+ return logger.warn "Unknown channel id #{data[:id]}, ignoring"
350
+ end
351
+
352
+ @guilds[data[:guild_id]]&.channels&.delete(data[:id])
353
+ dispatch(:channel_delete, channel)
354
+ when "CHANNEL_PINS_UPDATE"
355
+ nil # do in MESSAGE_UPDATE
356
+ when "THREAD_CREATE"
357
+ thread = Channel.make_channel(self, data)
358
+
359
+ dispatch(:thread_create, thread)
360
+ if data.key?(:member)
361
+ dispatch(:thread_join, thread)
362
+ else
363
+ dispatch(:thread_new, thread)
364
+ end
365
+ when "THREAD_UPDATE"
366
+ unless (thread = @channels[data[:id]])
367
+ return logger.warn "Unknown thread id #{data[:id]}, ignoring"
368
+ end
369
+
370
+ before =
371
+ Channel.make_channel(
372
+ self,
373
+ thread.instance_variable_get(:@data),
374
+ no_cache: true
375
+ )
376
+ thread.send(:_set_data, data)
377
+ dispatch(:thread_update, before, thread)
378
+ when "THREAD_DELETE"
379
+ unless (thread = @channels.delete(data[:id]))
380
+ return logger.warn "Unknown thread id #{data[:id]}, ignoring"
381
+ end
382
+
383
+ @guilds[data[:guild_id]]&.channels&.delete(data[:id])
384
+ dispatch(:thread_delete, thread)
385
+ when "THREAD_LIST_SYNC"
386
+ data[:threads].each do |raw_thread|
387
+ thread =
388
+ Channel.make_channel(
389
+ self,
390
+ raw_thread.merge(
391
+ {
392
+ member:
393
+ raw_thread[:members].find do |m|
394
+ m[:id] == raw_thread[:id]
395
+ end
396
+ }
397
+ )
398
+ )
399
+ @channels[thread.id] = thread
400
+ end
401
+ when "THREAD_MEMBER_UPDATE"
402
+ unless (thread = @channels[data[:id]])
403
+ return logger.warn "Unknown thread id #{data[:id]}, ignoring"
404
+ end
405
+
406
+ if (member = thread.members[data[:id]])
407
+ old =
408
+ ThreadChannel::Member.new(
409
+ self,
410
+ member.instance_variable_get(:@data),
411
+ data[:guild_id]
412
+ )
413
+ member.send(:_set_data, data)
414
+ else
415
+ old = nil
416
+ member = ThreadChannel::Member.new(self, data, data[:guild_id])
417
+ thread.members[data[:user_id]] = member
418
+ end
419
+ dispatch(:thread_member_update, thread, old, member)
420
+ when "THREAD_MEMBERS_UPDATE"
421
+ unless (thread = @channels[data[:id]])
422
+ return logger.warn "Unknown thread id #{data[:id]}, ignoring"
423
+ end
424
+
425
+ thread.instance_variable_set(:@member_count, data[:member_count])
426
+ members = []
427
+ (data[:added_members] || []).each do |raw_member|
428
+ member =
429
+ ThreadChannel::Member.new(self, raw_member, data[:guild_id])
430
+ thread.members[member.id] = member
431
+ members << member
432
+ end
433
+ removed_members = []
434
+ (data[:removed_member_ids] || []).each do |id|
435
+ removed_members << thread.members.delete(id)
436
+ end
437
+ dispatch(:thread_members_update, thread, members, removed_members)
438
+ when "STAGE_INSTANCE_CREATE"
439
+ instance = StageInstance.new(self, data)
440
+ dispatch(:stage_instance_create, instance)
441
+ when "STAGE_INSTANCE_UPDATE"
442
+ unless (channel = @channels[data[:channel_id]])
443
+ return(
444
+ logger.warn "Unknown channel id #{data[:channel_id]} , ignoring"
445
+ )
446
+ end
447
+ unless (instance = channel.stage_instances[data[:id]])
448
+ return(
449
+ logger.warn "Unknown stage instance id #{data[:id]}, ignoring"
450
+ )
451
+ end
452
+
453
+ old =
454
+ StageInstance.new(
455
+ self,
456
+ instance.instance_variable_get(:@data),
457
+ no_cache: true
458
+ )
459
+ current.send(:_set_data, data)
460
+ dispatch(:stage_instance_update, old, current)
461
+ when "STAGE_INSTANCE_DELETE"
462
+ unless (channel = @channels[data[:channel_id]])
463
+ return(
464
+ logger.warn "Unknown channel id #{data[:channel_id]} , ignoring"
465
+ )
466
+ end
467
+ unless (instance = channel.stage_instances.delete(data[:id]))
468
+ return(
469
+ logger.warn "Unknown stage instance id #{data[:id]}, ignoring"
470
+ )
471
+ end
472
+
473
+ dispatch(:stage_instance_delete, instance)
474
+ when "GUILD_MEMBER_ADD"
475
+ unless (guild = @guilds[data[:guild_id]])
476
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring"
477
+ end
478
+
479
+ nm =
480
+ Member.new(
481
+ self,
482
+ data[:guild_id],
483
+ data[:user].update({ no_cache: true }),
484
+ data
485
+ )
486
+ guild.members[nm.id] = nm
487
+ dispatch(:member_add, nm)
488
+ when "GUILD_MEMBER_UPDATE"
489
+ unless (guild = @guilds[data[:guild_id]])
490
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring"
491
+ end
492
+ unless (nm = guild.members[data[:user][:id]])
493
+ return logger.warn "Unknown member id #{data[:user][:id]}, ignoring"
494
+ end
495
+
496
+ old =
497
+ Member.new(
498
+ self,
499
+ data[:guild_id],
500
+ data[:user],
501
+ data.update({ no_cache: true })
502
+ )
503
+ nm.send(:_set_data, data[:user], data)
504
+ dispatch(:member_update, old, nm)
505
+ when "GUILD_MEMBER_REMOVE"
506
+ unless (guild = @guilds[data[:guild_id]])
507
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring"
508
+ end
509
+ unless (member = guild.members.delete(data[:user][:id]))
510
+ return logger.warn "Unknown member id #{data[:user][:id]}, ignoring"
511
+ end
512
+
513
+ dispatch(:member_remove, member)
514
+ when "GUILD_BAN_ADD"
515
+ unless (guild = @guilds[data[:guild_id]])
516
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring"
517
+ end
518
+
519
+ user =
520
+ if @users.has? data[:user][:id]
521
+ @users[data[:user][:id]]
522
+ else
523
+ User.new(self, data[:user].update({ no_cache: true }))
524
+ end
525
+
526
+ dispatch(:guild_ban_add, guild, user)
527
+ when "GUILD_BAN_REMOVE"
528
+ unless (guild = @guilds[data[:guild_id]])
529
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring"
530
+ end
531
+
532
+ user =
533
+ if @users.has? data[:user][:id]
534
+ @users[data[:user][:id]]
535
+ else
536
+ User.new(self, data[:user].update({ no_cache: true }))
537
+ end
538
+
539
+ dispatch(:guild_ban_remove, guild, user)
540
+ when "GUILD_EMOJIS_UPDATE"
541
+ unless (guild = @guilds[data[:guild_id]])
542
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring"
543
+ end
544
+
545
+ before_emojis = guild.emojis.values.map(&:id).to_set
546
+ data[:emojis].each do |emoji|
547
+ guild.emojis[emoji[:id]] = CustomEmoji.new(self, guild, emoji)
548
+ end
549
+ deleted_emojis = before_emojis - guild.emojis.values.map(&:id).to_set
550
+ deleted_emojis.each { |emoji| guild.emojis.delete(emoji) }
551
+ when "GUILD_INTEGRATIONS_UPDATE"
552
+ dispatch(:guild_integrations_update, @guilds[data[:guild_id]])
553
+ when "INTEGRATION_CREATE"
554
+ dispatch(
555
+ :integration_create,
556
+ Integration.new(self, data, data[:guild_id])
557
+ )
558
+ when "INTEGRATION_UPDATE"
559
+ unless (guild = @guilds[data[:guild_id]])
560
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring"
561
+ end
562
+
563
+ integration = Integration.new(self, data, data[:guild_id])
564
+ dispatch(:integration_update, integration)
565
+ when "INTEGRATION_DELETE"
566
+ unless (guild = @guilds[data[:guild_id]])
567
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring"
568
+ end
569
+
570
+ dispatch(:integration_delete, IntegrationDeleteEvent.new(self, data))
571
+ when "WEBHOOKS_UPDATE"
572
+ dispatch(:webhooks_update, WebhooksUpdateEvent.new(self, data))
573
+ when "INVITE_CREATE"
574
+ dispatch(:invite_create, Invite.new(self, data, true))
575
+ when "INVITE_DELETE"
576
+ dispatch(:invite_delete, InviteDeleteEvent.new(self, data))
577
+ when "VOICE_STATE_UPDATE"
578
+ unless (guild = @guilds[data[:guild_id]])
579
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring"
580
+ end
581
+
582
+ current = guild.voice_states[data[:user_id]]
583
+ if current.nil?
584
+ old = nil
585
+ current = VoiceState.new(self, data)
586
+ guild.voice_states[data[:user_id]] = current
587
+ else
588
+ guild.voice_states.remove(data[:user_id]) if data[:channel_id].nil?
589
+ old = VoiceState.new(self, current.instance_variable_get(:@data))
590
+ current.send(:_set_data, data)
591
+ end
592
+ dispatch(:voice_state_update, old, current)
593
+ if old&.channel != current&.channel
594
+ dispatch(:voice_channel_update, old, current)
595
+ case [old&.channel.nil?, current&.channel.nil?]
596
+ when [true, false]
597
+ dispatch(:voice_channel_connect, current)
598
+ when [false, true]
599
+ dispatch(:voice_channel_disconnect, old)
600
+ when [false, false]
601
+ dispatch(:voice_channel_move, old, current)
602
+ end
603
+ end
604
+ if old&.mute? != current&.mute?
605
+ dispatch(:voice_mute_update, old, current)
606
+ case [old&.mute?, current&.mute?]
607
+ when [false, true]
608
+ dispatch(:voice_mute_enable, current)
609
+ when [true, false]
610
+ dispatch(:voice_mute_disable, old)
611
+ end
612
+ end
613
+ if old&.deaf? != current&.deaf?
614
+ dispatch(:voice_deaf_update, old, current)
615
+ case [old&.deaf?, current&.deaf?]
616
+ when [false, true]
617
+ dispatch(:voice_deaf_enable, current)
618
+ when [true, false]
619
+ dispatch(:voice_deaf_disable, old)
620
+ end
621
+ end
622
+ if old&.self_mute? != current&.self_mute?
623
+ dispatch(:voice_self_mute_update, old, current)
624
+ case [old&.self_mute?, current&.self_mute?]
625
+ when [false, true]
626
+ dispatch(:voice_self_mute_enable, current)
627
+ when [true, false]
628
+ dispatch(:voice_self_mute_disable, old)
629
+ end
630
+ end
631
+ if old&.self_deaf? != current&.self_deaf?
632
+ dispatch(:voice_self_deaf_update, old, current)
633
+ case [old&.self_deaf?, current&.self_deaf?]
634
+ when [false, true]
635
+ dispatch(:voice_self_deaf_enable, current)
636
+ when [true, false]
637
+ dispatch(:voice_self_deaf_disable, old)
638
+ end
639
+ end
640
+ if old&.server_mute? != current&.server_mute?
641
+ dispatch(:voice_server_mute_update, old, current)
642
+ case [old&.server_mute?, current&.server_mute?]
643
+ when [false, true]
644
+ dispatch(:voice_server_mute_enable, current)
645
+ when [true, false]
646
+ dispatch(:voice_server_mute_disable, old)
647
+ end
648
+ end
649
+ if old&.server_deaf? != current&.server_deaf?
650
+ dispatch(:voice_server_deaf_update, old, current)
651
+ case [old&.server_deaf?, current&.server_deaf?]
652
+ when [false, true]
653
+ dispatch(:voice_server_deaf_enable, current)
654
+ when [true, false]
655
+ dispatch(:voice_server_deaf_disable, old)
656
+ end
657
+ end
658
+ if old&.video? != current&.video?
659
+ dispatch(:voice_video_update, old, current)
660
+ case [old&.video?, current&.video?]
661
+ when [false, true]
662
+ dispatch(:voice_video_start, current)
663
+ when [true, false]
664
+ dispatch(:voice_video_end, old)
665
+ end
666
+ end
667
+ if old&.stream? != current&.stream?
668
+ dispatch(:voice_stream_update, old, current)
669
+ case [old&.stream?, current&.stream?]
670
+ when [false, true]
671
+ dispatch(:voice_stream_start, current)
672
+ when [true, false]
673
+ dispatch(:voice_stream_end, old)
674
+ end
675
+ end
676
+ when "PRESENCE_UPDATE"
677
+ unless (guild = @guilds[data[:guild_id]])
678
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring"
679
+ end
680
+
681
+ guild.presences[data[:user][:id]] = Presence.new(self, data)
682
+ when "MESSAGE_UPDATE"
683
+ if (message = @messages[data[:id]])
684
+ before =
685
+ Message.new(
686
+ self,
687
+ message.instance_variable_get(:@data),
688
+ no_cache: true
689
+ )
690
+ message.send(
691
+ :_set_data,
692
+ message.instance_variable_get(:@data).merge(data)
693
+ )
694
+ else
695
+ before = nil
696
+ message = nil
697
+ end
698
+ if data[:edited_timestamp].nil?
699
+ if message.nil?
700
+ nil
701
+ elsif message.pinned?
702
+ message.instance_variable_set(:@pinned, false)
703
+ else
704
+ message.instance_variable_set(:@pinned, true)
705
+ end
706
+ dispatch(
707
+ :message_pin_update,
708
+ MessagePinEvent.new(self, data, message)
709
+ )
710
+ else
711
+ dispatch(
712
+ :message_update,
713
+ MessageUpdateEvent.new(self, data, before, current)
714
+ )
715
+ end
716
+ when "MESSAGE_DELETE"
717
+ if (message = @messages[data[:id]])
718
+ message.instance_variable_set(:@deleted, true)
719
+ end
720
+
721
+ dispatch(
722
+ :message_delete_id,
723
+ Snowflake.new(data[:id]),
724
+ channels[data[:channel_id]],
725
+ data[:guild_id] && guilds[data[:guild_id]]
726
+ )
727
+ dispatch(
728
+ :message_delete,
729
+ message,
730
+ channels[data[:channel_id]],
731
+ data[:guild_id] && guilds[data[:guild_id]]
732
+ )
733
+ when "MESSAGE_DELETE_BULK"
734
+ messages = []
735
+ data[:ids].each do |id|
736
+ if (message = @messages[id])
737
+ message.instance_variable_set(:@deleted, true)
738
+ messages.push(message)
739
+ else
740
+ messages.push(UnknownDeleteBulkMessage.new(self, id, data))
741
+ end
742
+ end
743
+ dispatch(:message_delete_bulk, messages)
744
+ when "MESSAGE_REACTION_ADD"
745
+ if (target_message = @messages[data[:message_id]])
746
+ if (
747
+ target_reaction =
748
+ target_message.reactions.find do |r|
749
+ if r.emoji.is_a?(UnicodeEmoji)
750
+ r.emoji.value == data[:emoji][:name]
751
+ else
752
+ r.emoji.id == data[:emoji][:id]
753
+ end
754
+ end
755
+ )
756
+ target_reaction.instance_variable_set(
757
+ :@count,
758
+ target_reaction.count + 1
759
+ )
760
+ else
761
+ target_message.reactions << Reaction.new(
762
+ target_message,
763
+ {
764
+ count: 1,
765
+ me: @user.id == data[:user_id],
766
+ emoji: data[:emoji]
767
+ }
768
+ )
769
+ end
770
+ end
771
+ dispatch(:reaction_add, ReactionEvent.new(self, data))
772
+ when "MESSAGE_REACTION_REMOVE"
773
+ if (target_message = @messages[data[:message_id]]) &&
774
+ (
775
+ target_reaction =
776
+ target_message.reactions.find do |r|
777
+ if data[:emoji][:id].nil?
778
+ r.emoji.name == data[:emoji][:name]
779
+ else
780
+ r.emoji.id == data[:emoji][:id]
781
+ end
782
+ end
783
+ )
784
+ target_reaction.instance_variable_set(
785
+ :@count,
786
+ target_reaction.count - 1
787
+ )
788
+ if target_reaction.count.zero?
789
+ target_message.reactions.delete(target_reaction)
790
+ end
791
+ end
792
+ dispatch(:reaction_remove, ReactionEvent.new(self, data))
793
+ when "MESSAGE_REACTION_REMOVE_ALL"
794
+ if (target_message = @messages[data[:message_id]])
795
+ target_message.reactions = []
796
+ end
797
+ dispatch(:reaction_remove_all, ReactionRemoveAllEvent.new(self, data))
798
+ when "MESSAGE_REACTION_REMOVE_EMOJI"
799
+ if (target_message = @messages[data[:message_id]]) &&
800
+ (
801
+ target_reaction =
802
+ target_message.reactions.find do |r|
803
+ if data[:emoji][:id].nil?
804
+ r.name == data[:emoji][:name]
805
+ else
806
+ r.id == data[:emoji][:id]
807
+ end
808
+ end
809
+ )
810
+ target_message.reactions.delete(target_reaction)
811
+ end
812
+ dispatch(
813
+ :reaction_remove_emoji,
814
+ ReactionRemoveEmojiEvent.new(self, data)
815
+ )
816
+ when "TYPING_START"
817
+ dispatch(:typing_start, TypingStartEvent.new(self, data))
818
+ when "INTERACTION_CREATE"
819
+ interaction = Interaction.make_interaction(self, data)
820
+ dispatch(:interaction_create, interaction)
821
+
822
+ dispatch(interaction.class.event_name, interaction)
823
+ when "RESUMED"
824
+ logger.info("Successfully resumed connection")
825
+ @tasks << handle_heartbeat
826
+ shard ? dispatch(:shard_resumed, shard) : dispatch(:resumed)
827
+ when "GUILD_SCHEDULED_EVENT_CREATE"
828
+ unless (guild = @guilds[data[:guild_id]])
829
+ logger.warn("Unknown guild id #{data[:guild_id]}, ignoring")
830
+ end
831
+ event = ScheduledEvent.new(self, data)
832
+ guild.scheduled_events[data[:id]] = event
833
+ dispatch(:scheduled_event_create, event)
834
+ when "GUILD_SCHEDULED_EVENT_UPDATE"
835
+ unless (guild = @guilds[data[:guild_id]])
836
+ logger.warn("Unknown guild id #{data[:guild_id]}, ignoring")
837
+ end
838
+ unless (event = guild.scheduled_events[data[:id]])
839
+ logger.warn("Unknown scheduled event id #{data[:id]}, ignoring")
840
+ end
841
+ old = event.dup
842
+ event.send(:_set_data, data)
843
+ dispatch(:scheduled_event_update, old, event)
844
+ if old.status == event.status
845
+ dispatch(:scheduled_event_edit, old, event)
846
+ else
847
+ case event.status
848
+ when :active
849
+ dispatch(:scheduled_event_start, event)
850
+ when :completed
851
+ dispatch(:scheduled_event_end, event)
852
+ end
853
+ end
854
+ when "GUILD_SCHEDULED_EVENT_DELETE"
855
+ unless (guild = @guilds[data[:guild_id]])
856
+ logger.warn("Unknown guild id #{data[:guild_id]}, ignoring")
857
+ end
858
+ unless (event = guild.scheduled_events[data[:id]])
859
+ logger.warn("Unknown scheduled event id #{data[:id]}, ignoring")
860
+ end
861
+ guild.scheduled_events.remove(data[:id])
862
+ dispatch(:scheduled_event_delete, event)
863
+ dispatch(:scheduled_event_cancel, event)
864
+ when "GUILD_SCHEDULED_EVENT_USER_ADD"
865
+ unless (guild = @guilds[data[:guild_id]])
866
+ logger.warn("Unknown guild id #{data[:guild_id]}, ignoring")
867
+ end
868
+ dispatch(
869
+ :scheduled_event_user_add,
870
+ ScheduledEventUserEvent.new(self, data)
871
+ )
872
+ when "GUILD_SCHEDULED_EVENT_USER_REMOVE"
873
+ unless (guild = @guilds[data[:guild_id]])
874
+ logger.warn("Unknown guild id #{data[:guild_id]}, ignoring")
875
+ end
876
+ dispatch(
877
+ :scheduled_event_user_remove,
878
+ ScheduledEventUserEvent.new(self, data)
879
+ )
880
+ when "AUTO_MODERATION_ACTION_EXECUTION"
881
+ dispatch(
882
+ :auto_moderation_action_execution,
883
+ AutoModerationActionExecutionEvent.new(self, data)
884
+ )
885
+ when "AUTO_MODERATION_RULE_CREATE"
886
+ dispatch(:auto_moderation_rule_create, AutoModRule.new(self, data))
887
+ when "AUTO_MODERATION_RULE_UPDATE"
888
+ dispatch(:auto_moderation_rule_update, AutoModRule.new(self, data))
889
+ when "AUTO_MODERATION_RULE_DELETE"
890
+ dispatch(:auto_moderation_rule_delete, AutoModRule.new(self, data))
891
+ else
892
+ if respond_to?("event_#{event_name.downcase}")
893
+ __send__("event_#{event_name.downcase}", data)
894
+ else
895
+ logger.debug "Unhandled event: #{event_name}\n#{data.inspect}"
896
+ end
897
+ end
898
+ end
899
+
900
+ def ready
901
+ Async do
902
+ if @fetch_member
903
+ logger.debug "Fetching members"
904
+ barrier = Async::Barrier.new
905
+
906
+ @guilds.each do |guild|
907
+ barrier.async(parent: barrier) { guild.fetch_members }
908
+ end
909
+ barrier.wait
910
+ end
911
+ @ready = true
912
+
913
+ if shard
914
+ logger.info("Shard #{shard_id} is ready!")
915
+ shard&.tap do |shard|
916
+ if shard.next_shard
917
+ dispatch(:shard_standby, shard)
918
+ shard.next_shard.tap do |next_shard|
919
+ logger.debug("Starting shard #{next_shard.id}")
920
+ next_shard.start
921
+ end
922
+ else
923
+ logger.info("All shards are ready!")
924
+ dispatch(:standby)
925
+ end
926
+ end
927
+ else
928
+ logger.info("Client is ready!")
929
+ dispatch(:standby)
930
+ end
931
+ end
932
+ end
933
+ end
934
+
935
+ #
936
+ # A class for connecting websocket with raw bytes data.
937
+ # @private
938
+ #
939
+ class RawConnection < Async::WebSocket::Connection
940
+ def initialize(*, **)
941
+ super
942
+ @closed = false
943
+ end
944
+
945
+ def inspect
946
+ "<#{self.class.name} #{io.fileno}>"
947
+ end
948
+
949
+ def closed?
950
+ @closed
951
+ end
952
+
953
+ def close
954
+ super
955
+ @closed = true
956
+ rescue StandardError
957
+ force_close
958
+ end
959
+
960
+ def force_close
961
+ io.close
962
+ @closed = true
963
+ end
964
+
965
+ def io
966
+ @framer
967
+ .instance_variable_get(:@stream)
968
+ .instance_variable_get(:@io)
969
+ .instance_variable_get(:@io)
970
+ .instance_variable_get(:@io)
971
+ end
972
+
973
+ def parse(buffer)
974
+ # noop
975
+ buffer.to_s
976
+ end
977
+
978
+ def dump(object)
979
+ # noop
980
+ object.to_s
981
+ end
982
+ end
983
+ end
984
+ end