shoukaku-bun 4.2.0-b

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.
@@ -0,0 +1,433 @@
1
+ import { Versions } from '../Constants';
2
+ import type { FilterOptions } from '../guild/Player';
3
+ import type { NodeOption } from '../Shoukaku';
4
+ import type { Node, NodeInfo, Stats } from './Node';
5
+
6
+ export type Severity = 'common' | 'suspicious' | 'fault';
7
+
8
+ export enum LoadType {
9
+ TRACK = 'track',
10
+ PLAYLIST = 'playlist',
11
+ SEARCH = 'search',
12
+ EMPTY = 'empty',
13
+ ERROR = 'error'
14
+ }
15
+
16
+ export interface Track {
17
+ encoded: string;
18
+ info: {
19
+ identifier: string;
20
+ isSeekable: boolean;
21
+ author: string;
22
+ length: number;
23
+ isStream: boolean;
24
+ position: number;
25
+ title: string;
26
+ uri?: string;
27
+ artworkUrl?: string;
28
+ isrc?: string;
29
+ sourceName: string;
30
+ };
31
+ pluginInfo: unknown;
32
+ }
33
+
34
+ export interface Playlist {
35
+ encoded: string;
36
+ info: {
37
+ name: string;
38
+ selectedTrack: number;
39
+ };
40
+ pluginInfo: unknown;
41
+ tracks: Track[];
42
+ }
43
+
44
+ export interface Exception {
45
+ message: string;
46
+ severity: Severity;
47
+ cause: string;
48
+ }
49
+
50
+ export interface TrackResult {
51
+ loadType: LoadType.TRACK;
52
+ data: Track;
53
+ }
54
+
55
+ export interface PlaylistResult {
56
+ loadType: LoadType.PLAYLIST;
57
+ data: Playlist;
58
+ }
59
+
60
+ export interface SearchResult {
61
+ loadType: LoadType.SEARCH;
62
+ data: Track[];
63
+ }
64
+
65
+ export interface EmptyResult {
66
+ loadType: LoadType.EMPTY;
67
+ data: Record<string, never>;
68
+ }
69
+
70
+ export interface ErrorResult {
71
+ loadType: LoadType.ERROR;
72
+ data: Exception;
73
+ }
74
+
75
+ export type LavalinkResponse = TrackResult | PlaylistResult | SearchResult | EmptyResult | ErrorResult;
76
+
77
+ export interface Address {
78
+ address: string;
79
+ failingTimestamp: number;
80
+ failingTime: string;
81
+ }
82
+
83
+ export interface RoutePlanner {
84
+ class: null | 'RotatingIpRoutePlanner' | 'NanoIpRoutePlanner' | 'RotatingNanoIpRoutePlanner' | 'BalancingIpRoutePlanner';
85
+ details: null | {
86
+ ipBlock: {
87
+ type: string;
88
+ size: string;
89
+ };
90
+ failingAddresses: Address[];
91
+ rotateIndex: string;
92
+ ipIndex: string;
93
+ currentAddress: string;
94
+ blockIndex: string;
95
+ currentAddressIndex: string;
96
+ };
97
+ }
98
+
99
+ export interface LavalinkPlayerVoice {
100
+ token: string;
101
+ endpoint: string;
102
+ sessionId: string;
103
+ connected?: boolean;
104
+ ping?: number;
105
+ }
106
+
107
+ export type LavalinkPlayerVoiceOptions = Omit<LavalinkPlayerVoice, 'connected' | 'ping'>;
108
+
109
+ export interface LavalinkPlayer {
110
+ guildId: string;
111
+ track?: Track;
112
+ volume: number;
113
+ paused: boolean;
114
+ voice: LavalinkPlayerVoice;
115
+ filters: FilterOptions;
116
+ }
117
+
118
+ export interface UpdatePlayerTrackOptions {
119
+ encoded?: string | null;
120
+ identifier?: string;
121
+ userData?: unknown;
122
+ }
123
+
124
+ export interface UpdatePlayerOptions {
125
+ track?: UpdatePlayerTrackOptions;
126
+ position?: number;
127
+ endTime?: number;
128
+ volume?: number;
129
+ paused?: boolean;
130
+ filters?: FilterOptions;
131
+ voice?: LavalinkPlayerVoiceOptions;
132
+ }
133
+
134
+ export interface UpdatePlayerInfo {
135
+ guildId: string;
136
+ playerOptions: UpdatePlayerOptions;
137
+ noReplace?: boolean;
138
+ }
139
+
140
+ export interface SessionInfo {
141
+ resumingKey?: string;
142
+ timeout: number;
143
+ }
144
+
145
+ interface FetchOptions {
146
+ endpoint: string;
147
+ options: {
148
+ headers?: Record<string, string>;
149
+ params?: Record<string, string>;
150
+ method?: string;
151
+ body?: Record<string, unknown>;
152
+ [key: string]: unknown;
153
+ };
154
+ }
155
+
156
+ interface FinalFetchOptions {
157
+ method: string;
158
+ headers: Record<string, string>;
159
+ signal: AbortSignal;
160
+ body?: string;
161
+ }
162
+
163
+ /**
164
+ * Wrapper around Lavalink REST API
165
+ */
166
+ export class Rest {
167
+ /**
168
+ * Node that initialized this instance
169
+ */
170
+ protected readonly node: Node;
171
+ /**
172
+ * URL of Lavalink
173
+ */
174
+ protected readonly url: string;
175
+ /**
176
+ * Credentials to access Lavalink
177
+ */
178
+ protected readonly auth: string;
179
+ /**
180
+ * @param node An instance of Node
181
+ * @param options The options to initialize this rest class
182
+ * @param options.name Name of this node
183
+ * @param options.url URL of Lavalink
184
+ * @param options.auth Credentials to access Lavalnk
185
+ * @param options.secure Weather to use secure protocols or not
186
+ * @param options.group Group of this node
187
+ */
188
+ constructor(node: Node, options: NodeOption) {
189
+ this.node = node;
190
+ this.url = `${options.secure ? 'https' : 'http'}://${options.url}/v${Versions.REST_VERSION}`;
191
+ this.auth = options.auth;
192
+ }
193
+
194
+ protected get sessionId(): string {
195
+ return this.node.sessionId!;
196
+ }
197
+
198
+ /**
199
+ * Resolve a track
200
+ * @param identifier Track ID
201
+ * @returns A promise that resolves to a Lavalink response
202
+ */
203
+ public resolve(identifier: string): Promise<LavalinkResponse | undefined> {
204
+ const options = {
205
+ endpoint: '/loadtracks',
206
+ options: { params: { identifier }}
207
+ };
208
+ return this.fetch(options);
209
+ }
210
+
211
+ /**
212
+ * Decode a track
213
+ * @param track Encoded track
214
+ * @returns Promise that resolves to a track
215
+ */
216
+ public decode(track: string): Promise<Track | undefined> {
217
+ const options = {
218
+ endpoint: '/decodetrack',
219
+ options: { params: { track }}
220
+ };
221
+ return this.fetch<Track>(options);
222
+ }
223
+
224
+ /**
225
+ * Gets all the player with the specified sessionId
226
+ * @returns Promise that resolves to an array of Lavalink players
227
+ */
228
+ public async getPlayers(): Promise<LavalinkPlayer[]> {
229
+ const options = {
230
+ endpoint: `/sessions/${this.sessionId}/players`,
231
+ options: {}
232
+ };
233
+ return await this.fetch<LavalinkPlayer[]>(options) ?? [];
234
+ }
235
+
236
+ /**
237
+ * Gets the player with the specified guildId
238
+ * @returns Promise that resolves to a Lavalink player
239
+ */
240
+ public getPlayer(guildId: string): Promise<LavalinkPlayer | undefined> {
241
+ const options = {
242
+ endpoint: `/sessions/${this.sessionId}/players/${guildId}`,
243
+ options: {}
244
+ };
245
+ return this.fetch(options);
246
+ }
247
+
248
+ /**
249
+ * Updates a Lavalink player
250
+ * @param data SessionId from Discord
251
+ * @returns Promise that resolves to a Lavalink player
252
+ */
253
+ public updatePlayer(data: UpdatePlayerInfo): Promise<LavalinkPlayer | undefined> {
254
+ const options = {
255
+ endpoint: `/sessions/${this.sessionId}/players/${data.guildId}`,
256
+ options: {
257
+ method: 'PATCH',
258
+ params: { noReplace: data.noReplace?.toString() ?? 'false' },
259
+ headers: { 'Content-Type': 'application/json' },
260
+ body: data.playerOptions as Record<string, unknown>
261
+ }
262
+ };
263
+ return this.fetch<LavalinkPlayer>(options);
264
+ }
265
+
266
+ /**
267
+ * Deletes a Lavalink player
268
+ * @param guildId guildId where this player is
269
+ */
270
+ public async destroyPlayer(guildId: string): Promise<void> {
271
+ const options = {
272
+ endpoint: `/sessions/${this.sessionId}/players/${guildId}`,
273
+ options: { method: 'DELETE' }
274
+ };
275
+ await this.fetch(options);
276
+ }
277
+
278
+ /**
279
+ * Updates the session with a resume boolean and timeout
280
+ * @param resuming Whether resuming is enabled for this session or not
281
+ * @param timeout Timeout to wait for resuming
282
+ * @returns Promise that resolves to a Lavalink player
283
+ */
284
+ public updateSession(resuming?: boolean, timeout?: number): Promise<SessionInfo | undefined> {
285
+ const options = {
286
+ endpoint: `/sessions/${this.sessionId}`,
287
+ options: {
288
+ method: 'PATCH',
289
+ headers: { 'Content-Type': 'application/json' },
290
+ body: { resuming, timeout }
291
+ }
292
+ };
293
+ return this.fetch(options);
294
+ }
295
+
296
+ /**
297
+ * Gets the status of this node
298
+ * @returns Promise that resolves to a node stats response
299
+ */
300
+ public stats(): Promise<Stats | undefined> {
301
+ const options = {
302
+ endpoint: '/stats',
303
+ options: {}
304
+ };
305
+ return this.fetch(options);
306
+ }
307
+
308
+ /**
309
+ * Get routeplanner status from Lavalink
310
+ * @returns Promise that resolves to a routeplanner response
311
+ */
312
+ public getRoutePlannerStatus(): Promise<RoutePlanner | undefined> {
313
+ const options = {
314
+ endpoint: '/routeplanner/status',
315
+ options: {}
316
+ };
317
+ return this.fetch(options);
318
+ }
319
+
320
+ /**
321
+ * Release blacklisted IP address into pool of IPs
322
+ * @param address IP address
323
+ */
324
+ public async unmarkFailedAddress(address: string): Promise<void> {
325
+ const options = {
326
+ endpoint: '/routeplanner/free/address',
327
+ options: {
328
+ method: 'POST',
329
+ headers: { 'Content-Type': 'application/json' },
330
+ body: { address }
331
+ }
332
+ };
333
+ await this.fetch(options);
334
+ }
335
+
336
+ /**
337
+ * Get Lavalink info
338
+ */
339
+ public getLavalinkInfo(): Promise<NodeInfo | undefined> {
340
+ const options = {
341
+ endpoint: '/info',
342
+ options: {
343
+ headers: { 'Content-Type': 'application/json' }
344
+ }
345
+ };
346
+ return this.fetch(options);
347
+ }
348
+
349
+ /**
350
+ * Make a request to Lavalink
351
+ * @param fetchOptions.endpoint Lavalink endpoint
352
+ * @param fetchOptions.options Options passed to fetch
353
+ * @throws `RestError` when encountering a Lavalink error response
354
+ * @internal
355
+ */
356
+ protected async fetch<T = unknown>(fetchOptions: FetchOptions) {
357
+ const { endpoint, options } = fetchOptions;
358
+ let headers = {
359
+ 'Authorization': this.auth,
360
+ 'User-Agent': this.node.manager.options.userAgent
361
+ };
362
+
363
+ if (options.headers) headers = { ...headers, ...options.headers };
364
+
365
+ const url = new URL(`${this.url}${endpoint}`);
366
+
367
+ if (options.params) url.search = new URLSearchParams(options.params).toString();
368
+
369
+ const abortController = new AbortController();
370
+ const timeout = setTimeout(() => abortController.abort(), this.node.manager.options.restTimeout * 1000);
371
+
372
+ const method = options.method?.toUpperCase() ?? 'GET';
373
+
374
+ const finalFetchOptions: FinalFetchOptions = {
375
+ method,
376
+ headers,
377
+ signal: abortController.signal
378
+ };
379
+
380
+ if (![ 'GET', 'HEAD' ].includes(method) && options.body)
381
+ finalFetchOptions.body = JSON.stringify(options.body);
382
+
383
+ const request = await fetch(url.toString(), finalFetchOptions)
384
+ .finally(() => clearTimeout(timeout));
385
+
386
+ if (!request.ok) {
387
+ const response = await request
388
+ .json()
389
+ .catch(() => null) as LavalinkRestError | null;
390
+ throw new RestError(response ?? {
391
+ timestamp: Date.now(),
392
+ status: request.status,
393
+ error: 'Unknown Error',
394
+ message: 'Unexpected error response from Lavalink server',
395
+ path: endpoint
396
+ });
397
+ }
398
+ try {
399
+ return await request.json() as T;
400
+ } catch {
401
+ return;
402
+ }
403
+ }
404
+ }
405
+
406
+ interface LavalinkRestError {
407
+ timestamp: number;
408
+ status: number;
409
+ error: string;
410
+ trace?: string;
411
+ message: string;
412
+ path: string;
413
+ }
414
+
415
+ export class RestError extends Error {
416
+ public timestamp: number;
417
+ public status: number;
418
+ public error: string;
419
+ public trace?: string;
420
+ public path: string;
421
+
422
+ constructor({ timestamp, status, error, trace, message, path }: LavalinkRestError) {
423
+ super(`Rest request failed with response code: ${status}${message ? ` | message: ${message}` : ''}`);
424
+ this.name = 'RestError';
425
+ this.timestamp = timestamp;
426
+ this.status = status;
427
+ this.error = error;
428
+ this.trace = trace;
429
+ this.message = message;
430
+ this.path = path;
431
+ Object.setPrototypeOf(this, new.target.prototype);
432
+ }
433
+ }