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.
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/index.ts +9 -0
- package/package.json +56 -0
- package/src/Constants.ts +55 -0
- package/src/Shoukaku.ts +295 -0
- package/src/Utils.ts +58 -0
- package/src/connectors/Connector.ts +49 -0
- package/src/connectors/README.md +42 -0
- package/src/connectors/libs/DiscordJS.ts +21 -0
- package/src/connectors/libs/Eris.ts +21 -0
- package/src/connectors/libs/OceanicJS.ts +21 -0
- package/src/connectors/libs/Seyfert.ts +26 -0
- package/src/connectors/libs/index.ts +4 -0
- package/src/guild/Connection.ts +248 -0
- package/src/guild/Player.ts +543 -0
- package/src/node/Node.ts +442 -0
- package/src/node/Rest.ts +433 -0
package/src/node/Rest.ts
ADDED
|
@@ -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
|
+
}
|