discordrb 3.3.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +152 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- data/.github/pull_request_template.md +37 -0
- data/.github/workflows/codeql.yml +65 -0
- data/.markdownlint.json +4 -0
- data/.rubocop.yml +39 -36
- data/CHANGELOG.md +874 -552
- data/Gemfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +80 -86
- data/Rakefile +2 -0
- data/bin/console +1 -0
- data/discordrb-webhooks.gemspec +9 -6
- data/discordrb.gemspec +21 -18
- data/lib/discordrb/allowed_mentions.rb +36 -0
- data/lib/discordrb/api/application.rb +202 -0
- data/lib/discordrb/api/channel.rb +236 -47
- data/lib/discordrb/api/interaction.rb +54 -0
- data/lib/discordrb/api/invite.rb +5 -5
- data/lib/discordrb/api/server.rb +94 -66
- data/lib/discordrb/api/user.rb +17 -11
- data/lib/discordrb/api/webhook.rb +63 -6
- data/lib/discordrb/api.rb +55 -16
- data/lib/discordrb/await.rb +0 -1
- data/lib/discordrb/bot.rb +480 -93
- data/lib/discordrb/cache.rb +31 -24
- data/lib/discordrb/colour_rgb.rb +43 -0
- data/lib/discordrb/commands/command_bot.rb +35 -12
- data/lib/discordrb/commands/container.rb +21 -24
- data/lib/discordrb/commands/parser.rb +20 -20
- data/lib/discordrb/commands/rate_limiter.rb +4 -3
- data/lib/discordrb/container.rb +209 -20
- data/lib/discordrb/data/activity.rb +271 -0
- data/lib/discordrb/data/application.rb +50 -0
- data/lib/discordrb/data/attachment.rb +71 -0
- data/lib/discordrb/data/audit_logs.rb +345 -0
- data/lib/discordrb/data/channel.rb +993 -0
- data/lib/discordrb/data/component.rb +229 -0
- data/lib/discordrb/data/embed.rb +251 -0
- data/lib/discordrb/data/emoji.rb +82 -0
- data/lib/discordrb/data/integration.rb +122 -0
- data/lib/discordrb/data/interaction.rb +800 -0
- data/lib/discordrb/data/invite.rb +137 -0
- data/lib/discordrb/data/member.rb +372 -0
- data/lib/discordrb/data/message.rb +414 -0
- data/lib/discordrb/data/overwrite.rb +108 -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 +248 -0
- data/lib/discordrb/data/server.rb +1004 -0
- data/lib/discordrb/data/user.rb +264 -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 +238 -0
- data/lib/discordrb/data.rb +28 -4180
- data/lib/discordrb/errors.rb +46 -4
- data/lib/discordrb/events/bans.rb +7 -5
- data/lib/discordrb/events/channels.rb +3 -1
- data/lib/discordrb/events/guilds.rb +16 -9
- data/lib/discordrb/events/interactions.rb +482 -0
- data/lib/discordrb/events/invites.rb +125 -0
- data/lib/discordrb/events/members.rb +6 -2
- data/lib/discordrb/events/message.rb +72 -27
- data/lib/discordrb/events/presence.rb +35 -18
- data/lib/discordrb/events/raw.rb +1 -3
- data/lib/discordrb/events/reactions.rb +49 -4
- data/lib/discordrb/events/threads.rb +96 -0
- 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 +99 -71
- 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 +159 -39
- data/lib/discordrb/version.rb +1 -1
- data/lib/discordrb/voice/encoder.rb +16 -7
- data/lib/discordrb/voice/network.rb +99 -47
- data/lib/discordrb/voice/sodium.rb +98 -0
- data/lib/discordrb/voice/voice_bot.rb +33 -25
- data/lib/discordrb/webhooks.rb +2 -0
- data/lib/discordrb.rb +107 -1
- metadata +126 -54
- data/.codeclimate.yml +0 -16
- data/.travis.yml +0 -33
- data/bin/travis_build_docs.sh +0 -17
- /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
|
@@ -136,7 +134,7 @@ module Discordrb
|
|
136
134
|
LARGE_THRESHOLD = 100
|
137
135
|
|
138
136
|
# The version of the gateway that's supposed to be used.
|
139
|
-
GATEWAY_VERSION =
|
137
|
+
GATEWAY_VERSION = 9
|
140
138
|
|
141
139
|
# Heartbeat ACKs are Discord's way of verifying on the client side whether the connection is still alive. If this is
|
142
140
|
# set to true (default value) the gateway client will use that functionality to detect zombie connections and
|
@@ -145,20 +143,22 @@ 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
|
-
|
146
|
+
# @return [Integer] the intent parameter sent to the gateway server.
|
147
|
+
attr_reader :intents
|
148
|
+
|
149
|
+
def initialize(bot, token, shard_key = nil, compress_mode = :stream, intents = ALL_INTENTS)
|
149
150
|
@token = token
|
150
151
|
@bot = bot
|
151
152
|
|
152
153
|
@shard_key = shard_key
|
153
154
|
|
154
|
-
@getc_mutex = Mutex.new
|
155
|
-
|
156
155
|
# Whether the connection to the gateway has succeeded yet
|
157
156
|
@ws_success = false
|
158
157
|
|
159
158
|
@check_heartbeat_acks = true
|
160
159
|
|
161
160
|
@compress_mode = compress_mode
|
161
|
+
@intents = intents
|
162
162
|
end
|
163
163
|
|
164
164
|
# Connect to the gateway server in a separate thread
|
@@ -170,8 +170,19 @@ module Discordrb
|
|
170
170
|
end
|
171
171
|
|
172
172
|
LOGGER.debug('WS thread created! Now waiting for confirmation that everything worked')
|
173
|
-
|
174
|
-
|
173
|
+
loop do
|
174
|
+
sleep(0.5)
|
175
|
+
|
176
|
+
if @ws_success
|
177
|
+
LOGGER.debug('Confirmation received! Exiting run.')
|
178
|
+
break
|
179
|
+
end
|
180
|
+
|
181
|
+
if @should_reconnect == false
|
182
|
+
LOGGER.debug('Reconnection flag was unset. Exiting run.')
|
183
|
+
break
|
184
|
+
end
|
185
|
+
end
|
175
186
|
end
|
176
187
|
|
177
188
|
# Prevents all further execution until the websocket thread stops (e.g. through a closed connection).
|
@@ -181,16 +192,16 @@ module Discordrb
|
|
181
192
|
|
182
193
|
# Whether the WebSocket connection to the gateway is currently open
|
183
194
|
def open?
|
184
|
-
@handshake
|
195
|
+
@handshake&.finished? && !@closed
|
185
196
|
end
|
186
197
|
|
187
198
|
# Stops the bot gracefully, disconnecting the websocket without immediately killing the thread. This means that
|
188
199
|
# Discord is immediately aware of the closed connection and makes the bot appear offline instantly.
|
189
200
|
#
|
190
201
|
# If this method doesn't work or you're looking for something more drastic, use {#kill} instead.
|
191
|
-
def stop
|
202
|
+
def stop
|
192
203
|
@should_reconnect = false
|
193
|
-
close
|
204
|
+
close
|
194
205
|
|
195
206
|
# Return nil so command bots don't send a message
|
196
207
|
nil
|
@@ -241,7 +252,7 @@ module Discordrb
|
|
241
252
|
def heartbeat
|
242
253
|
if check_heartbeat_acks
|
243
254
|
unless @last_heartbeat_acked
|
244
|
-
# We're in a bad situation - apparently the last heartbeat wasn't
|
255
|
+
# We're in a bad situation - apparently the last heartbeat wasn't ACK'd, which means the connection is likely
|
245
256
|
# a zombie. Reconnect
|
246
257
|
LOGGER.warn('Last heartbeat was not acked, so this is a zombie connection! Reconnecting')
|
247
258
|
|
@@ -269,12 +280,12 @@ module Discordrb
|
|
269
280
|
def identify
|
270
281
|
compress = @compress_mode == :large
|
271
282
|
send_identify(@token, {
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
}, compress,
|
283
|
+
os: RUBY_PLATFORM,
|
284
|
+
browser: 'discordrb',
|
285
|
+
device: 'discordrb',
|
286
|
+
referrer: '',
|
287
|
+
referring_domain: ''
|
288
|
+
}, compress, LARGE_THRESHOLD, @shard_key, @intents)
|
278
289
|
end
|
279
290
|
|
280
291
|
# Sends an identify packet (op 2). This starts a new session on the current connection and tells Discord who we are.
|
@@ -284,24 +295,25 @@ module Discordrb
|
|
284
295
|
# @param properties [Hash<Symbol => String>] A list of properties for Discord to use in analytics. The following
|
285
296
|
# keys are recognised:
|
286
297
|
#
|
287
|
-
# - "
|
288
|
-
# - "
|
289
|
-
# - "
|
290
|
-
# - "
|
291
|
-
# - "
|
298
|
+
# - "os" (recommended value: the operating system the bot is running on)
|
299
|
+
# - "browser" (recommended value: library name)
|
300
|
+
# - "device" (recommended value: library name)
|
301
|
+
# - "referrer" (recommended value: empty)
|
302
|
+
# - "referring_domain" (recommended value: empty)
|
292
303
|
#
|
293
304
|
# @param compress [true, false] Whether certain large packets should be compressed using zlib.
|
294
305
|
# @param large_threshold [Integer] The member threshold after which a server counts as large and will have to have
|
295
306
|
# its member list chunked.
|
296
307
|
# @param shard_key [Array(Integer, Integer), nil] The shard key to use for sharding, represented as
|
297
308
|
# [shard_id, num_shards], or nil if the bot should not be sharded.
|
298
|
-
def send_identify(token, properties, compress, large_threshold, shard_key = nil)
|
309
|
+
def send_identify(token, properties, compress, large_threshold, shard_key = nil, intents = ALL_INTENTS)
|
299
310
|
data = {
|
300
311
|
# Don't send a v anymore as it's entirely determined by the URL now
|
301
312
|
token: token,
|
302
313
|
properties: properties,
|
303
314
|
compress: compress,
|
304
|
-
large_threshold: large_threshold
|
315
|
+
large_threshold: large_threshold,
|
316
|
+
intents: intents
|
305
317
|
}
|
306
318
|
|
307
319
|
# Don't include the shard key at all if it is nil as Discord checks for its mere existence
|
@@ -312,7 +324,7 @@ module Discordrb
|
|
312
324
|
|
313
325
|
# Sends a status update packet (op 3). This sets the bot user's status (online/idle/...) and game playing/streaming.
|
314
326
|
# @param status [String] The status that should be set (`online`, `idle`, `dnd`, `invisible`).
|
315
|
-
# @param since [Integer] The
|
327
|
+
# @param since [Integer] The Unix timestamp in milliseconds when the status was set. Should only be provided when
|
316
328
|
# `afk` is true.
|
317
329
|
# @param game [Hash<Symbol => Object>, nil] `nil` if no game should be played, or a hash of `:game => "name"` if a
|
318
330
|
# game should be played. The hash can also contain additional attributes for streaming statuses.
|
@@ -360,7 +372,7 @@ module Discordrb
|
|
360
372
|
@instant_reconnect = true
|
361
373
|
@should_reconnect = true
|
362
374
|
|
363
|
-
close
|
375
|
+
close(4000)
|
364
376
|
end
|
365
377
|
|
366
378
|
# Sends a resume packet (op 6). This replays all events from a previous point specified by its packet sequence. This
|
@@ -402,12 +414,12 @@ module Discordrb
|
|
402
414
|
|
403
415
|
# Sends a custom packet over the connection. This can be useful to implement future yet unimplemented functionality
|
404
416
|
# or for testing. You probably shouldn't use this unless you know what you're doing.
|
405
|
-
# @param
|
417
|
+
# @param opcode [Integer] The opcode the packet should be sent as. Can be one of {Opcodes} or a custom value if
|
406
418
|
# necessary.
|
407
|
-
# @param packet [Object] Some arbitrary JSON-
|
408
|
-
def send_packet(
|
419
|
+
# @param packet [Object] Some arbitrary JSON-serializable data that should be sent as the `d` field.
|
420
|
+
def send_packet(opcode, packet)
|
409
421
|
data = {
|
410
|
-
op:
|
422
|
+
op: opcode,
|
411
423
|
d: packet
|
412
424
|
}
|
413
425
|
|
@@ -436,20 +448,18 @@ module Discordrb
|
|
436
448
|
@heartbeat_thread = Thread.new do
|
437
449
|
Thread.current[:discordrb_name] = 'heartbeat'
|
438
450
|
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)
|
451
|
+
# Send a heartbeat if heartbeats are active and either no session exists yet, or an existing session is
|
452
|
+
# suspended (e.g. after op7)
|
453
|
+
if (@session && !@session.suspended?) || !@session
|
454
|
+
sleep @heartbeat_interval
|
455
|
+
@bot.raise_heartbeat_event
|
456
|
+
heartbeat
|
457
|
+
else
|
458
|
+
sleep 1
|
452
459
|
end
|
460
|
+
rescue StandardError => e
|
461
|
+
LOGGER.error('An error occurred while heartbeating!')
|
462
|
+
LOGGER.log_exception(e)
|
453
463
|
end
|
454
464
|
end
|
455
465
|
end
|
@@ -502,7 +512,7 @@ module Discordrb
|
|
502
512
|
cert_store.set_default_paths
|
503
513
|
ctx.cert_store = cert_store
|
504
514
|
else
|
505
|
-
ctx.set_params ssl_version: :TLSv1_2
|
515
|
+
ctx.set_params ssl_version: :TLSv1_2 # rubocop:disable Naming/VariableNumber
|
506
516
|
end
|
507
517
|
|
508
518
|
socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
|
@@ -567,7 +577,7 @@ module Discordrb
|
|
567
577
|
|
568
578
|
# We're done! Delegate to the websocket loop
|
569
579
|
websocket_loop
|
570
|
-
rescue => e
|
580
|
+
rescue StandardError => e
|
571
581
|
LOGGER.error('An error occurred while connecting to the websocket!')
|
572
582
|
LOGGER.log_exception(e)
|
573
583
|
end
|
@@ -584,13 +594,17 @@ module Discordrb
|
|
584
594
|
unless @socket
|
585
595
|
LOGGER.warn('Socket is nil in websocket_loop! Reconnecting')
|
586
596
|
handle_internal_close('Socket is nil in websocket_loop')
|
597
|
+
next
|
587
598
|
end
|
588
599
|
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
600
|
+
# Get some data from the socket
|
601
|
+
begin
|
602
|
+
recv_data = @socket.readpartial(4096)
|
603
|
+
rescue EOFError
|
604
|
+
@pipe_broken = true
|
605
|
+
handle_internal_close('Socket EOF in websocket_loop')
|
606
|
+
next
|
607
|
+
end
|
594
608
|
|
595
609
|
# Check if we actually got data
|
596
610
|
unless recv_data
|
@@ -621,7 +635,7 @@ module Discordrb
|
|
621
635
|
# If the handshake hasn't finished, handle it
|
622
636
|
handle_handshake_data(recv_data)
|
623
637
|
end
|
624
|
-
rescue => e
|
638
|
+
rescue StandardError => e
|
625
639
|
handle_error(e)
|
626
640
|
end
|
627
641
|
end
|
@@ -645,12 +659,13 @@ module Discordrb
|
|
645
659
|
ZLIB_SUFFIX = "\x00\x00\xFF\xFF".b.freeze
|
646
660
|
|
647
661
|
def handle_message(msg)
|
648
|
-
|
662
|
+
case @compress_mode
|
663
|
+
when :large
|
649
664
|
if msg.byteslice(0) == 'x'
|
650
665
|
# The message is compressed, inflate it
|
651
666
|
msg = Zlib::Inflate.inflate(msg)
|
652
667
|
end
|
653
|
-
|
668
|
+
when :stream
|
654
669
|
# Write deflated string to buffer
|
655
670
|
@zlib_reader << msg
|
656
671
|
|
@@ -703,6 +718,7 @@ module Discordrb
|
|
703
718
|
|
704
719
|
@session = Session.new(data['session_id'])
|
705
720
|
@session.sequence = 0
|
721
|
+
@bot.__send__(:notify_ready) if @intents && (@intents & INTENTS[:servers]).zero?
|
706
722
|
when :RESUMED
|
707
723
|
# The RESUMED event is received after a successful op 6 (resume). It does nothing except tell the bot the
|
708
724
|
# connection is initiated (like READY would). Starting with v5, it doesn't set a new heartbeat interval anymore
|
@@ -750,7 +766,7 @@ module Discordrb
|
|
750
766
|
LOGGER.debug("Trace: #{packet['d']['_trace']}")
|
751
767
|
LOGGER.debug("Session: #{@session.inspect}")
|
752
768
|
|
753
|
-
if @session
|
769
|
+
if @session&.should_resume?
|
754
770
|
# Make sure we're sending heartbeats again
|
755
771
|
@session.resume
|
756
772
|
|
@@ -773,22 +789,41 @@ module Discordrb
|
|
773
789
|
handle_close(e)
|
774
790
|
end
|
775
791
|
|
792
|
+
# Close codes that are unrecoverable, after which we should not try to reconnect.
|
793
|
+
# - 4003: Not authenticated. How did this happen?
|
794
|
+
# - 4004: Authentication failed. Token was wrong, nothing we can do.
|
795
|
+
# - 4011: Sharding required. Currently requires developer intervention.
|
796
|
+
# - 4014: Use of disabled privileged intents.
|
797
|
+
FATAL_CLOSE_CODES = [4003, 4004, 4011, 4014].freeze
|
798
|
+
|
776
799
|
def handle_close(e)
|
800
|
+
@bot.__send__(:raise_event, Events::DisconnectEvent.new(@bot))
|
801
|
+
|
777
802
|
if e.respond_to? :code
|
778
803
|
# It is a proper close frame we're dealing with, print reason and message to console
|
779
804
|
LOGGER.error('Websocket close frame received!')
|
780
805
|
LOGGER.error("Code: #{e.code}")
|
781
806
|
LOGGER.error("Message: #{e.data}")
|
807
|
+
|
808
|
+
if e.code == 4014
|
809
|
+
LOGGER.error(<<~ERROR)
|
810
|
+
You attempted to identify with privileged intents that your bot is not authorized to use
|
811
|
+
Please enable the privileged intents on the bot page of your application on the discord developer page.
|
812
|
+
Read more here https://discord.com/developers/docs/topics/gateway#privileged-intents
|
813
|
+
ERROR
|
814
|
+
end
|
815
|
+
|
816
|
+
@should_reconnect = false if FATAL_CLOSE_CODES.include?(e.code)
|
782
817
|
elsif e.is_a? Exception
|
783
818
|
# Log the exception
|
784
819
|
LOGGER.error('The websocket connection has closed due to an error!')
|
785
820
|
LOGGER.log_exception(e)
|
786
821
|
else
|
787
|
-
LOGGER.error("The websocket connection has closed: #{e
|
822
|
+
LOGGER.error("The websocket connection has closed: #{e&.inspect || '(no information)'}")
|
788
823
|
end
|
789
824
|
end
|
790
825
|
|
791
|
-
def send(data, type = :text)
|
826
|
+
def send(data, type = :text, code = nil)
|
792
827
|
LOGGER.out(data)
|
793
828
|
|
794
829
|
unless @handshaked && !@closed
|
@@ -797,40 +832,33 @@ module Discordrb
|
|
797
832
|
end
|
798
833
|
|
799
834
|
# Create the frame we're going to send
|
800
|
-
frame = ::WebSocket::Frame::Outgoing::Client.new(data: data, type: type, version: @handshake.version)
|
835
|
+
frame = ::WebSocket::Frame::Outgoing::Client.new(data: data, type: type, version: @handshake.version, code: code)
|
801
836
|
|
802
837
|
# Try to send it
|
803
838
|
begin
|
804
839
|
@socket.write frame.to_s
|
805
|
-
rescue => e
|
840
|
+
rescue StandardError => e
|
806
841
|
# There has been an error!
|
807
842
|
@pipe_broken = true
|
808
843
|
handle_internal_close(e)
|
809
844
|
end
|
810
845
|
end
|
811
846
|
|
812
|
-
def close(
|
847
|
+
def close(code = 1000)
|
813
848
|
# If we're already closed, there's no need to do anything - return
|
814
849
|
return if @closed
|
815
850
|
|
816
851
|
# Suspend the session so we don't send heartbeats
|
817
|
-
@session
|
852
|
+
@session&.suspend
|
818
853
|
|
819
854
|
# Send a close frame (if we can)
|
820
|
-
send nil, :close unless @pipe_broken
|
855
|
+
send nil, :close, code unless @pipe_broken
|
821
856
|
|
822
857
|
# 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
|
858
|
+
@closed = true
|
831
859
|
|
832
860
|
# Close the socket if possible
|
833
|
-
@socket
|
861
|
+
@socket&.close
|
834
862
|
@socket = nil
|
835
863
|
|
836
864
|
# 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|
|