UNPKG

aqualink

Version:

An Lavalink/Nodelink client, focused in pure performance and features

1,543 lines (1,369 loc) 42.3 kB
import { EventEmitter } from 'node:events' declare module 'aqualink' { // Main Classes export class Aqua extends EventEmitter { constructor(client: any, nodes: NodeOptions[], options?: AquaOptions) // Core Properties client: any nodes: NodeOptions[] nodeMap: Map<string, Node> players: Map<string, Player> clientId: string | null initiated: boolean version: string options: AquaOptions failoverOptions: FailoverOptions // Configuration Properties shouldDeleteMessage: boolean defaultSearchPlatform: SearchSource leaveOnEnd: boolean restVersion: RestVersion plugins: Plugin[] autoResume: boolean infiniteReconnects: boolean urlFilteringEnabled?: boolean restrictedDomains: string[] allowedDomains: string[] loadBalancer: LoadBalancerStrategy send: (payload: any) => void autoRegionMigrate: boolean // Internal State Management _nodeStates: Map< string, { connected: boolean; failoverInProgress: boolean } > _failoverQueue: Map<string, number> _lastFailoverAttempt: Map<string, number> _brokenPlayers: Map<string, BrokenPlayerState> _rebuildLocks: Set<string> _leastUsedNodesCache: Node[] | null _leastUsedNodesCacheTime: number _nodeLoadCache: Map<string, { load: number; time: number }> _cleanupTimer: NodeJS.Timer | null _onNodeConnect?: (node: Node) => void _onNodeDisconnect?: (node: Node) => void // Getters get leastUsedNodes(): Node[] // Core Methods /** * Initializes the specific client id and connects to all nodes * @param clientId Client ID * @example * ```ts * await aqua.init(client.user.id); * ``` */ init(clientId: string): Promise<Aqua> /** * Creates a new node connection * @param options Modified node options */ createNode(options: NodeOptions): Promise<Node> /** * Destroys a node by identifier * @param identifier Node identifier (name or host) */ destroyNode(identifier: string): void /** * Updates the voice state of a player * @param data Voice state update packet */ updateVoiceState(data: VoiceStateUpdate | VoiceServerUpdate): void /** * Fetches nodes in a specific region * @param region Region name */ fetchRegion(region: string): Node[] /** * Creates a connection for a player * @param options Connection options */ createConnection(options: ConnectionOptions): Player /** * Creates a player on a specific node * @param node Node to create player on * @param options Player options */ createPlayer(node: Node, options: PlayerOptions): Player /** * Destroys a player * @param guildId Guild ID */ destroyPlayer(guildId: string): Promise<void> /** * Resolves a track or playlist * @param options Resolution options * @example * ```ts * const result = await aqua.resolve({ query: 'https://...', requester: user }); * ``` */ resolve(options: ResolveOptions): Promise<ResolveResponse> /** * Gets an existing player * @param guildId Guild ID */ get(guildId: string): Player /** * Searches for tracks * @param query Search query * @param requester Requester object * @param source Search source (ytsearch, scsearch, etc) */ search( query: string, requester: any, source?: SearchSource ): Promise<Track[] | null> // Save/Load Methods savePlayer(filePath?: string): Promise<void> savePlayerSync(filePath?: string): void loadPlayers(filePath?: string): Promise<void> // Failover and Migration Methods handleNodeFailover(failedNode: Node): Promise<void> /** * Moves a player to a different node * @param guildId Guild ID of the player * @param targetNode Target node to move to * @param reason Reason for migration (default: 'region') */ movePlayerToNode( guildId: string, targetNode: Node, reason?: string ): Promise<Player> // Utility Methods /** * Destroys the Aqua instance and all players */ destroy(): Promise<void> getTrace(limit?: number): TraceEntry[] // Internal Methods _invalidateCache(): void _getCachedNodeLoad(node: Node): number _calculateNodeLoad(node: Node): number _createNode(options: NodeOptions): Promise<Node> _destroyNode(identifier: string): void _cleanupNode(nodeId: string): void _storeBrokenPlayers(node: Node): void _rebuildBrokenPlayers(node: Node): Promise<void> _rebuildPlayer( brokenState: BrokenPlayerState, targetNode: Node ): Promise<Player> _migratePlayersOptimized( players: Player[], availableNodes: Node[] ): Promise<MigrationResult[]> _migratePlayer(player: Player, pickNode: () => Node): Promise<Player> _capturePlayerState(player: Player): PlayerState | null _createPlayerOnNode( targetNode: Node, playerState: PlayerState ): Promise<Player> _restorePlayerState( newPlayer: Player, playerState: PlayerState ): Promise<void> _getRequestNode(nodes?: string | Node | Node[]): Node _chooseLeastBusyNode(nodes: Node[]): Node | null _constructResponse( response: any, requester: any, requestNode: Node ): ResolveResponse _resolveSearchPlatform(source: string): SearchSource _getAvailableNodes(excludeNode?: Node): Node[] _performCleanup(): void _handlePlayerDestroy(player: Player): void _waitForFirstNode(timeout?: number): Promise<void> _restorePlayer(data: SavedPlayerData): Promise<void> _parseRequester(requesterString: string | null): any _loadPlugins(): Promise<void> _createDefaultSend(): (packet: any) => void _bindEventHandlers(): void _startCleanupTimer(): void _onNodeReady(node: Node, data: { resumed: boolean }): void _regionMatches(configuredRegion: string, extractedRegion: string): boolean _findBestNodeForRegion(region: string): Node | null // Optional bypass checks bypassChecks?: { nodeFetchInfo?: boolean } } export class Node extends EventEmitter { constructor( aqua: Aqua, connOptions: NodeOptions, options?: NodeAdditionalOptions ) // Core Properties aqua: Aqua host: string name: string port: number auth: string ssl: boolean sessionId: string | null regions: DiscordVoiceRegion[] wsUrl: string rest: Rest resumeTimeout: number autoResume: boolean reconnectTimeout: number reconnectTries: number infiniteReconnects: boolean connected: boolean info: NodeInfo | null isNodelink: boolean ws: any | null // WebSocket reconnectAttempted: number reconnectTimeoutId: NodeJS.Timeout | null isDestroyed: boolean stats: NodeStats players: Set<Player> options: NodeOptions // Additional Properties timeout: number maxPayload: number skipUTF8Validation: boolean _isConnecting: boolean _debugEnabled: boolean _headers: Record<string, string> _boundHandlers: Record<string, ReturnType<typeof this._boundHandlers>> // Methods connect(): Promise<void> destroy(clean?: boolean): void getStats(): Promise<NodeStats> // Internal Methods _handleOpen(): Promise<void> _handleError(error: any): void _handleMessage(data: any, isBinary: boolean): void _handleClose(code: number, reason: any): void _handleReady(payload: any): Promise<void> _resumePlayers(): Promise<void> _emitError(error: any): void _emitDebug(message: string | (() => string)): void } export class Player extends EventEmitter { constructor(aqua: Aqua, nodes: Node, options: PlayerOptions) // Static Properties static readonly LOOP_MODES: { readonly NONE: 0 readonly TRACK: 1 readonly QUEUE: 2 } static readonly EVENT_HANDLERS: Record<string, string> // Core Properties aqua: Aqua nodes: Node guildId: string textChannel: string voiceChannel: string connection: Connection filters: Filters state: number txId: number volume: number loop: LoopModeName | LoopMode queue: Queue shouldDeleteMessage: boolean leaveOnEnd: boolean playing: boolean paused: boolean connected: boolean destroyed: boolean current: Track | null position: number timestamp: number ping: number nowPlayingMessage: any isAutoplayEnabled: boolean isAutoplay: boolean autoplaySeed: AutoplaySeed | null deaf: boolean mute: boolean autoplayRetries: number reconnectionRetries: number _resuming: boolean _reconnecting: boolean previousIdentifiers: Set<string> self_deaf: boolean self_mute: boolean // Additional Internal Properties previousTracks: CircularBuffer _updateBatcher: MicrotaskUpdateBatcher _dataStore: Map<string, any> | null _voiceDownSince: number _voiceRecovering: boolean _voiceWatchdogTimer: NodeJS.Timer | null _boundPlayerUpdate: (packet: any) => void _boundEvent: (payload: any) => void _boundAquaPlayerMove: (oldChannel: string, newChannel: string) => void _lastVoiceChannel: string | null _lastTextChannel: string | null // Getters get previous(): Track | null get currenttrack(): Track | null // Core Methods /** * Plays a track. If no track is provided, plays the next track in the queue. * @param track The track to play * @param options Options for playback * @example * ```ts * // Play the next track in the queue * await player.play(); * * // Play a specific track * await player.play(track); * ``` */ play( track?: Track | null, options?: { paused?: boolean; startTime?: number; noReplace?: boolean } ): Promise<Player> /** * Connects the player to a voice channel * @param options Connection options * @example * ```ts * player.connect({ * guildId: '...', * voiceChannel: '...', * deaf: true * }); * ``` */ connect(options?: ConnectionOptions): Player /** * Destroys the player and optionally cleans up resources * @param options Destruction options */ destroy(options?: { preserveClient?: boolean skipRemote?: boolean preserveMessage?: boolean preserveReconnecting?: boolean preserveTracks?: boolean }): Player /** * Pauses or resumes the player * @param paused Whether to pause */ pause(paused: boolean): Player /** * Seeks to a position in the current track * @param position Position in milliseconds */ seek(position: number): Player /** * Stops the playback */ stop(): Player /** * Sets the player volume * @param volume Volume (0-1000) */ setVolume(volume: number): Player /** * Sets the loop mode * @param mode Loop mode (off, track, queue) */ setLoop(mode: LoopMode | LoopModeName): Player /** * Sets the text channel for the player * @param channel Channel ID */ setTextChannel(channel: string): Player /** * Sets the voice channel and moves the player * @param channel Channel ID */ setVoiceChannel(channel: string): Player /** * Disconnects the player from voice */ disconnect(): Player /** * Shuffles the queue */ shuffle(): Player /** * Gets the player queue */ getQueue(): Queue /** * Replays the current track from the beginning */ replay(): Player /** * Skips the current track */ skip(): Player // Advanced Methods getLyrics(options?: LyricsOptions): Promise<LyricsResponse | null> subscribeLiveLyrics(): Promise<any> unsubscribeLiveLyrics(): Promise<any> liveLyrics(guildId: string, state: boolean): Promise<any> autoplay(): Promise<Player> setAutoplay(enabled: boolean): Player updatePlayer(data: any): Promise<any> cleanup(): Promise<void> getActiveMixer(guildId: string): Promise<any[]> updateMixerVolume( guildId: string, mix: string, volume: number ): Promise<any> removeMixer(guildId: string, mix: string): Promise<any> addMixer(guildId: string, options: MixerOptions): Promise<any> getLoadLyrics(encodedTrack: string): Promise<LyricsResponse | null> // Data Methods set(key: string, value: any): void get(key: string): any clearData(): Player // Utility Methods send(data: any): void batchUpdatePlayer(data: any, immediate?: boolean): Promise<void> // Internal Methods _parseLoop(loop: any): LoopMode _bindEvents(): void _startWatchdog(): void _handlePlayerUpdate(packet: any): void _handleEvent(payload: any): Promise<void> _voiceWatchdog(): Promise<void> _attemptVoiceResume(): Promise<void> _getAutoplayTrack( sourceName: string, identifier: string, uri: string, requester: any, prev: Track ): Promise<Track | null> _handleAquaPlayerMove(oldChannel: string, newChannel: string): void // Event handler methods (called internally) trackStart(player: Player, track: Track): Promise<void> trackEnd(player: Player, track: Track, payload: any): Promise<void> trackError(player: Player, track: Track, payload: any): Promise<void> trackStuck(player: Player, track: Track, payload: any): Promise<void> trackChange(player: Player, track: Track, payload: any): Promise<void> socketClosed(player: Player, track: Track, payload: any): Promise<void> lyricsLine(player: Player, track: Track, payload: any): Promise<void> lyricsFound(player: Player, track: Track, payload: any): Promise<void> lyricsNotFound(player: Player, track: Track, payload: any): Promise<void> } export class Track { constructor(data?: TrackData, requester?: any, node?: Node) // Properties identifier: string isSeekable: boolean author: string position: number duration: number isStream: boolean title: string uri: string sourceName: string artworkUrl: string track: string | null playlist: PlaylistInfo | null requester: any nodes: Node node: Node | null // Internal Properties _infoCache: TrackInfo | null // Getters get info(): TrackInfo get length(): number get thumbnail(): string // Methods /** * Resolves local artwork/thumbnail */ resolveThumbnail(url?: string): string | null /** * Resolves the track if it needs (re)resolution */ resolve(aqua: Aqua, opts?: TrackResolutionOptions): Promise<Track | null> /** * Checks if the track is valid */ isValid(): boolean /** * Disposes the track and frees resources */ dispose(): void // Internal Methods _computeArtworkFromKnownSources(): string | null } export class Rest { constructor(aqua: Aqua, node: Node) aqua: Aqua node: Node sessionId: string calls: number // Additional Properties timeout: number baseUrl: string defaultHeaders: Record<string, string> agent: any // HTTP/HTTPS Agent useHttp2: boolean // Core Methods /** * Sets the session ID for the REST connection * @param sessionId The session ID */ setSessionId(sessionId: string): void /** * Makes a generic request to the Lavalink REST API * @param method HTTP method * @param endpoint API endpoint * @param body Request body */ makeRequest(method: HttpMethod, endpoint: string, body?: any): Promise<any> /** * Updates a player via REST * @param options Update options */ updatePlayer(options: UpdatePlayerOptions): Promise<any> /** * Destroys a player via REST * @param guildId Guild ID */ destroyPlayer(guildId: string): Promise<any> /** * Gets lyrics for a track * @param options Lyrics options */ getLyrics(options: GetLyricsOptions): Promise<LyricsResponse> /** * Subscribes to live lyrics events * @param guildId Guild ID * @param sync Whether to sync with playback */ subscribeLiveLyrics(guildId: string, sync?: boolean): Promise<any> /** * Unsubscribes from live lyrics * @param guildId Guild ID */ unsubscribeLiveLyrics(guildId: string): Promise<any> /** * Gets node statistics */ getStats(): Promise<NodeStats> // Additional REST Methods getPlayer(guildId: string): Promise<any> getPlayers(): Promise<any> decodeTrack(encodedTrack: string): Promise<any> decodeTracks(encodedTracks: string[]): Promise<any> getInfo(): Promise<NodeInfo> getVersion(): Promise<string> getRoutePlannerStatus(): Promise<any> freeRoutePlannerAddress(address: string): Promise<any> freeAllRoutePlannerAddresses(): Promise<any> addMixer(guildId: string, options: MixerOptions): Promise<any> getActiveMixer(guildId: string): Promise<any[]> updateMixerVolume( guildId: string, mix: string, volume: number ): Promise<any> removeMixer(guildId: string, mix: string): Promise<any> getLoadLyrics(encodedTrack: string): Promise<LyricsResponse> loadTracks(identifier: string): Promise<any> destroy(): void } export class Queue extends Array<Track> { constructor(...elements: Track[]) // Properties readonly size: number readonly first: Track | null readonly last: Track | null // Methods /** * Adds a track to the queue * @param track Track to add */ add(track: Track): Queue /** * Adds multiple tracks to the queue * @param tracks Tracks to add */ add(...tracks: Track[]): Queue push(track: Track): number unshift(track: Track): number shift(): Track | undefined remove(track: Track): boolean /** * Clears the queue */ clear(): void /** * Shuffles the queue */ shuffle(): Queue peek(): Track | null isEmpty(): boolean toArray(): Track[] at(index: number): Track | null dequeue(): Track | undefined enqueue(track: Track): Queue move(from: number, to: number): Queue swap(index1: number, index2: number): Queue } export class Filters { constructor(player: Player, options?: FilterOptions) player: Player _pendingUpdate: boolean filters: { volume: number equalizer: EqualizerBand[] karaoke: KaraokeSettings | null timescale: TimescaleSettings | null tremolo: TremoloSettings | null vibrato: VibratoSettings | null rotation: RotationSettings | null distortion: DistortionSettings | null channelMix: ChannelMixSettings | null lowPass: LowPassSettings | null } presets: { bassboost: number | null slowmode: boolean | null nightcore: boolean | null vaporwave: boolean | null _8d: boolean | null } // Filter Methods setEqualizer(bands: EqualizerBand[]): Filters setKaraoke(enabled: boolean, options?: KaraokeSettings): Filters setTimescale(enabled: boolean, options?: TimescaleSettings): Filters setTremolo(enabled: boolean, options?: TremoloSettings): Filters setVibrato(enabled: boolean, options?: VibratoSettings): Filters setRotation(enabled: boolean, options?: RotationSettings): Filters setDistortion(enabled: boolean, options?: DistortionSettings): Filters setChannelMix(enabled: boolean, options?: ChannelMixSettings): Filters setLowPass(enabled: boolean, options?: LowPassSettings): Filters setBassboost(enabled: boolean, options?: { value?: number }): Filters setSlowmode(enabled: boolean, options?: { rate?: number }): Filters setNightcore(enabled: boolean, options?: { rate?: number }): Filters setVaporwave(enabled: boolean, options?: { pitch?: number }): Filters set8D(enabled: boolean, options?: { rotationHz?: number }): Filters clearFilters(): Promise<Filters> updateFilters(): Promise<Filters> // Internal Methods _setFilter(filterName: string, enabled: boolean, options?: any): Filters _scheduleUpdate(): Filters } export class Connection { constructor(player: Player) voiceChannel: string sessionId: string | null endpoint: string | null token: string | null region: string | null sequence: number // Internal Properties _player: Player _aqua: Aqua _nodes: Node _guildId: string _clientId: string _lastEndpoint: string | null _pendingUpdate: any _updateTimer: NodeJS.Timeout | null _hasDebugListeners: boolean _hasMoveListeners: boolean _lastSentVoiceKey: string _lastVoiceDataUpdate: number _stateFlags: number _regionMigrationAttempted: boolean // Methods setServerUpdate(data: VoiceServerUpdate['d']): void setStateUpdate(data: VoiceStateUpdate['d']): void updateSequence(seq: number): void destroy(): void attemptResume(): Promise<boolean> resendVoiceUpdate(): boolean // Internal Methods _extractRegion(endpoint: string): string | null _scheduleVoiceUpdate(isResume?: boolean): void _executeVoiceUpdate(): void _sendUpdate(payload: any): Promise<void> _handleDisconnect(): void _clearPendingUpdate(): void _checkRegionMigration(): void } export class Plugin { constructor(name: string) name: string load(aqua: Aqua): void | Promise<void> unload?(aqua: Aqua): void | Promise<void> } // Utility Classes export class MicrotaskUpdateBatcher { constructor(player: Player) player: Player | null updates: any scheduled: number flush: () => Promise<void> batch(data: any, immediate?: boolean): Promise<void> destroy(): void _flush(): Promise<void> } export class CircularBuffer { constructor(size?: number) buffer: any[] size: number index: number count: number push(item: any): void getLast(): any clear(): void toArray(): any[] } // Configuration Interfaces export interface AquaOptions { shouldDeleteMessage?: boolean defaultSearchPlatform?: SearchSource leaveOnEnd?: boolean restVersion?: RestVersion plugins?: Plugin[] send?: (payload: any) => void autoResume?: boolean infiniteReconnects?: boolean urlFilteringEnabled?: boolean restrictedDomains?: string[] allowedDomains?: string[] loadBalancer?: LoadBalancerStrategy failoverOptions?: FailoverOptions useHttp2?: boolean autoRegionMigrate?: boolean debugTrace?: boolean traceMaxEntries?: number } export interface FailoverOptions { enabled?: boolean maxRetries?: number retryDelay?: number preservePosition?: boolean resumePlayback?: boolean cooldownTime?: number maxFailoverAttempts?: number } export interface NodeOptions { host: string name?: string port?: number | string auth?: string ssl?: boolean sessionId?: string regions?: DiscordVoiceRegion[] } export interface NodeAdditionalOptions { resumeTimeout?: number autoResume?: boolean reconnectTimeout?: number reconnectTries?: number infiniteReconnects?: boolean timeout?: number maxPayload?: number skipUTF8Validation?: boolean } export interface PlayerOptions { guildId: string textChannel: string voiceChannel: string defaultVolume?: number loop?: LoopModeName deaf?: boolean mute?: boolean } export interface ConnectionOptions { guildId: string voiceChannel: string textChannel?: string deaf?: boolean mute?: boolean defaultVolume?: number region?: DiscordVoiceRegion } export interface ResolveOptions { query: string source?: SearchSource | string requester: any nodes?: string | Node | Node[] } // Response and Data Interfaces export interface ResolveResponse { loadType: LoadType exception: LavalinkException | null playlistInfo: PlaylistInfo | null pluginInfo: Record<string, any> tracks: Track[] } export interface NodeStats { players: number playingPlayers: number uptime: number memory: { free: number used: number allocated: number reservable: number } cpu: { cores: number systemLoad: number lavalinkLoad: number } frameStats: { sent: number nulled: number deficit: number } ping?: number } export interface NodeInfo { version: { semver: string major: number minor: number patch: number } buildTime: number git: { branch: string commit: string commitTime: number } jvm: string lavaplayer: string sourceManagers: string[] filters: string[] plugins: Array<{ name: string version: string }> } export interface TrackInfo { identifier: string isSeekable: boolean author: string length: number isStream: boolean title: string uri: string sourceName: string artworkUrl: string position?: number } export interface TrackData { encoded?: string track?: string info: TrackInfo playlist?: PlaylistInfo node?: Node nodes?: Node } export interface PlaylistInfo { name: string selectedTrack?: number thumbnail?: string title?: string } export interface LavalinkException { message: string severity: string cause: string } // Filter Interfaces export interface FilterOptions { volume?: number equalizer?: EqualizerBand[] karaoke?: KaraokeSettings timescale?: TimescaleSettings tremolo?: TremoloSettings vibrato?: VibratoSettings rotation?: RotationSettings distortion?: DistortionSettings channelMix?: ChannelMixSettings lowPass?: LowPassSettings bassboost?: number slowmode?: boolean nightcore?: boolean vaporwave?: boolean _8d?: boolean } export interface EqualizerBand { band: number gain: number } export interface KaraokeSettings { level?: number monoLevel?: number filterBand?: number filterWidth?: number } export interface TimescaleSettings { speed?: number pitch?: number rate?: number } export interface TremoloSettings { frequency?: number depth?: number } export interface VibratoSettings { frequency?: number depth?: number } export interface RotationSettings { rotationHz?: number } export interface DistortionSettings { distortion?: number sinOffset?: number sinScale?: number cosOffset?: number cosScale?: number tanOffset?: number tanScale?: number offset?: number scale?: number } export interface ChannelMixSettings { leftToLeft?: number leftToRight?: number rightToLeft?: number rightToRight?: number } export interface LowPassSettings { smoothing?: number } export interface MixerOptions { identifier?: string encoded?: string userData?: any volume?: number } // Voice Update Interfaces export interface VoiceStateUpdate { d: { guild_id: string channel_id: string | null user_id: string session_id: string deaf: boolean mute: boolean self_deaf: boolean self_mute: boolean suppress: boolean request_to_speak_timestamp: string | null } t: 'VOICE_STATE_UPDATE' } export interface VoiceServerUpdate { d: { token: string guild_id: string endpoint: string | null } t: 'VOICE_SERVER_UPDATE' } // Utility Interfaces export interface LyricsOptions { query?: string useCurrentTrack?: boolean skipTrackSource?: boolean } export interface LyricsResponse { text?: string source?: string lines?: Array<{ line: string timestamp?: number }> } export interface AutoplaySeed { trackId: string artistIds: string } export interface UpdatePlayerOptions { guildId: string data: { track?: { encoded: string | null } position?: number volume?: number paused?: boolean filters?: any voice?: any } } export interface GetLyricsOptions { track: { info: TrackInfo encoded?: string identifier?: string guild_id?: string } skipTrackSource?: boolean } // State Management Interfaces export interface PlayerState { guildId: string textChannel: string voiceChannel: string volume: number paused: boolean position: number current: Track | null queue: Track[] repeat: LoopMode shuffle: boolean deaf: boolean connected: boolean } export interface BrokenPlayerState extends PlayerState { originalNodeId: string brokenAt: number } export interface MigrationResult { success: boolean error?: any } export interface SavedPlayerData { g: string // guildId t: string // textChannel v: string // voiceChannel u: string | null // uri p: number // position ts: number // timestamp q: string[] // queue uris r: string | null // requester vol: number // volume pa: boolean // paused pl: boolean // playing nw: string | null // nowPlayingMessage id } export interface TrackResolutionOptions { platform?: SearchSource node?: Node toFront?: boolean } /** * A map of Discord voice region codes to their string values. * Each entry shows the country and airport name in IntelliSense. * * Use `VoiceRegion.bom`, `VoiceRegion.gru`, etc. for full autocomplete descriptions, * or pass raw strings like `'bom'` directly both are accepted by `NodeOptions.regions`. * * @example * ```ts * // With descriptions in IntelliSense * { host: '...', regions: [VoiceRegion.gru, VoiceRegion.eze] } * * // Raw strings also work * { host: '...', regions: ['gru', 'eze'] } * ``` */ export const VoiceRegion: { // ─── Asia Pacific ───────────────────────────────────────────────────────── /** 🇮🇳 Mumbai Chhatrapati Shivaji Maharaj International */ readonly India: 'bom' /** 🇸🇬 Changi Airport */ readonly Singapore: 'sin' /** 🇯🇵 Tokyo Narita International */ readonly Japan: 'nrt' /** 🇰🇷 Seoul Incheon International */ readonly SouthKorea: 'icn' /** 🇭🇰 Hong Kong International */ readonly HongKong: 'hkg' /** 🇦🇺 Sydney Kingsford Smith */ readonly Australia: 'syd' /** 🇮🇩 Jakarta Soekarno-Hatta International */ readonly Indonesia: 'cgk' // ─── Europe ─────────────────────────────────────────────────────────────── /** 🇩🇪 Frankfurt Airport */ readonly Germany: 'fra' /** 🇳🇱 Amsterdam Schiphol */ readonly Netherlands: 'ams' /** 🇬🇧 London Heathrow */ readonly UnitedKingdom: 'lhr' /** 🇫🇷 Paris Charles de Gaulle */ readonly France: 'cdg' /** 🇪🇸 Madrid Barajas */ readonly Spain: 'mad' /** 🇮🇹 Milan Malpensa */ readonly Italy: 'mxp' /** 🇸🇪 Stockholm Arlanda */ readonly Sweden: 'arn' /** 🇫🇮 Helsinki Vantaa */ readonly Finland: 'hel' /** 🇵🇱 Warsaw Chopin */ readonly Poland: 'waw' /** 🇷🇴 Bucharest Henri Coanda */ readonly Romania: 'buh' /** 🇷🇺 St. Petersburg Pulkovo */ readonly RussiaSTP: 'led' /** 🇷🇺 Moscow Sheremetyevo */ readonly RussiaMoscow: 'svo' // ─── Middle East & Africa ───────────────────────────────────────────────── /** 🇮🇱 Tel Aviv Ben Gurion */ readonly Israel: 'tlv' /** 🇦🇪 Dubai International */ readonly UAE: 'dxb' /** 🇸🇦 Dammam King Fahd International */ readonly SaudiArabia: 'dmm' /** 🇿🇦 Johannesburg O.R. Tambo International */ readonly SouthAfrica: 'jnb' // ─── North America ──────────────────────────────────────────────────────── /** 🇺🇸 Newark / New York */ readonly USANewark: 'ewr' /** 🇺🇸 Washington D.C. Dulles */ readonly USAWashington: 'iad' /** 🇺🇸 Atlanta Hartsfield-Jackson */ readonly USAAtlanta: 'atl' /** 🇺🇸 Miami International */ readonly USAMiami: 'mia' /** 🇺🇸 Chicago O'Hare */ readonly USAChicago: 'ord' /** 🇺🇸 Dallas/Fort Worth */ readonly USADallas: 'dfw' /** 🇺🇸 Seattle-Tacoma / Oregon */ readonly USASeattle: 'sea' /** 🇺🇸 Los Angeles International */ readonly USALosAngeles: 'lax' /** 🇨🇦 Toronto Pearson International */ readonly CanadaToronto: 'yyz' /** 🇨🇦 Montreal Pierre Elliott Trudeau */ readonly CanadaMontreal: 'ymq' // ─── South America ──────────────────────────────────────────────────────── /** 🇧🇷 Sao Paulo Guarulhos International */ readonly Brazil: 'gru' /** 🇨🇱 Santiago Arturo Merino Benitez */ readonly Chile: 'scl' /** 🇦🇷 Buenos Aires Ministro Pistarini */ readonly Argentina: 'eze' } /** * Discord voice server region prefix (derived from IATA airport codes). * Used in `NodeOptions.regions` to match players to the best Lavalink node * based on Discord's voice endpoint (e.g. `c-bom06-xxxx.discord.media` -> `'bom'`). * * For descriptions on each value, use the `VoiceRegion` object in your IDE. */ export type DiscordVoiceRegion = | (typeof VoiceRegion)[keyof typeof VoiceRegion] | (string & {}) // Type Unions and Enums export type SearchSource = | 'ytsearch' | 'ytmsearch' | 'scsearch' | 'spsearch' | 'amsearch' | 'dzsearch' | 'yandexsearch' | 'soundcloud' | 'youtube' | 'spotify' | 'applemusic' | 'deezer' | 'bandcamp' | 'vimeo' | 'twitch' | 'http' export type LoopMode = 0 | 1 | 2 export type LoopModeName = 'none' | 'track' | 'queue' export type LoadType = | 'track' | 'playlist' | 'search' | 'empty' | 'error' | 'LOAD_FAILED' | 'NO_MATCHES' export type RestVersion = 'v3' | 'v4' export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' export type LoadBalancerStrategy = 'leastLoad' | 'leastRest' | 'random' export type EventHandler<T = any> = (...args: T[]) => void | Promise<void> // Event Interfaces export interface AquaEvents { nodeConnect: (node: Node) => void nodeConnected: (node: Node) => void nodeDisconnect: (node: Node, data: { code: number; reason: string }) => void nodeError: (node: Node, error: Error) => void nodeReconnect: (node: Node, data: any) => void nodeCreate: (node: Node) => void nodeDestroy: (node: Node) => void nodeReady: (node: Node, data: any) => void nodeFailover: (node: Node) => void nodeFailoverComplete: ( node: Node, successful: number, failed: number ) => void playerCreate: (player: Player) => void playerDestroy: (player: Player) => void playerUpdate: (player: Player, packet: any) => void playerMigrated: ( oldPlayer: Player, newPlayer: Player, targetNode: Node ) => void playerReconnected: (player: Player, data: any) => void trackStart: (player: Player, track: Track) => void trackEnd: (player: Player, track: Track, reason?: string) => void trackError: (player: Player, track: Track, error: any) => void trackStuck: (player: Player, track: Track, thresholdMs: number) => void trackChange: (player: Player, track: Track, payload: any) => void queueEnd: (player: Player) => void playerMove: (oldChannel: string, newChannel: string) => void playersRebuilt: (node: Node, count: number) => void reconnectionFailed: (player: Player, data: any) => void socketClosed: (player: Player, payload: any) => void lyricsLine: (player: Player, track: Track, payload: any) => void lyricsFound: (player: Player, track: Track, payload: any) => void lyricsNotFound: (player: Player, track: Track, payload: any) => void autoplayFailed: (player: Player, error: Error) => void debug: (source: string, message: string) => void error: (node: Node | null, error: Error) => void } export interface PlayerEvents { destroy: () => void playerUpdate: (packet: any) => void event: (payload: any) => void trackStart: (track: Track) => void trackEnd: (track: Track, reason?: string) => void trackError: (track: Track, error: any) => void trackStuck: (track: Track, thresholdMs: number) => void trackChange: (track: Track, payload: any) => void socketClosed: (payload: any) => void lyricsLine: (track: Track, payload: any) => void lyricsFound: (track: Track, payload: any) => void lyricsNotFound: (track: Track, payload: any) => void } // Event Emitter Type Extensions for Aqua interface Aqua { on<K extends keyof AquaEvents>(event: K, listener: AquaEvents[K]): this on(event: string | symbol, listener: (...args: any[]) => void): this once<K extends keyof AquaEvents>(event: K, listener: AquaEvents[K]): this once(event: string | symbol, listener: (...args: any[]) => void): this emit<K extends keyof AquaEvents>( event: K, ...args: Parameters<AquaEvents[K]> ): boolean emit(event: string | symbol, ...args: any[]): boolean off<K extends keyof AquaEvents>(event: K, listener: AquaEvents[K]): this off(event: string | symbol, listener: (...args: any[]) => void): this removeListener<K extends keyof AquaEvents>( event: K, listener: AquaEvents[K] ): this removeListener( event: string | symbol, listener: (...args: any[]) => void ): this addListener<K extends keyof AquaEvents>( event: K, listener: AquaEvents[K] ): this addListener( event: string | symbol, listener: (...args: any[]) => void ): this removeAllListeners<K extends keyof AquaEvents>(event?: K): this removeAllListeners(event?: string | symbol): this } interface Player { on<K extends keyof PlayerEvents>(event: K, listener: PlayerEvents[K]): this on(event: string | symbol, listener: (...args: any[]) => void): this once<K extends keyof PlayerEvents>( event: K, listener: PlayerEvents[K] ): this once(event: string | symbol, listener: (...args: any[]) => void): this emit<K extends keyof PlayerEvents>( event: K, ...args: Parameters<PlayerEvents[K]> ): boolean emit(event: string | symbol, ...args: any[]): boolean off<K extends keyof PlayerEvents>(event: K, listener: PlayerEvents[K]): this off(event: string | symbol, listener: (...args: any[]) => void): this removeListener<K extends keyof PlayerEvents>( event: K, listener: PlayerEvents[K] ): this removeListener( event: string | symbol, listener: (...args: any[]) => void ): this addListener<K extends keyof PlayerEvents>( event: K, listener: PlayerEvents[K] ): this addListener( event: string | symbol, listener: (...args: any[]) => void ): this removeAllListeners<K extends keyof PlayerEvents>(event?: K): this removeAllListeners(event?: string | symbol): this } // Additional Filter Preset Options export interface FilterPresetOptions { value?: number rate?: number pitch?: number rotationHz?: number } // Error Extensions export interface AquaError extends Error { statusCode?: number statusMessage?: string headers?: Record<string, string> body?: any } // Extended ResolveOptions for internal use export interface ExtendedResolveOptions extends ResolveOptions { node?: Node } // Export constants export const LOOP_MODES: { readonly NONE: 0 readonly TRACK: 1 readonly QUEUE: 2 } export const AqualinkEvents: { readonly TrackStart: 'trackStart' readonly TrackEnd: 'trackEnd' readonly TrackError: 'trackError' readonly TrackStuck: 'trackStuck' readonly TrackChange: 'trackChange' readonly SocketClosed: 'socketClosed' readonly LyricsLine: 'lyricsLine' readonly LyricsFound: 'lyricsFound' readonly LyricsNotFound: 'lyricsNotFound' readonly QueueEnd: 'queueEnd' readonly PlayerUpdate: 'playerUpdate' readonly PlayerMove: 'playerMove' readonly PlayerReconnected: 'playerReconnected' readonly AutoplayFailed: 'autoplayFailed' readonly ReconnectionFailed: 'reconnectionFailed' readonly NodeConnect: 'nodeConnect' readonly NodeCreate: 'nodeCreate' readonly NodeError: 'nodeError' readonly NodeDisconnect: 'nodeDisconnect' readonly NodeReconnect: 'nodeReconnect' readonly NodeDestroy: 'nodeDestroy' readonly NodeReady: 'nodeReady' readonly NodeCustomOp: 'nodeCustomOp' readonly NodeFailover: 'nodeFailover' readonly NodeFailoverComplete: 'nodeFailoverComplete' readonly Debug: 'debug' readonly Error: 'error' readonly PlayerCreate: 'playerCreate' readonly PlayerDestroy: 'playerDestroy' readonly PlayersRebuilt: 'playersRebuilt' readonly PlayerMigrated: 'playerMigrated' } }