discordrb 3.3.0 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of discordrb might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.circleci/config.yml +126 -0
- data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -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 +472 -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 +17 -17
- data/lib/discordrb.rb +73 -0
- data/lib/discordrb/allowed_mentions.rb +36 -0
- data/lib/discordrb/api.rb +40 -15
- data/lib/discordrb/api/channel.rb +57 -39
- data/lib/discordrb/api/invite.rb +3 -3
- data/lib/discordrb/api/server.rb +55 -50
- data/lib/discordrb/api/user.rb +8 -8
- data/lib/discordrb/api/webhook.rb +6 -6
- data/lib/discordrb/await.rb +0 -1
- data/lib/discordrb/bot.rb +164 -72
- data/lib/discordrb/cache.rb +4 -2
- data/lib/discordrb/colour_rgb.rb +43 -0
- data/lib/discordrb/commands/command_bot.rb +22 -6
- 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.rb +25 -4180
- data/lib/discordrb/data/activity.rb +264 -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/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 +3 -3
- 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
- metadata +93 -55
@@ -15,11 +15,16 @@ module Discordrb::Events
|
|
15
15
|
# @return [Symbol] the new status.
|
16
16
|
attr_reader :status
|
17
17
|
|
18
|
+
# @return [Hash<Symbol, Symbol>] the current online status (`:online`, `:idle` or `:dnd`) of the user
|
19
|
+
# on various device types (`:desktop`, `:mobile`, or `:web`). The value will be `nil` if the user is offline or invisible.
|
20
|
+
attr_reader :client_status
|
21
|
+
|
18
22
|
def initialize(data, bot)
|
19
23
|
@bot = bot
|
20
24
|
|
21
25
|
@user = bot.user(data['user']['id'].to_i)
|
22
26
|
@status = data['status'].to_sym
|
27
|
+
@client_status = user.client_status
|
23
28
|
@server = bot.server(data['guild_id'].to_i)
|
24
29
|
end
|
25
30
|
end
|
@@ -32,9 +37,10 @@ module Discordrb::Events
|
|
32
37
|
|
33
38
|
[
|
34
39
|
matches_all(@attributes[:from], event.user) do |a, e|
|
35
|
-
a ==
|
40
|
+
a == case a
|
41
|
+
when String
|
36
42
|
e.name
|
37
|
-
|
43
|
+
when Integer
|
38
44
|
e.id
|
39
45
|
else
|
40
46
|
e
|
@@ -92,9 +98,10 @@ module Discordrb::Events
|
|
92
98
|
|
93
99
|
[
|
94
100
|
matches_all(@attributes[:from], event.user) do |a, e|
|
95
|
-
a ==
|
101
|
+
a == case a
|
102
|
+
when String
|
96
103
|
e.name
|
97
|
-
|
104
|
+
when Integer
|
98
105
|
e.id
|
99
106
|
else
|
100
107
|
e
|
@@ -105,6 +112,9 @@ module Discordrb::Events
|
|
105
112
|
end,
|
106
113
|
matches_all(@attributes[:type], event.type) do |a, e|
|
107
114
|
a == e
|
115
|
+
end,
|
116
|
+
matches_all(@attributes[:client_status], event.client_status) do |a, e|
|
117
|
+
e.slice(a.keys) == a
|
108
118
|
end
|
109
119
|
].reduce(true, &:&)
|
110
120
|
end
|
data/lib/discordrb/events/raw.rb
CHANGED
@@ -30,9 +30,7 @@ module Discordrb::Events
|
|
30
30
|
[
|
31
31
|
matches_all(@attributes[:type] || @attributes[:t], event.type) do |a, e|
|
32
32
|
if a.is_a? Regexp
|
33
|
-
|
34
|
-
match = a.match(e)
|
35
|
-
match ? (e == match[0]) : false
|
33
|
+
a.match?(e)
|
36
34
|
else
|
37
35
|
e.to_s.casecmp(a.to_s).zero?
|
38
36
|
end
|
@@ -11,6 +11,9 @@ module Discordrb::Events
|
|
11
11
|
# @return [Emoji] the emoji that was reacted with.
|
12
12
|
attr_reader :emoji
|
13
13
|
|
14
|
+
# @!visibility private
|
15
|
+
attr_reader :message_id
|
16
|
+
|
14
17
|
def initialize(data, bot)
|
15
18
|
@bot = bot
|
16
19
|
|
@@ -54,13 +57,38 @@ module Discordrb::Events
|
|
54
57
|
|
55
58
|
[
|
56
59
|
matches_all(@attributes[:emoji], event.emoji) do |a, e|
|
57
|
-
|
60
|
+
case a
|
61
|
+
when Integer
|
58
62
|
e.id == a
|
59
|
-
|
63
|
+
when String
|
60
64
|
e.name == a || e.name == a.delete(':') || e.id == a.resolve_id
|
61
65
|
else
|
62
66
|
e == a
|
63
67
|
end
|
68
|
+
end,
|
69
|
+
matches_all(@attributes[:message], event.message_id) do |a, e|
|
70
|
+
a == e
|
71
|
+
end,
|
72
|
+
matches_all(@attributes[:in], event.channel) do |a, e|
|
73
|
+
case a
|
74
|
+
when String
|
75
|
+
# Make sure to remove the "#" from channel names in case it was specified
|
76
|
+
a.delete('#') == e.name
|
77
|
+
when Integer
|
78
|
+
a == e.id
|
79
|
+
else
|
80
|
+
a == e
|
81
|
+
end
|
82
|
+
end,
|
83
|
+
matches_all(@attributes[:from], event.user) do |a, e|
|
84
|
+
case a
|
85
|
+
when String
|
86
|
+
a == e.name
|
87
|
+
when :bot
|
88
|
+
e.current_bot?
|
89
|
+
else
|
90
|
+
a == e
|
91
|
+
end
|
64
92
|
end
|
65
93
|
].reduce(true, &:&)
|
66
94
|
end
|
@@ -82,6 +110,9 @@ module Discordrb::Events
|
|
82
110
|
class ReactionRemoveAllEvent < Event
|
83
111
|
include Respondable
|
84
112
|
|
113
|
+
# @!visibility private
|
114
|
+
attr_reader :message_id
|
115
|
+
|
85
116
|
def initialize(data, bot)
|
86
117
|
@bot = bot
|
87
118
|
|
@@ -107,7 +138,22 @@ module Discordrb::Events
|
|
107
138
|
return false unless event.is_a? ReactionRemoveAllEvent
|
108
139
|
|
109
140
|
# No attributes yet as there is no property available on the event that doesn't involve doing a resolution request
|
110
|
-
[
|
141
|
+
[
|
142
|
+
matches_all(@attributes[:message], event.message_id) do |a, e|
|
143
|
+
a == e
|
144
|
+
end,
|
145
|
+
matches_all(@attributes[:in], event.channel) do |a, e|
|
146
|
+
case a
|
147
|
+
when String
|
148
|
+
# Make sure to remove the "#" from channel names in case it was specified
|
149
|
+
a.delete('#') == e.name
|
150
|
+
when Integer
|
151
|
+
a == e.id
|
152
|
+
else
|
153
|
+
a == e
|
154
|
+
end
|
155
|
+
end
|
156
|
+
].reduce(true, &:&)
|
111
157
|
end
|
112
158
|
end
|
113
159
|
end
|
@@ -45,18 +45,20 @@ module Discordrb::Events
|
|
45
45
|
|
46
46
|
[
|
47
47
|
matches_all(@attributes[:in], event.channel) do |a, e|
|
48
|
-
|
48
|
+
case a
|
49
|
+
when String
|
49
50
|
a.delete('#') == e.name
|
50
|
-
|
51
|
+
when Integer
|
51
52
|
a == e.id
|
52
53
|
else
|
53
54
|
a == e
|
54
55
|
end
|
55
56
|
end,
|
56
57
|
matches_all(@attributes[:from], event.user) do |a, e|
|
57
|
-
a ==
|
58
|
+
a == case a
|
59
|
+
when String
|
58
60
|
e.name
|
59
|
-
|
61
|
+
when Integer
|
60
62
|
e.id
|
61
63
|
else
|
62
64
|
e
|
@@ -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
|