Class: Discordrb::Gateway
- Inherits:
-
Object
- Object
- Discordrb::Gateway
- Defined in:
- lib/discordrb/gateway.rb
Overview
Client for the Discord gateway protocol
Constant Summary collapse
- LARGE_THRESHOLD =
How many members there need to be in a server for it to count as "large"
100
- GATEWAY_VERSION =
The version of the gateway that's supposed to be used.
9
- FATAL_CLOSE_CODES =
Close codes that are unrecoverable, after which we should not try to reconnect.
- 4003: Not authenticated. How did this happen?
- 4004: Authentication failed. Token was wrong, nothing we can do.
- 4011: Sharding required. Currently requires developer intervention.
- 4014: Use of disabled privileged intents.
[4003, 4004, 4011, 4014].freeze
Instance Attribute Summary collapse
-
#check_heartbeat_acks ⇒ true, false
Heartbeat ACKs are Discord's way of verifying on the client side whether the connection is still alive.
-
#intents ⇒ Integer
readonly
The intent parameter sent to the gateway server.
Instance Method Summary collapse
-
#heartbeat ⇒ Object
Sends a heartbeat with the last received packet's seq (to acknowledge that we have received it and all packets before it), or if none have been received yet, with 0.
-
#identify ⇒ Object
Identifies to Discord with the default parameters.
-
#initialize(bot, token, shard_key = nil, compress_mode = :stream, intents = ALL_INTENTS) ⇒ Gateway
constructor
A new instance of Gateway.
-
#inject_error(e) ⇒ Object
Injects a terminal gateway error into the handler.
-
#inject_reconnect(url = nil) ⇒ Object
Injects a reconnect event (op 7) into the event processor, causing Discord to reconnect to the given gateway URL.
-
#inject_resume(seq) ⇒ Object
Injects a resume packet (op 6) into the gateway.
-
#kill ⇒ Object
Kills the websocket thread, stopping all connections to Discord.
-
#notify_ready ⇒ Object
Notifies the #run_async method that everything is ready and the caller can now continue (i.e. with syncing, or with doing processing and then syncing).
-
#open? ⇒ Boolean
Whether the WebSocket connection to the gateway is currently open.
-
#reconnect(attempt_resume = true) ⇒ Object
Reconnects the gateway connection in a controlled manner.
-
#resume ⇒ Object
Resumes the session from the last recorded point.
-
#run_async ⇒ Object
Connect to the gateway server in a separate thread.
-
#send_heartbeat(sequence) ⇒ Object
Sends a heartbeat packet (op 1).
-
#send_identify(token, properties, compress, large_threshold, shard_key = nil, intents = ALL_INTENTS) ⇒ Object
Sends an identify packet (op 2).
-
#send_packet(opcode, packet) ⇒ Object
Sends a custom packet over the connection.
-
#send_raw(data, type = :text) ⇒ Object
Sends custom raw data over the connection.
-
#send_request_members(server_id, query, limit) ⇒ Object
Sends a request members packet (op 8).
-
#send_resume(token, session_id, seq) ⇒ Object
Sends a resume packet (op 6).
-
#send_status_update(status, since, game, afk) ⇒ Object
Sends a status update packet (op 3).
-
#send_voice_state_update(server_id, channel_id, self_mute, self_deaf) ⇒ Object
Sends a voice state update packet (op 4).
-
#stop ⇒ Object
Stops the bot gracefully, disconnecting the websocket without immediately killing the thread.
-
#sync ⇒ Object
Prevents all further execution until the websocket thread stops (e.g. through a closed connection).
Constructor Details
#initialize(bot, token, shard_key = nil, compress_mode = :stream, intents = ALL_INTENTS) ⇒ Gateway
Returns a new instance of Gateway.
165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/discordrb/gateway.rb', line 165 def initialize(bot, token, shard_key = nil, compress_mode = :stream, intents = ALL_INTENTS) @token = token @bot = bot @shard_key = shard_key # Whether the connection to the gateway has succeeded yet @ws_success = false @check_heartbeat_acks = true @compress_mode = compress_mode @intents = intents end |
Instance Attribute Details
#check_heartbeat_acks ⇒ true, false
Heartbeat ACKs are Discord's way of verifying on the client side whether the connection is still alive. If this is set to true (default value) the gateway client will use that functionality to detect zombie connections and reconnect in such a case; however it may lead to instability if there's some problem with the ACKs. If this occurs it can simply be set to false.
160 161 162 |
# File 'lib/discordrb/gateway.rb', line 160 def check_heartbeat_acks @check_heartbeat_acks end |
#intents ⇒ Integer (readonly)
Returns the intent parameter sent to the gateway server.
163 164 165 |
# File 'lib/discordrb/gateway.rb', line 163 def intents @intents end |
Instance Method Details
#heartbeat ⇒ Object
Sends a heartbeat with the last received packet's seq (to acknowledge that we have received it and all packets before it), or if none have been received yet, with 0.
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/discordrb/gateway.rb', line 268 def heartbeat if check_heartbeat_acks unless @last_heartbeat_acked # We're in a bad situation - apparently the last heartbeat wasn't ACK'd, which means the connection is likely # a zombie. Reconnect LOGGER.warn('Last heartbeat was not acked, so this is a zombie connection! Reconnecting') # We can't send anything on zombie connections @pipe_broken = true reconnect return end @last_heartbeat_acked = false end send_heartbeat(@session ? @session.sequence : 0) end |
#identify ⇒ Object
Identifies to Discord with the default parameters.
296 297 298 299 300 301 302 303 304 305 |
# File 'lib/discordrb/gateway.rb', line 296 def identify compress = @compress_mode == :large send_identify(@token, { os: RUBY_PLATFORM, browser: 'discordrb', device: 'discordrb', referrer: '', referring_domain: '' }, compress, LARGE_THRESHOLD, @shard_key, @intents) end |
#inject_error(e) ⇒ Object
Injects a terminal gateway error into the handler. Useful for testing the reconnect logic.
261 262 263 |
# File 'lib/discordrb/gateway.rb', line 261 def inject_error(e) handle_internal_close(e) end |
#inject_reconnect(url = nil) ⇒ Object
Injects a reconnect event (op 7) into the event processor, causing Discord to reconnect to the given gateway URL. If the URL is set to nil, it will reconnect and get an entirely new gateway URL. This method has not much use outside of testing and implementing highly custom reconnect logic.
241 242 243 244 245 246 247 248 249 |
# File 'lib/discordrb/gateway.rb', line 241 def inject_reconnect(url = nil) # When no URL is specified, the data should be nil, as is the case with Discord-sent packets. data = url ? { url: url } : nil ({ op: Opcodes::RECONNECT, d: data }.to_json) end |
#inject_resume(seq) ⇒ Object
Injects a resume packet (op 6) into the gateway. If this is done with a running connection, it will cause an error. It has no use outside of testing stuff that I know of, but if you want to use it anyway for some reason, here it is.
255 256 257 |
# File 'lib/discordrb/gateway.rb', line 255 def inject_resume(seq) send_resume(raw_token, @session_id, seq || @sequence) end |
#kill ⇒ Object
Kills the websocket thread, stopping all connections to Discord.
227 228 229 |
# File 'lib/discordrb/gateway.rb', line 227 def kill @ws_thread.kill end |
#notify_ready ⇒ Object
Notifies the #run_async method that everything is ready and the caller can now continue (i.e. with syncing, or with doing processing and then syncing)
233 234 235 |
# File 'lib/discordrb/gateway.rb', line 233 def notify_ready @ws_success = true end |
#open? ⇒ Boolean
Whether the WebSocket connection to the gateway is currently open
210 211 212 |
# File 'lib/discordrb/gateway.rb', line 210 def open? @handshake&.finished? && !@closed end |
#reconnect(attempt_resume = true) ⇒ Object
Reconnects the gateway connection in a controlled manner.
385 386 387 388 389 390 391 392 |
# File 'lib/discordrb/gateway.rb', line 385 def reconnect(attempt_resume = true) @session.suspend if @session && attempt_resume @instant_reconnect = true @should_reconnect = true close(4000) end |
#resume ⇒ Object
Resumes the session from the last recorded point.
379 380 381 |
# File 'lib/discordrb/gateway.rb', line 379 def resume send_resume(@token, @session.session_id, @session.sequence) end |
#run_async ⇒ Object
Connect to the gateway server in a separate thread
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/discordrb/gateway.rb', line 181 def run_async @ws_thread = Thread.new do Thread.current[:discordrb_name] = 'websocket' connect_loop LOGGER.warn('The WS loop exited! Not sure if this is a good thing') end LOGGER.debug('WS thread created! Now waiting for confirmation that everything worked') loop do sleep(0.5) if @ws_success LOGGER.debug('Confirmation received! Exiting run.') break end if @should_reconnect == false LOGGER.debug('Reconnection flag was unset. Exiting run.') break end end end |
#send_heartbeat(sequence) ⇒ Object
Sends a heartbeat packet (op 1). This tells Discord that the current connection is still active and that the last packets until the given sequence have been processed (in case of a resume).
290 291 292 |
# File 'lib/discordrb/gateway.rb', line 290 def send_heartbeat(sequence) send_packet(Opcodes::HEARTBEAT, sequence) end |
#send_identify(token, properties, compress, large_threshold, shard_key = nil, intents = ALL_INTENTS) ⇒ Object
Sends an identify packet (op 2). This starts a new session on the current connection and tells Discord who we are. This can only be done once a connection.
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/discordrb/gateway.rb', line 325 def send_identify(token, properties, compress, large_threshold, shard_key = nil, intents = ALL_INTENTS) data = { # Don't send a v anymore as it's entirely determined by the URL now token: token, properties: properties, compress: compress, large_threshold: large_threshold, intents: intents } # Don't include the shard key at all if it is nil as Discord checks for its mere existence data[:shard] = shard_key if shard_key send_packet(Opcodes::IDENTIFY, data) end |
#send_packet(opcode, packet) ⇒ Object
Sends a custom packet over the connection. This can be useful to implement future yet unimplemented functionality or for testing. You probably shouldn't use this unless you know what you're doing.
436 437 438 439 440 441 442 443 |
# File 'lib/discordrb/gateway.rb', line 436 def send_packet(opcode, packet) data = { op: opcode, d: packet } send(data.to_json) end |
#send_raw(data, type = :text) ⇒ Object
Sends custom raw data over the connection. Only useful for testing; even if you know what you're doing you probably want to use #send_packet instead.
450 451 452 |
# File 'lib/discordrb/gateway.rb', line 450 def send_raw(data, type = :text) send(data, type) end |
#send_request_members(server_id, query, limit) ⇒ Object
Sends a request members packet (op 8). This will order Discord to gradually sent all requested members as dispatch
events with type GUILD_MEMBERS_CHUNK
. It is necessary to use this method in order to get all members of a large
server (see large_threshold
in #send_identify), however it can also be used for other purposes.
421 422 423 424 425 426 427 428 429 |
# File 'lib/discordrb/gateway.rb', line 421 def send_request_members(server_id, query, limit) data = { guild_id: server_id, query: query, limit: limit } send_packet(Opcodes::REQUEST_MEMBERS, data) end |
#send_resume(token, session_id, seq) ⇒ Object
Sends a resume packet (op 6). This replays all events from a previous point specified by its packet sequence. This will not work if the packet to resume from has already been acknowledged using a heartbeat, or if the session ID belongs to a now invalid session.
If this packet is sent at the beginning of a connection, it will act similarly to an #identify in that it creates a session on the current connection. Unlike identify however, this packet can also be sent in an existing session and will just replay some of the events.
404 405 406 407 408 409 410 411 412 |
# File 'lib/discordrb/gateway.rb', line 404 def send_resume(token, session_id, seq) data = { token: token, session_id: session_id, seq: seq } send_packet(Opcodes::RESUME, data) end |
#send_status_update(status, since, game, afk) ⇒ Object
Sends a status update packet (op 3). This sets the bot user's status (online/idle/...) and game playing/streaming.
348 349 350 351 352 353 354 355 356 357 |
# File 'lib/discordrb/gateway.rb', line 348 def send_status_update(status, since, game, afk) data = { status: status, since: since, game: game, afk: afk } send_packet(Opcodes::PRESENCE, data) end |
#send_voice_state_update(server_id, channel_id, self_mute, self_deaf) ⇒ Object
Sends a voice state update packet (op 4). This packet can connect a user to a voice channel, update self mute/deaf status in an existing voice connection, move the user to a new voice channel on the same server or disconnect an existing voice connection.
366 367 368 369 370 371 372 373 374 375 |
# File 'lib/discordrb/gateway.rb', line 366 def send_voice_state_update(server_id, channel_id, self_mute, self_deaf) data = { guild_id: server_id, channel_id: channel_id, self_mute: self_mute, self_deaf: self_deaf } send_packet(Opcodes::VOICE_STATE, data) end |
#stop ⇒ Object
Stops the bot gracefully, disconnecting the websocket without immediately killing the thread. This means that Discord is immediately aware of the closed connection and makes the bot appear offline instantly.
If this method doesn't work or you're looking for something more drastic, use #kill instead.
218 219 220 221 222 223 224 |
# File 'lib/discordrb/gateway.rb', line 218 def stop @should_reconnect = false close # Return nil so command bots don't send a message nil end |
#sync ⇒ Object
Prevents all further execution until the websocket thread stops (e.g. through a closed connection).
205 206 207 |
# File 'lib/discordrb/gateway.rb', line 205 def sync @ws_thread.join end |