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