auralis-sdk 0.4.2 → 0.5.0
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/README.md +12 -2
- package/dist/index.cjs +185 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -7
- package/dist/index.d.ts +87 -7
- package/dist/index.js +185 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -118,6 +118,20 @@ interface PlayerHost {
|
|
|
118
118
|
dropPlayer(guildId: bigint): void;
|
|
119
119
|
}
|
|
120
120
|
type AutoplayHook = (player: Player) => Promise<Track | null>;
|
|
121
|
+
/** Named EQ presets accepted by {@link Player.setEqualizer}. */
|
|
122
|
+
type EqPreset = "flat" | "bassboost" | "soft" | "nightcore" | "vaporwave" | "earrape" | "treble" | "karaoke";
|
|
123
|
+
/** Portable snapshot of a player's queue state, produced by {@link Player.export}
|
|
124
|
+
* and consumed by {@link Player.restore}. Safe to JSON.stringify / store. */
|
|
125
|
+
interface SerializedPlayer {
|
|
126
|
+
version: 1;
|
|
127
|
+
guildId: string;
|
|
128
|
+
queue: Track[];
|
|
129
|
+
history: Track[];
|
|
130
|
+
current: Track | null;
|
|
131
|
+
loop: LoopMode;
|
|
132
|
+
volume: number;
|
|
133
|
+
paused: boolean;
|
|
134
|
+
}
|
|
121
135
|
declare class Player {
|
|
122
136
|
private host;
|
|
123
137
|
queue: Track[];
|
|
@@ -155,6 +169,9 @@ declare class Player {
|
|
|
155
169
|
private maxPositionMs;
|
|
156
170
|
/** Alternate-upload attempts spent on the current dead track. */
|
|
157
171
|
private recoverAttempts;
|
|
172
|
+
/** Wall-clock time (ms) when the last PLAYER_UPDATE position was received.
|
|
173
|
+
* Used by the {@link positionMs} interpolating getter. */
|
|
174
|
+
private _positionTimestamp;
|
|
158
175
|
constructor(host: PlayerHost, guildId: Snowflake);
|
|
159
176
|
handleVoiceServerUpdate(data: VoiceServerUpdate): void;
|
|
160
177
|
handleVoiceStateUpdate(data: VoiceStateUpdate): void;
|
|
@@ -180,8 +197,8 @@ declare class Player {
|
|
|
180
197
|
* concurrent triggers — the idle check runs inside the serialized section, so
|
|
181
198
|
* exactly one caller wins and the rest see `current !== null` and no-op. */
|
|
182
199
|
private startIfIdle;
|
|
183
|
-
play(query: string | Track, requester?:
|
|
184
|
-
add(query: string | Track, requester?:
|
|
200
|
+
play(query: string | Track, requester?: Snowflake): Promise<Track[]>;
|
|
201
|
+
add(query: string | Track, requester?: Snowflake): Promise<Track[]>;
|
|
185
202
|
skip(): Promise<void>;
|
|
186
203
|
stop(): Promise<void>;
|
|
187
204
|
pause(): Promise<void>;
|
|
@@ -198,18 +215,68 @@ declare class Player {
|
|
|
198
215
|
/** Reorder the queue: move the track at `from` to `to`. No-op on bad indices. */
|
|
199
216
|
move(from: number, to: number): void;
|
|
200
217
|
/** Resolve `query` and insert it at `index` (clamped) instead of appending. */
|
|
201
|
-
insert(index: number, query: string | Track, requester?:
|
|
218
|
+
insert(index: number, query: string | Track, requester?: Snowflake): Promise<Track[]>;
|
|
202
219
|
/** Batch-enqueue several queries/tracks in order. Failed resolves are skipped. */
|
|
203
|
-
addMany(queries: (string | Track)[], requester?:
|
|
220
|
+
addMany(queries: (string | Track)[], requester?: Snowflake): Promise<Track[]>;
|
|
204
221
|
/** Skip forward to queue position `index`, discarding the ones in between. */
|
|
205
222
|
jump(index: number): Promise<void>;
|
|
206
223
|
/** Replay the previous track (from history), pushing the current one back on. */
|
|
207
224
|
previous(): Promise<void>;
|
|
208
225
|
/** Convenience EQ: bass boost in dB (0 clears). Built on {@link setFilters}. */
|
|
209
226
|
setBassBoost(gainDb: number): Promise<void>;
|
|
210
|
-
/** Convenience speed/pitch (nightcore/vaporwave): `rate` 1.0 = normal.
|
|
227
|
+
/** Convenience speed/pitch (nightcore/vaporwave): `rate` 1.0 = normal.
|
|
228
|
+
* Uses `atempo` for tempo and `asetrate` for pitch shift — both evaluated
|
|
229
|
+
* in JS so the filter string contains literal numbers, not expressions. */
|
|
211
230
|
setSpeed(rate: number): Promise<void>;
|
|
212
231
|
nowPlaying(): Track | null;
|
|
232
|
+
/** Enqueue a track or query to play immediately after the current one.
|
|
233
|
+
* Unlike `insert(0, ...)`, this is an explicit "play next" intent — it
|
|
234
|
+
* always inserts at position 0 regardless of queue state and starts
|
|
235
|
+
* playback if the player is idle. */
|
|
236
|
+
playNext(query: string | Track, requester?: Snowflake): Promise<Track[]>;
|
|
237
|
+
/** Remove all duplicate tracks from the queue (keeps first occurrence).
|
|
238
|
+
* Uniqueness is determined by `encoded` token. Returns the number removed. */
|
|
239
|
+
dedupe(): number;
|
|
240
|
+
/** Find all tracks in the queue whose title or author contains `query`
|
|
241
|
+
* (case-insensitive). Returns `[index, track]` pairs so callers can remove
|
|
242
|
+
* or jump to a result by index. */
|
|
243
|
+
find(query: string): [number, Track][];
|
|
244
|
+
/** Total duration of all queued tracks in milliseconds. Streams count as 0.
|
|
245
|
+
* Does not include the currently playing track. */
|
|
246
|
+
get totalDurationMs(): number;
|
|
247
|
+
/** Interpolated playback position in milliseconds. Between `PLAYER_UPDATE`
|
|
248
|
+
* frames this advances using wall-clock time so progress bars stay smooth.
|
|
249
|
+
* Returns 0 if nothing is playing or the player is paused. */
|
|
250
|
+
get positionMs(): number;
|
|
251
|
+
/** Serialize current queue, history, loop mode and volume into a plain object
|
|
252
|
+
* that can be JSON-stringified and stored. Restore with {@link Player.restore}. */
|
|
253
|
+
export(): SerializedPlayer;
|
|
254
|
+
/** Restore queue/history/loop/volume from a previously exported snapshot.
|
|
255
|
+
* Does NOT auto-start playback — call `startIfIdle()` or let the next
|
|
256
|
+
* voice event trigger it. */
|
|
257
|
+
restore(snapshot: SerializedPlayer): void;
|
|
258
|
+
/** Apply a named EQ preset. `"flat"` clears all filters.
|
|
259
|
+
*
|
|
260
|
+
* | preset | effect |
|
|
261
|
+
* |-------------|---------------------------------------------|
|
|
262
|
+
* | flat | no filters (passthrough) |
|
|
263
|
+
* | bassboost | heavy bass boost (+8 dB) |
|
|
264
|
+
* | soft | gentle bass lift (+3 dB) |
|
|
265
|
+
* | nightcore | speed+pitch up (1.3×) |
|
|
266
|
+
* | vaporwave | speed+pitch down (0.8×) |
|
|
267
|
+
* | earrape | extreme distortion / clipping (use wisely) |
|
|
268
|
+
* | treble | high-frequency boost (+6 dB) |
|
|
269
|
+
* | karaoke | basic vocal removal |
|
|
270
|
+
*/
|
|
271
|
+
setEqualizer(preset: EqPreset): Promise<void>;
|
|
272
|
+
/** Reverse the current queue order in-place. */
|
|
273
|
+
reverse(): void;
|
|
274
|
+
/** Remove all tracks from the queue that were requested by `requesterId`. */
|
|
275
|
+
removeBy(requesterId: Snowflake): number;
|
|
276
|
+
/** Peek at the next N tracks without mutating state. Defaults to 5. */
|
|
277
|
+
peek(n?: number): Track[];
|
|
278
|
+
/** Returns true if nothing is queued and nothing is playing. */
|
|
279
|
+
get idle(): boolean;
|
|
213
280
|
disconnect(): Promise<void>;
|
|
214
281
|
/** Append to history, evicting the oldest past `historyLimit` (leak guard). */
|
|
215
282
|
private pushHistory;
|
|
@@ -268,6 +335,10 @@ interface ClientEvents {
|
|
|
268
335
|
failed: Track;
|
|
269
336
|
alternate: Track;
|
|
270
337
|
}];
|
|
338
|
+
/** Emitted when a new Player is created via {@link Client.getPlayer}. */
|
|
339
|
+
playerCreated: [player: Player];
|
|
340
|
+
/** Emitted when a player is fully torn down via {@link Player.disconnect}. */
|
|
341
|
+
playerDestroyed: [guildId: bigint];
|
|
271
342
|
}
|
|
272
343
|
interface Client {
|
|
273
344
|
on<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this;
|
|
@@ -295,6 +366,13 @@ declare class Client extends EventEmitter implements PlayerHost {
|
|
|
295
366
|
lastStats: Record<string, unknown> | null;
|
|
296
367
|
constructor(baseUrl: string, token: string, opts?: ClientOptions);
|
|
297
368
|
start(): Promise<void>;
|
|
369
|
+
/** Resolves once the client fires `ready` or `resumed`. If it is already
|
|
370
|
+
* connected it resolves on the next tick. Rejects after `timeoutMs` (default
|
|
371
|
+
* 30 s) so callers don't hang forever on a bad URL/token. */
|
|
372
|
+
waitForReady(timeoutMs?: number): Promise<void>;
|
|
373
|
+
/** Gracefully disconnect every active player then close the WebSocket.
|
|
374
|
+
* Prefer this over `close()` when you want clean backend teardown. */
|
|
375
|
+
destroy(): Promise<void>;
|
|
298
376
|
close(): Promise<void>;
|
|
299
377
|
/** Emit "error" without the Node EventEmitter footgun: an unlistened "error"
|
|
300
378
|
* throws and would crash the host process, so fall back to a console warning. */
|
|
@@ -303,7 +381,8 @@ declare class Client extends EventEmitter implements PlayerHost {
|
|
|
303
381
|
players(): Player[];
|
|
304
382
|
dropPlayer(guildId: bigint): void;
|
|
305
383
|
loadTracks(identifier: string): Promise<LoadResult>;
|
|
306
|
-
/** `ytsearch:`-prefix a bare query and return the hits
|
|
384
|
+
/** `ytsearch:`-prefix a bare query and return the hits. Throws `LoadError` on
|
|
385
|
+
* a server-side error so callers aren't silently handed an empty array. */
|
|
307
386
|
search(query: string): Promise<Track[]>;
|
|
308
387
|
private get wsUrl();
|
|
309
388
|
private openSocket;
|
|
@@ -311,6 +390,7 @@ declare class Client extends EventEmitter implements PlayerHost {
|
|
|
311
390
|
* position/volume/paused are accurate again. Per-player failures are ignored. */
|
|
312
391
|
private resyncPlayers;
|
|
313
392
|
private subscribeAll;
|
|
393
|
+
private subscribeGuild;
|
|
314
394
|
private send;
|
|
315
395
|
private handleFrame;
|
|
316
396
|
}
|
|
@@ -338,4 +418,4 @@ declare class NotConnected extends AuralisError {
|
|
|
338
418
|
declare class LoadError extends AuralisError {
|
|
339
419
|
}
|
|
340
420
|
|
|
341
|
-
export { APIError, AuralisError, AuthError, type AutoplayHook, Client, type ClientEvents, type ClientOptions, EndReason, LoadError, type LoadResult, LoadType, LoopMode, NotConnected, Player, type PlayerHost, type PlayerState, REST, RateLimited, type Snowflake, type Track, type VoiceServerUpdate, type VoiceStateUpdate, endReasonAdvances };
|
|
421
|
+
export { APIError, AuralisError, AuthError, type AutoplayHook, Client, type ClientEvents, type ClientOptions, EndReason, type EqPreset, LoadError, type LoadResult, LoadType, LoopMode, NotConnected, Player, type PlayerHost, type PlayerState, REST, RateLimited, type SerializedPlayer, type Snowflake, type Track, type VoiceServerUpdate, type VoiceStateUpdate, endReasonAdvances };
|
package/dist/index.d.ts
CHANGED
|
@@ -118,6 +118,20 @@ interface PlayerHost {
|
|
|
118
118
|
dropPlayer(guildId: bigint): void;
|
|
119
119
|
}
|
|
120
120
|
type AutoplayHook = (player: Player) => Promise<Track | null>;
|
|
121
|
+
/** Named EQ presets accepted by {@link Player.setEqualizer}. */
|
|
122
|
+
type EqPreset = "flat" | "bassboost" | "soft" | "nightcore" | "vaporwave" | "earrape" | "treble" | "karaoke";
|
|
123
|
+
/** Portable snapshot of a player's queue state, produced by {@link Player.export}
|
|
124
|
+
* and consumed by {@link Player.restore}. Safe to JSON.stringify / store. */
|
|
125
|
+
interface SerializedPlayer {
|
|
126
|
+
version: 1;
|
|
127
|
+
guildId: string;
|
|
128
|
+
queue: Track[];
|
|
129
|
+
history: Track[];
|
|
130
|
+
current: Track | null;
|
|
131
|
+
loop: LoopMode;
|
|
132
|
+
volume: number;
|
|
133
|
+
paused: boolean;
|
|
134
|
+
}
|
|
121
135
|
declare class Player {
|
|
122
136
|
private host;
|
|
123
137
|
queue: Track[];
|
|
@@ -155,6 +169,9 @@ declare class Player {
|
|
|
155
169
|
private maxPositionMs;
|
|
156
170
|
/** Alternate-upload attempts spent on the current dead track. */
|
|
157
171
|
private recoverAttempts;
|
|
172
|
+
/** Wall-clock time (ms) when the last PLAYER_UPDATE position was received.
|
|
173
|
+
* Used by the {@link positionMs} interpolating getter. */
|
|
174
|
+
private _positionTimestamp;
|
|
158
175
|
constructor(host: PlayerHost, guildId: Snowflake);
|
|
159
176
|
handleVoiceServerUpdate(data: VoiceServerUpdate): void;
|
|
160
177
|
handleVoiceStateUpdate(data: VoiceStateUpdate): void;
|
|
@@ -180,8 +197,8 @@ declare class Player {
|
|
|
180
197
|
* concurrent triggers — the idle check runs inside the serialized section, so
|
|
181
198
|
* exactly one caller wins and the rest see `current !== null` and no-op. */
|
|
182
199
|
private startIfIdle;
|
|
183
|
-
play(query: string | Track, requester?:
|
|
184
|
-
add(query: string | Track, requester?:
|
|
200
|
+
play(query: string | Track, requester?: Snowflake): Promise<Track[]>;
|
|
201
|
+
add(query: string | Track, requester?: Snowflake): Promise<Track[]>;
|
|
185
202
|
skip(): Promise<void>;
|
|
186
203
|
stop(): Promise<void>;
|
|
187
204
|
pause(): Promise<void>;
|
|
@@ -198,18 +215,68 @@ declare class Player {
|
|
|
198
215
|
/** Reorder the queue: move the track at `from` to `to`. No-op on bad indices. */
|
|
199
216
|
move(from: number, to: number): void;
|
|
200
217
|
/** Resolve `query` and insert it at `index` (clamped) instead of appending. */
|
|
201
|
-
insert(index: number, query: string | Track, requester?:
|
|
218
|
+
insert(index: number, query: string | Track, requester?: Snowflake): Promise<Track[]>;
|
|
202
219
|
/** Batch-enqueue several queries/tracks in order. Failed resolves are skipped. */
|
|
203
|
-
addMany(queries: (string | Track)[], requester?:
|
|
220
|
+
addMany(queries: (string | Track)[], requester?: Snowflake): Promise<Track[]>;
|
|
204
221
|
/** Skip forward to queue position `index`, discarding the ones in between. */
|
|
205
222
|
jump(index: number): Promise<void>;
|
|
206
223
|
/** Replay the previous track (from history), pushing the current one back on. */
|
|
207
224
|
previous(): Promise<void>;
|
|
208
225
|
/** Convenience EQ: bass boost in dB (0 clears). Built on {@link setFilters}. */
|
|
209
226
|
setBassBoost(gainDb: number): Promise<void>;
|
|
210
|
-
/** Convenience speed/pitch (nightcore/vaporwave): `rate` 1.0 = normal.
|
|
227
|
+
/** Convenience speed/pitch (nightcore/vaporwave): `rate` 1.0 = normal.
|
|
228
|
+
* Uses `atempo` for tempo and `asetrate` for pitch shift — both evaluated
|
|
229
|
+
* in JS so the filter string contains literal numbers, not expressions. */
|
|
211
230
|
setSpeed(rate: number): Promise<void>;
|
|
212
231
|
nowPlaying(): Track | null;
|
|
232
|
+
/** Enqueue a track or query to play immediately after the current one.
|
|
233
|
+
* Unlike `insert(0, ...)`, this is an explicit "play next" intent — it
|
|
234
|
+
* always inserts at position 0 regardless of queue state and starts
|
|
235
|
+
* playback if the player is idle. */
|
|
236
|
+
playNext(query: string | Track, requester?: Snowflake): Promise<Track[]>;
|
|
237
|
+
/** Remove all duplicate tracks from the queue (keeps first occurrence).
|
|
238
|
+
* Uniqueness is determined by `encoded` token. Returns the number removed. */
|
|
239
|
+
dedupe(): number;
|
|
240
|
+
/** Find all tracks in the queue whose title or author contains `query`
|
|
241
|
+
* (case-insensitive). Returns `[index, track]` pairs so callers can remove
|
|
242
|
+
* or jump to a result by index. */
|
|
243
|
+
find(query: string): [number, Track][];
|
|
244
|
+
/** Total duration of all queued tracks in milliseconds. Streams count as 0.
|
|
245
|
+
* Does not include the currently playing track. */
|
|
246
|
+
get totalDurationMs(): number;
|
|
247
|
+
/** Interpolated playback position in milliseconds. Between `PLAYER_UPDATE`
|
|
248
|
+
* frames this advances using wall-clock time so progress bars stay smooth.
|
|
249
|
+
* Returns 0 if nothing is playing or the player is paused. */
|
|
250
|
+
get positionMs(): number;
|
|
251
|
+
/** Serialize current queue, history, loop mode and volume into a plain object
|
|
252
|
+
* that can be JSON-stringified and stored. Restore with {@link Player.restore}. */
|
|
253
|
+
export(): SerializedPlayer;
|
|
254
|
+
/** Restore queue/history/loop/volume from a previously exported snapshot.
|
|
255
|
+
* Does NOT auto-start playback — call `startIfIdle()` or let the next
|
|
256
|
+
* voice event trigger it. */
|
|
257
|
+
restore(snapshot: SerializedPlayer): void;
|
|
258
|
+
/** Apply a named EQ preset. `"flat"` clears all filters.
|
|
259
|
+
*
|
|
260
|
+
* | preset | effect |
|
|
261
|
+
* |-------------|---------------------------------------------|
|
|
262
|
+
* | flat | no filters (passthrough) |
|
|
263
|
+
* | bassboost | heavy bass boost (+8 dB) |
|
|
264
|
+
* | soft | gentle bass lift (+3 dB) |
|
|
265
|
+
* | nightcore | speed+pitch up (1.3×) |
|
|
266
|
+
* | vaporwave | speed+pitch down (0.8×) |
|
|
267
|
+
* | earrape | extreme distortion / clipping (use wisely) |
|
|
268
|
+
* | treble | high-frequency boost (+6 dB) |
|
|
269
|
+
* | karaoke | basic vocal removal |
|
|
270
|
+
*/
|
|
271
|
+
setEqualizer(preset: EqPreset): Promise<void>;
|
|
272
|
+
/** Reverse the current queue order in-place. */
|
|
273
|
+
reverse(): void;
|
|
274
|
+
/** Remove all tracks from the queue that were requested by `requesterId`. */
|
|
275
|
+
removeBy(requesterId: Snowflake): number;
|
|
276
|
+
/** Peek at the next N tracks without mutating state. Defaults to 5. */
|
|
277
|
+
peek(n?: number): Track[];
|
|
278
|
+
/** Returns true if nothing is queued and nothing is playing. */
|
|
279
|
+
get idle(): boolean;
|
|
213
280
|
disconnect(): Promise<void>;
|
|
214
281
|
/** Append to history, evicting the oldest past `historyLimit` (leak guard). */
|
|
215
282
|
private pushHistory;
|
|
@@ -268,6 +335,10 @@ interface ClientEvents {
|
|
|
268
335
|
failed: Track;
|
|
269
336
|
alternate: Track;
|
|
270
337
|
}];
|
|
338
|
+
/** Emitted when a new Player is created via {@link Client.getPlayer}. */
|
|
339
|
+
playerCreated: [player: Player];
|
|
340
|
+
/** Emitted when a player is fully torn down via {@link Player.disconnect}. */
|
|
341
|
+
playerDestroyed: [guildId: bigint];
|
|
271
342
|
}
|
|
272
343
|
interface Client {
|
|
273
344
|
on<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => void): this;
|
|
@@ -295,6 +366,13 @@ declare class Client extends EventEmitter implements PlayerHost {
|
|
|
295
366
|
lastStats: Record<string, unknown> | null;
|
|
296
367
|
constructor(baseUrl: string, token: string, opts?: ClientOptions);
|
|
297
368
|
start(): Promise<void>;
|
|
369
|
+
/** Resolves once the client fires `ready` or `resumed`. If it is already
|
|
370
|
+
* connected it resolves on the next tick. Rejects after `timeoutMs` (default
|
|
371
|
+
* 30 s) so callers don't hang forever on a bad URL/token. */
|
|
372
|
+
waitForReady(timeoutMs?: number): Promise<void>;
|
|
373
|
+
/** Gracefully disconnect every active player then close the WebSocket.
|
|
374
|
+
* Prefer this over `close()` when you want clean backend teardown. */
|
|
375
|
+
destroy(): Promise<void>;
|
|
298
376
|
close(): Promise<void>;
|
|
299
377
|
/** Emit "error" without the Node EventEmitter footgun: an unlistened "error"
|
|
300
378
|
* throws and would crash the host process, so fall back to a console warning. */
|
|
@@ -303,7 +381,8 @@ declare class Client extends EventEmitter implements PlayerHost {
|
|
|
303
381
|
players(): Player[];
|
|
304
382
|
dropPlayer(guildId: bigint): void;
|
|
305
383
|
loadTracks(identifier: string): Promise<LoadResult>;
|
|
306
|
-
/** `ytsearch:`-prefix a bare query and return the hits
|
|
384
|
+
/** `ytsearch:`-prefix a bare query and return the hits. Throws `LoadError` on
|
|
385
|
+
* a server-side error so callers aren't silently handed an empty array. */
|
|
307
386
|
search(query: string): Promise<Track[]>;
|
|
308
387
|
private get wsUrl();
|
|
309
388
|
private openSocket;
|
|
@@ -311,6 +390,7 @@ declare class Client extends EventEmitter implements PlayerHost {
|
|
|
311
390
|
* position/volume/paused are accurate again. Per-player failures are ignored. */
|
|
312
391
|
private resyncPlayers;
|
|
313
392
|
private subscribeAll;
|
|
393
|
+
private subscribeGuild;
|
|
314
394
|
private send;
|
|
315
395
|
private handleFrame;
|
|
316
396
|
}
|
|
@@ -338,4 +418,4 @@ declare class NotConnected extends AuralisError {
|
|
|
338
418
|
declare class LoadError extends AuralisError {
|
|
339
419
|
}
|
|
340
420
|
|
|
341
|
-
export { APIError, AuralisError, AuthError, type AutoplayHook, Client, type ClientEvents, type ClientOptions, EndReason, LoadError, type LoadResult, LoadType, LoopMode, NotConnected, Player, type PlayerHost, type PlayerState, REST, RateLimited, type Snowflake, type Track, type VoiceServerUpdate, type VoiceStateUpdate, endReasonAdvances };
|
|
421
|
+
export { APIError, AuralisError, AuthError, type AutoplayHook, Client, type ClientEvents, type ClientOptions, EndReason, type EqPreset, LoadError, type LoadResult, LoadType, LoopMode, NotConnected, Player, type PlayerHost, type PlayerState, REST, RateLimited, type SerializedPlayer, type Snowflake, type Track, type VoiceServerUpdate, type VoiceStateUpdate, endReasonAdvances };
|
package/dist/index.js
CHANGED
|
@@ -27,12 +27,12 @@ var LoadError = class extends AuralisError {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
// src/types.ts
|
|
30
|
-
var BIG_TAG = "
|
|
30
|
+
var BIG_TAG = "||BIGINT||";
|
|
31
31
|
function snowflakeStringify(obj) {
|
|
32
32
|
return JSON.stringify(
|
|
33
33
|
obj,
|
|
34
34
|
(_k, v) => typeof v === "bigint" ? `${BIG_TAG}${v}${BIG_TAG}` : v
|
|
35
|
-
).replace(
|
|
35
|
+
).replace(/"\|\|BIGINT\|\|(\d+)\|\|BIGINT\|\|"/g, "$1");
|
|
36
36
|
}
|
|
37
37
|
var LoopMode = /* @__PURE__ */ ((LoopMode2) => {
|
|
38
38
|
LoopMode2["Off"] = "off";
|
|
@@ -141,6 +141,9 @@ var Player = class {
|
|
|
141
141
|
maxPositionMs = 0;
|
|
142
142
|
/** Alternate-upload attempts spent on the current dead track. */
|
|
143
143
|
recoverAttempts = 0;
|
|
144
|
+
/** Wall-clock time (ms) when the last PLAYER_UPDATE position was received.
|
|
145
|
+
* Used by the {@link positionMs} interpolating getter. */
|
|
146
|
+
_positionTimestamp = 0;
|
|
144
147
|
// --- voice plumbing ---------------------------------------------------
|
|
145
148
|
handleVoiceServerUpdate(data) {
|
|
146
149
|
this.voiceServer = { token: data.token, endpoint: data.endpoint };
|
|
@@ -166,6 +169,7 @@ var Player = class {
|
|
|
166
169
|
* spotted, and clears the recovery counter once a track is genuinely playing. */
|
|
167
170
|
applyState(state) {
|
|
168
171
|
this.state = state;
|
|
172
|
+
this._positionTimestamp = Date.now();
|
|
169
173
|
if (state.positionMs > this.maxPositionMs) this.maxPositionMs = state.positionMs;
|
|
170
174
|
if (this.maxPositionMs > this.deadStreamThresholdMs) this.recoverAttempts = 0;
|
|
171
175
|
}
|
|
@@ -314,6 +318,7 @@ var Player = class {
|
|
|
314
318
|
const prev = this.history.pop();
|
|
315
319
|
if (!prev) return;
|
|
316
320
|
if (this.current !== null) this.queue.unshift(this.current);
|
|
321
|
+
this.current = null;
|
|
317
322
|
this.queue.unshift(prev);
|
|
318
323
|
await this.advance(true);
|
|
319
324
|
}
|
|
@@ -321,17 +326,139 @@ var Player = class {
|
|
|
321
326
|
async setBassBoost(gainDb) {
|
|
322
327
|
await this.setFilters(gainDb === 0 ? null : `bass=g=${gainDb}`);
|
|
323
328
|
}
|
|
324
|
-
/** Convenience speed/pitch (nightcore/vaporwave): `rate` 1.0 = normal.
|
|
329
|
+
/** Convenience speed/pitch (nightcore/vaporwave): `rate` 1.0 = normal.
|
|
330
|
+
* Uses `atempo` for tempo and `asetrate` for pitch shift — both evaluated
|
|
331
|
+
* in JS so the filter string contains literal numbers, not expressions. */
|
|
325
332
|
async setSpeed(rate) {
|
|
326
333
|
const r = Math.max(0.5, Math.min(2, rate));
|
|
327
|
-
await this.setFilters(r === 1 ? null : `atempo=${r},asetrate
|
|
334
|
+
await this.setFilters(r === 1 ? null : `atempo=${r},asetrate=${48e3 * r}`);
|
|
328
335
|
}
|
|
329
336
|
nowPlaying() {
|
|
330
337
|
return this.current;
|
|
331
338
|
}
|
|
339
|
+
/** Enqueue a track or query to play immediately after the current one.
|
|
340
|
+
* Unlike `insert(0, ...)`, this is an explicit "play next" intent — it
|
|
341
|
+
* always inserts at position 0 regardless of queue state and starts
|
|
342
|
+
* playback if the player is idle. */
|
|
343
|
+
async playNext(query, requester) {
|
|
344
|
+
return this.insert(0, query, requester);
|
|
345
|
+
}
|
|
346
|
+
/** Remove all duplicate tracks from the queue (keeps first occurrence).
|
|
347
|
+
* Uniqueness is determined by `encoded` token. Returns the number removed. */
|
|
348
|
+
dedupe() {
|
|
349
|
+
const seen = /* @__PURE__ */ new Set();
|
|
350
|
+
const before = this.queue.length;
|
|
351
|
+
this.queue = this.queue.filter((t) => {
|
|
352
|
+
if (seen.has(t.encoded)) return false;
|
|
353
|
+
seen.add(t.encoded);
|
|
354
|
+
return true;
|
|
355
|
+
});
|
|
356
|
+
return before - this.queue.length;
|
|
357
|
+
}
|
|
358
|
+
/** Find all tracks in the queue whose title or author contains `query`
|
|
359
|
+
* (case-insensitive). Returns `[index, track]` pairs so callers can remove
|
|
360
|
+
* or jump to a result by index. */
|
|
361
|
+
find(query) {
|
|
362
|
+
const q = query.toLowerCase();
|
|
363
|
+
return this.queue.reduce((acc, t, i) => {
|
|
364
|
+
if (t.title.toLowerCase().includes(q) || t.author.toLowerCase().includes(q)) {
|
|
365
|
+
acc.push([i, t]);
|
|
366
|
+
}
|
|
367
|
+
return acc;
|
|
368
|
+
}, []);
|
|
369
|
+
}
|
|
370
|
+
/** Total duration of all queued tracks in milliseconds. Streams count as 0.
|
|
371
|
+
* Does not include the currently playing track. */
|
|
372
|
+
get totalDurationMs() {
|
|
373
|
+
return this.queue.reduce((sum, t) => sum + (t.isStream ? 0 : t.lengthMs), 0);
|
|
374
|
+
}
|
|
375
|
+
/** Interpolated playback position in milliseconds. Between `PLAYER_UPDATE`
|
|
376
|
+
* frames this advances using wall-clock time so progress bars stay smooth.
|
|
377
|
+
* Returns 0 if nothing is playing or the player is paused. */
|
|
378
|
+
get positionMs() {
|
|
379
|
+
if (!this.state.playing || this.state.paused) return this.state.positionMs;
|
|
380
|
+
const elapsed = Date.now() - this._positionTimestamp;
|
|
381
|
+
return Math.min(
|
|
382
|
+
this.state.positionMs + elapsed,
|
|
383
|
+
this.current?.lengthMs ?? this.state.positionMs + elapsed
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
/** Serialize current queue, history, loop mode and volume into a plain object
|
|
387
|
+
* that can be JSON-stringified and stored. Restore with {@link Player.restore}. */
|
|
388
|
+
export() {
|
|
389
|
+
return {
|
|
390
|
+
version: 1,
|
|
391
|
+
guildId: String(this.guildId),
|
|
392
|
+
queue: this.queue.map((t) => ({ ...t })),
|
|
393
|
+
history: this.history.map((t) => ({ ...t })),
|
|
394
|
+
current: this.current ? { ...this.current } : null,
|
|
395
|
+
loop: this.loop,
|
|
396
|
+
volume: this.volume,
|
|
397
|
+
paused: this.paused
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
/** Restore queue/history/loop/volume from a previously exported snapshot.
|
|
401
|
+
* Does NOT auto-start playback — call `startIfIdle()` or let the next
|
|
402
|
+
* voice event trigger it. */
|
|
403
|
+
restore(snapshot) {
|
|
404
|
+
this.queue = snapshot.queue.map((t) => ({ ...t }));
|
|
405
|
+
this.history = snapshot.history.map((t) => ({ ...t }));
|
|
406
|
+
this.current = snapshot.current ? { ...snapshot.current } : null;
|
|
407
|
+
this.loop = snapshot.loop;
|
|
408
|
+
this.volume = snapshot.volume;
|
|
409
|
+
this.paused = snapshot.paused;
|
|
410
|
+
}
|
|
411
|
+
/** Apply a named EQ preset. `"flat"` clears all filters.
|
|
412
|
+
*
|
|
413
|
+
* | preset | effect |
|
|
414
|
+
* |-------------|---------------------------------------------|
|
|
415
|
+
* | flat | no filters (passthrough) |
|
|
416
|
+
* | bassboost | heavy bass boost (+8 dB) |
|
|
417
|
+
* | soft | gentle bass lift (+3 dB) |
|
|
418
|
+
* | nightcore | speed+pitch up (1.3×) |
|
|
419
|
+
* | vaporwave | speed+pitch down (0.8×) |
|
|
420
|
+
* | earrape | extreme distortion / clipping (use wisely) |
|
|
421
|
+
* | treble | high-frequency boost (+6 dB) |
|
|
422
|
+
* | karaoke | basic vocal removal |
|
|
423
|
+
*/
|
|
424
|
+
async setEqualizer(preset) {
|
|
425
|
+
const graphs = {
|
|
426
|
+
flat: null,
|
|
427
|
+
bassboost: "bass=g=8",
|
|
428
|
+
soft: "bass=g=3",
|
|
429
|
+
nightcore: `atempo=1.3,asetrate=${Math.round(48e3 * 1.3)}`,
|
|
430
|
+
vaporwave: `atempo=0.8,asetrate=${Math.round(48e3 * 0.8)}`,
|
|
431
|
+
earrape: "acrusher=level_in=8:level_out=18:bits=8:mode=log:aa=1",
|
|
432
|
+
treble: "treble=g=6",
|
|
433
|
+
karaoke: "pan=stereo|c0=c0-c1|c1=c1-c0"
|
|
434
|
+
};
|
|
435
|
+
await this.setFilters(graphs[preset]);
|
|
436
|
+
}
|
|
437
|
+
/** Reverse the current queue order in-place. */
|
|
438
|
+
reverse() {
|
|
439
|
+
this.queue.reverse();
|
|
440
|
+
}
|
|
441
|
+
/** Remove all tracks from the queue that were requested by `requesterId`. */
|
|
442
|
+
removeBy(requesterId) {
|
|
443
|
+
const id = String(requesterId);
|
|
444
|
+
const before = this.queue.length;
|
|
445
|
+
this.queue = this.queue.filter((t) => String(t.extras.requester) !== id);
|
|
446
|
+
return before - this.queue.length;
|
|
447
|
+
}
|
|
448
|
+
/** Peek at the next N tracks without mutating state. Defaults to 5. */
|
|
449
|
+
peek(n = 5) {
|
|
450
|
+
return this.queue.slice(0, n);
|
|
451
|
+
}
|
|
452
|
+
/** Returns true if nothing is queued and nothing is playing. */
|
|
453
|
+
get idle() {
|
|
454
|
+
return this.current === null && this.queue.length === 0;
|
|
455
|
+
}
|
|
332
456
|
async disconnect() {
|
|
333
457
|
this.queue = [];
|
|
334
458
|
this.current = null;
|
|
459
|
+
this.history = [];
|
|
460
|
+
this.paused = false;
|
|
461
|
+
this.volume = 100;
|
|
335
462
|
if (this.created) await this.host.rest.destroyPlayer(this.guildId);
|
|
336
463
|
this.created = false;
|
|
337
464
|
this.voiceServer = null;
|
|
@@ -469,7 +596,7 @@ var Player = class {
|
|
|
469
596
|
};
|
|
470
597
|
|
|
471
598
|
// src/rest.ts
|
|
472
|
-
var DEFAULT_TIMEOUT_MS =
|
|
599
|
+
var DEFAULT_TIMEOUT_MS = 16e3;
|
|
473
600
|
var RETRYABLE_STATUS = /* @__PURE__ */ new Set([502, 503]);
|
|
474
601
|
var BACKOFF_MS = [200, 500];
|
|
475
602
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -630,6 +757,38 @@ var Client = class extends EventEmitter {
|
|
|
630
757
|
this.closed = false;
|
|
631
758
|
this.openSocket();
|
|
632
759
|
}
|
|
760
|
+
/** Resolves once the client fires `ready` or `resumed`. If it is already
|
|
761
|
+
* connected it resolves on the next tick. Rejects after `timeoutMs` (default
|
|
762
|
+
* 30 s) so callers don't hang forever on a bad URL/token. */
|
|
763
|
+
waitForReady(timeoutMs = 3e4) {
|
|
764
|
+
return new Promise((resolve, reject) => {
|
|
765
|
+
if (this.hasConnected) {
|
|
766
|
+
resolve();
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
const onReady = () => {
|
|
770
|
+
clearTimeout(timer);
|
|
771
|
+
resolve();
|
|
772
|
+
};
|
|
773
|
+
const onResumed = () => {
|
|
774
|
+
clearTimeout(timer);
|
|
775
|
+
resolve();
|
|
776
|
+
};
|
|
777
|
+
const timer = setTimeout(() => {
|
|
778
|
+
this.off("ready", onReady);
|
|
779
|
+
this.off("resumed", onResumed);
|
|
780
|
+
reject(new Error("[auralis] waitForReady timed out"));
|
|
781
|
+
}, timeoutMs);
|
|
782
|
+
this.once("ready", onReady);
|
|
783
|
+
this.once("resumed", onResumed);
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
/** Gracefully disconnect every active player then close the WebSocket.
|
|
787
|
+
* Prefer this over `close()` when you want clean backend teardown. */
|
|
788
|
+
async destroy() {
|
|
789
|
+
await Promise.allSettled(this.players().map((p) => p.disconnect()));
|
|
790
|
+
await this.close();
|
|
791
|
+
}
|
|
633
792
|
async close() {
|
|
634
793
|
this.closed = true;
|
|
635
794
|
if (this.heartbeatTimer) {
|
|
@@ -657,6 +816,8 @@ var Client = class extends EventEmitter {
|
|
|
657
816
|
if (!p) {
|
|
658
817
|
p = new Player(this, id);
|
|
659
818
|
this.playersMap.set(id, p);
|
|
819
|
+
this.subscribeGuild(id);
|
|
820
|
+
this.emit("playerCreated", p);
|
|
660
821
|
}
|
|
661
822
|
return p;
|
|
662
823
|
}
|
|
@@ -665,16 +826,22 @@ var Client = class extends EventEmitter {
|
|
|
665
826
|
}
|
|
666
827
|
dropPlayer(guildId) {
|
|
667
828
|
this.playersMap.delete(guildId);
|
|
829
|
+
this.emit("playerDestroyed", guildId);
|
|
668
830
|
}
|
|
669
831
|
// --- metadata convenience --------------------------------------------
|
|
670
832
|
loadTracks(identifier) {
|
|
671
833
|
return this.rest.loadTracks(identifier);
|
|
672
834
|
}
|
|
673
|
-
/** `ytsearch:`-prefix a bare query and return the hits
|
|
835
|
+
/** `ytsearch:`-prefix a bare query and return the hits. Throws `LoadError` on
|
|
836
|
+
* a server-side error so callers aren't silently handed an empty array. */
|
|
674
837
|
async search(query) {
|
|
675
838
|
const first = query.split(" ", 1)[0];
|
|
676
839
|
const ident = first.includes(":") ? query : `ytsearch:${query}`;
|
|
677
|
-
|
|
840
|
+
const result = await this.loadTracks(ident);
|
|
841
|
+
if (result.loadType === "error" /* Error */) {
|
|
842
|
+
throw new LoadError(result.error ?? `search failed for: ${query}`);
|
|
843
|
+
}
|
|
844
|
+
return result.tracks;
|
|
678
845
|
}
|
|
679
846
|
// --- websocket --------------------------------------------------------
|
|
680
847
|
get wsUrl() {
|
|
@@ -733,6 +900,14 @@ var Client = class extends EventEmitter {
|
|
|
733
900
|
});
|
|
734
901
|
}
|
|
735
902
|
}
|
|
903
|
+
subscribeGuild(guildId) {
|
|
904
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
905
|
+
this.send({
|
|
906
|
+
op: "SUBSCRIBE",
|
|
907
|
+
d: { guilds: [String(guildId)] }
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
}
|
|
736
911
|
send(payload) {
|
|
737
912
|
if (this.ws?.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(payload));
|
|
738
913
|
}
|
|
@@ -768,6 +943,9 @@ var Client = class extends EventEmitter {
|
|
|
768
943
|
}
|
|
769
944
|
if (op === "STATS") {
|
|
770
945
|
this.lastStats = d;
|
|
946
|
+
const eventName2 = EVENT_OPS[op];
|
|
947
|
+
if (eventName2) this.emit(eventName2, null, d);
|
|
948
|
+
return;
|
|
771
949
|
}
|
|
772
950
|
const guildId = d.guild_id != null ? BigInt(d.guild_id) : null;
|
|
773
951
|
const player = guildId !== null ? this.playersMap.get(guildId) : void 0;
|