discorb 0.15.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +1 -0
  3. data/.github/workflows/build_main.yml +2 -2
  4. data/.github/workflows/build_version.yml +1 -1
  5. data/.github/workflows/codeql-analysis.yml +1 -1
  6. data/.github/workflows/lint-push.yml +3 -5
  7. data/.github/workflows/lint.yml +1 -1
  8. data/.github/workflows/spec.yml +30 -0
  9. data/.lefthook/commit-msg/validator.rb +5 -0
  10. data/.rspec +2 -0
  11. data/.rspec_parallel +2 -0
  12. data/.rubocop.yml +49 -8
  13. data/Changelog.md +32 -1
  14. data/Gemfile +14 -8
  15. data/Rakefile +46 -25
  16. data/bin/console +3 -3
  17. data/docs/Examples.md +1 -1
  18. data/docs/application_command.md +138 -46
  19. data/docs/cli/irb.md +2 -2
  20. data/docs/cli/new.md +14 -9
  21. data/docs/cli/run.md +7 -11
  22. data/docs/cli.md +17 -10
  23. data/docs/events.md +257 -193
  24. data/docs/extension.md +1 -2
  25. data/docs/faq.md +0 -1
  26. data/docs/tutorial.md +12 -12
  27. data/docs/voice_events.md +106 -106
  28. data/examples/commands/message.rb +63 -0
  29. data/examples/commands/permission.rb +18 -0
  30. data/examples/commands/slash.rb +44 -0
  31. data/examples/commands/user.rb +51 -0
  32. data/examples/components/authorization_button.rb +2 -2
  33. data/examples/components/select_menu.rb +2 -2
  34. data/examples/extension/main.rb +1 -1
  35. data/examples/extension/message_expander.rb +5 -2
  36. data/examples/simple/eval.rb +2 -2
  37. data/examples/simple/ping_pong.rb +1 -1
  38. data/examples/simple/rolepanel.rb +2 -2
  39. data/examples/simple/shard.rb +17 -0
  40. data/examples/simple/wait_for_message.rb +1 -1
  41. data/exe/discorb +31 -16
  42. data/lefthook.yml +45 -0
  43. data/lib/discorb/allowed_mentions.rb +8 -0
  44. data/lib/discorb/app_command/command.rb +184 -60
  45. data/lib/discorb/app_command/common.rb +25 -0
  46. data/lib/discorb/app_command/handler.rb +116 -34
  47. data/lib/discorb/app_command.rb +2 -1
  48. data/lib/discorb/application.rb +17 -7
  49. data/lib/discorb/asset.rb +10 -2
  50. data/lib/discorb/attachment.rb +17 -2
  51. data/lib/discorb/audit_logs.rb +53 -12
  52. data/lib/discorb/channel/base.rb +108 -0
  53. data/lib/discorb/channel/category.rb +32 -0
  54. data/lib/discorb/channel/container.rb +44 -0
  55. data/lib/discorb/channel/dm.rb +28 -0
  56. data/lib/discorb/channel/guild.rb +245 -0
  57. data/lib/discorb/channel/stage.rb +140 -0
  58. data/lib/discorb/channel/text.rb +345 -0
  59. data/lib/discorb/channel/thread.rb +321 -0
  60. data/lib/discorb/channel/voice.rb +79 -0
  61. data/lib/discorb/channel.rb +2 -1126
  62. data/lib/discorb/client.rb +160 -64
  63. data/lib/discorb/common.rb +18 -3
  64. data/lib/discorb/components/button.rb +7 -7
  65. data/lib/discorb/components/select_menu.rb +6 -18
  66. data/lib/discorb/components/text_input.rb +12 -2
  67. data/lib/discorb/components.rb +1 -1
  68. data/lib/discorb/dictionary.rb +2 -0
  69. data/lib/discorb/embed.rb +55 -14
  70. data/lib/discorb/emoji.rb +59 -5
  71. data/lib/discorb/emoji_table.rb +4970 -4
  72. data/lib/discorb/error.rb +7 -1
  73. data/lib/discorb/event.rb +56 -21
  74. data/lib/discorb/exe/about.rb +1 -0
  75. data/lib/discorb/exe/irb.rb +2 -4
  76. data/lib/discorb/exe/new.rb +95 -28
  77. data/lib/discorb/exe/run.rb +9 -37
  78. data/lib/discorb/exe/setup.rb +25 -12
  79. data/lib/discorb/exe/show.rb +4 -3
  80. data/lib/discorb/extend.rb +1 -0
  81. data/lib/discorb/extension.rb +6 -3
  82. data/lib/discorb/flag.rb +11 -0
  83. data/lib/discorb/gateway.rb +312 -169
  84. data/lib/discorb/gateway_requests.rb +4 -7
  85. data/lib/discorb/guild.rb +255 -89
  86. data/lib/discorb/guild_template.rb +34 -7
  87. data/lib/discorb/http.rb +23 -11
  88. data/lib/discorb/integration.rb +27 -9
  89. data/lib/discorb/intents.rb +8 -8
  90. data/lib/discorb/interaction/autocomplete.rb +31 -19
  91. data/lib/discorb/interaction/command.rb +70 -17
  92. data/lib/discorb/interaction/components.rb +20 -4
  93. data/lib/discorb/interaction/modal.rb +0 -1
  94. data/lib/discorb/interaction/response.rb +73 -22
  95. data/lib/discorb/interaction/root.rb +29 -14
  96. data/lib/discorb/interaction.rb +1 -0
  97. data/lib/discorb/invite.rb +16 -9
  98. data/lib/discorb/member.rb +46 -5
  99. data/lib/discorb/message.rb +56 -15
  100. data/lib/discorb/message_meta.rb +39 -9
  101. data/lib/discorb/modules.rb +56 -14
  102. data/lib/discorb/permission.rb +14 -5
  103. data/lib/discorb/presence.rb +43 -10
  104. data/lib/discorb/rate_limit.rb +13 -3
  105. data/lib/discorb/reaction.rb +10 -4
  106. data/lib/discorb/role.rb +31 -4
  107. data/lib/discorb/shard.rb +74 -0
  108. data/lib/discorb/sticker.rb +30 -21
  109. data/lib/discorb/user.rb +13 -1
  110. data/lib/discorb/utils/colored_puts.rb +1 -0
  111. data/lib/discorb/voice_state.rb +30 -8
  112. data/lib/discorb/webhook.rb +88 -25
  113. data/lib/discorb.rb +10 -6
  114. data/po/yard.pot +9 -9
  115. data/sig/discorb.rbs +7232 -5837
  116. metadata +23 -6
  117. data/examples/commands/bookmarker.rb +0 -42
  118. data/examples/commands/hello.rb +0 -10
  119. data/examples/commands/inspect.rb +0 -25
  120. data/lib/discorb/log.rb +0 -81
@@ -14,12 +14,22 @@ module Discorb
14
14
  module Gateway
15
15
  #
16
16
  # Represents an event.
17
+ # @abstract
17
18
  #
18
19
  class GatewayEvent
20
+ #
21
+ # Initializes a new instance of the GatewayEvent class.
19
22
  # @private
23
+ #
24
+ # @param [Hash] data The data of the event.
25
+ #
20
26
  def initialize(data)
21
27
  @data = data
22
28
  end
29
+
30
+ def inspect
31
+ "#<#{self.class}>"
32
+ end
23
33
  end
24
34
 
25
35
  #
@@ -61,7 +71,13 @@ module Discorb
61
71
  alias reactor fired_by
62
72
  alias from fired_by
63
73
 
74
+ #
75
+ # Initializes a new instance of the ReactionEvent class.
64
76
  # @private
77
+ #
78
+ # @param [Discorb::Client] client The client that instantiated the object.
79
+ # @param [Hash] data The data of the event.
80
+ #
65
81
  def initialize(client, data)
66
82
  @client = client
67
83
  @data = data
@@ -80,7 +96,8 @@ module Discorb
80
96
 
81
97
  unless @guild.nil?
82
98
  @member = if data.key?(:member)
83
- @guild.members[data[:member][:user][:id]] || Member.new(@client, @guild_id, data[:member][:user], data[:member])
99
+ @guild.members[data[:member][:user][:id]] || Member.new(@client, @guild_id, data[:member][:user],
100
+ data[:member])
84
101
  else
85
102
  @guild.members[data[:user_id]]
86
103
  end
@@ -114,6 +131,7 @@ module Discorb
114
131
  class IntegrationDeleteEvent < GatewayEvent
115
132
  # @return [Discorb::Snowflake] The ID of the integration.
116
133
  attr_reader :id
134
+
117
135
  # @!attribute [r] guild
118
136
  # @macro client_cache
119
137
  # @return [Discorb::Guild] The guild of the integration.
@@ -121,7 +139,14 @@ module Discorb
121
139
  # @macro client_cache
122
140
  # @return [Discorb::User] The user associated with the integration.
123
141
 
142
+ #
143
+ # Initialize a new instance of the IntegrationDeleteEvent class.
124
144
  # @private
145
+ #
146
+ #
147
+ # @param [Hash] data The data of the event.
148
+ #
149
+ #
125
150
  def initialize(_client, data)
126
151
  @id = Snowflake.new(data[:id])
127
152
  @guild_id = data[:guild_id]
@@ -157,7 +182,13 @@ module Discorb
157
182
  # @return [Discorb::Message] The message the reaction was sent in.
158
183
  attr_reader :message
159
184
 
185
+ #
186
+ # Initialize a new instance of the ReactionRemoveAllEvent class.
160
187
  # @private
188
+ #
189
+ # @param [Discorb::Client] client The client that instantiated the object.
190
+ # @param [Hash] data The data of the event.
191
+ #
161
192
  def initialize(client, data)
162
193
  @client = client
163
194
  @data = data
@@ -207,7 +238,13 @@ module Discorb
207
238
  # @return [Discorb::UnicodeEmoji, Discorb::PartialEmoji] The emoji that was reacted with.
208
239
  attr_reader :emoji
209
240
 
241
+ #
242
+ # Initialize a new instance of the ReactionRemoveEmojiEvent class.
210
243
  # @private
244
+ #
245
+ # @param [Discorb::Client] client The client that instantiated the object.
246
+ # @param [Hash] data The data of the event.
247
+ #
211
248
  def initialize(client, data)
212
249
  @client = client
213
250
  @data = data
@@ -246,7 +283,14 @@ module Discorb
246
283
  attr_reader :guild
247
284
  # @return [Discorb::ScheduledEvent] The scheduled event.
248
285
  attr_reader :scheduled_event
286
+
287
+ #
288
+ # Initialize a new instance of the ScheduledEventUserEvent class.
249
289
  # @private
290
+ #
291
+ # @param [Discorb::Client] client The client that instantiated the object.
292
+ # @param [Hash] data The data of the event.
293
+ #
250
294
  def initialize(client, data)
251
295
  @client = client
252
296
  @scheduled_event_id = Snowflake.new(data[:scheduled_event_id])
@@ -342,7 +386,13 @@ module Discorb
342
386
  # @macro client_cache
343
387
  # @return [Discorb::Guild] The guild the message was sent in.
344
388
 
389
+ #
390
+ # Initialize a new instance of the UnknownDeleteBulkMessage class.
345
391
  # @private
392
+ #
393
+ # @param [Discorb::Client] client The client that instantiated the object.
394
+ # @param [Hash] data The data of the event.
395
+ #
346
396
  def initialize(client, id, data)
347
397
  @client = client
348
398
  @id = id
@@ -374,7 +424,13 @@ module Discorb
374
424
  # @macro client_cache
375
425
  # @return [Discorb::Guild] The guild the message was sent in.
376
426
 
427
+ #
428
+ # Initialize a new instance of the InviteDeleteEvent class.
377
429
  # @private
430
+ #
431
+ # @param [Discorb::Client] client The client that instantiated the object.
432
+ # @param [Hash] data The data of the event.
433
+ #
378
434
  def initialize(client, data)
379
435
  @client = client
380
436
  @data = data
@@ -392,21 +448,6 @@ module Discorb
392
448
  end
393
449
  end
394
450
 
395
- #
396
- # Represents a `GUILD_INTEGRATIONS_UPDATE` event.
397
- #
398
- class GuildIntegrationsUpdateEvent < GatewayEvent
399
- def initialize(client, data)
400
- @client = client
401
- @data = data
402
- @guild_id = Snowflake.new(data[:guild_id])
403
- end
404
-
405
- def guild
406
- @client.guilds[@guild_id]
407
- end
408
- end
409
-
410
451
  #
411
452
  # Represents a `TYPING_START` event.
412
453
  #
@@ -430,7 +471,13 @@ module Discorb
430
471
  # @macro client_cache
431
472
  # @return [Discorb::Member, Discorb::User] The member or user that started typing.
432
473
 
474
+ #
475
+ # Initialize a new instance of the TypingStartEvent class.
433
476
  # @private
477
+ #
478
+ # @param [Discorb::Client] client The client that instantiated the object.
479
+ # @param [Hash] data The data of the event.
480
+ #
434
481
  def initialize(client, data)
435
482
  @client = client
436
483
  @data = data
@@ -438,7 +485,14 @@ module Discorb
438
485
  @guild_id = Snowflake.new(data[:guild_id]) if data.key?(:guild_id)
439
486
  @user_id = Snowflake.new(data[:user_id])
440
487
  @timestamp = Time.at(data[:timestamp])
441
- @member = guild.members[@user_id] || Member.new(@client, @guild_id, @client.users[@user_id].instance_variable_get(:@data), data[:member]) if guild
488
+ if guild
489
+ @member = guild.members[@user_id] || Member.new(
490
+ @client,
491
+ @guild_id,
492
+ @client.users[@user_id].instance_variable_get(:@data),
493
+ data[:member]
494
+ )
495
+ end
442
496
  end
443
497
 
444
498
  def user
@@ -507,7 +561,13 @@ module Discorb
507
561
  # @macro client_cache
508
562
  # @return [Discorb::Guild] The guild where the webhook was updated.
509
563
 
564
+ #
565
+ # Initialize a new instance of the WebhooksUpdateEvent class.
510
566
  # @private
567
+ #
568
+ # @param [Discorb::Client] client The client that instantiated the object.
569
+ # @param [Hash] data The data of the event.
570
+ #
511
571
  def initialize(client, data)
512
572
  @client = client
513
573
  @data = data
@@ -531,115 +591,136 @@ module Discorb
531
591
  private
532
592
 
533
593
  def connect_gateway(reconnect)
534
- if reconnect
535
- @log.info "Reconnecting to gateway..."
536
- else
537
- @log.info "Connecting to gateway..."
538
- end
539
594
  Async do
540
- if @connection && !@connection.closed?
541
- Async do
542
- @connection.close
543
- end
544
- end
545
- @http = HTTP.new(self)
546
- _, gateway_response = @http.request(Route.new("/gateway", "//gateway", :get)).wait
547
- gateway_url = gateway_response[:url]
548
- gateway_version = if @intents.to_h[:message_content].nil?
549
- warn "message_content intent not set, using gateway version 9. You should specify `message_content` intent for preventing unexpected changes in the future."
550
- 9
595
+ @mutex["gateway_#{shard_id}"] ||= Mutex.new
596
+ @mutex["gateway_#{shard_id}"].synchronize do
597
+ if reconnect
598
+ logger.info "Reconnecting to gateway..."
551
599
  else
552
- 10
600
+ logger.info "Connecting to gateway..."
553
601
  end
554
- endpoint = Async::HTTP::Endpoint.parse("#{gateway_url}?v=#{gateway_version}&encoding=json&compress=zlib-stream",
555
- alpn_protocols: Async::HTTP::Protocol::HTTP11.names)
556
- begin
557
- @connection = Async::WebSocket::Client.connect(endpoint, headers: [["User-Agent", Discorb::USER_AGENT]], handler: RawConnection)
558
- @zlib_stream = Zlib::Inflate.new(Zlib::MAX_WBITS)
559
- buffer = +""
602
+
603
+ @http = HTTP.new(self)
604
+ _, gateway_response = @http.request(Route.new("/gateway", "//gateway", :get)).wait
605
+ gateway_url = gateway_response[:url]
606
+ gateway_version = if @intents.to_h[:message_content].nil?
607
+ warn "message_content intent not set, using gateway version 9. " \
608
+ "You should specify `message_content` intent for preventing unexpected changes in the future."
609
+ 9
610
+ else
611
+ 10
612
+ end
613
+ endpoint = Async::HTTP::Endpoint.parse(
614
+ "#{gateway_url}?v=#{gateway_version}&encoding=json&compress=zlib-stream&_=#{Time.now.to_i}",
615
+ alpn_protocols: Async::HTTP::Protocol::HTTP11.names,
616
+ )
560
617
  begin
561
- while (message = @connection.read)
562
- buffer << message
563
- if message.end_with?((+"\x00\x00\xff\xff").force_encoding("ASCII-8BIT"))
564
- begin
565
- data = @zlib_stream.inflate(buffer)
566
- buffer = +""
567
- message = JSON.parse(data, symbolize_names: true)
568
- rescue JSON::ParserError
569
- buffer = +""
570
- @log.error "Received invalid JSON from gateway."
571
- @log.debug "#{data}"
572
- else
573
- handle_gateway(message, reconnect)
618
+ self.connection = Async::WebSocket::Client.connect(
619
+ endpoint,
620
+ headers: [["User-Agent", Discorb::USER_AGENT]],
621
+ handler: RawConnection,
622
+ )
623
+ zlib_stream = Zlib::Inflate.new(Zlib::MAX_WBITS)
624
+ buffer = +""
625
+ begin
626
+ while (message = connection.read)
627
+ buffer << message
628
+ if message.end_with?((+"\x00\x00\xff\xff").force_encoding("ASCII-8BIT"))
629
+ begin
630
+ data = zlib_stream.inflate(buffer)
631
+ buffer = +""
632
+ message = JSON.parse(data, symbolize_names: true)
633
+ rescue JSON::ParserError
634
+ buffer = +""
635
+ logger.error "Received invalid JSON from gateway."
636
+ logger.debug "#{data}"
637
+ else
638
+ handle_gateway(message, reconnect)
639
+ end
574
640
  end
575
641
  end
642
+ rescue Async::Wrapper::Cancelled,
643
+ OpenSSL::SSL::SSLError,
644
+ Async::Wrapper::WaitError,
645
+ EOFError,
646
+ Errno::EPIPE,
647
+ Errno::ECONNRESET,
648
+ IOError => e
649
+ next if @status == :closed
650
+
651
+ logger.error "Gateway connection closed accidentally: #{e.class}: #{e.message}"
652
+ connection.force_close
653
+ connect_gateway(true)
654
+ next
576
655
  end
577
- rescue Async::Wrapper::Cancelled,
578
- OpenSSL::SSL::SSLError,
579
- Async::Wrapper::WaitError,
580
- EOFError,
581
- Errno::EPIPE,
582
- Errno::ECONNRESET,
583
- IOError => e
584
- @log.error "Gateway connection closed accidentally: #{e.class}: #{e.message}"
585
- @connection.force_close
586
- connect_gateway(true)
587
- else # should never happen
588
- connect_gateway(true)
589
- end
590
- rescue Protocol::WebSocket::ClosedError => e
591
- @tasks.map(&:stop)
592
- case e.code
593
- when 4004
594
- raise ClientError.new("Authentication failed"), cause: nil
595
- when 4009
596
- @log.info "Session timed out, reconnecting."
597
- connect_gateway(true)
598
- when 4014
599
- raise ClientError.new("Disallowed intents were specified"), cause: nil
600
- when 4002, 4003, 4005, 4007
601
- raise ClientError.new(<<~ERROR), cause: e
602
- Disconnected from gateway, probably due to library issues.
603
- #{e.message}
604
-
605
- Please report this to the library issue tracker.
606
- https://github.com/discorb-lib/discorb/issues
607
- ERROR
608
- when 1001
609
- @log.info "Gateway closed with code 1001, reconnecting."
610
- connect_gateway(true)
611
- else
612
- @log.error "Discord WebSocket closed with code #{e.code}."
613
- @log.debug "#{e.message}"
656
+ rescue Protocol::WebSocket::ClosedError => e
657
+ @tasks.map(&:stop)
658
+ case e.code
659
+ when 4004
660
+ raise ClientError.new("Authentication failed"), cause: nil
661
+ when 4009
662
+ logger.info "Session timed out, reconnecting."
663
+ connection.force_close
664
+ connect_gateway(true)
665
+ next
666
+ when 4014
667
+ raise ClientError.new("Disallowed intents were specified"), cause: nil
668
+ when 4001, 4002, 4003, 4005, 4007
669
+ raise ClientError.new(<<~ERROR), cause: e
670
+ Disconnected from gateway, probably due to library issues.
671
+ #{e.message}
672
+
673
+ Please report this to the library issue tracker.
674
+ https://github.com/discorb-lib/discorb/issues
675
+ ERROR
676
+ when 1001
677
+ logger.info "Gateway closed with code 1001, reconnecting."
678
+ connection.force_close
679
+ connect_gateway(true)
680
+ next
681
+ else
682
+ logger.error "Discord WebSocket closed with code #{e.code}."
683
+ logger.debug "#{e.message}"
684
+ connection.force_close
685
+ connect_gateway(false)
686
+ next
687
+ end
688
+ rescue StandardError => e
689
+ logger.error "Discord WebSocket error: #{e.full_message}"
690
+ connection.force_close
614
691
  connect_gateway(false)
692
+ next
615
693
  end
616
- rescue StandardError => e
617
- @log.error "Discord WebSocket error: #{e.full_message}"
618
- @connection.force_close
619
- connect_gateway(false)
620
694
  end
621
695
  end
622
696
  end
623
697
 
624
698
  def send_gateway(opcode, **value)
625
- @connection.write({ op: opcode, d: value }.to_json)
626
- @connection.flush
627
- @log.debug "Sent message #{{ op: opcode, d: value }.to_json.gsub(@token, "[Token]")}"
699
+ if @shards.any? && shard.nil?
700
+ @shards.map(&:connection)
701
+ else
702
+ [connection]
703
+ end.each do |con|
704
+ con.write({ op: opcode, d: value }.to_json)
705
+ con.flush
706
+ end
707
+ logger.debug "Sent message to fd #{connection.io.fileno}: #{{ op: opcode, d: value }.to_json.gsub(@token,
708
+ "[Token]")}"
628
709
  end
629
710
 
630
711
  def handle_gateway(payload, reconnect)
631
712
  Async do |_task|
632
713
  data = payload[:d]
633
714
  @last_s = payload[:s] if payload[:s]
634
- @log.debug "Received message with opcode #{payload[:op]} from gateway:"
635
- @log.debug "#{payload.to_json.gsub(@token, "[Token]")}"
715
+ logger.debug "Received message with opcode #{payload[:op]} from gateway."
716
+ logger.debug "#{payload.to_json.gsub(@token, "[Token]")}"
636
717
  case payload[:op]
637
718
  when 10
638
719
  @heartbeat_interval = data[:heartbeat_interval]
639
720
  if reconnect
640
721
  payload = {
641
722
  token: @token,
642
- session_id: @session_id,
723
+ session_id: session_id,
643
724
  seq: @last_s,
644
725
  }
645
726
  send_gateway(6, **payload)
@@ -650,27 +731,29 @@ module Discorb
650
731
  compress: false,
651
732
  properties: { "$os" => RUBY_PLATFORM, "$browser" => "discorb", "$device" => "discorb" },
652
733
  }
734
+ payload[:shard] = [shard_id, @shard_count] if shard_id
653
735
  payload[:presence] = @identify_presence if @identify_presence
654
736
  send_gateway(2, **payload)
655
737
  end
656
738
  when 7
657
- @log.info "Received opcode 7, stopping tasks"
739
+ logger.info "Received opcode 7, stopping tasks"
658
740
  @tasks.map(&:stop)
659
741
  when 9
660
- @log.warn "Received opcode 9, closed connection"
742
+ logger.warn "Received opcode 9, closed connection"
661
743
  @tasks.map(&:stop)
662
744
  if data
663
- @log.info "Connection is resumable, reconnecting"
664
- @connection.close
745
+ logger.info "Connection is resumable, reconnecting"
746
+ connection.force_close
665
747
  connect_gateway(true)
666
748
  else
667
- @log.info "Connection is not resumable, reconnecting with opcode 2"
668
- @connection.close
749
+ logger.info "Connection is not resumable, reconnecting with opcode 2"
750
+ connection.force_close
751
+
669
752
  sleep(2)
670
753
  connect_gateway(false)
671
754
  end
672
755
  when 11
673
- @log.debug "Received opcode 11"
756
+ logger.debug "Received opcode 11"
674
757
  @ping = Time.now.to_f - @heartbeat_before
675
758
  when 0
676
759
  handle_event(payload[:t], data)
@@ -683,12 +766,12 @@ module Discorb
683
766
  interval = @heartbeat_interval
684
767
  sleep((interval / 1000.0 - 1) * rand)
685
768
  loop do
686
- unless @connection.closed?
769
+ unless connection.closed?
687
770
  @heartbeat_before = Time.now.to_f
688
- @connection.write({ op: 1, d: @last_s }.to_json)
689
- @connection.flush
690
- @log.debug "Sent opcode 1."
691
- @log.debug "Waiting for heartbeat."
771
+ connection.write({ op: 1, d: @last_s }.to_json)
772
+ connection.flush
773
+ logger.debug "Sent opcode 1."
774
+ logger.debug "Waiting for heartbeat."
692
775
  end
693
776
  sleep(interval / 1000.0 - 1)
694
777
  end
@@ -696,29 +779,34 @@ module Discorb
696
779
  end
697
780
 
698
781
  def handle_event(event_name, data)
699
- return @log.debug "Client isn't ready; event #{event_name} wasn't handled" if @wait_until_ready && !@ready && !%w[READY GUILD_CREATE].include?(event_name)
782
+ return logger.debug "Client isn't ready; event #{event_name} wasn't handled" if @wait_until_ready &&
783
+ !@ready &&
784
+ !%w[
785
+ READY GUILD_CREATE
786
+ ].include?(event_name)
700
787
 
701
788
  dispatch(:event_receive, event_name, data)
702
- @log.debug "Handling event #{event_name}"
789
+ logger.debug "Handling event #{event_name}"
703
790
  case event_name
704
791
  when "READY"
705
792
  @api_version = data[:v]
706
- @session_id = data[:session_id]
793
+ self.session_id = data[:session_id]
707
794
  @user = ClientUser.new(self, data[:user])
708
795
  @uncached_guilds = data[:guilds].map { |g| g[:id] }
709
796
  ready if (@uncached_guilds == []) || !@intents.guilds
710
797
  dispatch(:ready)
798
+
711
799
  @tasks << handle_heartbeat
712
800
  when "GUILD_CREATE"
713
801
  if @uncached_guilds.include?(data[:id])
714
802
  Guild.new(self, data, true)
715
803
  @uncached_guilds.delete(data[:id])
716
804
  if @uncached_guilds == []
717
- @log.debug "All guilds cached"
805
+ logger.debug "All guilds cached"
718
806
  ready
719
807
  end
720
808
  elsif @guilds.has?(data[:id])
721
- @guilds[data[:id]].send(:_set_data, data)
809
+ @guilds[data[:id]].send(:_set_data, data, true)
722
810
  dispatch(:guild_available, guild)
723
811
  else
724
812
  guild = Guild.new(self, data, true)
@@ -735,51 +823,53 @@ module Discorb
735
823
  current.send(:_set_data, data, false)
736
824
  dispatch(:guild_update, before, current)
737
825
  else
738
- @log.warn "Unknown guild id #{data[:id]}, ignoring"
826
+ logger.warn "Unknown guild id #{data[:id]}, ignoring"
739
827
  end
740
828
  when "GUILD_DELETE"
741
- return @log.warn "Unknown guild id #{data[:id]}, ignoring" unless (guild = @guilds.delete(data[:id]))
829
+ return logger.warn "Unknown guild id #{data[:id]}, ignoring" unless (guild = @guilds.delete(data[:id]))
742
830
 
743
831
  dispatch(:guild_delete, guild)
744
- if guild.has?(:unavailable)
832
+ if data[:unavailable]
745
833
  dispatch(:guild_destroy, guild)
746
834
  else
747
835
  dispatch(:guild_leave, guild)
748
836
  end
749
837
  when "GUILD_ROLE_CREATE"
750
- return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
838
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
751
839
 
752
840
  nr = Role.new(@client, guild, data[:role])
753
841
  guild.roles[data[:role][:id]] = nr
754
842
  dispatch(:role_create, nr)
755
843
  when "GUILD_ROLE_UPDATE"
756
- return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
757
- return @log.warn "Unknown role id #{data[:role][:id]}, ignoring" unless guild.roles.has?(data[:role][:id])
844
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
845
+ return logger.warn "Unknown role id #{data[:role][:id]}, ignoring" unless guild.roles.has?(data[:role][:id])
758
846
 
759
847
  current = guild.roles[data[:role][:id]]
760
848
  before = Role.new(@client, guild, current.instance_variable_get(:@data).update({ no_cache: true }))
761
849
  current.send(:_set_data, data[:role])
762
850
  dispatch(:role_update, before, current)
763
851
  when "GUILD_ROLE_DELETE"
764
- return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
765
- return @log.warn "Unknown role id #{data[:role_id]}, ignoring" unless (role = guild.roles.delete(data[:role_id]))
852
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
853
+ unless (role = guild.roles.delete(data[:role_id]))
854
+ return logger.warn "Unknown role id #{data[:role_id]}, ignoring"
855
+ end
766
856
 
767
857
  dispatch(:role_delete, role)
768
858
  when "CHANNEL_CREATE"
769
- return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
859
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
770
860
 
771
861
  nc = Channel.make_channel(self, data)
772
862
  guild.channels[data[:id]] = nc
773
863
 
774
864
  dispatch(:channel_create, nc)
775
865
  when "CHANNEL_UPDATE"
776
- return @log.warn "Unknown channel id #{data[:id]}, ignoring" unless (current = @channels[data[:id]])
866
+ return logger.warn "Unknown channel id #{data[:id]}, ignoring" unless (current = @channels[data[:id]])
777
867
 
778
868
  before = Channel.make_channel(self, current.instance_variable_get(:@data), no_cache: true)
779
869
  current.send(:_set_data, data)
780
870
  dispatch(:channel_update, before, current)
781
871
  when "CHANNEL_DELETE"
782
- return @log.warn "Unknown channel id #{data[:id]}, ignoring" unless (channel = @channels.delete(data[:id]))
872
+ return logger.warn "Unknown channel id #{data[:id]}, ignoring" unless (channel = @channels.delete(data[:id]))
783
873
 
784
874
  @guilds[data[:guild_id]]&.channels&.delete(data[:id])
785
875
  dispatch(:channel_delete, channel)
@@ -795,27 +885,29 @@ module Discorb
795
885
  dispatch(:thread_new, thread)
796
886
  end
797
887
  when "THREAD_UPDATE"
798
- return @log.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels[data[:id]])
888
+ return logger.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels[data[:id]])
799
889
 
800
890
  before = Channel.make_channel(self, thread.instance_variable_get(:@data), no_cache: true)
801
891
  thread.send(:_set_data, data)
802
892
  dispatch(:thread_update, before, thread)
803
893
  when "THREAD_DELETE"
804
- return @log.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels.delete(data[:id]))
894
+ return logger.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels.delete(data[:id]))
805
895
 
806
896
  @guilds[data[:guild_id]]&.channels&.delete(data[:id])
807
897
  dispatch(:thread_delete, thread)
808
898
  when "THREAD_LIST_SYNC"
809
899
  data[:threads].each do |raw_thread|
810
- thread = Channel.make_channel(self, raw_thread.merge({ member: raw_thread[:members].find { |m| m[:id] == raw_thread[:id] } }))
900
+ thread = Channel.make_channel(self, raw_thread.merge({ member: raw_thread[:members].find do |m|
901
+ m[:id] == raw_thread[:id]
902
+ end }))
811
903
  @channels[thread.id] = thread
812
904
  end
813
905
  when "THREAD_MEMBER_UPDATE"
814
- return @log.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels[data[:id]])
906
+ return logger.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels[data[:id]])
815
907
 
816
908
  if (member = thread.members[data[:id]])
817
909
  old = ThreadChannel::Member.new(self, member.instance_variable_get(:@data))
818
- member._set_data(data)
910
+ member.send(:_set_data, data)
819
911
  else
820
912
  old = nil
821
913
  member = ThreadChannel::Member.new(self, data)
@@ -823,7 +915,7 @@ module Discorb
823
915
  end
824
916
  dispatch(:thread_member_update, thread, old, member)
825
917
  when "THREAD_MEMBERS_UPDATE"
826
- return @log.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels[data[:id]])
918
+ return logger.warn "Unknown thread id #{data[:id]}, ignoring" unless (thread = @channels[data[:id]])
827
919
 
828
920
  thread.instance_variable_set(:@member_count, data[:member_count])
829
921
  members = []
@@ -841,37 +933,49 @@ module Discorb
841
933
  instance = StageInstance.new(self, data)
842
934
  dispatch(:stage_instance_create, instance)
843
935
  when "STAGE_INSTANCE_UPDATE"
844
- return @log.warn "Unknown channel id #{data[:channel_id]} , ignoring" unless (channel = @channels[data[:channel_id]])
845
- return @log.warn "Unknown stage instance id #{data[:id]}, ignoring" unless (instance = channel.stage_instances[data[:id]])
936
+ unless (channel = @channels[data[:channel_id]])
937
+ return logger.warn "Unknown channel id #{data[:channel_id]} , ignoring"
938
+ end
939
+ unless (instance = channel.stage_instances[data[:id]])
940
+ return logger.warn "Unknown stage instance id #{data[:id]}, ignoring"
941
+ end
846
942
 
847
943
  old = StageInstance.new(self, instance.instance_variable_get(:@data), no_cache: true)
848
944
  current.send(:_set_data, data)
849
945
  dispatch(:stage_instance_update, old, current)
850
946
  when "STAGE_INSTANCE_DELETE"
851
- return @log.warn "Unknown channel id #{data[:channel_id]} , ignoring" unless (channel = @channels[data[:channel_id]])
852
- return @log.warn "Unknown stage instance id #{data[:id]}, ignoring" unless (instance = channel.stage_instances.delete(data[:id]))
947
+ unless (channel = @channels[data[:channel_id]])
948
+ return logger.warn "Unknown channel id #{data[:channel_id]} , ignoring"
949
+ end
950
+ unless (instance = channel.stage_instances.delete(data[:id]))
951
+ return logger.warn "Unknown stage instance id #{data[:id]}, ignoring"
952
+ end
853
953
 
854
954
  dispatch(:stage_instance_delete, instance)
855
955
  when "GUILD_MEMBER_ADD"
856
- return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
956
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
857
957
 
858
958
  nm = Member.new(self, data[:guild_id], data[:user].update({ no_cache: true }), data)
859
959
  guild.members[nm.id] = nm
860
960
  dispatch(:member_add, nm)
861
961
  when "GUILD_MEMBER_UPDATE"
862
- return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
863
- return @log.warn "Unknown member id #{data[:user][:id]}, ignoring" unless (nm = guild.members[data[:user][:id]])
962
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
963
+ unless (nm = guild.members[data[:user][:id]])
964
+ return logger.warn "Unknown member id #{data[:user][:id]}, ignoring"
965
+ end
864
966
 
865
967
  old = Member.new(self, data[:guild_id], data[:user], data.update({ no_cache: true }))
866
968
  nm.send(:_set_data, data[:user], data)
867
969
  dispatch(:member_update, old, nm)
868
970
  when "GUILD_MEMBER_REMOVE"
869
- return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
870
- return @log.warn "Unknown member id #{data[:user][:id]}, ignoring" unless (member = guild.members.delete(data[:user][:id]))
971
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
972
+ unless (member = guild.members.delete(data[:user][:id]))
973
+ return logger.warn "Unknown member id #{data[:user][:id]}, ignoring"
974
+ end
871
975
 
872
976
  dispatch(:member_remove, member)
873
977
  when "GUILD_BAN_ADD"
874
- return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
978
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
875
979
 
876
980
  user = if @users.has? data[:user][:id]
877
981
  @users[data[:user][:id]]
@@ -881,7 +985,7 @@ module Discorb
881
985
 
882
986
  dispatch(:guild_ban_add, guild, user)
883
987
  when "GUILD_BAN_REMOVE"
884
- return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
988
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
885
989
 
886
990
  user = if @users.has? data[:user][:id]
887
991
  @users[data[:user][:id]]
@@ -891,7 +995,7 @@ module Discorb
891
995
 
892
996
  dispatch(:guild_ban_remove, guild, user)
893
997
  when "GUILD_EMOJIS_UPDATE"
894
- return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
998
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
895
999
 
896
1000
  before_emojis = guild.emojis.values.map(&:id).to_set
897
1001
  data[:emojis].each do |emoji|
@@ -902,17 +1006,16 @@ module Discorb
902
1006
  guild.emojis.delete(emoji)
903
1007
  end
904
1008
  when "GUILD_INTEGRATIONS_UPDATE"
905
- # dispatch(:guild_integrations_update, GuildIntegrationsUpdateEvent.new(self, data))
906
- # Currently not implemented
1009
+ dispatch(:guild_integrations_update, @guilds[data[:guild_id]])
907
1010
  when "INTEGRATION_CREATE"
908
1011
  dispatch(:integration_create, Integration.new(self, data, data[:guild_id]))
909
1012
  when "INTEGRATION_UPDATE"
910
- return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
1013
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
911
1014
 
912
1015
  integration = Integration.new(self, data, data[:guild_id])
913
1016
  dispatch(:integration_update, integration)
914
1017
  when "INTEGRATION_DELETE"
915
- return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
1018
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
916
1019
 
917
1020
  dispatch(:integration_delete, IntegrationDeleteEvent.new(self, data))
918
1021
  when "WEBHOOKS_UPDATE"
@@ -922,7 +1025,7 @@ module Discorb
922
1025
  when "INVITE_DELETE"
923
1026
  dispatch(:invite_delete, InviteDeleteEvent.new(self, data))
924
1027
  when "VOICE_STATE_UPDATE"
925
- return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
1028
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
926
1029
 
927
1030
  current = guild.voice_states[data[:user_id]]
928
1031
  if current.nil?
@@ -1019,7 +1122,7 @@ module Discorb
1019
1122
  end
1020
1123
  end
1021
1124
  when "PRESENCE_UPDATE"
1022
- return @log.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
1125
+ return logger.warn "Unknown guild id #{data[:guild_id]}, ignoring" unless (guild = @guilds[data[:guild_id]])
1023
1126
 
1024
1127
  guild.presences[data[:user][:id]] = Presence.new(self, data)
1025
1128
  when "MESSAGE_UPDATE"
@@ -1045,7 +1148,8 @@ module Discorb
1045
1148
  when "MESSAGE_DELETE"
1046
1149
  message.instance_variable_set(:@deleted, true) if (message = @messages[data[:id]])
1047
1150
 
1048
- dispatch(:message_delete_id, Snowflake.new(data[:id]), channels[data[:channel_id]], data[:guild_id] && guilds[data[:guild_id]])
1151
+ dispatch(:message_delete_id, Snowflake.new(data[:id]), channels[data[:channel_id]],
1152
+ data[:guild_id] && guilds[data[:guild_id]])
1049
1153
  dispatch(:message_delete, message, channels[data[:channel_id]], data[:guild_id] && guilds[data[:guild_id]])
1050
1154
  when "MESSAGE_DELETE_BULK"
1051
1155
  messages = []
@@ -1092,10 +1196,12 @@ module Discorb
1092
1196
  dispatch(:reaction_remove_all, ReactionRemoveAllEvent.new(self, data))
1093
1197
  when "MESSAGE_REACTION_REMOVE_EMOJI"
1094
1198
  if (target_message = @messages[data[:message_id]]) &&
1095
- (target_reaction = target_message.reactions.find { |r| data[:emoji][:id].nil? ? r.name == data[:emoji][:name] : r.id == data[:emoji][:id] })
1199
+ (target_reaction = target_message.reactions.find do |r|
1200
+ data[:emoji][:id].nil? ? r.name == data[:emoji][:name] : r.id == data[:emoji][:id]
1201
+ end)
1096
1202
  target_message.reactions.delete(target_reaction)
1097
1203
  end
1098
- dispatch(:reaction_remove_emoji, ReactionRemoveEmojiEvent.new(data))
1204
+ dispatch(:reaction_remove_emoji, ReactionRemoveEmojiEvent.new(self, data))
1099
1205
  when "TYPING_START"
1100
1206
  dispatch(:typing_start, TypingStartEvent.new(self, data))
1101
1207
  when "INTERACTION_CREATE"
@@ -1104,17 +1210,23 @@ module Discorb
1104
1210
 
1105
1211
  dispatch(interaction.class.event_name, interaction)
1106
1212
  when "RESUMED"
1107
- @log.info("Successfully resumed connection")
1213
+ logger.info("Successfully resumed connection")
1108
1214
  @tasks << handle_heartbeat
1109
- dispatch(:resumed)
1215
+ if shard
1216
+ dispatch(:shard_resumed, shard)
1217
+ else
1218
+ dispatch(:resumed)
1219
+ end
1110
1220
  when "GUILD_SCHEDULED_EVENT_CREATE"
1111
- @log.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
1221
+ logger.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
1112
1222
  event = ScheduledEvent.new(self, data)
1113
1223
  guild.scheduled_events[data[:id]] = event
1114
1224
  dispatch(:scheduled_event_create, event)
1115
1225
  when "GUILD_SCHEDULED_EVENT_UPDATE"
1116
- @log.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
1117
- @log.warn("Unknown scheduled event id #{data[:id]}, ignoring") unless (event = guild.scheduled_events[data[:id]])
1226
+ logger.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
1227
+ unless (event = guild.scheduled_events[data[:id]])
1228
+ logger.warn("Unknown scheduled event id #{data[:id]}, ignoring")
1229
+ end
1118
1230
  old = event.dup
1119
1231
  event.send(:_set_data, data)
1120
1232
  dispatch(:scheduled_event_update, old, event)
@@ -1129,22 +1241,24 @@ module Discorb
1129
1241
  end
1130
1242
  end
1131
1243
  when "GUILD_SCHEDULED_EVENT_DELETE"
1132
- @log.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
1133
- @log.warn("Unknown scheduled event id #{data[:id]}, ignoring") unless (event = guild.scheduled_events[data[:id]])
1244
+ logger.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
1245
+ unless (event = guild.scheduled_events[data[:id]])
1246
+ logger.warn("Unknown scheduled event id #{data[:id]}, ignoring")
1247
+ end
1134
1248
  guild.scheduled_events.remove(data[:id])
1135
1249
  dispatch(:scheduled_event_delete, event)
1136
1250
  dispatch(:scheduled_event_cancel, event)
1137
1251
  when "GUILD_SCHEDULED_EVENT_USER_ADD"
1138
- @log.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
1252
+ logger.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
1139
1253
  dispatch(:scheduled_event_user_add, ScheduledEventUserEvent.new(self, data))
1140
1254
  when "GUILD_SCHEDULED_EVENT_USER_REMOVE"
1141
- @log.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
1255
+ logger.warn("Unknown guild id #{data[:guild_id]}, ignoring") unless (guild = @guilds[data[:guild_id]])
1142
1256
  dispatch(:scheduled_event_user_remove, ScheduledEventUserEvent.new(self, data))
1143
1257
  else
1144
1258
  if respond_to?("event_" + event_name.downcase)
1145
1259
  __send__("event_" + event_name.downcase, data)
1146
1260
  else
1147
- @log.debug "Unhandled event: #{event_name}\n#{data.inspect}"
1261
+ logger.debug "Unhandled event: #{event_name}\n#{data.inspect}"
1148
1262
  end
1149
1263
  end
1150
1264
  end
@@ -1152,7 +1266,7 @@ module Discorb
1152
1266
  def ready
1153
1267
  Async do
1154
1268
  if @fetch_member
1155
- @log.debug "Fetching members"
1269
+ logger.debug "Fetching members"
1156
1270
  barrier = Async::Barrier.new
1157
1271
 
1158
1272
  @guilds.each do |guild|
@@ -1163,8 +1277,25 @@ module Discorb
1163
1277
  barrier.wait
1164
1278
  end
1165
1279
  @ready = true
1166
- dispatch(:standby)
1167
- @log.info("Client is ready!")
1280
+
1281
+ if self.shard
1282
+ logger.info("Shard #{shard_id} is ready!")
1283
+ self.shard&.tap do |shard|
1284
+ if shard.next_shard
1285
+ dispatch(:shard_standby, shard)
1286
+ shard.next_shard.tap do |next_shard|
1287
+ logger.debug("Starting shard #{next_shard.id}")
1288
+ next_shard.start
1289
+ end
1290
+ else
1291
+ logger.info("All shards are ready!")
1292
+ dispatch(:standby)
1293
+ end
1294
+ end
1295
+ else
1296
+ logger.info("Client is ready!")
1297
+ dispatch(:standby)
1298
+ end
1168
1299
  end
1169
1300
  end
1170
1301
  end
@@ -1179,6 +1310,10 @@ module Discorb
1179
1310
  @closed = false
1180
1311
  end
1181
1312
 
1313
+ def inspect
1314
+ "<#{self.class.name} #{io.fileno}>"
1315
+ end
1316
+
1182
1317
  def closed?
1183
1318
  @closed
1184
1319
  end
@@ -1191,10 +1326,18 @@ module Discorb
1191
1326
  end
1192
1327
 
1193
1328
  def force_close
1194
- @framer.instance_variable_get(:@stream).instance_variable_get(:@io).instance_variable_get(:@io).instance_variable_get(:@io).close
1329
+ io.close
1195
1330
  @closed = true
1196
1331
  end
1197
1332
 
1333
+ def io
1334
+ @framer
1335
+ .instance_variable_get(:@stream)
1336
+ .instance_variable_get(:@io)
1337
+ .instance_variable_get(:@io)
1338
+ .instance_variable_get(:@io)
1339
+ end
1340
+
1198
1341
  def parse(buffer)
1199
1342
  # noop
1200
1343
  buffer.to_s