lavalink-client 1.1.11 → 1.1.14

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.
@@ -156,84 +156,112 @@ class ManagerUtils {
156
156
  if (!node.info.sourceManagers?.length)
157
157
  throw new Error("Lavalink Node, has no sourceManagers enabled");
158
158
  // missing links: beam.pro local getyarn.io clypit pornhub reddit ocreamix soundgasm
159
- if ((LavalinkManagerStatics_1.SourceLinksRegexes.YoutubeMusicRegex.test(queryString) || LavalinkManagerStatics_1.SourceLinksRegexes.YoutubeRegex.test(queryString)) && !node.info.sourceManagers.includes("youtube")) {
159
+ if ((LavalinkManagerStatics_1.SourceLinksRegexes.YoutubeMusicRegex.test(queryString) || LavalinkManagerStatics_1.SourceLinksRegexes.YoutubeRegex.test(queryString)) && !node.info?.sourceManagers?.includes("youtube")) {
160
160
  throw new Error("Lavalink Node has not 'youtube' enabled");
161
161
  }
162
- if ((LavalinkManagerStatics_1.SourceLinksRegexes.SoundCloudMobileRegex.test(queryString) || LavalinkManagerStatics_1.SourceLinksRegexes.SoundCloudRegex.test(queryString)) && !node.info.sourceManagers.includes("soundcloud")) {
162
+ if ((LavalinkManagerStatics_1.SourceLinksRegexes.SoundCloudMobileRegex.test(queryString) || LavalinkManagerStatics_1.SourceLinksRegexes.SoundCloudRegex.test(queryString)) && !node.info?.sourceManagers?.includes("soundcloud")) {
163
163
  throw new Error("Lavalink Node has not 'soundcloud' enabled");
164
164
  }
165
- if (LavalinkManagerStatics_1.SourceLinksRegexes.bandcamp.test(queryString) && !node.info.sourceManagers.includes("bandcamp")) {
165
+ if (LavalinkManagerStatics_1.SourceLinksRegexes.bandcamp.test(queryString) && !node.info?.sourceManagers?.includes("bandcamp")) {
166
166
  throw new Error("Lavalink Node has not 'bandcamp' enabled");
167
167
  }
168
- if (LavalinkManagerStatics_1.SourceLinksRegexes.TwitchTv.test(queryString) && !node.info.sourceManagers.includes("twitch")) {
168
+ if (LavalinkManagerStatics_1.SourceLinksRegexes.TwitchTv.test(queryString) && !node.info?.sourceManagers?.includes("twitch")) {
169
169
  throw new Error("Lavalink Node has not 'twitch' enabled");
170
170
  }
171
- if (LavalinkManagerStatics_1.SourceLinksRegexes.vimeo.test(queryString) && !node.info.sourceManagers.includes("vimeo")) {
171
+ if (LavalinkManagerStatics_1.SourceLinksRegexes.vimeo.test(queryString) && !node.info?.sourceManagers?.includes("vimeo")) {
172
172
  throw new Error("Lavalink Node has not 'vimeo' enabled");
173
173
  }
174
- if (LavalinkManagerStatics_1.SourceLinksRegexes.tiktok.test(queryString) && !node.info.sourceManagers.includes("tiktok")) {
174
+ if (LavalinkManagerStatics_1.SourceLinksRegexes.tiktok.test(queryString) && !node.info?.sourceManagers?.includes("tiktok")) {
175
175
  throw new Error("Lavalink Node has not 'tiktok' enabled");
176
176
  }
177
- if (LavalinkManagerStatics_1.SourceLinksRegexes.mixcloud.test(queryString) && !node.info.sourceManagers.includes("mixcloud")) {
177
+ if (LavalinkManagerStatics_1.SourceLinksRegexes.mixcloud.test(queryString) && !node.info?.sourceManagers?.includes("mixcloud")) {
178
178
  throw new Error("Lavalink Node has not 'mixcloud' enabled");
179
179
  }
180
- if (LavalinkManagerStatics_1.SourceLinksRegexes.AllSpotifyRegex.test(queryString) && !node.info.sourceManagers.includes("spotify")) {
180
+ if (LavalinkManagerStatics_1.SourceLinksRegexes.AllSpotifyRegex.test(queryString) && !node.info?.sourceManagers?.includes("spotify")) {
181
181
  throw new Error("Lavalink Node has not 'spotify' enabled");
182
182
  }
183
- if (LavalinkManagerStatics_1.SourceLinksRegexes.appleMusic.test(queryString) && !node.info.sourceManagers.includes("applemusic")) {
183
+ if (LavalinkManagerStatics_1.SourceLinksRegexes.appleMusic.test(queryString) && !node.info?.sourceManagers?.includes("applemusic")) {
184
184
  throw new Error("Lavalink Node has not 'applemusic' enabled");
185
185
  }
186
- if (LavalinkManagerStatics_1.SourceLinksRegexes.AllDeezerRegex.test(queryString) && !node.info.sourceManagers.includes("deezer")) {
186
+ if (LavalinkManagerStatics_1.SourceLinksRegexes.AllDeezerRegex.test(queryString) && !node.info?.sourceManagers?.includes("deezer")) {
187
187
  throw new Error("Lavalink Node has not 'deezer' enabled");
188
188
  }
189
- if (LavalinkManagerStatics_1.SourceLinksRegexes.AllDeezerRegex.test(queryString) && node.info.sourceManagers.includes("deezer") && !node.info.sourceManagers.includes("http")) {
189
+ if (LavalinkManagerStatics_1.SourceLinksRegexes.AllDeezerRegex.test(queryString) && node.info?.sourceManagers?.includes("deezer") && !node.info?.sourceManagers?.includes("http")) {
190
190
  throw new Error("Lavalink Node has not 'http' enabled, which is required to have 'deezer' to work");
191
191
  }
192
- if (LavalinkManagerStatics_1.SourceLinksRegexes.musicYandex.test(queryString) && !node.info.sourceManagers.includes("yandexmusic")) {
192
+ if (LavalinkManagerStatics_1.SourceLinksRegexes.musicYandex.test(queryString) && !node.info?.sourceManagers?.includes("yandexmusic")) {
193
193
  throw new Error("Lavalink Node has not 'yandexmusic' enabled");
194
194
  }
195
195
  return;
196
196
  }
197
+ transformQuery(query) {
198
+ const Query = {
199
+ query: typeof query === "string" ? query : query.query,
200
+ source: LavalinkManagerStatics_1.DefaultSources[(typeof query === "string" ? undefined : query.source?.trim?.()?.toLowerCase?.()) ?? this.LavalinkManager?.options?.playerOptions?.defaultSearchPlatform?.toLowerCase?.()] ?? (typeof query === "string" ? undefined : query.source?.trim?.()?.toLowerCase?.()) ?? this.LavalinkManager?.options?.playerOptions?.defaultSearchPlatform?.toLowerCase?.()
201
+ };
202
+ const foundSource = Object.keys(LavalinkManagerStatics_1.DefaultSources).find(source => Query.query?.toLowerCase?.()?.startsWith(`${source}:`.toLowerCase()))?.trim?.()?.toLowerCase?.();
203
+ if (foundSource && LavalinkManagerStatics_1.DefaultSources[foundSource]) {
204
+ Query.source = LavalinkManagerStatics_1.DefaultSources[foundSource]; // set the source to ytsearch:
205
+ Query.query = Query.query.slice(`${foundSource}:`.length, Query.query.length); // remove ytsearch: from the query
206
+ }
207
+ return Query;
208
+ }
209
+ transformLavaSearchQuery(query) {
210
+ // transform the query object
211
+ const Query = {
212
+ query: typeof query === "string" ? query : query.query,
213
+ types: query.types ? ["track", "playlist", "artist", "album", "text"].filter(v => query.types?.find(x => x.toLowerCase().startsWith(v))) : ["track", "playlist", "artist", "album", /*"text"*/],
214
+ source: LavalinkManagerStatics_1.DefaultSources[(typeof query === "string" ? undefined : query.source?.trim?.()?.toLowerCase?.()) ?? this.LavalinkManager?.options?.playerOptions?.defaultSearchPlatform?.toLowerCase?.()] ?? (typeof query === "string" ? undefined : query.source?.trim?.()?.toLowerCase?.()) ?? this.LavalinkManager?.options?.playerOptions?.defaultSearchPlatform?.toLowerCase?.()
215
+ };
216
+ const foundSource = Object.keys(LavalinkManagerStatics_1.DefaultSources).find(source => Query.query.toLowerCase().startsWith(`${source}:`.toLowerCase()))?.trim?.()?.toLowerCase?.();
217
+ if (foundSource && LavalinkManagerStatics_1.DefaultSources[foundSource]) {
218
+ Query.source = LavalinkManagerStatics_1.DefaultSources[foundSource]; // set the source to ytsearch:
219
+ Query.query = Query.query.slice(`${foundSource}:`.length, Query.query.length); // remove ytsearch: from the query
220
+ }
221
+ return Query;
222
+ }
197
223
  validateSourceString(node, sourceString) {
198
224
  if (!sourceString)
199
225
  throw new Error(`No SourceString was provided`);
200
226
  const source = LavalinkManagerStatics_1.DefaultSources[sourceString.toLowerCase().trim()];
201
227
  if (!source)
202
228
  throw new Error(`Lavalink Node SearchQuerySource: '${sourceString}' is not available`);
203
- if (source === "amsearch" && !node.info.sourceManagers.includes("applemusic")) {
229
+ if (!node.info)
230
+ throw new Error("Lavalink Node does not have any info cached yet, not ready yet!");
231
+ if (source === "amsearch" && !node.info?.sourceManagers?.includes("applemusic")) {
204
232
  throw new Error("Lavalink Node has not 'applemusic' enabled, which is required to have 'amsearch' work");
205
233
  }
206
- if (source === "dzisrc" && !node.info.sourceManagers.includes("deezer")) {
234
+ if (source === "dzisrc" && !node.info?.sourceManagers?.includes("deezer")) {
207
235
  throw new Error("Lavalink Node has not 'deezer' enabled, which is required to have 'dzisrc' work");
208
236
  }
209
- if (source === "dzsearch" && !node.info.sourceManagers.includes("deezer")) {
237
+ if (source === "dzsearch" && !node.info?.sourceManagers?.includes("deezer")) {
210
238
  throw new Error("Lavalink Node has not 'deezer' enabled, which is required to have 'dzsearch' work");
211
239
  }
212
- if (source === "dzisrc" && node.info.sourceManagers.includes("deezer") && !node.info.sourceManagers.includes("http")) {
240
+ if (source === "dzisrc" && node.info?.sourceManagers?.includes("deezer") && !node.info?.sourceManagers?.includes("http")) {
213
241
  throw new Error("Lavalink Node has not 'http' enabled, which is required to have 'dzisrc' to work");
214
242
  }
215
- if (source === "dzsearch" && node.info.sourceManagers.includes("deezer") && !node.info.sourceManagers.includes("http")) {
243
+ if (source === "dzsearch" && node.info?.sourceManagers?.includes("deezer") && !node.info?.sourceManagers?.includes("http")) {
216
244
  throw new Error("Lavalink Node has not 'http' enabled, which is required to have 'dzsearch' to work");
217
245
  }
218
- if (source === "scsearch" && !node.info.sourceManagers.includes("soundcloud")) {
246
+ if (source === "scsearch" && !node.info?.sourceManagers?.includes("soundcloud")) {
219
247
  throw new Error("Lavalink Node has not 'soundcloud' enabled, which is required to have 'scsearch' work");
220
248
  }
221
- if (source === "speak" && !node.info.plugins.find(c => c.name.toLowerCase().includes(LavalinkManagerStatics_1.LavalinkPlugins.DuncteBot_Plugin.toLowerCase()))) {
249
+ if (source === "speak" && !node.info?.plugins?.find(c => c.name.toLowerCase().includes(LavalinkManagerStatics_1.LavalinkPlugins.DuncteBot_Plugin.toLowerCase()))) {
222
250
  throw new Error("Lavalink Node has not 'speak' enabled, which is required to have 'speak' work");
223
251
  }
224
- if (source === "tts" && !node.info.plugins.find(c => c.name.toLowerCase().includes(LavalinkManagerStatics_1.LavalinkPlugins.GoogleCloudTTS.toLowerCase()))) {
252
+ if (source === "tts" && !node.info?.plugins?.find(c => c.name.toLowerCase().includes(LavalinkManagerStatics_1.LavalinkPlugins.GoogleCloudTTS.toLowerCase()))) {
225
253
  throw new Error("Lavalink Node has not 'tts' enabled, which is required to have 'tts' work");
226
254
  }
227
- if (source === "ftts" && !(node.info.sourceManagers.includes("ftts") || node.info.sourceManagers.includes("flowery-tts") || node.info.sourceManagers.includes("flowerytts"))) {
255
+ if (source === "ftts" && !(node.info?.sourceManagers?.includes("ftts") || node.info?.sourceManagers?.includes("flowery-tts") || node.info?.sourceManagers?.includes("flowerytts"))) {
228
256
  throw new Error("Lavalink Node has not 'flowery-tts' enabled, which is required to have 'ftts' work");
229
257
  }
230
- if (source === "ymsearch" && !node.info.sourceManagers.includes("yandexmusic")) {
258
+ if (source === "ymsearch" && !node.info?.sourceManagers?.includes("yandexmusic")) {
231
259
  throw new Error("Lavalink Node has not 'yandexmusic' enabled, which is required to have 'ymsearch' work");
232
260
  }
233
- if (source === "ytmsearch" && !node.info.sourceManagers.includes("youtube")) {
261
+ if (source === "ytmsearch" && !node.info.sourceManagers?.includes("youtube")) {
234
262
  throw new Error("Lavalink Node has not 'youtube' enabled, which is required to have 'ytmsearch' work");
235
263
  }
236
- if (source === "ytsearch" && !node.info.sourceManagers.includes("youtube")) {
264
+ if (source === "ytsearch" && !node.info?.sourceManagers?.includes("youtube")) {
237
265
  throw new Error("Lavalink Node has not 'youtube' enabled, which is required to have 'ytsearch' work");
238
266
  }
239
267
  return;
@@ -46,19 +46,19 @@ export class FilterManager {
46
46
  },
47
47
  vibrato: {
48
48
  frequency: 0,
49
- depth: 0 // 0 < x = 1
49
+ depth: 0 // 0 < x <= 1
50
50
  },
51
51
  pluginFilters: {
52
- /*"lavalink-filter-plugin": {
53
- echo: {
54
- delay: 0,
55
- decay: 0
56
- },
57
- reverb: {
58
- delays: [0.037, 0.042, 0.048, 0.053],
59
- gains: [0.84, 0.83, 0.82, 0.81]
52
+ "lavalink-filter-plugin": {
53
+ echo: {
54
+ delay: 0,
55
+ decay: 0 // 0 < 1
56
+ },
57
+ reverb: {
58
+ delays: [],
59
+ gains: [] // [0.84, 0.83, 0.82, 0.81]
60
+ }
60
61
  }
61
- }*/
62
62
  },
63
63
  channelMix: audioOutputsData.stereo,
64
64
  /*distortion: {
@@ -95,6 +95,10 @@ export class FilterManager {
95
95
  delete sendData.pluginFilters?.["lavalink-filter-plugin"]?.echo;
96
96
  if (!this.filters.reverb)
97
97
  delete sendData.pluginFilters?.["lavalink-filter-plugin"]?.reverb;
98
+ if (sendData.pluginFilters?.["lavalink-filter-plugin"] && Object.values(sendData.pluginFilters?.["lavalink-filter-plugin"]).length === 0)
99
+ delete sendData.pluginFilters["lavalink-filter-plugin"];
100
+ if (sendData.pluginFilters && Object.values(sendData.pluginFilters).length === 0)
101
+ delete sendData.pluginFilters;
98
102
  if (!this.filters.lowPass)
99
103
  delete sendData.lowPass;
100
104
  if (!this.filters.karaoke)
@@ -108,9 +112,16 @@ export class FilterManager {
108
112
  if (!this.player.node.sessionId)
109
113
  throw new Error("The Lavalink-Node is either not ready or not up to date");
110
114
  sendData.equalizer = [...this.equalizerBands];
115
+ if (sendData.equalizer.length === 0)
116
+ delete sendData.equalizer;
111
117
  for (const key of [...Object.keys(sendData)]) {
112
118
  // delete disabled filters
113
- if (this.player.node.info && !this.player.node.info?.filters?.includes?.(key))
119
+ if (key === "pluginFilters") {
120
+ for (const key of [...Object.keys(sendData.pluginFilters)]) {
121
+ // if (this.player.node.info && !this.player.node.info?.plugins?.find?.(v => v.name === key)) delete sendData[key];
122
+ }
123
+ }
124
+ else if (this.player.node.info && !this.player.node.info?.filters?.includes?.(key))
114
125
  delete sendData[key];
115
126
  }
116
127
  const now = performance.now();
@@ -163,7 +174,7 @@ export class FilterManager {
163
174
  this.filters.karaoke = false;
164
175
  this.filters.volume = false;
165
176
  this.filters.audioOutput = "stereo";
166
- // disable all filters
177
+ // reset all filter datas
167
178
  for (const [key, value] of Object.entries({
168
179
  volume: 1,
169
180
  lowPass: {
@@ -180,13 +191,17 @@ export class FilterManager {
180
191
  pitch: 1,
181
192
  rate: 1 // 0 = x
182
193
  },
183
- echo: {
184
- delay: 0,
185
- decay: 0
186
- },
187
- reverb: {
188
- delays: [],
189
- gains: []
194
+ pluginFilters: {
195
+ "lavalink-filter-plugin": {
196
+ echo: {
197
+ delay: 0,
198
+ decay: 0
199
+ },
200
+ reverb: {
201
+ delays: [],
202
+ gains: []
203
+ },
204
+ }
190
205
  },
191
206
  rotation: {
192
207
  rotationHz: 0
@@ -360,7 +375,7 @@ export class FilterManager {
360
375
  * @param decay
361
376
  * @returns
362
377
  */
363
- async toggleEcho(delay = 1, decay = 0.5) {
378
+ async toggleEcho(delay = 4, decay = 0.8) {
364
379
  if (this.player.node.info && !this.player.node.info?.filters?.includes("echo"))
365
380
  throw new Error("Node#Info#filters does not include the 'echo' Filter (Node has it not enable aka not installed!)");
366
381
  if (!this.data)
@@ -369,6 +384,8 @@ export class FilterManager {
369
384
  this.data.pluginFilters = {};
370
385
  if (!this.data.pluginFilters["lavalink-filter-plugin"])
371
386
  this.data.pluginFilters["lavalink-filter-plugin"] = { echo: { decay: 0, delay: 0 }, reverb: { delays: [], gains: [] } };
387
+ if (!this.data.pluginFilters["lavalink-filter-plugin"].echo)
388
+ this.data.pluginFilters["lavalink-filter-plugin"].echo = { decay: 0, delay: 0 };
372
389
  this.data.pluginFilters["lavalink-filter-plugin"].echo.delay = this.filters.echo ? 0 : delay;
373
390
  this.data.pluginFilters["lavalink-filter-plugin"].echo.decay = this.filters.echo ? 0 : decay;
374
391
  this.filters.echo = !this.filters.echo;
@@ -390,6 +407,8 @@ export class FilterManager {
390
407
  this.data.pluginFilters = {};
391
408
  if (!this.data.pluginFilters["lavalink-filter-plugin"])
392
409
  this.data.pluginFilters["lavalink-filter-plugin"] = { echo: { decay: 0, delay: 0 }, reverb: { delays: [], gains: [] } };
410
+ if (!this.data.pluginFilters["lavalink-filter-plugin"].reverb)
411
+ this.data.pluginFilters["lavalink-filter-plugin"].reverb = { delays: [], gains: [] };
393
412
  this.data.pluginFilters["lavalink-filter-plugin"].reverb.delays = this.filters.reverb ? [] : delays;
394
413
  this.data.pluginFilters["lavalink-filter-plugin"].reverb.gains = this.filters.reverb ? [] : gains;
395
414
  this.filters.reverb = !this.filters.reverb;
@@ -2,7 +2,7 @@
2
2
  import { Dispatcher, Pool } from "undici";
3
3
  import { NodeManager } from "./NodeManager";
4
4
  import internal from "stream";
5
- import { InvalidLavalinkRestRequest, LavalinkPlayer, PlayerUpdateInfo, RoutePlanner, Session, Base64, SearchResult } from "./Utils";
5
+ import { InvalidLavalinkRestRequest, LavalinkPlayer, PlayerUpdateInfo, RoutePlanner, Session, Base64, SearchResult, LavaSearchResponse, LavaSearchQuery, SearchQuery } from "./Utils";
6
6
  import { DestroyReasonsType } from "./Player";
7
7
  import { Track } from "./Track";
8
8
  /** Modifies any outgoing REST requests. */
@@ -135,7 +135,8 @@ export declare class LavalinkNode {
135
135
  * @returns The returned data
136
136
  */
137
137
  request(endpoint: string, modify?: ModifyRequest, parseAsText?: boolean): Promise<unknown>;
138
- search(querySourceString: string, requestUser: unknown): Promise<SearchResult>;
138
+ search(query: SearchQuery, requestUser: unknown): Promise<SearchResult>;
139
+ lavaSearch(query: LavaSearchQuery, requestUser: unknown): Promise<SearchResult | LavaSearchResponse>;
139
140
  /**
140
141
  * Update the Player State on the Lavalink Server
141
142
  * @param data
@@ -160,7 +161,7 @@ export declare class LavalinkNode {
160
161
  * Destroys the Node-Connection (Websocket) and all player's of the node
161
162
  * @returns
162
163
  */
163
- destroy(destroyReason?: DestroyReasonsType): void;
164
+ destroy(destroyReason?: DestroyReasonsType, deleteNode?: boolean): void;
164
165
  /** Returns if connected to the Node. */
165
166
  get connected(): boolean;
166
167
  /**
@@ -92,8 +92,23 @@ export class LavalinkNode {
92
92
  throw new Error(`Node Request resulted into an error, request-URL: ${url} | headers: ${JSON.stringify(request.headers)}`);
93
93
  return parseAsText ? await request.body.text() : await request.body.json();
94
94
  }
95
- async search(querySourceString, requestUser) {
96
- const res = await this.request(`/loadsearch?query=${encodeURIComponent(decodeURIComponent(querySourceString))}`);
95
+ async search(query, requestUser) {
96
+ const Query = this.NodeManager.LavalinkManager.utils.transformQuery(query);
97
+ if (/^https?:\/\//.test(Query.query))
98
+ this.NodeManager.LavalinkManager.utils.validateQueryString(this, Query.source);
99
+ else if (Query.source)
100
+ this.NodeManager.LavalinkManager.utils.validateSourceString(this, Query.source);
101
+ if (["bcsearch", "bandcamp"].includes(Query.source)) {
102
+ throw new Error("Bandcamp Search only works on the player!");
103
+ }
104
+ let uri = `/loadtracks?identifier=`;
105
+ if (!/^https?:\/\//.test(Query.query))
106
+ uri += `${Query.source}:`;
107
+ if (Query.source === "ftts")
108
+ uri += `//${encodeURIComponent(encodeURI(decodeURIComponent(Query.query)))}`;
109
+ else
110
+ uri += encodeURIComponent(decodeURIComponent(Query.query));
111
+ const res = await this.request(uri);
97
112
  // transform the data which can be Error, Track or Track[] to enfore [Track]
98
113
  const resTracks = res.loadType === "playlist" ? res.data?.tracks : res.loadType === "track" ? [res.data] : res.loadType === "search" ? Array.isArray(res.data) ? res.data : [res.data] : [];
99
114
  return {
@@ -111,6 +126,28 @@ export class LavalinkNode {
111
126
  tracks: (resTracks.length ? resTracks.map(t => this.NodeManager.LavalinkManager.utils.buildTrack(t, requestUser)) : [])
112
127
  };
113
128
  }
129
+ async lavaSearch(query, requestUser) {
130
+ const Query = this.NodeManager.LavalinkManager.utils.transformLavaSearchQuery(query);
131
+ if (Query.source)
132
+ this.NodeManager.LavalinkManager.utils.validateSourceString(this, Query.source);
133
+ if (/^https?:\/\//.test(Query.query))
134
+ return await this.search({ query: Query.query, source: Query.source }, requestUser);
135
+ if (!["spsearch", "sprec", "amsearch", "dzsearch", "dzisrc", "ytmsearch", "ytsearch"].includes(Query.source))
136
+ throw new SyntaxError(`Query.source must be a source from LavaSrc: "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ytmsearch" | "ytsearch"`);
137
+ if (!this.info.plugins.find(v => v.name === "lavasearch-plugin"))
138
+ throw new RangeError(`there is no lavasearch-plugin available in the lavalink node: ${this.id}`);
139
+ if (!this.info.plugins.find(v => v.name === "lavasrc-plugin"))
140
+ throw new RangeError(`there is no lavasrc-plugin available in the lavalink node: ${this.id}`);
141
+ const res = await this.request(`/loadsearch?query=${Query.source ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}${Query.types?.length ? `&types=${Query.types.join(",")}` : ""}`);
142
+ return {
143
+ tracks: res.tracks?.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) || [],
144
+ 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)) })) || [],
145
+ artists: res.artists?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
146
+ playlists: res.playlists?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
147
+ texts: res.texts?.map(v => ({ text: v.text, pluginInfo: v?.plugin || v.pluginInfo })) || [],
148
+ pluginInfo: res.pluginInfo || res?.plugin
149
+ };
150
+ }
114
151
  /**
115
152
  * Update the Player State on the Lavalink Server
116
153
  * @param data
@@ -164,7 +201,7 @@ export class LavalinkNode {
164
201
  }
165
202
  this.socket = new WebSocket(`ws${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}/v4/websocket`, { headers });
166
203
  this.socket.on("open", this.open.bind(this));
167
- this.socket.on("close", this.close.bind(this));
204
+ this.socket.on("close", (code, reason) => this.close(code, reason?.toString()));
168
205
  this.socket.on("message", this.message.bind(this));
169
206
  this.socket.on("error", this.error.bind(this));
170
207
  }
@@ -176,19 +213,24 @@ export class LavalinkNode {
176
213
  * Destroys the Node-Connection (Websocket) and all player's of the node
177
214
  * @returns
178
215
  */
179
- destroy(destroyReason) {
216
+ destroy(destroyReason, deleteNode = true) {
180
217
  if (!this.connected)
181
218
  return;
182
219
  const players = this.NodeManager.LavalinkManager.players.filter(p => p.node.id == this.id);
183
220
  if (players)
184
221
  players.forEach(p => p.destroy(destroyReason || DestroyReasons.NodeDestroy));
185
- this.socket.close(1000, "destroy");
222
+ this.socket.close(1000, "Node-Destroy");
186
223
  this.socket.removeAllListeners();
187
224
  this.socket = null;
188
225
  this.reconnectAttempts = 1;
189
226
  clearTimeout(this.reconnectTimeout);
190
- this.NodeManager.emit("destroy", this, destroyReason);
191
- this.NodeManager.nodes.delete(this.id);
227
+ if (deleteNode) {
228
+ this.NodeManager.emit("destroy", this, destroyReason);
229
+ this.NodeManager.nodes.delete(this.id);
230
+ }
231
+ else {
232
+ this.NodeManager.emit("disconnect", this, { code: 1000, reason: destroyReason });
233
+ }
192
234
  return;
193
235
  }
194
236
  /** Returns if connected to the Node. */
@@ -402,7 +444,20 @@ export class LavalinkNode {
402
444
  get poolAddress() {
403
445
  return `http${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}`;
404
446
  }
405
- reconnect() {
447
+ reconnect(instaReconnect = false) {
448
+ if (instaReconnect) {
449
+ if (this.reconnectAttempts >= this.options.retryAmount) {
450
+ const error = new Error(`Unable to connect after ${this.options.retryAmount} attempts.`);
451
+ this.NodeManager.emit("error", this, error);
452
+ return this.destroy(DestroyReasons.NodeReconnectFail);
453
+ }
454
+ this.socket.removeAllListeners();
455
+ this.socket = null;
456
+ this.NodeManager.emit("reconnecting", this);
457
+ this.connect();
458
+ this.reconnectAttempts++;
459
+ return;
460
+ }
406
461
  this.reconnectTimeout = setTimeout(() => {
407
462
  if (this.reconnectAttempts >= this.options.retryAmount) {
408
463
  const error = new Error(`Unable to connect after ${this.options.retryAmount} attempts.`);
@@ -433,7 +488,7 @@ export class LavalinkNode {
433
488
  }
434
489
  close(code, reason) {
435
490
  this.NodeManager.emit("disconnect", this, { code, reason });
436
- if (code !== 1000 || reason !== "destroy")
491
+ if (code !== 1000 || reason !== "Node-Destroy")
437
492
  this.reconnect();
438
493
  }
439
494
  error(error) {
@@ -54,6 +54,22 @@ export declare interface NodeManager {
54
54
  export declare class NodeManager extends EventEmitter {
55
55
  nodes: MiniMap<string, LavalinkNode>;
56
56
  constructor(LavalinkManager: LavalinkManager);
57
+ /**
58
+ * Disconnects all Nodes from lavalink ws sockets
59
+ * @param deleteAllNodes if the nodes should also be deleted from nodeManager.nodes
60
+ * @returns amount of disconnected Nodes
61
+ */
62
+ disconnectAll(deleteAllNodes?: boolean): Promise<number>;
63
+ /**
64
+ * Connects all not connected nodes
65
+ * @returns Amount of connected Nodes
66
+ */
67
+ connectAll(): Promise<number>;
68
+ /**
69
+ * Forcefully reconnects all nodes
70
+ * @returns amount of nodes
71
+ */
72
+ reconnectAll(): Promise<number>;
57
73
  createNode(options: LavalinkNodeOptions): LavalinkNode;
58
74
  leastUsedNodes(sortType?: "memory" | "cpuLavalink" | "cpuSystem" | "calls" | "playingPlayers" | "players"): LavalinkNode[];
59
75
  deleteNode(node: LavalinkNodeIdentifier | LavalinkNode): void;
@@ -10,6 +10,59 @@ export class NodeManager extends EventEmitter {
10
10
  if (this.LavalinkManager.options.nodes)
11
11
  this.LavalinkManager.options.nodes.forEach(node => this.createNode(node));
12
12
  }
13
+ /**
14
+ * Disconnects all Nodes from lavalink ws sockets
15
+ * @param deleteAllNodes if the nodes should also be deleted from nodeManager.nodes
16
+ * @returns amount of disconnected Nodes
17
+ */
18
+ async disconnectAll(deleteAllNodes = false) {
19
+ if (!this.nodes.size)
20
+ throw new Error("There are no nodes to disconnect (no nodes in the nodemanager)");
21
+ if (!this.nodes.filter(v => v.connected).size)
22
+ throw new Error("There are no nodes to disconnect (all nodes disconnected)");
23
+ let counter = 0;
24
+ for (const node of [...this.nodes.values()]) {
25
+ if (!node.connected)
26
+ continue;
27
+ await node.destroy(DestroyReasons.DisconnectAllNodes, deleteAllNodes);
28
+ counter++;
29
+ }
30
+ return counter;
31
+ }
32
+ /**
33
+ * Connects all not connected nodes
34
+ * @returns Amount of connected Nodes
35
+ */
36
+ async connectAll() {
37
+ if (!this.nodes.size)
38
+ throw new Error("There are no nodes to connect (no nodes in the nodemanager)");
39
+ if (!this.nodes.filter(v => !v.connected).size)
40
+ throw new Error("There are no nodes to connect (all nodes connected)");
41
+ let counter = 0;
42
+ for (const node of [...this.nodes.values()]) {
43
+ if (node.connected)
44
+ continue;
45
+ await node.connect();
46
+ counter++;
47
+ }
48
+ return counter;
49
+ }
50
+ /**
51
+ * Forcefully reconnects all nodes
52
+ * @returns amount of nodes
53
+ */
54
+ async reconnectAll() {
55
+ if (!this.nodes.size)
56
+ throw new Error("There are no nodes to reconnect (no nodes in the nodemanager)");
57
+ let counter = 0;
58
+ for (const node of [...this.nodes.values()]) {
59
+ const sessionId = node.sessionId ? `${node.sessionId}` : undefined;
60
+ await node.destroy(DestroyReasons.ReconnectAllNodes, false);
61
+ await node.connect(sessionId);
62
+ counter++;
63
+ }
64
+ return counter;
65
+ }
13
66
  createNode(options) {
14
67
  if (this.nodes.has(options.id || `${options.host}:${options.port}`))
15
68
  return this.nodes.get(options.id || `${options.host}:${options.port}`);
@@ -3,8 +3,8 @@ import { LavalinkManager } from "./LavalinkManager";
3
3
  import { LavalinkNode } from "./Node";
4
4
  import { Queue } from "./Queue";
5
5
  import { Track, UnresolvedTrack } from "./Track";
6
- import { LavalinkPlayerVoiceOptions, SearchPlatform, SearchResult, LavaSearchType, LavaSearchResponse, LavaSrcSearchPlatformBase } from "./Utils";
7
- type PlayerDestroyReasons = "QueueEmpty" | "NodeDestroy" | "NodeDeleted" | "LavalinkNoVoice" | "NodeReconnectFail" | "PlayerReconnectFail" | "Disconnected" | "ChannelDeleted";
6
+ import { LavalinkPlayerVoiceOptions, SearchResult, LavaSearchResponse, LavaSearchQuery, SearchQuery } from "./Utils";
7
+ type PlayerDestroyReasons = "QueueEmpty" | "NodeDestroy" | "NodeDeleted" | "LavalinkNoVoice" | "NodeReconnectFail" | "PlayerReconnectFail" | "Disconnected" | "ChannelDeleted" | "ReconnectAllNodes" | "DisconnectAllNodes";
8
8
  export type DestroyReasonsType = PlayerDestroyReasons | string;
9
9
  export declare const DestroyReasons: Record<PlayerDestroyReasons, PlayerDestroyReasons>;
10
10
  export interface PlayerJson {
@@ -138,20 +138,13 @@ export declare class Player {
138
138
  * @param ignoreVolumeDecrementer If it should ignore the volumedecrementer option
139
139
  */
140
140
  setVolume(volume: number, ignoreVolumeDecrementer?: boolean): Promise<void>;
141
- lavaSearch(query: {
142
- query: string;
143
- source: LavaSrcSearchPlatformBase;
144
- types?: LavaSearchType[];
145
- }, requestUser: unknown): Promise<import("./Utils").UnresolvedSearchResult | SearchResult | LavaSearchResponse>;
141
+ lavaSearch(query: LavaSearchQuery, requestUser: unknown): Promise<SearchResult | LavaSearchResponse>;
146
142
  /**
147
143
  *
148
144
  * @param query Query for your data
149
145
  * @param requestUser
150
146
  */
151
- search(query: {
152
- query: string;
153
- source?: SearchPlatform;
154
- } | string, requestUser: unknown): Promise<import("./Utils").UnresolvedSearchResult | SearchResult>;
147
+ search(query: SearchQuery, requestUser: unknown): Promise<import("./Utils").UnresolvedSearchResult | SearchResult>;
155
148
  /**
156
149
  * Pause the player
157
150
  */
@@ -1,6 +1,5 @@
1
1
  import { bandCampSearch } from "./CustomSearches/BandCampSearch";
2
2
  import { FilterManager } from "./Filters";
3
- import { DefaultSources } from "./LavalinkManagerStatics";
4
3
  import { Queue, QueueSaver } from "./Queue";
5
4
  import { queueTrackEnd } from "./Utils";
6
5
  export const DestroyReasons = {
@@ -11,7 +10,9 @@ export const DestroyReasons = {
11
10
  NodeReconnectFail: "NodeReconnectFail",
12
11
  Disconnected: "Disconnected",
13
12
  PlayerReconnectFail: "PlayerReconnectFail",
14
- ChannelDeleted: "ChannelDeleted"
13
+ ChannelDeleted: "ChannelDeleted",
14
+ DisconnectAllNodes: "DisconnectAllNodes",
15
+ ReconnectAllNodes: "ReconnectAllNodes"
15
16
  };
16
17
  export class Player {
17
18
  /** The Guild Id of the Player */
@@ -208,35 +209,7 @@ export class Player {
208
209
  return;
209
210
  }
210
211
  async lavaSearch(query, requestUser) {
211
- // transform the query object
212
- const Query = {
213
- query: typeof query === "string" ? query : query.query,
214
- types: query.types ? ["track", "playlist", "artist", "album", "text"].filter(v => query.types?.find(x => x.toLowerCase().startsWith(v))) : ["track", "playlist", "artist", "album", "text"],
215
- source: DefaultSources[(typeof query === "string" ? undefined : query.source?.trim?.()?.toLowerCase?.()) ?? this.LavalinkManager.options.playerOptions.defaultSearchPlatform.toLowerCase()] ?? (typeof query === "string" ? undefined : query.source?.trim?.()?.toLowerCase?.()) ?? this.LavalinkManager.options.playerOptions.defaultSearchPlatform
216
- };
217
- // if user does player.search("ytsearch:Hello")
218
- const foundSource = Object.keys(DefaultSources).find(source => Query.query.toLowerCase().startsWith(`${source}:`.toLowerCase()))?.trim?.()?.toLowerCase?.();
219
- if (foundSource && DefaultSources[foundSource]) {
220
- Query.source = DefaultSources[foundSource]; // set the source to ytsearch:
221
- Query.query = Query.query.slice(`${foundSource}:`.length, Query.query.length); // remove ytsearch: from the query
222
- }
223
- if (Query.source)
224
- this.LavalinkManager.utils.validateSourceString(this.node, Query.source);
225
- if (!["spsearch", "sprec", "amsearch", "dzsearch", "dzisrc", "ytmsearch", "ytsearch"].includes(Query.source))
226
- throw new SyntaxError(`Query.source must be a source from LavaSrc: "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ytmsearch" | "ytsearch"`);
227
- if (/^https?:\/\//.test(Query.query))
228
- return await this.search({ query: Query.query, source: Query.source }, requestUser);
229
- if (!this.node.info.plugins.find(v => v.name === "lavasearch-plugin"))
230
- throw new RangeError(`there is no lavasearch-plugin available in the lavalink node: ${this.node.id}`);
231
- const res = await this.node.request(`/loadsearch?query=${Query.source ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}${Query.types?.length ? `&types=${Query.types.join(",")}` : ""}`);
232
- return {
233
- tracks: res.tracks?.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) || [],
234
- albums: res.albums?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
235
- artists: res.artists?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
236
- playlists: res.playlists?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
237
- texts: res.texts?.map(v => ({ text: v.text, pluginInfo: v?.plugin || v.pluginInfo })) || [],
238
- pluginInfo: res.pluginInfo || res?.plugin
239
- };
212
+ return this.node.lavaSearch(query, requestUser);
240
213
  }
241
214
  /**
242
215
  *
@@ -244,43 +217,14 @@ export class Player {
244
217
  * @param requestUser
245
218
  */
246
219
  async search(query, requestUser) {
247
- // transform the query object
248
- const Query = {
249
- query: typeof query === "string" ? query : query.query,
250
- source: DefaultSources[(typeof query === "string" ? undefined : query.source?.trim?.()?.toLowerCase?.()) ?? this.LavalinkManager.options.playerOptions.defaultSearchPlatform.toLowerCase()] ?? (typeof query === "string" ? undefined : query.source?.trim?.()?.toLowerCase?.()) ?? this.LavalinkManager.options.playerOptions.defaultSearchPlatform
251
- };
252
- // if user does player.search("ytsearch:Hello")
253
- const foundSource = Object.keys(DefaultSources).find(source => Query.query?.toLowerCase?.()?.startsWith(`${source}:`.toLowerCase()))?.trim?.()?.toLowerCase?.();
254
- if (foundSource && DefaultSources[foundSource]) {
255
- Query.source = DefaultSources[foundSource]; // set the source to ytsearch:
256
- Query.query = Query.query.slice(`${foundSource}:`.length, Query.query.length); // remove ytsearch: from the query
257
- }
220
+ const Query = this.LavalinkManager.utils.transformQuery(query);
258
221
  if (/^https?:\/\//.test(Query.query))
259
222
  this.LavalinkManager.utils.validateQueryString(this.node, Query.source);
260
223
  else if (Query.source)
261
224
  this.LavalinkManager.utils.validateSourceString(this.node, Query.source);
262
- if (["bcsearch", "bandcamp"].includes(Query.source)) {
225
+ if (["bcsearch", "bandcamp"].includes(Query.source))
263
226
  return await bandCampSearch(this, Query.query, requestUser);
264
- }
265
- // ftts query parameters: ?voice=Olivia&audio_format=ogg_opus&translate=False&silence=1000&speed=1.0 | example raw get query: https://api.flowery.pw/v1/tts?voice=Olivia&audio_format=ogg_opus&translate=False&silence=0&speed=1.0&text=Hello%20World
266
- // request the data
267
- const res = await this.node.request(`/loadtracks?identifier=${!/^https?:\/\//.test(Query.query) ? `${Query.source}:${Query.source === "ftts" ? "//" : ""}` : ""}${encodeURIComponent(Query.query)}`);
268
- // transform the data which can be Error, Track or Track[] to enfore [Track]
269
- const resTracks = res.loadType === "playlist" ? res.data?.tracks : res.loadType === "track" ? [res.data] : res.loadType === "search" ? Array.isArray(res.data) ? res.data : [res.data] : [];
270
- return {
271
- loadType: res.loadType,
272
- exception: res.loadType === "error" ? res.data : null,
273
- pluginInfo: res.pluginInfo || {},
274
- playlist: res.loadType === "playlist" ? {
275
- title: res.data.info?.name || res.data.pluginInfo?.name || null,
276
- author: res.data.info?.author || res.data.pluginInfo?.author || null,
277
- 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,
278
- 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,
279
- selectedTrack: typeof res.data?.info?.selectedTrack !== "number" || res.data?.info?.selectedTrack === -1 ? null : resTracks[res.data?.info?.selectedTrack] ? this.LavalinkManager.utils.buildTrack(resTracks[res.data?.info?.selectedTrack], requestUser) : null,
280
- duration: resTracks.length ? resTracks.reduce((acc, cur) => acc + (cur?.info?.duration || 0), 0) : 0,
281
- } : null,
282
- tracks: resTracks.length ? resTracks.map(t => this.LavalinkManager.utils.buildTrack(t, requestUser)) : []
283
- };
227
+ return this.node.search(Query, requestUser);
284
228
  }
285
229
  /**
286
230
  * Pause the player