lavalink-client 2.2.0 → 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 (32) hide show
  1. package/README.md +76 -1
  2. package/dist/cjs/structures/Filters.d.ts +1 -1
  3. package/dist/cjs/structures/Filters.js +5 -5
  4. package/dist/cjs/structures/LavalinkManager.d.ts +18 -1
  5. package/dist/cjs/structures/LavalinkManager.js +14 -1
  6. package/dist/cjs/structures/LavalinkManagerStatics.d.ts +3 -0
  7. package/dist/cjs/structures/LavalinkManagerStatics.js +3 -0
  8. package/dist/cjs/structures/Node.d.ts +307 -22
  9. package/dist/cjs/structures/Node.js +317 -68
  10. package/dist/cjs/structures/Player.d.ts +44 -8
  11. package/dist/cjs/structures/Player.js +26 -18
  12. package/dist/cjs/structures/Utils.d.ts +3 -0
  13. package/dist/cjs/structures/Utils.js +1 -0
  14. package/dist/esm/structures/Filters.d.ts +1 -1
  15. package/dist/esm/structures/Filters.js +5 -5
  16. package/dist/esm/structures/LavalinkManager.d.ts +18 -1
  17. package/dist/esm/structures/LavalinkManager.js +14 -1
  18. package/dist/esm/structures/LavalinkManagerStatics.d.ts +3 -0
  19. package/dist/esm/structures/LavalinkManagerStatics.js +3 -0
  20. package/dist/esm/structures/Node.d.ts +307 -22
  21. package/dist/esm/structures/Node.js +317 -68
  22. package/dist/esm/structures/Player.d.ts +44 -8
  23. package/dist/esm/structures/Player.js +26 -18
  24. package/dist/esm/structures/Utils.d.ts +3 -0
  25. package/dist/esm/structures/Utils.js +1 -0
  26. package/dist/types/structures/Filters.d.ts +1 -1
  27. package/dist/types/structures/LavalinkManager.d.ts +18 -1
  28. package/dist/types/structures/LavalinkManagerStatics.d.ts +3 -0
  29. package/dist/types/structures/Node.d.ts +307 -22
  30. package/dist/types/structures/Player.d.ts +44 -8
  31. package/dist/types/structures/Utils.d.ts +3 -0
  32. 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,13 +325,28 @@ 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)
@@ -254,14 +368,47 @@ export class LavalinkNode {
254
368
  }
255
369
  return;
256
370
  }
257
- /** Returns if connected to the Node. */
371
+ /**
372
+ * Returns if connected to the Node.
373
+ *
374
+ * @example
375
+ * ```ts
376
+ * const isConnected = player.node.connected;
377
+ * console.log("node is connected: ", isConnected ? "yes" : "no")
378
+ * ```
379
+ */
258
380
  get connected() {
259
381
  if (!this.socket)
260
382
  return false;
261
383
  return this.socket.readyState === WebSocket.OPEN;
262
384
  }
385
+ /**
386
+ * Returns the current ConnectionStatus
387
+ *
388
+ * @example
389
+ * ```ts
390
+ * try {
391
+ * const statusOfConnection = player.node.connectionStatus;
392
+ * console.log("node's connection status is:", statusOfConnection)
393
+ * } catch (error) {
394
+ * console.error("no socket available?", error)
395
+ * }
396
+ * ```
397
+ */
398
+ get connectionStatus() {
399
+ if (!this.socket)
400
+ throw new Error("no websocket was initialized yet");
401
+ return ["CONNECTING", "OPEN", "CLOSING", "CLOSED"][this.socket.readyState] || "UNKNOWN";
402
+ }
263
403
  /**
264
404
  * Gets all Players of a Node
405
+ * @returns array of players inside of lavalink
406
+ *
407
+ * @example
408
+ * ```ts
409
+ * const node = lavalink.nodes.get("NODEID");
410
+ * const playersOfLavalink = await node?.fetchAllPlayers();
411
+ * ```
265
412
  */
266
413
  async fetchAllPlayers() {
267
414
  if (!this.sessionId)
@@ -274,6 +421,13 @@ export class LavalinkNode {
274
421
  }
275
422
  /**
276
423
  * Gets specific Player Information
424
+ * @returns lavalink player object if player exists on lavalink
425
+ *
426
+ * @example
427
+ * ```ts
428
+ * const node = lavalink.nodes.get("NODEID");
429
+ * const playerInformation = await node?.fetchPlayer("guildId");
430
+ * ```
277
431
  */
278
432
  async fetchPlayer(guildId) {
279
433
  if (!this.sessionId)
@@ -284,6 +438,13 @@ export class LavalinkNode {
284
438
  * Updates the session with and enables/disables resuming and timeout
285
439
  * @param resuming Whether resuming is enabled for this session or not
286
440
  * @param timeout The timeout in seconds (default is 60s)
441
+ * @returns the result of the request
442
+ *
443
+ * @example
444
+ * ```ts
445
+ * const node = player.node || lavalink.nodes.get("NODEID");
446
+ * await node?.updateSession(true, 180e3); // will enable resuming for 180seconds
447
+ * ```
287
448
  */
288
449
  async updateSession(resuming, timeout) {
289
450
  if (!this.sessionId)
@@ -308,20 +469,35 @@ export class LavalinkNode {
308
469
  */
309
470
  decode = {
310
471
  /**
311
- * Decode a single track into its info, where BASE64 is the encoded base64 data.
312
- * @param encoded
313
- * @returns
472
+ * Decode a single track into its info
473
+ * @param encoded valid encoded base64 string from a track
474
+ * @param requester the requesteruser for building the track
475
+ * @returns decoded track from lavalink
476
+ *
477
+ * @example
478
+ * ```ts
479
+ * const encodedBase64 = 'QAACDgMACk5vIERpZ2dpdHkAC0JsYWNrc3RyZWV0AAAAAAAEo4AABjkxNjQ5NgABAB9odHRwczovL2RlZXplci5jb20vdHJhY2svOTE2NDk2AQBpaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvY292ZXIvZGFlN2EyNjViNzlmYjcxMjc4Y2RlMjUwNDg0OWQ2ZjcvMTAwMHgxMDAwLTAwMDAwMC04MC0wLTAuanBnAQAMVVNJUjE5NjAwOTc4AAZkZWV6ZXIBAChObyBEaWdnaXR5OiBUaGUgVmVyeSBCZXN0IE9mIEJsYWNrc3RyZWV0AQAjaHR0cHM6Ly93d3cuZGVlemVyLmNvbS9hbGJ1bS8xMDMyNTQBACJodHRwczovL3d3dy5kZWV6ZXIuY29tL2FydGlzdC8xODYxAQBqaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvYXJ0aXN0L2YxNmNhYzM2ZmVjMzkxZjczN2I3ZDQ4MmY1YWM3M2UzLzEwMDB4MTAwMC0wMDAwMDAtODAtMC0wLmpwZwEAT2h0dHBzOi8vY2RuLXByZXZpZXctYS5kemNkbi5uZXQvc3RyZWFtL2MtYTE1Yjg1NzFhYTYyMDBjMDQ0YmY1OWM3NmVkOTEyN2MtNi5tcDMAAAAAAAAAAAA=';
480
+ * const track = await player.node.decode.singleTrack(encodedBase64, interaction.user);
481
+ * ```
314
482
  */
315
483
  singleTrack: async (encoded, requester) => {
316
484
  if (!encoded)
317
485
  throw new SyntaxError("No encoded (Base64 string) was provided");
318
486
  // return the decoded + builded track
319
- return this.NodeManager.LavalinkManager.utils.buildTrack(await this.request(`/decodetrack?encodedTrack=${encoded}`), requester);
487
+ return this.NodeManager.LavalinkManager.utils?.buildTrack(await this.request(`/decodetrack?encodedTrack=${encodeURIComponent(encoded.replace(/\s/g, ""))}`), requester);
320
488
  },
321
489
  /**
490
+ * Decodes multiple tracks into their info
491
+ * @param encodeds valid encoded base64 string array from all tracks
492
+ * @param requester the requesteruser for building the tracks
493
+ * @returns array of all tracks you decoded
322
494
  *
323
- * @param encodeds Decodes multiple tracks into their info
324
- * @returns
495
+ * @example
496
+ * ```ts
497
+ * const encodedBase64_1 = 'QAACDgMACk5vIERpZ2dpdHkAC0JsYWNrc3RyZWV0AAAAAAAEo4AABjkxNjQ5NgABAB9odHRwczovL2RlZXplci5jb20vdHJhY2svOTE2NDk2AQBpaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvY292ZXIvZGFlN2EyNjViNzlmYjcxMjc4Y2RlMjUwNDg0OWQ2ZjcvMTAwMHgxMDAwLTAwMDAwMC04MC0wLTAuanBnAQAMVVNJUjE5NjAwOTc4AAZkZWV6ZXIBAChObyBEaWdnaXR5OiBUaGUgVmVyeSBCZXN0IE9mIEJsYWNrc3RyZWV0AQAjaHR0cHM6Ly93d3cuZGVlemVyLmNvbS9hbGJ1bS8xMDMyNTQBACJodHRwczovL3d3dy5kZWV6ZXIuY29tL2FydGlzdC8xODYxAQBqaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvYXJ0aXN0L2YxNmNhYzM2ZmVjMzkxZjczN2I3ZDQ4MmY1YWM3M2UzLzEwMDB4MTAwMC0wMDAwMDAtODAtMC0wLmpwZwEAT2h0dHBzOi8vY2RuLXByZXZpZXctYS5kemNkbi5uZXQvc3RyZWFtL2MtYTE1Yjg1NzFhYTYyMDBjMDQ0YmY1OWM3NmVkOTEyN2MtNi5tcDMAAAAAAAAAAAA=';
498
+ * const encodedBase64_2 = 'QAABJAMAClRhbGsgYSBMb3QACjQwNHZpbmNlbnQAAAAAAAHr1gBxTzpodHRwczovL2FwaS12Mi5zb3VuZGNsb3VkLmNvbS9tZWRpYS9zb3VuZGNsb3VkOnRyYWNrczo4NTE0MjEwNzYvMzUyYTRiOTAtNzYxOS00M2E5LWJiOGItMjIxMzE0YzFjNjNhL3N0cmVhbS9obHMAAQAsaHR0cHM6Ly9zb3VuZGNsb3VkLmNvbS80MDR2aW5jZW50L3RhbGstYS1sb3QBADpodHRwczovL2kxLnNuZGNkbi5jb20vYXJ0d29ya3MtRTN1ek5Gc0Y4QzBXLTAtb3JpZ2luYWwuanBnAQAMUVpITkExOTg1Nzg0AApzb3VuZGNsb3VkAAAAAAAAAAA=';
499
+ * const tracks = await player.node.decode.multipleTracks([encodedBase64_1, encodedBase64_2], interaction.user);
500
+ * ```
325
501
  */
326
502
  multipleTracks: async (encodeds, requester) => {
327
503
  if (!Array.isArray(encodeds) || !encodeds.every(v => typeof v === "string" && v.length > 1))
@@ -337,14 +513,24 @@ export class LavalinkNode {
337
513
  };
338
514
  /**
339
515
  * Request Lavalink statistics.
340
- * @returns
516
+ * @returns the lavalink node stats
517
+ *
518
+ * @example
519
+ * ```ts
520
+ * const lavalinkStats = await player.node.fetchStats();
521
+ * ```
341
522
  */
342
523
  async fetchStats() {
343
524
  return await this.request(`/stats`);
344
525
  }
345
526
  /**
346
527
  * Request Lavalink version.
347
- * @returns
528
+ * @returns the current used lavalink version
529
+ *
530
+ * @example
531
+ * ```ts
532
+ * const lavalinkVersion = await player.node.fetchVersion();
533
+ * ```
348
534
  */
349
535
  async fetchVersion() {
350
536
  // need to adjust path for no-prefix version info
@@ -352,7 +538,14 @@ export class LavalinkNode {
352
538
  }
353
539
  /**
354
540
  * Request Lavalink information.
355
- * @returns
541
+ * @returns lavalink info object
542
+ *
543
+ * @example
544
+ * ```ts
545
+ * const lavalinkInfo = await player.node.fetchInfo();
546
+ * const availablePlugins:string[] = lavalinkInfo.plugins.map(plugin => plugin.name);
547
+ * const availableSources:string[] = lavalinkInfo.sourceManagers;
548
+ * ```
356
549
  */
357
550
  async fetchInfo() {
358
551
  return await this.request(`/info`);
@@ -362,7 +555,15 @@ export class LavalinkNode {
362
555
  */
363
556
  routePlannerApi = {
364
557
  /**
365
- * Get routplanner Info from Lavalink
558
+ * Get routplanner Info from Lavalink for ip rotation
559
+ * @returns the status of the routeplanner
560
+ *
561
+ * @example
562
+ * ```ts
563
+ * const routePlannerStatus = await player.node.routePlannerApi.getStatus();
564
+ * const usedBlock = routePlannerStatus.details?.ipBlock;
565
+ * const currentIp = routePlannerStatus.currentAddress;
566
+ * ```
366
567
  */
367
568
  getStatus: async () => {
368
569
  if (!this.sessionId)
@@ -370,8 +571,14 @@ export class LavalinkNode {
370
571
  return await this.request(`/routeplanner/status`);
371
572
  },
372
573
  /**
373
- * Release blacklisted IP address into pool of IPs
574
+ * Release blacklisted IP address into pool of IPs for ip rotation
374
575
  * @param address IP address
576
+ * @returns request data of the request
577
+ *
578
+ * @example
579
+ * ```ts
580
+ * await player.node.routePlannerApi.unmarkFailedAddress("ipv6address");
581
+ * ```
375
582
  */
376
583
  unmarkFailedAddress: async (address) => {
377
584
  if (!this.sessionId)
@@ -385,6 +592,12 @@ export class LavalinkNode {
385
592
  },
386
593
  /**
387
594
  * Release all blacklisted IP addresses into pool of IPs
595
+ * @returns request data of the request
596
+ *
597
+ * @example
598
+ * ```ts
599
+ * await player.node.routePlannerApi.unmarkAllFailedAddresses();
600
+ * ```
388
601
  */
389
602
  unmarkAllFailedAddresses: async () => {
390
603
  if (!this.sessionId)
@@ -396,7 +609,7 @@ export class LavalinkNode {
396
609
  });
397
610
  }
398
611
  };
399
- /** Private Utils */
612
+ /** @private Utils for validating the */
400
613
  validate() {
401
614
  if (!this.options.authorization)
402
615
  throw new SyntaxError("LavalinkNode requires 'authorization'");
@@ -405,6 +618,12 @@ export class LavalinkNode {
405
618
  if (!this.options.port)
406
619
  throw new SyntaxError("LavalinkNode requires 'port'");
407
620
  }
621
+ /**
622
+ * Sync the data of the player you make an action to lavalink to
623
+ * @param data data to use to update the player
624
+ * @param res result data from lavalink, to override, if available
625
+ * @returns boolean
626
+ */
408
627
  syncPlayerData(data, res) {
409
628
  if (typeof data === "object" && typeof data?.guildId === "string" && typeof data.playerOptions === "object" && Object.keys(data.playerOptions).length > 1) {
410
629
  const player = this.NodeManager.LavalinkManager.getPlayer(data.guildId);
@@ -415,8 +634,9 @@ export class LavalinkNode {
415
634
  player.playing = !data.playerOptions.paused;
416
635
  }
417
636
  if (typeof data.playerOptions.position === "number") {
418
- player.position = data.playerOptions.position;
637
+ // player.position = data.playerOptions.position;
419
638
  player.lastPosition = data.playerOptions.position;
639
+ player.lastPositionChange = Date.now();
420
640
  }
421
641
  if (typeof data.playerOptions.voice !== "undefined")
422
642
  player.voice = data.playerOptions.voice;
@@ -467,9 +687,22 @@ export class LavalinkNode {
467
687
  }
468
688
  return true;
469
689
  }
690
+ /**
691
+ * Get the rest Adress for making requests
692
+ */
470
693
  get restAddress() {
471
694
  return `http${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}`;
472
695
  }
696
+ /**
697
+ * Reconnect to the lavalink node
698
+ * @param instaReconnect @default false wether to instantly try to reconnect
699
+ * @returns void
700
+ *
701
+ * @example
702
+ * ```ts
703
+ * await player.node.reconnect();
704
+ * ```
705
+ */
473
706
  reconnect(instaReconnect = false) {
474
707
  if (instaReconnect) {
475
708
  if (this.reconnectAttempts >= this.options.retryAmount) {
@@ -497,6 +730,7 @@ export class LavalinkNode {
497
730
  this.reconnectAttempts++;
498
731
  }, this.options.retryDelay || 1000);
499
732
  }
733
+ /** @private util function for handling opening events from websocket */
500
734
  async open() {
501
735
  if (this.reconnectTimeout)
502
736
  clearTimeout(this.reconnectTimeout);
@@ -509,16 +743,19 @@ export class LavalinkNode {
509
743
  }
510
744
  this.NodeManager.emit("connect", this);
511
745
  }
746
+ /** @private util function for handling closing events from websocket */
512
747
  close(code, reason) {
513
748
  this.NodeManager.emit("disconnect", this, { code, reason });
514
749
  if (code !== 1000 || reason !== "Node-Destroy")
515
750
  this.reconnect();
516
751
  }
752
+ /** @private util function for handling error events from websocket */
517
753
  error(error) {
518
754
  if (!error)
519
755
  return;
520
756
  this.NodeManager.emit("error", this, error);
521
757
  }
758
+ /** @private util function for handling message events from websocket */
522
759
  async message(d) {
523
760
  if (Array.isArray(d))
524
761
  d = Buffer.concat(d);
@@ -539,42 +776,15 @@ export class LavalinkNode {
539
776
  if (!player)
540
777
  return;
541
778
  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;
779
+ player.lastPositionChange = Date.now();
546
780
  player.lastPosition = payload.state.position || 0;
547
781
  player.connected = payload.state.connected;
548
782
  player.ping.ws = payload.state.ping >= 0 ? payload.state.ping : player.ping.ws <= 0 && player.connected ? null : player.ping.ws || 0;
549
783
  if (!player.createdTimeStamp && payload.state.time)
550
784
  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
- }
785
+ if (player.filterManager.filterUpdatedState === true && ((player.queue.current?.info?.duration || 0) <= (player.LavalinkManager.options.advancedOptions.maxFilterFixDuration || 600000) || isAbsolute(player.queue.current?.info?.uri))) {
786
+ player.filterManager.filterUpdatedState = false;
787
+ await player.seek(player.position);
578
788
  }
579
789
  this.NodeManager.LavalinkManager.emit("playerUpdate", oldPlayer, player);
580
790
  }
@@ -594,7 +804,7 @@ export class LavalinkNode {
594
804
  return;
595
805
  }
596
806
  }
597
- // LAVALINK EVENT HANDLING UTIL FUNCTION
807
+ /** @private middleware util function for handling all kind of events from websocket */
598
808
  async handleEvent(payload) {
599
809
  if (!payload.guildId)
600
810
  return;
@@ -621,7 +831,7 @@ export class LavalinkNode {
621
831
  this.SponsorBlockSegmentLoaded(player, player.queue.current, payload);
622
832
  break;
623
833
  case "SegmentSkipped":
624
- this.SponsorBlockSegmentkipped(player, player.queue.current, payload);
834
+ this.SponsorBlockSegmentSkipped(player, player.queue.current, payload);
625
835
  break;
626
836
  case "ChaptersLoaded":
627
837
  this.SponsorBlockChaptersLoaded(player, player.queue.current, payload);
@@ -635,7 +845,7 @@ export class LavalinkNode {
635
845
  }
636
846
  return;
637
847
  }
638
- // LAVALINK EVENT HANDLING FUNCTIONS
848
+ /** @private util function for handling trackStart event */
639
849
  trackStart(player, track, payload) {
640
850
  player.playing = true;
641
851
  player.paused = false;
@@ -644,6 +854,7 @@ export class LavalinkNode {
644
854
  return;
645
855
  return this.NodeManager.LavalinkManager.emit("trackStart", player, track, payload);
646
856
  }
857
+ /** @private util function for handling trackEnd event */
647
858
  async trackEnd(player, track, payload) {
648
859
  // If there are no songs in the queue
649
860
  if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying")))
@@ -680,6 +891,7 @@ export class LavalinkNode {
680
891
  // play track if autoSkip is true
681
892
  return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ noReplace: true });
682
893
  }
894
+ /** @private util function for handling trackStuck event */
683
895
  async trackStuck(player, track, payload) {
684
896
  this.NodeManager.LavalinkManager.emit("trackStuck", player, track, payload);
685
897
  // If there are no songs in the queue
@@ -693,6 +905,7 @@ export class LavalinkNode {
693
905
  // play track if autoSkip is true
694
906
  return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
695
907
  }
908
+ /** @private util function for handling trackError event */
696
909
  async trackError(player, track, payload) {
697
910
  this.NodeManager.LavalinkManager.emit("trackError", player, track, payload);
698
911
  return; // get's handled by trackEnd
@@ -707,23 +920,37 @@ export class LavalinkNode {
707
920
  // play track if autoSkip is true
708
921
  return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
709
922
  }
923
+ /** @private util function for handling socketClosed event */
710
924
  socketClosed(player, payload) {
711
925
  return this.NodeManager.LavalinkManager.emit("playerSocketClosed", player, payload);
712
926
  }
713
- // SPONSOR BLOCK EVENT FUNCTIONS
927
+ /** @private util function for handling SponsorBlock Segmentloaded event */
714
928
  SponsorBlockSegmentLoaded(player, track, payload) {
715
929
  return this.NodeManager.LavalinkManager.emit("SegmentsLoaded", player, track, payload);
716
930
  }
717
- SponsorBlockSegmentkipped(player, track, payload) {
931
+ /** @private util function for handling SponsorBlock SegmentSkipped event */
932
+ SponsorBlockSegmentSkipped(player, track, payload) {
718
933
  return this.NodeManager.LavalinkManager.emit("SegmentSkipped", player, track, payload);
719
934
  }
935
+ /** @private util function for handling SponsorBlock Chaptersloaded event */
720
936
  SponsorBlockChaptersLoaded(player, track, payload) {
721
937
  return this.NodeManager.LavalinkManager.emit("ChaptersLoaded", player, track, payload);
722
938
  }
939
+ /** @private util function for handling SponsorBlock Chaptersstarted event */
723
940
  SponsorBlockChapterStarted(player, track, payload) {
724
941
  return this.NodeManager.LavalinkManager.emit("ChapterStarted", player, track, payload);
725
942
  }
726
- // SPONSOR BLOCK EXECUTE FUNCTIONS
943
+ /**
944
+ * Get the current sponsorblocks for the sponsorblock plugin
945
+ * @param player passthrough the player
946
+ * @returns sponsorblock seggment from lavalink
947
+ *
948
+ * @example
949
+ * ```ts
950
+ * // use it on the player via player.getSponsorBlock();
951
+ * const sponsorBlockSegments = await player.node.getSponsorBlock(player);
952
+ * ```
953
+ */
727
954
  async getSponsorBlock(player) {
728
955
  // no plugin enabled
729
956
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -731,6 +958,17 @@ export class LavalinkNode {
731
958
  // do the request
732
959
  return await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`);
733
960
  }
961
+ /**
962
+ * Set the current sponsorblocks for the sponsorblock plugin
963
+ * @param player passthrough the player
964
+ * @returns void
965
+ *
966
+ * @example
967
+ * ```ts
968
+ * // use it on the player via player.setSponsorBlock();
969
+ * const sponsorBlockSegments = await player.node.setSponsorBlock(player, ["sponsor", "selfpromo"]);
970
+ * ```
971
+ */
734
972
  async setSponsorBlock(player, segments = ["sponsor", "selfpromo"]) {
735
973
  // no plugin enabled
736
974
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -749,6 +987,17 @@ export class LavalinkNode {
749
987
  });
750
988
  return;
751
989
  }
990
+ /**
991
+ * Delete the sponsorblock plugins
992
+ * @param player passthrough the player
993
+ * @returns void
994
+ *
995
+ * @example
996
+ * ```ts
997
+ * // use it on the player via player.deleteSponsorBlock();
998
+ * const sponsorBlockSegments = await player.node.deleteSponsorBlock(player);
999
+ * ```
1000
+ */
752
1001
  async deleteSponsorBlock(player) {
753
1002
  // no plugin enabled
754
1003
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -759,7 +1008,7 @@ export class LavalinkNode {
759
1008
  });
760
1009
  return;
761
1010
  }
762
- // UTIL FOR QUEUE END
1011
+ /** private util function for handling the queue end event */
763
1012
  async queueEnd(player, track, payload) {
764
1013
  // add previous track to the queue!
765
1014
  player.queue.current = null;