lavalink-client 2.4.6 → 2.5.0

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.
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'
@@ -39,11 +39,18 @@ 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",
47
54
  // speak PLUGIN
48
55
  "speak": "speak",
49
56
  "tts": "tts",
@@ -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
  };
@@ -754,9 +730,7 @@ class LavalinkNode {
754
730
  if (!this.info.plugins.find(v => v.name === "lavasrc-plugin") &&
755
731
  !this.info.plugins.find(v => v.name === "java-lyrics-plugin"))
756
732
  throw new RangeError(`there is no lyrics source (via lavasrc-plugin / java-lyrics-plugin) available in the lavalink node (required for lyrics): ${this.id}`);
757
- return await this.request(`/sessions/${this.sessionId}/players/${guildId}/lyrics/unsubscribe`, (options) => {
758
- options.method = "DELETE";
759
- });
733
+ return await this.request(`/sessions/${this.sessionId}/players/${guildId}/unsubscribe`);
760
734
  },
761
735
  };
762
736
  /**
@@ -1092,7 +1066,7 @@ class LavalinkNode {
1092
1066
  player.ping.ws = payload.state.ping >= 0 ? payload.state.ping : player.ping.ws <= 0 && player.connected ? null : player.ping.ws || 0;
1093
1067
  if (!player.createdTimeStamp && payload.state.time)
1094
1068
  player.createdTimeStamp = payload.state.time;
1095
- 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)))) {
1096
1070
  player.filterManager.filterUpdatedState = false;
1097
1071
  if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1098
1072
  this.NodeManager.LavalinkManager.emit("debug", Constants_1.DebugEvents.PlayerUpdateFilterFixApply, {
@@ -1338,22 +1312,22 @@ class LavalinkNode {
1338
1312
  return;
1339
1313
  }
1340
1314
  /** @private util function for handling SponsorBlock Segmentloaded event */
1341
- async SponsorBlockSegmentLoaded(player, track, payload) {
1315
+ SponsorBlockSegmentLoaded(player, track, payload) {
1342
1316
  this.NodeManager.LavalinkManager.emit("SegmentsLoaded", player, track || this.getTrackOfPayload(payload), payload);
1343
1317
  return;
1344
1318
  }
1345
1319
  /** @private util function for handling SponsorBlock SegmentSkipped event */
1346
- async SponsorBlockSegmentSkipped(player, track, payload) {
1320
+ SponsorBlockSegmentSkipped(player, track, payload) {
1347
1321
  this.NodeManager.LavalinkManager.emit("SegmentSkipped", player, track || this.getTrackOfPayload(payload), payload);
1348
1322
  return;
1349
1323
  }
1350
1324
  /** @private util function for handling SponsorBlock Chaptersloaded event */
1351
- async SponsorBlockChaptersLoaded(player, track, payload) {
1325
+ SponsorBlockChaptersLoaded(player, track, payload) {
1352
1326
  this.NodeManager.LavalinkManager.emit("ChaptersLoaded", player, track || this.getTrackOfPayload(payload), payload);
1353
1327
  return;
1354
1328
  }
1355
1329
  /** @private util function for handling SponsorBlock Chaptersstarted event */
1356
- async SponsorBlockChapterStarted(player, track, payload) {
1330
+ SponsorBlockChapterStarted(player, track, payload) {
1357
1331
  this.NodeManager.LavalinkManager.emit("ChapterStarted", player, track || this.getTrackOfPayload(payload), payload);
1358
1332
  return;
1359
1333
  }
@@ -1479,7 +1453,8 @@ class LavalinkNode {
1479
1453
  if (player.queue.current) {
1480
1454
  if (payload.type === "TrackEndEvent")
1481
1455
  this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
1482
- return player.play({ noReplace: true, paused: false });
1456
+ if (this.NodeManager.LavalinkManager.options.autoSkip)
1457
+ return player.play({ noReplace: true, paused: false });
1483
1458
  }
1484
1459
  }
1485
1460
  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;
@@ -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;
@@ -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";
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,7 +20,7 @@ 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 music" | "vkmusic" | "flowerytts" | "flowery" | "flowery.tts" | LavalinkClientSearchPlatformResolve | LavalinkClientSearchPlatform | "js" | "jiosaavn";
24
24
  export type SearchPlatform = LavalinkSearchPlatform | ClientSearchPlatform;
25
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";
26
26
  export interface PlaylistInfo {
@@ -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;
@@ -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
  }
@@ -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'
@@ -36,11 +36,18 @@ export const DefaultSources = {
36
36
  "dz": "dzsearch",
37
37
  "dzsearch": "dzsearch",
38
38
  "dzisrc": "dzisrc",
39
+ "dzrec": "dzrec",
39
40
  // yandexmusic
40
41
  "yandex music": "ymsearch",
41
42
  "yandexmusic": "ymsearch",
42
43
  "yandex": "ymsearch",
43
44
  "ymsearch": "ymsearch",
45
+ "ymrec": "ymrec",
46
+ // VK Music (lavasrc)
47
+ "vksearch": "vksearch",
48
+ "vkmusic": "vksearch",
49
+ "vk music": "vksearch",
50
+ "vkrec": "vkrec",
44
51
  // speak PLUGIN
45
52
  "speak": "speak",
46
53
  "tts": "tts",
@@ -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
@@ -87,32 +87,6 @@ export class LavalinkNode {
87
87
  this.options.regions = (this.options.regions || []).map(a => a.toLowerCase());
88
88
  Object.defineProperty(this, NodeSymbol, { configurable: true, value: true });
89
89
  }
90
- /**
91
- * Parse url params correctly for lavalink requests, including support for urls and uris.
92
- * @param url input url object
93
- * @param extraQueryUrlParams UrlSearchParams to use in a encodedURI, useful for example for flowertts
94
- * @returns the url as a valid string
95
- *
96
- * @example
97
- * ```ts
98
- * player.node.getRequestingUrl(new URL(`http://localhost:2333/v4/loadtracks?identifier=Never gonna give you up`));
99
- * ```
100
- */
101
- getRequestingUrl(url, extraQueryUrlParams) {
102
- if (!url.searchParams.size)
103
- return `${url.origin}${url.pathname}`;
104
- const keysToAdd = [];
105
- for (const [paramKey, paramValue] of url.searchParams.entries()) {
106
- const decoded = decodeURIComponent(paramValue).trim(); // double decoding, once internally, a second time if decoded by provided user.
107
- if (decoded.includes("://") && !/^https?:\/\//.test(decoded)) { // uri, but not url.
108
- const [key, ...values] = decoded.split("://");
109
- keysToAdd.push(`${paramKey}=${encodeURI(`${key}://${encodeURIComponent(values.join("://"))}${extraQueryUrlParams && extraQueryUrlParams?.size > 0 ? `?${extraQueryUrlParams.toString()}` : ""}`)}`);
110
- continue;
111
- }
112
- keysToAdd.push(`${paramKey}=${encodeURIComponent(decoded)}`);
113
- }
114
- return `${url.origin}${url.pathname}?${keysToAdd.join("&")}`;
115
- }
116
90
  /**
117
91
  * Raw Request util function
118
92
  * @param endpoint endpoint string
@@ -127,7 +101,7 @@ export class LavalinkNode {
127
101
  */
128
102
  async rawRequest(endpoint, modify) {
129
103
  const options = {
130
- path: `/${this.version}/${endpoint.replace(/^\//gm, "")}`,
104
+ path: `/${this.version}/${endpoint.startsWith("/") ? endpoint.slice(1) : endpoint}`,
131
105
  method: "GET",
132
106
  headers: {
133
107
  "Authorization": this.options.authorization
@@ -137,7 +111,12 @@ export class LavalinkNode {
137
111
  modify?.(options);
138
112
  const url = new URL(`${this.restAddress}${options.path}`);
139
113
  url.searchParams.append("trace", "true");
140
- const urlToUse = this.getRequestingUrl(url, options?.extraQueryUrlParams);
114
+ if (options.extraQueryUrlParams && options.extraQueryUrlParams?.size > 0) {
115
+ for (const [paramKey, paramValue] of options.extraQueryUrlParams.entries()) {
116
+ url.searchParams.append(paramKey, paramValue);
117
+ }
118
+ }
119
+ const urlToUse = url.toString();
141
120
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
142
121
  const { path, extraQueryUrlParams, ...fetchOptions } = options; // destructure fetch only options
143
122
  const response = await fetch(urlToUse, fetchOptions);
@@ -176,20 +155,17 @@ export class LavalinkNode {
176
155
  if (["bcsearch", "bandcamp"].includes(Query.source) && !this.info.sourceManagers.includes("bandcamp")) {
177
156
  throw new Error("Bandcamp Search only works on the player (lavaplayer version < 2.2.0!");
178
157
  }
179
- let uri = `/loadtracks?identifier=`;
158
+ const requestUrl = new URL(`${this.restAddress}/loadtracks`);
180
159
  if (/^https?:\/\//.test(Query.query) || ["http", "https", "link", "uri"].includes(Query.source)) { // if it's a link simply encode it
181
- const url = encodeURIComponent(Query.query);
182
- uri += url;
160
+ requestUrl.searchParams.append("identifier", Query.query);
183
161
  }
184
162
  else { // if not make a query out of it
185
- if (Query.source !== "local")
186
- uri += `${Query.source}:`; // only add the query source string if it's not a local track
187
- if (Query.source === "ftts")
188
- uri += `//${encodeURIComponent(Query.query)}`;
189
- else
190
- uri += encodeURIComponent(Query.query);
163
+ const fttsPrefix = Query.source === "ftts" ? "//" : "";
164
+ const prefix = Query.source !== "local" ? `${Query.source}:${fttsPrefix}` : "";
165
+ requestUrl.searchParams.append("identifier", `${prefix}${Query.query}`);
191
166
  }
192
- const res = await this.request(uri, (options) => {
167
+ const requestPathAndSearch = requestUrl.pathname + requestUrl.search;
168
+ const res = await this.request(requestPathAndSearch, (options) => {
193
169
  if (typeof query === "object" && typeof query.extraQueryUrlParams?.size === "number" && query.extraQueryUrlParams?.size > 0) {
194
170
  options.extraQueryUrlParams = query.extraQueryUrlParams;
195
171
  }
@@ -217,7 +193,7 @@ export class LavalinkNode {
217
193
  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,
218
194
  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,
219
195
  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,
220
- duration: resTracks.length ? resTracks.reduce((acc, cur) => acc + (cur?.info?.length || 0), 0) : 0,
196
+ duration: resTracks.length ? resTracks.reduce((acc, cur) => acc + (cur?.info?.duration || cur?.info?.length || 0), 0) : 0,
221
197
  } : null,
222
198
  tracks: (resTracks.length ? resTracks.map(t => this.NodeManager.LavalinkManager.utils.buildTrack(t, requestUser)) : [])
223
199
  };
@@ -750,9 +726,7 @@ export class LavalinkNode {
750
726
  if (!this.info.plugins.find(v => v.name === "lavasrc-plugin") &&
751
727
  !this.info.plugins.find(v => v.name === "java-lyrics-plugin"))
752
728
  throw new RangeError(`there is no lyrics source (via lavasrc-plugin / java-lyrics-plugin) available in the lavalink node (required for lyrics): ${this.id}`);
753
- return await this.request(`/sessions/${this.sessionId}/players/${guildId}/lyrics/unsubscribe`, (options) => {
754
- options.method = "DELETE";
755
- });
729
+ return await this.request(`/sessions/${this.sessionId}/players/${guildId}/unsubscribe`);
756
730
  },
757
731
  };
758
732
  /**
@@ -1088,7 +1062,7 @@ export class LavalinkNode {
1088
1062
  player.ping.ws = payload.state.ping >= 0 ? payload.state.ping : player.ping.ws <= 0 && player.connected ? null : player.ping.ws || 0;
1089
1063
  if (!player.createdTimeStamp && payload.state.time)
1090
1064
  player.createdTimeStamp = payload.state.time;
1091
- if (player.filterManager.filterUpdatedState === true && ((player.queue.current?.info?.duration || 0) <= (player.LavalinkManager.options.advancedOptions.maxFilterFixDuration || 600_000) || isAbsolute(player.queue.current?.info?.uri))) {
1065
+ if (player.filterManager.filterUpdatedState === true && ((player.queue.current?.info?.duration || 0) <= (player.LavalinkManager.options.advancedOptions.maxFilterFixDuration || 600_000) || (player.queue.current?.info?.uri && isAbsolute(player.queue.current?.info?.uri)))) {
1092
1066
  player.filterManager.filterUpdatedState = false;
1093
1067
  if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1094
1068
  this.NodeManager.LavalinkManager.emit("debug", DebugEvents.PlayerUpdateFilterFixApply, {
@@ -1334,22 +1308,22 @@ export class LavalinkNode {
1334
1308
  return;
1335
1309
  }
1336
1310
  /** @private util function for handling SponsorBlock Segmentloaded event */
1337
- async SponsorBlockSegmentLoaded(player, track, payload) {
1311
+ SponsorBlockSegmentLoaded(player, track, payload) {
1338
1312
  this.NodeManager.LavalinkManager.emit("SegmentsLoaded", player, track || this.getTrackOfPayload(payload), payload);
1339
1313
  return;
1340
1314
  }
1341
1315
  /** @private util function for handling SponsorBlock SegmentSkipped event */
1342
- async SponsorBlockSegmentSkipped(player, track, payload) {
1316
+ SponsorBlockSegmentSkipped(player, track, payload) {
1343
1317
  this.NodeManager.LavalinkManager.emit("SegmentSkipped", player, track || this.getTrackOfPayload(payload), payload);
1344
1318
  return;
1345
1319
  }
1346
1320
  /** @private util function for handling SponsorBlock Chaptersloaded event */
1347
- async SponsorBlockChaptersLoaded(player, track, payload) {
1321
+ SponsorBlockChaptersLoaded(player, track, payload) {
1348
1322
  this.NodeManager.LavalinkManager.emit("ChaptersLoaded", player, track || this.getTrackOfPayload(payload), payload);
1349
1323
  return;
1350
1324
  }
1351
1325
  /** @private util function for handling SponsorBlock Chaptersstarted event */
1352
- async SponsorBlockChapterStarted(player, track, payload) {
1326
+ SponsorBlockChapterStarted(player, track, payload) {
1353
1327
  this.NodeManager.LavalinkManager.emit("ChapterStarted", player, track || this.getTrackOfPayload(payload), payload);
1354
1328
  return;
1355
1329
  }
@@ -1475,7 +1449,8 @@ export class LavalinkNode {
1475
1449
  if (player.queue.current) {
1476
1450
  if (payload.type === "TrackEndEvent")
1477
1451
  this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
1478
- return player.play({ noReplace: true, paused: false });
1452
+ if (this.NodeManager.LavalinkManager.options.autoSkip)
1453
+ return player.play({ noReplace: true, paused: false });
1479
1454
  }
1480
1455
  }
1481
1456
  else {
@@ -71,6 +71,9 @@ export class Player {
71
71
  * @param LavalinkManager
72
72
  */
73
73
  constructor(options, LavalinkManager) {
74
+ if (typeof options?.customData === "object")
75
+ for (const [key, value] of Object.entries(options.customData))
76
+ this.set(key, value);
74
77
  this.options = options;
75
78
  this.filterManager = new FilterManager(this);
76
79
  this.LavalinkManager = LavalinkManager;
@@ -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;
@@ -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";
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,7 +20,7 @@ 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 music" | "vkmusic" | "flowerytts" | "flowery" | "flowery.tts" | LavalinkClientSearchPlatformResolve | LavalinkClientSearchPlatform | "js" | "jiosaavn";
24
24
  export type SearchPlatform = LavalinkSearchPlatform | ClientSearchPlatform;
25
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";
26
26
  export interface PlaylistInfo {
@@ -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;
@@ -25,7 +25,7 @@ export function parseLavalinkConnUrl(connectionUrl) {
25
25
  };
26
26
  }
27
27
  export class ManagerUtils {
28
- LavalinkManager = null;
28
+ LavalinkManager = undefined;
29
29
  constructor(LavalinkManager) {
30
30
  this.LavalinkManager = LavalinkManager;
31
31
  }
@@ -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
@@ -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;
@@ -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";
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,7 +20,7 @@ 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 music" | "vkmusic" | "flowerytts" | "flowery" | "flowery.tts" | LavalinkClientSearchPlatformResolve | LavalinkClientSearchPlatform | "js" | "jiosaavn";
24
24
  export type SearchPlatform = LavalinkSearchPlatform | ClientSearchPlatform;
25
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";
26
26
  export interface PlaylistInfo {
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavalink-client",
3
- "version": "2.4.6",
3
+ "version": "2.5.0",
4
4
  "description": "Easy, flexible and feature-rich lavalink@v4 Client. Both for Beginners and Proficients.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -59,12 +59,12 @@
59
59
  "homepage": "https://tomato6966.github.io/lavalink-client/",
60
60
  "devDependencies": {
61
61
  "@eslint/eslintrc": "^3.2.0",
62
- "@eslint/js": "^9.18.0",
63
- "@types/node": "^22.10.5",
64
- "@types/ws": "^8.5.13",
65
- "@typescript-eslint/eslint-plugin": "^8.20.0",
66
- "@typescript-eslint/parser": "^8.20.0",
67
- "eslint": "^9.18.0",
62
+ "@eslint/js": "^9.20.0",
63
+ "@types/node": "^22.13.4",
64
+ "@types/ws": "^8.5.14",
65
+ "@typescript-eslint/eslint-plugin": "^8.24.1",
66
+ "@typescript-eslint/parser": "^8.24.1",
67
+ "eslint": "^9.20.1",
68
68
  "tsc-alias": "^1.8.10",
69
69
  "typescript": "^5.7.3"
70
70
  },