Add events module with dashboard UI, scheduling, signups, and settings updates; extend env/readme.

This commit is contained in:
Pascal Prießnitz
2025-12-02 23:52:10 +01:00
parent 874b01c999
commit 829d160164
578 changed files with 37647 additions and 11590 deletions

View File

@@ -2,6 +2,7 @@ import { Buffer } from 'node:buffer';
import { EventEmitter } from 'node:events';
import { Readable, ReadableOptions } from 'node:stream';
import prism from 'prism-media';
import { VoiceDavePrepareTransitionData, VoiceDavePrepareEpochData, VoiceOpcodes, VoiceSendPayload, VoiceReceivePayload } from 'discord-api-types/voice/v8';
import WebSocket, { MessageEvent } from 'ws';
import { GatewayVoiceServerUpdateDispatchData, GatewayVoiceStateUpdateDispatchData } from 'discord-api-types/v10';
@@ -442,11 +443,11 @@ declare class AudioPlayer extends EventEmitter {
private unsubscribe;
/**
* The state that the player is in.
*
* @remarks
* The setter will perform clean-up operations where necessary.
*/
get state(): AudioPlayerState;
/**
* Sets a new state for the player, performing clean-up operations where necessary.
*/
set state(newState: AudioPlayerState);
/**
* Plays a new resource on the player. If the player is already playing a resource, the existing resource is destroyed
@@ -556,6 +557,174 @@ declare function getVoiceConnections(group: string): Map<string, VoiceConnection
*/
declare function getVoiceConnection(guildId: string, group?: string): VoiceConnection | undefined;
interface SessionMethods {
canPassthrough(userId: string): boolean;
decrypt(userId: string, mediaType: 0 | 1, packet: Buffer): Buffer;
encryptOpus(packet: Buffer): Buffer;
getSerializedKeyPackage(): Buffer;
getVerificationCode(userId: string): Promise<string>;
processCommit(commit: Buffer): void;
processProposals(optype: 0 | 1, proposals: Buffer, recognizedUserIds?: string[]): ProposalsResult;
processWelcome(welcome: Buffer): void;
ready: boolean;
reinit(protocolVersion: number, userId: string, channelId: string): void;
reset(): void;
setExternalSender(externalSender: Buffer): void;
setPassthroughMode(passthrough: boolean, expiry: number): void;
voicePrivacyCode: string;
}
interface ProposalsResult {
commit?: Buffer;
welcome?: Buffer;
}
interface TransitionResult {
success: boolean;
transitionId: number;
}
/**
* Options that dictate the session behavior.
*/
interface DAVESessionOptions {
decryptionFailureTolerance?: number | undefined;
}
interface DAVESession extends EventEmitter {
on(event: 'error', listener: (error: Error) => void): this;
on(event: 'debug', listener: (message: string) => void): this;
on(event: 'keyPackage', listener: (message: Buffer) => void): this;
on(event: 'invalidateTransition', listener: (transitionId: number) => void): this;
}
/**
* Manages the DAVE protocol group session.
*/
declare class DAVESession extends EventEmitter {
/**
* The channel id represented by this session.
*/
channelId: string;
/**
* The user id represented by this session.
*/
userId: string;
/**
* The protocol version being used.
*/
protocolVersion: number;
/**
* The last transition id executed.
*/
lastTransitionId?: number | undefined;
/**
* The pending transition.
*/
private pendingTransition?;
/**
* Whether this session was downgraded previously.
*/
private downgraded;
/**
* The amount of consecutive failures encountered when decrypting.
*/
private consecutiveFailures;
/**
* The amount of consecutive failures needed to attempt to recover.
*/
private readonly failureTolerance;
/**
* Whether this session is currently re-initializing due to an invalid transition.
*/
reinitializing: boolean;
/**
* The underlying DAVE Session of this wrapper.
*/
session: SessionMethods | undefined;
constructor(protocolVersion: number, userId: string, channelId: string, options: DAVESessionOptions);
/**
* The current voice privacy code of the session. Will be `null` if there is no session.
*/
get voicePrivacyCode(): string | null;
/**
* Gets the verification code for a user in the session.
*
* @throws Will throw if there is not an active session or the user id provided is invalid or not in the session.
*/
getVerificationCode(userId: string): Promise<string>;
/**
* Re-initializes (or initializes) the underlying session.
*/
reinit(): void;
/**
* Set the external sender for this session.
*
* @param externalSender - The external sender
*/
setExternalSender(externalSender: Buffer): void;
/**
* Prepare for a transition.
*
* @param data - The transition data
* @returns Whether we should signal to the voice server that we are ready
*/
prepareTransition(data: VoiceDavePrepareTransitionData): boolean;
/**
* Execute a transition.
*
* @param transitionId - The transition id to execute on
*/
executeTransition(transitionId: number): boolean | undefined;
/**
* Prepare for a new epoch.
*
* @param data - The epoch data
*/
prepareEpoch(data: VoiceDavePrepareEpochData): void;
/**
* Recover from an invalid transition by re-initializing.
*
* @param transitionId - The transition id to invalidate
*/
recoverFromInvalidTransition(transitionId: number): void;
/**
* Processes proposals from the MLS group.
*
* @param payload - The binary message payload
* @param connectedClients - The set of connected client IDs
* @returns The payload to send back to the voice server, if there is one
*/
processProposals(payload: Buffer, connectedClients: Set<string>): Buffer | undefined;
/**
* Processes a commit from the MLS group.
*
* @param payload - The payload
* @returns The transaction id and whether it was successful
*/
processCommit(payload: Buffer): TransitionResult;
/**
* Processes a welcome from the MLS group.
*
* @param payload - The payload
* @returns The transaction id and whether it was successful
*/
processWelcome(payload: Buffer): TransitionResult;
/**
* Encrypt a packet using end-to-end encryption.
*
* @param packet - The packet to encrypt
*/
encrypt(packet: Buffer): Buffer;
/**
* Decrypt a packet using end-to-end encryption.
*
* @param packet - The packet to decrypt
* @param userId - The user id that sent the packet
* @returns The decrypted packet, or `null` if the decryption failed but should be ignored
*/
decrypt(packet: Buffer, userId: string): Buffer | null;
/**
* Resets the session.
*/
destroy(): void;
}
/**
* Stores an IP address and port. Used to store socket details for the local client as well as
* for Discord.
@@ -634,6 +803,14 @@ declare class VoiceUDPSocket extends EventEmitter {
performIPDiscovery(ssrc: number): Promise<SocketConfig>;
}
/**
* A binary WebSocket message.
*/
interface BinaryWebSocketMessage {
op: VoiceOpcodes;
payload: Buffer;
seq: number;
}
interface VoiceWebSocket extends EventEmitter {
on(event: 'error', listener: (error: Error) => void): this;
on(event: 'open', listener: (event: WebSocket.Event) => void): this;
@@ -650,6 +827,12 @@ interface VoiceWebSocket extends EventEmitter {
* @eventProperty
*/
on(event: 'packet', listener: (packet: any) => void): this;
/**
* Binary message event.
*
* @eventProperty
*/
on(event: 'binary', listener: (message: BinaryWebSocketMessage) => void): this;
}
/**
* An extension of the WebSocket class to provide helper functionality when interacting
@@ -678,6 +861,10 @@ declare class VoiceWebSocket extends EventEmitter {
* The last recorded ping.
*/
ping?: number;
/**
* The last sequence number acknowledged from Discord. Will be `-1` if no sequence numbered messages have been received.
*/
sequence: number;
/**
* The debug logger function, if debugging is enabled.
*/
@@ -698,7 +885,7 @@ declare class VoiceWebSocket extends EventEmitter {
destroy(): void;
/**
* Handles message events on the WebSocket. Attempts to JSON parse the messages and emit them
* as packets.
* as packets. Binary messages will be parsed and emitted.
*
* @param event - The message event
*/
@@ -708,7 +895,14 @@ declare class VoiceWebSocket extends EventEmitter {
*
* @param packet - The packet to send
*/
sendPacket(packet: any): void;
sendPacket(packet: VoiceSendPayload): void;
/**
* Sends a binary message over the WebSocket.
*
* @param opcode - The opcode to use
* @param payload - The payload to send
*/
sendBinaryMessage(opcode: VoiceOpcodes, payload: Buffer): void;
/**
* Sends a heartbeat over the WebSocket.
*/
@@ -758,7 +952,7 @@ interface NetworkingIdentifyingState {
*/
interface NetworkingUdpHandshakingState {
code: NetworkingStatusCode.UdpHandshaking;
connectionData: Pick<ConnectionData, 'ssrc'>;
connectionData: Pick<ConnectionData, 'connectedClients' | 'ssrc'>;
connectionOptions: ConnectionOptions;
udp: VoiceUDPSocket;
ws: VoiceWebSocket;
@@ -768,7 +962,7 @@ interface NetworkingUdpHandshakingState {
*/
interface NetworkingSelectingProtocolState {
code: NetworkingStatusCode.SelectingProtocol;
connectionData: Pick<ConnectionData, 'ssrc'>;
connectionData: Pick<ConnectionData, 'connectedClients' | 'ssrc'>;
connectionOptions: ConnectionOptions;
udp: VoiceUDPSocket;
ws: VoiceWebSocket;
@@ -781,6 +975,7 @@ interface NetworkingReadyState {
code: NetworkingStatusCode.Ready;
connectionData: ConnectionData;
connectionOptions: ConnectionOptions;
dave?: DAVESession | undefined;
preparedPacket?: Buffer | undefined;
udp: VoiceUDPSocket;
ws: VoiceWebSocket;
@@ -793,6 +988,7 @@ interface NetworkingResumingState {
code: NetworkingStatusCode.Resuming;
connectionData: ConnectionData;
connectionOptions: ConnectionOptions;
dave?: DAVESession | undefined;
preparedPacket?: Buffer | undefined;
udp: VoiceUDPSocket;
ws: VoiceWebSocket;
@@ -814,6 +1010,7 @@ type NetworkingState = NetworkingClosedState | NetworkingIdentifyingState | Netw
* and VOICE_STATE_UPDATE packets.
*/
interface ConnectionOptions {
channelId: string;
endpoint: string;
serverId: string;
sessionId: string;
@@ -825,6 +1022,7 @@ interface ConnectionOptions {
* the connection, timing information for playback of streams.
*/
interface ConnectionData {
connectedClients: Set<string>;
encryptionMode: string;
nonce: number;
nonceBuffer: Buffer;
@@ -835,6 +1033,14 @@ interface ConnectionData {
ssrc: number;
timestamp: number;
}
/**
* Options for networking that dictate behavior.
*/
interface NetworkingOptions {
daveEncryption?: boolean | undefined;
debug?: boolean | undefined;
decryptionFailureTolerance?: number | undefined;
}
interface Networking extends EventEmitter {
/**
* Debug event for Networking.
@@ -845,6 +1051,7 @@ interface Networking extends EventEmitter {
on(event: 'error', listener: (error: Error) => void): this;
on(event: 'stateChange', listener: (oldState: NetworkingState, newState: NetworkingState) => void): this;
on(event: 'close', listener: (code: number) => void): this;
on(event: 'transitioned', listener: (transitionId: number) => void): this;
}
/**
* Manages the networking required to maintain a voice connection and dispatch audio packets
@@ -855,30 +1062,41 @@ declare class Networking extends EventEmitter {
* The debug logger function, if debugging is enabled.
*/
private readonly debug;
/**
* The options used to create this Networking instance.
*/
private readonly options;
/**
* Creates a new Networking instance.
*/
constructor(options: ConnectionOptions, debug: boolean);
constructor(connectionOptions: ConnectionOptions, options: NetworkingOptions);
/**
* Destroys the Networking instance, transitioning it into the Closed state.
*/
destroy(): void;
/**
* The current state of the networking instance.
*
* @remarks
* The setter will perform clean-up operations where necessary.
*/
get state(): NetworkingState;
/**
* Sets a new state for the networking instance, performing clean-up operations where necessary.
*/
set state(newState: NetworkingState);
/**
* Creates a new WebSocket to a Discord Voice gateway.
*
* @param endpoint - The endpoint to connect to
* @param lastSequence - The last sequence to set for this WebSocket
*/
private createWebSocket;
/**
* Propagates errors from the children VoiceWebSocket and VoiceUDPSocket.
* Creates a new DAVE session for this voice connection if we can create one.
*
* @param protocolVersion - The protocol version to use
*/
private createDaveSession;
/**
* Propagates errors from the children VoiceWebSocket, VoiceUDPSocket and DAVESession.
*
* @param error - The error that was emitted by a child
*/
@@ -906,6 +1124,24 @@ declare class Networking extends EventEmitter {
* @param packet - The received packet
*/
private onWsPacket;
/**
* Called when a binary message is received on the connection's WebSocket.
*
* @param message - The received message
*/
private onWsBinary;
/**
* Called when a new key package is ready to be sent to the voice server.
*
* @param keyPackage - The new key package
*/
private onDaveKeyPackage;
/**
* Called when the DAVE session wants to invalidate their transition and re-initialize.
*
* @param transitionId - The transition to invalidate
*/
private onDaveInvalidateTransition;
/**
* Propagates debug messages from the child WebSocket.
*
@@ -918,6 +1154,12 @@ declare class Networking extends EventEmitter {
* @param message - The emitted debug message
*/
private onUdpDebug;
/**
* Propagates debug messages from the child DAVESession.
*
* @param message - The emitted debug message
*/
private onDaveDebug;
/**
* Prepares an Opus packet for playback. This includes attaching metadata to it and encrypting it.
* It will be stored within the instance, and can be played by dispatchAudio()
@@ -953,6 +1195,7 @@ declare class Networking extends EventEmitter {
*
* @param opusPacket - The Opus packet to prepare
* @param connectionData - The current connection data of the instance
* @param daveSession - The DAVE session to use for encryption
*/
private createAudioPacket;
/**
@@ -960,6 +1203,7 @@ declare class Networking extends EventEmitter {
*
* @param opusPacket - The Opus packet to encrypt
* @param connectionData - The current connection data of the instance
* @param daveSession - The DAVE session to use for encryption
*/
private encryptOpusPacket;
}
@@ -1001,7 +1245,7 @@ declare class AudioReceiveStream extends Readable {
*/
readonly end: EndBehavior;
private endTimeout?;
constructor({ end, ...options }: AudioReceiveStreamOptions);
constructor(options: AudioReceiveStreamOptions);
push(buffer: Buffer | null): boolean;
private renewEndTimeout;
_read(): void;
@@ -1128,7 +1372,7 @@ declare class VoiceReceiver {
* @param packet - The received packet
* @internal
*/
onWsPacket(packet: any): void;
onWsPacket(packet: VoiceReceivePayload): void;
private decrypt;
/**
* Parses an audio packet, decrypting it to yield an Opus packet.
@@ -1137,6 +1381,7 @@ declare class VoiceReceiver {
* @param mode - The encryption mode
* @param nonce - The nonce buffer used by the connection for encryption
* @param secretKey - The secret key used by the connection for encryption
* @param userId - The user id that sent the packet
* @returns The parsed Opus packet
*/
private parsePacket;
@@ -1341,6 +1586,12 @@ interface VoiceConnection extends EventEmitter {
* @eventProperty
*/
on(event: 'stateChange', listener: (oldState: VoiceConnectionState, newState: VoiceConnectionState) => void): this;
/**
* Emitted when the end-to-end encrypted session has transitioned
*
* @eventProperty
*/
on(event: 'transitioned', listener: (transitionId: number) => void): this;
/**
* Emitted when the state of the voice connection changes to a specific status
*
@@ -1383,6 +1634,10 @@ declare class VoiceConnection extends EventEmitter {
* The debug logger function, if debugging is enabled.
*/
private readonly debug;
/**
* The options used to create this voice connection.
*/
private readonly options;
/**
* Creates a new voice connection.
*
@@ -1392,11 +1647,11 @@ declare class VoiceConnection extends EventEmitter {
constructor(joinConfig: JoinConfig, options: CreateVoiceConnectionOptions);
/**
* The current state of the voice connection.
*
* @remarks
* The setter will perform clean-up operations where necessary.
*/
get state(): VoiceConnectionState;
/**
* Updates the state of the voice connection, performing clean-up operations where necessary.
*/
set state(newState: VoiceConnectionState);
/**
* Registers a `VOICE_SERVER_UPDATE` packet to the voice connection. This will cause it to reconnect using the
@@ -1463,6 +1718,12 @@ declare class VoiceConnection extends EventEmitter {
* @param message - The debug message to propagate
*/
private onNetworkingDebug;
/**
* Propagates transitions from the underlying network instance.
*
* @param transitionId - The transition id
*/
private onNetworkingTransitioned;
/**
* Prepares an audio packet for dispatch.
*
@@ -1530,6 +1791,20 @@ declare class VoiceConnection extends EventEmitter {
ws: number | undefined;
udp: number | undefined;
};
/**
* The current voice privacy code of the encrypted session.
*
* @remarks
* For this data to be available, the VoiceConnection must be in the Ready state,
* and the connection would have to be end-to-end encrypted.
*/
get voicePrivacyCode(): string | undefined;
/**
* Gets the verification code for a user in the session.
*
* @throws Will throw if end-to-end encryption is not on or if the user id provided is not in the session.
*/
getVerificationCode(userId: string): Promise<string>;
/**
* Called when a subscription of this voice connection to an audio player is removed.
*
@@ -1543,11 +1818,20 @@ declare class VoiceConnection extends EventEmitter {
*/
interface CreateVoiceConnectionOptions {
adapterCreator: DiscordGatewayAdapterCreator;
/**
* Whether to use the DAVE protocol for end-to-end encryption. Defaults to true.
*/
daveEncryption?: boolean | undefined;
/**
* If true, debug messages will be enabled for the voice connection and its
* related components. Defaults to false.
*/
debug?: boolean | undefined;
/**
* The amount of consecutive decryption failures needed to try to
* re-initialize the end-to-end encrypted session to recover. Defaults to 24.
*/
decryptionFailureTolerance?: number | undefined;
}
/**
* The options that can be given when joining a voice channel.
@@ -1641,4 +1925,4 @@ declare function demuxProbe(stream: Readable, probeSize?: number, validator?: ty
*/
declare const version: string;
export { AudioPlayer, type AudioPlayerBufferingState, AudioPlayerError, type AudioPlayerIdleState, type AudioPlayerPausedState, type AudioPlayerPlayingState, type AudioPlayerState, AudioPlayerStatus, AudioReceiveStream, type AudioReceiveStreamOptions, AudioResource, type CreateAudioPlayerOptions, type CreateAudioResourceOptions, type CreateVoiceConnectionOptions, type DiscordGatewayAdapterCreator, type DiscordGatewayAdapterImplementerMethods, type DiscordGatewayAdapterLibraryMethods, type EndBehavior, EndBehaviorType, type JoinConfig, type JoinVoiceChannelOptions, NoSubscriberBehavior, PlayerSubscription, type ProbeInfo, SSRCMap, SpeakingMap, StreamType, VoiceConnection, type VoiceConnectionConnectingState, type VoiceConnectionDestroyedState, VoiceConnectionDisconnectReason, type VoiceConnectionDisconnectedBaseState, type VoiceConnectionDisconnectedOtherState, type VoiceConnectionDisconnectedState, type VoiceConnectionDisconnectedWebSocketState, type VoiceConnectionReadyState, type VoiceConnectionSignallingState, type VoiceConnectionState, VoiceConnectionStatus, VoiceReceiver, type VoiceUserData, createAudioPlayer, createAudioResource, createDefaultAudioReceiveStreamOptions, demuxProbe, entersState, generateDependencyReport, getGroups, getVoiceConnection, getVoiceConnections, joinVoiceChannel, validateDiscordOpusHead, version };
export { AudioPlayer, type AudioPlayerBufferingState, AudioPlayerError, type AudioPlayerIdleState, type AudioPlayerPausedState, type AudioPlayerPlayingState, type AudioPlayerState, AudioPlayerStatus, AudioReceiveStream, type AudioReceiveStreamOptions, AudioResource, type ConnectionData, type ConnectionOptions, type CreateAudioPlayerOptions, type CreateAudioResourceOptions, type CreateVoiceConnectionOptions, DAVESession, type DiscordGatewayAdapterCreator, type DiscordGatewayAdapterImplementerMethods, type DiscordGatewayAdapterLibraryMethods, type Edge, type EndBehavior, EndBehaviorType, type JoinConfig, type JoinVoiceChannelOptions, Networking, type NetworkingClosedState, type NetworkingIdentifyingState, type NetworkingOpeningWsState, type NetworkingReadyState, type NetworkingResumingState, type NetworkingSelectingProtocolState, type NetworkingState, NetworkingStatusCode, type NetworkingUdpHandshakingState, NoSubscriberBehavior, Node, PlayerSubscription, type ProbeInfo, SSRCMap, type SocketConfig, SpeakingMap, StreamType, TransformerType, VoiceConnection, type VoiceConnectionConnectingState, type VoiceConnectionDestroyedState, VoiceConnectionDisconnectReason, type VoiceConnectionDisconnectedBaseState, type VoiceConnectionDisconnectedOtherState, type VoiceConnectionDisconnectedState, type VoiceConnectionDisconnectedWebSocketState, type VoiceConnectionReadyState, type VoiceConnectionSignallingState, type VoiceConnectionState, VoiceConnectionStatus, VoiceReceiver, VoiceUDPSocket, type VoiceUserData, VoiceWebSocket, createAudioPlayer, createAudioResource, createDefaultAudioReceiveStreamOptions, demuxProbe, entersState, generateDependencyReport, getGroups, getVoiceConnection, getVoiceConnections, joinVoiceChannel, validateDiscordOpusHead, version };