lavalink-client 1.1.12 → 1.1.15

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.
@@ -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
  /**
@@ -96,8 +96,23 @@ class LavalinkNode {
96
96
  throw new Error(`Node Request resulted into an error, request-URL: ${url} | headers: ${JSON.stringify(request.headers)}`);
97
97
  return parseAsText ? await request.body.text() : await request.body.json();
98
98
  }
99
- async search(querySourceString, requestUser) {
100
- const res = await this.request(`/loadsearch?query=${encodeURIComponent(decodeURIComponent(querySourceString))}`);
99
+ async search(query, requestUser) {
100
+ const Query = this.NodeManager.LavalinkManager.utils.transformQuery(query);
101
+ if (/^https?:\/\//.test(Query.query))
102
+ this.NodeManager.LavalinkManager.utils.validateQueryString(this, Query.source);
103
+ else if (Query.source)
104
+ this.NodeManager.LavalinkManager.utils.validateSourceString(this, Query.source);
105
+ if (["bcsearch", "bandcamp"].includes(Query.source)) {
106
+ throw new Error("Bandcamp Search only works on the player!");
107
+ }
108
+ let uri = `/loadtracks?identifier=`;
109
+ if (!/^https?:\/\//.test(Query.query))
110
+ uri += `${Query.source}:`;
111
+ if (Query.source === "ftts")
112
+ uri += `//${encodeURIComponent(encodeURI(decodeURIComponent(Query.query)))}`;
113
+ else
114
+ uri += encodeURIComponent(decodeURIComponent(Query.query));
115
+ const res = await this.request(uri);
101
116
  // transform the data which can be Error, Track or Track[] to enfore [Track]
102
117
  const resTracks = res.loadType === "playlist" ? res.data?.tracks : res.loadType === "track" ? [res.data] : res.loadType === "search" ? Array.isArray(res.data) ? res.data : [res.data] : [];
103
118
  return {
@@ -115,6 +130,28 @@ class LavalinkNode {
115
130
  tracks: (resTracks.length ? resTracks.map(t => this.NodeManager.LavalinkManager.utils.buildTrack(t, requestUser)) : [])
116
131
  };
117
132
  }
133
+ async lavaSearch(query, requestUser) {
134
+ const Query = this.NodeManager.LavalinkManager.utils.transformLavaSearchQuery(query);
135
+ if (Query.source)
136
+ this.NodeManager.LavalinkManager.utils.validateSourceString(this, Query.source);
137
+ if (/^https?:\/\//.test(Query.query))
138
+ return await this.search({ query: Query.query, source: Query.source }, requestUser);
139
+ if (!["spsearch", "sprec", "amsearch", "dzsearch", "dzisrc", "ytmsearch", "ytsearch"].includes(Query.source))
140
+ throw new SyntaxError(`Query.source must be a source from LavaSrc: "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ytmsearch" | "ytsearch"`);
141
+ if (!this.info.plugins.find(v => v.name === "lavasearch-plugin"))
142
+ throw new RangeError(`there is no lavasearch-plugin available in the lavalink node: ${this.id}`);
143
+ if (!this.info.plugins.find(v => v.name === "lavasrc-plugin"))
144
+ throw new RangeError(`there is no lavasrc-plugin available in the lavalink node: ${this.id}`);
145
+ const res = await this.request(`/loadsearch?query=${Query.source ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}${Query.types?.length ? `&types=${Query.types.join(",")}` : ""}`);
146
+ return {
147
+ tracks: res.tracks?.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) || [],
148
+ 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)) })) || [],
149
+ 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)) })) || [],
150
+ 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)) })) || [],
151
+ texts: res.texts?.map(v => ({ text: v.text, pluginInfo: v?.plugin || v.pluginInfo })) || [],
152
+ pluginInfo: res.pluginInfo || res?.plugin
153
+ };
154
+ }
118
155
  /**
119
156
  * Update the Player State on the Lavalink Server
120
157
  * @param data
@@ -168,7 +205,7 @@ class LavalinkNode {
168
205
  }
169
206
  this.socket = new ws_1.default(`ws${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}/v4/websocket`, { headers });
170
207
  this.socket.on("open", this.open.bind(this));
171
- this.socket.on("close", this.close.bind(this));
208
+ this.socket.on("close", (code, reason) => this.close(code, reason?.toString()));
172
209
  this.socket.on("message", this.message.bind(this));
173
210
  this.socket.on("error", this.error.bind(this));
174
211
  }
@@ -180,19 +217,24 @@ class LavalinkNode {
180
217
  * Destroys the Node-Connection (Websocket) and all player's of the node
181
218
  * @returns
182
219
  */
183
- destroy(destroyReason) {
220
+ destroy(destroyReason, deleteNode = true) {
184
221
  if (!this.connected)
185
222
  return;
186
223
  const players = this.NodeManager.LavalinkManager.players.filter(p => p.node.id == this.id);
187
224
  if (players)
188
225
  players.forEach(p => p.destroy(destroyReason || Player_1.DestroyReasons.NodeDestroy));
189
- this.socket.close(1000, "destroy");
226
+ this.socket.close(1000, "Node-Destroy");
190
227
  this.socket.removeAllListeners();
191
228
  this.socket = null;
192
229
  this.reconnectAttempts = 1;
193
230
  clearTimeout(this.reconnectTimeout);
194
- this.NodeManager.emit("destroy", this, destroyReason);
195
- this.NodeManager.nodes.delete(this.id);
231
+ if (deleteNode) {
232
+ this.NodeManager.emit("destroy", this, destroyReason);
233
+ this.NodeManager.nodes.delete(this.id);
234
+ }
235
+ else {
236
+ this.NodeManager.emit("disconnect", this, { code: 1000, reason: destroyReason });
237
+ }
196
238
  return;
197
239
  }
198
240
  /** Returns if connected to the Node. */
@@ -406,7 +448,20 @@ class LavalinkNode {
406
448
  get poolAddress() {
407
449
  return `http${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}`;
408
450
  }
409
- reconnect() {
451
+ reconnect(instaReconnect = false) {
452
+ if (instaReconnect) {
453
+ if (this.reconnectAttempts >= this.options.retryAmount) {
454
+ const error = new Error(`Unable to connect after ${this.options.retryAmount} attempts.`);
455
+ this.NodeManager.emit("error", this, error);
456
+ return this.destroy(Player_1.DestroyReasons.NodeReconnectFail);
457
+ }
458
+ this.socket.removeAllListeners();
459
+ this.socket = null;
460
+ this.NodeManager.emit("reconnecting", this);
461
+ this.connect();
462
+ this.reconnectAttempts++;
463
+ return;
464
+ }
410
465
  this.reconnectTimeout = setTimeout(() => {
411
466
  if (this.reconnectAttempts >= this.options.retryAmount) {
412
467
  const error = new Error(`Unable to connect after ${this.options.retryAmount} attempts.`);
@@ -420,24 +475,21 @@ class LavalinkNode {
420
475
  this.reconnectAttempts++;
421
476
  }, this.options.retryDelay || 1000);
422
477
  }
423
- open() {
478
+ async open() {
424
479
  if (this.reconnectTimeout)
425
480
  clearTimeout(this.reconnectTimeout);
426
481
  // reset the reconnect attempts amount
427
482
  this.reconnectAttempts = 1;
483
+ this.info = await this.fetchInfo().catch(() => null);
484
+ if (!this.info && ["v3", "v4"].includes(this.version)) {
485
+ const errorString = `Lavalink Node (${this.poolAddress}) does not provide any /${this.version}/info`;
486
+ throw new Error(errorString);
487
+ }
428
488
  this.NodeManager.emit("connect", this);
429
- setTimeout(() => {
430
- this.fetchInfo().then(x => this.info = x).catch(() => null).then(() => {
431
- if (!this.info && ["v3", "v4"].includes(this.version)) {
432
- const errorString = `Lavalink Node (${this.poolAddress}) does not provide any /${this.version}/info`;
433
- throw new Error(errorString);
434
- }
435
- });
436
- }, 1500);
437
489
  }
438
490
  close(code, reason) {
439
491
  this.NodeManager.emit("disconnect", this, { code, reason });
440
- if (code !== 1000 || reason !== "destroy")
492
+ if (code !== 1000 || reason !== "Node-Destroy")
441
493
  this.reconnect();
442
494
  }
443
495
  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;
@@ -13,6 +13,59 @@ class NodeManager extends stream_1.EventEmitter {
13
13
  if (this.LavalinkManager.options.nodes)
14
14
  this.LavalinkManager.options.nodes.forEach(node => this.createNode(node));
15
15
  }
16
+ /**
17
+ * Disconnects all Nodes from lavalink ws sockets
18
+ * @param deleteAllNodes if the nodes should also be deleted from nodeManager.nodes
19
+ * @returns amount of disconnected Nodes
20
+ */
21
+ async disconnectAll(deleteAllNodes = false) {
22
+ if (!this.nodes.size)
23
+ throw new Error("There are no nodes to disconnect (no nodes in the nodemanager)");
24
+ if (!this.nodes.filter(v => v.connected).size)
25
+ throw new Error("There are no nodes to disconnect (all nodes disconnected)");
26
+ let counter = 0;
27
+ for (const node of [...this.nodes.values()]) {
28
+ if (!node.connected)
29
+ continue;
30
+ await node.destroy(Player_1.DestroyReasons.DisconnectAllNodes, deleteAllNodes);
31
+ counter++;
32
+ }
33
+ return counter;
34
+ }
35
+ /**
36
+ * Connects all not connected nodes
37
+ * @returns Amount of connected Nodes
38
+ */
39
+ async connectAll() {
40
+ if (!this.nodes.size)
41
+ throw new Error("There are no nodes to connect (no nodes in the nodemanager)");
42
+ if (!this.nodes.filter(v => !v.connected).size)
43
+ throw new Error("There are no nodes to connect (all nodes connected)");
44
+ let counter = 0;
45
+ for (const node of [...this.nodes.values()]) {
46
+ if (node.connected)
47
+ continue;
48
+ await node.connect();
49
+ counter++;
50
+ }
51
+ return counter;
52
+ }
53
+ /**
54
+ * Forcefully reconnects all nodes
55
+ * @returns amount of nodes
56
+ */
57
+ async reconnectAll() {
58
+ if (!this.nodes.size)
59
+ throw new Error("There are no nodes to reconnect (no nodes in the nodemanager)");
60
+ let counter = 0;
61
+ for (const node of [...this.nodes.values()]) {
62
+ const sessionId = node.sessionId ? `${node.sessionId}` : undefined;
63
+ await node.destroy(Player_1.DestroyReasons.ReconnectAllNodes, false);
64
+ await node.connect(sessionId);
65
+ counter++;
66
+ }
67
+ return counter;
68
+ }
16
69
  createNode(options) {
17
70
  if (this.nodes.has(options.id || `${options.host}:${options.port}`))
18
71
  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
  */
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Player = exports.DestroyReasons = void 0;
4
4
  const BandCampSearch_1 = require("./CustomSearches/BandCampSearch");
5
5
  const Filters_1 = require("./Filters");
6
- const LavalinkManagerStatics_1 = require("./LavalinkManagerStatics");
7
6
  const Queue_1 = require("./Queue");
8
7
  const Utils_1 = require("./Utils");
9
8
  exports.DestroyReasons = {
@@ -14,7 +13,9 @@ exports.DestroyReasons = {
14
13
  NodeReconnectFail: "NodeReconnectFail",
15
14
  Disconnected: "Disconnected",
16
15
  PlayerReconnectFail: "PlayerReconnectFail",
17
- ChannelDeleted: "ChannelDeleted"
16
+ ChannelDeleted: "ChannelDeleted",
17
+ DisconnectAllNodes: "DisconnectAllNodes",
18
+ ReconnectAllNodes: "ReconnectAllNodes"
18
19
  };
19
20
  class Player {
20
21
  /** The Guild Id of the Player */
@@ -211,35 +212,7 @@ class Player {
211
212
  return;
212
213
  }
213
214
  async lavaSearch(query, requestUser) {
214
- // transform the query object
215
- const Query = {
216
- query: typeof query === "string" ? query : query.query,
217
- types: query.types ? ["track", "playlist", "artist", "album", "text"].filter(v => query.types?.find(x => x.toLowerCase().startsWith(v))) : ["track", "playlist", "artist", "album", "text"],
218
- 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
219
- };
220
- // if user does player.search("ytsearch:Hello")
221
- const foundSource = Object.keys(LavalinkManagerStatics_1.DefaultSources).find(source => Query.query.toLowerCase().startsWith(`${source}:`.toLowerCase()))?.trim?.()?.toLowerCase?.();
222
- if (foundSource && LavalinkManagerStatics_1.DefaultSources[foundSource]) {
223
- Query.source = LavalinkManagerStatics_1.DefaultSources[foundSource]; // set the source to ytsearch:
224
- Query.query = Query.query.slice(`${foundSource}:`.length, Query.query.length); // remove ytsearch: from the query
225
- }
226
- if (Query.source)
227
- this.LavalinkManager.utils.validateSourceString(this.node, Query.source);
228
- if (!["spsearch", "sprec", "amsearch", "dzsearch", "dzisrc", "ytmsearch", "ytsearch"].includes(Query.source))
229
- throw new SyntaxError(`Query.source must be a source from LavaSrc: "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ytmsearch" | "ytsearch"`);
230
- if (/^https?:\/\//.test(Query.query))
231
- return await this.search({ query: Query.query, source: Query.source }, requestUser);
232
- if (!this.node.info.plugins.find(v => v.name === "lavasearch-plugin"))
233
- throw new RangeError(`there is no lavasearch-plugin available in the lavalink node: ${this.node.id}`);
234
- const res = await this.node.request(`/loadsearch?query=${Query.source ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}${Query.types?.length ? `&types=${Query.types.join(",")}` : ""}`);
235
- return {
236
- tracks: res.tracks?.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) || [],
237
- albums: res.albums?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
238
- artists: res.artists?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
239
- playlists: res.playlists?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
240
- texts: res.texts?.map(v => ({ text: v.text, pluginInfo: v?.plugin || v.pluginInfo })) || [],
241
- pluginInfo: res.pluginInfo || res?.plugin
242
- };
215
+ return this.node.lavaSearch(query, requestUser);
243
216
  }
244
217
  /**
245
218
  *
@@ -247,43 +220,14 @@ class Player {
247
220
  * @param requestUser
248
221
  */
249
222
  async search(query, requestUser) {
250
- // transform the query object
251
- const Query = {
252
- query: typeof query === "string" ? query : query.query,
253
- 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
254
- };
255
- // if user does player.search("ytsearch:Hello")
256
- const foundSource = Object.keys(LavalinkManagerStatics_1.DefaultSources).find(source => Query.query?.toLowerCase?.()?.startsWith(`${source}:`.toLowerCase()))?.trim?.()?.toLowerCase?.();
257
- if (foundSource && LavalinkManagerStatics_1.DefaultSources[foundSource]) {
258
- Query.source = LavalinkManagerStatics_1.DefaultSources[foundSource]; // set the source to ytsearch:
259
- Query.query = Query.query.slice(`${foundSource}:`.length, Query.query.length); // remove ytsearch: from the query
260
- }
223
+ const Query = this.LavalinkManager.utils.transformQuery(query);
261
224
  if (/^https?:\/\//.test(Query.query))
262
225
  this.LavalinkManager.utils.validateQueryString(this.node, Query.source);
263
226
  else if (Query.source)
264
227
  this.LavalinkManager.utils.validateSourceString(this.node, Query.source);
265
- if (["bcsearch", "bandcamp"].includes(Query.source)) {
228
+ if (["bcsearch", "bandcamp"].includes(Query.source))
266
229
  return await (0, BandCampSearch_1.bandCampSearch)(this, Query.query, requestUser);
267
- }
268
- // 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
269
- // request the data
270
- const res = await this.node.request(`/loadtracks?identifier=${!/^https?:\/\//.test(Query.query) ? `${Query.source}:${Query.source === "ftts" ? "//" : ""}` : ""}${encodeURIComponent(Query.query)}`);
271
- // transform the data which can be Error, Track or Track[] to enfore [Track]
272
- const resTracks = res.loadType === "playlist" ? res.data?.tracks : res.loadType === "track" ? [res.data] : res.loadType === "search" ? Array.isArray(res.data) ? res.data : [res.data] : [];
273
- return {
274
- loadType: res.loadType,
275
- exception: res.loadType === "error" ? res.data : null,
276
- pluginInfo: res.pluginInfo || {},
277
- playlist: res.loadType === "playlist" ? {
278
- title: res.data.info?.name || res.data.pluginInfo?.name || null,
279
- author: res.data.info?.author || res.data.pluginInfo?.author || null,
280
- 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,
281
- 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,
282
- 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,
283
- duration: resTracks.length ? resTracks.reduce((acc, cur) => acc + (cur?.info?.duration || 0), 0) : 0,
284
- } : null,
285
- tracks: resTracks.length ? resTracks.map(t => this.LavalinkManager.utils.buildTrack(t, requestUser)) : []
286
- };
230
+ return this.node.search(Query, requestUser);
287
231
  }
288
232
  /**
289
233
  * Pause the player
@@ -89,6 +89,15 @@ export declare class ManagerUtils {
89
89
  isUnresolvedTrackQuery(data: UnresolvedQuery | any): boolean;
90
90
  getClosestTrack(data: UnresolvedTrack, player: Player): Promise<Track | undefined>;
91
91
  validateQueryString(node: LavalinkNode, queryString: string): void;
92
+ transformQuery(query: SearchQuery): {
93
+ query: string;
94
+ source: any;
95
+ };
96
+ transformLavaSearchQuery(query: LavaSearchQuery): {
97
+ query: string;
98
+ types: string[];
99
+ source: any;
100
+ };
92
101
  validateSourceString(node: LavalinkNode, sourceString: SearchPlatform): void;
93
102
  }
94
103
  /**
@@ -322,4 +331,13 @@ export interface LavaSearchResponse {
322
331
  /** Addition result data provided by plugins */
323
332
  pluginInfo: PluginInfo;
324
333
  }
334
+ export type SearchQuery = {
335
+ query: string;
336
+ source?: SearchPlatform;
337
+ } | string;
338
+ export type LavaSearchQuery = {
339
+ query: string;
340
+ source: LavaSrcSearchPlatformBase;
341
+ types?: LavaSearchType[];
342
+ };
325
343
  export {};
@@ -194,6 +194,32 @@ class ManagerUtils {
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`);
@@ -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.`);
@@ -416,24 +471,21 @@ export class LavalinkNode {
416
471
  this.reconnectAttempts++;
417
472
  }, this.options.retryDelay || 1000);
418
473
  }
419
- open() {
474
+ async open() {
420
475
  if (this.reconnectTimeout)
421
476
  clearTimeout(this.reconnectTimeout);
422
477
  // reset the reconnect attempts amount
423
478
  this.reconnectAttempts = 1;
479
+ this.info = await this.fetchInfo().catch(() => null);
480
+ if (!this.info && ["v3", "v4"].includes(this.version)) {
481
+ const errorString = `Lavalink Node (${this.poolAddress}) does not provide any /${this.version}/info`;
482
+ throw new Error(errorString);
483
+ }
424
484
  this.NodeManager.emit("connect", this);
425
- setTimeout(() => {
426
- this.fetchInfo().then(x => this.info = x).catch(() => null).then(() => {
427
- if (!this.info && ["v3", "v4"].includes(this.version)) {
428
- const errorString = `Lavalink Node (${this.poolAddress}) does not provide any /${this.version}/info`;
429
- throw new Error(errorString);
430
- }
431
- });
432
- }, 1500);
433
485
  }
434
486
  close(code, reason) {
435
487
  this.NodeManager.emit("disconnect", this, { code, reason });
436
- if (code !== 1000 || reason !== "destroy")
488
+ if (code !== 1000 || reason !== "Node-Destroy")
437
489
  this.reconnect();
438
490
  }
439
491
  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
@@ -89,6 +89,15 @@ export declare class ManagerUtils {
89
89
  isUnresolvedTrackQuery(data: UnresolvedQuery | any): boolean;
90
90
  getClosestTrack(data: UnresolvedTrack, player: Player): Promise<Track | undefined>;
91
91
  validateQueryString(node: LavalinkNode, queryString: string): void;
92
+ transformQuery(query: SearchQuery): {
93
+ query: string;
94
+ source: any;
95
+ };
96
+ transformLavaSearchQuery(query: LavaSearchQuery): {
97
+ query: string;
98
+ types: string[];
99
+ source: any;
100
+ };
92
101
  validateSourceString(node: LavalinkNode, sourceString: SearchPlatform): void;
93
102
  }
94
103
  /**
@@ -322,4 +331,13 @@ export interface LavaSearchResponse {
322
331
  /** Addition result data provided by plugins */
323
332
  pluginInfo: PluginInfo;
324
333
  }
334
+ export type SearchQuery = {
335
+ query: string;
336
+ source?: SearchPlatform;
337
+ } | string;
338
+ export type LavaSearchQuery = {
339
+ query: string;
340
+ source: LavaSrcSearchPlatformBase;
341
+ types?: LavaSearchType[];
342
+ };
325
343
  export {};
@@ -191,6 +191,32 @@ export class ManagerUtils {
191
191
  }
192
192
  return;
193
193
  }
194
+ transformQuery(query) {
195
+ const Query = {
196
+ query: typeof query === "string" ? query : query.query,
197
+ 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?.toLowerCase?.()
198
+ };
199
+ const foundSource = Object.keys(DefaultSources).find(source => Query.query?.toLowerCase?.()?.startsWith(`${source}:`.toLowerCase()))?.trim?.()?.toLowerCase?.();
200
+ if (foundSource && DefaultSources[foundSource]) {
201
+ Query.source = DefaultSources[foundSource]; // set the source to ytsearch:
202
+ Query.query = Query.query.slice(`${foundSource}:`.length, Query.query.length); // remove ytsearch: from the query
203
+ }
204
+ return Query;
205
+ }
206
+ transformLavaSearchQuery(query) {
207
+ // transform the query object
208
+ const Query = {
209
+ query: typeof query === "string" ? query : query.query,
210
+ types: query.types ? ["track", "playlist", "artist", "album", "text"].filter(v => query.types?.find(x => x.toLowerCase().startsWith(v))) : ["track", "playlist", "artist", "album", /*"text"*/],
211
+ 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?.toLowerCase?.()
212
+ };
213
+ const foundSource = Object.keys(DefaultSources).find(source => Query.query.toLowerCase().startsWith(`${source}:`.toLowerCase()))?.trim?.()?.toLowerCase?.();
214
+ if (foundSource && DefaultSources[foundSource]) {
215
+ Query.source = DefaultSources[foundSource]; // set the source to ytsearch:
216
+ Query.query = Query.query.slice(`${foundSource}:`.length, Query.query.length); // remove ytsearch: from the query
217
+ }
218
+ return Query;
219
+ }
194
220
  validateSourceString(node, sourceString) {
195
221
  if (!sourceString)
196
222
  throw new Error(`No SourceString was provided`);
@@ -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
  /**
@@ -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;
@@ -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
  */
@@ -89,6 +89,15 @@ export declare class ManagerUtils {
89
89
  isUnresolvedTrackQuery(data: UnresolvedQuery | any): boolean;
90
90
  getClosestTrack(data: UnresolvedTrack, player: Player): Promise<Track | undefined>;
91
91
  validateQueryString(node: LavalinkNode, queryString: string): void;
92
+ transformQuery(query: SearchQuery): {
93
+ query: string;
94
+ source: any;
95
+ };
96
+ transformLavaSearchQuery(query: LavaSearchQuery): {
97
+ query: string;
98
+ types: string[];
99
+ source: any;
100
+ };
92
101
  validateSourceString(node: LavalinkNode, sourceString: SearchPlatform): void;
93
102
  }
94
103
  /**
@@ -322,4 +331,13 @@ export interface LavaSearchResponse {
322
331
  /** Addition result data provided by plugins */
323
332
  pluginInfo: PluginInfo;
324
333
  }
334
+ export type SearchQuery = {
335
+ query: string;
336
+ source?: SearchPlatform;
337
+ } | string;
338
+ export type LavaSearchQuery = {
339
+ query: string;
340
+ source: LavaSrcSearchPlatformBase;
341
+ types?: LavaSearchType[];
342
+ };
325
343
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavalink-client",
3
- "version": "1.1.12",
3
+ "version": "1.1.15",
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",