lavalink-client 2.2.0 → 2.2.2

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 (42) hide show
  1. package/README.md +116 -15
  2. package/dist/cjs/structures/Filters.d.ts +1 -1
  3. package/dist/cjs/structures/Filters.js +9 -9
  4. package/dist/cjs/structures/LavalinkManager.d.ts +24 -7
  5. package/dist/cjs/structures/LavalinkManager.js +15 -2
  6. package/dist/cjs/structures/LavalinkManagerStatics.d.ts +3 -0
  7. package/dist/cjs/structures/LavalinkManagerStatics.js +4 -1
  8. package/dist/cjs/structures/Node.d.ts +307 -22
  9. package/dist/cjs/structures/Node.js +328 -72
  10. package/dist/cjs/structures/NodeManager.js +3 -1
  11. package/dist/cjs/structures/Player.d.ts +44 -8
  12. package/dist/cjs/structures/Player.js +27 -18
  13. package/dist/cjs/structures/Queue.d.ts +47 -0
  14. package/dist/cjs/structures/Queue.js +104 -1
  15. package/dist/cjs/structures/Track.d.ts +1 -0
  16. package/dist/cjs/structures/Utils.d.ts +3 -0
  17. package/dist/cjs/structures/Utils.js +6 -4
  18. package/dist/esm/structures/Filters.d.ts +1 -1
  19. package/dist/esm/structures/Filters.js +9 -9
  20. package/dist/esm/structures/LavalinkManager.d.ts +24 -7
  21. package/dist/esm/structures/LavalinkManager.js +15 -2
  22. package/dist/esm/structures/LavalinkManagerStatics.d.ts +3 -0
  23. package/dist/esm/structures/LavalinkManagerStatics.js +4 -1
  24. package/dist/esm/structures/Node.d.ts +307 -22
  25. package/dist/esm/structures/Node.js +328 -72
  26. package/dist/esm/structures/NodeManager.js +3 -1
  27. package/dist/esm/structures/Player.d.ts +44 -8
  28. package/dist/esm/structures/Player.js +27 -18
  29. package/dist/esm/structures/Queue.d.ts +47 -0
  30. package/dist/esm/structures/Queue.js +104 -1
  31. package/dist/esm/structures/Track.d.ts +1 -0
  32. package/dist/esm/structures/Utils.d.ts +3 -0
  33. package/dist/esm/structures/Utils.js +6 -4
  34. package/dist/types/structures/Filters.d.ts +1 -1
  35. package/dist/types/structures/LavalinkManager.d.ts +24 -7
  36. package/dist/types/structures/LavalinkManagerStatics.d.ts +3 -0
  37. package/dist/types/structures/Node.d.ts +307 -22
  38. package/dist/types/structures/Player.d.ts +44 -8
  39. package/dist/types/structures/Queue.d.ts +47 -0
  40. package/dist/types/structures/Track.d.ts +1 -0
  41. package/dist/types/structures/Utils.d.ts +3 -0
  42. package/package.json +2 -3
@@ -7,11 +7,15 @@ const ws_1 = tslib_1.__importDefault(require("ws"));
7
7
  const Player_1 = require("./Player");
8
8
  const Utils_1 = require("./Utils");
9
9
  exports.validSponsorBlocks = ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "filler"];
10
+ /**
11
+ * Lavalink Node creator class
12
+ */
10
13
  class LavalinkNode {
11
14
  /** The provided Options of the Node */
12
15
  options;
13
16
  /** The amount of rest calls the node has made. */
14
17
  calls = 0;
18
+ /** Stats from lavalink, will be updated via an interval by lavalink. */
15
19
  stats = {
16
20
  players: 0,
17
21
  playingPlayers: 0,
@@ -33,6 +37,7 @@ class LavalinkNode {
33
37
  sent: 0,
34
38
  }
35
39
  };
40
+ /** The current sessionId, only present when connected */
36
41
  sessionId = null;
37
42
  /** Wether the node resuming is enabled or not */
38
43
  resuming = { enabled: true, timeout: null };
@@ -52,6 +57,14 @@ class LavalinkNode {
52
57
  * Create a new Node
53
58
  * @param options Lavalink Node Options
54
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
+ * ```
55
68
  */
56
69
  constructor(options, manager) {
57
70
  this.options = {
@@ -68,11 +81,43 @@ class LavalinkNode {
68
81
  this.options.regions = (this.options.regions || []).map(a => a.toLowerCase());
69
82
  Object.defineProperty(this, Utils_1.NodeSymbol, { configurable: true, value: true });
70
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
+ }
71
110
  /**
72
111
  * Raw Request util function
73
112
  * @param endpoint endpoint string
74
113
  * @param modify modify the request
75
- * @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
+ * ```
76
121
  */
77
122
  async rawRequest(endpoint, modify) {
78
123
  const options = {
@@ -86,16 +131,23 @@ class LavalinkNode {
86
131
  modify?.(options);
87
132
  const url = new URL(`${this.restAddress}${options.path}`);
88
133
  url.searchParams.append("trace", "true");
134
+ const urlToUse = this.getRequestingUrl(url, options?.extraQueryUrlParams);
89
135
  delete options.path;
90
- const request = await fetch(url.href, options);
136
+ delete options.extraQueryUrlParams;
137
+ const request = await fetch(urlToUse, options);
91
138
  this.calls++;
92
139
  return { request, options };
93
140
  }
94
141
  /**
95
- * 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
96
143
  * @param endpoint The endpoint that we will make the call to
97
144
  * @param modify Used to modify the request before being sent
98
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
+ * ```
99
151
  */
100
152
  async request(endpoint, modify, parseAsText = false) {
101
153
  const { request, options } = await this.rawRequest(endpoint, modify);
@@ -109,9 +161,17 @@ class LavalinkNode {
109
161
  * Search something raw on the node, please note only add tracks to players of that node
110
162
  * @param query SearchQuery Object
111
163
  * @param requestUser Request User for creating the player(s)
164
+ * @param throwOnEmpty Wether to throw on an empty result or not
112
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
+ * ```
113
173
  */
114
- async search(query, requestUser) {
174
+ async search(query, requestUser, throwOnEmpty = false) {
115
175
  const Query = this.NodeManager.LavalinkManager.utils.transformQuery(query);
116
176
  this.NodeManager.LavalinkManager.utils.validateQueryString(this, Query.query, Query.source);
117
177
  if (Query.source)
@@ -121,19 +181,25 @@ class LavalinkNode {
121
181
  }
122
182
  let uri = `/loadtracks?identifier=`;
123
183
  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));
184
+ uri += encodeURIComponent(Query.query);
125
185
  }
126
186
  else { // if not make a query out of it
127
187
  if (Query.source !== "local")
128
188
  uri += `${Query.source}:`; // only add the query source string if it's not a local track
129
189
  if (Query.source === "ftts")
130
- uri += `//${encodeURIComponent(encodeURI(decodeURIComponent(Query.query)))}`;
190
+ uri += `//${encodeURIComponent(Query.query)}`;
131
191
  else
132
- uri += encodeURIComponent(decodeURIComponent(Query.query));
192
+ uri += encodeURIComponent(Query.query);
133
193
  }
134
- 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
+ });
135
199
  // transform the data which can be Error, Track or Track[] to enfore [Track]
136
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");
137
203
  return {
138
204
  loadType: res.loadType,
139
205
  exception: res.loadType === "error" ? res.data : null,
@@ -150,6 +216,19 @@ class LavalinkNode {
150
216
  tracks: (resTracks.length ? resTracks.map(t => this.NodeManager.LavalinkManager.utils.buildTrack(t, requestUser)) : [])
151
217
  };
152
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
+ */
153
232
  async lavaSearch(query, requestUser, throwOnEmpty = false) {
154
233
  const Query = this.NodeManager.LavalinkManager.utils.transformLavaSearchQuery(query);
155
234
  if (Query.source)
@@ -163,9 +242,9 @@ class LavalinkNode {
163
242
  if (!this.info.plugins.find(v => v.name === "lavasrc-plugin"))
164
243
  throw new RangeError(`there is no lavasrc-plugin available in the lavalink node: ${this.id}`);
165
244
  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)
167
- throw new Error("Nothing found");
168
245
  const res = (request.status === 204 ? {} : await request.json());
246
+ if (throwOnEmpty === true && !Object.entries(res).flat().filter(Boolean).length)
247
+ throw new Error("Nothing found");
169
248
  return {
170
249
  tracks: res.tracks?.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) || [],
171
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)) })) || [],
@@ -177,8 +256,14 @@ class LavalinkNode {
177
256
  }
178
257
  /**
179
258
  * Update the Player State on the Lavalink Server
180
- * @param data
181
- * @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
+ * ```
182
267
  */
183
268
  async updatePlayer(data) {
184
269
  if (!this.sessionId)
@@ -200,7 +285,13 @@ class LavalinkNode {
200
285
  /**
201
286
  * Destroys the Player on the Lavalink Server
202
287
  * @param guildId
203
- * @returns
288
+ * @returns request result
289
+ *
290
+ * @example
291
+ * ```ts
292
+ * // use player.destroy() instead
293
+ * player.node.destroyPlayer(player.guildId);
294
+ * ```
204
295
  */
205
296
  async destroyPlayer(guildId) {
206
297
  if (!this.sessionId)
@@ -210,7 +301,15 @@ class LavalinkNode {
210
301
  /**
211
302
  * Connect to the Lavalink Node
212
303
  * @param sessionId Provide the Session Id of the previous connection, to resume the node and it's player(s)
213
- * @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
+ * ```
214
313
  */
215
314
  connect(sessionId) {
216
315
  if (this.connected)
@@ -230,20 +329,37 @@ class LavalinkNode {
230
329
  this.socket.on("message", this.message.bind(this));
231
330
  this.socket.on("error", this.error.bind(this));
232
331
  }
233
- /** 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
+ */
234
341
  get id() {
235
342
  return this.options.id || `${this.options.host}:${this.options.port}`;
236
343
  }
237
344
  /**
238
345
  * Destroys the Node-Connection (Websocket) and all player's of the node
239
- * @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
+ * ```
240
354
  */
241
355
  destroy(destroyReason, deleteNode = true) {
242
356
  if (!this.connected)
243
357
  return;
244
- const players = this.NodeManager.LavalinkManager.players.filter(p => p.node.id == this.id);
358
+ const players = this.NodeManager.LavalinkManager.players.filter(p => p.node.id === this.id);
245
359
  if (players)
246
- players.forEach(p => p.destroy(destroyReason || Player_1.DestroyReasons.NodeDestroy));
360
+ players.forEach(p => {
361
+ p.destroy(destroyReason || Player_1.DestroyReasons.NodeDestroy);
362
+ });
247
363
  this.socket.close(1000, "Node-Destroy");
248
364
  this.socket.removeAllListeners();
249
365
  this.socket = null;
@@ -258,14 +374,47 @@ class LavalinkNode {
258
374
  }
259
375
  return;
260
376
  }
261
- /** Returns if connected to the Node. */
377
+ /**
378
+ * Returns if connected to the Node.
379
+ *
380
+ * @example
381
+ * ```ts
382
+ * const isConnected = player.node.connected;
383
+ * console.log("node is connected: ", isConnected ? "yes" : "no")
384
+ * ```
385
+ */
262
386
  get connected() {
263
387
  if (!this.socket)
264
388
  return false;
265
389
  return this.socket.readyState === ws_1.default.OPEN;
266
390
  }
391
+ /**
392
+ * Returns the current ConnectionStatus
393
+ *
394
+ * @example
395
+ * ```ts
396
+ * try {
397
+ * const statusOfConnection = player.node.connectionStatus;
398
+ * console.log("node's connection status is:", statusOfConnection)
399
+ * } catch (error) {
400
+ * console.error("no socket available?", error)
401
+ * }
402
+ * ```
403
+ */
404
+ get connectionStatus() {
405
+ if (!this.socket)
406
+ throw new Error("no websocket was initialized yet");
407
+ return ["CONNECTING", "OPEN", "CLOSING", "CLOSED"][this.socket.readyState] || "UNKNOWN";
408
+ }
267
409
  /**
268
410
  * Gets all Players of a Node
411
+ * @returns array of players inside of lavalink
412
+ *
413
+ * @example
414
+ * ```ts
415
+ * const node = lavalink.nodes.get("NODEID");
416
+ * const playersOfLavalink = await node?.fetchAllPlayers();
417
+ * ```
269
418
  */
270
419
  async fetchAllPlayers() {
271
420
  if (!this.sessionId)
@@ -278,6 +427,13 @@ class LavalinkNode {
278
427
  }
279
428
  /**
280
429
  * Gets specific Player Information
430
+ * @returns lavalink player object if player exists on lavalink
431
+ *
432
+ * @example
433
+ * ```ts
434
+ * const node = lavalink.nodes.get("NODEID");
435
+ * const playerInformation = await node?.fetchPlayer("guildId");
436
+ * ```
281
437
  */
282
438
  async fetchPlayer(guildId) {
283
439
  if (!this.sessionId)
@@ -288,6 +444,13 @@ class LavalinkNode {
288
444
  * Updates the session with and enables/disables resuming and timeout
289
445
  * @param resuming Whether resuming is enabled for this session or not
290
446
  * @param timeout The timeout in seconds (default is 60s)
447
+ * @returns the result of the request
448
+ *
449
+ * @example
450
+ * ```ts
451
+ * const node = player.node || lavalink.nodes.get("NODEID");
452
+ * await node?.updateSession(true, 180e3); // will enable resuming for 180seconds
453
+ * ```
291
454
  */
292
455
  async updateSession(resuming, timeout) {
293
456
  if (!this.sessionId)
@@ -312,20 +475,35 @@ class LavalinkNode {
312
475
  */
313
476
  decode = {
314
477
  /**
315
- * Decode a single track into its info, where BASE64 is the encoded base64 data.
316
- * @param encoded
317
- * @returns
478
+ * Decode a single track into its info
479
+ * @param encoded valid encoded base64 string from a track
480
+ * @param requester the requesteruser for building the track
481
+ * @returns decoded track from lavalink
482
+ *
483
+ * @example
484
+ * ```ts
485
+ * const encodedBase64 = 'QAACDgMACk5vIERpZ2dpdHkAC0JsYWNrc3RyZWV0AAAAAAAEo4AABjkxNjQ5NgABAB9odHRwczovL2RlZXplci5jb20vdHJhY2svOTE2NDk2AQBpaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvY292ZXIvZGFlN2EyNjViNzlmYjcxMjc4Y2RlMjUwNDg0OWQ2ZjcvMTAwMHgxMDAwLTAwMDAwMC04MC0wLTAuanBnAQAMVVNJUjE5NjAwOTc4AAZkZWV6ZXIBAChObyBEaWdnaXR5OiBUaGUgVmVyeSBCZXN0IE9mIEJsYWNrc3RyZWV0AQAjaHR0cHM6Ly93d3cuZGVlemVyLmNvbS9hbGJ1bS8xMDMyNTQBACJodHRwczovL3d3dy5kZWV6ZXIuY29tL2FydGlzdC8xODYxAQBqaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvYXJ0aXN0L2YxNmNhYzM2ZmVjMzkxZjczN2I3ZDQ4MmY1YWM3M2UzLzEwMDB4MTAwMC0wMDAwMDAtODAtMC0wLmpwZwEAT2h0dHBzOi8vY2RuLXByZXZpZXctYS5kemNkbi5uZXQvc3RyZWFtL2MtYTE1Yjg1NzFhYTYyMDBjMDQ0YmY1OWM3NmVkOTEyN2MtNi5tcDMAAAAAAAAAAAA=';
486
+ * const track = await player.node.decode.singleTrack(encodedBase64, interaction.user);
487
+ * ```
318
488
  */
319
489
  singleTrack: async (encoded, requester) => {
320
490
  if (!encoded)
321
491
  throw new SyntaxError("No encoded (Base64 string) was provided");
322
492
  // return the decoded + builded track
323
- return this.NodeManager.LavalinkManager.utils.buildTrack(await this.request(`/decodetrack?encodedTrack=${encoded}`), requester);
493
+ return this.NodeManager.LavalinkManager.utils?.buildTrack(await this.request(`/decodetrack?encodedTrack=${encodeURIComponent(encoded.replace(/\s/g, ""))}`), requester);
324
494
  },
325
495
  /**
496
+ * Decodes multiple tracks into their info
497
+ * @param encodeds valid encoded base64 string array from all tracks
498
+ * @param requester the requesteruser for building the tracks
499
+ * @returns array of all tracks you decoded
326
500
  *
327
- * @param encodeds Decodes multiple tracks into their info
328
- * @returns
501
+ * @example
502
+ * ```ts
503
+ * const encodedBase64_1 = 'QAACDgMACk5vIERpZ2dpdHkAC0JsYWNrc3RyZWV0AAAAAAAEo4AABjkxNjQ5NgABAB9odHRwczovL2RlZXplci5jb20vdHJhY2svOTE2NDk2AQBpaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvY292ZXIvZGFlN2EyNjViNzlmYjcxMjc4Y2RlMjUwNDg0OWQ2ZjcvMTAwMHgxMDAwLTAwMDAwMC04MC0wLTAuanBnAQAMVVNJUjE5NjAwOTc4AAZkZWV6ZXIBAChObyBEaWdnaXR5OiBUaGUgVmVyeSBCZXN0IE9mIEJsYWNrc3RyZWV0AQAjaHR0cHM6Ly93d3cuZGVlemVyLmNvbS9hbGJ1bS8xMDMyNTQBACJodHRwczovL3d3dy5kZWV6ZXIuY29tL2FydGlzdC8xODYxAQBqaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvYXJ0aXN0L2YxNmNhYzM2ZmVjMzkxZjczN2I3ZDQ4MmY1YWM3M2UzLzEwMDB4MTAwMC0wMDAwMDAtODAtMC0wLmpwZwEAT2h0dHBzOi8vY2RuLXByZXZpZXctYS5kemNkbi5uZXQvc3RyZWFtL2MtYTE1Yjg1NzFhYTYyMDBjMDQ0YmY1OWM3NmVkOTEyN2MtNi5tcDMAAAAAAAAAAAA=';
504
+ * const encodedBase64_2 = 'QAABJAMAClRhbGsgYSBMb3QACjQwNHZpbmNlbnQAAAAAAAHr1gBxTzpodHRwczovL2FwaS12Mi5zb3VuZGNsb3VkLmNvbS9tZWRpYS9zb3VuZGNsb3VkOnRyYWNrczo4NTE0MjEwNzYvMzUyYTRiOTAtNzYxOS00M2E5LWJiOGItMjIxMzE0YzFjNjNhL3N0cmVhbS9obHMAAQAsaHR0cHM6Ly9zb3VuZGNsb3VkLmNvbS80MDR2aW5jZW50L3RhbGstYS1sb3QBADpodHRwczovL2kxLnNuZGNkbi5jb20vYXJ0d29ya3MtRTN1ek5Gc0Y4QzBXLTAtb3JpZ2luYWwuanBnAQAMUVpITkExOTg1Nzg0AApzb3VuZGNsb3VkAAAAAAAAAAA=';
505
+ * const tracks = await player.node.decode.multipleTracks([encodedBase64_1, encodedBase64_2], interaction.user);
506
+ * ```
329
507
  */
330
508
  multipleTracks: async (encodeds, requester) => {
331
509
  if (!Array.isArray(encodeds) || !encodeds.every(v => typeof v === "string" && v.length > 1))
@@ -341,14 +519,24 @@ class LavalinkNode {
341
519
  };
342
520
  /**
343
521
  * Request Lavalink statistics.
344
- * @returns
522
+ * @returns the lavalink node stats
523
+ *
524
+ * @example
525
+ * ```ts
526
+ * const lavalinkStats = await player.node.fetchStats();
527
+ * ```
345
528
  */
346
529
  async fetchStats() {
347
530
  return await this.request(`/stats`);
348
531
  }
349
532
  /**
350
533
  * Request Lavalink version.
351
- * @returns
534
+ * @returns the current used lavalink version
535
+ *
536
+ * @example
537
+ * ```ts
538
+ * const lavalinkVersion = await player.node.fetchVersion();
539
+ * ```
352
540
  */
353
541
  async fetchVersion() {
354
542
  // need to adjust path for no-prefix version info
@@ -356,7 +544,14 @@ class LavalinkNode {
356
544
  }
357
545
  /**
358
546
  * Request Lavalink information.
359
- * @returns
547
+ * @returns lavalink info object
548
+ *
549
+ * @example
550
+ * ```ts
551
+ * const lavalinkInfo = await player.node.fetchInfo();
552
+ * const availablePlugins:string[] = lavalinkInfo.plugins.map(plugin => plugin.name);
553
+ * const availableSources:string[] = lavalinkInfo.sourceManagers;
554
+ * ```
360
555
  */
361
556
  async fetchInfo() {
362
557
  return await this.request(`/info`);
@@ -366,7 +561,15 @@ class LavalinkNode {
366
561
  */
367
562
  routePlannerApi = {
368
563
  /**
369
- * Get routplanner Info from Lavalink
564
+ * Get routplanner Info from Lavalink for ip rotation
565
+ * @returns the status of the routeplanner
566
+ *
567
+ * @example
568
+ * ```ts
569
+ * const routePlannerStatus = await player.node.routePlannerApi.getStatus();
570
+ * const usedBlock = routePlannerStatus.details?.ipBlock;
571
+ * const currentIp = routePlannerStatus.currentAddress;
572
+ * ```
370
573
  */
371
574
  getStatus: async () => {
372
575
  if (!this.sessionId)
@@ -374,8 +577,14 @@ class LavalinkNode {
374
577
  return await this.request(`/routeplanner/status`);
375
578
  },
376
579
  /**
377
- * Release blacklisted IP address into pool of IPs
580
+ * Release blacklisted IP address into pool of IPs for ip rotation
378
581
  * @param address IP address
582
+ * @returns request data of the request
583
+ *
584
+ * @example
585
+ * ```ts
586
+ * await player.node.routePlannerApi.unmarkFailedAddress("ipv6address");
587
+ * ```
379
588
  */
380
589
  unmarkFailedAddress: async (address) => {
381
590
  if (!this.sessionId)
@@ -389,6 +598,12 @@ class LavalinkNode {
389
598
  },
390
599
  /**
391
600
  * Release all blacklisted IP addresses into pool of IPs
601
+ * @returns request data of the request
602
+ *
603
+ * @example
604
+ * ```ts
605
+ * await player.node.routePlannerApi.unmarkAllFailedAddresses();
606
+ * ```
392
607
  */
393
608
  unmarkAllFailedAddresses: async () => {
394
609
  if (!this.sessionId)
@@ -400,7 +615,7 @@ class LavalinkNode {
400
615
  });
401
616
  }
402
617
  };
403
- /** Private Utils */
618
+ /** @private Utils for validating the */
404
619
  validate() {
405
620
  if (!this.options.authorization)
406
621
  throw new SyntaxError("LavalinkNode requires 'authorization'");
@@ -409,6 +624,12 @@ class LavalinkNode {
409
624
  if (!this.options.port)
410
625
  throw new SyntaxError("LavalinkNode requires 'port'");
411
626
  }
627
+ /**
628
+ * Sync the data of the player you make an action to lavalink to
629
+ * @param data data to use to update the player
630
+ * @param res result data from lavalink, to override, if available
631
+ * @returns boolean
632
+ */
412
633
  syncPlayerData(data, res) {
413
634
  if (typeof data === "object" && typeof data?.guildId === "string" && typeof data.playerOptions === "object" && Object.keys(data.playerOptions).length > 1) {
414
635
  const player = this.NodeManager.LavalinkManager.getPlayer(data.guildId);
@@ -419,8 +640,9 @@ class LavalinkNode {
419
640
  player.playing = !data.playerOptions.paused;
420
641
  }
421
642
  if (typeof data.playerOptions.position === "number") {
422
- player.position = data.playerOptions.position;
643
+ // player.position = data.playerOptions.position;
423
644
  player.lastPosition = data.playerOptions.position;
645
+ player.lastPositionChange = Date.now();
424
646
  }
425
647
  if (typeof data.playerOptions.voice !== "undefined")
426
648
  player.voice = data.playerOptions.voice;
@@ -471,9 +693,22 @@ class LavalinkNode {
471
693
  }
472
694
  return true;
473
695
  }
696
+ /**
697
+ * Get the rest Adress for making requests
698
+ */
474
699
  get restAddress() {
475
700
  return `http${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}`;
476
701
  }
702
+ /**
703
+ * Reconnect to the lavalink node
704
+ * @param instaReconnect @default false wether to instantly try to reconnect
705
+ * @returns void
706
+ *
707
+ * @example
708
+ * ```ts
709
+ * await player.node.reconnect();
710
+ * ```
711
+ */
477
712
  reconnect(instaReconnect = false) {
478
713
  if (instaReconnect) {
479
714
  if (this.reconnectAttempts >= this.options.retryAmount) {
@@ -501,6 +736,7 @@ class LavalinkNode {
501
736
  this.reconnectAttempts++;
502
737
  }, this.options.retryDelay || 1000);
503
738
  }
739
+ /** @private util function for handling opening events from websocket */
504
740
  async open() {
505
741
  if (this.reconnectTimeout)
506
742
  clearTimeout(this.reconnectTimeout);
@@ -513,16 +749,19 @@ class LavalinkNode {
513
749
  }
514
750
  this.NodeManager.emit("connect", this);
515
751
  }
752
+ /** @private util function for handling closing events from websocket */
516
753
  close(code, reason) {
517
754
  this.NodeManager.emit("disconnect", this, { code, reason });
518
755
  if (code !== 1000 || reason !== "Node-Destroy")
519
756
  this.reconnect();
520
757
  }
758
+ /** @private util function for handling error events from websocket */
521
759
  error(error) {
522
760
  if (!error)
523
761
  return;
524
762
  this.NodeManager.emit("error", this, error);
525
763
  }
764
+ /** @private util function for handling message events from websocket */
526
765
  async message(d) {
527
766
  if (Array.isArray(d))
528
767
  d = Buffer.concat(d);
@@ -543,42 +782,15 @@ class LavalinkNode {
543
782
  if (!player)
544
783
  return;
545
784
  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;
785
+ player.lastPositionChange = Date.now();
550
786
  player.lastPosition = payload.state.position || 0;
551
787
  player.connected = payload.state.connected;
552
788
  player.ping.ws = payload.state.ping >= 0 ? payload.state.ping : player.ping.ws <= 0 && player.connected ? null : player.ping.ws || 0;
553
789
  if (!player.createdTimeStamp && payload.state.time)
554
790
  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 || (0, path_1.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 || (0, path_1.isAbsolute)(player.queue.current?.info?.uri))
579
- player.seek(player.position);
580
- player.filterManager.filterUpdatedState = 0;
581
- }
791
+ 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))) {
792
+ player.filterManager.filterUpdatedState = false;
793
+ await player.seek(player.position);
582
794
  }
583
795
  this.NodeManager.LavalinkManager.emit("playerUpdate", oldPlayer, player);
584
796
  }
@@ -598,7 +810,7 @@ class LavalinkNode {
598
810
  return;
599
811
  }
600
812
  }
601
- // LAVALINK EVENT HANDLING UTIL FUNCTION
813
+ /** @private middleware util function for handling all kind of events from websocket */
602
814
  async handleEvent(payload) {
603
815
  if (!payload.guildId)
604
816
  return;
@@ -625,7 +837,7 @@ class LavalinkNode {
625
837
  this.SponsorBlockSegmentLoaded(player, player.queue.current, payload);
626
838
  break;
627
839
  case "SegmentSkipped":
628
- this.SponsorBlockSegmentkipped(player, player.queue.current, payload);
840
+ this.SponsorBlockSegmentSkipped(player, player.queue.current, payload);
629
841
  break;
630
842
  case "ChaptersLoaded":
631
843
  this.SponsorBlockChaptersLoaded(player, player.queue.current, payload);
@@ -639,7 +851,7 @@ class LavalinkNode {
639
851
  }
640
852
  return;
641
853
  }
642
- // LAVALINK EVENT HANDLING FUNCTIONS
854
+ /** @private util function for handling trackStart event */
643
855
  trackStart(player, track, payload) {
644
856
  player.playing = true;
645
857
  player.paused = false;
@@ -648,6 +860,7 @@ class LavalinkNode {
648
860
  return;
649
861
  return this.NodeManager.LavalinkManager.emit("trackStart", player, track, payload);
650
862
  }
863
+ /** @private util function for handling trackEnd event */
651
864
  async trackEnd(player, track, payload) {
652
865
  // If there are no songs in the queue
653
866
  if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying")))
@@ -669,7 +882,7 @@ class LavalinkNode {
669
882
  // remove tracks from the queue
670
883
  if (player.repeatMode !== "track" || player.get("internal_skipped"))
671
884
  await (0, Utils_1.queueTrackEnd)(player);
672
- else if (player.queue.current) { // If there was a current Track already and repeatmode === true, add it to the queue.
885
+ else if (player.queue.current && !player.queue.current?.pluginInfo?.clientData?.previousTrack) { // If there was a current Track already and repeatmode === true, add it to the queue.
673
886
  player.queue.previous.unshift(player.queue.current);
674
887
  if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
675
888
  player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
@@ -684,6 +897,7 @@ class LavalinkNode {
684
897
  // play track if autoSkip is true
685
898
  return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ noReplace: true });
686
899
  }
900
+ /** @private util function for handling trackStuck event */
687
901
  async trackStuck(player, track, payload) {
688
902
  this.NodeManager.LavalinkManager.emit("trackStuck", player, track, payload);
689
903
  // If there are no songs in the queue
@@ -697,6 +911,7 @@ class LavalinkNode {
697
911
  // play track if autoSkip is true
698
912
  return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
699
913
  }
914
+ /** @private util function for handling trackError event */
700
915
  async trackError(player, track, payload) {
701
916
  this.NodeManager.LavalinkManager.emit("trackError", player, track, payload);
702
917
  return; // get's handled by trackEnd
@@ -711,23 +926,37 @@ class LavalinkNode {
711
926
  // play track if autoSkip is true
712
927
  return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
713
928
  }
929
+ /** @private util function for handling socketClosed event */
714
930
  socketClosed(player, payload) {
715
931
  return this.NodeManager.LavalinkManager.emit("playerSocketClosed", player, payload);
716
932
  }
717
- // SPONSOR BLOCK EVENT FUNCTIONS
933
+ /** @private util function for handling SponsorBlock Segmentloaded event */
718
934
  SponsorBlockSegmentLoaded(player, track, payload) {
719
935
  return this.NodeManager.LavalinkManager.emit("SegmentsLoaded", player, track, payload);
720
936
  }
721
- SponsorBlockSegmentkipped(player, track, payload) {
937
+ /** @private util function for handling SponsorBlock SegmentSkipped event */
938
+ SponsorBlockSegmentSkipped(player, track, payload) {
722
939
  return this.NodeManager.LavalinkManager.emit("SegmentSkipped", player, track, payload);
723
940
  }
941
+ /** @private util function for handling SponsorBlock Chaptersloaded event */
724
942
  SponsorBlockChaptersLoaded(player, track, payload) {
725
943
  return this.NodeManager.LavalinkManager.emit("ChaptersLoaded", player, track, payload);
726
944
  }
945
+ /** @private util function for handling SponsorBlock Chaptersstarted event */
727
946
  SponsorBlockChapterStarted(player, track, payload) {
728
947
  return this.NodeManager.LavalinkManager.emit("ChapterStarted", player, track, payload);
729
948
  }
730
- // SPONSOR BLOCK EXECUTE FUNCTIONS
949
+ /**
950
+ * Get the current sponsorblocks for the sponsorblock plugin
951
+ * @param player passthrough the player
952
+ * @returns sponsorblock seggment from lavalink
953
+ *
954
+ * @example
955
+ * ```ts
956
+ * // use it on the player via player.getSponsorBlock();
957
+ * const sponsorBlockSegments = await player.node.getSponsorBlock(player);
958
+ * ```
959
+ */
731
960
  async getSponsorBlock(player) {
732
961
  // no plugin enabled
733
962
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -735,6 +964,17 @@ class LavalinkNode {
735
964
  // do the request
736
965
  return await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`);
737
966
  }
967
+ /**
968
+ * Set the current sponsorblocks for the sponsorblock plugin
969
+ * @param player passthrough the player
970
+ * @returns void
971
+ *
972
+ * @example
973
+ * ```ts
974
+ * // use it on the player via player.setSponsorBlock();
975
+ * const sponsorBlockSegments = await player.node.setSponsorBlock(player, ["sponsor", "selfpromo"]);
976
+ * ```
977
+ */
738
978
  async setSponsorBlock(player, segments = ["sponsor", "selfpromo"]) {
739
979
  // no plugin enabled
740
980
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -753,6 +993,17 @@ class LavalinkNode {
753
993
  });
754
994
  return;
755
995
  }
996
+ /**
997
+ * Delete the sponsorblock plugins
998
+ * @param player passthrough the player
999
+ * @returns void
1000
+ *
1001
+ * @example
1002
+ * ```ts
1003
+ * // use it on the player via player.deleteSponsorBlock();
1004
+ * const sponsorBlockSegments = await player.node.deleteSponsorBlock(player);
1005
+ * ```
1006
+ */
756
1007
  async deleteSponsorBlock(player) {
757
1008
  // no plugin enabled
758
1009
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -763,7 +1014,7 @@ class LavalinkNode {
763
1014
  });
764
1015
  return;
765
1016
  }
766
- // UTIL FOR QUEUE END
1017
+ /** private util function for handling the queue end event */
767
1018
  async queueEnd(player, track, payload) {
768
1019
  // add previous track to the queue!
769
1020
  player.queue.current = null;
@@ -780,7 +1031,12 @@ class LavalinkNode {
780
1031
  }
781
1032
  }
782
1033
  player.set("internal_autoplayStopPlaying", undefined);
783
- player.queue.previous.unshift(track);
1034
+ if (track && !track?.pluginInfo?.clientData?.previousTrack) { // If there was a current Track already and repeatmode === true, add it to the queue.
1035
+ player.queue.previous.unshift(track);
1036
+ if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
1037
+ player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
1038
+ await player.queue.utils.save();
1039
+ }
784
1040
  if (payload?.reason !== "stopped") {
785
1041
  await player.queue.utils.save();
786
1042
  }