class Discordrb::Voice::VoiceWS
Represents a websocket client connection to the voice server. The websocket connection (sometimes called vWS) is used to manage general data about the connection, such as sending the speaking packet, which determines the green circle around users on Discord, and obtaining UDP connection info.
Attributes
@return [VoiceUDP] the UDP voice connection over which the actual audio data is sent.
Public Class Methods
Makes a new voice websocket client, but doesn't connect it (see {#connect} for that) @param channel [Channel] The voice channel to connect to @param bot [Bot] The regular bot to which this vWS is bound @param token [String] The authentication token which is also used for REST requests @param session [String] The voice session ID Discord sends over the regular websocket @param endpoint [String] The endpoint URL to connect to
# File lib/discordrb/voice/network.rb, line 130 def initialize(channel, bot, token, session, endpoint) raise 'RbNaCl is unavailable - unable to create voice bot! Please read https://github.com/meew0/discordrb/wiki/Installing-libsodium' unless RBNACL_AVAILABLE @channel = channel @bot = bot @token = token @session = session @endpoint = endpoint.gsub(':80', '') @udp = VoiceUDP.new end
Public Instance Methods
Communication goes like this: me discord
| |
websocket connect -> |
| | | <- websocket opcode 2 | |
UDP discovery -> |
| | | <- UDP reply packet | |
websocket opcode 1 -> |
| |
…
# File lib/discordrb/voice/network.rb, line 251 def connect # Connect websocket @thread = Thread.new do Thread.current[:discordrb_name] = 'vws' init_ws end @bot.debug('Started websocket initialization, now waiting for UDP discovery reply') # Now wait for opcode 2 and the resulting UDP reply packet ip, port = @udp.receive_discovery_reply @bot.debug("UDP discovery reply received! #{ip} #{port}") # Send UDP init packet with received UDP data send_udp_connection(ip, port, @udp_mode) @bot.debug('Waiting for op 4 now') # Wait for op 4, then finish sleep 0.05 until @ready end
Disconnects the websocket and kills the thread
# File lib/discordrb/voice/network.rb, line 274 def destroy @heartbeat_running = false end
Send a heartbeat (op 3), has to be done every @heartbeat_interval seconds or the connection will terminate
# File lib/discordrb/voice/network.rb, line 179 def send_heartbeat millis = Time.now.strftime('%s%L').to_i @bot.debug("Sending voice heartbeat at #{millis}") @client.send({ op: 3, d: nil }.to_json) end
Send a connection init packet (op 0) @param server_id [Integer] The ID of the server to connect to @param bot_user_id [Integer] The ID of the bot that is connecting @param session_id [String] The voice session ID @param token [String] The Discord authentication token
# File lib/discordrb/voice/network.rb, line 148 def send_init(server_id, bot_user_id, session_id, token) @client.send({ op: 0, d: { server_id: server_id, user_id: bot_user_id, session_id: session_id, token: token } }.to_json) end
Send a speaking packet (op 5). This determines the green circle around the avatar in the voice channel @param value [true, false] Whether or not the bot should be speaking
# File lib/discordrb/voice/network.rb, line 191 def send_speaking(value) @bot.debug("Speaking: #{value}") @client.send({ op: 5, d: { speaking: value, delay: 0 } }.to_json) end
Sends the UDP connection packet (op 1) @param ip [String] The IP to bind UDP to @param port [Integer] The port to bind UDP to @param mode [Object] Which mode to use for the voice connection
# File lib/discordrb/voice/network.rb, line 164 def send_udp_connection(ip, port, mode) @client.send({ op: 1, d: { protocol: 'udp', data: { address: ip, port: port, mode: mode } } }.to_json) end
@!visibility private
# File lib/discordrb/voice/network.rb, line 213 def websocket_message(msg) @bot.debug("Received VWS message! #{msg}") packet = JSON.parse(msg) case packet['op'] when 2 # Opcode 2 contains data to initialize the UDP connection @ws_data = packet['d'] @heartbeat_interval = @ws_data['heartbeat_interval'] @ssrc = @ws_data['ssrc'] @port = @ws_data['port'] @udp_mode = mode @udp.connect(@endpoint, @port, @ssrc) @udp.send_discovery when 4 # Opcode 4 sends the secret key used for encryption @ws_data = packet['d'] @ready = true @udp.secret_key = @ws_data['secret_key'].pack('C*') end end
Event handlers; public for websocket-simple to work correctly @!visibility private
# File lib/discordrb/voice/network.rb, line 204 def websocket_open # Give the current thread a name ('Voice Web Socket Internal') Thread.current[:discordrb_name] = 'vws-i' # Send the init packet send_init(@channel.server.id, @bot.profile.id, @session, @token) end
Private Instance Methods
# File lib/discordrb/voice/network.rb, line 285 def heartbeat_loop @heartbeat_running = true while @heartbeat_running if @heartbeat_interval sleep @heartbeat_interval / 1000.0 send_heartbeat else # If no interval has been set yet, sleep a second and check again sleep 1 end end end
# File lib/discordrb/voice/network.rb, line 298 def init_ws host = "wss://#{@endpoint}:443" @bot.debug("Connecting VWS to host: #{host}") # Connect the WS @client = Discordrb::WebSocket.new( host, method(:websocket_open), method(:websocket_message), proc { |e| Discordrb::LOGGER.error "VWS error: #{e}" }, proc { |e| Discordrb::LOGGER.warn "VWS close: #{e}" } ) @bot.debug('VWS connected') # Block any further execution heartbeat_loop end
@return [String] the mode string that signifies whether encryption should be used or not
# File lib/discordrb/voice/network.rb, line 281 def mode @udp.encrypted? ? ENCRYPTED_MODE : PLAIN_MODE end