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
@@ -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,13 +329,28 @@ 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)
@@ -258,14 +372,47 @@ class LavalinkNode {
258
372
  }
259
373
  return;
260
374
  }
261
- /** Returns if connected to the Node. */
375
+ /**
376
+ * Returns if connected to the Node.
377
+ *
378
+ * @example
379
+ * ```ts
380
+ * const isConnected = player.node.connected;
381
+ * console.log("node is connected: ", isConnected ? "yes" : "no")
382
+ * ```
383
+ */
262
384
  get connected() {
263
385
  if (!this.socket)
264
386
  return false;
265
387
  return this.socket.readyState === ws_1.default.OPEN;
266
388
  }
389
+ /**
390
+ * Returns the current ConnectionStatus
391
+ *
392
+ * @example
393
+ * ```ts
394
+ * try {
395
+ * const statusOfConnection = player.node.connectionStatus;
396
+ * console.log("node's connection status is:", statusOfConnection)
397
+ * } catch (error) {
398
+ * console.error("no socket available?", error)
399
+ * }
400
+ * ```
401
+ */
402
+ get connectionStatus() {
403
+ if (!this.socket)
404
+ throw new Error("no websocket was initialized yet");
405
+ return ["CONNECTING", "OPEN", "CLOSING", "CLOSED"][this.socket.readyState] || "UNKNOWN";
406
+ }
267
407
  /**
268
408
  * Gets all Players of a Node
409
+ * @returns array of players inside of lavalink
410
+ *
411
+ * @example
412
+ * ```ts
413
+ * const node = lavalink.nodes.get("NODEID");
414
+ * const playersOfLavalink = await node?.fetchAllPlayers();
415
+ * ```
269
416
  */
270
417
  async fetchAllPlayers() {
271
418
  if (!this.sessionId)
@@ -278,6 +425,13 @@ class LavalinkNode {
278
425
  }
279
426
  /**
280
427
  * Gets specific Player Information
428
+ * @returns lavalink player object if player exists on lavalink
429
+ *
430
+ * @example
431
+ * ```ts
432
+ * const node = lavalink.nodes.get("NODEID");
433
+ * const playerInformation = await node?.fetchPlayer("guildId");
434
+ * ```
281
435
  */
282
436
  async fetchPlayer(guildId) {
283
437
  if (!this.sessionId)
@@ -288,6 +442,13 @@ class LavalinkNode {
288
442
  * Updates the session with and enables/disables resuming and timeout
289
443
  * @param resuming Whether resuming is enabled for this session or not
290
444
  * @param timeout The timeout in seconds (default is 60s)
445
+ * @returns the result of the request
446
+ *
447
+ * @example
448
+ * ```ts
449
+ * const node = player.node || lavalink.nodes.get("NODEID");
450
+ * await node?.updateSession(true, 180e3); // will enable resuming for 180seconds
451
+ * ```
291
452
  */
292
453
  async updateSession(resuming, timeout) {
293
454
  if (!this.sessionId)
@@ -312,20 +473,35 @@ class LavalinkNode {
312
473
  */
313
474
  decode = {
314
475
  /**
315
- * Decode a single track into its info, where BASE64 is the encoded base64 data.
316
- * @param encoded
317
- * @returns
476
+ * Decode a single track into its info
477
+ * @param encoded valid encoded base64 string from a track
478
+ * @param requester the requesteruser for building the track
479
+ * @returns decoded track from lavalink
480
+ *
481
+ * @example
482
+ * ```ts
483
+ * const encodedBase64 = 'QAACDgMACk5vIERpZ2dpdHkAC0JsYWNrc3RyZWV0AAAAAAAEo4AABjkxNjQ5NgABAB9odHRwczovL2RlZXplci5jb20vdHJhY2svOTE2NDk2AQBpaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvY292ZXIvZGFlN2EyNjViNzlmYjcxMjc4Y2RlMjUwNDg0OWQ2ZjcvMTAwMHgxMDAwLTAwMDAwMC04MC0wLTAuanBnAQAMVVNJUjE5NjAwOTc4AAZkZWV6ZXIBAChObyBEaWdnaXR5OiBUaGUgVmVyeSBCZXN0IE9mIEJsYWNrc3RyZWV0AQAjaHR0cHM6Ly93d3cuZGVlemVyLmNvbS9hbGJ1bS8xMDMyNTQBACJodHRwczovL3d3dy5kZWV6ZXIuY29tL2FydGlzdC8xODYxAQBqaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvYXJ0aXN0L2YxNmNhYzM2ZmVjMzkxZjczN2I3ZDQ4MmY1YWM3M2UzLzEwMDB4MTAwMC0wMDAwMDAtODAtMC0wLmpwZwEAT2h0dHBzOi8vY2RuLXByZXZpZXctYS5kemNkbi5uZXQvc3RyZWFtL2MtYTE1Yjg1NzFhYTYyMDBjMDQ0YmY1OWM3NmVkOTEyN2MtNi5tcDMAAAAAAAAAAAA=';
484
+ * const track = await player.node.decode.singleTrack(encodedBase64, interaction.user);
485
+ * ```
318
486
  */
319
487
  singleTrack: async (encoded, requester) => {
320
488
  if (!encoded)
321
489
  throw new SyntaxError("No encoded (Base64 string) was provided");
322
490
  // return the decoded + builded track
323
- return this.NodeManager.LavalinkManager.utils.buildTrack(await this.request(`/decodetrack?encodedTrack=${encoded}`), requester);
491
+ return this.NodeManager.LavalinkManager.utils?.buildTrack(await this.request(`/decodetrack?encodedTrack=${encodeURIComponent(encoded.replace(/\s/g, ""))}`), requester);
324
492
  },
325
493
  /**
494
+ * Decodes multiple tracks into their info
495
+ * @param encodeds valid encoded base64 string array from all tracks
496
+ * @param requester the requesteruser for building the tracks
497
+ * @returns array of all tracks you decoded
326
498
  *
327
- * @param encodeds Decodes multiple tracks into their info
328
- * @returns
499
+ * @example
500
+ * ```ts
501
+ * const encodedBase64_1 = 'QAACDgMACk5vIERpZ2dpdHkAC0JsYWNrc3RyZWV0AAAAAAAEo4AABjkxNjQ5NgABAB9odHRwczovL2RlZXplci5jb20vdHJhY2svOTE2NDk2AQBpaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvY292ZXIvZGFlN2EyNjViNzlmYjcxMjc4Y2RlMjUwNDg0OWQ2ZjcvMTAwMHgxMDAwLTAwMDAwMC04MC0wLTAuanBnAQAMVVNJUjE5NjAwOTc4AAZkZWV6ZXIBAChObyBEaWdnaXR5OiBUaGUgVmVyeSBCZXN0IE9mIEJsYWNrc3RyZWV0AQAjaHR0cHM6Ly93d3cuZGVlemVyLmNvbS9hbGJ1bS8xMDMyNTQBACJodHRwczovL3d3dy5kZWV6ZXIuY29tL2FydGlzdC8xODYxAQBqaHR0cHM6Ly9lLWNkbnMtaW1hZ2VzLmR6Y2RuLm5ldC9pbWFnZXMvYXJ0aXN0L2YxNmNhYzM2ZmVjMzkxZjczN2I3ZDQ4MmY1YWM3M2UzLzEwMDB4MTAwMC0wMDAwMDAtODAtMC0wLmpwZwEAT2h0dHBzOi8vY2RuLXByZXZpZXctYS5kemNkbi5uZXQvc3RyZWFtL2MtYTE1Yjg1NzFhYTYyMDBjMDQ0YmY1OWM3NmVkOTEyN2MtNi5tcDMAAAAAAAAAAAA=';
502
+ * const encodedBase64_2 = 'QAABJAMAClRhbGsgYSBMb3QACjQwNHZpbmNlbnQAAAAAAAHr1gBxTzpodHRwczovL2FwaS12Mi5zb3VuZGNsb3VkLmNvbS9tZWRpYS9zb3VuZGNsb3VkOnRyYWNrczo4NTE0MjEwNzYvMzUyYTRiOTAtNzYxOS00M2E5LWJiOGItMjIxMzE0YzFjNjNhL3N0cmVhbS9obHMAAQAsaHR0cHM6Ly9zb3VuZGNsb3VkLmNvbS80MDR2aW5jZW50L3RhbGstYS1sb3QBADpodHRwczovL2kxLnNuZGNkbi5jb20vYXJ0d29ya3MtRTN1ek5Gc0Y4QzBXLTAtb3JpZ2luYWwuanBnAQAMUVpITkExOTg1Nzg0AApzb3VuZGNsb3VkAAAAAAAAAAA=';
503
+ * const tracks = await player.node.decode.multipleTracks([encodedBase64_1, encodedBase64_2], interaction.user);
504
+ * ```
329
505
  */
330
506
  multipleTracks: async (encodeds, requester) => {
331
507
  if (!Array.isArray(encodeds) || !encodeds.every(v => typeof v === "string" && v.length > 1))
@@ -341,14 +517,24 @@ class LavalinkNode {
341
517
  };
342
518
  /**
343
519
  * Request Lavalink statistics.
344
- * @returns
520
+ * @returns the lavalink node stats
521
+ *
522
+ * @example
523
+ * ```ts
524
+ * const lavalinkStats = await player.node.fetchStats();
525
+ * ```
345
526
  */
346
527
  async fetchStats() {
347
528
  return await this.request(`/stats`);
348
529
  }
349
530
  /**
350
531
  * Request Lavalink version.
351
- * @returns
532
+ * @returns the current used lavalink version
533
+ *
534
+ * @example
535
+ * ```ts
536
+ * const lavalinkVersion = await player.node.fetchVersion();
537
+ * ```
352
538
  */
353
539
  async fetchVersion() {
354
540
  // need to adjust path for no-prefix version info
@@ -356,7 +542,14 @@ class LavalinkNode {
356
542
  }
357
543
  /**
358
544
  * Request Lavalink information.
359
- * @returns
545
+ * @returns lavalink info object
546
+ *
547
+ * @example
548
+ * ```ts
549
+ * const lavalinkInfo = await player.node.fetchInfo();
550
+ * const availablePlugins:string[] = lavalinkInfo.plugins.map(plugin => plugin.name);
551
+ * const availableSources:string[] = lavalinkInfo.sourceManagers;
552
+ * ```
360
553
  */
361
554
  async fetchInfo() {
362
555
  return await this.request(`/info`);
@@ -366,7 +559,15 @@ class LavalinkNode {
366
559
  */
367
560
  routePlannerApi = {
368
561
  /**
369
- * Get routplanner Info from Lavalink
562
+ * Get routplanner Info from Lavalink for ip rotation
563
+ * @returns the status of the routeplanner
564
+ *
565
+ * @example
566
+ * ```ts
567
+ * const routePlannerStatus = await player.node.routePlannerApi.getStatus();
568
+ * const usedBlock = routePlannerStatus.details?.ipBlock;
569
+ * const currentIp = routePlannerStatus.currentAddress;
570
+ * ```
370
571
  */
371
572
  getStatus: async () => {
372
573
  if (!this.sessionId)
@@ -374,8 +575,14 @@ class LavalinkNode {
374
575
  return await this.request(`/routeplanner/status`);
375
576
  },
376
577
  /**
377
- * Release blacklisted IP address into pool of IPs
578
+ * Release blacklisted IP address into pool of IPs for ip rotation
378
579
  * @param address IP address
580
+ * @returns request data of the request
581
+ *
582
+ * @example
583
+ * ```ts
584
+ * await player.node.routePlannerApi.unmarkFailedAddress("ipv6address");
585
+ * ```
379
586
  */
380
587
  unmarkFailedAddress: async (address) => {
381
588
  if (!this.sessionId)
@@ -389,6 +596,12 @@ class LavalinkNode {
389
596
  },
390
597
  /**
391
598
  * Release all blacklisted IP addresses into pool of IPs
599
+ * @returns request data of the request
600
+ *
601
+ * @example
602
+ * ```ts
603
+ * await player.node.routePlannerApi.unmarkAllFailedAddresses();
604
+ * ```
392
605
  */
393
606
  unmarkAllFailedAddresses: async () => {
394
607
  if (!this.sessionId)
@@ -400,7 +613,7 @@ class LavalinkNode {
400
613
  });
401
614
  }
402
615
  };
403
- /** Private Utils */
616
+ /** @private Utils for validating the */
404
617
  validate() {
405
618
  if (!this.options.authorization)
406
619
  throw new SyntaxError("LavalinkNode requires 'authorization'");
@@ -409,6 +622,12 @@ class LavalinkNode {
409
622
  if (!this.options.port)
410
623
  throw new SyntaxError("LavalinkNode requires 'port'");
411
624
  }
625
+ /**
626
+ * Sync the data of the player you make an action to lavalink to
627
+ * @param data data to use to update the player
628
+ * @param res result data from lavalink, to override, if available
629
+ * @returns boolean
630
+ */
412
631
  syncPlayerData(data, res) {
413
632
  if (typeof data === "object" && typeof data?.guildId === "string" && typeof data.playerOptions === "object" && Object.keys(data.playerOptions).length > 1) {
414
633
  const player = this.NodeManager.LavalinkManager.getPlayer(data.guildId);
@@ -419,8 +638,9 @@ class LavalinkNode {
419
638
  player.playing = !data.playerOptions.paused;
420
639
  }
421
640
  if (typeof data.playerOptions.position === "number") {
422
- player.position = data.playerOptions.position;
641
+ // player.position = data.playerOptions.position;
423
642
  player.lastPosition = data.playerOptions.position;
643
+ player.lastPositionChange = Date.now();
424
644
  }
425
645
  if (typeof data.playerOptions.voice !== "undefined")
426
646
  player.voice = data.playerOptions.voice;
@@ -471,9 +691,22 @@ class LavalinkNode {
471
691
  }
472
692
  return true;
473
693
  }
694
+ /**
695
+ * Get the rest Adress for making requests
696
+ */
474
697
  get restAddress() {
475
698
  return `http${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}`;
476
699
  }
700
+ /**
701
+ * Reconnect to the lavalink node
702
+ * @param instaReconnect @default false wether to instantly try to reconnect
703
+ * @returns void
704
+ *
705
+ * @example
706
+ * ```ts
707
+ * await player.node.reconnect();
708
+ * ```
709
+ */
477
710
  reconnect(instaReconnect = false) {
478
711
  if (instaReconnect) {
479
712
  if (this.reconnectAttempts >= this.options.retryAmount) {
@@ -501,6 +734,7 @@ class LavalinkNode {
501
734
  this.reconnectAttempts++;
502
735
  }, this.options.retryDelay || 1000);
503
736
  }
737
+ /** @private util function for handling opening events from websocket */
504
738
  async open() {
505
739
  if (this.reconnectTimeout)
506
740
  clearTimeout(this.reconnectTimeout);
@@ -513,16 +747,19 @@ class LavalinkNode {
513
747
  }
514
748
  this.NodeManager.emit("connect", this);
515
749
  }
750
+ /** @private util function for handling closing events from websocket */
516
751
  close(code, reason) {
517
752
  this.NodeManager.emit("disconnect", this, { code, reason });
518
753
  if (code !== 1000 || reason !== "Node-Destroy")
519
754
  this.reconnect();
520
755
  }
756
+ /** @private util function for handling error events from websocket */
521
757
  error(error) {
522
758
  if (!error)
523
759
  return;
524
760
  this.NodeManager.emit("error", this, error);
525
761
  }
762
+ /** @private util function for handling message events from websocket */
526
763
  async message(d) {
527
764
  if (Array.isArray(d))
528
765
  d = Buffer.concat(d);
@@ -543,42 +780,15 @@ class LavalinkNode {
543
780
  if (!player)
544
781
  return;
545
782
  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;
783
+ player.lastPositionChange = Date.now();
550
784
  player.lastPosition = payload.state.position || 0;
551
785
  player.connected = payload.state.connected;
552
786
  player.ping.ws = payload.state.ping >= 0 ? payload.state.ping : player.ping.ws <= 0 && player.connected ? null : player.ping.ws || 0;
553
787
  if (!player.createdTimeStamp && payload.state.time)
554
788
  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
- }
789
+ if (player.filterManager.filterUpdatedState === true && ((player.queue.current?.info?.duration || 0) <= (player.LavalinkManager.options.advancedOptions.maxFilterFixDuration || 600000) || (0, path_1.isAbsolute)(player.queue.current?.info?.uri))) {
790
+ player.filterManager.filterUpdatedState = false;
791
+ await player.seek(player.position);
582
792
  }
583
793
  this.NodeManager.LavalinkManager.emit("playerUpdate", oldPlayer, player);
584
794
  }
@@ -598,7 +808,7 @@ class LavalinkNode {
598
808
  return;
599
809
  }
600
810
  }
601
- // LAVALINK EVENT HANDLING UTIL FUNCTION
811
+ /** @private middleware util function for handling all kind of events from websocket */
602
812
  async handleEvent(payload) {
603
813
  if (!payload.guildId)
604
814
  return;
@@ -625,7 +835,7 @@ class LavalinkNode {
625
835
  this.SponsorBlockSegmentLoaded(player, player.queue.current, payload);
626
836
  break;
627
837
  case "SegmentSkipped":
628
- this.SponsorBlockSegmentkipped(player, player.queue.current, payload);
838
+ this.SponsorBlockSegmentSkipped(player, player.queue.current, payload);
629
839
  break;
630
840
  case "ChaptersLoaded":
631
841
  this.SponsorBlockChaptersLoaded(player, player.queue.current, payload);
@@ -639,7 +849,7 @@ class LavalinkNode {
639
849
  }
640
850
  return;
641
851
  }
642
- // LAVALINK EVENT HANDLING FUNCTIONS
852
+ /** @private util function for handling trackStart event */
643
853
  trackStart(player, track, payload) {
644
854
  player.playing = true;
645
855
  player.paused = false;
@@ -648,6 +858,7 @@ class LavalinkNode {
648
858
  return;
649
859
  return this.NodeManager.LavalinkManager.emit("trackStart", player, track, payload);
650
860
  }
861
+ /** @private util function for handling trackEnd event */
651
862
  async trackEnd(player, track, payload) {
652
863
  // If there are no songs in the queue
653
864
  if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying")))
@@ -684,6 +895,7 @@ class LavalinkNode {
684
895
  // play track if autoSkip is true
685
896
  return this.NodeManager.LavalinkManager.options.autoSkip && player.play({ noReplace: true });
686
897
  }
898
+ /** @private util function for handling trackStuck event */
687
899
  async trackStuck(player, track, payload) {
688
900
  this.NodeManager.LavalinkManager.emit("trackStuck", player, track, payload);
689
901
  // If there are no songs in the queue
@@ -697,6 +909,7 @@ class LavalinkNode {
697
909
  // play track if autoSkip is true
698
910
  return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
699
911
  }
912
+ /** @private util function for handling trackError event */
700
913
  async trackError(player, track, payload) {
701
914
  this.NodeManager.LavalinkManager.emit("trackError", player, track, payload);
702
915
  return; // get's handled by trackEnd
@@ -711,23 +924,37 @@ class LavalinkNode {
711
924
  // play track if autoSkip is true
712
925
  return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
713
926
  }
927
+ /** @private util function for handling socketClosed event */
714
928
  socketClosed(player, payload) {
715
929
  return this.NodeManager.LavalinkManager.emit("playerSocketClosed", player, payload);
716
930
  }
717
- // SPONSOR BLOCK EVENT FUNCTIONS
931
+ /** @private util function for handling SponsorBlock Segmentloaded event */
718
932
  SponsorBlockSegmentLoaded(player, track, payload) {
719
933
  return this.NodeManager.LavalinkManager.emit("SegmentsLoaded", player, track, payload);
720
934
  }
721
- SponsorBlockSegmentkipped(player, track, payload) {
935
+ /** @private util function for handling SponsorBlock SegmentSkipped event */
936
+ SponsorBlockSegmentSkipped(player, track, payload) {
722
937
  return this.NodeManager.LavalinkManager.emit("SegmentSkipped", player, track, payload);
723
938
  }
939
+ /** @private util function for handling SponsorBlock Chaptersloaded event */
724
940
  SponsorBlockChaptersLoaded(player, track, payload) {
725
941
  return this.NodeManager.LavalinkManager.emit("ChaptersLoaded", player, track, payload);
726
942
  }
943
+ /** @private util function for handling SponsorBlock Chaptersstarted event */
727
944
  SponsorBlockChapterStarted(player, track, payload) {
728
945
  return this.NodeManager.LavalinkManager.emit("ChapterStarted", player, track, payload);
729
946
  }
730
- // SPONSOR BLOCK EXECUTE FUNCTIONS
947
+ /**
948
+ * Get the current sponsorblocks for the sponsorblock plugin
949
+ * @param player passthrough the player
950
+ * @returns sponsorblock seggment from lavalink
951
+ *
952
+ * @example
953
+ * ```ts
954
+ * // use it on the player via player.getSponsorBlock();
955
+ * const sponsorBlockSegments = await player.node.getSponsorBlock(player);
956
+ * ```
957
+ */
731
958
  async getSponsorBlock(player) {
732
959
  // no plugin enabled
733
960
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -735,6 +962,17 @@ class LavalinkNode {
735
962
  // do the request
736
963
  return await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`);
737
964
  }
965
+ /**
966
+ * Set the current sponsorblocks for the sponsorblock plugin
967
+ * @param player passthrough the player
968
+ * @returns void
969
+ *
970
+ * @example
971
+ * ```ts
972
+ * // use it on the player via player.setSponsorBlock();
973
+ * const sponsorBlockSegments = await player.node.setSponsorBlock(player, ["sponsor", "selfpromo"]);
974
+ * ```
975
+ */
738
976
  async setSponsorBlock(player, segments = ["sponsor", "selfpromo"]) {
739
977
  // no plugin enabled
740
978
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -753,6 +991,17 @@ class LavalinkNode {
753
991
  });
754
992
  return;
755
993
  }
994
+ /**
995
+ * Delete the sponsorblock plugins
996
+ * @param player passthrough the player
997
+ * @returns void
998
+ *
999
+ * @example
1000
+ * ```ts
1001
+ * // use it on the player via player.deleteSponsorBlock();
1002
+ * const sponsorBlockSegments = await player.node.deleteSponsorBlock(player);
1003
+ * ```
1004
+ */
756
1005
  async deleteSponsorBlock(player) {
757
1006
  // no plugin enabled
758
1007
  if (!this.info.plugins.find(v => v.name === "sponsorblock-plugin"))
@@ -763,7 +1012,7 @@ class LavalinkNode {
763
1012
  });
764
1013
  return;
765
1014
  }
766
- // UTIL FOR QUEUE END
1015
+ /** private util function for handling the queue end event */
767
1016
  async queueEnd(player, track, payload) {
768
1017
  // add previous track to the queue!
769
1018
  player.queue.current = null;