auralis-sdk 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +27 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +27 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -537,10 +537,13 @@ var Player = class {
|
|
|
537
537
|
if (this.current !== null) this.pushHistory(this.current);
|
|
538
538
|
return this.queue.shift();
|
|
539
539
|
}
|
|
540
|
-
if (this.loop === "queue" /* Queue */
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
540
|
+
if (this.loop === "queue" /* Queue */) {
|
|
541
|
+
const cycle = [...this.history, ...this.current ? [this.current] : []];
|
|
542
|
+
if (cycle.length > 0) {
|
|
543
|
+
this.queue = cycle;
|
|
544
|
+
this.history = [];
|
|
545
|
+
return this.queue.shift() ?? null;
|
|
546
|
+
}
|
|
544
547
|
}
|
|
545
548
|
if (this.autoplay !== null) return this.autoplay(this);
|
|
546
549
|
return null;
|
|
@@ -552,14 +555,27 @@ var Player = class {
|
|
|
552
555
|
return this.serialize(() => this.advanceInner(forced));
|
|
553
556
|
}
|
|
554
557
|
async advanceInner(forced = false) {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
558
|
+
for (let attempts = this.queue.length + 1; attempts >= 0; attempts--) {
|
|
559
|
+
const next = await this.nextTrack(forced);
|
|
560
|
+
if (next === null) {
|
|
561
|
+
this.current = null;
|
|
562
|
+
if (this.created) await this.host.rest.stop(this.guildId);
|
|
563
|
+
this.host.emit("queueEnd", this);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
try {
|
|
567
|
+
await this.playTrack(next);
|
|
568
|
+
return;
|
|
569
|
+
} catch (e) {
|
|
570
|
+
if (e instanceof NotConnected) {
|
|
571
|
+
this.queue.unshift(next);
|
|
572
|
+
throw e;
|
|
573
|
+
}
|
|
574
|
+
this.current = null;
|
|
575
|
+
forced = true;
|
|
576
|
+
this.host.emit("trackException", this, { track: next, error: String(e) });
|
|
577
|
+
}
|
|
561
578
|
}
|
|
562
|
-
await this.playTrack(next);
|
|
563
579
|
}
|
|
564
580
|
async playTrack(track) {
|
|
565
581
|
if (!this.connected) throw new NotConnected(`guild ${this.guildId} has no voice connection`);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/errors.ts","../src/types.ts","../src/player.ts","../src/rest.ts"],"sourcesContent":["/**\n * auralis-sdk — the official Node.js SDK for the Auralis music platform.\n *\n * The queue, skip, loop, and shuffle all live in this library — the backend only\n * plays what it is told. See {@link Player} for the full command surface.\n *\n * ```ts\n * import { Client, LoopMode } from \"auralis-sdk\";\n *\n * const { apiBaseUrl, token } = await loadAuralisConfig();\n * const client = new Client(apiBaseUrl, token);\n * await client.start();\n * client.on(\"trackStart\", (player) => console.log(\"now playing:\", player.current?.title));\n *\n * const player = client.getPlayer(guildId);\n * // ... wire player.handleVoiceServerUpdate / handleVoiceStateUpdate from your bot ...\n * await player.play(\"ytsearch:never gonna give you up\");\n * ```\n */\n\nexport { Client } from \"./client.js\";\nexport type { ClientOptions, ClientEvents } from \"./client.js\";\nexport { Player } from \"./player.js\";\nexport type { PlayerHost, AutoplayHook, EqPreset, SerializedPlayer } from \"./player.js\";\nexport { REST } from \"./rest.js\";\nexport {\n LoopMode,\n LoadType,\n EndReason,\n endReasonAdvances,\n} from \"./types.js\";\nexport type {\n Snowflake,\n Track,\n LoadResult,\n PlayerState,\n VoiceServerUpdate,\n VoiceStateUpdate,\n} from \"./types.js\";\nexport {\n AuralisError,\n AuthError,\n RateLimited,\n APIError,\n NotConnected,\n LoadError,\n} from \"./errors.js\";\n","/** The top-level client: owns the REST wrapper, the WebSocket event loop, the\n * per-guild players, and event dispatch (via Node's EventEmitter). */\n\nimport { EventEmitter } from \"node:events\";\nimport WebSocket from \"ws\";\n\nimport { APIError, LoadError } from \"./errors.js\";\nimport { Player, PlayerHost } from \"./player.js\";\nimport { REST } from \"./rest.js\";\nimport { LoadResult, LoadType, Snowflake, Track, endReasonAdvances, playerStateFromJson } from \"./types.js\";\n\nexport interface ClientOptions {\n reconnect?: boolean;\n}\n\n/** Strongly-typed event map for `client.on(...)` / `client.emit(...)`. */\nexport interface ClientEvents {\n ready: [];\n /** Fired instead of `ready` when a reconnect re-establishes the session. */\n resumed: [];\n disconnect: [];\n error: [err: unknown];\n trackStart: [player: Player | bigint, data: any];\n trackEnd: [player: Player | bigint, data: any];\n trackException: [player: Player | bigint, data: any];\n trackStuck: [player: Player | bigint, data: any];\n playerUpdate: [player: Player | bigint, data: any];\n voiceClosed: [player: Player | bigint, data: any];\n // STATS is node-wide, so there may be no player/guild attached.\n stats: [player: Player | bigint | null, data: any];\n queueEnd: [player: Player];\n /** A dead source could not be recovered — the bot should tell the user. */\n trackStreamDead: [player: Player, data: { failed: Track }];\n /** A dead source was auto-recovered by playing an alternate upload. */\n trackRecovered: [player: Player, data: { failed: Track; alternate: Track }];\n /** Emitted when a new Player is created via {@link Client.getPlayer}. */\n playerCreated: [player: Player];\n /** Emitted when a player is fully torn down via {@link Player.disconnect}. */\n playerDestroyed: [guildId: bigint];\n}\n\n// WS op -> the event name we emit to listeners.\nconst EVENT_OPS: Record<string, keyof ClientEvents> = {\n TRACK_START: \"trackStart\",\n TRACK_END: \"trackEnd\",\n TRACK_EXCEPTION: \"trackException\",\n TRACK_STUCK: \"trackStuck\",\n PLAYER_UPDATE: \"playerUpdate\",\n VOICE_CLOSED: \"voiceClosed\",\n STATS: \"stats\",\n};\n\n// Declaration-merge typed on/once/off/emit overloads onto the class.\nexport interface Client {\n on<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this;\n once<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this;\n off<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this;\n emit<K extends keyof ClientEvents>(event: K, ...args: ClientEvents[K]): boolean;\n}\n\nexport class Client extends EventEmitter implements PlayerHost {\n readonly rest: REST;\n private baseUrl: string;\n private token: string;\n private reconnect: boolean;\n\n private playersMap = new Map<bigint, Player>();\n private ws: WebSocket | null = null;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private sessionId: string | null = null;\n private closed = false;\n private backoff = 1000;\n private hasConnected = false;\n private heartbeatSentAt = 0;\n\n /** Last measured heartbeat round-trip latency in ms (-1 until first ack). */\n latencyMs = -1;\n /** Most recent STATS frame payload from the node, or null. */\n lastStats: Record<string, unknown> | null = null;\n\n constructor(baseUrl: string, token: string, opts: ClientOptions = {}) {\n super();\n this.baseUrl = baseUrl.replace(/\\/$/, \"\");\n this.token = token;\n this.reconnect = opts.reconnect ?? true;\n this.rest = new REST(this.baseUrl, token);\n }\n\n // --- lifecycle --------------------------------------------------------\n async start(): Promise<void> {\n this.closed = false;\n this.openSocket();\n }\n\n /** Resolves once the client fires `ready` or `resumed`. If it is already\n * connected it resolves on the next tick. Rejects after `timeoutMs` (default\n * 30 s) so callers don't hang forever on a bad URL/token. */\n waitForReady(timeoutMs = 30_000): Promise<void> {\n return new Promise((resolve, reject) => {\n if (this.hasConnected) {\n resolve();\n return;\n }\n const onReady = () => { clearTimeout(timer); resolve(); };\n const onResumed = () => { clearTimeout(timer); resolve(); };\n const timer = setTimeout(() => {\n this.off(\"ready\", onReady);\n this.off(\"resumed\", onResumed);\n reject(new Error(\"[auralis] waitForReady timed out\"));\n }, timeoutMs);\n this.once(\"ready\", onReady);\n this.once(\"resumed\", onResumed);\n });\n }\n\n /** Gracefully disconnect every active player then close the WebSocket.\n * Prefer this over `close()` when you want clean backend teardown. */\n async destroy(): Promise<void> {\n await Promise.allSettled(this.players().map((p) => p.disconnect()));\n await this.close();\n }\n\n async close(): Promise<void> {\n this.closed = true;\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n // Drop our listeners so a lingering socket can't fire callbacks post-close.\n this.ws?.removeAllListeners();\n this.ws?.close();\n this.ws = null;\n }\n\n /** Emit \"error\" without the Node EventEmitter footgun: an unlistened \"error\"\n * throws and would crash the host process, so fall back to a console warning. */\n private safeError(err: unknown): void {\n if (this.listenerCount(\"error\") > 0) this.emit(\"error\", err);\n else console.warn(\"[auralis] unhandled client error:\", err);\n }\n\n // --- players ----------------------------------------------------------\n getPlayer(guildId: Snowflake): Player {\n const id = BigInt(guildId);\n let p = this.playersMap.get(id);\n if (!p) {\n p = new Player(this, id);\n this.playersMap.set(id, p);\n this.subscribeGuild(id);\n this.emit(\"playerCreated\", p);\n }\n return p;\n }\n\n players(): Player[] {\n return [...this.playersMap.values()];\n }\n\n dropPlayer(guildId: bigint): void {\n this.playersMap.delete(guildId);\n this.emit(\"playerDestroyed\", guildId);\n }\n\n // --- metadata convenience --------------------------------------------\n loadTracks(identifier: string): Promise<LoadResult> {\n return this.rest.loadTracks(identifier);\n }\n\n /** `ytsearch:`-prefix a bare query and return the hits. Throws `LoadError` on\n * a server-side error so callers aren't silently handed an empty array. */\n async search(query: string): Promise<Track[]> {\n const first = query.split(\" \", 1)[0];\n const ident = first.includes(\":\") ? query : `ytsearch:${query}`;\n const result = await this.loadTracks(ident);\n if (result.loadType === LoadType.Error) {\n throw new LoadError(result.error ?? `search failed for: ${query}`);\n }\n return result.tracks;\n }\n\n // --- websocket --------------------------------------------------------\n private get wsUrl(): string {\n return this.baseUrl.replace(/^http/, \"ws\") + \"/v1/ws\";\n }\n\n private openSocket(): void {\n if (this.closed) return;\n const ws = new WebSocket(this.wsUrl, {\n headers: { Authorization: `Bearer ${this.token}` },\n });\n this.ws = ws;\n\n ws.on(\"open\", () => {\n this.backoff = 1000;\n this.subscribeAll();\n });\n ws.on(\"message\", (raw) => {\n let frame: any;\n try {\n frame = JSON.parse(raw.toString());\n } catch {\n return;\n }\n // handleFrame is async; a rejected promise here would be an unhandled\n // rejection (process-fatal on newer Node). Route failures to safeError.\n this.handleFrame(frame).catch((e) => this.safeError(e));\n });\n ws.on(\"error\", (err) => this.safeError(err));\n ws.on(\"close\", () => {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n this.ws = null;\n if (this.closed || !this.reconnect) return;\n this.emit(\"disconnect\");\n // Backoff with ±25% jitter so many clients don't reconnect in lockstep.\n const jitter = this.backoff * (0.75 + Math.random() * 0.5);\n this.reconnectTimer = setTimeout(() => this.openSocket(), jitter);\n this.backoff = Math.min(this.backoff * 2, 30000);\n });\n }\n\n /** After a reconnect, pull each player's authoritative state from the node so\n * position/volume/paused are accurate again. Per-player failures are ignored. */\n private async resyncPlayers(): Promise<void> {\n await Promise.all(\n this.players().map(async (p) => {\n try {\n p.applyState(await this.rest.getState(p.guildId));\n } catch {\n // node may not have this player (it was dropped during the gap) — skip\n }\n }),\n );\n }\n\n private subscribeAll(): void {\n if (this.ws?.readyState === WebSocket.OPEN && this.playersMap.size > 0) {\n this.send({\n op: \"SUBSCRIBE\",\n d: { guilds: [...this.playersMap.keys()].map(String) },\n });\n }\n }\n\n private subscribeGuild(guildId: bigint): void {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.send({\n op: \"SUBSCRIBE\",\n d: { guilds: [String(guildId)] },\n });\n }\n }\n\n private send(payload: unknown): void {\n if (this.ws?.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(payload));\n }\n\n private async handleFrame(frame: any): Promise<void> {\n const op: string = frame.op;\n const d = frame.d ?? {};\n\n if (op === \"HELLO\") {\n const interval = (d.heartbeat_interval_ms ?? 30000) as number;\n if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);\n let seq = 0;\n this.heartbeatTimer = setInterval(() => {\n this.heartbeatSentAt = Date.now();\n this.send({ op: \"HEARTBEAT\", d: { seq: ++seq } });\n }, interval);\n return;\n }\n if (op === \"READY\") {\n const resumed = this.hasConnected;\n this.sessionId = d.session_id ?? null;\n this.hasConnected = true;\n this.subscribeAll();\n if (resumed) {\n // Reconnect: resync each player's live state from the node so callers\n // see accurate position/volume/paused after the gap (a practical resume).\n await this.resyncPlayers();\n this.emit(\"resumed\");\n } else {\n this.emit(\"ready\");\n }\n return;\n }\n if (op === \"HEARTBEAT_ACK\") {\n if (this.heartbeatSentAt) this.latencyMs = Date.now() - this.heartbeatSentAt;\n return;\n }\n if (op === \"STATS\") {\n this.lastStats = d;\n // STATS is node-wide — emit and return before the per-guild player lookup.\n const eventName = EVENT_OPS[op];\n if (eventName) this.emit(eventName, null, d);\n return;\n }\n\n // WS frames carry guild_id as a string — BigInt keeps 64-bit ids exact so\n // the lookup matches the player keyed by getPlayer(BigInt(...)).\n const guildId = d.guild_id != null ? BigInt(d.guild_id) : null;\n const player = guildId !== null ? this.playersMap.get(guildId) : undefined;\n\n if (op === \"TRACK_END\" && player) {\n // Drive auto-advance BEFORE notifying the user so handlers see new state.\n await player.onTrackEnd(endReasonAdvances(d.reason ?? \"FINISHED\"));\n } else if (op === \"PLAYER_UPDATE\" && player) {\n player.applyState(playerStateFromJson(d.state ?? {}));\n } else if (op === \"TRACK_STUCK\" && player) {\n // Skip the stalled track before notifying, so handlers see the new state.\n await player.onTrackStuck();\n } else if (op === \"VOICE_CLOSED\" && player && d.by_remote && player.rejoinOnVoiceClosed) {\n // Discord killed the session; reset so a re-join re-creates and resumes.\n player.handleVoiceClosed();\n }\n\n const eventName = EVENT_OPS[op];\n if (eventName) this.emit(eventName, player ?? guildId, d);\n }\n}\n","/** Exception hierarchy. Every network/protocol failure is an `AuralisError`. */\n\nexport class AuralisError extends Error {}\n\n/** 401/403 — the bearer token is missing, wrong, or not allowed. */\nexport class AuthError extends AuralisError {}\n\n/** 429 — too many requests. `retryAfter` is seconds if the server gave one. */\nexport class RateLimited extends AuralisError {\n constructor(message: string, public retryAfter?: number) {\n super(message);\n }\n}\n\n/** Any other non-2xx HTTP response. */\nexport class APIError extends AuralisError {\n constructor(public status: number, message: string) {\n super(`HTTP ${status}: ${message}`);\n }\n}\n\n/** Tried to play before the guild's voice connection was established. */\nexport class NotConnected extends AuralisError {}\n\n/** `loadtracks` returned `error` or nothing usable. */\nexport class LoadError extends AuralisError {}\n","/** Wire vocabulary and data models — mirror the control-plane JSON exactly. */\n\n/** A Discord snowflake. Accepted as string/number/bigint at the API boundary and\n * normalized to `bigint` internally: a 64-bit id cannot survive a JS `number`\n * (over 2^53 the low digits corrupt), and the control plane parses ids as `u64`. */\nexport type Snowflake = string | number | bigint;\n\n// A sentinel that is valid JSON string content but cannot appear in real API\n// data (encoded track tokens, guild ids, etc.). The double-boundary markers\n// make accidental collision with real string values negligible.\nconst BIG_TAG = \"||BIGINT||\";\n\n/** `JSON.stringify` that emits `bigint` values as bare (unquoted) JSON numbers.\n * The control plane's voice payload wants snowflakes as `u64` numbers, but a JS\n * `number` can't hold one exactly and `JSON.stringify` throws on `bigint`. We tag\n * bigints, then strip the quotes so the wire value is an exact integer literal. */\nexport function snowflakeStringify(obj: unknown): string {\n return JSON.stringify(obj, (_k, v) =>\n typeof v === \"bigint\" ? `${BIG_TAG}${v}${BIG_TAG}` : v,\n ).replace(/\"\\|\\|BIGINT\\|\\|(\\d+)\\|\\|BIGINT\\|\\|\"/g, \"$1\");\n}\n\n/** How the queue advances when a track finishes. */\nexport enum LoopMode {\n Off = \"off\",\n Track = \"track\", // replay the current track forever\n Queue = \"queue\", // loop the whole queue\n}\n\n/** `load_type` returned by `GET /v1/loadtracks`. */\nexport enum LoadType {\n Track = \"track\",\n Playlist = \"playlist\",\n Search = \"search\",\n Empty = \"empty\",\n Error = \"error\",\n}\n\n/** Why a track ended. Only FINISHED / LOAD_FAILED advance the queue. */\nexport enum EndReason {\n Finished = \"FINISHED\",\n LoadFailed = \"LOAD_FAILED\",\n Stopped = \"STOPPED\",\n Replaced = \"REPLACED\",\n Cleanup = \"CLEANUP\",\n}\n\nexport function endReasonAdvances(reason: string): boolean {\n return reason === EndReason.Finished || reason === EndReason.LoadFailed;\n}\n\n/** A playable track. `encoded` is the opaque token the backend needs; the rest\n * is display metadata the bot keeps for now-playing embeds. */\nexport interface Track {\n encoded: string;\n identifier: string;\n title: string;\n author: string;\n lengthMs: number;\n isStream: boolean;\n uri?: string;\n sourceName: string;\n artworkUrl?: string;\n isrc?: string;\n /** Free-form, set by the bot (e.g. who requested it). Never sent upstream. */\n extras: Record<string, unknown>;\n}\n\nexport function trackFromJson(d: any): Track {\n return {\n encoded: d.encoded,\n identifier: d.identifier ?? \"\",\n title: d.title ?? \"\",\n author: d.author ?? \"\",\n lengthMs: d.length_ms ?? 0,\n isStream: d.is_stream ?? false,\n uri: d.uri ?? undefined,\n sourceName: d.source_name ?? \"\",\n artworkUrl: d.artwork_url ?? undefined,\n isrc: d.isrc ?? undefined,\n extras: {},\n };\n}\n\nexport interface LoadResult {\n loadType: LoadType;\n tracks: Track[];\n playlistName?: string;\n error?: string;\n}\n\nexport function loadResultFromJson(d: any): LoadResult {\n return {\n loadType: (d.load_type ?? \"empty\") as LoadType,\n tracks: (d.tracks ?? []).map(trackFromJson),\n playlistName: d.playlist_name ?? undefined,\n error: d.error ?? undefined,\n };\n}\n\nexport interface PlayerState {\n connected: boolean;\n playing: boolean;\n paused: boolean;\n positionMs: number;\n volume: number;\n track?: Track;\n}\n\nexport function playerStateFromJson(d: any): PlayerState {\n return {\n connected: d.connected ?? false,\n playing: d.playing ?? false,\n paused: d.paused ?? false,\n positionMs: d.position_ms ?? 0,\n volume: d.volume ?? 100,\n track: d.track ? trackFromJson(d.track) : undefined,\n };\n}\n\n/** Raw Discord voice payloads the bot forwards from its gateway. */\nexport interface VoiceServerUpdate {\n token: string;\n endpoint: string;\n}\nexport interface VoiceStateUpdate {\n session_id: string;\n channel_id: Snowflake | null;\n user_id: Snowflake;\n}\n","/** Per-guild player: the client-side queue brain.\n *\n * The backend is deliberately queue-agnostic — it only knows play / stop / pause\n * / seek / volume. Everything queue-shaped (skip, loop, shuffle, auto-advance,\n * autoplay) lives here, driven by the `TRACK_END` event.\n *\n * Advance rules (the one path everything funnels through):\n * - `play()` issues a REST *replace* → the old track's `TRACK_END(REPLACED)` is\n * ignored by the client, so we never double-advance.\n * - a natural finish → `TRACK_END(FINISHED|LOAD_FAILED)` → `advance()`.\n * - `skip()` forces `advance()` (ignoring `LoopMode.Track`).\n */\n\nimport { APIError, LoadError, NotConnected } from \"./errors.js\";\nimport { REST } from \"./rest.js\";\nimport {\n LoadType,\n LoopMode,\n PlayerState,\n Snowflake,\n Track,\n VoiceServerUpdate,\n VoiceStateUpdate,\n} from \"./types.js\";\n\n/** What a Player needs from its owner. The `Client` implements this. */\nexport interface PlayerHost {\n rest: REST;\n emit(event: string, ...args: unknown[]): void;\n dropPlayer(guildId: bigint): void;\n}\n\nexport type AutoplayHook = (player: Player) => Promise<Track | null>;\n\n/** Named EQ presets accepted by {@link Player.setEqualizer}. */\nexport type EqPreset =\n | \"flat\"\n | \"bassboost\"\n | \"soft\"\n | \"nightcore\"\n | \"vaporwave\"\n | \"earrape\"\n | \"treble\"\n | \"karaoke\";\n\n/** Portable snapshot of a player's queue state, produced by {@link Player.export}\n * and consumed by {@link Player.restore}. Safe to JSON.stringify / store. */\nexport interface SerializedPlayer {\n version: 1;\n guildId: string;\n queue: Track[];\n history: Track[];\n current: Track | null;\n loop: LoopMode;\n volume: number;\n paused: boolean;\n}\n\nexport class Player {\n queue: Track[] = [];\n history: Track[] = [];\n current: Track | null = null;\n /** Cap on retained history so a long-lived player can't leak memory. Queue-loop\n * still works; it just restores the most recent `historyLimit` tracks. */\n historyLimit = 200;\n loop: LoopMode = LoopMode.Off;\n volume = 100;\n paused = false;\n state: PlayerState = {\n connected: false,\n playing: false,\n paused: false,\n positionMs: 0,\n volume: 100,\n };\n /** Optional async hook used when the queue and loop are exhausted. */\n autoplay: AutoplayHook | null = null;\n\n /** When a non-stream track \"finishes\" without ever streaming (dead source:\n * geo/age-restricted, throttled, removed), search for and play an alternate\n * upload instead of silently draining the queue. */\n recoverDeadStreams = true;\n /** A track whose furthest observed position never passed this many ms is\n * treated as never-streamed (used by {@link recoverDeadStreams}). */\n deadStreamThresholdMs = 1_500;\n /** Give up recovering a single dead track after this many alternate attempts. */\n deadStreamRecoveryLimit = 2;\n /** Auto-skip a track the node reports as `TRACK_STUCK` instead of stalling. */\n advanceOnStuck = true;\n /** On a remote `VOICE_CLOSED`, reset so the next voice material re-creates the\n * player and resumes the current track (the bot must re-send its op-4 join). */\n rejoinOnVoiceClosed = true;\n\n /** The guild this player drives. Always a `bigint` so 64-bit ids stay exact. */\n readonly guildId: bigint;\n\n private created = false;\n private voiceServer: { token: string; endpoint: string } | null = null;\n private voiceState: { session_id: string; channel_id: bigint; user_id: bigint } | null = null;\n /** Furthest position seen for the current track — reset on every track start. */\n private maxPositionMs = 0;\n /** Alternate-upload attempts spent on the current dead track. */\n private recoverAttempts = 0;\n /** Wall-clock time (ms) when the last PLAYER_UPDATE position was received.\n * Used by the {@link positionMs} interpolating getter. */\n private _positionTimestamp = 0;\n\n constructor(private host: PlayerHost, guildId: Snowflake) {\n this.guildId = BigInt(guildId);\n }\n\n // --- voice plumbing ---------------------------------------------------\n handleVoiceServerUpdate(data: VoiceServerUpdate): void {\n this.voiceServer = { token: data.token, endpoint: data.endpoint };\n void this.startIfIdle();\n }\n\n handleVoiceStateUpdate(data: VoiceStateUpdate): void {\n if (data.channel_id === null || data.channel_id === undefined) {\n this.voiceState = null;\n return;\n }\n this.voiceState = {\n session_id: data.session_id,\n channel_id: BigInt(data.channel_id),\n user_id: BigInt(data.user_id),\n };\n void this.startIfIdle();\n }\n\n get connected(): boolean {\n return this.voiceServer !== null && this.voiceState !== null;\n }\n\n /** Called by the client on `PLAYER_UPDATE` (and reconnect resync). Tracks the\n * furthest position reached so a \"finished but never streamed\" track can be\n * spotted, and clears the recovery counter once a track is genuinely playing. */\n applyState(state: PlayerState): void {\n this.state = state;\n this._positionTimestamp = Date.now();\n if (state.positionMs > this.maxPositionMs) this.maxPositionMs = state.positionMs;\n if (this.maxPositionMs > this.deadStreamThresholdMs) this.recoverAttempts = 0;\n }\n\n /** Discord terminated the voice session (remote `VOICE_CLOSED`). The node has\n * dropped our player, so reset local voice/created state and requeue the\n * current track; once the bot re-sends its op-4 join, fresh voice material\n * re-creates the player and resumes. Bot-initiated closes don't call this. */\n handleVoiceClosed(): void {\n if (this.current !== null) {\n this.queue.unshift(this.current);\n this.current = null;\n }\n this.created = false;\n this.voiceServer = null;\n this.voiceState = null;\n }\n\n private voicePayload(): Record<string, unknown> {\n return { ...this.voiceServer!, ...this.voiceState!, guild_id: this.guildId };\n }\n\n /** Serializes all queue advancement. Without it, concurrent triggers — `play()`,\n * a voice-update `startIfIdle()`, a `TRACK_END`, `skip()` — can each observe\n * `current === null` and both create a player, which serializes on the control\n * plane's per-guild ownership lock and blows the request timeout (the classic\n * \"operation was aborted\"). Every advance path runs through this chain. */\n private opLock: Promise<unknown> = Promise.resolve();\n private serialize<T>(fn: () => Promise<T>): Promise<T> {\n const run = this.opLock.then(fn, fn);\n this.opLock = run.then(() => {}, () => {});\n return run;\n }\n\n /** Start the queue only if the player is idle. Safe to call from multiple\n * concurrent triggers — the idle check runs inside the serialized section, so\n * exactly one caller wins and the rest see `current !== null` and no-op. */\n private startIfIdle(): Promise<void> {\n return this.serialize(async () => {\n if (this.connected && this.current === null && this.queue.length > 0) {\n await this.advanceInner(false);\n }\n });\n }\n\n // --- public commands --------------------------------------------------\n async play(query: string | Track, requester?: Snowflake): Promise<Track[]> {\n const tracks = await this.resolve(query);\n for (const t of tracks) {\n if (requester !== undefined) t.extras.requester = requester;\n this.queue.push(t);\n }\n await this.startIfIdle();\n return tracks;\n }\n\n async add(query: string | Track, requester?: Snowflake): Promise<Track[]> {\n const tracks = await this.resolve(query);\n for (const t of tracks) {\n if (requester !== undefined) t.extras.requester = requester;\n this.queue.push(t);\n }\n return tracks;\n }\n\n async skip(): Promise<void> {\n await this.advance(true);\n }\n\n async stop(): Promise<void> {\n this.queue = [];\n this.current = null;\n this.history = []; // don't let a later queue-loop replay tracks from before a stop\n if (this.created) await this.host.rest.stop(this.guildId);\n }\n\n async pause(): Promise<void> {\n await this.setPausedInternal(true);\n }\n\n async resume(): Promise<void> {\n await this.setPausedInternal(false);\n }\n\n async setPause(paused: boolean): Promise<void> {\n await this.setPausedInternal(paused);\n }\n\n async seek(positionMs: number): Promise<void> {\n if (this.created) await this.host.rest.seek(this.guildId, positionMs);\n }\n\n async setVolume(volume: number): Promise<void> {\n this.volume = Math.max(0, Math.min(1000, volume));\n if (this.created) await this.host.rest.setVolume(this.guildId, this.volume);\n }\n\n setLoop(mode: LoopMode): void {\n this.loop = mode;\n }\n\n shuffle(): void {\n for (let i = this.queue.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [this.queue[i], this.queue[j]] = [this.queue[j], this.queue[i]];\n }\n }\n\n /** Apply a raw ffmpeg `-af` filtergraph (e.g. `\"bass=g=8\"`); `null` clears. */\n async setFilters(audioGraph: string | null): Promise<void> {\n if (this.created) await this.host.rest.setFilters(this.guildId, audioGraph);\n }\n\n remove(index: number): Track | undefined {\n return this.queue.splice(index, 1)[0];\n }\n\n clear(): void {\n this.queue = [];\n }\n\n /** Reorder the queue: move the track at `from` to `to`. No-op on bad indices. */\n move(from: number, to: number): void {\n if (from < 0 || from >= this.queue.length || to < 0 || to >= this.queue.length) return;\n const [t] = this.queue.splice(from, 1);\n this.queue.splice(to, 0, t);\n }\n\n /** Resolve `query` and insert it at `index` (clamped) instead of appending. */\n async insert(index: number, query: string | Track, requester?: Snowflake): Promise<Track[]> {\n const tracks = await this.resolve(query);\n for (const t of tracks) if (requester !== undefined) t.extras.requester = requester;\n const at = Math.max(0, Math.min(index, this.queue.length));\n this.queue.splice(at, 0, ...tracks);\n await this.startIfIdle();\n return tracks;\n }\n\n /** Batch-enqueue several queries/tracks in order. Failed resolves are skipped. */\n async addMany(queries: (string | Track)[], requester?: Snowflake): Promise<Track[]> {\n const added: Track[] = [];\n for (const q of queries) {\n try {\n const tracks = await this.resolve(q);\n for (const t of tracks) {\n if (requester !== undefined) t.extras.requester = requester;\n this.queue.push(t);\n added.push(t);\n }\n } catch {\n // skip a bad query rather than aborting the whole batch\n }\n }\n if (added.length > 0) await this.startIfIdle();\n return added;\n }\n\n /** Skip forward to queue position `index`, discarding the ones in between. */\n async jump(index: number): Promise<void> {\n if (index < 0 || index >= this.queue.length) return;\n const skipped = this.queue.splice(0, index);\n for (const t of skipped) this.pushHistory(t);\n await this.advance(true);\n }\n\n /** Replay the previous track (from history), pushing the current one back on. */\n async previous(): Promise<void> {\n const prev = this.history.pop();\n if (!prev) return;\n // Push current back to the front of the queue — advance(true) will call\n // nextTrack(forced=true), which pushes `this.current` to history again.\n // To avoid a double push, clear `this.current` before advancing so\n // nextTrack sees null and skips the pushHistory call.\n if (this.current !== null) this.queue.unshift(this.current);\n this.current = null;\n this.queue.unshift(prev);\n await this.advance(true);\n }\n\n /** Convenience EQ: bass boost in dB (0 clears). Built on {@link setFilters}. */\n async setBassBoost(gainDb: number): Promise<void> {\n await this.setFilters(gainDb === 0 ? null : `bass=g=${gainDb}`);\n }\n\n /** Convenience speed/pitch (nightcore/vaporwave): `rate` 1.0 = normal.\n * Uses `atempo` for tempo and `asetrate` for pitch shift — both evaluated\n * in JS so the filter string contains literal numbers, not expressions. */\n async setSpeed(rate: number): Promise<void> {\n const r = Math.max(0.5, Math.min(2.0, rate));\n await this.setFilters(r === 1 ? null : `atempo=${r},asetrate=${48000 * r}`);\n }\n\n nowPlaying(): Track | null {\n return this.current;\n }\n\n /** Enqueue a track or query to play immediately after the current one.\n * Unlike `insert(0, ...)`, this is an explicit \"play next\" intent — it\n * always inserts at position 0 regardless of queue state and starts\n * playback if the player is idle. */\n async playNext(query: string | Track, requester?: Snowflake): Promise<Track[]> {\n return this.insert(0, query, requester);\n }\n\n /** Remove all duplicate tracks from the queue (keeps first occurrence).\n * Uniqueness is determined by `encoded` token. Returns the number removed. */\n dedupe(): number {\n const seen = new Set<string>();\n const before = this.queue.length;\n this.queue = this.queue.filter((t) => {\n if (seen.has(t.encoded)) return false;\n seen.add(t.encoded);\n return true;\n });\n return before - this.queue.length;\n }\n\n /** Find all tracks in the queue whose title or author contains `query`\n * (case-insensitive). Returns `[index, track]` pairs so callers can remove\n * or jump to a result by index. */\n find(query: string): [number, Track][] {\n const q = query.toLowerCase();\n return this.queue.reduce<[number, Track][]>((acc, t, i) => {\n if (t.title.toLowerCase().includes(q) || t.author.toLowerCase().includes(q)) {\n acc.push([i, t]);\n }\n return acc;\n }, []);\n }\n\n /** Total duration of all queued tracks in milliseconds. Streams count as 0.\n * Does not include the currently playing track. */\n get totalDurationMs(): number {\n return this.queue.reduce((sum, t) => sum + (t.isStream ? 0 : t.lengthMs), 0);\n }\n\n /** Interpolated playback position in milliseconds. Between `PLAYER_UPDATE`\n * frames this advances using wall-clock time so progress bars stay smooth.\n * Returns 0 if nothing is playing or the player is paused. */\n get positionMs(): number {\n if (!this.state.playing || this.state.paused) return this.state.positionMs;\n const elapsed = Date.now() - this._positionTimestamp;\n return Math.min(\n this.state.positionMs + elapsed,\n this.current?.lengthMs ?? this.state.positionMs + elapsed,\n );\n }\n\n /** Serialize current queue, history, loop mode and volume into a plain object\n * that can be JSON-stringified and stored. Restore with {@link Player.restore}. */\n export(): SerializedPlayer {\n return {\n version: 1,\n guildId: String(this.guildId),\n queue: this.queue.map((t) => ({ ...t })),\n history: this.history.map((t) => ({ ...t })),\n current: this.current ? { ...this.current } : null,\n loop: this.loop,\n volume: this.volume,\n paused: this.paused,\n };\n }\n\n /** Restore queue/history/loop/volume from a previously exported snapshot.\n * Does NOT auto-start playback — call `startIfIdle()` or let the next\n * voice event trigger it. */\n restore(snapshot: SerializedPlayer): void {\n this.queue = snapshot.queue.map((t) => ({ ...t }));\n this.history = snapshot.history.map((t) => ({ ...t }));\n this.current = snapshot.current ? { ...snapshot.current } : null;\n this.loop = snapshot.loop;\n this.volume = snapshot.volume;\n this.paused = snapshot.paused;\n }\n\n /** Apply a named EQ preset. `\"flat\"` clears all filters.\n *\n * | preset | effect |\n * |-------------|---------------------------------------------|\n * | flat | no filters (passthrough) |\n * | bassboost | heavy bass boost (+8 dB) |\n * | soft | gentle bass lift (+3 dB) |\n * | nightcore | speed+pitch up (1.3×) |\n * | vaporwave | speed+pitch down (0.8×) |\n * | earrape | extreme distortion / clipping (use wisely) |\n * | treble | high-frequency boost (+6 dB) |\n * | karaoke | basic vocal removal |\n */\n async setEqualizer(preset: EqPreset): Promise<void> {\n const graphs: Record<EqPreset, string | null> = {\n flat: null,\n bassboost: \"bass=g=8\",\n soft: \"bass=g=3\",\n nightcore: `atempo=1.3,asetrate=${Math.round(48000 * 1.3)}`,\n vaporwave: `atempo=0.8,asetrate=${Math.round(48000 * 0.8)}`,\n earrape: \"acrusher=level_in=8:level_out=18:bits=8:mode=log:aa=1\",\n treble: \"treble=g=6\",\n karaoke: \"pan=stereo|c0=c0-c1|c1=c1-c0\",\n };\n await this.setFilters(graphs[preset]);\n }\n\n /** Reverse the current queue order in-place. */\n reverse(): void {\n this.queue.reverse();\n }\n\n /** Remove all tracks from the queue that were requested by `requesterId`. */\n removeBy(requesterId: Snowflake): number {\n const id = String(requesterId);\n const before = this.queue.length;\n this.queue = this.queue.filter((t) => String(t.extras.requester) !== id);\n return before - this.queue.length;\n }\n\n /** Peek at the next N tracks without mutating state. Defaults to 5. */\n peek(n = 5): Track[] {\n return this.queue.slice(0, n);\n }\n\n /** Returns true if nothing is queued and nothing is playing. */\n get idle(): boolean {\n return this.current === null && this.queue.length === 0;\n }\n\n async disconnect(): Promise<void> {\n this.queue = [];\n this.current = null;\n this.history = [];\n this.paused = false;\n this.volume = 100;\n if (this.created) await this.host.rest.destroyPlayer(this.guildId);\n this.created = false;\n this.voiceServer = null;\n this.voiceState = null;\n this.host.dropPlayer(this.guildId);\n }\n\n // --- internals --------------------------------------------------------\n /** Append to history, evicting the oldest past `historyLimit` (leak guard). */\n private pushHistory(track: Track): void {\n this.history.push(track);\n if (this.history.length > this.historyLimit) {\n this.history.splice(0, this.history.length - this.historyLimit);\n }\n }\n\n private async resolve(query: string | Track): Promise<Track[]> {\n if (typeof query !== \"string\") return [query];\n const result = await this.host.rest.loadTracks(query);\n if (result.error || result.tracks.length === 0) {\n throw new LoadError(result.error ?? `no tracks for ${query}`);\n }\n return result.loadType === LoadType.Playlist ? result.tracks : [result.tracks[0]];\n }\n\n private async nextTrack(forced: boolean): Promise<Track | null> {\n if (this.loop === LoopMode.Track && this.current !== null && !forced) {\n return this.current;\n }\n if (this.queue.length > 0) {\n if (this.current !== null) this.pushHistory(this.current);\n return this.queue.shift()!;\n }\n if (this.loop === LoopMode.Queue && this.history.length > 0) {\n this.queue = [...this.history, ...(this.current ? [this.current] : [])];\n this.history = [];\n return this.queue.shift() ?? null;\n }\n if (this.autoplay !== null) return this.autoplay(this);\n return null;\n }\n\n /** Public advance paths (skip / previous / jump / track-end / stuck) always\n * advance, but still run through the serialized op-lock so they can't race a\n * concurrent {@link startIfIdle} into a double-create. */\n private advance(forced = false): Promise<void> {\n return this.serialize(() => this.advanceInner(forced));\n }\n\n private async advanceInner(forced = false): Promise<void> {\n const next = await this.nextTrack(forced);\n if (next === null) {\n this.current = null;\n if (this.created) await this.host.rest.stop(this.guildId);\n this.host.emit(\"queueEnd\", this);\n return;\n }\n await this.playTrack(next);\n }\n\n private async playTrack(track: Track): Promise<void> {\n if (!this.connected) throw new NotConnected(`guild ${this.guildId} has no voice connection`);\n this.current = track;\n this.maxPositionMs = 0; // fresh track — restart dead-stream position tracking\n if (!this.created) {\n await this.createBackendPlayer(track);\n } else {\n try {\n await this.host.rest.play(this.guildId, track.encoded);\n } catch (e) {\n // 404 GUILD_NOT_FOUND: the backend lost our player (its node restarted, or\n // the control plane dropped it on a disconnect) while our local `created`\n // is stale. Recreate it from our held voice material rather than 404-ing\n // forever — otherwise one node blip wedges every future play until restart.\n if (e instanceof APIError && e.status === 404) {\n this.created = false;\n await this.createBackendPlayer(track);\n } else {\n throw e;\n }\n }\n }\n }\n\n private async createBackendPlayer(track: Track): Promise<void> {\n await this.host.rest.createPlayer(this.guildId, this.voicePayload(), {\n track: track.encoded,\n volume: this.volume,\n paused: this.paused,\n });\n this.created = true;\n }\n\n private async setPausedInternal(paused: boolean): Promise<void> {\n this.paused = paused;\n if (this.created) await this.host.rest.setPaused(this.guildId, paused);\n }\n\n /** Called by the client's WS loop when a TRACK_END for this guild arrives.\n * `advances` is true for FINISHED / LOAD_FAILED. Before draining the queue we\n * check for a dead source and try to recover it with an alternate upload. */\n async onTrackEnd(advances: boolean): Promise<void> {\n if (!advances) return;\n // Loop-a-single-track is an explicit user choice — never second-guess it.\n if (this.recoverDeadStreams && this.loop !== LoopMode.Track && this.isDeadStream()) {\n if (await this.tryRecover()) return; // an alternate upload is now playing\n }\n await this.advance();\n }\n\n /** Called by the client on `TRACK_STUCK`: skip the stalled track. */\n async onTrackStuck(): Promise<void> {\n if (this.advanceOnStuck) await this.advance(true);\n }\n\n /** A non-stream track that \"finished\" without its position ever advancing\n * almost certainly never streamed. Genuinely short tracks (length at most a\n * few × the threshold) are excluded so we don't loop on real short songs. */\n private isDeadStream(): boolean {\n const t = this.current;\n if (t === null || t.isStream) return false;\n if (this.maxPositionMs > this.deadStreamThresholdMs) return false;\n return t.lengthMs === 0 || t.lengthMs > this.deadStreamThresholdMs * 4;\n }\n\n /** Search for an alternate upload of the current (dead) track and play it.\n * Returns true when an alternate started; false to let the caller advance. */\n private async tryRecover(): Promise<boolean> {\n const failed = this.current;\n if (failed === null) return false;\n\n this.recoverAttempts++;\n if (this.recoverAttempts > this.deadStreamRecoveryLimit) {\n this.recoverAttempts = 0;\n this.host.emit(\"trackStreamDead\", this, { failed });\n return false;\n }\n\n const query = [failed.title, failed.author].filter(Boolean).join(\" \").trim();\n if (query === \"\") return false;\n\n let tracks: Track[];\n try {\n tracks = (await this.host.rest.loadTracks(`ytsearch:${query}`)).tracks;\n } catch {\n return false; // search failed — advance normally\n }\n\n // Prefer a different upload than the one that just failed.\n const alt = tracks.find((t) => t.encoded && t.encoded !== failed.encoded);\n if (!alt) return false;\n\n alt.extras = { ...alt.extras, ...failed.extras }; // keep requester etc.\n await this.playTrack(alt);\n this.host.emit(\"trackRecovered\", this, { failed, alternate: alt });\n return true;\n }\n}\n","/** Thin wrapper over the control-plane HTTP API using the global `fetch`\n * (Node >= 18). Knows nothing about queues — just request/response. */\n\nimport { APIError, AuralisError, AuthError, RateLimited } from \"./errors.js\";\nimport {\n LoadResult,\n PlayerState,\n Snowflake,\n loadResultFromJson,\n playerStateFromJson,\n snowflakeStringify,\n} from \"./types.js\";\n\n/** Abort a stalled request rather than hanging a command forever. */\nconst DEFAULT_TIMEOUT_MS = 16_000;\n/** Transient control-plane statuses (node not ready / link failed) worth retrying. */\nconst RETRYABLE_STATUS = new Set([502, 503]);\n/** Per-attempt backoff. Length also sets how many extra tries an idempotent GET gets. */\nconst BACKOFF_MS = [200, 500];\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport class REST {\n private base: string;\n private headers: Record<string, string>;\n /** Per-request timeout (ms). Exposed so a slow-network deployment can raise it. */\n timeoutMs = DEFAULT_TIMEOUT_MS;\n\n constructor(baseUrl: string, token: string) {\n this.base = baseUrl.replace(/\\/$/, \"\");\n this.headers = {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n };\n }\n\n private async fetchWithTimeout(method: string, path: string, body?: unknown): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n return await fetch(`${this.base}${path}`, {\n method,\n headers: this.headers,\n // snowflakeStringify keeps 64-bit voice ids (channel_id/user_id) exact —\n // the control plane parses them as u64 bare numbers.\n body: body === undefined ? undefined : snowflakeStringify(body),\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n\n // Retry policy: GET is idempotent, so retry it on both transient 502/503 and\n // network/timeout errors. Writes only retry on 502/503 (a network error might\n // mean the write half-applied). 401/403/429/other 4xx are terminal — no retry.\n private async request(method: string, path: string, body?: unknown): Promise<any> {\n const idempotent = method === \"GET\";\n const maxAttempts = idempotent ? BACKOFF_MS.length + 1 : 2;\n let lastErr: unknown;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const isLast = attempt === maxAttempts - 1;\n try {\n const res = await this.fetchWithTimeout(method, path, body);\n if (res.status === 401 || res.status === 403) throw new AuthError(await res.text());\n if (res.status === 429) {\n const retry = res.headers.get(\"Retry-After\");\n throw new RateLimited(await res.text(), retry ? Number(retry) : undefined);\n }\n if (RETRYABLE_STATUS.has(res.status) && !isLast) {\n await sleep(BACKOFF_MS[attempt]);\n continue;\n }\n if (res.status >= 400) throw new APIError(res.status, await res.text());\n const ct = res.headers.get(\"Content-Type\") ?? \"\";\n return ct.includes(\"application/json\") ? res.json() : res.text();\n } catch (err) {\n // Protocol errors carry a definitive answer from the server — never retry.\n if (err instanceof AuralisError) throw err;\n // Network error / timeout: retry idempotent requests only.\n lastErr = err;\n if (idempotent && !isLast) {\n await sleep(BACKOFF_MS[attempt]);\n continue;\n }\n throw err;\n }\n }\n throw lastErr; // unreachable: the loop either returns or throws\n }\n\n // --- metadata ---------------------------------------------------------\n async loadTracks(identifier: string): Promise<LoadResult> {\n const q = new URLSearchParams({ identifier });\n return loadResultFromJson(await this.request(\"GET\", `/v1/loadtracks?${q}`));\n }\n\n async prewarm(encoded: string[], guildId?: bigint): Promise<any> {\n const body: any = { tracks: encoded.map((e) => ({ encoded: e })) };\n if (guildId !== undefined) body.guild_id = guildId;\n return this.request(\"POST\", \"/v1/tracks/prewarm\", body);\n }\n\n // --- player lifecycle -------------------------------------------------\n async createPlayer(\n guildId: bigint,\n voice: Record<string, unknown>,\n opts: { track?: string; volume?: number; paused?: boolean; audioGraph?: string } = {},\n ): Promise<any> {\n const body: any = {\n voice,\n volume: opts.volume ?? 100,\n paused: opts.paused ?? false,\n };\n if (opts.track) body.track = { encoded: opts.track };\n if (opts.audioGraph !== undefined) body.filters = { audio_graph: opts.audioGraph };\n return this.request(\"POST\", `/v1/guilds/${guildId}/players`, body);\n }\n\n async destroyPlayer(guildId: bigint): Promise<any> {\n return this.request(\"DELETE\", `/v1/guilds/${guildId}/players`);\n }\n\n async getState(guildId: bigint): Promise<PlayerState> {\n return playerStateFromJson(await this.request(\"GET\", `/v1/guilds/${guildId}/player`));\n }\n\n // --- playback control -------------------------------------------------\n async play(guildId: bigint, track: string, audioGraph?: string): Promise<any> {\n const body: any = { track: { encoded: track } };\n if (audioGraph !== undefined) body.filters = { audio_graph: audioGraph };\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/play`, body);\n }\n\n async stop(guildId: bigint): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/stop`, {});\n }\n\n async setPaused(guildId: bigint, paused: boolean): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/pause`, { paused });\n }\n\n async setVolume(guildId: bigint, volume: number): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/volume`, { volume });\n }\n\n async seek(guildId: bigint, positionMs: number): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/seek`, {\n position_ms: positionMs,\n });\n }\n\n async setFilters(guildId: bigint, audioGraph: string | null): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/filters`, {\n filters: { audio_graph: audioGraph },\n });\n }\n}\n"],"mappings":"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","names":["LoopMode","LoadType","EndReason","WebSocket","eventName"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/errors.ts","../src/types.ts","../src/player.ts","../src/rest.ts"],"sourcesContent":["/**\n * auralis-sdk — the official Node.js SDK for the Auralis music platform.\n *\n * The queue, skip, loop, and shuffle all live in this library — the backend only\n * plays what it is told. See {@link Player} for the full command surface.\n *\n * ```ts\n * import { Client, LoopMode } from \"auralis-sdk\";\n *\n * const { apiBaseUrl, token } = await loadAuralisConfig();\n * const client = new Client(apiBaseUrl, token);\n * await client.start();\n * client.on(\"trackStart\", (player) => console.log(\"now playing:\", player.current?.title));\n *\n * const player = client.getPlayer(guildId);\n * // ... wire player.handleVoiceServerUpdate / handleVoiceStateUpdate from your bot ...\n * await player.play(\"ytsearch:never gonna give you up\");\n * ```\n */\n\nexport { Client } from \"./client.js\";\nexport type { ClientOptions, ClientEvents } from \"./client.js\";\nexport { Player } from \"./player.js\";\nexport type { PlayerHost, AutoplayHook, EqPreset, SerializedPlayer } from \"./player.js\";\nexport { REST } from \"./rest.js\";\nexport {\n LoopMode,\n LoadType,\n EndReason,\n endReasonAdvances,\n} from \"./types.js\";\nexport type {\n Snowflake,\n Track,\n LoadResult,\n PlayerState,\n VoiceServerUpdate,\n VoiceStateUpdate,\n} from \"./types.js\";\nexport {\n AuralisError,\n AuthError,\n RateLimited,\n APIError,\n NotConnected,\n LoadError,\n} from \"./errors.js\";\n","/** The top-level client: owns the REST wrapper, the WebSocket event loop, the\n * per-guild players, and event dispatch (via Node's EventEmitter). */\n\nimport { EventEmitter } from \"node:events\";\nimport WebSocket from \"ws\";\n\nimport { APIError, LoadError } from \"./errors.js\";\nimport { Player, PlayerHost } from \"./player.js\";\nimport { REST } from \"./rest.js\";\nimport { LoadResult, LoadType, Snowflake, Track, endReasonAdvances, playerStateFromJson } from \"./types.js\";\n\nexport interface ClientOptions {\n reconnect?: boolean;\n}\n\n/** Strongly-typed event map for `client.on(...)` / `client.emit(...)`. */\nexport interface ClientEvents {\n ready: [];\n /** Fired instead of `ready` when a reconnect re-establishes the session. */\n resumed: [];\n disconnect: [];\n error: [err: unknown];\n trackStart: [player: Player | bigint, data: any];\n trackEnd: [player: Player | bigint, data: any];\n trackException: [player: Player | bigint, data: any];\n trackStuck: [player: Player | bigint, data: any];\n playerUpdate: [player: Player | bigint, data: any];\n voiceClosed: [player: Player | bigint, data: any];\n // STATS is node-wide, so there may be no player/guild attached.\n stats: [player: Player | bigint | null, data: any];\n queueEnd: [player: Player];\n /** A dead source could not be recovered — the bot should tell the user. */\n trackStreamDead: [player: Player, data: { failed: Track }];\n /** A dead source was auto-recovered by playing an alternate upload. */\n trackRecovered: [player: Player, data: { failed: Track; alternate: Track }];\n /** Emitted when a new Player is created via {@link Client.getPlayer}. */\n playerCreated: [player: Player];\n /** Emitted when a player is fully torn down via {@link Player.disconnect}. */\n playerDestroyed: [guildId: bigint];\n}\n\n// WS op -> the event name we emit to listeners.\nconst EVENT_OPS: Record<string, keyof ClientEvents> = {\n TRACK_START: \"trackStart\",\n TRACK_END: \"trackEnd\",\n TRACK_EXCEPTION: \"trackException\",\n TRACK_STUCK: \"trackStuck\",\n PLAYER_UPDATE: \"playerUpdate\",\n VOICE_CLOSED: \"voiceClosed\",\n STATS: \"stats\",\n};\n\n// Declaration-merge typed on/once/off/emit overloads onto the class.\nexport interface Client {\n on<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this;\n once<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this;\n off<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this;\n emit<K extends keyof ClientEvents>(event: K, ...args: ClientEvents[K]): boolean;\n}\n\nexport class Client extends EventEmitter implements PlayerHost {\n readonly rest: REST;\n private baseUrl: string;\n private token: string;\n private reconnect: boolean;\n\n private playersMap = new Map<bigint, Player>();\n private ws: WebSocket | null = null;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private sessionId: string | null = null;\n private closed = false;\n private backoff = 1000;\n private hasConnected = false;\n private heartbeatSentAt = 0;\n\n /** Last measured heartbeat round-trip latency in ms (-1 until first ack). */\n latencyMs = -1;\n /** Most recent STATS frame payload from the node, or null. */\n lastStats: Record<string, unknown> | null = null;\n\n constructor(baseUrl: string, token: string, opts: ClientOptions = {}) {\n super();\n this.baseUrl = baseUrl.replace(/\\/$/, \"\");\n this.token = token;\n this.reconnect = opts.reconnect ?? true;\n this.rest = new REST(this.baseUrl, token);\n }\n\n // --- lifecycle --------------------------------------------------------\n async start(): Promise<void> {\n this.closed = false;\n this.openSocket();\n }\n\n /** Resolves once the client fires `ready` or `resumed`. If it is already\n * connected it resolves on the next tick. Rejects after `timeoutMs` (default\n * 30 s) so callers don't hang forever on a bad URL/token. */\n waitForReady(timeoutMs = 30_000): Promise<void> {\n return new Promise((resolve, reject) => {\n if (this.hasConnected) {\n resolve();\n return;\n }\n const onReady = () => { clearTimeout(timer); resolve(); };\n const onResumed = () => { clearTimeout(timer); resolve(); };\n const timer = setTimeout(() => {\n this.off(\"ready\", onReady);\n this.off(\"resumed\", onResumed);\n reject(new Error(\"[auralis] waitForReady timed out\"));\n }, timeoutMs);\n this.once(\"ready\", onReady);\n this.once(\"resumed\", onResumed);\n });\n }\n\n /** Gracefully disconnect every active player then close the WebSocket.\n * Prefer this over `close()` when you want clean backend teardown. */\n async destroy(): Promise<void> {\n await Promise.allSettled(this.players().map((p) => p.disconnect()));\n await this.close();\n }\n\n async close(): Promise<void> {\n this.closed = true;\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n // Drop our listeners so a lingering socket can't fire callbacks post-close.\n this.ws?.removeAllListeners();\n this.ws?.close();\n this.ws = null;\n }\n\n /** Emit \"error\" without the Node EventEmitter footgun: an unlistened \"error\"\n * throws and would crash the host process, so fall back to a console warning. */\n private safeError(err: unknown): void {\n if (this.listenerCount(\"error\") > 0) this.emit(\"error\", err);\n else console.warn(\"[auralis] unhandled client error:\", err);\n }\n\n // --- players ----------------------------------------------------------\n getPlayer(guildId: Snowflake): Player {\n const id = BigInt(guildId);\n let p = this.playersMap.get(id);\n if (!p) {\n p = new Player(this, id);\n this.playersMap.set(id, p);\n this.subscribeGuild(id);\n this.emit(\"playerCreated\", p);\n }\n return p;\n }\n\n players(): Player[] {\n return [...this.playersMap.values()];\n }\n\n dropPlayer(guildId: bigint): void {\n this.playersMap.delete(guildId);\n this.emit(\"playerDestroyed\", guildId);\n }\n\n // --- metadata convenience --------------------------------------------\n loadTracks(identifier: string): Promise<LoadResult> {\n return this.rest.loadTracks(identifier);\n }\n\n /** `ytsearch:`-prefix a bare query and return the hits. Throws `LoadError` on\n * a server-side error so callers aren't silently handed an empty array. */\n async search(query: string): Promise<Track[]> {\n const first = query.split(\" \", 1)[0];\n const ident = first.includes(\":\") ? query : `ytsearch:${query}`;\n const result = await this.loadTracks(ident);\n if (result.loadType === LoadType.Error) {\n throw new LoadError(result.error ?? `search failed for: ${query}`);\n }\n return result.tracks;\n }\n\n // --- websocket --------------------------------------------------------\n private get wsUrl(): string {\n return this.baseUrl.replace(/^http/, \"ws\") + \"/v1/ws\";\n }\n\n private openSocket(): void {\n if (this.closed) return;\n const ws = new WebSocket(this.wsUrl, {\n headers: { Authorization: `Bearer ${this.token}` },\n });\n this.ws = ws;\n\n ws.on(\"open\", () => {\n this.backoff = 1000;\n this.subscribeAll();\n });\n ws.on(\"message\", (raw) => {\n let frame: any;\n try {\n frame = JSON.parse(raw.toString());\n } catch {\n return;\n }\n // handleFrame is async; a rejected promise here would be an unhandled\n // rejection (process-fatal on newer Node). Route failures to safeError.\n this.handleFrame(frame).catch((e) => this.safeError(e));\n });\n ws.on(\"error\", (err) => this.safeError(err));\n ws.on(\"close\", () => {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n this.ws = null;\n if (this.closed || !this.reconnect) return;\n this.emit(\"disconnect\");\n // Backoff with ±25% jitter so many clients don't reconnect in lockstep.\n const jitter = this.backoff * (0.75 + Math.random() * 0.5);\n this.reconnectTimer = setTimeout(() => this.openSocket(), jitter);\n this.backoff = Math.min(this.backoff * 2, 30000);\n });\n }\n\n /** After a reconnect, pull each player's authoritative state from the node so\n * position/volume/paused are accurate again. Per-player failures are ignored. */\n private async resyncPlayers(): Promise<void> {\n await Promise.all(\n this.players().map(async (p) => {\n try {\n p.applyState(await this.rest.getState(p.guildId));\n } catch {\n // node may not have this player (it was dropped during the gap) — skip\n }\n }),\n );\n }\n\n private subscribeAll(): void {\n if (this.ws?.readyState === WebSocket.OPEN && this.playersMap.size > 0) {\n this.send({\n op: \"SUBSCRIBE\",\n d: { guilds: [...this.playersMap.keys()].map(String) },\n });\n }\n }\n\n private subscribeGuild(guildId: bigint): void {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.send({\n op: \"SUBSCRIBE\",\n d: { guilds: [String(guildId)] },\n });\n }\n }\n\n private send(payload: unknown): void {\n if (this.ws?.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(payload));\n }\n\n private async handleFrame(frame: any): Promise<void> {\n const op: string = frame.op;\n const d = frame.d ?? {};\n\n if (op === \"HELLO\") {\n const interval = (d.heartbeat_interval_ms ?? 30000) as number;\n if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);\n let seq = 0;\n this.heartbeatTimer = setInterval(() => {\n this.heartbeatSentAt = Date.now();\n this.send({ op: \"HEARTBEAT\", d: { seq: ++seq } });\n }, interval);\n return;\n }\n if (op === \"READY\") {\n const resumed = this.hasConnected;\n this.sessionId = d.session_id ?? null;\n this.hasConnected = true;\n this.subscribeAll();\n if (resumed) {\n // Reconnect: resync each player's live state from the node so callers\n // see accurate position/volume/paused after the gap (a practical resume).\n await this.resyncPlayers();\n this.emit(\"resumed\");\n } else {\n this.emit(\"ready\");\n }\n return;\n }\n if (op === \"HEARTBEAT_ACK\") {\n if (this.heartbeatSentAt) this.latencyMs = Date.now() - this.heartbeatSentAt;\n return;\n }\n if (op === \"STATS\") {\n this.lastStats = d;\n // STATS is node-wide — emit and return before the per-guild player lookup.\n const eventName = EVENT_OPS[op];\n if (eventName) this.emit(eventName, null, d);\n return;\n }\n\n // WS frames carry guild_id as a string — BigInt keeps 64-bit ids exact so\n // the lookup matches the player keyed by getPlayer(BigInt(...)).\n const guildId = d.guild_id != null ? BigInt(d.guild_id) : null;\n const player = guildId !== null ? this.playersMap.get(guildId) : undefined;\n\n if (op === \"TRACK_END\" && player) {\n // Drive auto-advance BEFORE notifying the user so handlers see new state.\n await player.onTrackEnd(endReasonAdvances(d.reason ?? \"FINISHED\"));\n } else if (op === \"PLAYER_UPDATE\" && player) {\n player.applyState(playerStateFromJson(d.state ?? {}));\n } else if (op === \"TRACK_STUCK\" && player) {\n // Skip the stalled track before notifying, so handlers see the new state.\n await player.onTrackStuck();\n } else if (op === \"VOICE_CLOSED\" && player && d.by_remote && player.rejoinOnVoiceClosed) {\n // Discord killed the session; reset so a re-join re-creates and resumes.\n player.handleVoiceClosed();\n }\n\n const eventName = EVENT_OPS[op];\n if (eventName) this.emit(eventName, player ?? guildId, d);\n }\n}\n","/** Exception hierarchy. Every network/protocol failure is an `AuralisError`. */\n\nexport class AuralisError extends Error {}\n\n/** 401/403 — the bearer token is missing, wrong, or not allowed. */\nexport class AuthError extends AuralisError {}\n\n/** 429 — too many requests. `retryAfter` is seconds if the server gave one. */\nexport class RateLimited extends AuralisError {\n constructor(message: string, public retryAfter?: number) {\n super(message);\n }\n}\n\n/** Any other non-2xx HTTP response. */\nexport class APIError extends AuralisError {\n constructor(public status: number, message: string) {\n super(`HTTP ${status}: ${message}`);\n }\n}\n\n/** Tried to play before the guild's voice connection was established. */\nexport class NotConnected extends AuralisError {}\n\n/** `loadtracks` returned `error` or nothing usable. */\nexport class LoadError extends AuralisError {}\n","/** Wire vocabulary and data models — mirror the control-plane JSON exactly. */\n\n/** A Discord snowflake. Accepted as string/number/bigint at the API boundary and\n * normalized to `bigint` internally: a 64-bit id cannot survive a JS `number`\n * (over 2^53 the low digits corrupt), and the control plane parses ids as `u64`. */\nexport type Snowflake = string | number | bigint;\n\n// A sentinel that is valid JSON string content but cannot appear in real API\n// data (encoded track tokens, guild ids, etc.). The double-boundary markers\n// make accidental collision with real string values negligible.\nconst BIG_TAG = \"||BIGINT||\";\n\n/** `JSON.stringify` that emits `bigint` values as bare (unquoted) JSON numbers.\n * The control plane's voice payload wants snowflakes as `u64` numbers, but a JS\n * `number` can't hold one exactly and `JSON.stringify` throws on `bigint`. We tag\n * bigints, then strip the quotes so the wire value is an exact integer literal. */\nexport function snowflakeStringify(obj: unknown): string {\n return JSON.stringify(obj, (_k, v) =>\n typeof v === \"bigint\" ? `${BIG_TAG}${v}${BIG_TAG}` : v,\n ).replace(/\"\\|\\|BIGINT\\|\\|(\\d+)\\|\\|BIGINT\\|\\|\"/g, \"$1\");\n}\n\n/** How the queue advances when a track finishes. */\nexport enum LoopMode {\n Off = \"off\",\n Track = \"track\", // replay the current track forever\n Queue = \"queue\", // loop the whole queue\n}\n\n/** `load_type` returned by `GET /v1/loadtracks`. */\nexport enum LoadType {\n Track = \"track\",\n Playlist = \"playlist\",\n Search = \"search\",\n Empty = \"empty\",\n Error = \"error\",\n}\n\n/** Why a track ended. Only FINISHED / LOAD_FAILED advance the queue. */\nexport enum EndReason {\n Finished = \"FINISHED\",\n LoadFailed = \"LOAD_FAILED\",\n Stopped = \"STOPPED\",\n Replaced = \"REPLACED\",\n Cleanup = \"CLEANUP\",\n}\n\nexport function endReasonAdvances(reason: string): boolean {\n return reason === EndReason.Finished || reason === EndReason.LoadFailed;\n}\n\n/** A playable track. `encoded` is the opaque token the backend needs; the rest\n * is display metadata the bot keeps for now-playing embeds. */\nexport interface Track {\n encoded: string;\n identifier: string;\n title: string;\n author: string;\n lengthMs: number;\n isStream: boolean;\n uri?: string;\n sourceName: string;\n artworkUrl?: string;\n isrc?: string;\n /** Free-form, set by the bot (e.g. who requested it). Never sent upstream. */\n extras: Record<string, unknown>;\n}\n\nexport function trackFromJson(d: any): Track {\n return {\n encoded: d.encoded,\n identifier: d.identifier ?? \"\",\n title: d.title ?? \"\",\n author: d.author ?? \"\",\n lengthMs: d.length_ms ?? 0,\n isStream: d.is_stream ?? false,\n uri: d.uri ?? undefined,\n sourceName: d.source_name ?? \"\",\n artworkUrl: d.artwork_url ?? undefined,\n isrc: d.isrc ?? undefined,\n extras: {},\n };\n}\n\nexport interface LoadResult {\n loadType: LoadType;\n tracks: Track[];\n playlistName?: string;\n error?: string;\n}\n\nexport function loadResultFromJson(d: any): LoadResult {\n return {\n loadType: (d.load_type ?? \"empty\") as LoadType,\n tracks: (d.tracks ?? []).map(trackFromJson),\n playlistName: d.playlist_name ?? undefined,\n error: d.error ?? undefined,\n };\n}\n\nexport interface PlayerState {\n connected: boolean;\n playing: boolean;\n paused: boolean;\n positionMs: number;\n volume: number;\n track?: Track;\n}\n\nexport function playerStateFromJson(d: any): PlayerState {\n return {\n connected: d.connected ?? false,\n playing: d.playing ?? false,\n paused: d.paused ?? false,\n positionMs: d.position_ms ?? 0,\n volume: d.volume ?? 100,\n track: d.track ? trackFromJson(d.track) : undefined,\n };\n}\n\n/** Raw Discord voice payloads the bot forwards from its gateway. */\nexport interface VoiceServerUpdate {\n token: string;\n endpoint: string;\n}\nexport interface VoiceStateUpdate {\n session_id: string;\n channel_id: Snowflake | null;\n user_id: Snowflake;\n}\n","/** Per-guild player: the client-side queue brain.\n *\n * The backend is deliberately queue-agnostic — it only knows play / stop / pause\n * / seek / volume. Everything queue-shaped (skip, loop, shuffle, auto-advance,\n * autoplay) lives here, driven by the `TRACK_END` event.\n *\n * Advance rules (the one path everything funnels through):\n * - `play()` issues a REST *replace* → the old track's `TRACK_END(REPLACED)` is\n * ignored by the client, so we never double-advance.\n * - a natural finish → `TRACK_END(FINISHED|LOAD_FAILED)` → `advance()`.\n * - `skip()` forces `advance()` (ignoring `LoopMode.Track`).\n */\n\nimport { APIError, LoadError, NotConnected } from \"./errors.js\";\nimport { REST } from \"./rest.js\";\nimport {\n LoadType,\n LoopMode,\n PlayerState,\n Snowflake,\n Track,\n VoiceServerUpdate,\n VoiceStateUpdate,\n} from \"./types.js\";\n\n/** What a Player needs from its owner. The `Client` implements this. */\nexport interface PlayerHost {\n rest: REST;\n emit(event: string, ...args: unknown[]): void;\n dropPlayer(guildId: bigint): void;\n}\n\nexport type AutoplayHook = (player: Player) => Promise<Track | null>;\n\n/** Named EQ presets accepted by {@link Player.setEqualizer}. */\nexport type EqPreset =\n | \"flat\"\n | \"bassboost\"\n | \"soft\"\n | \"nightcore\"\n | \"vaporwave\"\n | \"earrape\"\n | \"treble\"\n | \"karaoke\";\n\n/** Portable snapshot of a player's queue state, produced by {@link Player.export}\n * and consumed by {@link Player.restore}. Safe to JSON.stringify / store. */\nexport interface SerializedPlayer {\n version: 1;\n guildId: string;\n queue: Track[];\n history: Track[];\n current: Track | null;\n loop: LoopMode;\n volume: number;\n paused: boolean;\n}\n\nexport class Player {\n queue: Track[] = [];\n history: Track[] = [];\n current: Track | null = null;\n /** Cap on retained history so a long-lived player can't leak memory. Queue-loop\n * still works; it just restores the most recent `historyLimit` tracks. */\n historyLimit = 200;\n loop: LoopMode = LoopMode.Off;\n volume = 100;\n paused = false;\n state: PlayerState = {\n connected: false,\n playing: false,\n paused: false,\n positionMs: 0,\n volume: 100,\n };\n /** Optional async hook used when the queue and loop are exhausted. */\n autoplay: AutoplayHook | null = null;\n\n /** When a non-stream track \"finishes\" without ever streaming (dead source:\n * geo/age-restricted, throttled, removed), search for and play an alternate\n * upload instead of silently draining the queue. */\n recoverDeadStreams = true;\n /** A track whose furthest observed position never passed this many ms is\n * treated as never-streamed (used by {@link recoverDeadStreams}). */\n deadStreamThresholdMs = 1_500;\n /** Give up recovering a single dead track after this many alternate attempts. */\n deadStreamRecoveryLimit = 2;\n /** Auto-skip a track the node reports as `TRACK_STUCK` instead of stalling. */\n advanceOnStuck = true;\n /** On a remote `VOICE_CLOSED`, reset so the next voice material re-creates the\n * player and resumes the current track (the bot must re-send its op-4 join). */\n rejoinOnVoiceClosed = true;\n\n /** The guild this player drives. Always a `bigint` so 64-bit ids stay exact. */\n readonly guildId: bigint;\n\n private created = false;\n private voiceServer: { token: string; endpoint: string } | null = null;\n private voiceState: { session_id: string; channel_id: bigint; user_id: bigint } | null = null;\n /** Furthest position seen for the current track — reset on every track start. */\n private maxPositionMs = 0;\n /** Alternate-upload attempts spent on the current dead track. */\n private recoverAttempts = 0;\n /** Wall-clock time (ms) when the last PLAYER_UPDATE position was received.\n * Used by the {@link positionMs} interpolating getter. */\n private _positionTimestamp = 0;\n\n constructor(private host: PlayerHost, guildId: Snowflake) {\n this.guildId = BigInt(guildId);\n }\n\n // --- voice plumbing ---------------------------------------------------\n handleVoiceServerUpdate(data: VoiceServerUpdate): void {\n this.voiceServer = { token: data.token, endpoint: data.endpoint };\n void this.startIfIdle();\n }\n\n handleVoiceStateUpdate(data: VoiceStateUpdate): void {\n if (data.channel_id === null || data.channel_id === undefined) {\n this.voiceState = null;\n return;\n }\n this.voiceState = {\n session_id: data.session_id,\n channel_id: BigInt(data.channel_id),\n user_id: BigInt(data.user_id),\n };\n void this.startIfIdle();\n }\n\n get connected(): boolean {\n return this.voiceServer !== null && this.voiceState !== null;\n }\n\n /** Called by the client on `PLAYER_UPDATE` (and reconnect resync). Tracks the\n * furthest position reached so a \"finished but never streamed\" track can be\n * spotted, and clears the recovery counter once a track is genuinely playing. */\n applyState(state: PlayerState): void {\n this.state = state;\n this._positionTimestamp = Date.now();\n if (state.positionMs > this.maxPositionMs) this.maxPositionMs = state.positionMs;\n if (this.maxPositionMs > this.deadStreamThresholdMs) this.recoverAttempts = 0;\n }\n\n /** Discord terminated the voice session (remote `VOICE_CLOSED`). The node has\n * dropped our player, so reset local voice/created state and requeue the\n * current track; once the bot re-sends its op-4 join, fresh voice material\n * re-creates the player and resumes. Bot-initiated closes don't call this. */\n handleVoiceClosed(): void {\n if (this.current !== null) {\n this.queue.unshift(this.current);\n this.current = null;\n }\n this.created = false;\n this.voiceServer = null;\n this.voiceState = null;\n }\n\n private voicePayload(): Record<string, unknown> {\n return { ...this.voiceServer!, ...this.voiceState!, guild_id: this.guildId };\n }\n\n /** Serializes all queue advancement. Without it, concurrent triggers — `play()`,\n * a voice-update `startIfIdle()`, a `TRACK_END`, `skip()` — can each observe\n * `current === null` and both create a player, which serializes on the control\n * plane's per-guild ownership lock and blows the request timeout (the classic\n * \"operation was aborted\"). Every advance path runs through this chain. */\n private opLock: Promise<unknown> = Promise.resolve();\n private serialize<T>(fn: () => Promise<T>): Promise<T> {\n const run = this.opLock.then(fn, fn);\n this.opLock = run.then(() => {}, () => {});\n return run;\n }\n\n /** Start the queue only if the player is idle. Safe to call from multiple\n * concurrent triggers — the idle check runs inside the serialized section, so\n * exactly one caller wins and the rest see `current !== null` and no-op. */\n private startIfIdle(): Promise<void> {\n return this.serialize(async () => {\n if (this.connected && this.current === null && this.queue.length > 0) {\n await this.advanceInner(false);\n }\n });\n }\n\n // --- public commands --------------------------------------------------\n async play(query: string | Track, requester?: Snowflake): Promise<Track[]> {\n const tracks = await this.resolve(query);\n for (const t of tracks) {\n if (requester !== undefined) t.extras.requester = requester;\n this.queue.push(t);\n }\n await this.startIfIdle();\n return tracks;\n }\n\n async add(query: string | Track, requester?: Snowflake): Promise<Track[]> {\n const tracks = await this.resolve(query);\n for (const t of tracks) {\n if (requester !== undefined) t.extras.requester = requester;\n this.queue.push(t);\n }\n return tracks;\n }\n\n async skip(): Promise<void> {\n await this.advance(true);\n }\n\n async stop(): Promise<void> {\n this.queue = [];\n this.current = null;\n this.history = []; // don't let a later queue-loop replay tracks from before a stop\n if (this.created) await this.host.rest.stop(this.guildId);\n }\n\n async pause(): Promise<void> {\n await this.setPausedInternal(true);\n }\n\n async resume(): Promise<void> {\n await this.setPausedInternal(false);\n }\n\n async setPause(paused: boolean): Promise<void> {\n await this.setPausedInternal(paused);\n }\n\n async seek(positionMs: number): Promise<void> {\n if (this.created) await this.host.rest.seek(this.guildId, positionMs);\n }\n\n async setVolume(volume: number): Promise<void> {\n this.volume = Math.max(0, Math.min(1000, volume));\n if (this.created) await this.host.rest.setVolume(this.guildId, this.volume);\n }\n\n setLoop(mode: LoopMode): void {\n this.loop = mode;\n }\n\n shuffle(): void {\n for (let i = this.queue.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [this.queue[i], this.queue[j]] = [this.queue[j], this.queue[i]];\n }\n }\n\n /** Apply a raw ffmpeg `-af` filtergraph (e.g. `\"bass=g=8\"`); `null` clears. */\n async setFilters(audioGraph: string | null): Promise<void> {\n if (this.created) await this.host.rest.setFilters(this.guildId, audioGraph);\n }\n\n remove(index: number): Track | undefined {\n return this.queue.splice(index, 1)[0];\n }\n\n clear(): void {\n this.queue = [];\n }\n\n /** Reorder the queue: move the track at `from` to `to`. No-op on bad indices. */\n move(from: number, to: number): void {\n if (from < 0 || from >= this.queue.length || to < 0 || to >= this.queue.length) return;\n const [t] = this.queue.splice(from, 1);\n this.queue.splice(to, 0, t);\n }\n\n /** Resolve `query` and insert it at `index` (clamped) instead of appending. */\n async insert(index: number, query: string | Track, requester?: Snowflake): Promise<Track[]> {\n const tracks = await this.resolve(query);\n for (const t of tracks) if (requester !== undefined) t.extras.requester = requester;\n const at = Math.max(0, Math.min(index, this.queue.length));\n this.queue.splice(at, 0, ...tracks);\n await this.startIfIdle();\n return tracks;\n }\n\n /** Batch-enqueue several queries/tracks in order. Failed resolves are skipped. */\n async addMany(queries: (string | Track)[], requester?: Snowflake): Promise<Track[]> {\n const added: Track[] = [];\n for (const q of queries) {\n try {\n const tracks = await this.resolve(q);\n for (const t of tracks) {\n if (requester !== undefined) t.extras.requester = requester;\n this.queue.push(t);\n added.push(t);\n }\n } catch {\n // skip a bad query rather than aborting the whole batch\n }\n }\n if (added.length > 0) await this.startIfIdle();\n return added;\n }\n\n /** Skip forward to queue position `index`, discarding the ones in between. */\n async jump(index: number): Promise<void> {\n if (index < 0 || index >= this.queue.length) return;\n const skipped = this.queue.splice(0, index);\n for (const t of skipped) this.pushHistory(t);\n await this.advance(true);\n }\n\n /** Replay the previous track (from history), pushing the current one back on. */\n async previous(): Promise<void> {\n const prev = this.history.pop();\n if (!prev) return;\n // Push current back to the front of the queue — advance(true) will call\n // nextTrack(forced=true), which pushes `this.current` to history again.\n // To avoid a double push, clear `this.current` before advancing so\n // nextTrack sees null and skips the pushHistory call.\n if (this.current !== null) this.queue.unshift(this.current);\n this.current = null;\n this.queue.unshift(prev);\n await this.advance(true);\n }\n\n /** Convenience EQ: bass boost in dB (0 clears). Built on {@link setFilters}. */\n async setBassBoost(gainDb: number): Promise<void> {\n await this.setFilters(gainDb === 0 ? null : `bass=g=${gainDb}`);\n }\n\n /** Convenience speed/pitch (nightcore/vaporwave): `rate` 1.0 = normal.\n * Uses `atempo` for tempo and `asetrate` for pitch shift — both evaluated\n * in JS so the filter string contains literal numbers, not expressions. */\n async setSpeed(rate: number): Promise<void> {\n const r = Math.max(0.5, Math.min(2.0, rate));\n await this.setFilters(r === 1 ? null : `atempo=${r},asetrate=${48000 * r}`);\n }\n\n nowPlaying(): Track | null {\n return this.current;\n }\n\n /** Enqueue a track or query to play immediately after the current one.\n * Unlike `insert(0, ...)`, this is an explicit \"play next\" intent — it\n * always inserts at position 0 regardless of queue state and starts\n * playback if the player is idle. */\n async playNext(query: string | Track, requester?: Snowflake): Promise<Track[]> {\n return this.insert(0, query, requester);\n }\n\n /** Remove all duplicate tracks from the queue (keeps first occurrence).\n * Uniqueness is determined by `encoded` token. Returns the number removed. */\n dedupe(): number {\n const seen = new Set<string>();\n const before = this.queue.length;\n this.queue = this.queue.filter((t) => {\n if (seen.has(t.encoded)) return false;\n seen.add(t.encoded);\n return true;\n });\n return before - this.queue.length;\n }\n\n /** Find all tracks in the queue whose title or author contains `query`\n * (case-insensitive). Returns `[index, track]` pairs so callers can remove\n * or jump to a result by index. */\n find(query: string): [number, Track][] {\n const q = query.toLowerCase();\n return this.queue.reduce<[number, Track][]>((acc, t, i) => {\n if (t.title.toLowerCase().includes(q) || t.author.toLowerCase().includes(q)) {\n acc.push([i, t]);\n }\n return acc;\n }, []);\n }\n\n /** Total duration of all queued tracks in milliseconds. Streams count as 0.\n * Does not include the currently playing track. */\n get totalDurationMs(): number {\n return this.queue.reduce((sum, t) => sum + (t.isStream ? 0 : t.lengthMs), 0);\n }\n\n /** Interpolated playback position in milliseconds. Between `PLAYER_UPDATE`\n * frames this advances using wall-clock time so progress bars stay smooth.\n * Returns 0 if nothing is playing or the player is paused. */\n get positionMs(): number {\n if (!this.state.playing || this.state.paused) return this.state.positionMs;\n const elapsed = Date.now() - this._positionTimestamp;\n return Math.min(\n this.state.positionMs + elapsed,\n this.current?.lengthMs ?? this.state.positionMs + elapsed,\n );\n }\n\n /** Serialize current queue, history, loop mode and volume into a plain object\n * that can be JSON-stringified and stored. Restore with {@link Player.restore}. */\n export(): SerializedPlayer {\n return {\n version: 1,\n guildId: String(this.guildId),\n queue: this.queue.map((t) => ({ ...t })),\n history: this.history.map((t) => ({ ...t })),\n current: this.current ? { ...this.current } : null,\n loop: this.loop,\n volume: this.volume,\n paused: this.paused,\n };\n }\n\n /** Restore queue/history/loop/volume from a previously exported snapshot.\n * Does NOT auto-start playback — call `startIfIdle()` or let the next\n * voice event trigger it. */\n restore(snapshot: SerializedPlayer): void {\n this.queue = snapshot.queue.map((t) => ({ ...t }));\n this.history = snapshot.history.map((t) => ({ ...t }));\n this.current = snapshot.current ? { ...snapshot.current } : null;\n this.loop = snapshot.loop;\n this.volume = snapshot.volume;\n this.paused = snapshot.paused;\n }\n\n /** Apply a named EQ preset. `\"flat\"` clears all filters.\n *\n * | preset | effect |\n * |-------------|---------------------------------------------|\n * | flat | no filters (passthrough) |\n * | bassboost | heavy bass boost (+8 dB) |\n * | soft | gentle bass lift (+3 dB) |\n * | nightcore | speed+pitch up (1.3×) |\n * | vaporwave | speed+pitch down (0.8×) |\n * | earrape | extreme distortion / clipping (use wisely) |\n * | treble | high-frequency boost (+6 dB) |\n * | karaoke | basic vocal removal |\n */\n async setEqualizer(preset: EqPreset): Promise<void> {\n const graphs: Record<EqPreset, string | null> = {\n flat: null,\n bassboost: \"bass=g=8\",\n soft: \"bass=g=3\",\n nightcore: `atempo=1.3,asetrate=${Math.round(48000 * 1.3)}`,\n vaporwave: `atempo=0.8,asetrate=${Math.round(48000 * 0.8)}`,\n earrape: \"acrusher=level_in=8:level_out=18:bits=8:mode=log:aa=1\",\n treble: \"treble=g=6\",\n karaoke: \"pan=stereo|c0=c0-c1|c1=c1-c0\",\n };\n await this.setFilters(graphs[preset]);\n }\n\n /** Reverse the current queue order in-place. */\n reverse(): void {\n this.queue.reverse();\n }\n\n /** Remove all tracks from the queue that were requested by `requesterId`. */\n removeBy(requesterId: Snowflake): number {\n const id = String(requesterId);\n const before = this.queue.length;\n this.queue = this.queue.filter((t) => String(t.extras.requester) !== id);\n return before - this.queue.length;\n }\n\n /** Peek at the next N tracks without mutating state. Defaults to 5. */\n peek(n = 5): Track[] {\n return this.queue.slice(0, n);\n }\n\n /** Returns true if nothing is queued and nothing is playing. */\n get idle(): boolean {\n return this.current === null && this.queue.length === 0;\n }\n\n async disconnect(): Promise<void> {\n this.queue = [];\n this.current = null;\n this.history = [];\n this.paused = false;\n this.volume = 100;\n if (this.created) await this.host.rest.destroyPlayer(this.guildId);\n this.created = false;\n this.voiceServer = null;\n this.voiceState = null;\n this.host.dropPlayer(this.guildId);\n }\n\n // --- internals --------------------------------------------------------\n /** Append to history, evicting the oldest past `historyLimit` (leak guard). */\n private pushHistory(track: Track): void {\n this.history.push(track);\n if (this.history.length > this.historyLimit) {\n this.history.splice(0, this.history.length - this.historyLimit);\n }\n }\n\n private async resolve(query: string | Track): Promise<Track[]> {\n if (typeof query !== \"string\") return [query];\n const result = await this.host.rest.loadTracks(query);\n if (result.error || result.tracks.length === 0) {\n throw new LoadError(result.error ?? `no tracks for ${query}`);\n }\n return result.loadType === LoadType.Playlist ? result.tracks : [result.tracks[0]];\n }\n\n private async nextTrack(forced: boolean): Promise<Track | null> {\n if (this.loop === LoopMode.Track && this.current !== null && !forced) {\n return this.current;\n }\n if (this.queue.length > 0) {\n if (this.current !== null) this.pushHistory(this.current);\n return this.queue.shift()!;\n }\n if (this.loop === LoopMode.Queue) {\n // The cycle is everything played so far plus the track that just ended.\n // Gating this on a non-empty history meant a queue-loop over a *single*\n // track (nothing in history yet) fell through and stopped instead.\n const cycle = [...this.history, ...(this.current ? [this.current] : [])];\n if (cycle.length > 0) {\n this.queue = cycle;\n this.history = [];\n return this.queue.shift() ?? null;\n }\n }\n if (this.autoplay !== null) return this.autoplay(this);\n return null;\n }\n\n /** Public advance paths (skip / previous / jump / track-end / stuck) always\n * advance, but still run through the serialized op-lock so they can't race a\n * concurrent {@link startIfIdle} into a double-create. */\n private advance(forced = false): Promise<void> {\n return this.serialize(() => this.advanceInner(forced));\n }\n\n private async advanceInner(forced = false): Promise<void> {\n // A failed start is terminal unless we handle it here: nothing is playing on\n // the node, so no TRACK_END will ever arrive to drive a retry and the guild\n // would sit silent forever with a full queue. Report the bad track and take\n // the next one. Bounded by the queue length so an all-bad queue still ends.\n for (let attempts = this.queue.length + 1; attempts >= 0; attempts--) {\n const next = await this.nextTrack(forced);\n if (next === null) {\n this.current = null;\n if (this.created) await this.host.rest.stop(this.guildId);\n this.host.emit(\"queueEnd\", this);\n return;\n }\n try {\n await this.playTrack(next);\n return;\n } catch (e) {\n if (e instanceof NotConnected) {\n // nextTrack() already shifted it off the queue — put it back so the\n // track isn't lost, then let the re-join resume it.\n this.queue.unshift(next);\n throw e;\n }\n this.current = null; // keep it out of history, and off LoopMode.Track\n forced = true; // never let LoopMode.Track re-serve the track that failed\n this.host.emit(\"trackException\", this, { track: next, error: String(e) });\n }\n }\n }\n\n private async playTrack(track: Track): Promise<void> {\n if (!this.connected) throw new NotConnected(`guild ${this.guildId} has no voice connection`);\n this.current = track;\n this.maxPositionMs = 0; // fresh track — restart dead-stream position tracking\n if (!this.created) {\n await this.createBackendPlayer(track);\n } else {\n try {\n await this.host.rest.play(this.guildId, track.encoded);\n } catch (e) {\n // 404 GUILD_NOT_FOUND: the backend lost our player (its node restarted, or\n // the control plane dropped it on a disconnect) while our local `created`\n // is stale. Recreate it from our held voice material rather than 404-ing\n // forever — otherwise one node blip wedges every future play until restart.\n if (e instanceof APIError && e.status === 404) {\n this.created = false;\n await this.createBackendPlayer(track);\n } else {\n throw e;\n }\n }\n }\n }\n\n private async createBackendPlayer(track: Track): Promise<void> {\n await this.host.rest.createPlayer(this.guildId, this.voicePayload(), {\n track: track.encoded,\n volume: this.volume,\n paused: this.paused,\n });\n this.created = true;\n }\n\n private async setPausedInternal(paused: boolean): Promise<void> {\n this.paused = paused;\n if (this.created) await this.host.rest.setPaused(this.guildId, paused);\n }\n\n /** Called by the client's WS loop when a TRACK_END for this guild arrives.\n * `advances` is true for FINISHED / LOAD_FAILED. Before draining the queue we\n * check for a dead source and try to recover it with an alternate upload. */\n async onTrackEnd(advances: boolean): Promise<void> {\n if (!advances) return;\n // Loop-a-single-track is an explicit user choice — never second-guess it.\n if (this.recoverDeadStreams && this.loop !== LoopMode.Track && this.isDeadStream()) {\n if (await this.tryRecover()) return; // an alternate upload is now playing\n }\n await this.advance();\n }\n\n /** Called by the client on `TRACK_STUCK`: skip the stalled track. */\n async onTrackStuck(): Promise<void> {\n if (this.advanceOnStuck) await this.advance(true);\n }\n\n /** A non-stream track that \"finished\" without its position ever advancing\n * almost certainly never streamed. Genuinely short tracks (length at most a\n * few × the threshold) are excluded so we don't loop on real short songs. */\n private isDeadStream(): boolean {\n const t = this.current;\n if (t === null || t.isStream) return false;\n if (this.maxPositionMs > this.deadStreamThresholdMs) return false;\n return t.lengthMs === 0 || t.lengthMs > this.deadStreamThresholdMs * 4;\n }\n\n /** Search for an alternate upload of the current (dead) track and play it.\n * Returns true when an alternate started; false to let the caller advance. */\n private async tryRecover(): Promise<boolean> {\n const failed = this.current;\n if (failed === null) return false;\n\n this.recoverAttempts++;\n if (this.recoverAttempts > this.deadStreamRecoveryLimit) {\n this.recoverAttempts = 0;\n this.host.emit(\"trackStreamDead\", this, { failed });\n return false;\n }\n\n const query = [failed.title, failed.author].filter(Boolean).join(\" \").trim();\n if (query === \"\") return false;\n\n let tracks: Track[];\n try {\n tracks = (await this.host.rest.loadTracks(`ytsearch:${query}`)).tracks;\n } catch {\n return false; // search failed — advance normally\n }\n\n // Prefer a different upload than the one that just failed.\n const alt = tracks.find((t) => t.encoded && t.encoded !== failed.encoded);\n if (!alt) return false;\n\n alt.extras = { ...alt.extras, ...failed.extras }; // keep requester etc.\n await this.playTrack(alt);\n this.host.emit(\"trackRecovered\", this, { failed, alternate: alt });\n return true;\n }\n}\n","/** Thin wrapper over the control-plane HTTP API using the global `fetch`\n * (Node >= 18). Knows nothing about queues — just request/response. */\n\nimport { APIError, AuralisError, AuthError, RateLimited } from \"./errors.js\";\nimport {\n LoadResult,\n PlayerState,\n Snowflake,\n loadResultFromJson,\n playerStateFromJson,\n snowflakeStringify,\n} from \"./types.js\";\n\n/** Abort a stalled request rather than hanging a command forever. */\nconst DEFAULT_TIMEOUT_MS = 16_000;\n/** Transient control-plane statuses (node not ready / link failed) worth retrying. */\nconst RETRYABLE_STATUS = new Set([502, 503]);\n/** Per-attempt backoff. Length also sets how many extra tries an idempotent GET gets. */\nconst BACKOFF_MS = [200, 500];\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport class REST {\n private base: string;\n private headers: Record<string, string>;\n /** Per-request timeout (ms). Exposed so a slow-network deployment can raise it. */\n timeoutMs = DEFAULT_TIMEOUT_MS;\n\n constructor(baseUrl: string, token: string) {\n this.base = baseUrl.replace(/\\/$/, \"\");\n this.headers = {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n };\n }\n\n private async fetchWithTimeout(method: string, path: string, body?: unknown): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n return await fetch(`${this.base}${path}`, {\n method,\n headers: this.headers,\n // snowflakeStringify keeps 64-bit voice ids (channel_id/user_id) exact —\n // the control plane parses them as u64 bare numbers.\n body: body === undefined ? undefined : snowflakeStringify(body),\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n\n // Retry policy: GET is idempotent, so retry it on both transient 502/503 and\n // network/timeout errors. Writes only retry on 502/503 (a network error might\n // mean the write half-applied). 401/403/429/other 4xx are terminal — no retry.\n private async request(method: string, path: string, body?: unknown): Promise<any> {\n const idempotent = method === \"GET\";\n const maxAttempts = idempotent ? BACKOFF_MS.length + 1 : 2;\n let lastErr: unknown;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const isLast = attempt === maxAttempts - 1;\n try {\n const res = await this.fetchWithTimeout(method, path, body);\n if (res.status === 401 || res.status === 403) throw new AuthError(await res.text());\n if (res.status === 429) {\n const retry = res.headers.get(\"Retry-After\");\n throw new RateLimited(await res.text(), retry ? Number(retry) : undefined);\n }\n if (RETRYABLE_STATUS.has(res.status) && !isLast) {\n await sleep(BACKOFF_MS[attempt]);\n continue;\n }\n if (res.status >= 400) throw new APIError(res.status, await res.text());\n const ct = res.headers.get(\"Content-Type\") ?? \"\";\n return ct.includes(\"application/json\") ? res.json() : res.text();\n } catch (err) {\n // Protocol errors carry a definitive answer from the server — never retry.\n if (err instanceof AuralisError) throw err;\n // Network error / timeout: retry idempotent requests only.\n lastErr = err;\n if (idempotent && !isLast) {\n await sleep(BACKOFF_MS[attempt]);\n continue;\n }\n throw err;\n }\n }\n throw lastErr; // unreachable: the loop either returns or throws\n }\n\n // --- metadata ---------------------------------------------------------\n async loadTracks(identifier: string): Promise<LoadResult> {\n const q = new URLSearchParams({ identifier });\n return loadResultFromJson(await this.request(\"GET\", `/v1/loadtracks?${q}`));\n }\n\n async prewarm(encoded: string[], guildId?: bigint): Promise<any> {\n const body: any = { tracks: encoded.map((e) => ({ encoded: e })) };\n if (guildId !== undefined) body.guild_id = guildId;\n return this.request(\"POST\", \"/v1/tracks/prewarm\", body);\n }\n\n // --- player lifecycle -------------------------------------------------\n async createPlayer(\n guildId: bigint,\n voice: Record<string, unknown>,\n opts: { track?: string; volume?: number; paused?: boolean; audioGraph?: string } = {},\n ): Promise<any> {\n const body: any = {\n voice,\n volume: opts.volume ?? 100,\n paused: opts.paused ?? false,\n };\n if (opts.track) body.track = { encoded: opts.track };\n if (opts.audioGraph !== undefined) body.filters = { audio_graph: opts.audioGraph };\n return this.request(\"POST\", `/v1/guilds/${guildId}/players`, body);\n }\n\n async destroyPlayer(guildId: bigint): Promise<any> {\n return this.request(\"DELETE\", `/v1/guilds/${guildId}/players`);\n }\n\n async getState(guildId: bigint): Promise<PlayerState> {\n return playerStateFromJson(await this.request(\"GET\", `/v1/guilds/${guildId}/player`));\n }\n\n // --- playback control -------------------------------------------------\n async play(guildId: bigint, track: string, audioGraph?: string): Promise<any> {\n const body: any = { track: { encoded: track } };\n if (audioGraph !== undefined) body.filters = { audio_graph: audioGraph };\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/play`, body);\n }\n\n async stop(guildId: bigint): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/stop`, {});\n }\n\n async setPaused(guildId: bigint, paused: boolean): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/pause`, { paused });\n }\n\n async setVolume(guildId: bigint, volume: number): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/volume`, { volume });\n }\n\n async seek(guildId: bigint, positionMs: number): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/seek`, {\n position_ms: positionMs,\n });\n }\n\n async setFilters(guildId: bigint, audioGraph: string | null): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/filters`, {\n filters: { audio_graph: audioGraph },\n });\n }\n}\n"],"mappings":"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","names":["LoopMode","LoadType","EndReason","WebSocket","eventName"]}
|
package/dist/index.js
CHANGED
|
@@ -489,10 +489,13 @@ var Player = class {
|
|
|
489
489
|
if (this.current !== null) this.pushHistory(this.current);
|
|
490
490
|
return this.queue.shift();
|
|
491
491
|
}
|
|
492
|
-
if (this.loop === "queue" /* Queue */
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
492
|
+
if (this.loop === "queue" /* Queue */) {
|
|
493
|
+
const cycle = [...this.history, ...this.current ? [this.current] : []];
|
|
494
|
+
if (cycle.length > 0) {
|
|
495
|
+
this.queue = cycle;
|
|
496
|
+
this.history = [];
|
|
497
|
+
return this.queue.shift() ?? null;
|
|
498
|
+
}
|
|
496
499
|
}
|
|
497
500
|
if (this.autoplay !== null) return this.autoplay(this);
|
|
498
501
|
return null;
|
|
@@ -504,14 +507,27 @@ var Player = class {
|
|
|
504
507
|
return this.serialize(() => this.advanceInner(forced));
|
|
505
508
|
}
|
|
506
509
|
async advanceInner(forced = false) {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
510
|
+
for (let attempts = this.queue.length + 1; attempts >= 0; attempts--) {
|
|
511
|
+
const next = await this.nextTrack(forced);
|
|
512
|
+
if (next === null) {
|
|
513
|
+
this.current = null;
|
|
514
|
+
if (this.created) await this.host.rest.stop(this.guildId);
|
|
515
|
+
this.host.emit("queueEnd", this);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
await this.playTrack(next);
|
|
520
|
+
return;
|
|
521
|
+
} catch (e) {
|
|
522
|
+
if (e instanceof NotConnected) {
|
|
523
|
+
this.queue.unshift(next);
|
|
524
|
+
throw e;
|
|
525
|
+
}
|
|
526
|
+
this.current = null;
|
|
527
|
+
forced = true;
|
|
528
|
+
this.host.emit("trackException", this, { track: next, error: String(e) });
|
|
529
|
+
}
|
|
513
530
|
}
|
|
514
|
-
await this.playTrack(next);
|
|
515
531
|
}
|
|
516
532
|
async playTrack(track) {
|
|
517
533
|
if (!this.connected) throw new NotConnected(`guild ${this.guildId} has no voice connection`);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/errors.ts","../src/types.ts","../src/player.ts","../src/rest.ts"],"sourcesContent":["/** The top-level client: owns the REST wrapper, the WebSocket event loop, the\n * per-guild players, and event dispatch (via Node's EventEmitter). */\n\nimport { EventEmitter } from \"node:events\";\nimport WebSocket from \"ws\";\n\nimport { APIError, LoadError } from \"./errors.js\";\nimport { Player, PlayerHost } from \"./player.js\";\nimport { REST } from \"./rest.js\";\nimport { LoadResult, LoadType, Snowflake, Track, endReasonAdvances, playerStateFromJson } from \"./types.js\";\n\nexport interface ClientOptions {\n reconnect?: boolean;\n}\n\n/** Strongly-typed event map for `client.on(...)` / `client.emit(...)`. */\nexport interface ClientEvents {\n ready: [];\n /** Fired instead of `ready` when a reconnect re-establishes the session. */\n resumed: [];\n disconnect: [];\n error: [err: unknown];\n trackStart: [player: Player | bigint, data: any];\n trackEnd: [player: Player | bigint, data: any];\n trackException: [player: Player | bigint, data: any];\n trackStuck: [player: Player | bigint, data: any];\n playerUpdate: [player: Player | bigint, data: any];\n voiceClosed: [player: Player | bigint, data: any];\n // STATS is node-wide, so there may be no player/guild attached.\n stats: [player: Player | bigint | null, data: any];\n queueEnd: [player: Player];\n /** A dead source could not be recovered — the bot should tell the user. */\n trackStreamDead: [player: Player, data: { failed: Track }];\n /** A dead source was auto-recovered by playing an alternate upload. */\n trackRecovered: [player: Player, data: { failed: Track; alternate: Track }];\n /** Emitted when a new Player is created via {@link Client.getPlayer}. */\n playerCreated: [player: Player];\n /** Emitted when a player is fully torn down via {@link Player.disconnect}. */\n playerDestroyed: [guildId: bigint];\n}\n\n// WS op -> the event name we emit to listeners.\nconst EVENT_OPS: Record<string, keyof ClientEvents> = {\n TRACK_START: \"trackStart\",\n TRACK_END: \"trackEnd\",\n TRACK_EXCEPTION: \"trackException\",\n TRACK_STUCK: \"trackStuck\",\n PLAYER_UPDATE: \"playerUpdate\",\n VOICE_CLOSED: \"voiceClosed\",\n STATS: \"stats\",\n};\n\n// Declaration-merge typed on/once/off/emit overloads onto the class.\nexport interface Client {\n on<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this;\n once<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this;\n off<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this;\n emit<K extends keyof ClientEvents>(event: K, ...args: ClientEvents[K]): boolean;\n}\n\nexport class Client extends EventEmitter implements PlayerHost {\n readonly rest: REST;\n private baseUrl: string;\n private token: string;\n private reconnect: boolean;\n\n private playersMap = new Map<bigint, Player>();\n private ws: WebSocket | null = null;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private sessionId: string | null = null;\n private closed = false;\n private backoff = 1000;\n private hasConnected = false;\n private heartbeatSentAt = 0;\n\n /** Last measured heartbeat round-trip latency in ms (-1 until first ack). */\n latencyMs = -1;\n /** Most recent STATS frame payload from the node, or null. */\n lastStats: Record<string, unknown> | null = null;\n\n constructor(baseUrl: string, token: string, opts: ClientOptions = {}) {\n super();\n this.baseUrl = baseUrl.replace(/\\/$/, \"\");\n this.token = token;\n this.reconnect = opts.reconnect ?? true;\n this.rest = new REST(this.baseUrl, token);\n }\n\n // --- lifecycle --------------------------------------------------------\n async start(): Promise<void> {\n this.closed = false;\n this.openSocket();\n }\n\n /** Resolves once the client fires `ready` or `resumed`. If it is already\n * connected it resolves on the next tick. Rejects after `timeoutMs` (default\n * 30 s) so callers don't hang forever on a bad URL/token. */\n waitForReady(timeoutMs = 30_000): Promise<void> {\n return new Promise((resolve, reject) => {\n if (this.hasConnected) {\n resolve();\n return;\n }\n const onReady = () => { clearTimeout(timer); resolve(); };\n const onResumed = () => { clearTimeout(timer); resolve(); };\n const timer = setTimeout(() => {\n this.off(\"ready\", onReady);\n this.off(\"resumed\", onResumed);\n reject(new Error(\"[auralis] waitForReady timed out\"));\n }, timeoutMs);\n this.once(\"ready\", onReady);\n this.once(\"resumed\", onResumed);\n });\n }\n\n /** Gracefully disconnect every active player then close the WebSocket.\n * Prefer this over `close()` when you want clean backend teardown. */\n async destroy(): Promise<void> {\n await Promise.allSettled(this.players().map((p) => p.disconnect()));\n await this.close();\n }\n\n async close(): Promise<void> {\n this.closed = true;\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n // Drop our listeners so a lingering socket can't fire callbacks post-close.\n this.ws?.removeAllListeners();\n this.ws?.close();\n this.ws = null;\n }\n\n /** Emit \"error\" without the Node EventEmitter footgun: an unlistened \"error\"\n * throws and would crash the host process, so fall back to a console warning. */\n private safeError(err: unknown): void {\n if (this.listenerCount(\"error\") > 0) this.emit(\"error\", err);\n else console.warn(\"[auralis] unhandled client error:\", err);\n }\n\n // --- players ----------------------------------------------------------\n getPlayer(guildId: Snowflake): Player {\n const id = BigInt(guildId);\n let p = this.playersMap.get(id);\n if (!p) {\n p = new Player(this, id);\n this.playersMap.set(id, p);\n this.subscribeGuild(id);\n this.emit(\"playerCreated\", p);\n }\n return p;\n }\n\n players(): Player[] {\n return [...this.playersMap.values()];\n }\n\n dropPlayer(guildId: bigint): void {\n this.playersMap.delete(guildId);\n this.emit(\"playerDestroyed\", guildId);\n }\n\n // --- metadata convenience --------------------------------------------\n loadTracks(identifier: string): Promise<LoadResult> {\n return this.rest.loadTracks(identifier);\n }\n\n /** `ytsearch:`-prefix a bare query and return the hits. Throws `LoadError` on\n * a server-side error so callers aren't silently handed an empty array. */\n async search(query: string): Promise<Track[]> {\n const first = query.split(\" \", 1)[0];\n const ident = first.includes(\":\") ? query : `ytsearch:${query}`;\n const result = await this.loadTracks(ident);\n if (result.loadType === LoadType.Error) {\n throw new LoadError(result.error ?? `search failed for: ${query}`);\n }\n return result.tracks;\n }\n\n // --- websocket --------------------------------------------------------\n private get wsUrl(): string {\n return this.baseUrl.replace(/^http/, \"ws\") + \"/v1/ws\";\n }\n\n private openSocket(): void {\n if (this.closed) return;\n const ws = new WebSocket(this.wsUrl, {\n headers: { Authorization: `Bearer ${this.token}` },\n });\n this.ws = ws;\n\n ws.on(\"open\", () => {\n this.backoff = 1000;\n this.subscribeAll();\n });\n ws.on(\"message\", (raw) => {\n let frame: any;\n try {\n frame = JSON.parse(raw.toString());\n } catch {\n return;\n }\n // handleFrame is async; a rejected promise here would be an unhandled\n // rejection (process-fatal on newer Node). Route failures to safeError.\n this.handleFrame(frame).catch((e) => this.safeError(e));\n });\n ws.on(\"error\", (err) => this.safeError(err));\n ws.on(\"close\", () => {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n this.ws = null;\n if (this.closed || !this.reconnect) return;\n this.emit(\"disconnect\");\n // Backoff with ±25% jitter so many clients don't reconnect in lockstep.\n const jitter = this.backoff * (0.75 + Math.random() * 0.5);\n this.reconnectTimer = setTimeout(() => this.openSocket(), jitter);\n this.backoff = Math.min(this.backoff * 2, 30000);\n });\n }\n\n /** After a reconnect, pull each player's authoritative state from the node so\n * position/volume/paused are accurate again. Per-player failures are ignored. */\n private async resyncPlayers(): Promise<void> {\n await Promise.all(\n this.players().map(async (p) => {\n try {\n p.applyState(await this.rest.getState(p.guildId));\n } catch {\n // node may not have this player (it was dropped during the gap) — skip\n }\n }),\n );\n }\n\n private subscribeAll(): void {\n if (this.ws?.readyState === WebSocket.OPEN && this.playersMap.size > 0) {\n this.send({\n op: \"SUBSCRIBE\",\n d: { guilds: [...this.playersMap.keys()].map(String) },\n });\n }\n }\n\n private subscribeGuild(guildId: bigint): void {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.send({\n op: \"SUBSCRIBE\",\n d: { guilds: [String(guildId)] },\n });\n }\n }\n\n private send(payload: unknown): void {\n if (this.ws?.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(payload));\n }\n\n private async handleFrame(frame: any): Promise<void> {\n const op: string = frame.op;\n const d = frame.d ?? {};\n\n if (op === \"HELLO\") {\n const interval = (d.heartbeat_interval_ms ?? 30000) as number;\n if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);\n let seq = 0;\n this.heartbeatTimer = setInterval(() => {\n this.heartbeatSentAt = Date.now();\n this.send({ op: \"HEARTBEAT\", d: { seq: ++seq } });\n }, interval);\n return;\n }\n if (op === \"READY\") {\n const resumed = this.hasConnected;\n this.sessionId = d.session_id ?? null;\n this.hasConnected = true;\n this.subscribeAll();\n if (resumed) {\n // Reconnect: resync each player's live state from the node so callers\n // see accurate position/volume/paused after the gap (a practical resume).\n await this.resyncPlayers();\n this.emit(\"resumed\");\n } else {\n this.emit(\"ready\");\n }\n return;\n }\n if (op === \"HEARTBEAT_ACK\") {\n if (this.heartbeatSentAt) this.latencyMs = Date.now() - this.heartbeatSentAt;\n return;\n }\n if (op === \"STATS\") {\n this.lastStats = d;\n // STATS is node-wide — emit and return before the per-guild player lookup.\n const eventName = EVENT_OPS[op];\n if (eventName) this.emit(eventName, null, d);\n return;\n }\n\n // WS frames carry guild_id as a string — BigInt keeps 64-bit ids exact so\n // the lookup matches the player keyed by getPlayer(BigInt(...)).\n const guildId = d.guild_id != null ? BigInt(d.guild_id) : null;\n const player = guildId !== null ? this.playersMap.get(guildId) : undefined;\n\n if (op === \"TRACK_END\" && player) {\n // Drive auto-advance BEFORE notifying the user so handlers see new state.\n await player.onTrackEnd(endReasonAdvances(d.reason ?? \"FINISHED\"));\n } else if (op === \"PLAYER_UPDATE\" && player) {\n player.applyState(playerStateFromJson(d.state ?? {}));\n } else if (op === \"TRACK_STUCK\" && player) {\n // Skip the stalled track before notifying, so handlers see the new state.\n await player.onTrackStuck();\n } else if (op === \"VOICE_CLOSED\" && player && d.by_remote && player.rejoinOnVoiceClosed) {\n // Discord killed the session; reset so a re-join re-creates and resumes.\n player.handleVoiceClosed();\n }\n\n const eventName = EVENT_OPS[op];\n if (eventName) this.emit(eventName, player ?? guildId, d);\n }\n}\n","/** Exception hierarchy. Every network/protocol failure is an `AuralisError`. */\n\nexport class AuralisError extends Error {}\n\n/** 401/403 — the bearer token is missing, wrong, or not allowed. */\nexport class AuthError extends AuralisError {}\n\n/** 429 — too many requests. `retryAfter` is seconds if the server gave one. */\nexport class RateLimited extends AuralisError {\n constructor(message: string, public retryAfter?: number) {\n super(message);\n }\n}\n\n/** Any other non-2xx HTTP response. */\nexport class APIError extends AuralisError {\n constructor(public status: number, message: string) {\n super(`HTTP ${status}: ${message}`);\n }\n}\n\n/** Tried to play before the guild's voice connection was established. */\nexport class NotConnected extends AuralisError {}\n\n/** `loadtracks` returned `error` or nothing usable. */\nexport class LoadError extends AuralisError {}\n","/** Wire vocabulary and data models — mirror the control-plane JSON exactly. */\n\n/** A Discord snowflake. Accepted as string/number/bigint at the API boundary and\n * normalized to `bigint` internally: a 64-bit id cannot survive a JS `number`\n * (over 2^53 the low digits corrupt), and the control plane parses ids as `u64`. */\nexport type Snowflake = string | number | bigint;\n\n// A sentinel that is valid JSON string content but cannot appear in real API\n// data (encoded track tokens, guild ids, etc.). The double-boundary markers\n// make accidental collision with real string values negligible.\nconst BIG_TAG = \"||BIGINT||\";\n\n/** `JSON.stringify` that emits `bigint` values as bare (unquoted) JSON numbers.\n * The control plane's voice payload wants snowflakes as `u64` numbers, but a JS\n * `number` can't hold one exactly and `JSON.stringify` throws on `bigint`. We tag\n * bigints, then strip the quotes so the wire value is an exact integer literal. */\nexport function snowflakeStringify(obj: unknown): string {\n return JSON.stringify(obj, (_k, v) =>\n typeof v === \"bigint\" ? `${BIG_TAG}${v}${BIG_TAG}` : v,\n ).replace(/\"\\|\\|BIGINT\\|\\|(\\d+)\\|\\|BIGINT\\|\\|\"/g, \"$1\");\n}\n\n/** How the queue advances when a track finishes. */\nexport enum LoopMode {\n Off = \"off\",\n Track = \"track\", // replay the current track forever\n Queue = \"queue\", // loop the whole queue\n}\n\n/** `load_type` returned by `GET /v1/loadtracks`. */\nexport enum LoadType {\n Track = \"track\",\n Playlist = \"playlist\",\n Search = \"search\",\n Empty = \"empty\",\n Error = \"error\",\n}\n\n/** Why a track ended. Only FINISHED / LOAD_FAILED advance the queue. */\nexport enum EndReason {\n Finished = \"FINISHED\",\n LoadFailed = \"LOAD_FAILED\",\n Stopped = \"STOPPED\",\n Replaced = \"REPLACED\",\n Cleanup = \"CLEANUP\",\n}\n\nexport function endReasonAdvances(reason: string): boolean {\n return reason === EndReason.Finished || reason === EndReason.LoadFailed;\n}\n\n/** A playable track. `encoded` is the opaque token the backend needs; the rest\n * is display metadata the bot keeps for now-playing embeds. */\nexport interface Track {\n encoded: string;\n identifier: string;\n title: string;\n author: string;\n lengthMs: number;\n isStream: boolean;\n uri?: string;\n sourceName: string;\n artworkUrl?: string;\n isrc?: string;\n /** Free-form, set by the bot (e.g. who requested it). Never sent upstream. */\n extras: Record<string, unknown>;\n}\n\nexport function trackFromJson(d: any): Track {\n return {\n encoded: d.encoded,\n identifier: d.identifier ?? \"\",\n title: d.title ?? \"\",\n author: d.author ?? \"\",\n lengthMs: d.length_ms ?? 0,\n isStream: d.is_stream ?? false,\n uri: d.uri ?? undefined,\n sourceName: d.source_name ?? \"\",\n artworkUrl: d.artwork_url ?? undefined,\n isrc: d.isrc ?? undefined,\n extras: {},\n };\n}\n\nexport interface LoadResult {\n loadType: LoadType;\n tracks: Track[];\n playlistName?: string;\n error?: string;\n}\n\nexport function loadResultFromJson(d: any): LoadResult {\n return {\n loadType: (d.load_type ?? \"empty\") as LoadType,\n tracks: (d.tracks ?? []).map(trackFromJson),\n playlistName: d.playlist_name ?? undefined,\n error: d.error ?? undefined,\n };\n}\n\nexport interface PlayerState {\n connected: boolean;\n playing: boolean;\n paused: boolean;\n positionMs: number;\n volume: number;\n track?: Track;\n}\n\nexport function playerStateFromJson(d: any): PlayerState {\n return {\n connected: d.connected ?? false,\n playing: d.playing ?? false,\n paused: d.paused ?? false,\n positionMs: d.position_ms ?? 0,\n volume: d.volume ?? 100,\n track: d.track ? trackFromJson(d.track) : undefined,\n };\n}\n\n/** Raw Discord voice payloads the bot forwards from its gateway. */\nexport interface VoiceServerUpdate {\n token: string;\n endpoint: string;\n}\nexport interface VoiceStateUpdate {\n session_id: string;\n channel_id: Snowflake | null;\n user_id: Snowflake;\n}\n","/** Per-guild player: the client-side queue brain.\n *\n * The backend is deliberately queue-agnostic — it only knows play / stop / pause\n * / seek / volume. Everything queue-shaped (skip, loop, shuffle, auto-advance,\n * autoplay) lives here, driven by the `TRACK_END` event.\n *\n * Advance rules (the one path everything funnels through):\n * - `play()` issues a REST *replace* → the old track's `TRACK_END(REPLACED)` is\n * ignored by the client, so we never double-advance.\n * - a natural finish → `TRACK_END(FINISHED|LOAD_FAILED)` → `advance()`.\n * - `skip()` forces `advance()` (ignoring `LoopMode.Track`).\n */\n\nimport { APIError, LoadError, NotConnected } from \"./errors.js\";\nimport { REST } from \"./rest.js\";\nimport {\n LoadType,\n LoopMode,\n PlayerState,\n Snowflake,\n Track,\n VoiceServerUpdate,\n VoiceStateUpdate,\n} from \"./types.js\";\n\n/** What a Player needs from its owner. The `Client` implements this. */\nexport interface PlayerHost {\n rest: REST;\n emit(event: string, ...args: unknown[]): void;\n dropPlayer(guildId: bigint): void;\n}\n\nexport type AutoplayHook = (player: Player) => Promise<Track | null>;\n\n/** Named EQ presets accepted by {@link Player.setEqualizer}. */\nexport type EqPreset =\n | \"flat\"\n | \"bassboost\"\n | \"soft\"\n | \"nightcore\"\n | \"vaporwave\"\n | \"earrape\"\n | \"treble\"\n | \"karaoke\";\n\n/** Portable snapshot of a player's queue state, produced by {@link Player.export}\n * and consumed by {@link Player.restore}. Safe to JSON.stringify / store. */\nexport interface SerializedPlayer {\n version: 1;\n guildId: string;\n queue: Track[];\n history: Track[];\n current: Track | null;\n loop: LoopMode;\n volume: number;\n paused: boolean;\n}\n\nexport class Player {\n queue: Track[] = [];\n history: Track[] = [];\n current: Track | null = null;\n /** Cap on retained history so a long-lived player can't leak memory. Queue-loop\n * still works; it just restores the most recent `historyLimit` tracks. */\n historyLimit = 200;\n loop: LoopMode = LoopMode.Off;\n volume = 100;\n paused = false;\n state: PlayerState = {\n connected: false,\n playing: false,\n paused: false,\n positionMs: 0,\n volume: 100,\n };\n /** Optional async hook used when the queue and loop are exhausted. */\n autoplay: AutoplayHook | null = null;\n\n /** When a non-stream track \"finishes\" without ever streaming (dead source:\n * geo/age-restricted, throttled, removed), search for and play an alternate\n * upload instead of silently draining the queue. */\n recoverDeadStreams = true;\n /** A track whose furthest observed position never passed this many ms is\n * treated as never-streamed (used by {@link recoverDeadStreams}). */\n deadStreamThresholdMs = 1_500;\n /** Give up recovering a single dead track after this many alternate attempts. */\n deadStreamRecoveryLimit = 2;\n /** Auto-skip a track the node reports as `TRACK_STUCK` instead of stalling. */\n advanceOnStuck = true;\n /** On a remote `VOICE_CLOSED`, reset so the next voice material re-creates the\n * player and resumes the current track (the bot must re-send its op-4 join). */\n rejoinOnVoiceClosed = true;\n\n /** The guild this player drives. Always a `bigint` so 64-bit ids stay exact. */\n readonly guildId: bigint;\n\n private created = false;\n private voiceServer: { token: string; endpoint: string } | null = null;\n private voiceState: { session_id: string; channel_id: bigint; user_id: bigint } | null = null;\n /** Furthest position seen for the current track — reset on every track start. */\n private maxPositionMs = 0;\n /** Alternate-upload attempts spent on the current dead track. */\n private recoverAttempts = 0;\n /** Wall-clock time (ms) when the last PLAYER_UPDATE position was received.\n * Used by the {@link positionMs} interpolating getter. */\n private _positionTimestamp = 0;\n\n constructor(private host: PlayerHost, guildId: Snowflake) {\n this.guildId = BigInt(guildId);\n }\n\n // --- voice plumbing ---------------------------------------------------\n handleVoiceServerUpdate(data: VoiceServerUpdate): void {\n this.voiceServer = { token: data.token, endpoint: data.endpoint };\n void this.startIfIdle();\n }\n\n handleVoiceStateUpdate(data: VoiceStateUpdate): void {\n if (data.channel_id === null || data.channel_id === undefined) {\n this.voiceState = null;\n return;\n }\n this.voiceState = {\n session_id: data.session_id,\n channel_id: BigInt(data.channel_id),\n user_id: BigInt(data.user_id),\n };\n void this.startIfIdle();\n }\n\n get connected(): boolean {\n return this.voiceServer !== null && this.voiceState !== null;\n }\n\n /** Called by the client on `PLAYER_UPDATE` (and reconnect resync). Tracks the\n * furthest position reached so a \"finished but never streamed\" track can be\n * spotted, and clears the recovery counter once a track is genuinely playing. */\n applyState(state: PlayerState): void {\n this.state = state;\n this._positionTimestamp = Date.now();\n if (state.positionMs > this.maxPositionMs) this.maxPositionMs = state.positionMs;\n if (this.maxPositionMs > this.deadStreamThresholdMs) this.recoverAttempts = 0;\n }\n\n /** Discord terminated the voice session (remote `VOICE_CLOSED`). The node has\n * dropped our player, so reset local voice/created state and requeue the\n * current track; once the bot re-sends its op-4 join, fresh voice material\n * re-creates the player and resumes. Bot-initiated closes don't call this. */\n handleVoiceClosed(): void {\n if (this.current !== null) {\n this.queue.unshift(this.current);\n this.current = null;\n }\n this.created = false;\n this.voiceServer = null;\n this.voiceState = null;\n }\n\n private voicePayload(): Record<string, unknown> {\n return { ...this.voiceServer!, ...this.voiceState!, guild_id: this.guildId };\n }\n\n /** Serializes all queue advancement. Without it, concurrent triggers — `play()`,\n * a voice-update `startIfIdle()`, a `TRACK_END`, `skip()` — can each observe\n * `current === null` and both create a player, which serializes on the control\n * plane's per-guild ownership lock and blows the request timeout (the classic\n * \"operation was aborted\"). Every advance path runs through this chain. */\n private opLock: Promise<unknown> = Promise.resolve();\n private serialize<T>(fn: () => Promise<T>): Promise<T> {\n const run = this.opLock.then(fn, fn);\n this.opLock = run.then(() => {}, () => {});\n return run;\n }\n\n /** Start the queue only if the player is idle. Safe to call from multiple\n * concurrent triggers — the idle check runs inside the serialized section, so\n * exactly one caller wins and the rest see `current !== null` and no-op. */\n private startIfIdle(): Promise<void> {\n return this.serialize(async () => {\n if (this.connected && this.current === null && this.queue.length > 0) {\n await this.advanceInner(false);\n }\n });\n }\n\n // --- public commands --------------------------------------------------\n async play(query: string | Track, requester?: Snowflake): Promise<Track[]> {\n const tracks = await this.resolve(query);\n for (const t of tracks) {\n if (requester !== undefined) t.extras.requester = requester;\n this.queue.push(t);\n }\n await this.startIfIdle();\n return tracks;\n }\n\n async add(query: string | Track, requester?: Snowflake): Promise<Track[]> {\n const tracks = await this.resolve(query);\n for (const t of tracks) {\n if (requester !== undefined) t.extras.requester = requester;\n this.queue.push(t);\n }\n return tracks;\n }\n\n async skip(): Promise<void> {\n await this.advance(true);\n }\n\n async stop(): Promise<void> {\n this.queue = [];\n this.current = null;\n this.history = []; // don't let a later queue-loop replay tracks from before a stop\n if (this.created) await this.host.rest.stop(this.guildId);\n }\n\n async pause(): Promise<void> {\n await this.setPausedInternal(true);\n }\n\n async resume(): Promise<void> {\n await this.setPausedInternal(false);\n }\n\n async setPause(paused: boolean): Promise<void> {\n await this.setPausedInternal(paused);\n }\n\n async seek(positionMs: number): Promise<void> {\n if (this.created) await this.host.rest.seek(this.guildId, positionMs);\n }\n\n async setVolume(volume: number): Promise<void> {\n this.volume = Math.max(0, Math.min(1000, volume));\n if (this.created) await this.host.rest.setVolume(this.guildId, this.volume);\n }\n\n setLoop(mode: LoopMode): void {\n this.loop = mode;\n }\n\n shuffle(): void {\n for (let i = this.queue.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [this.queue[i], this.queue[j]] = [this.queue[j], this.queue[i]];\n }\n }\n\n /** Apply a raw ffmpeg `-af` filtergraph (e.g. `\"bass=g=8\"`); `null` clears. */\n async setFilters(audioGraph: string | null): Promise<void> {\n if (this.created) await this.host.rest.setFilters(this.guildId, audioGraph);\n }\n\n remove(index: number): Track | undefined {\n return this.queue.splice(index, 1)[0];\n }\n\n clear(): void {\n this.queue = [];\n }\n\n /** Reorder the queue: move the track at `from` to `to`. No-op on bad indices. */\n move(from: number, to: number): void {\n if (from < 0 || from >= this.queue.length || to < 0 || to >= this.queue.length) return;\n const [t] = this.queue.splice(from, 1);\n this.queue.splice(to, 0, t);\n }\n\n /** Resolve `query` and insert it at `index` (clamped) instead of appending. */\n async insert(index: number, query: string | Track, requester?: Snowflake): Promise<Track[]> {\n const tracks = await this.resolve(query);\n for (const t of tracks) if (requester !== undefined) t.extras.requester = requester;\n const at = Math.max(0, Math.min(index, this.queue.length));\n this.queue.splice(at, 0, ...tracks);\n await this.startIfIdle();\n return tracks;\n }\n\n /** Batch-enqueue several queries/tracks in order. Failed resolves are skipped. */\n async addMany(queries: (string | Track)[], requester?: Snowflake): Promise<Track[]> {\n const added: Track[] = [];\n for (const q of queries) {\n try {\n const tracks = await this.resolve(q);\n for (const t of tracks) {\n if (requester !== undefined) t.extras.requester = requester;\n this.queue.push(t);\n added.push(t);\n }\n } catch {\n // skip a bad query rather than aborting the whole batch\n }\n }\n if (added.length > 0) await this.startIfIdle();\n return added;\n }\n\n /** Skip forward to queue position `index`, discarding the ones in between. */\n async jump(index: number): Promise<void> {\n if (index < 0 || index >= this.queue.length) return;\n const skipped = this.queue.splice(0, index);\n for (const t of skipped) this.pushHistory(t);\n await this.advance(true);\n }\n\n /** Replay the previous track (from history), pushing the current one back on. */\n async previous(): Promise<void> {\n const prev = this.history.pop();\n if (!prev) return;\n // Push current back to the front of the queue — advance(true) will call\n // nextTrack(forced=true), which pushes `this.current` to history again.\n // To avoid a double push, clear `this.current` before advancing so\n // nextTrack sees null and skips the pushHistory call.\n if (this.current !== null) this.queue.unshift(this.current);\n this.current = null;\n this.queue.unshift(prev);\n await this.advance(true);\n }\n\n /** Convenience EQ: bass boost in dB (0 clears). Built on {@link setFilters}. */\n async setBassBoost(gainDb: number): Promise<void> {\n await this.setFilters(gainDb === 0 ? null : `bass=g=${gainDb}`);\n }\n\n /** Convenience speed/pitch (nightcore/vaporwave): `rate` 1.0 = normal.\n * Uses `atempo` for tempo and `asetrate` for pitch shift — both evaluated\n * in JS so the filter string contains literal numbers, not expressions. */\n async setSpeed(rate: number): Promise<void> {\n const r = Math.max(0.5, Math.min(2.0, rate));\n await this.setFilters(r === 1 ? null : `atempo=${r},asetrate=${48000 * r}`);\n }\n\n nowPlaying(): Track | null {\n return this.current;\n }\n\n /** Enqueue a track or query to play immediately after the current one.\n * Unlike `insert(0, ...)`, this is an explicit \"play next\" intent — it\n * always inserts at position 0 regardless of queue state and starts\n * playback if the player is idle. */\n async playNext(query: string | Track, requester?: Snowflake): Promise<Track[]> {\n return this.insert(0, query, requester);\n }\n\n /** Remove all duplicate tracks from the queue (keeps first occurrence).\n * Uniqueness is determined by `encoded` token. Returns the number removed. */\n dedupe(): number {\n const seen = new Set<string>();\n const before = this.queue.length;\n this.queue = this.queue.filter((t) => {\n if (seen.has(t.encoded)) return false;\n seen.add(t.encoded);\n return true;\n });\n return before - this.queue.length;\n }\n\n /** Find all tracks in the queue whose title or author contains `query`\n * (case-insensitive). Returns `[index, track]` pairs so callers can remove\n * or jump to a result by index. */\n find(query: string): [number, Track][] {\n const q = query.toLowerCase();\n return this.queue.reduce<[number, Track][]>((acc, t, i) => {\n if (t.title.toLowerCase().includes(q) || t.author.toLowerCase().includes(q)) {\n acc.push([i, t]);\n }\n return acc;\n }, []);\n }\n\n /** Total duration of all queued tracks in milliseconds. Streams count as 0.\n * Does not include the currently playing track. */\n get totalDurationMs(): number {\n return this.queue.reduce((sum, t) => sum + (t.isStream ? 0 : t.lengthMs), 0);\n }\n\n /** Interpolated playback position in milliseconds. Between `PLAYER_UPDATE`\n * frames this advances using wall-clock time so progress bars stay smooth.\n * Returns 0 if nothing is playing or the player is paused. */\n get positionMs(): number {\n if (!this.state.playing || this.state.paused) return this.state.positionMs;\n const elapsed = Date.now() - this._positionTimestamp;\n return Math.min(\n this.state.positionMs + elapsed,\n this.current?.lengthMs ?? this.state.positionMs + elapsed,\n );\n }\n\n /** Serialize current queue, history, loop mode and volume into a plain object\n * that can be JSON-stringified and stored. Restore with {@link Player.restore}. */\n export(): SerializedPlayer {\n return {\n version: 1,\n guildId: String(this.guildId),\n queue: this.queue.map((t) => ({ ...t })),\n history: this.history.map((t) => ({ ...t })),\n current: this.current ? { ...this.current } : null,\n loop: this.loop,\n volume: this.volume,\n paused: this.paused,\n };\n }\n\n /** Restore queue/history/loop/volume from a previously exported snapshot.\n * Does NOT auto-start playback — call `startIfIdle()` or let the next\n * voice event trigger it. */\n restore(snapshot: SerializedPlayer): void {\n this.queue = snapshot.queue.map((t) => ({ ...t }));\n this.history = snapshot.history.map((t) => ({ ...t }));\n this.current = snapshot.current ? { ...snapshot.current } : null;\n this.loop = snapshot.loop;\n this.volume = snapshot.volume;\n this.paused = snapshot.paused;\n }\n\n /** Apply a named EQ preset. `\"flat\"` clears all filters.\n *\n * | preset | effect |\n * |-------------|---------------------------------------------|\n * | flat | no filters (passthrough) |\n * | bassboost | heavy bass boost (+8 dB) |\n * | soft | gentle bass lift (+3 dB) |\n * | nightcore | speed+pitch up (1.3×) |\n * | vaporwave | speed+pitch down (0.8×) |\n * | earrape | extreme distortion / clipping (use wisely) |\n * | treble | high-frequency boost (+6 dB) |\n * | karaoke | basic vocal removal |\n */\n async setEqualizer(preset: EqPreset): Promise<void> {\n const graphs: Record<EqPreset, string | null> = {\n flat: null,\n bassboost: \"bass=g=8\",\n soft: \"bass=g=3\",\n nightcore: `atempo=1.3,asetrate=${Math.round(48000 * 1.3)}`,\n vaporwave: `atempo=0.8,asetrate=${Math.round(48000 * 0.8)}`,\n earrape: \"acrusher=level_in=8:level_out=18:bits=8:mode=log:aa=1\",\n treble: \"treble=g=6\",\n karaoke: \"pan=stereo|c0=c0-c1|c1=c1-c0\",\n };\n await this.setFilters(graphs[preset]);\n }\n\n /** Reverse the current queue order in-place. */\n reverse(): void {\n this.queue.reverse();\n }\n\n /** Remove all tracks from the queue that were requested by `requesterId`. */\n removeBy(requesterId: Snowflake): number {\n const id = String(requesterId);\n const before = this.queue.length;\n this.queue = this.queue.filter((t) => String(t.extras.requester) !== id);\n return before - this.queue.length;\n }\n\n /** Peek at the next N tracks without mutating state. Defaults to 5. */\n peek(n = 5): Track[] {\n return this.queue.slice(0, n);\n }\n\n /** Returns true if nothing is queued and nothing is playing. */\n get idle(): boolean {\n return this.current === null && this.queue.length === 0;\n }\n\n async disconnect(): Promise<void> {\n this.queue = [];\n this.current = null;\n this.history = [];\n this.paused = false;\n this.volume = 100;\n if (this.created) await this.host.rest.destroyPlayer(this.guildId);\n this.created = false;\n this.voiceServer = null;\n this.voiceState = null;\n this.host.dropPlayer(this.guildId);\n }\n\n // --- internals --------------------------------------------------------\n /** Append to history, evicting the oldest past `historyLimit` (leak guard). */\n private pushHistory(track: Track): void {\n this.history.push(track);\n if (this.history.length > this.historyLimit) {\n this.history.splice(0, this.history.length - this.historyLimit);\n }\n }\n\n private async resolve(query: string | Track): Promise<Track[]> {\n if (typeof query !== \"string\") return [query];\n const result = await this.host.rest.loadTracks(query);\n if (result.error || result.tracks.length === 0) {\n throw new LoadError(result.error ?? `no tracks for ${query}`);\n }\n return result.loadType === LoadType.Playlist ? result.tracks : [result.tracks[0]];\n }\n\n private async nextTrack(forced: boolean): Promise<Track | null> {\n if (this.loop === LoopMode.Track && this.current !== null && !forced) {\n return this.current;\n }\n if (this.queue.length > 0) {\n if (this.current !== null) this.pushHistory(this.current);\n return this.queue.shift()!;\n }\n if (this.loop === LoopMode.Queue && this.history.length > 0) {\n this.queue = [...this.history, ...(this.current ? [this.current] : [])];\n this.history = [];\n return this.queue.shift() ?? null;\n }\n if (this.autoplay !== null) return this.autoplay(this);\n return null;\n }\n\n /** Public advance paths (skip / previous / jump / track-end / stuck) always\n * advance, but still run through the serialized op-lock so they can't race a\n * concurrent {@link startIfIdle} into a double-create. */\n private advance(forced = false): Promise<void> {\n return this.serialize(() => this.advanceInner(forced));\n }\n\n private async advanceInner(forced = false): Promise<void> {\n const next = await this.nextTrack(forced);\n if (next === null) {\n this.current = null;\n if (this.created) await this.host.rest.stop(this.guildId);\n this.host.emit(\"queueEnd\", this);\n return;\n }\n await this.playTrack(next);\n }\n\n private async playTrack(track: Track): Promise<void> {\n if (!this.connected) throw new NotConnected(`guild ${this.guildId} has no voice connection`);\n this.current = track;\n this.maxPositionMs = 0; // fresh track — restart dead-stream position tracking\n if (!this.created) {\n await this.createBackendPlayer(track);\n } else {\n try {\n await this.host.rest.play(this.guildId, track.encoded);\n } catch (e) {\n // 404 GUILD_NOT_FOUND: the backend lost our player (its node restarted, or\n // the control plane dropped it on a disconnect) while our local `created`\n // is stale. Recreate it from our held voice material rather than 404-ing\n // forever — otherwise one node blip wedges every future play until restart.\n if (e instanceof APIError && e.status === 404) {\n this.created = false;\n await this.createBackendPlayer(track);\n } else {\n throw e;\n }\n }\n }\n }\n\n private async createBackendPlayer(track: Track): Promise<void> {\n await this.host.rest.createPlayer(this.guildId, this.voicePayload(), {\n track: track.encoded,\n volume: this.volume,\n paused: this.paused,\n });\n this.created = true;\n }\n\n private async setPausedInternal(paused: boolean): Promise<void> {\n this.paused = paused;\n if (this.created) await this.host.rest.setPaused(this.guildId, paused);\n }\n\n /** Called by the client's WS loop when a TRACK_END for this guild arrives.\n * `advances` is true for FINISHED / LOAD_FAILED. Before draining the queue we\n * check for a dead source and try to recover it with an alternate upload. */\n async onTrackEnd(advances: boolean): Promise<void> {\n if (!advances) return;\n // Loop-a-single-track is an explicit user choice — never second-guess it.\n if (this.recoverDeadStreams && this.loop !== LoopMode.Track && this.isDeadStream()) {\n if (await this.tryRecover()) return; // an alternate upload is now playing\n }\n await this.advance();\n }\n\n /** Called by the client on `TRACK_STUCK`: skip the stalled track. */\n async onTrackStuck(): Promise<void> {\n if (this.advanceOnStuck) await this.advance(true);\n }\n\n /** A non-stream track that \"finished\" without its position ever advancing\n * almost certainly never streamed. Genuinely short tracks (length at most a\n * few × the threshold) are excluded so we don't loop on real short songs. */\n private isDeadStream(): boolean {\n const t = this.current;\n if (t === null || t.isStream) return false;\n if (this.maxPositionMs > this.deadStreamThresholdMs) return false;\n return t.lengthMs === 0 || t.lengthMs > this.deadStreamThresholdMs * 4;\n }\n\n /** Search for an alternate upload of the current (dead) track and play it.\n * Returns true when an alternate started; false to let the caller advance. */\n private async tryRecover(): Promise<boolean> {\n const failed = this.current;\n if (failed === null) return false;\n\n this.recoverAttempts++;\n if (this.recoverAttempts > this.deadStreamRecoveryLimit) {\n this.recoverAttempts = 0;\n this.host.emit(\"trackStreamDead\", this, { failed });\n return false;\n }\n\n const query = [failed.title, failed.author].filter(Boolean).join(\" \").trim();\n if (query === \"\") return false;\n\n let tracks: Track[];\n try {\n tracks = (await this.host.rest.loadTracks(`ytsearch:${query}`)).tracks;\n } catch {\n return false; // search failed — advance normally\n }\n\n // Prefer a different upload than the one that just failed.\n const alt = tracks.find((t) => t.encoded && t.encoded !== failed.encoded);\n if (!alt) return false;\n\n alt.extras = { ...alt.extras, ...failed.extras }; // keep requester etc.\n await this.playTrack(alt);\n this.host.emit(\"trackRecovered\", this, { failed, alternate: alt });\n return true;\n }\n}\n","/** Thin wrapper over the control-plane HTTP API using the global `fetch`\n * (Node >= 18). Knows nothing about queues — just request/response. */\n\nimport { APIError, AuralisError, AuthError, RateLimited } from \"./errors.js\";\nimport {\n LoadResult,\n PlayerState,\n Snowflake,\n loadResultFromJson,\n playerStateFromJson,\n snowflakeStringify,\n} from \"./types.js\";\n\n/** Abort a stalled request rather than hanging a command forever. */\nconst DEFAULT_TIMEOUT_MS = 16_000;\n/** Transient control-plane statuses (node not ready / link failed) worth retrying. */\nconst RETRYABLE_STATUS = new Set([502, 503]);\n/** Per-attempt backoff. Length also sets how many extra tries an idempotent GET gets. */\nconst BACKOFF_MS = [200, 500];\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport class REST {\n private base: string;\n private headers: Record<string, string>;\n /** Per-request timeout (ms). Exposed so a slow-network deployment can raise it. */\n timeoutMs = DEFAULT_TIMEOUT_MS;\n\n constructor(baseUrl: string, token: string) {\n this.base = baseUrl.replace(/\\/$/, \"\");\n this.headers = {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n };\n }\n\n private async fetchWithTimeout(method: string, path: string, body?: unknown): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n return await fetch(`${this.base}${path}`, {\n method,\n headers: this.headers,\n // snowflakeStringify keeps 64-bit voice ids (channel_id/user_id) exact —\n // the control plane parses them as u64 bare numbers.\n body: body === undefined ? undefined : snowflakeStringify(body),\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n\n // Retry policy: GET is idempotent, so retry it on both transient 502/503 and\n // network/timeout errors. Writes only retry on 502/503 (a network error might\n // mean the write half-applied). 401/403/429/other 4xx are terminal — no retry.\n private async request(method: string, path: string, body?: unknown): Promise<any> {\n const idempotent = method === \"GET\";\n const maxAttempts = idempotent ? BACKOFF_MS.length + 1 : 2;\n let lastErr: unknown;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const isLast = attempt === maxAttempts - 1;\n try {\n const res = await this.fetchWithTimeout(method, path, body);\n if (res.status === 401 || res.status === 403) throw new AuthError(await res.text());\n if (res.status === 429) {\n const retry = res.headers.get(\"Retry-After\");\n throw new RateLimited(await res.text(), retry ? Number(retry) : undefined);\n }\n if (RETRYABLE_STATUS.has(res.status) && !isLast) {\n await sleep(BACKOFF_MS[attempt]);\n continue;\n }\n if (res.status >= 400) throw new APIError(res.status, await res.text());\n const ct = res.headers.get(\"Content-Type\") ?? \"\";\n return ct.includes(\"application/json\") ? res.json() : res.text();\n } catch (err) {\n // Protocol errors carry a definitive answer from the server — never retry.\n if (err instanceof AuralisError) throw err;\n // Network error / timeout: retry idempotent requests only.\n lastErr = err;\n if (idempotent && !isLast) {\n await sleep(BACKOFF_MS[attempt]);\n continue;\n }\n throw err;\n }\n }\n throw lastErr; // unreachable: the loop either returns or throws\n }\n\n // --- metadata ---------------------------------------------------------\n async loadTracks(identifier: string): Promise<LoadResult> {\n const q = new URLSearchParams({ identifier });\n return loadResultFromJson(await this.request(\"GET\", `/v1/loadtracks?${q}`));\n }\n\n async prewarm(encoded: string[], guildId?: bigint): Promise<any> {\n const body: any = { tracks: encoded.map((e) => ({ encoded: e })) };\n if (guildId !== undefined) body.guild_id = guildId;\n return this.request(\"POST\", \"/v1/tracks/prewarm\", body);\n }\n\n // --- player lifecycle -------------------------------------------------\n async createPlayer(\n guildId: bigint,\n voice: Record<string, unknown>,\n opts: { track?: string; volume?: number; paused?: boolean; audioGraph?: string } = {},\n ): Promise<any> {\n const body: any = {\n voice,\n volume: opts.volume ?? 100,\n paused: opts.paused ?? false,\n };\n if (opts.track) body.track = { encoded: opts.track };\n if (opts.audioGraph !== undefined) body.filters = { audio_graph: opts.audioGraph };\n return this.request(\"POST\", `/v1/guilds/${guildId}/players`, body);\n }\n\n async destroyPlayer(guildId: bigint): Promise<any> {\n return this.request(\"DELETE\", `/v1/guilds/${guildId}/players`);\n }\n\n async getState(guildId: bigint): Promise<PlayerState> {\n return playerStateFromJson(await this.request(\"GET\", `/v1/guilds/${guildId}/player`));\n }\n\n // --- playback control -------------------------------------------------\n async play(guildId: bigint, track: string, audioGraph?: string): Promise<any> {\n const body: any = { track: { encoded: track } };\n if (audioGraph !== undefined) body.filters = { audio_graph: audioGraph };\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/play`, body);\n }\n\n async stop(guildId: bigint): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/stop`, {});\n }\n\n async setPaused(guildId: bigint, paused: boolean): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/pause`, { paused });\n }\n\n async setVolume(guildId: bigint, volume: number): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/volume`, { volume });\n }\n\n async seek(guildId: bigint, positionMs: number): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/seek`, {\n position_ms: positionMs,\n });\n }\n\n async setFilters(guildId: bigint, audioGraph: string | null): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/filters`, {\n filters: { audio_graph: audioGraph },\n });\n }\n}\n"],"mappings":"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","names":["LoopMode","LoadType","EndReason","eventName"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/errors.ts","../src/types.ts","../src/player.ts","../src/rest.ts"],"sourcesContent":["/** The top-level client: owns the REST wrapper, the WebSocket event loop, the\n * per-guild players, and event dispatch (via Node's EventEmitter). */\n\nimport { EventEmitter } from \"node:events\";\nimport WebSocket from \"ws\";\n\nimport { APIError, LoadError } from \"./errors.js\";\nimport { Player, PlayerHost } from \"./player.js\";\nimport { REST } from \"./rest.js\";\nimport { LoadResult, LoadType, Snowflake, Track, endReasonAdvances, playerStateFromJson } from \"./types.js\";\n\nexport interface ClientOptions {\n reconnect?: boolean;\n}\n\n/** Strongly-typed event map for `client.on(...)` / `client.emit(...)`. */\nexport interface ClientEvents {\n ready: [];\n /** Fired instead of `ready` when a reconnect re-establishes the session. */\n resumed: [];\n disconnect: [];\n error: [err: unknown];\n trackStart: [player: Player | bigint, data: any];\n trackEnd: [player: Player | bigint, data: any];\n trackException: [player: Player | bigint, data: any];\n trackStuck: [player: Player | bigint, data: any];\n playerUpdate: [player: Player | bigint, data: any];\n voiceClosed: [player: Player | bigint, data: any];\n // STATS is node-wide, so there may be no player/guild attached.\n stats: [player: Player | bigint | null, data: any];\n queueEnd: [player: Player];\n /** A dead source could not be recovered — the bot should tell the user. */\n trackStreamDead: [player: Player, data: { failed: Track }];\n /** A dead source was auto-recovered by playing an alternate upload. */\n trackRecovered: [player: Player, data: { failed: Track; alternate: Track }];\n /** Emitted when a new Player is created via {@link Client.getPlayer}. */\n playerCreated: [player: Player];\n /** Emitted when a player is fully torn down via {@link Player.disconnect}. */\n playerDestroyed: [guildId: bigint];\n}\n\n// WS op -> the event name we emit to listeners.\nconst EVENT_OPS: Record<string, keyof ClientEvents> = {\n TRACK_START: \"trackStart\",\n TRACK_END: \"trackEnd\",\n TRACK_EXCEPTION: \"trackException\",\n TRACK_STUCK: \"trackStuck\",\n PLAYER_UPDATE: \"playerUpdate\",\n VOICE_CLOSED: \"voiceClosed\",\n STATS: \"stats\",\n};\n\n// Declaration-merge typed on/once/off/emit overloads onto the class.\nexport interface Client {\n on<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this;\n once<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this;\n off<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this;\n emit<K extends keyof ClientEvents>(event: K, ...args: ClientEvents[K]): boolean;\n}\n\nexport class Client extends EventEmitter implements PlayerHost {\n readonly rest: REST;\n private baseUrl: string;\n private token: string;\n private reconnect: boolean;\n\n private playersMap = new Map<bigint, Player>();\n private ws: WebSocket | null = null;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private sessionId: string | null = null;\n private closed = false;\n private backoff = 1000;\n private hasConnected = false;\n private heartbeatSentAt = 0;\n\n /** Last measured heartbeat round-trip latency in ms (-1 until first ack). */\n latencyMs = -1;\n /** Most recent STATS frame payload from the node, or null. */\n lastStats: Record<string, unknown> | null = null;\n\n constructor(baseUrl: string, token: string, opts: ClientOptions = {}) {\n super();\n this.baseUrl = baseUrl.replace(/\\/$/, \"\");\n this.token = token;\n this.reconnect = opts.reconnect ?? true;\n this.rest = new REST(this.baseUrl, token);\n }\n\n // --- lifecycle --------------------------------------------------------\n async start(): Promise<void> {\n this.closed = false;\n this.openSocket();\n }\n\n /** Resolves once the client fires `ready` or `resumed`. If it is already\n * connected it resolves on the next tick. Rejects after `timeoutMs` (default\n * 30 s) so callers don't hang forever on a bad URL/token. */\n waitForReady(timeoutMs = 30_000): Promise<void> {\n return new Promise((resolve, reject) => {\n if (this.hasConnected) {\n resolve();\n return;\n }\n const onReady = () => { clearTimeout(timer); resolve(); };\n const onResumed = () => { clearTimeout(timer); resolve(); };\n const timer = setTimeout(() => {\n this.off(\"ready\", onReady);\n this.off(\"resumed\", onResumed);\n reject(new Error(\"[auralis] waitForReady timed out\"));\n }, timeoutMs);\n this.once(\"ready\", onReady);\n this.once(\"resumed\", onResumed);\n });\n }\n\n /** Gracefully disconnect every active player then close the WebSocket.\n * Prefer this over `close()` when you want clean backend teardown. */\n async destroy(): Promise<void> {\n await Promise.allSettled(this.players().map((p) => p.disconnect()));\n await this.close();\n }\n\n async close(): Promise<void> {\n this.closed = true;\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n // Drop our listeners so a lingering socket can't fire callbacks post-close.\n this.ws?.removeAllListeners();\n this.ws?.close();\n this.ws = null;\n }\n\n /** Emit \"error\" without the Node EventEmitter footgun: an unlistened \"error\"\n * throws and would crash the host process, so fall back to a console warning. */\n private safeError(err: unknown): void {\n if (this.listenerCount(\"error\") > 0) this.emit(\"error\", err);\n else console.warn(\"[auralis] unhandled client error:\", err);\n }\n\n // --- players ----------------------------------------------------------\n getPlayer(guildId: Snowflake): Player {\n const id = BigInt(guildId);\n let p = this.playersMap.get(id);\n if (!p) {\n p = new Player(this, id);\n this.playersMap.set(id, p);\n this.subscribeGuild(id);\n this.emit(\"playerCreated\", p);\n }\n return p;\n }\n\n players(): Player[] {\n return [...this.playersMap.values()];\n }\n\n dropPlayer(guildId: bigint): void {\n this.playersMap.delete(guildId);\n this.emit(\"playerDestroyed\", guildId);\n }\n\n // --- metadata convenience --------------------------------------------\n loadTracks(identifier: string): Promise<LoadResult> {\n return this.rest.loadTracks(identifier);\n }\n\n /** `ytsearch:`-prefix a bare query and return the hits. Throws `LoadError` on\n * a server-side error so callers aren't silently handed an empty array. */\n async search(query: string): Promise<Track[]> {\n const first = query.split(\" \", 1)[0];\n const ident = first.includes(\":\") ? query : `ytsearch:${query}`;\n const result = await this.loadTracks(ident);\n if (result.loadType === LoadType.Error) {\n throw new LoadError(result.error ?? `search failed for: ${query}`);\n }\n return result.tracks;\n }\n\n // --- websocket --------------------------------------------------------\n private get wsUrl(): string {\n return this.baseUrl.replace(/^http/, \"ws\") + \"/v1/ws\";\n }\n\n private openSocket(): void {\n if (this.closed) return;\n const ws = new WebSocket(this.wsUrl, {\n headers: { Authorization: `Bearer ${this.token}` },\n });\n this.ws = ws;\n\n ws.on(\"open\", () => {\n this.backoff = 1000;\n this.subscribeAll();\n });\n ws.on(\"message\", (raw) => {\n let frame: any;\n try {\n frame = JSON.parse(raw.toString());\n } catch {\n return;\n }\n // handleFrame is async; a rejected promise here would be an unhandled\n // rejection (process-fatal on newer Node). Route failures to safeError.\n this.handleFrame(frame).catch((e) => this.safeError(e));\n });\n ws.on(\"error\", (err) => this.safeError(err));\n ws.on(\"close\", () => {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n this.ws = null;\n if (this.closed || !this.reconnect) return;\n this.emit(\"disconnect\");\n // Backoff with ±25% jitter so many clients don't reconnect in lockstep.\n const jitter = this.backoff * (0.75 + Math.random() * 0.5);\n this.reconnectTimer = setTimeout(() => this.openSocket(), jitter);\n this.backoff = Math.min(this.backoff * 2, 30000);\n });\n }\n\n /** After a reconnect, pull each player's authoritative state from the node so\n * position/volume/paused are accurate again. Per-player failures are ignored. */\n private async resyncPlayers(): Promise<void> {\n await Promise.all(\n this.players().map(async (p) => {\n try {\n p.applyState(await this.rest.getState(p.guildId));\n } catch {\n // node may not have this player (it was dropped during the gap) — skip\n }\n }),\n );\n }\n\n private subscribeAll(): void {\n if (this.ws?.readyState === WebSocket.OPEN && this.playersMap.size > 0) {\n this.send({\n op: \"SUBSCRIBE\",\n d: { guilds: [...this.playersMap.keys()].map(String) },\n });\n }\n }\n\n private subscribeGuild(guildId: bigint): void {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.send({\n op: \"SUBSCRIBE\",\n d: { guilds: [String(guildId)] },\n });\n }\n }\n\n private send(payload: unknown): void {\n if (this.ws?.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(payload));\n }\n\n private async handleFrame(frame: any): Promise<void> {\n const op: string = frame.op;\n const d = frame.d ?? {};\n\n if (op === \"HELLO\") {\n const interval = (d.heartbeat_interval_ms ?? 30000) as number;\n if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);\n let seq = 0;\n this.heartbeatTimer = setInterval(() => {\n this.heartbeatSentAt = Date.now();\n this.send({ op: \"HEARTBEAT\", d: { seq: ++seq } });\n }, interval);\n return;\n }\n if (op === \"READY\") {\n const resumed = this.hasConnected;\n this.sessionId = d.session_id ?? null;\n this.hasConnected = true;\n this.subscribeAll();\n if (resumed) {\n // Reconnect: resync each player's live state from the node so callers\n // see accurate position/volume/paused after the gap (a practical resume).\n await this.resyncPlayers();\n this.emit(\"resumed\");\n } else {\n this.emit(\"ready\");\n }\n return;\n }\n if (op === \"HEARTBEAT_ACK\") {\n if (this.heartbeatSentAt) this.latencyMs = Date.now() - this.heartbeatSentAt;\n return;\n }\n if (op === \"STATS\") {\n this.lastStats = d;\n // STATS is node-wide — emit and return before the per-guild player lookup.\n const eventName = EVENT_OPS[op];\n if (eventName) this.emit(eventName, null, d);\n return;\n }\n\n // WS frames carry guild_id as a string — BigInt keeps 64-bit ids exact so\n // the lookup matches the player keyed by getPlayer(BigInt(...)).\n const guildId = d.guild_id != null ? BigInt(d.guild_id) : null;\n const player = guildId !== null ? this.playersMap.get(guildId) : undefined;\n\n if (op === \"TRACK_END\" && player) {\n // Drive auto-advance BEFORE notifying the user so handlers see new state.\n await player.onTrackEnd(endReasonAdvances(d.reason ?? \"FINISHED\"));\n } else if (op === \"PLAYER_UPDATE\" && player) {\n player.applyState(playerStateFromJson(d.state ?? {}));\n } else if (op === \"TRACK_STUCK\" && player) {\n // Skip the stalled track before notifying, so handlers see the new state.\n await player.onTrackStuck();\n } else if (op === \"VOICE_CLOSED\" && player && d.by_remote && player.rejoinOnVoiceClosed) {\n // Discord killed the session; reset so a re-join re-creates and resumes.\n player.handleVoiceClosed();\n }\n\n const eventName = EVENT_OPS[op];\n if (eventName) this.emit(eventName, player ?? guildId, d);\n }\n}\n","/** Exception hierarchy. Every network/protocol failure is an `AuralisError`. */\n\nexport class AuralisError extends Error {}\n\n/** 401/403 — the bearer token is missing, wrong, or not allowed. */\nexport class AuthError extends AuralisError {}\n\n/** 429 — too many requests. `retryAfter` is seconds if the server gave one. */\nexport class RateLimited extends AuralisError {\n constructor(message: string, public retryAfter?: number) {\n super(message);\n }\n}\n\n/** Any other non-2xx HTTP response. */\nexport class APIError extends AuralisError {\n constructor(public status: number, message: string) {\n super(`HTTP ${status}: ${message}`);\n }\n}\n\n/** Tried to play before the guild's voice connection was established. */\nexport class NotConnected extends AuralisError {}\n\n/** `loadtracks` returned `error` or nothing usable. */\nexport class LoadError extends AuralisError {}\n","/** Wire vocabulary and data models — mirror the control-plane JSON exactly. */\n\n/** A Discord snowflake. Accepted as string/number/bigint at the API boundary and\n * normalized to `bigint` internally: a 64-bit id cannot survive a JS `number`\n * (over 2^53 the low digits corrupt), and the control plane parses ids as `u64`. */\nexport type Snowflake = string | number | bigint;\n\n// A sentinel that is valid JSON string content but cannot appear in real API\n// data (encoded track tokens, guild ids, etc.). The double-boundary markers\n// make accidental collision with real string values negligible.\nconst BIG_TAG = \"||BIGINT||\";\n\n/** `JSON.stringify` that emits `bigint` values as bare (unquoted) JSON numbers.\n * The control plane's voice payload wants snowflakes as `u64` numbers, but a JS\n * `number` can't hold one exactly and `JSON.stringify` throws on `bigint`. We tag\n * bigints, then strip the quotes so the wire value is an exact integer literal. */\nexport function snowflakeStringify(obj: unknown): string {\n return JSON.stringify(obj, (_k, v) =>\n typeof v === \"bigint\" ? `${BIG_TAG}${v}${BIG_TAG}` : v,\n ).replace(/\"\\|\\|BIGINT\\|\\|(\\d+)\\|\\|BIGINT\\|\\|\"/g, \"$1\");\n}\n\n/** How the queue advances when a track finishes. */\nexport enum LoopMode {\n Off = \"off\",\n Track = \"track\", // replay the current track forever\n Queue = \"queue\", // loop the whole queue\n}\n\n/** `load_type` returned by `GET /v1/loadtracks`. */\nexport enum LoadType {\n Track = \"track\",\n Playlist = \"playlist\",\n Search = \"search\",\n Empty = \"empty\",\n Error = \"error\",\n}\n\n/** Why a track ended. Only FINISHED / LOAD_FAILED advance the queue. */\nexport enum EndReason {\n Finished = \"FINISHED\",\n LoadFailed = \"LOAD_FAILED\",\n Stopped = \"STOPPED\",\n Replaced = \"REPLACED\",\n Cleanup = \"CLEANUP\",\n}\n\nexport function endReasonAdvances(reason: string): boolean {\n return reason === EndReason.Finished || reason === EndReason.LoadFailed;\n}\n\n/** A playable track. `encoded` is the opaque token the backend needs; the rest\n * is display metadata the bot keeps for now-playing embeds. */\nexport interface Track {\n encoded: string;\n identifier: string;\n title: string;\n author: string;\n lengthMs: number;\n isStream: boolean;\n uri?: string;\n sourceName: string;\n artworkUrl?: string;\n isrc?: string;\n /** Free-form, set by the bot (e.g. who requested it). Never sent upstream. */\n extras: Record<string, unknown>;\n}\n\nexport function trackFromJson(d: any): Track {\n return {\n encoded: d.encoded,\n identifier: d.identifier ?? \"\",\n title: d.title ?? \"\",\n author: d.author ?? \"\",\n lengthMs: d.length_ms ?? 0,\n isStream: d.is_stream ?? false,\n uri: d.uri ?? undefined,\n sourceName: d.source_name ?? \"\",\n artworkUrl: d.artwork_url ?? undefined,\n isrc: d.isrc ?? undefined,\n extras: {},\n };\n}\n\nexport interface LoadResult {\n loadType: LoadType;\n tracks: Track[];\n playlistName?: string;\n error?: string;\n}\n\nexport function loadResultFromJson(d: any): LoadResult {\n return {\n loadType: (d.load_type ?? \"empty\") as LoadType,\n tracks: (d.tracks ?? []).map(trackFromJson),\n playlistName: d.playlist_name ?? undefined,\n error: d.error ?? undefined,\n };\n}\n\nexport interface PlayerState {\n connected: boolean;\n playing: boolean;\n paused: boolean;\n positionMs: number;\n volume: number;\n track?: Track;\n}\n\nexport function playerStateFromJson(d: any): PlayerState {\n return {\n connected: d.connected ?? false,\n playing: d.playing ?? false,\n paused: d.paused ?? false,\n positionMs: d.position_ms ?? 0,\n volume: d.volume ?? 100,\n track: d.track ? trackFromJson(d.track) : undefined,\n };\n}\n\n/** Raw Discord voice payloads the bot forwards from its gateway. */\nexport interface VoiceServerUpdate {\n token: string;\n endpoint: string;\n}\nexport interface VoiceStateUpdate {\n session_id: string;\n channel_id: Snowflake | null;\n user_id: Snowflake;\n}\n","/** Per-guild player: the client-side queue brain.\n *\n * The backend is deliberately queue-agnostic — it only knows play / stop / pause\n * / seek / volume. Everything queue-shaped (skip, loop, shuffle, auto-advance,\n * autoplay) lives here, driven by the `TRACK_END` event.\n *\n * Advance rules (the one path everything funnels through):\n * - `play()` issues a REST *replace* → the old track's `TRACK_END(REPLACED)` is\n * ignored by the client, so we never double-advance.\n * - a natural finish → `TRACK_END(FINISHED|LOAD_FAILED)` → `advance()`.\n * - `skip()` forces `advance()` (ignoring `LoopMode.Track`).\n */\n\nimport { APIError, LoadError, NotConnected } from \"./errors.js\";\nimport { REST } from \"./rest.js\";\nimport {\n LoadType,\n LoopMode,\n PlayerState,\n Snowflake,\n Track,\n VoiceServerUpdate,\n VoiceStateUpdate,\n} from \"./types.js\";\n\n/** What a Player needs from its owner. The `Client` implements this. */\nexport interface PlayerHost {\n rest: REST;\n emit(event: string, ...args: unknown[]): void;\n dropPlayer(guildId: bigint): void;\n}\n\nexport type AutoplayHook = (player: Player) => Promise<Track | null>;\n\n/** Named EQ presets accepted by {@link Player.setEqualizer}. */\nexport type EqPreset =\n | \"flat\"\n | \"bassboost\"\n | \"soft\"\n | \"nightcore\"\n | \"vaporwave\"\n | \"earrape\"\n | \"treble\"\n | \"karaoke\";\n\n/** Portable snapshot of a player's queue state, produced by {@link Player.export}\n * and consumed by {@link Player.restore}. Safe to JSON.stringify / store. */\nexport interface SerializedPlayer {\n version: 1;\n guildId: string;\n queue: Track[];\n history: Track[];\n current: Track | null;\n loop: LoopMode;\n volume: number;\n paused: boolean;\n}\n\nexport class Player {\n queue: Track[] = [];\n history: Track[] = [];\n current: Track | null = null;\n /** Cap on retained history so a long-lived player can't leak memory. Queue-loop\n * still works; it just restores the most recent `historyLimit` tracks. */\n historyLimit = 200;\n loop: LoopMode = LoopMode.Off;\n volume = 100;\n paused = false;\n state: PlayerState = {\n connected: false,\n playing: false,\n paused: false,\n positionMs: 0,\n volume: 100,\n };\n /** Optional async hook used when the queue and loop are exhausted. */\n autoplay: AutoplayHook | null = null;\n\n /** When a non-stream track \"finishes\" without ever streaming (dead source:\n * geo/age-restricted, throttled, removed), search for and play an alternate\n * upload instead of silently draining the queue. */\n recoverDeadStreams = true;\n /** A track whose furthest observed position never passed this many ms is\n * treated as never-streamed (used by {@link recoverDeadStreams}). */\n deadStreamThresholdMs = 1_500;\n /** Give up recovering a single dead track after this many alternate attempts. */\n deadStreamRecoveryLimit = 2;\n /** Auto-skip a track the node reports as `TRACK_STUCK` instead of stalling. */\n advanceOnStuck = true;\n /** On a remote `VOICE_CLOSED`, reset so the next voice material re-creates the\n * player and resumes the current track (the bot must re-send its op-4 join). */\n rejoinOnVoiceClosed = true;\n\n /** The guild this player drives. Always a `bigint` so 64-bit ids stay exact. */\n readonly guildId: bigint;\n\n private created = false;\n private voiceServer: { token: string; endpoint: string } | null = null;\n private voiceState: { session_id: string; channel_id: bigint; user_id: bigint } | null = null;\n /** Furthest position seen for the current track — reset on every track start. */\n private maxPositionMs = 0;\n /** Alternate-upload attempts spent on the current dead track. */\n private recoverAttempts = 0;\n /** Wall-clock time (ms) when the last PLAYER_UPDATE position was received.\n * Used by the {@link positionMs} interpolating getter. */\n private _positionTimestamp = 0;\n\n constructor(private host: PlayerHost, guildId: Snowflake) {\n this.guildId = BigInt(guildId);\n }\n\n // --- voice plumbing ---------------------------------------------------\n handleVoiceServerUpdate(data: VoiceServerUpdate): void {\n this.voiceServer = { token: data.token, endpoint: data.endpoint };\n void this.startIfIdle();\n }\n\n handleVoiceStateUpdate(data: VoiceStateUpdate): void {\n if (data.channel_id === null || data.channel_id === undefined) {\n this.voiceState = null;\n return;\n }\n this.voiceState = {\n session_id: data.session_id,\n channel_id: BigInt(data.channel_id),\n user_id: BigInt(data.user_id),\n };\n void this.startIfIdle();\n }\n\n get connected(): boolean {\n return this.voiceServer !== null && this.voiceState !== null;\n }\n\n /** Called by the client on `PLAYER_UPDATE` (and reconnect resync). Tracks the\n * furthest position reached so a \"finished but never streamed\" track can be\n * spotted, and clears the recovery counter once a track is genuinely playing. */\n applyState(state: PlayerState): void {\n this.state = state;\n this._positionTimestamp = Date.now();\n if (state.positionMs > this.maxPositionMs) this.maxPositionMs = state.positionMs;\n if (this.maxPositionMs > this.deadStreamThresholdMs) this.recoverAttempts = 0;\n }\n\n /** Discord terminated the voice session (remote `VOICE_CLOSED`). The node has\n * dropped our player, so reset local voice/created state and requeue the\n * current track; once the bot re-sends its op-4 join, fresh voice material\n * re-creates the player and resumes. Bot-initiated closes don't call this. */\n handleVoiceClosed(): void {\n if (this.current !== null) {\n this.queue.unshift(this.current);\n this.current = null;\n }\n this.created = false;\n this.voiceServer = null;\n this.voiceState = null;\n }\n\n private voicePayload(): Record<string, unknown> {\n return { ...this.voiceServer!, ...this.voiceState!, guild_id: this.guildId };\n }\n\n /** Serializes all queue advancement. Without it, concurrent triggers — `play()`,\n * a voice-update `startIfIdle()`, a `TRACK_END`, `skip()` — can each observe\n * `current === null` and both create a player, which serializes on the control\n * plane's per-guild ownership lock and blows the request timeout (the classic\n * \"operation was aborted\"). Every advance path runs through this chain. */\n private opLock: Promise<unknown> = Promise.resolve();\n private serialize<T>(fn: () => Promise<T>): Promise<T> {\n const run = this.opLock.then(fn, fn);\n this.opLock = run.then(() => {}, () => {});\n return run;\n }\n\n /** Start the queue only if the player is idle. Safe to call from multiple\n * concurrent triggers — the idle check runs inside the serialized section, so\n * exactly one caller wins and the rest see `current !== null` and no-op. */\n private startIfIdle(): Promise<void> {\n return this.serialize(async () => {\n if (this.connected && this.current === null && this.queue.length > 0) {\n await this.advanceInner(false);\n }\n });\n }\n\n // --- public commands --------------------------------------------------\n async play(query: string | Track, requester?: Snowflake): Promise<Track[]> {\n const tracks = await this.resolve(query);\n for (const t of tracks) {\n if (requester !== undefined) t.extras.requester = requester;\n this.queue.push(t);\n }\n await this.startIfIdle();\n return tracks;\n }\n\n async add(query: string | Track, requester?: Snowflake): Promise<Track[]> {\n const tracks = await this.resolve(query);\n for (const t of tracks) {\n if (requester !== undefined) t.extras.requester = requester;\n this.queue.push(t);\n }\n return tracks;\n }\n\n async skip(): Promise<void> {\n await this.advance(true);\n }\n\n async stop(): Promise<void> {\n this.queue = [];\n this.current = null;\n this.history = []; // don't let a later queue-loop replay tracks from before a stop\n if (this.created) await this.host.rest.stop(this.guildId);\n }\n\n async pause(): Promise<void> {\n await this.setPausedInternal(true);\n }\n\n async resume(): Promise<void> {\n await this.setPausedInternal(false);\n }\n\n async setPause(paused: boolean): Promise<void> {\n await this.setPausedInternal(paused);\n }\n\n async seek(positionMs: number): Promise<void> {\n if (this.created) await this.host.rest.seek(this.guildId, positionMs);\n }\n\n async setVolume(volume: number): Promise<void> {\n this.volume = Math.max(0, Math.min(1000, volume));\n if (this.created) await this.host.rest.setVolume(this.guildId, this.volume);\n }\n\n setLoop(mode: LoopMode): void {\n this.loop = mode;\n }\n\n shuffle(): void {\n for (let i = this.queue.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [this.queue[i], this.queue[j]] = [this.queue[j], this.queue[i]];\n }\n }\n\n /** Apply a raw ffmpeg `-af` filtergraph (e.g. `\"bass=g=8\"`); `null` clears. */\n async setFilters(audioGraph: string | null): Promise<void> {\n if (this.created) await this.host.rest.setFilters(this.guildId, audioGraph);\n }\n\n remove(index: number): Track | undefined {\n return this.queue.splice(index, 1)[0];\n }\n\n clear(): void {\n this.queue = [];\n }\n\n /** Reorder the queue: move the track at `from` to `to`. No-op on bad indices. */\n move(from: number, to: number): void {\n if (from < 0 || from >= this.queue.length || to < 0 || to >= this.queue.length) return;\n const [t] = this.queue.splice(from, 1);\n this.queue.splice(to, 0, t);\n }\n\n /** Resolve `query` and insert it at `index` (clamped) instead of appending. */\n async insert(index: number, query: string | Track, requester?: Snowflake): Promise<Track[]> {\n const tracks = await this.resolve(query);\n for (const t of tracks) if (requester !== undefined) t.extras.requester = requester;\n const at = Math.max(0, Math.min(index, this.queue.length));\n this.queue.splice(at, 0, ...tracks);\n await this.startIfIdle();\n return tracks;\n }\n\n /** Batch-enqueue several queries/tracks in order. Failed resolves are skipped. */\n async addMany(queries: (string | Track)[], requester?: Snowflake): Promise<Track[]> {\n const added: Track[] = [];\n for (const q of queries) {\n try {\n const tracks = await this.resolve(q);\n for (const t of tracks) {\n if (requester !== undefined) t.extras.requester = requester;\n this.queue.push(t);\n added.push(t);\n }\n } catch {\n // skip a bad query rather than aborting the whole batch\n }\n }\n if (added.length > 0) await this.startIfIdle();\n return added;\n }\n\n /** Skip forward to queue position `index`, discarding the ones in between. */\n async jump(index: number): Promise<void> {\n if (index < 0 || index >= this.queue.length) return;\n const skipped = this.queue.splice(0, index);\n for (const t of skipped) this.pushHistory(t);\n await this.advance(true);\n }\n\n /** Replay the previous track (from history), pushing the current one back on. */\n async previous(): Promise<void> {\n const prev = this.history.pop();\n if (!prev) return;\n // Push current back to the front of the queue — advance(true) will call\n // nextTrack(forced=true), which pushes `this.current` to history again.\n // To avoid a double push, clear `this.current` before advancing so\n // nextTrack sees null and skips the pushHistory call.\n if (this.current !== null) this.queue.unshift(this.current);\n this.current = null;\n this.queue.unshift(prev);\n await this.advance(true);\n }\n\n /** Convenience EQ: bass boost in dB (0 clears). Built on {@link setFilters}. */\n async setBassBoost(gainDb: number): Promise<void> {\n await this.setFilters(gainDb === 0 ? null : `bass=g=${gainDb}`);\n }\n\n /** Convenience speed/pitch (nightcore/vaporwave): `rate` 1.0 = normal.\n * Uses `atempo` for tempo and `asetrate` for pitch shift — both evaluated\n * in JS so the filter string contains literal numbers, not expressions. */\n async setSpeed(rate: number): Promise<void> {\n const r = Math.max(0.5, Math.min(2.0, rate));\n await this.setFilters(r === 1 ? null : `atempo=${r},asetrate=${48000 * r}`);\n }\n\n nowPlaying(): Track | null {\n return this.current;\n }\n\n /** Enqueue a track or query to play immediately after the current one.\n * Unlike `insert(0, ...)`, this is an explicit \"play next\" intent — it\n * always inserts at position 0 regardless of queue state and starts\n * playback if the player is idle. */\n async playNext(query: string | Track, requester?: Snowflake): Promise<Track[]> {\n return this.insert(0, query, requester);\n }\n\n /** Remove all duplicate tracks from the queue (keeps first occurrence).\n * Uniqueness is determined by `encoded` token. Returns the number removed. */\n dedupe(): number {\n const seen = new Set<string>();\n const before = this.queue.length;\n this.queue = this.queue.filter((t) => {\n if (seen.has(t.encoded)) return false;\n seen.add(t.encoded);\n return true;\n });\n return before - this.queue.length;\n }\n\n /** Find all tracks in the queue whose title or author contains `query`\n * (case-insensitive). Returns `[index, track]` pairs so callers can remove\n * or jump to a result by index. */\n find(query: string): [number, Track][] {\n const q = query.toLowerCase();\n return this.queue.reduce<[number, Track][]>((acc, t, i) => {\n if (t.title.toLowerCase().includes(q) || t.author.toLowerCase().includes(q)) {\n acc.push([i, t]);\n }\n return acc;\n }, []);\n }\n\n /** Total duration of all queued tracks in milliseconds. Streams count as 0.\n * Does not include the currently playing track. */\n get totalDurationMs(): number {\n return this.queue.reduce((sum, t) => sum + (t.isStream ? 0 : t.lengthMs), 0);\n }\n\n /** Interpolated playback position in milliseconds. Between `PLAYER_UPDATE`\n * frames this advances using wall-clock time so progress bars stay smooth.\n * Returns 0 if nothing is playing or the player is paused. */\n get positionMs(): number {\n if (!this.state.playing || this.state.paused) return this.state.positionMs;\n const elapsed = Date.now() - this._positionTimestamp;\n return Math.min(\n this.state.positionMs + elapsed,\n this.current?.lengthMs ?? this.state.positionMs + elapsed,\n );\n }\n\n /** Serialize current queue, history, loop mode and volume into a plain object\n * that can be JSON-stringified and stored. Restore with {@link Player.restore}. */\n export(): SerializedPlayer {\n return {\n version: 1,\n guildId: String(this.guildId),\n queue: this.queue.map((t) => ({ ...t })),\n history: this.history.map((t) => ({ ...t })),\n current: this.current ? { ...this.current } : null,\n loop: this.loop,\n volume: this.volume,\n paused: this.paused,\n };\n }\n\n /** Restore queue/history/loop/volume from a previously exported snapshot.\n * Does NOT auto-start playback — call `startIfIdle()` or let the next\n * voice event trigger it. */\n restore(snapshot: SerializedPlayer): void {\n this.queue = snapshot.queue.map((t) => ({ ...t }));\n this.history = snapshot.history.map((t) => ({ ...t }));\n this.current = snapshot.current ? { ...snapshot.current } : null;\n this.loop = snapshot.loop;\n this.volume = snapshot.volume;\n this.paused = snapshot.paused;\n }\n\n /** Apply a named EQ preset. `\"flat\"` clears all filters.\n *\n * | preset | effect |\n * |-------------|---------------------------------------------|\n * | flat | no filters (passthrough) |\n * | bassboost | heavy bass boost (+8 dB) |\n * | soft | gentle bass lift (+3 dB) |\n * | nightcore | speed+pitch up (1.3×) |\n * | vaporwave | speed+pitch down (0.8×) |\n * | earrape | extreme distortion / clipping (use wisely) |\n * | treble | high-frequency boost (+6 dB) |\n * | karaoke | basic vocal removal |\n */\n async setEqualizer(preset: EqPreset): Promise<void> {\n const graphs: Record<EqPreset, string | null> = {\n flat: null,\n bassboost: \"bass=g=8\",\n soft: \"bass=g=3\",\n nightcore: `atempo=1.3,asetrate=${Math.round(48000 * 1.3)}`,\n vaporwave: `atempo=0.8,asetrate=${Math.round(48000 * 0.8)}`,\n earrape: \"acrusher=level_in=8:level_out=18:bits=8:mode=log:aa=1\",\n treble: \"treble=g=6\",\n karaoke: \"pan=stereo|c0=c0-c1|c1=c1-c0\",\n };\n await this.setFilters(graphs[preset]);\n }\n\n /** Reverse the current queue order in-place. */\n reverse(): void {\n this.queue.reverse();\n }\n\n /** Remove all tracks from the queue that were requested by `requesterId`. */\n removeBy(requesterId: Snowflake): number {\n const id = String(requesterId);\n const before = this.queue.length;\n this.queue = this.queue.filter((t) => String(t.extras.requester) !== id);\n return before - this.queue.length;\n }\n\n /** Peek at the next N tracks without mutating state. Defaults to 5. */\n peek(n = 5): Track[] {\n return this.queue.slice(0, n);\n }\n\n /** Returns true if nothing is queued and nothing is playing. */\n get idle(): boolean {\n return this.current === null && this.queue.length === 0;\n }\n\n async disconnect(): Promise<void> {\n this.queue = [];\n this.current = null;\n this.history = [];\n this.paused = false;\n this.volume = 100;\n if (this.created) await this.host.rest.destroyPlayer(this.guildId);\n this.created = false;\n this.voiceServer = null;\n this.voiceState = null;\n this.host.dropPlayer(this.guildId);\n }\n\n // --- internals --------------------------------------------------------\n /** Append to history, evicting the oldest past `historyLimit` (leak guard). */\n private pushHistory(track: Track): void {\n this.history.push(track);\n if (this.history.length > this.historyLimit) {\n this.history.splice(0, this.history.length - this.historyLimit);\n }\n }\n\n private async resolve(query: string | Track): Promise<Track[]> {\n if (typeof query !== \"string\") return [query];\n const result = await this.host.rest.loadTracks(query);\n if (result.error || result.tracks.length === 0) {\n throw new LoadError(result.error ?? `no tracks for ${query}`);\n }\n return result.loadType === LoadType.Playlist ? result.tracks : [result.tracks[0]];\n }\n\n private async nextTrack(forced: boolean): Promise<Track | null> {\n if (this.loop === LoopMode.Track && this.current !== null && !forced) {\n return this.current;\n }\n if (this.queue.length > 0) {\n if (this.current !== null) this.pushHistory(this.current);\n return this.queue.shift()!;\n }\n if (this.loop === LoopMode.Queue) {\n // The cycle is everything played so far plus the track that just ended.\n // Gating this on a non-empty history meant a queue-loop over a *single*\n // track (nothing in history yet) fell through and stopped instead.\n const cycle = [...this.history, ...(this.current ? [this.current] : [])];\n if (cycle.length > 0) {\n this.queue = cycle;\n this.history = [];\n return this.queue.shift() ?? null;\n }\n }\n if (this.autoplay !== null) return this.autoplay(this);\n return null;\n }\n\n /** Public advance paths (skip / previous / jump / track-end / stuck) always\n * advance, but still run through the serialized op-lock so they can't race a\n * concurrent {@link startIfIdle} into a double-create. */\n private advance(forced = false): Promise<void> {\n return this.serialize(() => this.advanceInner(forced));\n }\n\n private async advanceInner(forced = false): Promise<void> {\n // A failed start is terminal unless we handle it here: nothing is playing on\n // the node, so no TRACK_END will ever arrive to drive a retry and the guild\n // would sit silent forever with a full queue. Report the bad track and take\n // the next one. Bounded by the queue length so an all-bad queue still ends.\n for (let attempts = this.queue.length + 1; attempts >= 0; attempts--) {\n const next = await this.nextTrack(forced);\n if (next === null) {\n this.current = null;\n if (this.created) await this.host.rest.stop(this.guildId);\n this.host.emit(\"queueEnd\", this);\n return;\n }\n try {\n await this.playTrack(next);\n return;\n } catch (e) {\n if (e instanceof NotConnected) {\n // nextTrack() already shifted it off the queue — put it back so the\n // track isn't lost, then let the re-join resume it.\n this.queue.unshift(next);\n throw e;\n }\n this.current = null; // keep it out of history, and off LoopMode.Track\n forced = true; // never let LoopMode.Track re-serve the track that failed\n this.host.emit(\"trackException\", this, { track: next, error: String(e) });\n }\n }\n }\n\n private async playTrack(track: Track): Promise<void> {\n if (!this.connected) throw new NotConnected(`guild ${this.guildId} has no voice connection`);\n this.current = track;\n this.maxPositionMs = 0; // fresh track — restart dead-stream position tracking\n if (!this.created) {\n await this.createBackendPlayer(track);\n } else {\n try {\n await this.host.rest.play(this.guildId, track.encoded);\n } catch (e) {\n // 404 GUILD_NOT_FOUND: the backend lost our player (its node restarted, or\n // the control plane dropped it on a disconnect) while our local `created`\n // is stale. Recreate it from our held voice material rather than 404-ing\n // forever — otherwise one node blip wedges every future play until restart.\n if (e instanceof APIError && e.status === 404) {\n this.created = false;\n await this.createBackendPlayer(track);\n } else {\n throw e;\n }\n }\n }\n }\n\n private async createBackendPlayer(track: Track): Promise<void> {\n await this.host.rest.createPlayer(this.guildId, this.voicePayload(), {\n track: track.encoded,\n volume: this.volume,\n paused: this.paused,\n });\n this.created = true;\n }\n\n private async setPausedInternal(paused: boolean): Promise<void> {\n this.paused = paused;\n if (this.created) await this.host.rest.setPaused(this.guildId, paused);\n }\n\n /** Called by the client's WS loop when a TRACK_END for this guild arrives.\n * `advances` is true for FINISHED / LOAD_FAILED. Before draining the queue we\n * check for a dead source and try to recover it with an alternate upload. */\n async onTrackEnd(advances: boolean): Promise<void> {\n if (!advances) return;\n // Loop-a-single-track is an explicit user choice — never second-guess it.\n if (this.recoverDeadStreams && this.loop !== LoopMode.Track && this.isDeadStream()) {\n if (await this.tryRecover()) return; // an alternate upload is now playing\n }\n await this.advance();\n }\n\n /** Called by the client on `TRACK_STUCK`: skip the stalled track. */\n async onTrackStuck(): Promise<void> {\n if (this.advanceOnStuck) await this.advance(true);\n }\n\n /** A non-stream track that \"finished\" without its position ever advancing\n * almost certainly never streamed. Genuinely short tracks (length at most a\n * few × the threshold) are excluded so we don't loop on real short songs. */\n private isDeadStream(): boolean {\n const t = this.current;\n if (t === null || t.isStream) return false;\n if (this.maxPositionMs > this.deadStreamThresholdMs) return false;\n return t.lengthMs === 0 || t.lengthMs > this.deadStreamThresholdMs * 4;\n }\n\n /** Search for an alternate upload of the current (dead) track and play it.\n * Returns true when an alternate started; false to let the caller advance. */\n private async tryRecover(): Promise<boolean> {\n const failed = this.current;\n if (failed === null) return false;\n\n this.recoverAttempts++;\n if (this.recoverAttempts > this.deadStreamRecoveryLimit) {\n this.recoverAttempts = 0;\n this.host.emit(\"trackStreamDead\", this, { failed });\n return false;\n }\n\n const query = [failed.title, failed.author].filter(Boolean).join(\" \").trim();\n if (query === \"\") return false;\n\n let tracks: Track[];\n try {\n tracks = (await this.host.rest.loadTracks(`ytsearch:${query}`)).tracks;\n } catch {\n return false; // search failed — advance normally\n }\n\n // Prefer a different upload than the one that just failed.\n const alt = tracks.find((t) => t.encoded && t.encoded !== failed.encoded);\n if (!alt) return false;\n\n alt.extras = { ...alt.extras, ...failed.extras }; // keep requester etc.\n await this.playTrack(alt);\n this.host.emit(\"trackRecovered\", this, { failed, alternate: alt });\n return true;\n }\n}\n","/** Thin wrapper over the control-plane HTTP API using the global `fetch`\n * (Node >= 18). Knows nothing about queues — just request/response. */\n\nimport { APIError, AuralisError, AuthError, RateLimited } from \"./errors.js\";\nimport {\n LoadResult,\n PlayerState,\n Snowflake,\n loadResultFromJson,\n playerStateFromJson,\n snowflakeStringify,\n} from \"./types.js\";\n\n/** Abort a stalled request rather than hanging a command forever. */\nconst DEFAULT_TIMEOUT_MS = 16_000;\n/** Transient control-plane statuses (node not ready / link failed) worth retrying. */\nconst RETRYABLE_STATUS = new Set([502, 503]);\n/** Per-attempt backoff. Length also sets how many extra tries an idempotent GET gets. */\nconst BACKOFF_MS = [200, 500];\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport class REST {\n private base: string;\n private headers: Record<string, string>;\n /** Per-request timeout (ms). Exposed so a slow-network deployment can raise it. */\n timeoutMs = DEFAULT_TIMEOUT_MS;\n\n constructor(baseUrl: string, token: string) {\n this.base = baseUrl.replace(/\\/$/, \"\");\n this.headers = {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n };\n }\n\n private async fetchWithTimeout(method: string, path: string, body?: unknown): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n return await fetch(`${this.base}${path}`, {\n method,\n headers: this.headers,\n // snowflakeStringify keeps 64-bit voice ids (channel_id/user_id) exact —\n // the control plane parses them as u64 bare numbers.\n body: body === undefined ? undefined : snowflakeStringify(body),\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n\n // Retry policy: GET is idempotent, so retry it on both transient 502/503 and\n // network/timeout errors. Writes only retry on 502/503 (a network error might\n // mean the write half-applied). 401/403/429/other 4xx are terminal — no retry.\n private async request(method: string, path: string, body?: unknown): Promise<any> {\n const idempotent = method === \"GET\";\n const maxAttempts = idempotent ? BACKOFF_MS.length + 1 : 2;\n let lastErr: unknown;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const isLast = attempt === maxAttempts - 1;\n try {\n const res = await this.fetchWithTimeout(method, path, body);\n if (res.status === 401 || res.status === 403) throw new AuthError(await res.text());\n if (res.status === 429) {\n const retry = res.headers.get(\"Retry-After\");\n throw new RateLimited(await res.text(), retry ? Number(retry) : undefined);\n }\n if (RETRYABLE_STATUS.has(res.status) && !isLast) {\n await sleep(BACKOFF_MS[attempt]);\n continue;\n }\n if (res.status >= 400) throw new APIError(res.status, await res.text());\n const ct = res.headers.get(\"Content-Type\") ?? \"\";\n return ct.includes(\"application/json\") ? res.json() : res.text();\n } catch (err) {\n // Protocol errors carry a definitive answer from the server — never retry.\n if (err instanceof AuralisError) throw err;\n // Network error / timeout: retry idempotent requests only.\n lastErr = err;\n if (idempotent && !isLast) {\n await sleep(BACKOFF_MS[attempt]);\n continue;\n }\n throw err;\n }\n }\n throw lastErr; // unreachable: the loop either returns or throws\n }\n\n // --- metadata ---------------------------------------------------------\n async loadTracks(identifier: string): Promise<LoadResult> {\n const q = new URLSearchParams({ identifier });\n return loadResultFromJson(await this.request(\"GET\", `/v1/loadtracks?${q}`));\n }\n\n async prewarm(encoded: string[], guildId?: bigint): Promise<any> {\n const body: any = { tracks: encoded.map((e) => ({ encoded: e })) };\n if (guildId !== undefined) body.guild_id = guildId;\n return this.request(\"POST\", \"/v1/tracks/prewarm\", body);\n }\n\n // --- player lifecycle -------------------------------------------------\n async createPlayer(\n guildId: bigint,\n voice: Record<string, unknown>,\n opts: { track?: string; volume?: number; paused?: boolean; audioGraph?: string } = {},\n ): Promise<any> {\n const body: any = {\n voice,\n volume: opts.volume ?? 100,\n paused: opts.paused ?? false,\n };\n if (opts.track) body.track = { encoded: opts.track };\n if (opts.audioGraph !== undefined) body.filters = { audio_graph: opts.audioGraph };\n return this.request(\"POST\", `/v1/guilds/${guildId}/players`, body);\n }\n\n async destroyPlayer(guildId: bigint): Promise<any> {\n return this.request(\"DELETE\", `/v1/guilds/${guildId}/players`);\n }\n\n async getState(guildId: bigint): Promise<PlayerState> {\n return playerStateFromJson(await this.request(\"GET\", `/v1/guilds/${guildId}/player`));\n }\n\n // --- playback control -------------------------------------------------\n async play(guildId: bigint, track: string, audioGraph?: string): Promise<any> {\n const body: any = { track: { encoded: track } };\n if (audioGraph !== undefined) body.filters = { audio_graph: audioGraph };\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/play`, body);\n }\n\n async stop(guildId: bigint): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/stop`, {});\n }\n\n async setPaused(guildId: bigint, paused: boolean): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/pause`, { paused });\n }\n\n async setVolume(guildId: bigint, volume: number): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/volume`, { volume });\n }\n\n async seek(guildId: bigint, positionMs: number): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/seek`, {\n position_ms: positionMs,\n });\n }\n\n async setFilters(guildId: bigint, audioGraph: string | null): Promise<any> {\n return this.request(\"POST\", `/v1/guilds/${guildId}/player/filters`, {\n filters: { audio_graph: audioGraph },\n });\n }\n}\n"],"mappings":"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","names":["LoopMode","LoadType","EndReason","eventName"]}
|