lavalink-client 2.1.7 → 2.2.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.
Files changed (39) hide show
  1. package/README.md +87 -1
  2. package/dist/cjs/structures/CustomSearches/BandCampSearch.js +3 -2
  3. package/dist/cjs/structures/Filters.d.ts +1 -1
  4. package/dist/cjs/structures/Filters.js +5 -5
  5. package/dist/cjs/structures/LavalinkManager.d.ts +23 -5
  6. package/dist/cjs/structures/LavalinkManager.js +15 -1
  7. package/dist/cjs/structures/LavalinkManagerStatics.d.ts +3 -0
  8. package/dist/cjs/structures/LavalinkManagerStatics.js +8 -1
  9. package/dist/cjs/structures/Node.d.ts +317 -35
  10. package/dist/cjs/structures/Node.js +330 -85
  11. package/dist/cjs/structures/NodeManager.d.ts +1 -1
  12. package/dist/cjs/structures/Player.d.ts +44 -8
  13. package/dist/cjs/structures/Player.js +35 -27
  14. package/dist/cjs/structures/Queue.js +1 -1
  15. package/dist/cjs/structures/Utils.d.ts +5 -2
  16. package/dist/cjs/structures/Utils.js +7 -4
  17. package/dist/esm/structures/CustomSearches/BandCampSearch.js +2 -1
  18. package/dist/esm/structures/Filters.d.ts +1 -1
  19. package/dist/esm/structures/Filters.js +5 -5
  20. package/dist/esm/structures/LavalinkManager.d.ts +23 -5
  21. package/dist/esm/structures/LavalinkManager.js +15 -1
  22. package/dist/esm/structures/LavalinkManagerStatics.d.ts +3 -0
  23. package/dist/esm/structures/LavalinkManagerStatics.js +8 -1
  24. package/dist/esm/structures/Node.d.ts +317 -35
  25. package/dist/esm/structures/Node.js +330 -85
  26. package/dist/esm/structures/NodeManager.d.ts +1 -1
  27. package/dist/esm/structures/Player.d.ts +44 -8
  28. package/dist/esm/structures/Player.js +35 -27
  29. package/dist/esm/structures/Queue.js +1 -1
  30. package/dist/esm/structures/Utils.d.ts +5 -2
  31. package/dist/esm/structures/Utils.js +7 -4
  32. package/dist/types/structures/Filters.d.ts +1 -1
  33. package/dist/types/structures/LavalinkManager.d.ts +23 -5
  34. package/dist/types/structures/LavalinkManagerStatics.d.ts +3 -0
  35. package/dist/types/structures/Node.d.ts +317 -35
  36. package/dist/types/structures/NodeManager.d.ts +1 -1
  37. package/dist/types/structures/Player.d.ts +44 -8
  38. package/dist/types/structures/Utils.d.ts +5 -2
  39. package/package.json +5 -4
@@ -1,14 +1,17 @@
1
1
  import { isAbsolute } from "path";
2
- import { Pool } from "undici";
3
2
  import WebSocket from "ws";
4
3
  import { DestroyReasons } from "./Player";
5
4
  import { NodeSymbol, queueTrackEnd } from "./Utils";
6
5
  export const validSponsorBlocks = ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "filler"];
6
+ /**
7
+ * Lavalink Node creator class
8
+ */
7
9
  export class LavalinkNode {
8
10
  /** The provided Options of the Node */
9
11
  options;
10
12
  /** The amount of rest calls the node has made. */
11
13
  calls = 0;
14
+ /** Stats from lavalink, will be updated via an interval by lavalink. */
12
15
  stats = {
13
16
  players: 0,
14
17
  playingPlayers: 0,
@@ -30,6 +33,7 @@ export class LavalinkNode {
30
33
  sent: 0,
31
34
  }
32
35
  };
36
+ /** The current sessionId, only present when connected */
33
37
  sessionId = null;
34
38
  /** Wether the node resuming is enabled or not */
35
39
  resuming = { enabled: true, timeout: null };
@@ -43,97 +47,155 @@ export class LavalinkNode {
43
47
  reconnectAttempts = 1;
44
48
  /** The Socket of the Lavalink */
45
49
  socket = null;
46
- /** The Rest Server for this Lavalink */
47
- rest;
48
50
  /** Version of what the Lavalink Server should be */
49
51
  version = "v4";
50
52
  /**
51
53
  * Create a new Node
52
54
  * @param options Lavalink Node Options
53
55
  * @param manager Node Manager
56
+ *
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * // don't create a node manually, instead use:
61
+ *
62
+ * client.lavalink.nodeManager.createNode(options)
63
+ * ```
54
64
  */
55
65
  constructor(options, manager) {
56
66
  this.options = {
57
67
  secure: false,
58
68
  retryAmount: 5,
59
69
  retryDelay: 30e3,
60
- requestTimeout: 10e3,
70
+ requestSignalTimeoutMS: 10000,
61
71
  ...options
62
72
  };
63
73
  this.NodeManager = manager;
64
74
  this.validate();
65
75
  if (this.options.secure && this.options.port !== 443)
66
76
  throw new SyntaxError("If secure is true, then the port must be 443");
67
- this.rest = new Pool(this.poolAddress, this.options.poolOptions);
68
77
  this.options.regions = (this.options.regions || []).map(a => a.toLowerCase());
69
78
  Object.defineProperty(this, NodeSymbol, { configurable: true, value: true });
70
79
  }
80
+ /**
81
+ * Parse url params correctly for lavalink requests, including support for urls and uris.
82
+ * @param url input url object
83
+ * @param extraQueryUrlParams UrlSearchParams to use in a encodedURI, useful for example for flowertts
84
+ * @returns the url as a valid string
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * player.node.getRequestingUrl(new URL(`http://localhost:2333/v4/loadtracks?identifier=Never gonna give you up`));
89
+ * ```
90
+ */
91
+ getRequestingUrl(url, extraQueryUrlParams) {
92
+ if (!url.searchParams.size)
93
+ return `${url.origin}${url.pathname}`;
94
+ const keysToAdd = [];
95
+ for (const [paramKey, paramValue] of url.searchParams.entries()) {
96
+ const decoded = decodeURIComponent(paramValue).trim(); // double decoding, once internally, a second time if decoded by provided user.
97
+ if (decoded.includes("://") && !/^https?:\/\//.test(decoded)) { // uri, but not url.
98
+ const [key, ...values] = decoded.split("://");
99
+ keysToAdd.push(`${paramKey}=${encodeURI(`${key}://${encodeURIComponent(values.join("://"))}${extraQueryUrlParams && extraQueryUrlParams?.size > 0 ? `?${extraQueryUrlParams.toString()}` : ""}`)}`);
100
+ continue;
101
+ }
102
+ keysToAdd.push(`${paramKey}=${encodeURIComponent(decoded)}`);
103
+ }
104
+ return `${url.origin}${url.pathname}?${keysToAdd.join("&")}`;
105
+ }
71
106
  /**
72
107
  * Raw Request util function
73
108
  * @param endpoint endpoint string
74
109
  * @param modify modify the request
75
- * @returns
110
+ * @param extraQueryUrlParams UrlSearchParams to use in a encodedURI, useful for example for flowertts
111
+ * @returns object containing request and option information
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * player.node.rawRequest(`/loadtracks?identifier=Never gonna give you up`, (options) => options.method = "GET");
116
+ * ```
76
117
  */
77
118
  async rawRequest(endpoint, modify) {
78
119
  const options = {
79
120
  path: `/${this.version}/${endpoint.replace(/^\//gm, "")}`,
80
121
  method: "GET",
81
122
  headers: {
82
- Authorization: this.options.authorization
123
+ "Authorization": this.options.authorization
83
124
  },
84
- headersTimeout: this.options.requestTimeout,
125
+ signal: this.options.requestSignalTimeoutMS && this.options.requestSignalTimeoutMS > 0 ? AbortSignal.timeout(this.options.requestSignalTimeoutMS) : undefined,
85
126
  };
86
127
  modify?.(options);
87
- const url = new URL(`${this.poolAddress}${options.path}`);
128
+ const url = new URL(`${this.restAddress}${options.path}`);
88
129
  url.searchParams.append("trace", "true");
89
- options.path = url.pathname + url.search;
90
- const request = await this.rest.request(options);
130
+ const urlToUse = this.getRequestingUrl(url, options?.extraQueryUrlParams);
131
+ delete options.path;
132
+ delete options.extraQueryUrlParams;
133
+ const request = await fetch(urlToUse, options);
91
134
  this.calls++;
92
135
  return { request, options };
93
136
  }
94
137
  /**
95
- * Makes an API call to the Node
138
+ * Makes an API call to the Node. Should only be used for manual parsing like for not supported plugins
96
139
  * @param endpoint The endpoint that we will make the call to
97
140
  * @param modify Used to modify the request before being sent
98
141
  * @returns The returned data
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * player.node.request(`/loadtracks?identifier=Never gonna give you up`, (options) => options.method = "GET", false);
146
+ * ```
99
147
  */
100
148
  async request(endpoint, modify, parseAsText = false) {
101
149
  const { request, options } = await this.rawRequest(endpoint, modify);
102
150
  if (["DELETE", "PUT"].includes(options.method))
103
151
  return;
104
- if (request.statusCode === 404)
152
+ if (request.status === 404)
105
153
  throw new Error(`Node Request resulted into an error, request-PATH: ${options.path} | headers: ${JSON.stringify(request.headers)}`);
106
- return parseAsText ? await request.body.text() : await request.body.json();
154
+ return parseAsText ? await request.text() : await request.json();
107
155
  }
108
156
  /**
109
157
  * Search something raw on the node, please note only add tracks to players of that node
110
158
  * @param query SearchQuery Object
111
159
  * @param requestUser Request User for creating the player(s)
160
+ * @param throwOnEmpty Wether to throw on an empty result or not
112
161
  * @returns Searchresult
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * // use player.search() instead
166
+ * player.node.search({ query: "Never gonna give you up by Rick Astley", source: "soundcloud" }, interaction.user);
167
+ * player.node.search({ query: "https://deezer.com/track/123456789" }, interaction.user);
168
+ * ```
113
169
  */
114
- async search(query, requestUser) {
170
+ async search(query, requestUser, throwOnEmpty = false) {
115
171
  const Query = this.NodeManager.LavalinkManager.utils.transformQuery(query);
116
172
  this.NodeManager.LavalinkManager.utils.validateQueryString(this, Query.query, Query.source);
117
173
  if (Query.source)
118
174
  this.NodeManager.LavalinkManager.utils.validateSourceString(this, Query.source);
119
- if (["bcsearch", "bandcamp"].includes(Query.source)) {
120
- throw new Error("Bandcamp Search only works on the player!");
175
+ if (["bcsearch", "bandcamp"].includes(Query.source) && !this.info.sourceManagers.includes("bandcamp")) {
176
+ throw new Error("Bandcamp Search only works on the player (lavaplayer version < 2.2.0!");
121
177
  }
122
178
  let uri = `/loadtracks?identifier=`;
123
179
  if (/^https?:\/\//.test(Query.query) || ["http", "https", "link", "uri"].includes(Query.source)) { // if it's a link simply encode it
124
- uri += encodeURIComponent(decodeURIComponent(Query.query));
180
+ uri += encodeURIComponent(Query.query);
125
181
  }
126
182
  else { // if not make a query out of it
127
183
  if (Query.source !== "local")
128
184
  uri += `${Query.source}:`; // only add the query source string if it's not a local track
129
185
  if (Query.source === "ftts")
130
- uri += `//${encodeURIComponent(encodeURI(decodeURIComponent(Query.query)))}`;
186
+ uri += `//${encodeURIComponent(Query.query)}`;
131
187
  else
132
- uri += encodeURIComponent(decodeURIComponent(Query.query));
188
+ uri += encodeURIComponent(Query.query);
133
189
  }
134
- const res = await this.request(uri);
190
+ const res = await this.request(uri, (options) => {
191
+ if (typeof query === "object" && typeof query.extraQueryUrlParams?.size === "number" && query.extraQueryUrlParams?.size > 0) {
192
+ options.extraQueryUrlParams = query.extraQueryUrlParams;
193
+ }
194
+ });
135
195
  // transform the data which can be Error, Track or Track[] to enfore [Track]
136
196
  const resTracks = res.loadType === "playlist" ? res.data?.tracks : res.loadType === "track" ? [res.data] : res.loadType === "search" ? Array.isArray(res.data) ? res.data : [res.data] : [];
197
+ if (throwOnEmpty === true && (res.loadType === "empty" || !resTracks.length))
198
+ throw new Error("Nothing found");
137
199
  return {
138
200
  loadType: res.loadType,
139
201
  exception: res.loadType === "error" ? res.data : null,
@@ -150,6 +212,19 @@ export class LavalinkNode {
150
212
  tracks: (resTracks.length ? resTracks.map(t => this.NodeManager.LavalinkManager.utils.buildTrack(t, requestUser)) : [])
151
213
  };
152
214
  }
215
+ /**
216
+ * Search something using the lavaSearchPlugin (filtered searches by types)
217
+ * @param query LavaSearchQuery Object
218
+ * @param requestUser Request User for creating the player(s)
219
+ * @param throwOnEmpty Wether to throw on an empty result or not
220
+ * @returns LavaSearchresult
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * // use player.search() instead
225
+ * player.node.lavaSearch({ types: ["playlist", "album"], query: "Rick Astley", source: "spotify" }, interaction.user);
226
+ * ```
227
+ */
153
228
  async lavaSearch(query, requestUser, throwOnEmpty = false) {
154
229
  const Query = this.NodeManager.LavalinkManager.utils.transformLavaSearchQuery(query);
155
230
  if (Query.source)
@@ -163,9 +238,9 @@ export class LavalinkNode {
163
238
  if (!this.info.plugins.find(v => v.name === "lavasrc-plugin"))
164
239
  throw new RangeError(`there is no lavasrc-plugin available in the lavalink node: ${this.id}`);
165
240
  const { request } = await this.rawRequest(`/loadsearch?query=${Query.source ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}${Query.types?.length ? `&types=${Query.types.join(",")}` : ""}`);
166
- if (throwOnEmpty === true)
241
+ const res = (request.status === 204 ? {} : await request.json());
242
+ if (throwOnEmpty === true && !Object.entries(res).flat().filter(Boolean).length)
167
243
  throw new Error("Nothing found");
168
- const res = (request.statusCode === 204 ? {} : await request.body.json());
169
244
  return {
170
245
  tracks: res.tracks?.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) || [],
171
246
  albums: res.albums?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
@@ -177,8 +252,14 @@ export class LavalinkNode {
177
252
  }
178
253
  /**
179
254
  * Update the Player State on the Lavalink Server
180
- * @param data
181
- * @returns
255
+ * @param data data to send to lavalink and sync locally
256
+ * @returns result from lavalink
257
+ *
258
+ * @example
259
+ * ```ts
260
+ * // use player.search() instead
261
+ * player.node.updatePlayer({ guildId: player.guildId, playerOptions: { paused: true } }); // example to pause it
262
+ * ```
182
263
  */
183
264
  async updatePlayer(data) {
184
265
  if (!this.sessionId)
@@ -190,7 +271,7 @@ export class LavalinkNode {
190
271
  r.headers["Content-Type"] = "application/json";
191
272
  r.body = JSON.stringify(data.playerOptions);
192
273
  if (data.noReplace) {
193
- const url = new URL(`${this.poolAddress}${r.path}`);
274
+ const url = new URL(`${this.restAddress}${r.path}`);
194
275
  url.searchParams.append("noReplace", data.noReplace === true && typeof data.noReplace === "boolean" ? "true" : "false");
195
276
  r.path = url.pathname + url.search;
196
277
  }
@@ -200,7 +281,13 @@ export class LavalinkNode {
200
281
  /**
201
282
  * Destroys the Player on the Lavalink Server
202
283
  * @param guildId
203
- * @returns
284
+ * @returns request result
285
+ *
286
+ * @example
287
+ * ```ts
288
+ * // use player.destroy() instead
289
+ * player.node.destroyPlayer(player.guildId);
290
+ * ```
204
291
  */
205
292
  async destroyPlayer(guildId) {
206
293
  if (!this.sessionId)
@@ -210,7 +297,15 @@ export class LavalinkNode {
210
297
  /**
211
298
  * Connect to the Lavalink Node
212
299
  * @param sessionId Provide the Session Id of the previous connection, to resume the node and it's player(s)
213
- * @returns
300
+ * @returns void
301
+ *
302
+ * @example
303
+ * ```ts
304
+ * player.node.connect(); // if provided on bootup in managerOptions#nodes, this will be called automatically when doing lavalink.init()
305
+ *
306
+ * // or connect from a resuming session:
307
+ * player.node.connect("sessionId");
308
+ * ```
214
309
  */
215
310
  connect(sessionId) {
216
311
  if (this.connected)
@@ -230,13 +325,28 @@ export class LavalinkNode {
230
325
  this.socket.on("message", this.message.bind(this));
231
326
  this.socket.on("error", this.error.bind(this));
232
327
  }
233
- /** Get the id of the node */
328
+ /**
329
+ * Get the id of the node
330
+ *
331
+ * @example
332
+ * ```ts
333
+ * const nodeId = player.node.id;
334
+ * console.log("node id is: ", nodeId)
335
+ * ```
336
+ */
234
337
  get id() {
235
338
  return this.options.id || `${this.options.host}:${this.options.port}`;
236
339
  }
237
340
  /**
238
341
  * Destroys the Node-Connection (Websocket) and all player's of the node
239
- * @returns
342
+ * @param destroyReason Destroyreason to use when destroying the players
343
+ * @param deleteNode wether to delete the nodte from the nodes list too, if false it will emit a disconnect. @default true
344
+ * @returns void
345
+ *
346
+ * @example
347
+ * ```ts
348
+ * player.node.destroy("custom Player Destroy Reason", true);
349
+ * ```
240
350
  */
241
351
  destroy(destroyReason, deleteNode = true) {
242
352
  if (!this.connected)
@@ -258,14 +368,47 @@ export class LavalinkNode {
258
368
  }
259
369
  return;
260
370
  }
261
- /** Returns if connected to the Node. */
371
+ /**
372
+ * Returns if connected to the Node.
373
+ *
374
+ * @example
375
+ * ```ts
376
+ * const isConnected = player.node.connected;
377
+ * console.log("node is connected: ", isConnected ? "yes" : "no")
378
+ * ```
379
+ */
262
380
  get connected() {
263
381
  if (!this.socket)
264
382
  return false;
265
383
  return this.socket.readyState === WebSocket.OPEN;
266
384
  }
385
+ /**
386
+ * Returns the current ConnectionStatus
387
+ *
388
+ * @example
389
+ * ```ts
390
+ * try {
391
+ * const statusOfConnection = player.node.connectionStatus;
392
+ * console.log("node's connection status is:", statusOfConnection)
393
+ * } catch (error) {
394
+ * console.error("no socket available?", error)
395
+ * }
396
+ * ```
397
+ */
398
+ get connectionStatus() {
399
+ if (!this.socket)
400
+ throw new Error("no websocket was initialized yet");
401
+ return ["CONNECTING", "OPEN", "CLOSING", "CLOSED"][this.socket.readyState] || "UNKNOWN";
402
+ }
267
403
  /**
268
404
  * Gets all Players of a Node
405
+ * @returns array of players inside of lavalink
406
+ *
407
+ * @example
408
+ * ```ts
409
+ * const node = lavalink.nodes.get("NODEID");
410
+ * const playersOfLavalink = await node?.fetchAllPlayers();
411
+ * ```
269
412
  */
270
413
  async fetchAllPlayers() {
271
414
  if (!this.sessionId)
@@ -278,6 +421,13 @@ export class LavalinkNode {
278
421
  }
279
422
  /**
280
423
  * Gets specific Player Information
424
+ * @returns lavalink player object if player exists on lavalink
425
+ *
426
+ * @example
427
+ * ```ts
428
+ * const node = lavalink.nodes.get("NODEID");
429
+ * const playerInformation = await node?.fetchPlayer("guildId");
430
+ * ```
281
431
  */
282
432
  async fetchPlayer(guildId) {
283
433
  if (!this.sessionId)
@@ -288,6 +438,13 @@ export class LavalinkNode {
288
438
  * Updates the session with and enables/disables resuming and timeout
289
439
  * @param resuming Whether resuming is enabled for this session or not
290
440
  * @param timeout The timeout in seconds (default is 60s)
441
+ * @returns the result of the request
442
+ *
443
+ * @example
444
+ * ```ts
445
+ * const node = player.node || lavalink.nodes.get("NODEID");
446
+ * await node?.updateSession(true, 180e3); // will enable resuming for 180seconds
447
+ * ```
291
448
  */
292
449
  async updateSession(resuming, timeout) {
293
450
  if (!this.sessionId)
@@ -312,20 +469,35 @@ export class LavalinkNode {
312
469
  */
313
470
  decode = {
314
471
  /**
315
- * Decode a single track into its info, where BASE64 is the encoded base64 data.
316
- * @param encoded
317
- * @returns
472
+ * Decode a single track into its info
473
+ * @param encoded valid encoded base64 string from a track
474
+ * @param requester the requesteruser for building the track
475
+ * @returns decoded track from lavalink
476
+ *
477
+ * @example
478
+ * ```ts
479
+ * const encodedBase64 = 'QAACDgMACk5vIERpZ2dpdHkAC0JsYWNrc3RyZWV0AAAAAAAEo4AABjkxNjQ5NgABAB9odHRwczovL2RlZXplci5jb20vdHJhY2svOTE2NDk2AQBpaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvY292ZXIvZGFlN2EyNjViNzlmYjcxMjc4Y2RlMjUwNDg0OWQ2ZjcvMTAwMHgxMDAwLTAwMDAwMC04MC0wLTAuanBnAQAMVVNJUjE5NjAwOTc4AAZkZWV6ZXIBAChObyBEaWdnaXR5OiBUaGUgVmVyeSBCZXN0IE9mIEJsYWNrc3RyZWV0AQAjaHR0cHM6Ly93d3cuZGVlemVyLmNvbS9hbGJ1bS8xMDMyNTQBACJodHRwczovL3d3dy5kZWV6ZXIuY29tL2FydGlzdC8xODYxAQBqaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvYXJ0aXN0L2YxNmNhYzM2ZmVjMzkxZjczN2I3ZDQ4MmY1YWM3M2UzLzEwMDB4MTAwMC0wMDAwMDAtODAtMC0wLmpwZwEAT2h0dHBzOi8vY2RuLXByZXZpZXctYS5kemNkbi5uZXQvc3RyZWFtL2MtYTE1Yjg1NzFhYTYyMDBjMDQ0YmY1OWM3NmVkOTEyN2MtNi5tcDMAAAAAAAAAAAA=';
480
+ * const track = await player.node.decode.singleTrack(encodedBase64, interaction.user);
481
+ * ```
318
482
  */
319
483
  singleTrack: async (encoded, requester) => {
320
484
  if (!encoded)
321
485
  throw new SyntaxError("No encoded (Base64 string) was provided");
322
486
  // return the decoded + builded track
323
- return this.NodeManager.LavalinkManager.utils.buildTrack(await this.request(`/decodetrack?encodedTrack=${encoded}`), requester);
487
+ return this.NodeManager.LavalinkManager.utils?.buildTrack(await this.request(`/decodetrack?encodedTrack=${encodeURIComponent(encoded.replace(/\s/g, ""))}`), requester);
324
488
  },
325
489
  /**
490
+ * Decodes multiple tracks into their info
491
+ * @param encodeds valid encoded base64 string array from all tracks
492
+ * @param requester the requesteruser for building the tracks
493
+ * @returns array of all tracks you decoded
326
494
  *
327
- * @param encodeds Decodes multiple tracks into their info
328
- * @returns
495
+ * @example
496
+ * ```ts
497
+ * const encodedBase64_1 = 'QAACDgMACk5vIERpZ2dpdHkAC0JsYWNrc3RyZWV0AAAAAAAEo4AABjkxNjQ5NgABAB9odHRwczovL2RlZXplci5jb20vdHJhY2svOTE2NDk2AQBpaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvY292ZXIvZGFlN2EyNjViNzlmYjcxMjc4Y2RlMjUwNDg0OWQ2ZjcvMTAwMHgxMDAwLTAwMDAwMC04MC0wLTAuanBnAQAMVVNJUjE5NjAwOTc4AAZkZWV6ZXIBAChObyBEaWdnaXR5OiBUaGUgVmVyeSBCZXN0IE9mIEJsYWNrc3RyZWV0AQAjaHR0cHM6Ly93d3cuZGVlemVyLmNvbS9hbGJ1bS8xMDMyNTQBACJodHRwczovL3d3dy5kZWV6ZXIuY29tL2FydGlzdC8xODYxAQBqaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvYXJ0aXN0L2YxNmNhYzM2ZmVjMzkxZjczN2I3ZDQ4MmY1YWM3M2UzLzEwMDB4MTAwMC0wMDAwMDAtODAtMC0wLmpwZwEAT2h0dHBzOi8vY2RuLXByZXZpZXctYS5kemNkbi5uZXQvc3RyZWFtL2MtYTE1Yjg1NzFhYTYyMDBjMDQ0YmY1OWM3NmVkOTEyN2MtNi5tcDMAAAAAAAAAAAA=';
498
+ * const encodedBase64_2 = 'QAABJAMAClRhbGsgYSBMb3QACjQwNHZpbmNlbnQAAAAAAAHr1gBxTzpodHRwczovL2FwaS12Mi5zb3VuZGNsb3VkLmNvbS9tZWRpYS9zb3VuZGNsb3VkOnRyYWNrczo4NTE0MjEwNzYvMzUyYTRiOTAtNzYxOS00M2E5LWJiOGItMjIxMzE0YzFjNjNhL3N0cmVhbS9obHMAAQAsaHR0cHM6Ly9zb3VuZGNsb3VkLmNvbS80MDR2aW5jZW50L3RhbGstYS1sb3QBADpodHRwczovL2kxLnNuZGNkbi5jb20vYXJ0d29ya3MtRTN1ek5Gc0Y4QzBXLTAtb3JpZ2luYWwuanBnAQAMUVpITkExOTg1Nzg0AApzb3VuZGNsb3VkAAAAAAAAAAA=';
499
+ * const tracks = await player.node.decode.multipleTracks([encodedBase64_1, encodedBase64_2], interaction.user);
500
+ * ```
329
501
  */
330
502
  multipleTracks: async (encodeds, requester) => {
331
503
  if (!Array.isArray(encodeds) || !encodeds.every(v => typeof v === "string" && v.length > 1))
@@ -341,14 +513,24 @@ export class LavalinkNode {
341
513
  };
342
514
  /**
343
515
  * Request Lavalink statistics.
344
- * @returns
516
+ * @returns the lavalink node stats
517
+ *
518
+ * @example
519
+ * ```ts
520
+ * const lavalinkStats = await player.node.fetchStats();
521
+ * ```
345
522
  */
346
523
  async fetchStats() {
347
524
  return await this.request(`/stats`);
348
525
  }
349
526
  /**
350
527
  * Request Lavalink version.
351
- * @returns
528
+ * @returns the current used lavalink version
529
+ *
530
+ * @example
531
+ * ```ts
532
+ * const lavalinkVersion = await player.node.fetchVersion();
533
+ * ```
352
534
  */
353
535
  async fetchVersion() {
354
536
  // need to adjust path for no-prefix version info
@@ -356,7 +538,14 @@ export class LavalinkNode {
356
538
  }
357
539
  /**
358
540
  * Request Lavalink information.
359
- * @returns
541
+ * @returns lavalink info object
542
+ *
543
+ * @example
544
+ * ```ts
545
+ * const lavalinkInfo = await player.node.fetchInfo();
546
+ * const availablePlugins:string[] = lavalinkInfo.plugins.map(plugin => plugin.name);
547
+ * const availableSources:string[] = lavalinkInfo.sourceManagers;
548
+ * ```
360
549
  */
361
550
  async fetchInfo() {
362
551
  return await this.request(`/info`);
@@ -366,7 +555,15 @@ export class LavalinkNode {
366
555
  */
367
556
  routePlannerApi = {
368
557
  /**
369
- * Get routplanner Info from Lavalink
558
+ * Get routplanner Info from Lavalink for ip rotation
559
+ * @returns the status of the routeplanner
560
+ *
561
+ * @example
562
+ * ```ts
563
+ * const routePlannerStatus = await player.node.routePlannerApi.getStatus();
564
+ * const usedBlock = routePlannerStatus.details?.ipBlock;
565
+ * const currentIp = routePlannerStatus.currentAddress;
566
+ * ```
370
567
  */
371
568
  getStatus: async () => {
372
569
  if (!this.sessionId)
@@ -374,8 +571,14 @@ export class LavalinkNode {
374
571
  return await this.request(`/routeplanner/status`);
375
572
  },
376
573
  /**
377
- * Release blacklisted IP address into pool of IPs
574
+ * Release blacklisted IP address into pool of IPs for ip rotation
378
575
  * @param address IP address
576
+ * @returns request data of the request
577
+ *
578
+ * @example
579
+ * ```ts
580
+ * await player.node.routePlannerApi.unmarkFailedAddress("ipv6address");
581
+ * ```
379
582
  */
380
583
  unmarkFailedAddress: async (address) => {
381
584
  if (!this.sessionId)
@@ -389,6 +592,12 @@ export class LavalinkNode {
389
592
  },
390
593
  /**
391
594
  * Release all blacklisted IP addresses into pool of IPs
595
+ * @returns request data of the request
596
+ *
597
+ * @example
598
+ * ```ts
599
+ * await player.node.routePlannerApi.unmarkAllFailedAddresses();
600
+ * ```
392
601
  */
393
602
  unmarkAllFailedAddresses: async () => {
394
603
  if (!this.sessionId)
@@ -400,7 +609,7 @@ export class LavalinkNode {
400
609
  });
401
610
  }
402
611
  };
403
- /** Private Utils */
612
+ /** @private Utils for validating the */
404
613
  validate() {
405
614
  if (!this.options.authorization)
406
615
  throw new SyntaxError("LavalinkNode requires 'authorization'");
@@ -409,6 +618,12 @@ export class LavalinkNode {
409
618
  if (!this.options.port)
410
619
  throw new SyntaxError("LavalinkNode requires 'port'");
411
620
  }
621
+ /**
622
+ * Sync the data of the player you make an action to lavalink to
623
+ * @param data data to use to update the player
624
+ * @param res result data from lavalink, to override, if available
625
+ * @returns boolean
626
+ */
412
627
  syncPlayerData(data, res) {
413
628
  if (typeof data === "object" && typeof data?.guildId === "string" && typeof data.playerOptions === "object" && Object.keys(data.playerOptions).length > 1) {
414
629
  const player = this.NodeManager.LavalinkManager.getPlayer(data.guildId);
@@ -419,8 +634,9 @@ export class LavalinkNode {
419
634
  player.playing = !data.playerOptions.paused;
420
635
  }
421
636
  if (typeof data.playerOptions.position === "number") {
422
- player.position = data.playerOptions.position;
637
+ // player.position = data.playerOptions.position;
423
638
  player.lastPosition = data.playerOptions.position;
639
+ player.lastPositionChange = Date.now();
424
640
  }
425
641
  if (typeof data.playerOptions.voice !== "undefined")
426
642
  player.voice = data.playerOptions.voice;
@@ -471,9 +687,22 @@ export class LavalinkNode {
471
687
  }
472
688
  return true;
473
689
  }
474
- get poolAddress() {
690
+ /**
691
+ * Get the rest Adress for making requests
692
+ */
693
+ get restAddress() {
475
694
  return `http${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}`;
476
695
  }
696
+ /**
697
+ * Reconnect to the lavalink node
698
+ * @param instaReconnect @default false wether to instantly try to reconnect
699
+ * @returns void
700
+ *
701
+ * @example
702
+ * ```ts
703
+ * await player.node.reconnect();
704
+ * ```
705
+ */
477
706
  reconnect(instaReconnect = false) {
478
707
  if (instaReconnect) {
479
708
  if (this.reconnectAttempts >= this.options.retryAmount) {
@@ -501,28 +730,32 @@ export class LavalinkNode {
501
730
  this.reconnectAttempts++;
502
731
  }, this.options.retryDelay || 1000);
503
732
  }
733
+ /** @private util function for handling opening events from websocket */
504
734
  async open() {
505
735
  if (this.reconnectTimeout)
506
736
  clearTimeout(this.reconnectTimeout);
507
737
  // reset the reconnect attempts amount
508
738
  this.reconnectAttempts = 1;
509
- this.info = await this.fetchInfo().catch(() => null);
739
+ this.info = await this.fetchInfo().catch((e) => (console.error(e, "ON-OPEN-FETCH"), null));
510
740
  if (!this.info && ["v3", "v4"].includes(this.version)) {
511
- const errorString = `Lavalink Node (${this.poolAddress}) does not provide any /${this.version}/info`;
741
+ const errorString = `Lavalink Node (${this.restAddress}) does not provide any /${this.version}/info`;
512
742
  throw new Error(errorString);
513
743
  }
514
744
  this.NodeManager.emit("connect", this);
515
745
  }
746
+ /** @private util function for handling closing events from websocket */
516
747
  close(code, reason) {
517
748
  this.NodeManager.emit("disconnect", this, { code, reason });
518
749
  if (code !== 1000 || reason !== "Node-Destroy")
519
750
  this.reconnect();
520
751
  }
752
+ /** @private util function for handling error events from websocket */
521
753
  error(error) {
522
754
  if (!error)
523
755
  return;
524
756
  this.NodeManager.emit("error", this, error);
525
757
  }
758
+ /** @private util function for handling message events from websocket */
526
759
  async message(d) {
527
760
  if (Array.isArray(d))
528
761
  d = Buffer.concat(d);
@@ -543,42 +776,15 @@ export class LavalinkNode {
543
776
  if (!player)
544
777
  return;
545
778
  const oldPlayer = player?.toJSON();
546
- if (player.get("internal_updateInterval"))
547
- clearInterval(player.get("internal_updateInterval"));
548
- // override the position
549
- player.position = payload.state.position || 0;
779
+ player.lastPositionChange = Date.now();
550
780
  player.lastPosition = payload.state.position || 0;
551
781
  player.connected = payload.state.connected;
552
782
  player.ping.ws = payload.state.ping >= 0 ? payload.state.ping : player.ping.ws <= 0 && player.connected ? null : player.ping.ws || 0;
553
783
  if (!player.createdTimeStamp && payload.state.time)
554
784
  player.createdTimeStamp = payload.state.time;
555
- if (typeof this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval === "number" && this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval >= 10) {
556
- player.set("internal_updateInterval", setInterval(() => {
557
- player.position += this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval || 250;
558
- if (player.filterManager.filterUpdatedState >= 1) {
559
- player.filterManager.filterUpdatedState++;
560
- const maxMins = 8;
561
- const currentDuration = player.queue.current?.info?.duration || 0;
562
- if (currentDuration <= maxMins * 6e4 || isAbsolute(player.queue.current?.info?.uri)) {
563
- if (player.filterManager.filterUpdatedState >= ((this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval || 250) > 400 ? 2 : 3)) {
564
- player.filterManager.filterUpdatedState = 0;
565
- player.seek(player.position);
566
- }
567
- }
568
- else {
569
- player.filterManager.filterUpdatedState = 0;
570
- }
571
- }
572
- }, this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval || 250));
573
- }
574
- else {
575
- if (player.filterManager.filterUpdatedState >= 1) { // if no interval but instafix available, findable via the "filterUpdatedState" property
576
- const maxMins = 8;
577
- const currentDuration = player.queue.current?.info?.duration || 0;
578
- if (currentDuration <= maxMins * 6e4 || isAbsolute(player.queue.current?.info?.uri))
579
- player.seek(player.position);
580
- player.filterManager.filterUpdatedState = 0;
581
- }
785
+ if (player.filterManager.filterUpdatedState === true && ((player.queue.current?.info?.duration || 0) <= (player.LavalinkManager.options.advancedOptions.maxFilterFixDuration || 600000) || isAbsolute(player.queue.current?.info?.uri))) {
786
+ player.filterManager.filterUpdatedState = false;
787
+ await player.seek(player.position);
582
788
  }
583
789
  this.NodeManager.LavalinkManager.emit("playerUpdate", oldPlayer, player);
584
790
  }
@@ -598,7 +804,7 @@ export class LavalinkNode {
598
804
  return;
599
805
  }
600
806
  }
601
- // LAVALINK EVENT HANDLING UTIL FUNCTION
807
+ /** @private middleware util function for handling all kind of events from websocket */
602
808
  async handleEvent(payload) {
603
809
  if (!payload.guildId)
604
810
  return;
@@ -625,7 +831,7 @@ export class LavalinkNode {
625
831
  this.SponsorBlockSegmentLoaded(player, player.queue.current, payload);
626
832
  break;
627
833
  case "SegmentSkipped":
628
- this.SponsorBlockSegmentkipped(player, player.queue.current, payload);
834
+ this.SponsorBlockSegmentSkipped(player, player.queue.current, payload);
629
835
  break;
630
836
  case "ChaptersLoaded":
631
837
  this.SponsorBlockChaptersLoaded(player, player.queue.current, payload);
@@ -639,7 +845,7 @@ export class LavalinkNode {
639
845
  }
640
846
  return;
641
847
  }
642
- // LAVALINK EVENT HANDLING FUNCTIONS
848
+ /** @private util function for handling trackStart event */
643
849
  trackStart(player, track, payload) {
644
850
  player.playing = true;
645
851
  player.paused = false;
@@ -648,6 +854,7 @@ export class LavalinkNode {
648
854
  return;
649
855
  return this.NodeManager.LavalinkManager.emit("trackStart", player, track, payload);
650
856
  }
857
+ /** @private util function for handling trackEnd event */
651
858
  async trackEnd(player, track, payload) {
652
859
  // If there are no songs in the queue
653
860
  if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying")))
@@ -684,6 +891,7 @@ export class LavalinkNode {
684
891
  // play track if autoSkip is true
685
892
  return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ noReplace: true });
686
893
  }
894
+ /** @private util function for handling trackStuck event */
687
895
  async trackStuck(player, track, payload) {
688
896
  this.NodeManager.LavalinkManager.emit("trackStuck", player, track, payload);
689
897
  // If there are no songs in the queue
@@ -697,6 +905,7 @@ export class LavalinkNode {
697
905
  // play track if autoSkip is true
698
906
  return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
699
907
  }
908
+ /** @private util function for handling trackError event */
700
909
  async trackError(player, track, payload) {
701
910
  this.NodeManager.LavalinkManager.emit("trackError", player, track, payload);
702
911
  return; // get's handled by trackEnd
@@ -711,23 +920,37 @@ export class LavalinkNode {
711
920
  // play track if autoSkip is true
712
921
  return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
713
922
  }
923
+ /** @private util function for handling socketClosed event */
714
924
  socketClosed(player, payload) {
715
925
  return this.NodeManager.LavalinkManager.emit("playerSocketClosed", player, payload);
716
926
  }
717
- // SPONSOR BLOCK EVENT FUNCTIONS
927
+ /** @private util function for handling SponsorBlock Segmentloaded event */
718
928
  SponsorBlockSegmentLoaded(player, track, payload) {
719
929
  return this.NodeManager.LavalinkManager.emit("SegmentsLoaded", player, track, payload);
720
930
  }
721
- SponsorBlockSegmentkipped(player, track, payload) {
931
+ /** @private util function for handling SponsorBlock SegmentSkipped event */
932
+ SponsorBlockSegmentSkipped(player, track, payload) {
722
933
  return this.NodeManager.LavalinkManager.emit("SegmentSkipped", player, track, payload);
723
934
  }
935
+ /** @private util function for handling SponsorBlock Chaptersloaded event */
724
936
  SponsorBlockChaptersLoaded(player, track, payload) {
725
937
  return this.NodeManager.LavalinkManager.emit("ChaptersLoaded", player, track, payload);
726
938
  }
939
+ /** @private util function for handling SponsorBlock Chaptersstarted event */
727
940
  SponsorBlockChapterStarted(player, track, payload) {
728
941
  return this.NodeManager.LavalinkManager.emit("ChapterStarted", player, track, payload);
729
942
  }
730
- // SPONSOR BLOCK EXECUTE FUNCTIONS
943
+ /**
944
+ * Get the current sponsorblocks for the sponsorblock plugin
945
+ * @param player passthrough the player
946
+ * @returns sponsorblock seggment from lavalink
947
+ *
948
+ * @example
949
+ * ```ts
950
+ * // use it on the player via player.getSponsorBlock();
951
+ * const sponsorBlockSegments = await player.node.getSponsorBlock(player);
952
+ * ```
953
+ */
731
954
  async getSponsorBlock(player) {
732
955
  // no plugin enabled
733
956
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -735,6 +958,17 @@ export class LavalinkNode {
735
958
  // do the request
736
959
  return await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`);
737
960
  }
961
+ /**
962
+ * Set the current sponsorblocks for the sponsorblock plugin
963
+ * @param player passthrough the player
964
+ * @returns void
965
+ *
966
+ * @example
967
+ * ```ts
968
+ * // use it on the player via player.setSponsorBlock();
969
+ * const sponsorBlockSegments = await player.node.setSponsorBlock(player, ["sponsor", "selfpromo"]);
970
+ * ```
971
+ */
738
972
  async setSponsorBlock(player, segments = ["sponsor", "selfpromo"]) {
739
973
  // no plugin enabled
740
974
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -753,6 +987,17 @@ export class LavalinkNode {
753
987
  });
754
988
  return;
755
989
  }
990
+ /**
991
+ * Delete the sponsorblock plugins
992
+ * @param player passthrough the player
993
+ * @returns void
994
+ *
995
+ * @example
996
+ * ```ts
997
+ * // use it on the player via player.deleteSponsorBlock();
998
+ * const sponsorBlockSegments = await player.node.deleteSponsorBlock(player);
999
+ * ```
1000
+ */
756
1001
  async deleteSponsorBlock(player) {
757
1002
  // no plugin enabled
758
1003
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -763,7 +1008,7 @@ export class LavalinkNode {
763
1008
  });
764
1009
  return;
765
1010
  }
766
- // UTIL FOR QUEUE END
1011
+ /** private util function for handling the queue end event */
767
1012
  async queueEnd(player, track, payload) {
768
1013
  // add previous track to the queue!
769
1014
  player.queue.current = null;