discorb 0.19.0 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
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