import { VoiceOpcodes } from 'discord-api-types/voice/v4' ; import type { ConnectionData } from '../networking/Networking' ; import { methods } from '../util/Secretbox' ; import type { VoiceConnection } from '../VoiceConnection' ; import { AudioReceiveStream , AudioReceiveStreamOptions , createDefaultAudioReceiveStreamOptions , } from './AudioReceiveStream' ; import { SpeakingMap } from './SpeakingMap' ; import { SSRCMap } from './SSRCMap' ; beta export class VoiceReceiver { /**
     * The attached connection of this receiver.
     */ public readonly voiceConnection ; /**
     * Maps SSRCs to Discord user ids.
     */ public readonly ssrcMap : SSRCMap ; /**
     * The current audio subscriptions of this receiver.
     */ public readonly subscriptions : Map < string , AudioReceiveStream > ; internal public connectionData : Partial < ConnectionData > ; /**
     * The speaking map of the receiver.
     */ public readonly speaking : SpeakingMap ; public constructor ( voiceConnection ) { this . voiceConnection = voiceConnection ; this . ssrcMap = new SSRCMap ( ) ; this . speaking = new SpeakingMap ( ) ; this . subscriptions = new Map ( ) ; this . connectionData = { } ; this . onWsPacket = this . onWsPacket . bind ( this ) ; this . onUdpMessage = this . onUdpMessage . bind ( this ) ; } param packet internal public onWsPacket ( packet ) { if ( packet . op === VoiceOpcodes . ClientDisconnect && typeof packet . d ?. user_id === 'string' ) { this . ssrcMap . delete ( packet . d . user_id ) ; } else if ( packet . op === VoiceOpcodes . Speaking && typeof packet . d ?. user_id === 'string' && typeof packet . d ?. ssrc === 'number' ) { this . ssrcMap . update ( { userId : packet . d . user_id , audioSSRC : packet . d . ssrc } ) ; } else if ( packet . op === VoiceOpcodes . ClientConnect && typeof packet . d ?. user_id === 'string' && typeof packet . d ?. audio_ssrc === 'number' ) { this . ssrcMap . update ( { userId : packet . d . user_id , audioSSRC : packet . d . audio_ssrc , videoSSRC : packet . d . video_ssrc === 0 ? undefined : packet . d . video_ssrc , } ) ; } } private decrypt ( buffer , mode , nonce , secretKey ) { let end ; if ( mode === 'xsalsa20_poly1305_lite' ) { buffer . copy ( nonce , 0 , buffer . length - 4 ) ; end = buffer . length - 4 ; } else if ( mode === 'xsalsa20_poly1305_suffix' ) { buffer . copy ( nonce , 0 , buffer . length - 24 ) ; end = buffer . length - 24 ; } else { buffer . copy ( nonce , 0 , 0 , 12 ) ; } const decrypted = methods . open ( buffer . slice ( 12 , end ) , nonce , secretKey ) ; if ( ! decrypted ) return ; return Buffer . from ( decrypted ) ; } param buffer param mode param nonce param secretKey returns private parsePacket ( buffer , mode , nonce , secretKey ) { let packet = this . decrypt ( buffer , mode , nonce , secretKey ) ; if ( ! packet ) return ; if ( packet [ 0 ] === 0xbe && packet [ 1 ] === 0xde && packet . length > 4 ) { const headerExtensionLength = packet . readUInt16BE ( 2 ) ; let offset = 4 ; for ( let i = 0 ; i < headerExtensionLength ; i ++ ) { const byte = packet [ offset ] ; offset ++ ; if ( byte === 0 ) continue ; offset += 1 + ( byte >> 4 ) ; } const byte = packet . readUInt8 ( offset ) ; if ( byte === 0x00 || byte === 0x02 ) offset ++ ; packet = packet . slice ( offset ) ; } return packet ; } param msg internal public onUdpMessage ( msg ) { if ( msg . length <= 8 ) return ; const ssrc = msg . readUInt32BE ( 8 ) ; const userData = this . ssrcMap . get ( ssrc ) ; if ( ! userData ) return ; this . speaking . onPacket ( userData . userId ) ; const stream = this . subscriptions . get ( userData . userId ) ; if ( ! stream ) return ; if ( this . connectionData . encryptionMode && this . connectionData . nonceBuffer && this . connectionData . secretKey ) { const packet = this . parsePacket ( msg , this . connectionData . encryptionMode , this . connectionData . nonceBuffer , this . connectionData . secretKey ) ; if ( packet ) { stream . push ( packet ) ; } else { stream . destroy ( new Error ( 'Failed to parse packet' ) ) ; } } } param target returns public subscribe ( userId , options ? ) { const existing = this . subscriptions . get ( userId ) ; if ( existing ) return existing ; const stream = new AudioReceiveStream ( { ... createDefaultAudioReceiveStreamOptions ( ) , ... options , } ) ; stream . once ( 'close' , ( ) => this . subscriptions . delete ( userId ) ) ; this . subscriptions . set ( userId , stream ) ; return stream ; } }