buddy

node MVC discord bot
Log | Files | Refs | README

VoiceUDPClient.js (4159B)


      1 'use strict';
      2 
      3 const udp = require('dgram');
      4 const EventEmitter = require('events');
      5 const { Error } = require('../../../errors');
      6 const { VoiceOPCodes } = require('../../../util/Constants');
      7 
      8 /**
      9  * Represents a UDP client for a Voice Connection.
     10  * @extends {EventEmitter}
     11  * @private
     12  */
     13 class VoiceConnectionUDPClient extends EventEmitter {
     14   constructor(voiceConnection) {
     15     super();
     16 
     17     /**
     18      * The voice connection that this UDP client serves
     19      * @type {VoiceConnection}
     20      */
     21     this.voiceConnection = voiceConnection;
     22 
     23     /**
     24      * The UDP socket
     25      * @type {?Socket}
     26      */
     27     this.socket = null;
     28 
     29     /**
     30      * The address of the Discord voice server
     31      * @type {?string}
     32      */
     33     this.discordAddress = null;
     34 
     35     /**
     36      * The local IP address
     37      * @type {?string}
     38      */
     39     this.localAddress = null;
     40 
     41     /**
     42      * The local port
     43      * @type {?string}
     44      */
     45     this.localPort = null;
     46 
     47     this.voiceConnection.on('closing', this.shutdown.bind(this));
     48   }
     49 
     50   shutdown() {
     51     this.emit('debug', `[UDP] shutdown requested`);
     52     if (this.socket) {
     53       this.socket.removeAllListeners('message');
     54       try {
     55         this.socket.close();
     56       } finally {
     57         this.socket = null;
     58       }
     59     }
     60   }
     61 
     62   /**
     63    * The port of the Discord voice server
     64    * @type {number}
     65    * @readonly
     66    */
     67   get discordPort() {
     68     return this.voiceConnection.authentication.port;
     69   }
     70 
     71   /**
     72    * Send a packet to the UDP client.
     73    * @param {Object} packet The packet to send
     74    * @returns {Promise<Object>}
     75    */
     76   send(packet) {
     77     return new Promise((resolve, reject) => {
     78       if (!this.socket) throw new Error('UDP_SEND_FAIL');
     79       if (!this.discordAddress || !this.discordPort) throw new Error('UDP_ADDRESS_MALFORMED');
     80       this.socket.send(packet, 0, packet.length, this.discordPort, this.discordAddress, error => {
     81         if (error) {
     82           this.emit('debug', `[UDP] >> ERROR: ${error}`);
     83           reject(error);
     84         } else {
     85           resolve(packet);
     86         }
     87       });
     88     });
     89   }
     90 
     91   async createUDPSocket(address) {
     92     this.discordAddress = address;
     93     const socket = (this.socket = udp.createSocket('udp4'));
     94     socket.on('error', e => {
     95       this.emit('debug', `[UDP] Error: ${e}`);
     96       this.emit('error', e);
     97     });
     98     socket.on('close', () => {
     99       this.emit('debug', '[UDP] socket closed');
    100     });
    101     this.emit('debug', `[UDP] created socket`);
    102     socket.once('message', message => {
    103       this.emit('debug', `[UDP] message: [${[...message]}] (${message})`);
    104       // Stop if the sockets have been deleted because the connection has been closed already
    105       if (!this.voiceConnection.sockets.ws) return;
    106 
    107       const packet = parseLocalPacket(message);
    108       if (packet.error) {
    109         this.emit('debug', `[UDP] ERROR: ${packet.error}`);
    110         this.emit('error', packet.error);
    111         return;
    112       }
    113 
    114       this.localAddress = packet.address;
    115       this.localPort = packet.port;
    116 
    117       this.voiceConnection.sockets.ws.sendPacket({
    118         op: VoiceOPCodes.SELECT_PROTOCOL,
    119         d: {
    120           protocol: 'udp',
    121           data: {
    122             address: packet.address,
    123             port: packet.port,
    124             mode: this.voiceConnection.authentication.mode,
    125           },
    126         },
    127       });
    128 
    129       this.emit('debug', `[UDP] << ${JSON.stringify(packet)}`);
    130 
    131       socket.on('message', buffer => this.voiceConnection.receiver.packets.push(buffer));
    132     });
    133 
    134     const blankMessage = Buffer.alloc(70);
    135     blankMessage.writeUIntBE(this.voiceConnection.authentication.ssrc, 0, 4);
    136     this.emit('debug', `Sending IP discovery packet: [${[...blankMessage]}]`);
    137     await this.send(blankMessage);
    138     this.emit('debug', `Successfully sent IP discovery packet`);
    139   }
    140 }
    141 
    142 function parseLocalPacket(message) {
    143   try {
    144     const packet = Buffer.from(message);
    145     let address = '';
    146     for (let i = 4; i < packet.indexOf(0, i); i++) address += String.fromCharCode(packet[i]);
    147     const port = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10);
    148     return { address, port };
    149   } catch (error) {
    150     return { error };
    151   }
    152 }
    153 
    154 module.exports = VoiceConnectionUDPClient;