class MijDiscord::Core::Gateway
Constants
- GATEWAY_VERSION
- LARGE_THRESHOLD
Attributes
check_heartbeat_acks[RW]
Public Class Methods
new(bot, auth, shard_key = nil)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 113 def initialize(bot, auth, shard_key = nil) @bot, @auth, @shard_key = bot, auth, shard_key @ws_success = false @getc_mutex = Mutex.new @check_heartbeat_acks = true end
Public Instance Methods
heartbeat()
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 170 def heartbeat if check_heartbeat_acks unless @last_heartbeat_acked MijDiscord::LOGGER.warn('Gateway') { 'Heartbeat not acknowledged, attempting to reconnect' } @broken_pipe = true reconnect(true) return end @last_heartbeat_acked = false end send_heartbeat(@session&.sequence || 0) end
kill()
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 155 def kill @ws_thread&.kill nil end
notify_ready()
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 274 def notify_ready @ws_success = true end
open?()
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 160 def open? @handshake&.finished? && !@ws_closed end
reconnect(try_resume = true)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 186 def reconnect(try_resume = true) @session&.suspend if try_resume @instant_reconnect = true @should_reconnect = true ws_close(false) nil end
run_async()
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 122 def run_async @ws_thread = Thread.new do Thread.current[:mij_discord] = 'websocket' @reconnect_delay = 1.0 loop do ws_connect break unless @should_reconnect if @instant_reconnect @reconnect_delay = 1.0 @instant_reconnect = false else sleep(@reconnect_delay) @reconnect_delay = [@reconnect_delay * 1.5, 120].min end end MijDiscord::LOGGER.info('Gateway') { 'Websocket loop has been terminated' } end sleep(0.2) until @ws_success MijDiscord::LOGGER.info('Gateway') { 'Connection established and confirmed' } nil end
send_heartbeat(sequence)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 196 def send_heartbeat(sequence) send_packet(Opcodes::HEARTBEAT, sequence) end
send_identify(token, properties, compress, large_threshold, shard_key)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 200 def send_identify(token, properties, compress, large_threshold, shard_key) data = { token: token, properties: properties, compress: compress, large_threshold: large_threshold, } data[:shard] = shard_key if shard_key send_packet(Opcodes::IDENTIFY, data) end
send_packet(opcode, packet)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 259 def send_packet(opcode, packet) data = { op: opcode, d: packet, } ws_send(data.to_json, :text) nil end
send_raw(data, type = :text)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 269 def send_raw(data, type = :text) ws_send(data, type) nil end
send_request_guild_sync(guilds)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 255 def send_request_guild_sync(guilds) send_packet(Opcodes::GUILD_SYNC, guilds) end
send_request_members(server_id, query = '', limit = 0)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 245 def send_request_members(server_id, query = '', limit = 0) data = { guild_id: server_id, query: query, limit: limit, } send_packet(Opcodes::REQUEST_MEMBERS, data) end
send_resume(token, session_id, sequence)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 235 def send_resume(token, session_id, sequence) data = { token: token, session_id: session_id, seq: sequence, } send_packet(Opcodes::RESUME, data) end
send_status_update(status, since, game, afk)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 213 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)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 224 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(no_sync = false)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 164 def stop(no_sync = false) @should_reconnect = false ws_close(no_sync) nil end
sync()
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 150 def sync @ws_thread&.join nil end
Private Instance Methods
get_gateway_url()
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 341 def get_gateway_url response = API.gateway(@auth) raw_url = JSON.parse(response)['url'] raw_url << '/' unless raw_url.end_with? '/' "#{raw_url}?encoding=json&v=#{GATEWAY_VERSION}" end
handle_dispatch(packet)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 484 def handle_dispatch(packet) data, type = packet['d'], packet['t'].to_sym case type when :READY @session = Session.new(data['session_id']) MijDiscord::LOGGER.info('Gateway') { "Received READY packet (user: #{data['user']['id']})" } MijDiscord::LOGGER.info('Gateway') { "Using gateway protocol version #{data['v']}, requested #{GATEWAY_VERSION}" } when :RESUMED MijDiscord::LOGGER.info('Gateway') { 'Received session resume confirmation' } return end @bot.handle_dispatch(type, data) end
handle_hello(packet)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 501 def handle_hello(packet) interval = packet['d']['heartbeat_interval'].to_f / 1000.0 setup_heartbeat(interval) if @session&.should_resume? @session.resume send_resume_self else send_identify_self end end
handle_message(msg)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 459 def handle_message(msg) msg = Zlib::Inflate.inflate(msg) if msg.byteslice(0) == 'x' packet = JSON.parse(msg) @session&.sequence = packet['s'] if packet['s'] case (opc = packet['op'].to_i) when Opcodes::DISPATCH handle_dispatch(packet) when Opcodes::HELLO handle_hello(packet) when Opcodes::RECONNECT reconnect when Opcodes::INVALIDATE_SESSION @session&.invalidate send_identify_self when Opcodes::HEARTBEAT_ACK @last_heartbeat_acked = true if @check_heartbeat_acks when Opcodes::HEARTBEAT send_heartbeat(packet['s']) else MijDiscord::LOGGER.error('Gateway') { "Invalid opcode received: #{opc}" } end end
obtain_socket(uri)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 321 def obtain_socket(uri) secure = %w[https wss].include?(uri.scheme) socket = TCPSocket.new(uri.host, uri.port || (secure ? 443 : 80)) if secure ctx = OpenSSL::SSL::SSLContext.new ctx.ssl_version = 'SSLv23' ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE # use VERIFY_PEER for verification cert_store = OpenSSL::X509::Store.new cert_store.set_default_paths ctx.cert_store = cert_store socket = OpenSSL::SSL::SSLSocket.new(socket, ctx) socket.connect end socket end
send_identify_self()
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 280 def send_identify_self props = { '$os': RUBY_PLATFORM, '$browser': 'mij-discord', '$device': 'mij-discord', '$referrer': '', '$referring_domain': '', } send_identify(@auth, props, true, LARGE_THRESHOLD, @shard_key) end
send_resume_self()
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 292 def send_resume_self send_resume(@auth, @session.session_id, @session.sequence) end
setup_heartbeat(interval)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 296 def setup_heartbeat(interval) @last_heartbeat_acked = true return if @heartbeat_thread @heartbeat_thread = Thread.new do Thread.current[:mij_discord] = 'heartbeat' loop do begin if @session&.suspended? sleep(1.0) else sleep(interval) @bot.handle_heartbeat heartbeat end rescue => exc MijDiscord::LOGGER.error('Gateway') { 'An error occurred during heartbeat' } MijDiscord::LOGGER.error('Gateway') { exc } end end end end
ws_close(no_sync)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 440 def ws_close(no_sync) return if @ws_closed @session&.suspend ws_send(nil, :close) unless @broken_pipe if no_sync @ws_closed = true else @getc_mutex.synchronize { @ws_closed = true } end @socket&.close @socket = nil @bot.handle_dispatch(:DISCONNECT, nil) end
ws_connect()
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 348 def ws_connect url = get_gateway_url gateway_uri = URI.parse(url) @socket = obtain_socket(gateway_uri) @handshake = WebSocket::Handshake::Client.new(url: url) @handshake_done, @broken_pipe, @ws_closed = false, false, false ws_mainloop rescue => exc MijDiscord::LOGGER.error('Gateway') { 'An error occurred during websocket connect' } MijDiscord::LOGGER.error('Gateway') { exc } end
ws_mainloop()
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 362 def ws_mainloop @bot.handle_dispatch(:CONNECT, nil) @socket.write(@handshake.to_s) frame = WebSocket::Frame::Incoming::Client.new until @ws_closed begin unless @socket ws_close(false) MijDiscord::LOGGER.error('Gateway') { 'Socket object is nil in main websocket loop' } end recv_data = nil @getc_mutex.synchronize { recv_data = @socket&.getc } unless recv_data sleep(1.0) next end if @handshake_done frame << recv_data loop do msg = frame.next break unless msg if msg.respond_to?(:code) && msg.code MijDiscord::LOGGER.warn('Gateway') { 'Received websocket close frame' } MijDiscord::LOGGER.warn('Gateway') { "(code: #{msg.code}, info: #{msg.data})" } codes = [1000, 4004, 4010, 4011] if codes.include?(msg.code) ws_close(false) else MijDiscord::LOGGER.warn('Gateway') { 'Non-fatal code, attempting to reconnect' } reconnect(true) end break end handle_message(msg.data) end else @handshake << recv_data @handshake_done = true if @handshake.finished? end rescue Errno::ECONNRESET @broken_pipe = true reconnect(true) MijDiscord::LOGGER.warn('Gateway') { 'Connection reset by remote host, attempting to reconnect' } rescue => exc MijDiscord::LOGGER.error('Gateway') { 'An error occurred in main websocket loop' } MijDiscord::LOGGER.error('Gateway') { exc } end end end
ws_send(data, type)
click to toggle source
# File lib/mij-discord/core/gateway.rb, line 423 def ws_send(data, type) unless @handshake_done && !@ws_closed raise 'Tried to send something to the websocket while not being connected!' end frame = WebSocket::Frame::Outgoing::Client.new(data: data, type: type, version: @handshake.version) begin @socket.write frame.to_s rescue => e @broken_pipe = true ws_close(false) MijDiscord::LOGGER.error('Gateway') { 'An error occurred during websocket write' } MijDiscord::LOGGER.error('Gateway') { e } end end