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
@@ -3,11 +3,15 @@ import WebSocket from "ws";
3
3
  import { DestroyReasons } from "./Player";
4
4
  import { NodeSymbol, queueTrackEnd } from "./Utils";
5
5
  export const validSponsorBlocks = ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "filler"];
6
+ /**
7
+ * Lavalink Node creator class
8
+ */
6
9
  export class LavalinkNode {
7
10
  /** The provided Options of the Node */
8
11
  options;
9
12
  /** The amount of rest calls the node has made. */
10
13
  calls = 0;
14
+ /** Stats from lavalink, will be updated via an interval by lavalink. */
11
15
  stats = {
12
16
  players: 0,
13
17
  playingPlayers: 0,
@@ -29,6 +33,7 @@ export class LavalinkNode {
29
33
  sent: 0,
30
34
  }
31
35
  };
36
+ /** The current sessionId, only present when connected */
32
37
  sessionId = null;
33
38
  /** Wether the node resuming is enabled or not */
34
39
  resuming = { enabled: true, timeout: null };
@@ -48,6 +53,14 @@ export class LavalinkNode {
48
53
  * Create a new Node
49
54
  * @param options Lavalink Node Options
50
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
+ * ```
51
64
  */
52
65
  constructor(options, manager) {
53
66
  this.options = {
@@ -64,11 +77,43 @@ export class LavalinkNode {
64
77
  this.options.regions = (this.options.regions || []).map(a => a.toLowerCase());
65
78
  Object.defineProperty(this, NodeSymbol, { configurable: true, value: true });
66
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
+ }
67
106
  /**
68
107
  * Raw Request util function
69
108
  * @param endpoint endpoint string
70
109
  * @param modify modify the request
71
- * @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
+ * ```
72
117
  */
73
118
  async rawRequest(endpoint, modify) {
74
119
  const options = {
@@ -82,16 +127,23 @@ export class LavalinkNode {
82
127
  modify?.(options);
83
128
  const url = new URL(`${this.restAddress}${options.path}`);
84
129
  url.searchParams.append("trace", "true");
130
+ const urlToUse = this.getRequestingUrl(url, options?.extraQueryUrlParams);
85
131
  delete options.path;
86
- const request = await fetch(url.href, options);
132
+ delete options.extraQueryUrlParams;
133
+ const request = await fetch(urlToUse, options);
87
134
  this.calls++;
88
135
  return { request, options };
89
136
  }
90
137
  /**
91
- * 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
92
139
  * @param endpoint The endpoint that we will make the call to
93
140
  * @param modify Used to modify the request before being sent
94
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
+ * ```
95
147
  */
96
148
  async request(endpoint, modify, parseAsText = false) {
97
149
  const { request, options } = await this.rawRequest(endpoint, modify);
@@ -105,9 +157,17 @@ export class LavalinkNode {
105
157
  * Search something raw on the node, please note only add tracks to players of that node
106
158
  * @param query SearchQuery Object
107
159
  * @param requestUser Request User for creating the player(s)
160
+ * @param throwOnEmpty Wether to throw on an empty result or not
108
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
+ * ```
109
169
  */
110
- async search(query, requestUser) {
170
+ async search(query, requestUser, throwOnEmpty = false) {
111
171
  const Query = this.NodeManager.LavalinkManager.utils.transformQuery(query);
112
172
  this.NodeManager.LavalinkManager.utils.validateQueryString(this, Query.query, Query.source);
113
173
  if (Query.source)
@@ -117,19 +177,25 @@ export class LavalinkNode {
117
177
  }
118
178
  let uri = `/loadtracks?identifier=`;
119
179
  if (/^https?:\/\//.test(Query.query) || ["http", "https", "link", "uri"].includes(Query.source)) { // if it's a link simply encode it
120
- uri += encodeURIComponent(decodeURIComponent(Query.query));
180
+ uri += encodeURIComponent(Query.query);
121
181
  }
122
182
  else { // if not make a query out of it
123
183
  if (Query.source !== "local")
124
184
  uri += `${Query.source}:`; // only add the query source string if it's not a local track
125
185
  if (Query.source === "ftts")
126
- uri += `//${encodeURIComponent(encodeURI(decodeURIComponent(Query.query)))}`;
186
+ uri += `//${encodeURIComponent(Query.query)}`;
127
187
  else
128
- uri += encodeURIComponent(decodeURIComponent(Query.query));
188
+ uri += encodeURIComponent(Query.query);
129
189
  }
130
- 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
+ });
131
195
  // transform the data which can be Error, Track or Track[] to enfore [Track]
132
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");
133
199
  return {
134
200
  loadType: res.loadType,
135
201
  exception: res.loadType === "error" ? res.data : null,
@@ -146,6 +212,19 @@ export class LavalinkNode {
146
212
  tracks: (resTracks.length ? resTracks.map(t => this.NodeManager.LavalinkManager.utils.buildTrack(t, requestUser)) : [])
147
213
  };
148
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
+ */
149
228
  async lavaSearch(query, requestUser, throwOnEmpty = false) {
150
229
  const Query = this.NodeManager.LavalinkManager.utils.transformLavaSearchQuery(query);
151
230
  if (Query.source)
@@ -159,9 +238,9 @@ export class LavalinkNode {
159
238
  if (!this.info.plugins.find(v => v.name === "lavasrc-plugin"))
160
239
  throw new RangeError(`there is no lavasrc-plugin available in the lavalink node: ${this.id}`);
161
240
  const { request } = await this.rawRequest(`/loadsearch?query=${Query.source ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}${Query.types?.length ? `&types=${Query.types.join(",")}` : ""}`);
162
- if (throwOnEmpty === true)
163
- throw new Error("Nothing found");
164
241
  const res = (request.status === 204 ? {} : await request.json());
242
+ if (throwOnEmpty === true && !Object.entries(res).flat().filter(Boolean).length)
243
+ throw new Error("Nothing found");
165
244
  return {
166
245
  tracks: res.tracks?.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) || [],
167
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)) })) || [],
@@ -173,8 +252,14 @@ export class LavalinkNode {
173
252
  }
174
253
  /**
175
254
  * Update the Player State on the Lavalink Server
176
- * @param data
177
- * @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
+ * ```
178
263
  */
179
264
  async updatePlayer(data) {
180
265
  if (!this.sessionId)
@@ -196,7 +281,13 @@ export class LavalinkNode {
196
281
  /**
197
282
  * Destroys the Player on the Lavalink Server
198
283
  * @param guildId
199
- * @returns
284
+ * @returns request result
285
+ *
286
+ * @example
287
+ * ```ts
288
+ * // use player.destroy() instead
289
+ * player.node.destroyPlayer(player.guildId);
290
+ * ```
200
291
  */
201
292
  async destroyPlayer(guildId) {
202
293
  if (!this.sessionId)
@@ -206,7 +297,15 @@ export class LavalinkNode {
206
297
  /**
207
298
  * Connect to the Lavalink Node
208
299
  * @param sessionId Provide the Session Id of the previous connection, to resume the node and it's player(s)
209
- * @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
+ * ```
210
309
  */
211
310
  connect(sessionId) {
212
311
  if (this.connected)
@@ -226,20 +325,37 @@ export class LavalinkNode {
226
325
  this.socket.on("message", this.message.bind(this));
227
326
  this.socket.on("error", this.error.bind(this));
228
327
  }
229
- /** 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
+ */
230
337
  get id() {
231
338
  return this.options.id || `${this.options.host}:${this.options.port}`;
232
339
  }
233
340
  /**
234
341
  * Destroys the Node-Connection (Websocket) and all player's of the node
235
- * @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
+ * ```
236
350
  */
237
351
  destroy(destroyReason, deleteNode = true) {
238
352
  if (!this.connected)
239
353
  return;
240
- const players = this.NodeManager.LavalinkManager.players.filter(p => p.node.id == this.id);
354
+ const players = this.NodeManager.LavalinkManager.players.filter(p => p.node.id === this.id);
241
355
  if (players)
242
- players.forEach(p => p.destroy(destroyReason || DestroyReasons.NodeDestroy));
356
+ players.forEach(p => {
357
+ p.destroy(destroyReason || DestroyReasons.NodeDestroy);
358
+ });
243
359
  this.socket.close(1000, "Node-Destroy");
244
360
  this.socket.removeAllListeners();
245
361
  this.socket = null;
@@ -254,14 +370,47 @@ export class LavalinkNode {
254
370
  }
255
371
  return;
256
372
  }
257
- /** Returns if connected to the Node. */
373
+ /**
374
+ * Returns if connected to the Node.
375
+ *
376
+ * @example
377
+ * ```ts
378
+ * const isConnected = player.node.connected;
379
+ * console.log("node is connected: ", isConnected ? "yes" : "no")
380
+ * ```
381
+ */
258
382
  get connected() {
259
383
  if (!this.socket)
260
384
  return false;
261
385
  return this.socket.readyState === WebSocket.OPEN;
262
386
  }
387
+ /**
388
+ * Returns the current ConnectionStatus
389
+ *
390
+ * @example
391
+ * ```ts
392
+ * try {
393
+ * const statusOfConnection = player.node.connectionStatus;
394
+ * console.log("node's connection status is:", statusOfConnection)
395
+ * } catch (error) {
396
+ * console.error("no socket available?", error)
397
+ * }
398
+ * ```
399
+ */
400
+ get connectionStatus() {
401
+ if (!this.socket)
402
+ throw new Error("no websocket was initialized yet");
403
+ return ["CONNECTING", "OPEN", "CLOSING", "CLOSED"][this.socket.readyState] || "UNKNOWN";
404
+ }
263
405
  /**
264
406
  * Gets all Players of a Node
407
+ * @returns array of players inside of lavalink
408
+ *
409
+ * @example
410
+ * ```ts
411
+ * const node = lavalink.nodes.get("NODEID");
412
+ * const playersOfLavalink = await node?.fetchAllPlayers();
413
+ * ```
265
414
  */
266
415
  async fetchAllPlayers() {
267
416
  if (!this.sessionId)
@@ -274,6 +423,13 @@ export class LavalinkNode {
274
423
  }
275
424
  /**
276
425
  * Gets specific Player Information
426
+ * @returns lavalink player object if player exists on lavalink
427
+ *
428
+ * @example
429
+ * ```ts
430
+ * const node = lavalink.nodes.get("NODEID");
431
+ * const playerInformation = await node?.fetchPlayer("guildId");
432
+ * ```
277
433
  */
278
434
  async fetchPlayer(guildId) {
279
435
  if (!this.sessionId)
@@ -284,6 +440,13 @@ export class LavalinkNode {
284
440
  * Updates the session with and enables/disables resuming and timeout
285
441
  * @param resuming Whether resuming is enabled for this session or not
286
442
  * @param timeout The timeout in seconds (default is 60s)
443
+ * @returns the result of the request
444
+ *
445
+ * @example
446
+ * ```ts
447
+ * const node = player.node || lavalink.nodes.get("NODEID");
448
+ * await node?.updateSession(true, 180e3); // will enable resuming for 180seconds
449
+ * ```
287
450
  */
288
451
  async updateSession(resuming, timeout) {
289
452
  if (!this.sessionId)
@@ -308,20 +471,35 @@ export class LavalinkNode {
308
471
  */
309
472
  decode = {
310
473
  /**
311
- * Decode a single track into its info, where BASE64 is the encoded base64 data.
312
- * @param encoded
313
- * @returns
474
+ * Decode a single track into its info
475
+ * @param encoded valid encoded base64 string from a track
476
+ * @param requester the requesteruser for building the track
477
+ * @returns decoded track from lavalink
478
+ *
479
+ * @example
480
+ * ```ts
481
+ * const encodedBase64 = 'QAACDgMACk5vIERpZ2dpdHkAC0JsYWNrc3RyZWV0AAAAAAAEo4AABjkxNjQ5NgABAB9odHRwczovL2RlZXplci5jb20vdHJhY2svOTE2NDk2AQBpaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvY292ZXIvZGFlN2EyNjViNzlmYjcxMjc4Y2RlMjUwNDg0OWQ2ZjcvMTAwMHgxMDAwLTAwMDAwMC04MC0wLTAuanBnAQAMVVNJUjE5NjAwOTc4AAZkZWV6ZXIBAChObyBEaWdnaXR5OiBUaGUgVmVyeSBCZXN0IE9mIEJsYWNrc3RyZWV0AQAjaHR0cHM6Ly93d3cuZGVlemVyLmNvbS9hbGJ1bS8xMDMyNTQBACJodHRwczovL3d3dy5kZWV6ZXIuY29tL2FydGlzdC8xODYxAQBqaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvYXJ0aXN0L2YxNmNhYzM2ZmVjMzkxZjczN2I3ZDQ4MmY1YWM3M2UzLzEwMDB4MTAwMC0wMDAwMDAtODAtMC0wLmpwZwEAT2h0dHBzOi8vY2RuLXByZXZpZXctYS5kemNkbi5uZXQvc3RyZWFtL2MtYTE1Yjg1NzFhYTYyMDBjMDQ0YmY1OWM3NmVkOTEyN2MtNi5tcDMAAAAAAAAAAAA=';
482
+ * const track = await player.node.decode.singleTrack(encodedBase64, interaction.user);
483
+ * ```
314
484
  */
315
485
  singleTrack: async (encoded, requester) => {
316
486
  if (!encoded)
317
487
  throw new SyntaxError("No encoded (Base64 string) was provided");
318
488
  // return the decoded + builded track
319
- return this.NodeManager.LavalinkManager.utils.buildTrack(await this.request(`/decodetrack?encodedTrack=${encoded}`), requester);
489
+ return this.NodeManager.LavalinkManager.utils?.buildTrack(await this.request(`/decodetrack?encodedTrack=${encodeURIComponent(encoded.replace(/\s/g, ""))}`), requester);
320
490
  },
321
491
  /**
492
+ * Decodes multiple tracks into their info
493
+ * @param encodeds valid encoded base64 string array from all tracks
494
+ * @param requester the requesteruser for building the tracks
495
+ * @returns array of all tracks you decoded
322
496
  *
323
- * @param encodeds Decodes multiple tracks into their info
324
- * @returns
497
+ * @example
498
+ * ```ts
499
+ * const encodedBase64_1 = 'QAACDgMACk5vIERpZ2dpdHkAC0JsYWNrc3RyZWV0AAAAAAAEo4AABjkxNjQ5NgABAB9odHRwczovL2RlZXplci5jb20vdHJhY2svOTE2NDk2AQBpaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvY292ZXIvZGFlN2EyNjViNzlmYjcxMjc4Y2RlMjUwNDg0OWQ2ZjcvMTAwMHgxMDAwLTAwMDAwMC04MC0wLTAuanBnAQAMVVNJUjE5NjAwOTc4AAZkZWV6ZXIBAChObyBEaWdnaXR5OiBUaGUgVmVyeSBCZXN0IE9mIEJsYWNrc3RyZWV0AQAjaHR0cHM6Ly93d3cuZGVlemVyLmNvbS9hbGJ1bS8xMDMyNTQBACJodHRwczovL3d3dy5kZWV6ZXIuY29tL2FydGlzdC8xODYxAQBqaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvYXJ0aXN0L2YxNmNhYzM2ZmVjMzkxZjczN2I3ZDQ4MmY1YWM3M2UzLzEwMDB4MTAwMC0wMDAwMDAtODAtMC0wLmpwZwEAT2h0dHBzOi8vY2RuLXByZXZpZXctYS5kemNkbi5uZXQvc3RyZWFtL2MtYTE1Yjg1NzFhYTYyMDBjMDQ0YmY1OWM3NmVkOTEyN2MtNi5tcDMAAAAAAAAAAAA=';
500
+ * const encodedBase64_2 = 'QAABJAMAClRhbGsgYSBMb3QACjQwNHZpbmNlbnQAAAAAAAHr1gBxTzpodHRwczovL2FwaS12Mi5zb3VuZGNsb3VkLmNvbS9tZWRpYS9zb3VuZGNsb3VkOnRyYWNrczo4NTE0MjEwNzYvMzUyYTRiOTAtNzYxOS00M2E5LWJiOGItMjIxMzE0YzFjNjNhL3N0cmVhbS9obHMAAQAsaHR0cHM6Ly9zb3VuZGNsb3VkLmNvbS80MDR2aW5jZW50L3RhbGstYS1sb3QBADpodHRwczovL2kxLnNuZGNkbi5jb20vYXJ0d29ya3MtRTN1ek5Gc0Y4QzBXLTAtb3JpZ2luYWwuanBnAQAMUVpITkExOTg1Nzg0AApzb3VuZGNsb3VkAAAAAAAAAAA=';
501
+ * const tracks = await player.node.decode.multipleTracks([encodedBase64_1, encodedBase64_2], interaction.user);
502
+ * ```
325
503
  */
326
504
  multipleTracks: async (encodeds, requester) => {
327
505
  if (!Array.isArray(encodeds) || !encodeds.every(v => typeof v === "string" && v.length > 1))
@@ -337,14 +515,24 @@ export class LavalinkNode {
337
515
  };
338
516
  /**
339
517
  * Request Lavalink statistics.
340
- * @returns
518
+ * @returns the lavalink node stats
519
+ *
520
+ * @example
521
+ * ```ts
522
+ * const lavalinkStats = await player.node.fetchStats();
523
+ * ```
341
524
  */
342
525
  async fetchStats() {
343
526
  return await this.request(`/stats`);
344
527
  }
345
528
  /**
346
529
  * Request Lavalink version.
347
- * @returns
530
+ * @returns the current used lavalink version
531
+ *
532
+ * @example
533
+ * ```ts
534
+ * const lavalinkVersion = await player.node.fetchVersion();
535
+ * ```
348
536
  */
349
537
  async fetchVersion() {
350
538
  // need to adjust path for no-prefix version info
@@ -352,7 +540,14 @@ export class LavalinkNode {
352
540
  }
353
541
  /**
354
542
  * Request Lavalink information.
355
- * @returns
543
+ * @returns lavalink info object
544
+ *
545
+ * @example
546
+ * ```ts
547
+ * const lavalinkInfo = await player.node.fetchInfo();
548
+ * const availablePlugins:string[] = lavalinkInfo.plugins.map(plugin => plugin.name);
549
+ * const availableSources:string[] = lavalinkInfo.sourceManagers;
550
+ * ```
356
551
  */
357
552
  async fetchInfo() {
358
553
  return await this.request(`/info`);
@@ -362,7 +557,15 @@ export class LavalinkNode {
362
557
  */
363
558
  routePlannerApi = {
364
559
  /**
365
- * Get routplanner Info from Lavalink
560
+ * Get routplanner Info from Lavalink for ip rotation
561
+ * @returns the status of the routeplanner
562
+ *
563
+ * @example
564
+ * ```ts
565
+ * const routePlannerStatus = await player.node.routePlannerApi.getStatus();
566
+ * const usedBlock = routePlannerStatus.details?.ipBlock;
567
+ * const currentIp = routePlannerStatus.currentAddress;
568
+ * ```
366
569
  */
367
570
  getStatus: async () => {
368
571
  if (!this.sessionId)
@@ -370,8 +573,14 @@ export class LavalinkNode {
370
573
  return await this.request(`/routeplanner/status`);
371
574
  },
372
575
  /**
373
- * Release blacklisted IP address into pool of IPs
576
+ * Release blacklisted IP address into pool of IPs for ip rotation
374
577
  * @param address IP address
578
+ * @returns request data of the request
579
+ *
580
+ * @example
581
+ * ```ts
582
+ * await player.node.routePlannerApi.unmarkFailedAddress("ipv6address");
583
+ * ```
375
584
  */
376
585
  unmarkFailedAddress: async (address) => {
377
586
  if (!this.sessionId)
@@ -385,6 +594,12 @@ export class LavalinkNode {
385
594
  },
386
595
  /**
387
596
  * Release all blacklisted IP addresses into pool of IPs
597
+ * @returns request data of the request
598
+ *
599
+ * @example
600
+ * ```ts
601
+ * await player.node.routePlannerApi.unmarkAllFailedAddresses();
602
+ * ```
388
603
  */
389
604
  unmarkAllFailedAddresses: async () => {
390
605
  if (!this.sessionId)
@@ -396,7 +611,7 @@ export class LavalinkNode {
396
611
  });
397
612
  }
398
613
  };
399
- /** Private Utils */
614
+ /** @private Utils for validating the */
400
615
  validate() {
401
616
  if (!this.options.authorization)
402
617
  throw new SyntaxError("LavalinkNode requires 'authorization'");
@@ -405,6 +620,12 @@ export class LavalinkNode {
405
620
  if (!this.options.port)
406
621
  throw new SyntaxError("LavalinkNode requires 'port'");
407
622
  }
623
+ /**
624
+ * Sync the data of the player you make an action to lavalink to
625
+ * @param data data to use to update the player
626
+ * @param res result data from lavalink, to override, if available
627
+ * @returns boolean
628
+ */
408
629
  syncPlayerData(data, res) {
409
630
  if (typeof data === "object" && typeof data?.guildId === "string" && typeof data.playerOptions === "object" && Object.keys(data.playerOptions).length > 1) {
410
631
  const player = this.NodeManager.LavalinkManager.getPlayer(data.guildId);
@@ -415,8 +636,9 @@ export class LavalinkNode {
415
636
  player.playing = !data.playerOptions.paused;
416
637
  }
417
638
  if (typeof data.playerOptions.position === "number") {
418
- player.position = data.playerOptions.position;
639
+ // player.position = data.playerOptions.position;
419
640
  player.lastPosition = data.playerOptions.position;
641
+ player.lastPositionChange = Date.now();
420
642
  }
421
643
  if (typeof data.playerOptions.voice !== "undefined")
422
644
  player.voice = data.playerOptions.voice;
@@ -467,9 +689,22 @@ export class LavalinkNode {
467
689
  }
468
690
  return true;
469
691
  }
692
+ /**
693
+ * Get the rest Adress for making requests
694
+ */
470
695
  get restAddress() {
471
696
  return `http${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}`;
472
697
  }
698
+ /**
699
+ * Reconnect to the lavalink node
700
+ * @param instaReconnect @default false wether to instantly try to reconnect
701
+ * @returns void
702
+ *
703
+ * @example
704
+ * ```ts
705
+ * await player.node.reconnect();
706
+ * ```
707
+ */
473
708
  reconnect(instaReconnect = false) {
474
709
  if (instaReconnect) {
475
710
  if (this.reconnectAttempts >= this.options.retryAmount) {
@@ -497,6 +732,7 @@ export class LavalinkNode {
497
732
  this.reconnectAttempts++;
498
733
  }, this.options.retryDelay || 1000);
499
734
  }
735
+ /** @private util function for handling opening events from websocket */
500
736
  async open() {
501
737
  if (this.reconnectTimeout)
502
738
  clearTimeout(this.reconnectTimeout);
@@ -509,16 +745,19 @@ export class LavalinkNode {
509
745
  }
510
746
  this.NodeManager.emit("connect", this);
511
747
  }
748
+ /** @private util function for handling closing events from websocket */
512
749
  close(code, reason) {
513
750
  this.NodeManager.emit("disconnect", this, { code, reason });
514
751
  if (code !== 1000 || reason !== "Node-Destroy")
515
752
  this.reconnect();
516
753
  }
754
+ /** @private util function for handling error events from websocket */
517
755
  error(error) {
518
756
  if (!error)
519
757
  return;
520
758
  this.NodeManager.emit("error", this, error);
521
759
  }
760
+ /** @private util function for handling message events from websocket */
522
761
  async message(d) {
523
762
  if (Array.isArray(d))
524
763
  d = Buffer.concat(d);
@@ -539,42 +778,15 @@ export class LavalinkNode {
539
778
  if (!player)
540
779
  return;
541
780
  const oldPlayer = player?.toJSON();
542
- if (player.get("internal_updateInterval"))
543
- clearInterval(player.get("internal_updateInterval"));
544
- // override the position
545
- player.position = payload.state.position || 0;
781
+ player.lastPositionChange = Date.now();
546
782
  player.lastPosition = payload.state.position || 0;
547
783
  player.connected = payload.state.connected;
548
784
  player.ping.ws = payload.state.ping >= 0 ? payload.state.ping : player.ping.ws <= 0 && player.connected ? null : player.ping.ws || 0;
549
785
  if (!player.createdTimeStamp && payload.state.time)
550
786
  player.createdTimeStamp = payload.state.time;
551
- if (typeof this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval === "number" && this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval >= 10) {
552
- player.set("internal_updateInterval", setInterval(() => {
553
- player.position += this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval || 250;
554
- if (player.filterManager.filterUpdatedState >= 1) {
555
- player.filterManager.filterUpdatedState++;
556
- const maxMins = 8;
557
- const currentDuration = player.queue.current?.info?.duration || 0;
558
- if (currentDuration <= maxMins * 6e4 || isAbsolute(player.queue.current?.info?.uri)) {
559
- if (player.filterManager.filterUpdatedState >= ((this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval || 250) > 400 ? 2 : 3)) {
560
- player.filterManager.filterUpdatedState = 0;
561
- player.seek(player.position);
562
- }
563
- }
564
- else {
565
- player.filterManager.filterUpdatedState = 0;
566
- }
567
- }
568
- }, this.NodeManager.LavalinkManager.options.playerOptions.clientBasedPositionUpdateInterval || 250));
569
- }
570
- else {
571
- if (player.filterManager.filterUpdatedState >= 1) { // if no interval but instafix available, findable via the "filterUpdatedState" property
572
- const maxMins = 8;
573
- const currentDuration = player.queue.current?.info?.duration || 0;
574
- if (currentDuration <= maxMins * 6e4 || isAbsolute(player.queue.current?.info?.uri))
575
- player.seek(player.position);
576
- player.filterManager.filterUpdatedState = 0;
577
- }
787
+ if (player.filterManager.filterUpdatedState === true && ((player.queue.current?.info?.duration || 0) <= (player.LavalinkManager.options.advancedOptions.maxFilterFixDuration || 600000) || isAbsolute(player.queue.current?.info?.uri))) {
788
+ player.filterManager.filterUpdatedState = false;
789
+ await player.seek(player.position);
578
790
  }
579
791
  this.NodeManager.LavalinkManager.emit("playerUpdate", oldPlayer, player);
580
792
  }
@@ -594,7 +806,7 @@ export class LavalinkNode {
594
806
  return;
595
807
  }
596
808
  }
597
- // LAVALINK EVENT HANDLING UTIL FUNCTION
809
+ /** @private middleware util function for handling all kind of events from websocket */
598
810
  async handleEvent(payload) {
599
811
  if (!payload.guildId)
600
812
  return;
@@ -621,7 +833,7 @@ export class LavalinkNode {
621
833
  this.SponsorBlockSegmentLoaded(player, player.queue.current, payload);
622
834
  break;
623
835
  case "SegmentSkipped":
624
- this.SponsorBlockSegmentkipped(player, player.queue.current, payload);
836
+ this.SponsorBlockSegmentSkipped(player, player.queue.current, payload);
625
837
  break;
626
838
  case "ChaptersLoaded":
627
839
  this.SponsorBlockChaptersLoaded(player, player.queue.current, payload);
@@ -635,7 +847,7 @@ export class LavalinkNode {
635
847
  }
636
848
  return;
637
849
  }
638
- // LAVALINK EVENT HANDLING FUNCTIONS
850
+ /** @private util function for handling trackStart event */
639
851
  trackStart(player, track, payload) {
640
852
  player.playing = true;
641
853
  player.paused = false;
@@ -644,6 +856,7 @@ export class LavalinkNode {
644
856
  return;
645
857
  return this.NodeManager.LavalinkManager.emit("trackStart", player, track, payload);
646
858
  }
859
+ /** @private util function for handling trackEnd event */
647
860
  async trackEnd(player, track, payload) {
648
861
  // If there are no songs in the queue
649
862
  if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying")))
@@ -665,7 +878,7 @@ export class LavalinkNode {
665
878
  // remove tracks from the queue
666
879
  if (player.repeatMode !== "track" || player.get("internal_skipped"))
667
880
  await queueTrackEnd(player);
668
- else if (player.queue.current) { // If there was a current Track already and repeatmode === true, add it to the queue.
881
+ 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.
669
882
  player.queue.previous.unshift(player.queue.current);
670
883
  if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
671
884
  player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
@@ -680,6 +893,7 @@ export class LavalinkNode {
680
893
  // play track if autoSkip is true
681
894
  return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ noReplace: true });
682
895
  }
896
+ /** @private util function for handling trackStuck event */
683
897
  async trackStuck(player, track, payload) {
684
898
  this.NodeManager.LavalinkManager.emit("trackStuck", player, track, payload);
685
899
  // If there are no songs in the queue
@@ -693,6 +907,7 @@ export class LavalinkNode {
693
907
  // play track if autoSkip is true
694
908
  return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
695
909
  }
910
+ /** @private util function for handling trackError event */
696
911
  async trackError(player, track, payload) {
697
912
  this.NodeManager.LavalinkManager.emit("trackError", player, track, payload);
698
913
  return; // get's handled by trackEnd
@@ -707,23 +922,37 @@ export class LavalinkNode {
707
922
  // play track if autoSkip is true
708
923
  return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
709
924
  }
925
+ /** @private util function for handling socketClosed event */
710
926
  socketClosed(player, payload) {
711
927
  return this.NodeManager.LavalinkManager.emit("playerSocketClosed", player, payload);
712
928
  }
713
- // SPONSOR BLOCK EVENT FUNCTIONS
929
+ /** @private util function for handling SponsorBlock Segmentloaded event */
714
930
  SponsorBlockSegmentLoaded(player, track, payload) {
715
931
  return this.NodeManager.LavalinkManager.emit("SegmentsLoaded", player, track, payload);
716
932
  }
717
- SponsorBlockSegmentkipped(player, track, payload) {
933
+ /** @private util function for handling SponsorBlock SegmentSkipped event */
934
+ SponsorBlockSegmentSkipped(player, track, payload) {
718
935
  return this.NodeManager.LavalinkManager.emit("SegmentSkipped", player, track, payload);
719
936
  }
937
+ /** @private util function for handling SponsorBlock Chaptersloaded event */
720
938
  SponsorBlockChaptersLoaded(player, track, payload) {
721
939
  return this.NodeManager.LavalinkManager.emit("ChaptersLoaded", player, track, payload);
722
940
  }
941
+ /** @private util function for handling SponsorBlock Chaptersstarted event */
723
942
  SponsorBlockChapterStarted(player, track, payload) {
724
943
  return this.NodeManager.LavalinkManager.emit("ChapterStarted", player, track, payload);
725
944
  }
726
- // SPONSOR BLOCK EXECUTE FUNCTIONS
945
+ /**
946
+ * Get the current sponsorblocks for the sponsorblock plugin
947
+ * @param player passthrough the player
948
+ * @returns sponsorblock seggment from lavalink
949
+ *
950
+ * @example
951
+ * ```ts
952
+ * // use it on the player via player.getSponsorBlock();
953
+ * const sponsorBlockSegments = await player.node.getSponsorBlock(player);
954
+ * ```
955
+ */
727
956
  async getSponsorBlock(player) {
728
957
  // no plugin enabled
729
958
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -731,6 +960,17 @@ export class LavalinkNode {
731
960
  // do the request
732
961
  return await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`);
733
962
  }
963
+ /**
964
+ * Set the current sponsorblocks for the sponsorblock plugin
965
+ * @param player passthrough the player
966
+ * @returns void
967
+ *
968
+ * @example
969
+ * ```ts
970
+ * // use it on the player via player.setSponsorBlock();
971
+ * const sponsorBlockSegments = await player.node.setSponsorBlock(player, ["sponsor", "selfpromo"]);
972
+ * ```
973
+ */
734
974
  async setSponsorBlock(player, segments = ["sponsor", "selfpromo"]) {
735
975
  // no plugin enabled
736
976
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -749,6 +989,17 @@ export class LavalinkNode {
749
989
  });
750
990
  return;
751
991
  }
992
+ /**
993
+ * Delete the sponsorblock plugins
994
+ * @param player passthrough the player
995
+ * @returns void
996
+ *
997
+ * @example
998
+ * ```ts
999
+ * // use it on the player via player.deleteSponsorBlock();
1000
+ * const sponsorBlockSegments = await player.node.deleteSponsorBlock(player);
1001
+ * ```
1002
+ */
752
1003
  async deleteSponsorBlock(player) {
753
1004
  // no plugin enabled
754
1005
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -759,7 +1010,7 @@ export class LavalinkNode {
759
1010
  });
760
1011
  return;
761
1012
  }
762
- // UTIL FOR QUEUE END
1013
+ /** private util function for handling the queue end event */
763
1014
  async queueEnd(player, track, payload) {
764
1015
  // add previous track to the queue!
765
1016
  player.queue.current = null;
@@ -776,7 +1027,12 @@ export class LavalinkNode {
776
1027
  }
777
1028
  }
778
1029
  player.set("internal_autoplayStopPlaying", undefined);
779
- player.queue.previous.unshift(track);
1030
+ if (track && !track?.pluginInfo?.clientData?.previousTrack) { // If there was a current Track already and repeatmode === true, add it to the queue.
1031
+ player.queue.previous.unshift(track);
1032
+ if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
1033
+ player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
1034
+ await player.queue.utils.save();
1035
+ }
780
1036
  if (payload?.reason !== "stopped") {
781
1037
  await player.queue.utils.save();
782
1038
  }