discordrb 3.3.0 → 3.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +126 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
- data/.github/pull_request_template.md +37 -0
- data/.rubocop.yml +34 -37
- data/.travis.yml +5 -6
- data/CHANGELOG.md +504 -347
- data/Gemfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +61 -79
- data/Rakefile +2 -0
- data/bin/console +1 -0
- data/discordrb-webhooks.gemspec +6 -6
- data/discordrb.gemspec +18 -18
- data/lib/discordrb/allowed_mentions.rb +36 -0
- data/lib/discordrb/api/channel.rb +62 -39
- data/lib/discordrb/api/invite.rb +3 -3
- data/lib/discordrb/api/server.rb +57 -50
- data/lib/discordrb/api/user.rb +9 -8
- data/lib/discordrb/api/webhook.rb +6 -6
- data/lib/discordrb/api.rb +40 -15
- data/lib/discordrb/await.rb +0 -1
- data/lib/discordrb/bot.rb +175 -73
- data/lib/discordrb/cache.rb +4 -2
- data/lib/discordrb/colour_rgb.rb +43 -0
- data/lib/discordrb/commands/command_bot.rb +30 -9
- data/lib/discordrb/commands/container.rb +20 -23
- data/lib/discordrb/commands/parser.rb +18 -18
- data/lib/discordrb/commands/rate_limiter.rb +3 -2
- data/lib/discordrb/container.rb +77 -17
- data/lib/discordrb/data/activity.rb +271 -0
- data/lib/discordrb/data/application.rb +50 -0
- data/lib/discordrb/data/attachment.rb +56 -0
- data/lib/discordrb/data/audit_logs.rb +345 -0
- data/lib/discordrb/data/channel.rb +849 -0
- data/lib/discordrb/data/embed.rb +251 -0
- data/lib/discordrb/data/emoji.rb +82 -0
- data/lib/discordrb/data/integration.rb +83 -0
- data/lib/discordrb/data/invite.rb +137 -0
- data/lib/discordrb/data/member.rb +297 -0
- data/lib/discordrb/data/message.rb +334 -0
- data/lib/discordrb/data/overwrite.rb +102 -0
- data/lib/discordrb/data/profile.rb +91 -0
- data/lib/discordrb/data/reaction.rb +33 -0
- data/lib/discordrb/data/recipient.rb +34 -0
- data/lib/discordrb/data/role.rb +191 -0
- data/lib/discordrb/data/server.rb +1002 -0
- data/lib/discordrb/data/user.rb +204 -0
- data/lib/discordrb/data/voice_region.rb +45 -0
- data/lib/discordrb/data/voice_state.rb +41 -0
- data/lib/discordrb/data/webhook.rb +145 -0
- data/lib/discordrb/data.rb +25 -4180
- data/lib/discordrb/errors.rb +2 -1
- data/lib/discordrb/events/bans.rb +7 -5
- data/lib/discordrb/events/channels.rb +2 -0
- data/lib/discordrb/events/guilds.rb +16 -9
- data/lib/discordrb/events/invites.rb +125 -0
- data/lib/discordrb/events/members.rb +6 -2
- data/lib/discordrb/events/message.rb +69 -27
- data/lib/discordrb/events/presence.rb +14 -4
- data/lib/discordrb/events/raw.rb +1 -3
- data/lib/discordrb/events/reactions.rb +49 -3
- data/lib/discordrb/events/typing.rb +6 -4
- data/lib/discordrb/events/voice_server_update.rb +47 -0
- data/lib/discordrb/events/voice_state_update.rb +15 -10
- data/lib/discordrb/events/webhooks.rb +9 -6
- data/lib/discordrb/gateway.rb +72 -57
- data/lib/discordrb/id_object.rb +39 -0
- data/lib/discordrb/light/integrations.rb +1 -1
- data/lib/discordrb/light/light_bot.rb +1 -1
- data/lib/discordrb/logger.rb +4 -4
- data/lib/discordrb/paginator.rb +57 -0
- data/lib/discordrb/permissions.rb +103 -8
- data/lib/discordrb/version.rb +1 -1
- data/lib/discordrb/voice/encoder.rb +16 -7
- data/lib/discordrb/voice/network.rb +84 -43
- data/lib/discordrb/voice/sodium.rb +96 -0
- data/lib/discordrb/voice/voice_bot.rb +34 -26
- data/lib/discordrb.rb +73 -0
- metadata +98 -60
- /data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'discordrb/events/generic'
|
4
|
+
require 'discordrb/data'
|
5
|
+
|
6
|
+
module Discordrb::Events
|
7
|
+
# Event raised when a server's voice server is updating.
|
8
|
+
# Sent when initially connecting to voice and when a voice instance fails
|
9
|
+
# over to a new server.
|
10
|
+
# This event is exposed for use with library agnostic interfaces like telecom and
|
11
|
+
# lavalink.
|
12
|
+
class VoiceServerUpdateEvent < Event
|
13
|
+
# @return [String] The voice connection token
|
14
|
+
attr_reader :token
|
15
|
+
|
16
|
+
# @return [Server] The server this update is for.
|
17
|
+
attr_reader :server
|
18
|
+
|
19
|
+
# @return [String] The voice server host.
|
20
|
+
attr_reader :endpoint
|
21
|
+
|
22
|
+
def initialize(data, bot)
|
23
|
+
@bot = bot
|
24
|
+
|
25
|
+
@token = data['token']
|
26
|
+
@endpoint = data['endpoint']
|
27
|
+
@server = bot.server(data['guild_id'])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Event handler for VoiceServerUpdateEvent
|
32
|
+
class VoiceServerUpdateEventHandler < EventHandler
|
33
|
+
def matches?(event)
|
34
|
+
return false unless event.is_a? VoiceServerUpdateEvent
|
35
|
+
|
36
|
+
[
|
37
|
+
matches_all(@attributes[:from], event.server) do |a, e|
|
38
|
+
a == if a.is_a? String
|
39
|
+
e.name
|
40
|
+
else
|
41
|
+
e
|
42
|
+
end
|
43
|
+
end
|
44
|
+
]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -38,37 +38,38 @@ module Discordrb::Events
|
|
38
38
|
|
39
39
|
[
|
40
40
|
matches_all(@attributes[:from], event.user) do |a, e|
|
41
|
-
a ==
|
41
|
+
a == case a
|
42
|
+
when String
|
42
43
|
e.name
|
43
|
-
|
44
|
+
when Integer
|
44
45
|
e.id
|
45
46
|
else
|
46
47
|
e
|
47
48
|
end
|
48
49
|
end,
|
49
50
|
matches_all(@attributes[:mute], event.mute) do |a, e|
|
50
|
-
a == if a.is_a?
|
51
|
+
a == if a.is_a? String
|
51
52
|
e.to_s
|
52
53
|
else
|
53
54
|
e
|
54
55
|
end
|
55
56
|
end,
|
56
57
|
matches_all(@attributes[:deaf], event.deaf) do |a, e|
|
57
|
-
a == if a.is_a?
|
58
|
+
a == if a.is_a? String
|
58
59
|
e.to_s
|
59
60
|
else
|
60
61
|
e
|
61
62
|
end
|
62
63
|
end,
|
63
64
|
matches_all(@attributes[:self_mute], event.self_mute) do |a, e|
|
64
|
-
a == if a.is_a?
|
65
|
+
a == if a.is_a? String
|
65
66
|
e.to_s
|
66
67
|
else
|
67
68
|
e
|
68
69
|
end
|
69
70
|
end,
|
70
71
|
matches_all(@attributes[:self_deaf], event.self_deaf) do |a, e|
|
71
|
-
a == if a.is_a?
|
72
|
+
a == if a.is_a? String
|
72
73
|
e.to_s
|
73
74
|
else
|
74
75
|
e
|
@@ -76,9 +77,11 @@ module Discordrb::Events
|
|
76
77
|
end,
|
77
78
|
matches_all(@attributes[:channel], event.channel) do |a, e|
|
78
79
|
next unless e # Don't bother if the channel is nil
|
79
|
-
|
80
|
+
|
81
|
+
a == case a
|
82
|
+
when String
|
80
83
|
e.name
|
81
|
-
|
84
|
+
when Integer
|
82
85
|
e.id
|
83
86
|
else
|
84
87
|
e
|
@@ -86,9 +89,11 @@ module Discordrb::Events
|
|
86
89
|
end,
|
87
90
|
matches_all(@attributes[:old_channel], event.old_channel) do |a, e|
|
88
91
|
next unless e # Don't bother if the channel is nil
|
89
|
-
|
92
|
+
|
93
|
+
a == case a
|
94
|
+
when String
|
90
95
|
e.name
|
91
|
-
|
96
|
+
when Integer
|
92
97
|
e.id
|
93
98
|
else
|
94
99
|
e
|
@@ -28,28 +28,31 @@ module Discordrb::Events
|
|
28
28
|
|
29
29
|
[
|
30
30
|
matches_all(@attributes[:server], event.server) do |a, e|
|
31
|
-
a ==
|
31
|
+
a == case a
|
32
|
+
when String
|
32
33
|
e.name
|
33
|
-
|
34
|
+
when Integer
|
34
35
|
e.id
|
35
36
|
else
|
36
37
|
e
|
37
38
|
end
|
38
39
|
end,
|
39
40
|
matches_all(@attributes[:channel], event.channel) do |a, e|
|
40
|
-
|
41
|
+
case a
|
42
|
+
when String
|
41
43
|
# Make sure to remove the "#" from channel names in case it was specified
|
42
44
|
a.delete('#') == e.name
|
43
|
-
|
45
|
+
when Integer
|
44
46
|
a == e.id
|
45
47
|
else
|
46
48
|
a == e
|
47
49
|
end
|
48
50
|
end,
|
49
51
|
matches_all(@attributes[:webhook], event) do |a, e|
|
50
|
-
a ==
|
52
|
+
a == case a
|
53
|
+
when String
|
51
54
|
e.name
|
52
|
-
|
55
|
+
when Integer
|
53
56
|
e.id
|
54
57
|
else
|
55
58
|
e
|
data/lib/discordrb/gateway.rb
CHANGED
@@ -25,8 +25,6 @@
|
|
25
25
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
26
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27
27
|
|
28
|
-
require 'thread'
|
29
|
-
|
30
28
|
module Discordrb
|
31
29
|
# Gateway packet opcodes
|
32
30
|
module Opcodes
|
@@ -145,20 +143,19 @@ module Discordrb
|
|
145
143
|
# @return [true, false] whether or not this gateway should check for heartbeat ACKs.
|
146
144
|
attr_accessor :check_heartbeat_acks
|
147
145
|
|
148
|
-
def initialize(bot, token, shard_key = nil, compress_mode = :stream)
|
146
|
+
def initialize(bot, token, shard_key = nil, compress_mode = :stream, intents = nil)
|
149
147
|
@token = token
|
150
148
|
@bot = bot
|
151
149
|
|
152
150
|
@shard_key = shard_key
|
153
151
|
|
154
|
-
@getc_mutex = Mutex.new
|
155
|
-
|
156
152
|
# Whether the connection to the gateway has succeeded yet
|
157
153
|
@ws_success = false
|
158
154
|
|
159
155
|
@check_heartbeat_acks = true
|
160
156
|
|
161
157
|
@compress_mode = compress_mode
|
158
|
+
@intents = intents
|
162
159
|
end
|
163
160
|
|
164
161
|
# Connect to the gateway server in a separate thread
|
@@ -170,8 +167,19 @@ module Discordrb
|
|
170
167
|
end
|
171
168
|
|
172
169
|
LOGGER.debug('WS thread created! Now waiting for confirmation that everything worked')
|
173
|
-
|
174
|
-
|
170
|
+
loop do
|
171
|
+
sleep(0.5)
|
172
|
+
|
173
|
+
if @ws_success
|
174
|
+
LOGGER.debug('Confirmation received! Exiting run.')
|
175
|
+
break
|
176
|
+
end
|
177
|
+
|
178
|
+
if @should_reconnect == false
|
179
|
+
LOGGER.debug('Reconnection flag was unset. Exiting run.')
|
180
|
+
break
|
181
|
+
end
|
182
|
+
end
|
175
183
|
end
|
176
184
|
|
177
185
|
# Prevents all further execution until the websocket thread stops (e.g. through a closed connection).
|
@@ -181,16 +189,16 @@ module Discordrb
|
|
181
189
|
|
182
190
|
# Whether the WebSocket connection to the gateway is currently open
|
183
191
|
def open?
|
184
|
-
@handshake
|
192
|
+
@handshake&.finished? && !@closed
|
185
193
|
end
|
186
194
|
|
187
195
|
# Stops the bot gracefully, disconnecting the websocket without immediately killing the thread. This means that
|
188
196
|
# Discord is immediately aware of the closed connection and makes the bot appear offline instantly.
|
189
197
|
#
|
190
198
|
# If this method doesn't work or you're looking for something more drastic, use {#kill} instead.
|
191
|
-
def stop
|
199
|
+
def stop
|
192
200
|
@should_reconnect = false
|
193
|
-
close
|
201
|
+
close
|
194
202
|
|
195
203
|
# Return nil so command bots don't send a message
|
196
204
|
nil
|
@@ -241,7 +249,7 @@ module Discordrb
|
|
241
249
|
def heartbeat
|
242
250
|
if check_heartbeat_acks
|
243
251
|
unless @last_heartbeat_acked
|
244
|
-
# We're in a bad situation - apparently the last heartbeat wasn't
|
252
|
+
# We're in a bad situation - apparently the last heartbeat wasn't ACK'd, which means the connection is likely
|
245
253
|
# a zombie. Reconnect
|
246
254
|
LOGGER.warn('Last heartbeat was not acked, so this is a zombie connection! Reconnecting')
|
247
255
|
|
@@ -303,6 +311,7 @@ module Discordrb
|
|
303
311
|
compress: compress,
|
304
312
|
large_threshold: large_threshold
|
305
313
|
}
|
314
|
+
data[:intents] = @intents unless @intents.nil?
|
306
315
|
|
307
316
|
# Don't include the shard key at all if it is nil as Discord checks for its mere existence
|
308
317
|
data[:shard] = shard_key if shard_key
|
@@ -312,7 +321,7 @@ module Discordrb
|
|
312
321
|
|
313
322
|
# Sends a status update packet (op 3). This sets the bot user's status (online/idle/...) and game playing/streaming.
|
314
323
|
# @param status [String] The status that should be set (`online`, `idle`, `dnd`, `invisible`).
|
315
|
-
# @param since [Integer] The
|
324
|
+
# @param since [Integer] The Unix timestamp in milliseconds when the status was set. Should only be provided when
|
316
325
|
# `afk` is true.
|
317
326
|
# @param game [Hash<Symbol => Object>, nil] `nil` if no game should be played, or a hash of `:game => "name"` if a
|
318
327
|
# game should be played. The hash can also contain additional attributes for streaming statuses.
|
@@ -360,7 +369,7 @@ module Discordrb
|
|
360
369
|
@instant_reconnect = true
|
361
370
|
@should_reconnect = true
|
362
371
|
|
363
|
-
close
|
372
|
+
close(4000)
|
364
373
|
end
|
365
374
|
|
366
375
|
# Sends a resume packet (op 6). This replays all events from a previous point specified by its packet sequence. This
|
@@ -402,12 +411,12 @@ module Discordrb
|
|
402
411
|
|
403
412
|
# Sends a custom packet over the connection. This can be useful to implement future yet unimplemented functionality
|
404
413
|
# or for testing. You probably shouldn't use this unless you know what you're doing.
|
405
|
-
# @param
|
414
|
+
# @param opcode [Integer] The opcode the packet should be sent as. Can be one of {Opcodes} or a custom value if
|
406
415
|
# necessary.
|
407
|
-
# @param packet [Object] Some arbitrary JSON-
|
408
|
-
def send_packet(
|
416
|
+
# @param packet [Object] Some arbitrary JSON-serializable data that should be sent as the `d` field.
|
417
|
+
def send_packet(opcode, packet)
|
409
418
|
data = {
|
410
|
-
op:
|
419
|
+
op: opcode,
|
411
420
|
d: packet
|
412
421
|
}
|
413
422
|
|
@@ -436,20 +445,18 @@ module Discordrb
|
|
436
445
|
@heartbeat_thread = Thread.new do
|
437
446
|
Thread.current[:discordrb_name] = 'heartbeat'
|
438
447
|
loop do
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
sleep 1
|
448
|
-
end
|
449
|
-
rescue => e
|
450
|
-
LOGGER.error('An error occurred while heartbeating!')
|
451
|
-
LOGGER.log_exception(e)
|
448
|
+
# Send a heartbeat if heartbeats are active and either no session exists yet, or an existing session is
|
449
|
+
# suspended (e.g. after op7)
|
450
|
+
if (@session && !@session.suspended?) || !@session
|
451
|
+
sleep @heartbeat_interval
|
452
|
+
@bot.raise_heartbeat_event
|
453
|
+
heartbeat
|
454
|
+
else
|
455
|
+
sleep 1
|
452
456
|
end
|
457
|
+
rescue StandardError => e
|
458
|
+
LOGGER.error('An error occurred while heartbeating!')
|
459
|
+
LOGGER.log_exception(e)
|
453
460
|
end
|
454
461
|
end
|
455
462
|
end
|
@@ -502,7 +509,7 @@ module Discordrb
|
|
502
509
|
cert_store.set_default_paths
|
503
510
|
ctx.cert_store = cert_store
|
504
511
|
else
|
505
|
-
ctx.set_params ssl_version: :TLSv1_2
|
512
|
+
ctx.set_params ssl_version: :TLSv1_2 # rubocop:disable Naming/VariableNumber
|
506
513
|
end
|
507
514
|
|
508
515
|
socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
|
@@ -567,7 +574,7 @@ module Discordrb
|
|
567
574
|
|
568
575
|
# We're done! Delegate to the websocket loop
|
569
576
|
websocket_loop
|
570
|
-
rescue => e
|
577
|
+
rescue StandardError => e
|
571
578
|
LOGGER.error('An error occurred while connecting to the websocket!')
|
572
579
|
LOGGER.log_exception(e)
|
573
580
|
end
|
@@ -584,13 +591,17 @@ module Discordrb
|
|
584
591
|
unless @socket
|
585
592
|
LOGGER.warn('Socket is nil in websocket_loop! Reconnecting')
|
586
593
|
handle_internal_close('Socket is nil in websocket_loop')
|
594
|
+
next
|
587
595
|
end
|
588
596
|
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
597
|
+
# Get some data from the socket
|
598
|
+
begin
|
599
|
+
recv_data = @socket.readpartial(4096)
|
600
|
+
rescue EOFError
|
601
|
+
@pipe_broken = true
|
602
|
+
handle_internal_close('Socket EOF in websocket_loop')
|
603
|
+
next
|
604
|
+
end
|
594
605
|
|
595
606
|
# Check if we actually got data
|
596
607
|
unless recv_data
|
@@ -621,7 +632,7 @@ module Discordrb
|
|
621
632
|
# If the handshake hasn't finished, handle it
|
622
633
|
handle_handshake_data(recv_data)
|
623
634
|
end
|
624
|
-
rescue => e
|
635
|
+
rescue StandardError => e
|
625
636
|
handle_error(e)
|
626
637
|
end
|
627
638
|
end
|
@@ -645,12 +656,13 @@ module Discordrb
|
|
645
656
|
ZLIB_SUFFIX = "\x00\x00\xFF\xFF".b.freeze
|
646
657
|
|
647
658
|
def handle_message(msg)
|
648
|
-
|
659
|
+
case @compress_mode
|
660
|
+
when :large
|
649
661
|
if msg.byteslice(0) == 'x'
|
650
662
|
# The message is compressed, inflate it
|
651
663
|
msg = Zlib::Inflate.inflate(msg)
|
652
664
|
end
|
653
|
-
|
665
|
+
when :stream
|
654
666
|
# Write deflated string to buffer
|
655
667
|
@zlib_reader << msg
|
656
668
|
|
@@ -703,6 +715,7 @@ module Discordrb
|
|
703
715
|
|
704
716
|
@session = Session.new(data['session_id'])
|
705
717
|
@session.sequence = 0
|
718
|
+
@bot.__send__(:notify_ready) if @intents && (@intents & INTENTS[:servers]).zero?
|
706
719
|
when :RESUMED
|
707
720
|
# The RESUMED event is received after a successful op 6 (resume). It does nothing except tell the bot the
|
708
721
|
# connection is initiated (like READY would). Starting with v5, it doesn't set a new heartbeat interval anymore
|
@@ -750,7 +763,7 @@ module Discordrb
|
|
750
763
|
LOGGER.debug("Trace: #{packet['d']['_trace']}")
|
751
764
|
LOGGER.debug("Session: #{@session.inspect}")
|
752
765
|
|
753
|
-
if @session
|
766
|
+
if @session&.should_resume?
|
754
767
|
# Make sure we're sending heartbeats again
|
755
768
|
@session.resume
|
756
769
|
|
@@ -773,22 +786,31 @@ module Discordrb
|
|
773
786
|
handle_close(e)
|
774
787
|
end
|
775
788
|
|
789
|
+
# Close codes that are unrecoverable, after which we should not try to reconnect.
|
790
|
+
# - 4003: Not authenticated. How did this happen?
|
791
|
+
# - 4004: Authentication failed. Token was wrong, nothing we can do.
|
792
|
+
# - 4011: Sharding required. Currently requires developer intervention.
|
793
|
+
FATAL_CLOSE_CODES = [4003, 4004, 4011].freeze
|
794
|
+
|
776
795
|
def handle_close(e)
|
796
|
+
@bot.__send__(:raise_event, Events::DisconnectEvent.new(@bot))
|
797
|
+
|
777
798
|
if e.respond_to? :code
|
778
799
|
# It is a proper close frame we're dealing with, print reason and message to console
|
779
800
|
LOGGER.error('Websocket close frame received!')
|
780
801
|
LOGGER.error("Code: #{e.code}")
|
781
802
|
LOGGER.error("Message: #{e.data}")
|
803
|
+
@should_reconnect = false if FATAL_CLOSE_CODES.include?(e.code)
|
782
804
|
elsif e.is_a? Exception
|
783
805
|
# Log the exception
|
784
806
|
LOGGER.error('The websocket connection has closed due to an error!')
|
785
807
|
LOGGER.log_exception(e)
|
786
808
|
else
|
787
|
-
LOGGER.error("The websocket connection has closed: #{e
|
809
|
+
LOGGER.error("The websocket connection has closed: #{e&.inspect || '(no information)'}")
|
788
810
|
end
|
789
811
|
end
|
790
812
|
|
791
|
-
def send(data, type = :text)
|
813
|
+
def send(data, type = :text, code = nil)
|
792
814
|
LOGGER.out(data)
|
793
815
|
|
794
816
|
unless @handshaked && !@closed
|
@@ -797,40 +819,33 @@ module Discordrb
|
|
797
819
|
end
|
798
820
|
|
799
821
|
# Create the frame we're going to send
|
800
|
-
frame = ::WebSocket::Frame::Outgoing::Client.new(data: data, type: type, version: @handshake.version)
|
822
|
+
frame = ::WebSocket::Frame::Outgoing::Client.new(data: data, type: type, version: @handshake.version, code: code)
|
801
823
|
|
802
824
|
# Try to send it
|
803
825
|
begin
|
804
826
|
@socket.write frame.to_s
|
805
|
-
rescue => e
|
827
|
+
rescue StandardError => e
|
806
828
|
# There has been an error!
|
807
829
|
@pipe_broken = true
|
808
830
|
handle_internal_close(e)
|
809
831
|
end
|
810
832
|
end
|
811
833
|
|
812
|
-
def close(
|
834
|
+
def close(code = 1000)
|
813
835
|
# If we're already closed, there's no need to do anything - return
|
814
836
|
return if @closed
|
815
837
|
|
816
838
|
# Suspend the session so we don't send heartbeats
|
817
|
-
@session
|
839
|
+
@session&.suspend
|
818
840
|
|
819
841
|
# Send a close frame (if we can)
|
820
|
-
send nil, :close unless @pipe_broken
|
842
|
+
send nil, :close, code unless @pipe_broken
|
821
843
|
|
822
844
|
# We're officially closed, notify the main loop.
|
823
|
-
|
824
|
-
# close afterwards, don't coincide with the main loop reading something from the SSL socket.
|
825
|
-
# This would cause a segfault due to (I suspect) Ruby bug #12292: https://bugs.ruby-lang.org/issues/12292
|
826
|
-
if no_sync
|
827
|
-
@closed = true
|
828
|
-
else
|
829
|
-
@getc_mutex.synchronize { @closed = true }
|
830
|
-
end
|
845
|
+
@closed = true
|
831
846
|
|
832
847
|
# Close the socket if possible
|
833
|
-
@socket
|
848
|
+
@socket&.close
|
834
849
|
@socket = nil
|
835
850
|
|
836
851
|
# Make sure we do necessary things as soon as we're closed
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Discordrb
|
4
|
+
# Mixin for objects that have IDs
|
5
|
+
module IDObject
|
6
|
+
# @return [Integer] the ID which uniquely identifies this object across Discord.
|
7
|
+
attr_reader :id
|
8
|
+
alias_method :resolve_id, :id
|
9
|
+
alias_method :hash, :id
|
10
|
+
|
11
|
+
# ID based comparison
|
12
|
+
def ==(other)
|
13
|
+
Discordrb.id_compare(@id, other)
|
14
|
+
end
|
15
|
+
|
16
|
+
alias_method :eql?, :==
|
17
|
+
|
18
|
+
# Estimates the time this object was generated on based on the beginning of the ID. This is fairly accurate but
|
19
|
+
# shouldn't be relied on as Discord might change its algorithm at any time
|
20
|
+
# @return [Time] when this object was created at
|
21
|
+
def creation_time
|
22
|
+
# Milliseconds
|
23
|
+
ms = (@id >> 22) + DISCORD_EPOCH
|
24
|
+
Time.at(ms / 1000.0)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Creates an artificial snowflake at the given point in time. Useful for comparing against.
|
28
|
+
# @param time [Time] The time the snowflake should represent.
|
29
|
+
# @return [Integer] a snowflake with the timestamp data as the given time
|
30
|
+
def self.synthesise(time)
|
31
|
+
ms = (time.to_f * 1000).to_i
|
32
|
+
(ms - DISCORD_EPOCH) << 22
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
alias_method :synthesize, :synthesise
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -49,7 +49,7 @@ module Discordrb::Light
|
|
49
49
|
# Twitch account connection of the server owner).
|
50
50
|
attr_reader :server_connection
|
51
51
|
|
52
|
-
# @return [Connection] the connection integrated with the server (i.
|
52
|
+
# @return [Connection] the connection integrated with the server (i.e. your connection)
|
53
53
|
attr_reader :integrated_connection
|
54
54
|
|
55
55
|
# @!visibility private
|
@@ -6,7 +6,7 @@ require 'discordrb/api/user'
|
|
6
6
|
require 'discordrb/light/data'
|
7
7
|
require 'discordrb/light/integrations'
|
8
8
|
|
9
|
-
# This module contains classes to allow connections to bots without a connection to the gateway socket, i.
|
9
|
+
# This module contains classes to allow connections to bots without a connection to the gateway socket, i.e. bots
|
10
10
|
# that only use the REST part of the API.
|
11
11
|
module Discordrb::Light
|
12
12
|
# A bot that only uses the REST part of the API. Hierarchically unrelated to the regular {Discordrb::Bot}. Useful to
|
data/lib/discordrb/logger.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Discordrb
|
4
4
|
# The format log timestamps should be in, in strftime format
|
5
|
-
LOG_TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S.%L'
|
5
|
+
LOG_TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S.%L'
|
6
6
|
|
7
7
|
# Logs debug messages
|
8
8
|
class Logger
|
@@ -18,7 +18,7 @@ module Discordrb
|
|
18
18
|
# Creates a new logger.
|
19
19
|
# @param fancy [true, false] Whether this logger uses fancy mode (ANSI escape codes to make the output colourful)
|
20
20
|
# @param streams [Array<IO>, Array<#puts & #flush>] the streams the logger should write to.
|
21
|
-
def initialize(fancy = false, streams = [
|
21
|
+
def initialize(fancy = false, streams = [$stdout])
|
22
22
|
@fancy = fancy
|
23
23
|
self.mode = :normal
|
24
24
|
|
@@ -38,10 +38,10 @@ module Discordrb
|
|
38
38
|
}.freeze
|
39
39
|
|
40
40
|
# The ANSI format code that resets formatting
|
41
|
-
FORMAT_RESET = "\u001B[0m"
|
41
|
+
FORMAT_RESET = "\u001B[0m"
|
42
42
|
|
43
43
|
# The ANSI format code that makes something bold
|
44
|
-
FORMAT_BOLD = "\u001B[1m"
|
44
|
+
FORMAT_BOLD = "\u001B[1m"
|
45
45
|
|
46
46
|
MODES.each do |mode, hash|
|
47
47
|
define_method(mode) do |message|
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Discordrb
|
4
|
+
# Utility class for wrapping paginated endpoints. It is [Enumerable](https://ruby-doc.org/core-2.5.1/Enumerable.html),
|
5
|
+
# similar to an `Array`, so most of the same methods can be used to filter the results of the request
|
6
|
+
# that it wraps. If you simply want an array of all of the results, `#to_a` can be called.
|
7
|
+
class Paginator
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
# Creates a new {Paginator}
|
11
|
+
# @param limit [Integer] the maximum number of items to request before stopping
|
12
|
+
# @param direction [:up, :down] the order in which results are returned in
|
13
|
+
# @yield [Array, nil] the last page of results, or nil if this is the first iteration.
|
14
|
+
# This should be used to request the next page of results.
|
15
|
+
# @yieldreturn [Array] the next page of results
|
16
|
+
def initialize(limit, direction, &block)
|
17
|
+
@count = 0
|
18
|
+
@limit = limit
|
19
|
+
@direction = direction
|
20
|
+
@block = block
|
21
|
+
end
|
22
|
+
|
23
|
+
# Yields every item produced by the wrapped request, until it returns
|
24
|
+
# no more results or the configured `limit` is reached.
|
25
|
+
def each
|
26
|
+
last_page = nil
|
27
|
+
until limit_check
|
28
|
+
page = @block.call(last_page)
|
29
|
+
return if page.empty?
|
30
|
+
|
31
|
+
enumerator = case @direction
|
32
|
+
when :down
|
33
|
+
page.each
|
34
|
+
when :up
|
35
|
+
page.reverse_each
|
36
|
+
end
|
37
|
+
|
38
|
+
enumerator.each do |item|
|
39
|
+
yield item
|
40
|
+
@count += 1
|
41
|
+
break if limit_check
|
42
|
+
end
|
43
|
+
|
44
|
+
last_page = page
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Whether the paginator limit has been exceeded
|
51
|
+
def limit_check
|
52
|
+
return false if @limit.nil?
|
53
|
+
|
54
|
+
@count >= @limit
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|