aqualink
Version:
An Lavalink/Nodelink client, focused in pure performance and features
1,543 lines (1,369 loc) • 42.3 kB
TypeScript
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'
}
}