lavalink-client 2.4.7 → 2.5.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 +24 -0
  2. package/dist/cjs/structures/CustomSearches/BandCampSearch.js +3 -1
  3. package/dist/cjs/structures/LavalinkManager.js +4 -4
  4. package/dist/cjs/structures/LavalinkManagerStatics.js +21 -0
  5. package/dist/cjs/structures/Node.d.ts +0 -12
  6. package/dist/cjs/structures/Node.js +32 -48
  7. package/dist/cjs/structures/Player.js +39 -0
  8. package/dist/cjs/structures/Types/Manager.d.ts +2 -0
  9. package/dist/cjs/structures/Types/Player.d.ts +2 -0
  10. package/dist/cjs/structures/Types/Track.d.ts +1 -1
  11. package/dist/cjs/structures/Types/Utils.d.ts +5 -5
  12. package/dist/cjs/structures/Utils.d.ts +2 -2
  13. package/dist/cjs/structures/Utils.js +30 -6
  14. package/dist/esm/structures/CustomSearches/BandCampSearch.js +3 -1
  15. package/dist/esm/structures/LavalinkManager.js +4 -4
  16. package/dist/esm/structures/LavalinkManagerStatics.js +21 -0
  17. package/dist/esm/structures/Node.d.ts +0 -12
  18. package/dist/esm/structures/Node.js +32 -48
  19. package/dist/esm/structures/Player.js +39 -0
  20. package/dist/esm/structures/Types/Manager.d.ts +2 -0
  21. package/dist/esm/structures/Types/Player.d.ts +2 -0
  22. package/dist/esm/structures/Types/Track.d.ts +1 -1
  23. package/dist/esm/structures/Types/Utils.d.ts +5 -5
  24. package/dist/esm/structures/Utils.d.ts +2 -2
  25. package/dist/esm/structures/Utils.js +30 -6
  26. package/dist/types/structures/Node.d.ts +0 -12
  27. package/dist/types/structures/Types/Manager.d.ts +2 -0
  28. package/dist/types/structures/Types/Player.d.ts +2 -0
  29. package/dist/types/structures/Types/Track.d.ts +1 -1
  30. package/dist/types/structures/Types/Utils.d.ts +5 -5
  31. package/dist/types/structures/Utils.d.ts +2 -2
  32. package/package.json +11 -11
package/README.md CHANGED
@@ -819,3 +819,27 @@ if(previousTrack) {
819
819
  - remove structuredClone so that it works in bun more stable
820
820
  - Player Options Validation also allows single property objects
821
821
  - Some typos were fixed
822
+
823
+
824
+
825
+ ## **Version 2.5.0**
826
+ - Deps got updated
827
+ - Handling of parsing url got updated to use the URL object
828
+ - Flowerytts requests needs to be changed now:
829
+
830
+ ```js
831
+ const query = "Hello World How are you?";
832
+ const voice = "Ava";
833
+ const speed = "1.0";
834
+
835
+ const fttsParams = new URLSearchParams();
836
+ if(voice) fttsParams.append("voice", voice);
837
+ if(speed) fttsParams.append("speed", speed);
838
+
839
+ const response = await player.search({
840
+ // For flowerytts you need to send a URL
841
+ // and if you want to add optiojns, this is how you add the params to the query..
842
+ query: `${encodeURI(query)}${fttsParams.size ? `?${fttsParams.toString()}` : ""}`,
843
+ source: "ftts"
844
+ }, interaction.user);
845
+ ```
@@ -8,7 +8,9 @@ const bandCampSearch = async (player, query, requestUser) => {
8
8
  console.log(`Lavalink-Client-Debug | SEARCHING | - ${query} on lavalink-client`);
9
9
  player.LavalinkManager.utils.validateQueryString(player.node, query);
10
10
  try {
11
- const data = await fetch(`https://bandcamp.com/api/nusearch/2/autocomplete?q=${encodeURIComponent(query)}`, {
11
+ const requestUrl = new URL("https://bandcamp.com/api/nusearch/2/autocomplete");
12
+ requestUrl.searchParams.append("q", query);
13
+ const data = await fetch(requestUrl.toString(), {
12
14
  headers: {
13
15
  'User-Agent': 'android-async-http/1.4.1 (http://loopj.com/android-async-http)',
14
16
  'Cookie': '$Version=1'
@@ -495,7 +495,7 @@ class LavalinkManager extends events_1.EventEmitter {
495
495
  functionLayer: "LavalinkManager > sendRawData()",
496
496
  });
497
497
  if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
498
- console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Sent updatePlayer for voice token session", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, playerVoice: player.voice, update });
498
+ console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Can't send updatePlayer for voice token session - Missing sessionId", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, update, playerVoice: player.voice });
499
499
  }
500
500
  else {
501
501
  await player.node.updatePlayer({
@@ -505,8 +505,8 @@ class LavalinkManager extends events_1.EventEmitter {
505
505
  token: update.token,
506
506
  endpoint: update.endpoint,
507
507
  sessionId: sessionId2Use,
508
- }
509
- }
508
+ },
509
+ },
510
510
  });
511
511
  if (this.options?.advancedOptions?.enableDebugEvents) {
512
512
  this.emit("debug", Constants_1.DebugEvents.NoAudioDebug, {
@@ -516,7 +516,7 @@ class LavalinkManager extends events_1.EventEmitter {
516
516
  });
517
517
  }
518
518
  if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
519
- console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Can't send updatePlayer for voice token session - Missing sessionId", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, } });
519
+ console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Sent updatePlayer for voice token session", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, playerVoice: player.voice, update });
520
520
  }
521
521
  return;
522
522
  }
@@ -39,11 +39,24 @@ exports.DefaultSources = {
39
39
  "dz": "dzsearch",
40
40
  "dzsearch": "dzsearch",
41
41
  "dzisrc": "dzisrc",
42
+ "dzrec": "dzrec",
42
43
  // yandexmusic
43
44
  "yandex music": "ymsearch",
44
45
  "yandexmusic": "ymsearch",
45
46
  "yandex": "ymsearch",
46
47
  "ymsearch": "ymsearch",
48
+ "ymrec": "ymrec",
49
+ // VK Music (lavasrc)
50
+ "vksearch": "vksearch",
51
+ "vkmusic": "vksearch",
52
+ "vk music": "vksearch",
53
+ "vkrec": "vkrec",
54
+ "vk": "vksearch",
55
+ // Qobuz (lavasrc)
56
+ "qbsearch": "qbsearch",
57
+ "qobuz": "qbsearch",
58
+ "qbisrc": "qbisrc",
59
+ "qbrec": "qbrec",
47
60
  // speak PLUGIN
48
61
  "speak": "speak",
49
62
  "tts": "tts",
@@ -66,6 +79,12 @@ exports.DefaultSources = {
66
79
  "https": "https",
67
80
  "link": "link",
68
81
  "uri": "uri",
82
+ // tidal
83
+ "tidal": "tdsearch",
84
+ "td": "tdsearch",
85
+ "tidal music": "tdsearch",
86
+ "tdsearch": "tdsearch",
87
+ "tdrec": "tdrec",
69
88
  // jiosaavn
70
89
  "jiosaavn": "jssearch",
71
90
  "js": "jssearch",
@@ -118,6 +137,8 @@ exports.SourceLinksRegexes = {
118
137
  SpotifyAlbumRegex: /(https?:\/\/)(www\.)?open\.spotify\.com\/((?<region>[a-zA-Z-]+)\/)?(user\/(?<user>[a-zA-Z0-9-_]+)\/)?album\/(?<identifier>[a-zA-Z0-9-_]+)/,
119
138
  AllSpotifyRegex: /(https?:\/\/)(www\.)?open\.spotify\.com\/((?<region>[a-zA-Z-]+)\/)?(user\/(?<user>[a-zA-Z0-9-_]+)\/)?(?<type>track|album|playlist|artist|episode|show)\/(?<identifier>[a-zA-Z0-9-_]+)/,
120
139
  appleMusic: /https?:\/\/?(?:www\.)?music\.apple\.com\/(\S+)/,
140
+ /** From tidal */
141
+ tidal: /https?:\/\/?(?:www\.)?(?:tidal|listen)\.tidal\.com\/(?<type>track|album|playlist|artist)\/(?<identifier>[a-zA-Z0-9-_]+)/,
121
142
  /** From jiosaavn-plugin */
122
143
  jiosaavn: /(https?:\/\/)(www\.)?jiosaavn\.com\/(?<type>song|album|featured|artist)\/([a-zA-Z0-9-_\/,]+)/,
123
144
  /** FROM DUNCTE BOT PLUGIN */
@@ -53,18 +53,6 @@ export declare class LavalinkNode {
53
53
  * ```
54
54
  */
55
55
  constructor(options: LavalinkNodeOptions, manager: NodeManager);
56
- /**
57
- * Parse url params correctly for lavalink requests, including support for urls and uris.
58
- * @param url input url object
59
- * @param extraQueryUrlParams UrlSearchParams to use in a encodedURI, useful for example for flowertts
60
- * @returns the url as a valid string
61
- *
62
- * @example
63
- * ```ts
64
- * player.node.getRequestingUrl(new URL(`http://localhost:2333/v4/loadtracks?identifier=Never gonna give you up`));
65
- * ```
66
- */
67
- private getRequestingUrl;
68
56
  /**
69
57
  * Raw Request util function
70
58
  * @param endpoint endpoint string
@@ -91,32 +91,6 @@ class LavalinkNode {
91
91
  this.options.regions = (this.options.regions || []).map(a => a.toLowerCase());
92
92
  Object.defineProperty(this, Utils_1.NodeSymbol, { configurable: true, value: true });
93
93
  }
94
- /**
95
- * Parse url params correctly for lavalink requests, including support for urls and uris.
96
- * @param url input url object
97
- * @param extraQueryUrlParams UrlSearchParams to use in a encodedURI, useful for example for flowertts
98
- * @returns the url as a valid string
99
- *
100
- * @example
101
- * ```ts
102
- * player.node.getRequestingUrl(new URL(`http://localhost:2333/v4/loadtracks?identifier=Never gonna give you up`));
103
- * ```
104
- */
105
- getRequestingUrl(url, extraQueryUrlParams) {
106
- if (!url.searchParams.size)
107
- return `${url.origin}${url.pathname}`;
108
- const keysToAdd = [];
109
- for (const [paramKey, paramValue] of url.searchParams.entries()) {
110
- const decoded = decodeURIComponent(paramValue).trim(); // double decoding, once internally, a second time if decoded by provided user.
111
- if (decoded.includes("://") && !/^https?:\/\//.test(decoded)) { // uri, but not url.
112
- const [key, ...values] = decoded.split("://");
113
- keysToAdd.push(`${paramKey}=${encodeURI(`${key}://${encodeURIComponent(values.join("://"))}${extraQueryUrlParams && extraQueryUrlParams?.size > 0 ? `?${extraQueryUrlParams.toString()}` : ""}`)}`);
114
- continue;
115
- }
116
- keysToAdd.push(`${paramKey}=${encodeURIComponent(decoded)}`);
117
- }
118
- return `${url.origin}${url.pathname}?${keysToAdd.join("&")}`;
119
- }
120
94
  /**
121
95
  * Raw Request util function
122
96
  * @param endpoint endpoint string
@@ -131,7 +105,7 @@ class LavalinkNode {
131
105
  */
132
106
  async rawRequest(endpoint, modify) {
133
107
  const options = {
134
- path: `/${this.version}/${endpoint.replace(/^\//gm, "")}`,
108
+ path: `/${this.version}/${endpoint.startsWith("/") ? endpoint.slice(1) : endpoint}`,
135
109
  method: "GET",
136
110
  headers: {
137
111
  "Authorization": this.options.authorization
@@ -141,7 +115,12 @@ class LavalinkNode {
141
115
  modify?.(options);
142
116
  const url = new URL(`${this.restAddress}${options.path}`);
143
117
  url.searchParams.append("trace", "true");
144
- const urlToUse = this.getRequestingUrl(url, options?.extraQueryUrlParams);
118
+ if (options.extraQueryUrlParams && options.extraQueryUrlParams?.size > 0) {
119
+ for (const [paramKey, paramValue] of options.extraQueryUrlParams.entries()) {
120
+ url.searchParams.append(paramKey, paramValue);
121
+ }
122
+ }
123
+ const urlToUse = url.toString();
145
124
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
146
125
  const { path, extraQueryUrlParams, ...fetchOptions } = options; // destructure fetch only options
147
126
  const response = await fetch(urlToUse, fetchOptions);
@@ -180,20 +159,17 @@ class LavalinkNode {
180
159
  if (["bcsearch", "bandcamp"].includes(Query.source) && !this.info.sourceManagers.includes("bandcamp")) {
181
160
  throw new Error("Bandcamp Search only works on the player (lavaplayer version < 2.2.0!");
182
161
  }
183
- let uri = `/loadtracks?identifier=`;
162
+ const requestUrl = new URL(`${this.restAddress}/loadtracks`);
184
163
  if (/^https?:\/\//.test(Query.query) || ["http", "https", "link", "uri"].includes(Query.source)) { // if it's a link simply encode it
185
- const url = encodeURIComponent(Query.query);
186
- uri += url;
164
+ requestUrl.searchParams.append("identifier", Query.query);
187
165
  }
188
166
  else { // if not make a query out of it
189
- if (Query.source !== "local")
190
- uri += `${Query.source}:`; // only add the query source string if it's not a local track
191
- if (Query.source === "ftts")
192
- uri += `//${encodeURIComponent(Query.query)}`;
193
- else
194
- uri += encodeURIComponent(Query.query);
167
+ const fttsPrefix = Query.source === "ftts" ? "//" : "";
168
+ const prefix = Query.source !== "local" ? `${Query.source}:${fttsPrefix}` : "";
169
+ requestUrl.searchParams.append("identifier", `${prefix}${Query.query}`);
195
170
  }
196
- const res = await this.request(uri, (options) => {
171
+ const requestPathAndSearch = requestUrl.pathname + requestUrl.search;
172
+ const res = await this.request(requestPathAndSearch, (options) => {
197
173
  if (typeof query === "object" && typeof query.extraQueryUrlParams?.size === "number" && query.extraQueryUrlParams?.size > 0) {
198
174
  options.extraQueryUrlParams = query.extraQueryUrlParams;
199
175
  }
@@ -221,7 +197,7 @@ class LavalinkNode {
221
197
  thumbnail: (res.data.info?.artworkUrl) || (res.data.pluginInfo?.artworkUrl) || ((typeof res.data?.info?.selectedTrack !== "number" || res.data?.info?.selectedTrack === -1) ? null : resTracks[res.data?.info?.selectedTrack] ? (resTracks[res.data?.info?.selectedTrack]?.info?.artworkUrl || resTracks[res.data?.info?.selectedTrack]?.info?.pluginInfo?.artworkUrl) : null) || null,
222
198
  uri: res.data.info?.url || res.data.info?.uri || res.data.info?.link || res.data.pluginInfo?.url || res.data.pluginInfo?.uri || res.data.pluginInfo?.link || null,
223
199
  selectedTrack: typeof res.data?.info?.selectedTrack !== "number" || res.data?.info?.selectedTrack === -1 ? null : resTracks[res.data?.info?.selectedTrack] ? this.NodeManager.LavalinkManager.utils.buildTrack(resTracks[res.data?.info?.selectedTrack], requestUser) : null,
224
- duration: resTracks.length ? resTracks.reduce((acc, cur) => acc + (cur?.info?.length || 0), 0) : 0,
200
+ duration: resTracks.length ? resTracks.reduce((acc, cur) => acc + (cur?.info?.duration || cur?.info?.length || 0), 0) : 0,
225
201
  } : null,
226
202
  tracks: (resTracks.length ? resTracks.map(t => this.NodeManager.LavalinkManager.utils.buildTrack(t, requestUser)) : [])
227
203
  };
@@ -1090,7 +1066,7 @@ class LavalinkNode {
1090
1066
  player.ping.ws = payload.state.ping >= 0 ? payload.state.ping : player.ping.ws <= 0 && player.connected ? null : player.ping.ws || 0;
1091
1067
  if (!player.createdTimeStamp && payload.state.time)
1092
1068
  player.createdTimeStamp = payload.state.time;
1093
- if (player.filterManager.filterUpdatedState === true && ((player.queue.current?.info?.duration || 0) <= (player.LavalinkManager.options.advancedOptions.maxFilterFixDuration || 600_000) || (0, path_1.isAbsolute)(player.queue.current?.info?.uri))) {
1069
+ if (player.filterManager.filterUpdatedState === true && ((player.queue.current?.info?.duration || 0) <= (player.LavalinkManager.options.advancedOptions.maxFilterFixDuration || 600_000) || (player.queue.current?.info?.uri && (0, path_1.isAbsolute)(player.queue.current?.info?.uri)))) {
1094
1070
  player.filterManager.filterUpdatedState = false;
1095
1071
  if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1096
1072
  this.NodeManager.LavalinkManager.emit("debug", Constants_1.DebugEvents.PlayerUpdateFilterFixApply, {
@@ -1295,8 +1271,15 @@ class LavalinkNode {
1295
1271
  }
1296
1272
  this.NodeManager.LavalinkManager.emit("trackStuck", player, track || this.getTrackOfPayload(payload), payload);
1297
1273
  // If there are no songs in the queue
1298
- if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying")))
1299
- return this.queueEnd(player, track || this.getTrackOfPayload(payload), payload);
1274
+ if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying"))) {
1275
+ try { //Sometimes the trackStuck event triggers from the Lavalink server, but the track continues playing or resumes after. We explicitly end the track in such cases
1276
+ await player.node.updatePlayer({ guildId: player.guildId, playerOptions: { track: { encoded: null } } }); //trackEnd -> queueEnd
1277
+ return;
1278
+ }
1279
+ catch {
1280
+ return this.queueEnd(player, track || this.getTrackOfPayload(payload), payload);
1281
+ }
1282
+ }
1300
1283
  // remove the current track, and enqueue the next one
1301
1284
  await (0, Utils_1.queueTrackEnd)(player);
1302
1285
  // if no track available, end queue
@@ -1305,7 +1288,7 @@ class LavalinkNode {
1305
1288
  }
1306
1289
  // play track if autoSkip is true
1307
1290
  if (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) {
1308
- player.play({ noReplace: true });
1291
+ player.play({ track: player.queue.current, noReplace: false }); // Replace the stuck track with the new track.
1309
1292
  }
1310
1293
  return;
1311
1294
  }
@@ -1336,22 +1319,22 @@ class LavalinkNode {
1336
1319
  return;
1337
1320
  }
1338
1321
  /** @private util function for handling SponsorBlock Segmentloaded event */
1339
- async SponsorBlockSegmentLoaded(player, track, payload) {
1322
+ SponsorBlockSegmentLoaded(player, track, payload) {
1340
1323
  this.NodeManager.LavalinkManager.emit("SegmentsLoaded", player, track || this.getTrackOfPayload(payload), payload);
1341
1324
  return;
1342
1325
  }
1343
1326
  /** @private util function for handling SponsorBlock SegmentSkipped event */
1344
- async SponsorBlockSegmentSkipped(player, track, payload) {
1327
+ SponsorBlockSegmentSkipped(player, track, payload) {
1345
1328
  this.NodeManager.LavalinkManager.emit("SegmentSkipped", player, track || this.getTrackOfPayload(payload), payload);
1346
1329
  return;
1347
1330
  }
1348
1331
  /** @private util function for handling SponsorBlock Chaptersloaded event */
1349
- async SponsorBlockChaptersLoaded(player, track, payload) {
1332
+ SponsorBlockChaptersLoaded(player, track, payload) {
1350
1333
  this.NodeManager.LavalinkManager.emit("ChaptersLoaded", player, track || this.getTrackOfPayload(payload), payload);
1351
1334
  return;
1352
1335
  }
1353
1336
  /** @private util function for handling SponsorBlock Chaptersstarted event */
1354
- async SponsorBlockChapterStarted(player, track, payload) {
1337
+ SponsorBlockChapterStarted(player, track, payload) {
1355
1338
  this.NodeManager.LavalinkManager.emit("ChapterStarted", player, track || this.getTrackOfPayload(payload), payload);
1356
1339
  return;
1357
1340
  }
@@ -1477,7 +1460,8 @@ class LavalinkNode {
1477
1460
  if (player.queue.current) {
1478
1461
  if (payload.type === "TrackEndEvent")
1479
1462
  this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
1480
- return player.play({ noReplace: true, paused: false });
1463
+ if (this.NodeManager.LavalinkManager.options.autoSkip)
1464
+ return player.play({ noReplace: true, paused: false });
1481
1465
  }
1482
1466
  }
1483
1467
  else {
@@ -74,6 +74,9 @@ class Player {
74
74
  * @param LavalinkManager
75
75
  */
76
76
  constructor(options, LavalinkManager) {
77
+ if (typeof options?.customData === "object")
78
+ for (const [key, value] of Object.entries(options.customData))
79
+ this.set(key, value);
77
80
  this.options = options;
78
81
  this.filterManager = new Filters_1.FilterManager(this);
79
82
  this.LavalinkManager = LavalinkManager;
@@ -267,6 +270,8 @@ class Player {
267
270
  delete options.clientTrack;
268
271
  if (options && "track" in options)
269
272
  delete options.track;
273
+ // get rid of the current song without shifting the queue, so that the shifting can happen inside the next .play() call when "autoSkipOnResolveError" is true
274
+ await (0, Utils_1.queueTrackEnd)(this, true);
270
275
  // try to play the next track if possible
271
276
  if (this.LavalinkManager.options?.autoSkipOnResolveError === true && this.queue.tracks[0])
272
277
  return this.play(options);
@@ -402,6 +407,8 @@ class Player {
402
407
  const now = performance.now();
403
408
  await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { paused: true } });
404
409
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
410
+ // emit the event
411
+ this.LavalinkManager.emit("playerPaused", this, this.queue.current);
405
412
  return this;
406
413
  }
407
414
  /**
@@ -414,6 +421,8 @@ class Player {
414
421
  const now = performance.now();
415
422
  await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { paused: false } });
416
423
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
424
+ // emit the event
425
+ this.LavalinkManager.emit("playerResumed", this, this.queue.current);
417
426
  return this;
418
427
  }
419
428
  /**
@@ -679,6 +688,7 @@ class Player {
679
688
  }
680
689
  const data = this.toJSON();
681
690
  const currentTrack = this.queue.current;
691
+ const segments = await this.getSponsorBlock().catch(() => []);
682
692
  const voiceData = this.voice;
683
693
  if (!voiceData.endpoint ||
684
694
  !voiceData.sessionId ||
@@ -703,6 +713,33 @@ class Player {
703
713
  }
704
714
  });
705
715
  });
716
+ const hasSponsorBlock = this.node.info?.plugins?.find(v => v.name === "sponsorblock-plugin");
717
+ if (hasSponsorBlock) {
718
+ if (segments.length) {
719
+ await this.setSponsorBlock(segments).catch(error => {
720
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
721
+ this.LavalinkManager.emit("debug", Constants_1.DebugEvents.PlayerChangeNode, {
722
+ state: "error",
723
+ error: error,
724
+ message: `Player > changeNode() Unable to set SponsorBlock Segments`,
725
+ functionLayer: "Player > changeNode()",
726
+ });
727
+ }
728
+ });
729
+ }
730
+ else {
731
+ await this.setSponsorBlock().catch(error => {
732
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
733
+ this.LavalinkManager.emit("debug", Constants_1.DebugEvents.PlayerChangeNode, {
734
+ state: "error",
735
+ error: error,
736
+ message: `Player > changeNode() Unable to set SponsorBlock Segments`,
737
+ functionLayer: "Player > changeNode()",
738
+ });
739
+ }
740
+ });
741
+ }
742
+ }
706
743
  if (currentTrack) { // If there is a current track, send it to the new node.
707
744
  await this.node.updatePlayer({
708
745
  guildId: this.guildId,
@@ -716,6 +753,8 @@ class Player {
716
753
  }
717
754
  });
718
755
  }
756
+ this.paused = data.paused;
757
+ this.playing = data.playing;
719
758
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
720
759
  return this.node.id;
721
760
  }
@@ -172,6 +172,8 @@ export interface LavalinkManagerEvents {
172
172
  * @event Manager#LyricsNotFound
173
173
  */
174
174
  "LyricsNotFound": (player: Player, track: Track | UnresolvedTrack | null, payload: LyricsNotFoundEvent) => void;
175
+ "playerResumed": (player: Player, track: Track | UnresolvedTrack | null) => void;
176
+ "playerPaused": (player: Player, track: Track | UnresolvedTrack | null) => void;
175
177
  }
176
178
  /**
177
179
  * The Bot client Options needed for the manager
@@ -73,6 +73,8 @@ export interface PlayerOptions {
73
73
  instaUpdateFiltersFix?: boolean;
74
74
  /** If a volume should be applied via filters instead of lavalink-volume */
75
75
  applyVolumeAsFilter?: boolean;
76
+ /** Custom Data for the player get/set datastorage */
77
+ customData?: anyObject;
76
78
  }
77
79
  export type anyObject = {
78
80
  [key: string | number]: string | number | null | anyObject;
@@ -4,7 +4,7 @@ import type { Base64 } from "./Utils.js";
4
4
  /** Sourcenames provided by lavalink server */
5
5
  export type LavalinkSourceNames = "youtube" | "youtubemusic" | "soundcloud" | "bandcamp" | "twitch";
6
6
  /** Source Names provided by lava src plugin */
7
- export type LavalinkPlugin_LavaSrc_SourceNames = "deezer" | "spotify" | "applemusic" | "yandexmusic" | "flowery-tts";
7
+ export type LavalinkPlugin_LavaSrc_SourceNames = "deezer" | "spotify" | "applemusic" | "yandexmusic" | "flowery-tts" | "vkmusic" | "tidal" | "qobuz";
8
8
  /** Source Names provided by jiosaavan plugin */
9
9
  export type LavalinkPlugin_JioSaavn_SourceNames = "jiosaavn";
10
10
  /** The SourceNames provided by lavalink */
@@ -11,7 +11,7 @@ export type Opaque<T, K> = T & {
11
11
  export type IntegerNumber = Opaque<number, 'Int'>;
12
12
  /** Opqaue tyep for floatnumber */
13
13
  export type FloatNumber = Opaque<number, 'Float'>;
14
- export type LavaSrcSearchPlatformBase = "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ymsearch";
14
+ export type LavaSrcSearchPlatformBase = "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "dzrec" | "ymsearch" | "ymrec" | "vksearch" | "vkrec" | "tdsearch" | "tdrec" | "qbsearch" | "qbisrc" | "qbrec";
15
15
  export type LavaSrcSearchPlatform = LavaSrcSearchPlatformBase | "ftts";
16
16
  export type JioSaavnSearchPlatform = "jssearch" | "jsrec";
17
17
  export type DuncteSearchPlatform = "speak" | "phsearch" | "pornhub" | "porn" | "tts";
@@ -20,9 +20,9 @@ export type LavalinkClientSearchPlatformResolve = "bandcamp" | "bc";
20
20
  export type LavalinkSearchPlatform = "ytsearch" | "ytmsearch" | "scsearch" | "bcsearch" | LavaSrcSearchPlatform | DuncteSearchPlatform | JioSaavnSearchPlatform | LavalinkClientSearchPlatform;
21
21
  export type ClientCustomSearchPlatformUtils = "local" | "http" | "https" | "link" | "uri";
22
22
  export type ClientSearchPlatform = ClientCustomSearchPlatformUtils | // for file/link requests
23
- "youtube" | "yt" | "youtube music" | "youtubemusic" | "ytm" | "musicyoutube" | "music youtube" | "soundcloud" | "sc" | "am" | "apple music" | "applemusic" | "apple" | "musicapple" | "music apple" | "sp" | "spsuggestion" | "spotify" | "spotify.com" | "spotifycom" | "dz" | "deezer" | "yandex" | "yandex music" | "yandexmusic" | "flowerytts" | "flowery" | "flowery.tts" | LavalinkClientSearchPlatformResolve | LavalinkClientSearchPlatform | "js" | "jiosaavn";
23
+ "youtube" | "yt" | "youtube music" | "youtubemusic" | "ytm" | "musicyoutube" | "music youtube" | "soundcloud" | "sc" | "am" | "apple music" | "applemusic" | "apple" | "musicapple" | "music apple" | "sp" | "spsuggestion" | "spotify" | "spotify.com" | "spotifycom" | "dz" | "deezer" | "yandex" | "yandex music" | "yandexmusic" | "vk" | "vk music" | "vkmusic" | "tidal" | "tidal music" | "qobuz" | "flowerytts" | "flowery" | "flowery.tts" | LavalinkClientSearchPlatformResolve | LavalinkClientSearchPlatform | "js" | "jiosaavn" | "td" | "tidal" | "tdrec";
24
24
  export type SearchPlatform = LavalinkSearchPlatform | ClientSearchPlatform;
25
- export type SourcesRegex = "YoutubeRegex" | "YoutubeMusicRegex" | "SoundCloudRegex" | "SoundCloudMobileRegex" | "DeezerTrackRegex" | "DeezerArtistRegex" | "DeezerEpisodeRegex" | "DeezerMixesRegex" | "DeezerPageLinkRegex" | "DeezerPlaylistRegex" | "DeezerAlbumRegex" | "AllDeezerRegex" | "AllDeezerRegexWithoutPageLink" | "SpotifySongRegex" | "SpotifyPlaylistRegex" | "SpotifyArtistRegex" | "SpotifyEpisodeRegex" | "SpotifyShowRegex" | "SpotifyAlbumRegex" | "AllSpotifyRegex" | "mp3Url" | "m3uUrl" | "m3u8Url" | "mp4Url" | "m4aUrl" | "wavUrl" | "aacpUrl" | "tiktok" | "mixcloud" | "musicYandex" | "radiohost" | "bandcamp" | "jiosaavn" | "appleMusic" | "TwitchTv" | "vimeo";
25
+ export type SourcesRegex = "YoutubeRegex" | "YoutubeMusicRegex" | "SoundCloudRegex" | "SoundCloudMobileRegex" | "DeezerTrackRegex" | "DeezerArtistRegex" | "DeezerEpisodeRegex" | "DeezerMixesRegex" | "DeezerPageLinkRegex" | "DeezerPlaylistRegex" | "DeezerAlbumRegex" | "AllDeezerRegex" | "AllDeezerRegexWithoutPageLink" | "SpotifySongRegex" | "SpotifyPlaylistRegex" | "SpotifyArtistRegex" | "SpotifyEpisodeRegex" | "SpotifyShowRegex" | "SpotifyAlbumRegex" | "AllSpotifyRegex" | "mp3Url" | "m3uUrl" | "m3u8Url" | "mp4Url" | "m4aUrl" | "wavUrl" | "aacpUrl" | "tiktok" | "mixcloud" | "musicYandex" | "radiohost" | "bandcamp" | "jiosaavn" | "appleMusic" | "tidal" | "TwitchTv" | "vimeo";
26
26
  export interface PlaylistInfo {
27
27
  /** The playlist name */
28
28
  name: string;
@@ -92,13 +92,13 @@ export interface TrackEndEvent extends PlayerEvent {
92
92
  export interface TrackExceptionEvent extends PlayerEvent {
93
93
  type: "TrackExceptionEvent";
94
94
  exception?: Exception;
95
- tracK: LavalinkTrack;
95
+ track: LavalinkTrack;
96
96
  error: string;
97
97
  }
98
98
  export interface TrackStuckEvent extends PlayerEvent {
99
99
  type: "TrackStuckEvent";
100
100
  thresholdMs: number;
101
- tracK: LavalinkTrack;
101
+ track: LavalinkTrack;
102
102
  }
103
103
  export interface WebSocketClosedEvent extends PlayerEvent {
104
104
  type: "WebSocketClosedEvent";
@@ -20,7 +20,7 @@ export declare function parseLavalinkConnUrl(connectionUrl: string): {
20
20
  port: number;
21
21
  };
22
22
  export declare class ManagerUtils {
23
- LavalinkManager: LavalinkManager | null;
23
+ LavalinkManager: LavalinkManager | undefined;
24
24
  constructor(LavalinkManager?: LavalinkManager);
25
25
  buildPluginInfo(data: any, clientData?: any): any;
26
26
  buildTrack(data: LavalinkTrack | Track, requester: unknown): Track;
@@ -112,4 +112,4 @@ export declare class MiniMap<K, V> extends Map<K, V> {
112
112
  map<T>(fn: (value: V, key: K, miniMap: this) => T): T[];
113
113
  map<This, T>(fn: (this: This, value: V, key: K, miniMap: this) => T, thisArg: This): T[];
114
114
  }
115
- export declare function queueTrackEnd(player: Player): Promise<Track>;
115
+ export declare function queueTrackEnd(player: Player, dontShiftQueue?: boolean): Promise<Track>;
@@ -30,7 +30,7 @@ function parseLavalinkConnUrl(connectionUrl) {
30
30
  };
31
31
  }
32
32
  class ManagerUtils {
33
- LavalinkManager = null;
33
+ LavalinkManager = undefined;
34
34
  constructor(LavalinkManager) {
35
35
  this.LavalinkManager = LavalinkManager;
36
36
  }
@@ -307,6 +307,9 @@ class ManagerUtils {
307
307
  if (LavalinkManagerStatics_1.SourceLinksRegexes.jiosaavn.test(queryString) && !node.info?.sourceManagers?.includes("jiosaavn")) {
308
308
  throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'jiosaavn' (via jiosaavn-plugin) enabled");
309
309
  }
310
+ if (LavalinkManagerStatics_1.SourceLinksRegexes.tidal.test(queryString) && !node.info?.sourceManagers?.includes("tidal")) {
311
+ throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'tidal' enabled");
312
+ }
310
313
  return;
311
314
  }
312
315
  transformQuery(query) {
@@ -371,6 +374,12 @@ class ManagerUtils {
371
374
  if (source === "speak" && !node.info?.plugins?.find(c => c.name.toLowerCase().includes(LavalinkManagerStatics_1.LavalinkPlugins.DuncteBot_Plugin.toLowerCase()))) {
372
375
  throw new Error("Lavalink Node has not 'speak' enabled, which is required to have 'speak' work");
373
376
  }
377
+ if (source === "tdsearch" && !node.info?.sourceManagers?.includes("tidal")) {
378
+ throw new Error("Lavalink Node has not 'tidal' enabled, which is required to have 'tdsearch' work");
379
+ }
380
+ if (source === "tdrec" && !node.info?.sourceManagers?.includes("tidal")) {
381
+ throw new Error("Lavalink Node has not 'tidal' enabled, which is required to have 'tdrec' work");
382
+ }
374
383
  if (source === "tts" && !node.info?.plugins?.find(c => c.name.toLowerCase().includes(LavalinkManagerStatics_1.LavalinkPlugins.GoogleCloudTTS.toLowerCase()))) {
375
384
  throw new Error("Lavalink Node has not 'tts' enabled, which is required to have 'tts' work");
376
385
  }
@@ -386,6 +395,21 @@ class ManagerUtils {
386
395
  if (source === "ytsearch" && !node.info?.sourceManagers?.includes("youtube")) {
387
396
  throw new Error("Lavalink Node has not 'youtube' enabled, which is required to have 'ytsearch' work");
388
397
  }
398
+ if (source === "vksearch" && !node.info?.sourceManagers?.includes("vkmusic")) {
399
+ throw new Error("Lavalink Node has not 'vkmusic' enabled, which is required to have 'vksearch' work");
400
+ }
401
+ if (source === "vkrec" && !node.info?.sourceManagers?.includes("vkmusic")) {
402
+ throw new Error("Lavalink Node has not 'vkmusic' enabled, which is required to have 'vkrec' work");
403
+ }
404
+ if (source === "qbsearch" && !node.info?.sourceManagers?.includes("qobuz")) {
405
+ throw new Error("Lavalink Node has not 'qobuz' enabled, which is required to have 'qbsearch' work");
406
+ }
407
+ if (source === "qbisrc" && !node.info?.sourceManagers?.includes("qobuz")) {
408
+ throw new Error("Lavalink Node has not 'qobuz' enabled, which is required to have 'qbisrc' work");
409
+ }
410
+ if (source === "qbrec" && !node.info?.sourceManagers?.includes("qobuz")) {
411
+ throw new Error("Lavalink Node has not 'qobuz' enabled, which is required to have 'qbrec' work");
412
+ }
389
413
  return;
390
414
  }
391
415
  }
@@ -418,7 +442,7 @@ class MiniMap extends Map {
418
442
  }
419
443
  }
420
444
  exports.MiniMap = MiniMap;
421
- async function queueTrackEnd(player) {
445
+ async function queueTrackEnd(player, dontShiftQueue = false) {
422
446
  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.
423
447
  player.queue.previous.unshift(player.queue.current);
424
448
  if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
@@ -428,10 +452,10 @@ async function queueTrackEnd(player) {
428
452
  // and if repeatMode == queue, add it back to the queue!
429
453
  if (player.repeatMode === "queue" && player.queue.current)
430
454
  player.queue.tracks.push(player.queue.current);
431
- // change the current Track to the next upcoming one
432
- const nextSong = player.queue.tracks.shift();
455
+ // change the current Track to the next upcoming one
456
+ const nextSong = dontShiftQueue ? null : player.queue.tracks.shift();
433
457
  try {
434
- if (player.LavalinkManager.utils.isUnresolvedTrack(nextSong))
458
+ if (nextSong && player.LavalinkManager.utils.isUnresolvedTrack(nextSong))
435
459
  await nextSong.resolve(player);
436
460
  player.queue.current = nextSong || null;
437
461
  // save it in the DB
@@ -448,7 +472,7 @@ async function queueTrackEnd(player) {
448
472
  }
449
473
  player.LavalinkManager.emit("trackError", player, player.queue.current, error);
450
474
  // try to play the next track if possible
451
- if (player.LavalinkManager.options?.autoSkipOnResolveError === true && player.queue.tracks[0])
475
+ if (!dontShiftQueue && player.LavalinkManager.options?.autoSkipOnResolveError === true && player.queue.tracks[0])
452
476
  return queueTrackEnd(player);
453
477
  }
454
478
  // return the new current Track
@@ -5,7 +5,9 @@ export const bandCampSearch = async (player, query, requestUser) => {
5
5
  console.log(`Lavalink-Client-Debug | SEARCHING | - ${query} on lavalink-client`);
6
6
  player.LavalinkManager.utils.validateQueryString(player.node, query);
7
7
  try {
8
- const data = await fetch(`https://bandcamp.com/api/nusearch/2/autocomplete?q=${encodeURIComponent(query)}`, {
8
+ const requestUrl = new URL("https://bandcamp.com/api/nusearch/2/autocomplete");
9
+ requestUrl.searchParams.append("q", query);
10
+ const data = await fetch(requestUrl.toString(), {
9
11
  headers: {
10
12
  'User-Agent': 'android-async-http/1.4.1 (http://loopj.com/android-async-http)',
11
13
  'Cookie': '$Version=1'
@@ -492,7 +492,7 @@ export class LavalinkManager extends EventEmitter {
492
492
  functionLayer: "LavalinkManager > sendRawData()",
493
493
  });
494
494
  if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
495
- console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Sent updatePlayer for voice token session", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, playerVoice: player.voice, update });
495
+ console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Can't send updatePlayer for voice token session - Missing sessionId", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, update, playerVoice: player.voice });
496
496
  }
497
497
  else {
498
498
  await player.node.updatePlayer({
@@ -502,8 +502,8 @@ export class LavalinkManager extends EventEmitter {
502
502
  token: update.token,
503
503
  endpoint: update.endpoint,
504
504
  sessionId: sessionId2Use,
505
- }
506
- }
505
+ },
506
+ },
507
507
  });
508
508
  if (this.options?.advancedOptions?.enableDebugEvents) {
509
509
  this.emit("debug", DebugEvents.NoAudioDebug, {
@@ -513,7 +513,7 @@ export class LavalinkManager extends EventEmitter {
513
513
  });
514
514
  }
515
515
  if (this.options?.advancedOptions?.debugOptions?.noAudio === true)
516
- console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Can't send updatePlayer for voice token session - Missing sessionId", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, } });
516
+ console.debug("Lavalink-Client-Debug | NO-AUDIO [::] sendRawData function, Sent updatePlayer for voice token session", { voice: { token: update.token, endpoint: update.endpoint, sessionId: sessionId2Use, }, playerVoice: player.voice, update });
517
517
  }
518
518
  return;
519
519
  }