lavalink-client 1.1.1 → 1.1.3

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.
@@ -91,6 +91,8 @@ class LavalinkNode {
91
91
  this.calls++;
92
92
  if (options.method === "DELETE")
93
93
  return;
94
+ if (request.statusCode === 404)
95
+ throw new Error(`Node Request resulted into an error, request-URL: ${url} | headers: ${JSON.stringify(request.headers)}`);
94
96
  return parseAsText ? await request.body.text() : await request.body.json();
95
97
  }
96
98
  /**
@@ -2,8 +2,8 @@ import { EQBand, FilterData, FilterManager, LavalinkFilterData } from "./Filters
2
2
  import { LavalinkManager } from "./LavalinkManager";
3
3
  import { LavalinkNode } from "./Node";
4
4
  import { Queue } from "./Queue";
5
- import { Track } from "./Track";
6
- import { LavalinkPlayerVoiceOptions, SearchPlatform, SearchResult } from "./Utils";
5
+ import { Track, UnresolvedTrack } from "./Track";
6
+ import { LavalinkPlayerVoiceOptions, SearchPlatform, SearchResult, LavaSearchType, LavaSearchResponse, LavaSrcSearchPlatformBase } from "./Utils";
7
7
  type PlayerDestroyReasons = "QueueEmpty" | "NodeDestroy" | "NodeDeleted" | "LavalinkNoVoice" | "NodeReconnectFail" | "PlayerReconnectFail" | "Disconnected" | "ChannelDeleted";
8
8
  export type DestroyReasonsType = PlayerDestroyReasons | string;
9
9
  export declare const DestroyReasons: Record<PlayerDestroyReasons, PlayerDestroyReasons>;
@@ -43,7 +43,7 @@ export interface PlayerOptions {
43
43
  }
44
44
  export interface PlayOptions {
45
45
  /** Which Track to play | don't provide, if it should pick from the Queue */
46
- track?: Track;
46
+ track?: Track | UnresolvedTrack;
47
47
  /** Encoded Track to use, instead of the queue system... */
48
48
  encodedTrack?: string | null;
49
49
  /** Encoded Track to use&search, instead of the queue system (yt only)... */
@@ -138,6 +138,11 @@ 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<SearchResult | LavaSearchResponse>;
141
146
  /**
142
147
  *
143
148
  * @param query Query for your data
@@ -123,7 +123,9 @@ class Player {
123
123
  clearTimeout(this.get("internal_queueempty"));
124
124
  this.set("internal_queueempty", undefined);
125
125
  }
126
- if (options?.track && this.LavalinkManager.utils.isTrack(options?.track)) {
126
+ if (options?.track && (this.LavalinkManager.utils.isTrack(options?.track) || this.LavalinkManager.utils.isUnresolvedTrack(options.track))) {
127
+ if (this.LavalinkManager.utils.isUnresolvedTrack(options.track))
128
+ await options.track.resolve(this);
127
129
  await this.queue.add(options?.track, 0);
128
130
  await (0, Utils_1.queueTrackEnd)(this);
129
131
  }
@@ -131,13 +133,17 @@ class Player {
131
133
  await (0, Utils_1.queueTrackEnd)(this);
132
134
  // @ts-ignore
133
135
  if (this.queue.current && this.LavalinkManager.utils.isUnresolvedTrack(this.queue.current)) {
134
- try {
135
- this.queue.current = await this.LavalinkManager.utils.getClosestTrack({ ...(this.queue.current || {}) }, this);
136
+ try { // @ts-ignore
137
+ this.queue.current = await this.queue.current.resolve(this);
136
138
  }
137
139
  catch (error) {
138
140
  this.LavalinkManager.emit("trackError", this, this.queue.current, error);
141
+ if (options && "track" in options)
142
+ delete options.track;
143
+ if (options && "encodedTrack" in options)
144
+ delete options.encodedTrack;
139
145
  if (this.queue.tracks[0])
140
- return this.play();
146
+ return this.play(options);
141
147
  return;
142
148
  }
143
149
  }
@@ -202,6 +208,37 @@ class Player {
202
208
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
203
209
  return;
204
210
  }
211
+ async lavaSearch(query, requestUser) {
212
+ // transform the query object
213
+ const Query = {
214
+ query: typeof query === "string" ? query : query.query,
215
+ types: query.types ? ["track", "playlist", "artist", "album", "text"].filter(v => query.types?.find(x => x.toLowerCase().startsWith(v))) : ["track", "playlist", "artist", "album", "text"],
216
+ 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
217
+ };
218
+ // if user does player.search("ytsearch:Hello")
219
+ const foundSource = [...Object.keys(LavalinkManagerStatics_1.DefaultSources)].find(source => Query.query.toLowerCase().startsWith(`${source}:`.toLowerCase()))?.trim?.()?.toLowerCase?.();
220
+ if (foundSource && LavalinkManagerStatics_1.DefaultSources[foundSource]) {
221
+ Query.source = LavalinkManagerStatics_1.DefaultSources[foundSource]; // set the source to ytsearch:
222
+ Query.query = Query.query.slice(`${foundSource}:`.length, Query.query.length); // remove ytsearch: from the query
223
+ }
224
+ if (Query.source)
225
+ this.LavalinkManager.utils.validateSourceString(this.node, Query.source);
226
+ if (!["spsearch", "sprec", "amsearch", "dzsearch", "dzisrc", "ytmsearch", "ytsearch"].includes(Query.source))
227
+ throw new SyntaxError(`Query.source must be a source from LavaSrc: "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ytmsearch" | "ytsearch"`);
228
+ if (/^https?:\/\//.test(Query.query))
229
+ return await this.search({ query: Query.query, source: Query.source }, requestUser);
230
+ if (!this.node.info.plugins.find(v => v.name === "lavasearch-plugin"))
231
+ throw new RangeError(`there is no lavasearch-plugin available in the lavalink node: ${this.node.id}`);
232
+ const res = await this.node.request(`/loadsearch?query=${Query.source ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}${Query.types?.length ? `&types=${Query.types.join(",")}` : ""}`);
233
+ return {
234
+ tracks: res.tracks?.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) || [],
235
+ albums: res.albums?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
236
+ artists: res.artists?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
237
+ playlists: res.playlists?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
238
+ texts: res.texts?.map(v => ({ text: v.text, pluginInfo: v?.plugin || v.pluginInfo })) || [],
239
+ pluginInfo: res.pluginInfo || res?.plugin
240
+ };
241
+ }
205
242
  /**
206
243
  *
207
244
  * @param query Query for your data
@@ -211,14 +248,18 @@ class Player {
211
248
  // transform the query object
212
249
  const Query = {
213
250
  query: typeof query === "string" ? query : query.query,
214
- source: LavalinkManagerStatics_1.DefaultSources[(typeof query === "string" ? undefined : query.source) ?? this.LavalinkManager.options.playerOptions.defaultSearchPlatform] ?? (typeof query === "string" ? undefined : query.source) ?? this.LavalinkManager.options.playerOptions.defaultSearchPlatform
251
+ 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
215
252
  };
216
253
  // if user does player.search("ytsearch:Hello")
217
- const foundSource = [...Object.keys(LavalinkManagerStatics_1.DefaultSources)].find(source => Query.query.startsWith(`${source}:`));
254
+ const foundSource = [...Object.keys(LavalinkManagerStatics_1.DefaultSources)].find(source => Query.query?.toLowerCase?.()?.startsWith(`${source}:`.toLowerCase()))?.trim?.()?.toLowerCase?.();
218
255
  if (foundSource && LavalinkManagerStatics_1.DefaultSources[foundSource]) {
219
256
  Query.source = LavalinkManagerStatics_1.DefaultSources[foundSource]; // set the source to ytsearch:
220
- Query.query = Query.query.replace(`${foundSource}:`, ""); // remove ytsearch: from the query
257
+ Query.query = Query.query.slice(`${foundSource}:`.length, Query.query.length); // remove ytsearch: from the query
221
258
  }
259
+ if (/^https?:\/\//.test(Query.query))
260
+ this.LavalinkManager.utils.validatedQueryString(this.node, Query.source);
261
+ else if (Query.source)
262
+ this.LavalinkManager.utils.validateSourceString(this.node, Query.source);
222
263
  // 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
223
264
  // request the data
224
265
  const res = await this.node.request(`/loadtracks?identifier=${!/^https?:\/\//.test(Query.query) ? `${Query.source}:${Query.source === "ftts" ? "//" : ""}` : ""}${encodeURIComponent(Query.query)}`);
@@ -17,7 +17,21 @@ export interface TrackInfo {
17
17
  }
18
18
  export interface PluginInfo {
19
19
  /** The Type provided by a plugin */
20
- type?: string;
20
+ type?: "album" | "playlist" | "artist" | "recommendations" | string;
21
+ /** The Identifier provided by a plugin */
22
+ albumName?: string;
23
+ /** The url of the album art */
24
+ albumArtUrl?: string;
25
+ /** The url of the artist */
26
+ artistUrl?: string;
27
+ /** The url of the artist artwork */
28
+ artistArtworkUrl?: string;
29
+ /** The url of the preview */
30
+ previewUrl?: string;
31
+ /** Whether the track is a preview */
32
+ isPreview?: boolean;
33
+ /** The total number of tracks in the playlist */
34
+ totalTracks?: number;
21
35
  /** The Identifier provided by a plugin */
22
36
  identifier?: string;
23
37
  /** The ArtworkUrl provided by a plugin */
@@ -7,13 +7,18 @@ export declare const TrackSymbol: unique symbol;
7
7
  export declare const UnresolvedTrackSymbol: unique symbol;
8
8
  export declare const QueueSymbol: unique symbol;
9
9
  export declare const NodeSymbol: unique symbol;
10
- export type LavalinkSearchPlatform = "ytsearch" | "ytmsearch" | "scsearch" | "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ymsearch" | "speak" | "tts" | "ftts";
10
+ export type LavaSrcSearchPlatformBase = "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ymsearch";
11
+ export type LavaSrcSearchPlatform = LavaSrcSearchPlatformBase | "ftts";
12
+ export type DuncteSearchPlatform = "speak" | "tts";
13
+ export type LavalinkSearchPlatform = "ytsearch" | "ytmsearch" | "scsearch" | LavaSrcSearchPlatform | DuncteSearchPlatform;
11
14
  export type ClientSearchPlatform = "youtube" | "yt" | "youtube music" | "youtubemusic" | "ytm" | "soundcloud" | "sc" | "am" | "apple music" | "applemusic" | "apple" | "sp" | "spsuggestion" | "spotify" | "dz" | "deezer" | "yandex" | "yandex music" | "yandexmusic";
12
15
  export type SearchPlatform = LavalinkSearchPlatform | ClientSearchPlatform;
13
16
  export type SourcesRegex = "YoutubeRegex" | "YoutubeMusicRegex" | "SoundCloudRegex" | "SoundCloudMobileRegex" | "DeezerTrackRegex" | "DeezerArtistRegex" | "DeezerEpisodeRegex" | "DeezerMixesRegex" | "DeezerPageLinkRegex" | "DeezerPlaylistRegex" | "DeezerAlbumRegex" | "AllDeezerRegex" | "AllDeezerRegexWithoutPageLink" | "SpotifySongRegex" | "SpotifyPlaylistRegex" | "SpotifyArtistRegex" | "SpotifyEpisodeRegex" | "SpotifyShowRegex" | "SpotifyAlbumRegex" | "AllSpotifyRegex" | "mp3Url" | "m3uUrl" | "m3u8Url" | "mp4Url" | "m4aUrl" | "wavUrl" | "aacpUrl" | "tiktok" | "mixcloud" | "musicYandex" | "radiohost" | "bandcamp" | "appleMusic" | "TwitchTv" | "vimeo";
14
17
  export interface PlaylistInfo {
15
18
  /** The playlist title. */
16
19
  title: string;
20
+ /** The playlist name (if provided instead of title) */
21
+ name: string;
17
22
  /** The Playlist Author */
18
23
  author?: string;
19
24
  /** The Playlist Thumbnail */
@@ -72,7 +77,8 @@ export declare class ManagerUitls {
72
77
  * @param requester
73
78
  */
74
79
  buildUnresolvedTrack(query: UnresolvedQuery | UnresolvedTrack, requester: unknown): UnresolvedTrack;
75
- validatedQuery(queryString: string, node: LavalinkNode): void;
80
+ validatedQueryString(node: LavalinkNode, queryString: string): void;
81
+ validateSourceString(node: LavalinkNode, sourceString: SearchPlatform): void;
76
82
  }
77
83
  /**
78
84
  * @internal
@@ -282,4 +288,27 @@ export interface NodeMessage extends NodeStats {
282
288
  guildId: string;
283
289
  }
284
290
  export declare function queueTrackEnd(player: Player): Promise<Track>;
291
+ export type LavaSearchType = "track" | "album" | "artist" | "playlist" | "text" | "tracks" | "albums" | "artists" | "playlists" | "texts";
292
+ export interface LavaSearchFilteredResponse {
293
+ info: PlaylistInfo;
294
+ pluginInfo: PluginInfo;
295
+ tracks: Track[];
296
+ }
297
+ export interface LavaSearchResponse {
298
+ /** An array of tracks, only present if track is in types */
299
+ tracks: Track[];
300
+ /** An array of albums, only present if album is in types */
301
+ albums: LavaSearchFilteredResponse[];
302
+ /** An array of artists, only present if artist is in types */
303
+ artists: LavaSearchFilteredResponse[];
304
+ /** An array of playlists, only present if playlist is in types */
305
+ playlists: LavaSearchFilteredResponse[];
306
+ /** An array of text results, only present if text is in types */
307
+ texts: {
308
+ text: string;
309
+ pluginInfo: PluginInfo;
310
+ }[];
311
+ /** Addition result data provided by plugins */
312
+ pluginInfo: PluginInfo;
313
+ }
285
314
  export {};
@@ -112,7 +112,7 @@ class ManagerUitls {
112
112
  return false;
113
113
  if (data[exports.UnresolvedTrackSymbol] === true)
114
114
  return true;
115
- return typeof data === "object" && "info" in data && typeof data.info.title === "string" && typeof data.resolve === "function";
115
+ return typeof data === "object" && (("info" in data && typeof data.info.title === "string") || typeof data.encoded === "string") && typeof data.resolve === "function";
116
116
  }
117
117
  /**
118
118
  * Checks if the provided argument is a valid UnresolvedTrack.
@@ -134,7 +134,7 @@ class ManagerUitls {
134
134
  throw new RangeError('Argument "query" must be present.');
135
135
  const unresolvedTrack = {
136
136
  encoded: query.encoded || undefined,
137
- info: query.info ?? query,
137
+ info: query.info ? query.info : query.title ? query : undefined,
138
138
  requester: typeof this.manager.options?.playerOptions?.requesterTransformer === "function" ? this.manager.options?.playerOptions?.requesterTransformer((query?.requester || requester)) : requester,
139
139
  async resolve(player) {
140
140
  const closest = await getClosestTrack(this, player, player.LavalinkManager.utils);
@@ -145,10 +145,12 @@ class ManagerUitls {
145
145
  return;
146
146
  }
147
147
  };
148
+ if (!this.isUnresolvedTrack(unresolvedTrack))
149
+ throw SyntaxError("Could not build Unresolved Track");
148
150
  Object.defineProperty(unresolvedTrack, exports.UnresolvedTrackSymbol, { configurable: true, value: true });
149
151
  return unresolvedTrack;
150
152
  }
151
- validatedQuery(queryString, node) {
153
+ validatedQueryString(node, queryString) {
152
154
  if (!node.info)
153
155
  throw new Error("No Lavalink Node was provided");
154
156
  if (!node.info.sourceManagers?.length)
@@ -190,12 +192,14 @@ class ManagerUitls {
190
192
  if (LavalinkManagerStatics_1.SourceLinksRegexes.musicYandex.test(queryString) && !node.info.sourceManagers.includes("yandexmusic")) {
191
193
  throw new Error("Lavalink Node has not 'yandexmusic' enabled");
192
194
  }
193
- const hasSource = queryString.split(":")[0];
194
- if (queryString.split(" ").length <= 1 || !queryString.split(" ")[0].includes(":"))
195
- return;
196
- const source = LavalinkManagerStatics_1.DefaultSources[hasSource];
195
+ return;
196
+ }
197
+ validateSourceString(node, sourceString) {
198
+ if (!sourceString)
199
+ throw new Error(`No SourceString was provided`);
200
+ const source = LavalinkManagerStatics_1.DefaultSources[sourceString.toLowerCase()] || Object.values(LavalinkManagerStatics_1.DefaultSources).find(v => v.toLowerCase() === sourceString?.toLowerCase());
197
201
  if (!source)
198
- throw new Error(`Lavalink Node SearchQuerySource: '${hasSource}' is not available`);
202
+ throw new Error(`Lavalink Node SearchQuerySource: '${sourceString}' is not available`);
199
203
  if (source === "amsearch" && !node.info.sourceManagers.includes("applemusic")) {
200
204
  throw new Error("Lavalink Node has not 'applemusic' enabled, which is required to have 'amsearch' work");
201
205
  }
@@ -320,8 +324,8 @@ async function getClosestTrack(data, player, utils) {
320
324
  return utils.buildTrack(data, data.requester);
321
325
  if (!utils.isUnresolvedTrack(data))
322
326
  throw new RangeError("Track is not an unresolved Track");
323
- if (!data?.info?.title)
324
- throw new SyntaxError("the track title is required for unresolved tracks");
327
+ if (!data?.info?.title && typeof data.encoded !== "string" && !data.info.uri)
328
+ throw new SyntaxError("the track uri / title / encoded Base64 string is required for unresolved tracks");
325
329
  if (!data.requester)
326
330
  throw new SyntaxError("The requester is required");
327
331
  // try to decode the track, if possible
@@ -87,6 +87,8 @@ export class LavalinkNode {
87
87
  this.calls++;
88
88
  if (options.method === "DELETE")
89
89
  return;
90
+ if (request.statusCode === 404)
91
+ throw new Error(`Node Request resulted into an error, request-URL: ${url} | headers: ${JSON.stringify(request.headers)}`);
90
92
  return parseAsText ? await request.body.text() : await request.body.json();
91
93
  }
92
94
  /**
@@ -2,8 +2,8 @@ import { EQBand, FilterData, FilterManager, LavalinkFilterData } from "./Filters
2
2
  import { LavalinkManager } from "./LavalinkManager";
3
3
  import { LavalinkNode } from "./Node";
4
4
  import { Queue } from "./Queue";
5
- import { Track } from "./Track";
6
- import { LavalinkPlayerVoiceOptions, SearchPlatform, SearchResult } from "./Utils";
5
+ import { Track, UnresolvedTrack } from "./Track";
6
+ import { LavalinkPlayerVoiceOptions, SearchPlatform, SearchResult, LavaSearchType, LavaSearchResponse, LavaSrcSearchPlatformBase } from "./Utils";
7
7
  type PlayerDestroyReasons = "QueueEmpty" | "NodeDestroy" | "NodeDeleted" | "LavalinkNoVoice" | "NodeReconnectFail" | "PlayerReconnectFail" | "Disconnected" | "ChannelDeleted";
8
8
  export type DestroyReasonsType = PlayerDestroyReasons | string;
9
9
  export declare const DestroyReasons: Record<PlayerDestroyReasons, PlayerDestroyReasons>;
@@ -43,7 +43,7 @@ export interface PlayerOptions {
43
43
  }
44
44
  export interface PlayOptions {
45
45
  /** Which Track to play | don't provide, if it should pick from the Queue */
46
- track?: Track;
46
+ track?: Track | UnresolvedTrack;
47
47
  /** Encoded Track to use, instead of the queue system... */
48
48
  encodedTrack?: string | null;
49
49
  /** Encoded Track to use&search, instead of the queue system (yt only)... */
@@ -138,6 +138,11 @@ 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<SearchResult | LavaSearchResponse>;
141
146
  /**
142
147
  *
143
148
  * @param query Query for your data
@@ -120,7 +120,9 @@ export class Player {
120
120
  clearTimeout(this.get("internal_queueempty"));
121
121
  this.set("internal_queueempty", undefined);
122
122
  }
123
- if (options?.track && this.LavalinkManager.utils.isTrack(options?.track)) {
123
+ if (options?.track && (this.LavalinkManager.utils.isTrack(options?.track) || this.LavalinkManager.utils.isUnresolvedTrack(options.track))) {
124
+ if (this.LavalinkManager.utils.isUnresolvedTrack(options.track))
125
+ await options.track.resolve(this);
124
126
  await this.queue.add(options?.track, 0);
125
127
  await queueTrackEnd(this);
126
128
  }
@@ -128,13 +130,17 @@ export class Player {
128
130
  await queueTrackEnd(this);
129
131
  // @ts-ignore
130
132
  if (this.queue.current && this.LavalinkManager.utils.isUnresolvedTrack(this.queue.current)) {
131
- try {
132
- this.queue.current = await this.LavalinkManager.utils.getClosestTrack({ ...(this.queue.current || {}) }, this);
133
+ try { // @ts-ignore
134
+ this.queue.current = await this.queue.current.resolve(this);
133
135
  }
134
136
  catch (error) {
135
137
  this.LavalinkManager.emit("trackError", this, this.queue.current, error);
138
+ if (options && "track" in options)
139
+ delete options.track;
140
+ if (options && "encodedTrack" in options)
141
+ delete options.encodedTrack;
136
142
  if (this.queue.tracks[0])
137
- return this.play();
143
+ return this.play(options);
138
144
  return;
139
145
  }
140
146
  }
@@ -199,6 +205,37 @@ export class Player {
199
205
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
200
206
  return;
201
207
  }
208
+ async lavaSearch(query, requestUser) {
209
+ // transform the query object
210
+ const Query = {
211
+ query: typeof query === "string" ? query : query.query,
212
+ types: query.types ? ["track", "playlist", "artist", "album", "text"].filter(v => query.types?.find(x => x.toLowerCase().startsWith(v))) : ["track", "playlist", "artist", "album", "text"],
213
+ 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
214
+ };
215
+ // if user does player.search("ytsearch:Hello")
216
+ const foundSource = [...Object.keys(DefaultSources)].find(source => Query.query.toLowerCase().startsWith(`${source}:`.toLowerCase()))?.trim?.()?.toLowerCase?.();
217
+ if (foundSource && DefaultSources[foundSource]) {
218
+ Query.source = 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
+ if (Query.source)
222
+ this.LavalinkManager.utils.validateSourceString(this.node, Query.source);
223
+ if (!["spsearch", "sprec", "amsearch", "dzsearch", "dzisrc", "ytmsearch", "ytsearch"].includes(Query.source))
224
+ throw new SyntaxError(`Query.source must be a source from LavaSrc: "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ytmsearch" | "ytsearch"`);
225
+ if (/^https?:\/\//.test(Query.query))
226
+ return await this.search({ query: Query.query, source: Query.source }, requestUser);
227
+ if (!this.node.info.plugins.find(v => v.name === "lavasearch-plugin"))
228
+ throw new RangeError(`there is no lavasearch-plugin available in the lavalink node: ${this.node.id}`);
229
+ const res = await this.node.request(`/loadsearch?query=${Query.source ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}${Query.types?.length ? `&types=${Query.types.join(",")}` : ""}`);
230
+ return {
231
+ tracks: res.tracks?.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) || [],
232
+ albums: res.albums?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
233
+ artists: res.artists?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
234
+ playlists: res.playlists?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
235
+ texts: res.texts?.map(v => ({ text: v.text, pluginInfo: v?.plugin || v.pluginInfo })) || [],
236
+ pluginInfo: res.pluginInfo || res?.plugin
237
+ };
238
+ }
202
239
  /**
203
240
  *
204
241
  * @param query Query for your data
@@ -208,14 +245,18 @@ export class Player {
208
245
  // transform the query object
209
246
  const Query = {
210
247
  query: typeof query === "string" ? query : query.query,
211
- source: DefaultSources[(typeof query === "string" ? undefined : query.source) ?? this.LavalinkManager.options.playerOptions.defaultSearchPlatform] ?? (typeof query === "string" ? undefined : query.source) ?? this.LavalinkManager.options.playerOptions.defaultSearchPlatform
248
+ 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
212
249
  };
213
250
  // if user does player.search("ytsearch:Hello")
214
- const foundSource = [...Object.keys(DefaultSources)].find(source => Query.query.startsWith(`${source}:`));
251
+ const foundSource = [...Object.keys(DefaultSources)].find(source => Query.query?.toLowerCase?.()?.startsWith(`${source}:`.toLowerCase()))?.trim?.()?.toLowerCase?.();
215
252
  if (foundSource && DefaultSources[foundSource]) {
216
253
  Query.source = DefaultSources[foundSource]; // set the source to ytsearch:
217
- Query.query = Query.query.replace(`${foundSource}:`, ""); // remove ytsearch: from the query
254
+ Query.query = Query.query.slice(`${foundSource}:`.length, Query.query.length); // remove ytsearch: from the query
218
255
  }
256
+ if (/^https?:\/\//.test(Query.query))
257
+ this.LavalinkManager.utils.validatedQueryString(this.node, Query.source);
258
+ else if (Query.source)
259
+ this.LavalinkManager.utils.validateSourceString(this.node, Query.source);
219
260
  // 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
220
261
  // request the data
221
262
  const res = await this.node.request(`/loadtracks?identifier=${!/^https?:\/\//.test(Query.query) ? `${Query.source}:${Query.source === "ftts" ? "//" : ""}` : ""}${encodeURIComponent(Query.query)}`);
@@ -17,7 +17,21 @@ export interface TrackInfo {
17
17
  }
18
18
  export interface PluginInfo {
19
19
  /** The Type provided by a plugin */
20
- type?: string;
20
+ type?: "album" | "playlist" | "artist" | "recommendations" | string;
21
+ /** The Identifier provided by a plugin */
22
+ albumName?: string;
23
+ /** The url of the album art */
24
+ albumArtUrl?: string;
25
+ /** The url of the artist */
26
+ artistUrl?: string;
27
+ /** The url of the artist artwork */
28
+ artistArtworkUrl?: string;
29
+ /** The url of the preview */
30
+ previewUrl?: string;
31
+ /** Whether the track is a preview */
32
+ isPreview?: boolean;
33
+ /** The total number of tracks in the playlist */
34
+ totalTracks?: number;
21
35
  /** The Identifier provided by a plugin */
22
36
  identifier?: string;
23
37
  /** The ArtworkUrl provided by a plugin */
@@ -7,13 +7,18 @@ export declare const TrackSymbol: unique symbol;
7
7
  export declare const UnresolvedTrackSymbol: unique symbol;
8
8
  export declare const QueueSymbol: unique symbol;
9
9
  export declare const NodeSymbol: unique symbol;
10
- export type LavalinkSearchPlatform = "ytsearch" | "ytmsearch" | "scsearch" | "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ymsearch" | "speak" | "tts" | "ftts";
10
+ export type LavaSrcSearchPlatformBase = "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ymsearch";
11
+ export type LavaSrcSearchPlatform = LavaSrcSearchPlatformBase | "ftts";
12
+ export type DuncteSearchPlatform = "speak" | "tts";
13
+ export type LavalinkSearchPlatform = "ytsearch" | "ytmsearch" | "scsearch" | LavaSrcSearchPlatform | DuncteSearchPlatform;
11
14
  export type ClientSearchPlatform = "youtube" | "yt" | "youtube music" | "youtubemusic" | "ytm" | "soundcloud" | "sc" | "am" | "apple music" | "applemusic" | "apple" | "sp" | "spsuggestion" | "spotify" | "dz" | "deezer" | "yandex" | "yandex music" | "yandexmusic";
12
15
  export type SearchPlatform = LavalinkSearchPlatform | ClientSearchPlatform;
13
16
  export type SourcesRegex = "YoutubeRegex" | "YoutubeMusicRegex" | "SoundCloudRegex" | "SoundCloudMobileRegex" | "DeezerTrackRegex" | "DeezerArtistRegex" | "DeezerEpisodeRegex" | "DeezerMixesRegex" | "DeezerPageLinkRegex" | "DeezerPlaylistRegex" | "DeezerAlbumRegex" | "AllDeezerRegex" | "AllDeezerRegexWithoutPageLink" | "SpotifySongRegex" | "SpotifyPlaylistRegex" | "SpotifyArtistRegex" | "SpotifyEpisodeRegex" | "SpotifyShowRegex" | "SpotifyAlbumRegex" | "AllSpotifyRegex" | "mp3Url" | "m3uUrl" | "m3u8Url" | "mp4Url" | "m4aUrl" | "wavUrl" | "aacpUrl" | "tiktok" | "mixcloud" | "musicYandex" | "radiohost" | "bandcamp" | "appleMusic" | "TwitchTv" | "vimeo";
14
17
  export interface PlaylistInfo {
15
18
  /** The playlist title. */
16
19
  title: string;
20
+ /** The playlist name (if provided instead of title) */
21
+ name: string;
17
22
  /** The Playlist Author */
18
23
  author?: string;
19
24
  /** The Playlist Thumbnail */
@@ -72,7 +77,8 @@ export declare class ManagerUitls {
72
77
  * @param requester
73
78
  */
74
79
  buildUnresolvedTrack(query: UnresolvedQuery | UnresolvedTrack, requester: unknown): UnresolvedTrack;
75
- validatedQuery(queryString: string, node: LavalinkNode): void;
80
+ validatedQueryString(node: LavalinkNode, queryString: string): void;
81
+ validateSourceString(node: LavalinkNode, sourceString: SearchPlatform): void;
76
82
  }
77
83
  /**
78
84
  * @internal
@@ -282,4 +288,27 @@ export interface NodeMessage extends NodeStats {
282
288
  guildId: string;
283
289
  }
284
290
  export declare function queueTrackEnd(player: Player): Promise<Track>;
291
+ export type LavaSearchType = "track" | "album" | "artist" | "playlist" | "text" | "tracks" | "albums" | "artists" | "playlists" | "texts";
292
+ export interface LavaSearchFilteredResponse {
293
+ info: PlaylistInfo;
294
+ pluginInfo: PluginInfo;
295
+ tracks: Track[];
296
+ }
297
+ export interface LavaSearchResponse {
298
+ /** An array of tracks, only present if track is in types */
299
+ tracks: Track[];
300
+ /** An array of albums, only present if album is in types */
301
+ albums: LavaSearchFilteredResponse[];
302
+ /** An array of artists, only present if artist is in types */
303
+ artists: LavaSearchFilteredResponse[];
304
+ /** An array of playlists, only present if playlist is in types */
305
+ playlists: LavaSearchFilteredResponse[];
306
+ /** An array of text results, only present if text is in types */
307
+ texts: {
308
+ text: string;
309
+ pluginInfo: PluginInfo;
310
+ }[];
311
+ /** Addition result data provided by plugins */
312
+ pluginInfo: PluginInfo;
313
+ }
285
314
  export {};
@@ -109,7 +109,7 @@ export class ManagerUitls {
109
109
  return false;
110
110
  if (data[UnresolvedTrackSymbol] === true)
111
111
  return true;
112
- return typeof data === "object" && "info" in data && typeof data.info.title === "string" && typeof data.resolve === "function";
112
+ return typeof data === "object" && (("info" in data && typeof data.info.title === "string") || typeof data.encoded === "string") && typeof data.resolve === "function";
113
113
  }
114
114
  /**
115
115
  * Checks if the provided argument is a valid UnresolvedTrack.
@@ -131,7 +131,7 @@ export class ManagerUitls {
131
131
  throw new RangeError('Argument "query" must be present.');
132
132
  const unresolvedTrack = {
133
133
  encoded: query.encoded || undefined,
134
- info: query.info ?? query,
134
+ info: query.info ? query.info : query.title ? query : undefined,
135
135
  requester: typeof this.manager.options?.playerOptions?.requesterTransformer === "function" ? this.manager.options?.playerOptions?.requesterTransformer((query?.requester || requester)) : requester,
136
136
  async resolve(player) {
137
137
  const closest = await getClosestTrack(this, player, player.LavalinkManager.utils);
@@ -142,10 +142,12 @@ export class ManagerUitls {
142
142
  return;
143
143
  }
144
144
  };
145
+ if (!this.isUnresolvedTrack(unresolvedTrack))
146
+ throw SyntaxError("Could not build Unresolved Track");
145
147
  Object.defineProperty(unresolvedTrack, UnresolvedTrackSymbol, { configurable: true, value: true });
146
148
  return unresolvedTrack;
147
149
  }
148
- validatedQuery(queryString, node) {
150
+ validatedQueryString(node, queryString) {
149
151
  if (!node.info)
150
152
  throw new Error("No Lavalink Node was provided");
151
153
  if (!node.info.sourceManagers?.length)
@@ -187,12 +189,14 @@ export class ManagerUitls {
187
189
  if (SourceLinksRegexes.musicYandex.test(queryString) && !node.info.sourceManagers.includes("yandexmusic")) {
188
190
  throw new Error("Lavalink Node has not 'yandexmusic' enabled");
189
191
  }
190
- const hasSource = queryString.split(":")[0];
191
- if (queryString.split(" ").length <= 1 || !queryString.split(" ")[0].includes(":"))
192
- return;
193
- const source = DefaultSources[hasSource];
192
+ return;
193
+ }
194
+ validateSourceString(node, sourceString) {
195
+ if (!sourceString)
196
+ throw new Error(`No SourceString was provided`);
197
+ const source = DefaultSources[sourceString.toLowerCase()] || Object.values(DefaultSources).find(v => v.toLowerCase() === sourceString?.toLowerCase());
194
198
  if (!source)
195
- throw new Error(`Lavalink Node SearchQuerySource: '${hasSource}' is not available`);
199
+ throw new Error(`Lavalink Node SearchQuerySource: '${sourceString}' is not available`);
196
200
  if (source === "amsearch" && !node.info.sourceManagers.includes("applemusic")) {
197
201
  throw new Error("Lavalink Node has not 'applemusic' enabled, which is required to have 'amsearch' work");
198
202
  }
@@ -314,8 +318,8 @@ async function getClosestTrack(data, player, utils) {
314
318
  return utils.buildTrack(data, data.requester);
315
319
  if (!utils.isUnresolvedTrack(data))
316
320
  throw new RangeError("Track is not an unresolved Track");
317
- if (!data?.info?.title)
318
- throw new SyntaxError("the track title is required for unresolved tracks");
321
+ if (!data?.info?.title && typeof data.encoded !== "string" && !data.info.uri)
322
+ throw new SyntaxError("the track uri / title / encoded Base64 string is required for unresolved tracks");
319
323
  if (!data.requester)
320
324
  throw new SyntaxError("The requester is required");
321
325
  // try to decode the track, if possible
@@ -2,8 +2,8 @@ import { EQBand, FilterData, FilterManager, LavalinkFilterData } from "./Filters
2
2
  import { LavalinkManager } from "./LavalinkManager";
3
3
  import { LavalinkNode } from "./Node";
4
4
  import { Queue } from "./Queue";
5
- import { Track } from "./Track";
6
- import { LavalinkPlayerVoiceOptions, SearchPlatform, SearchResult } from "./Utils";
5
+ import { Track, UnresolvedTrack } from "./Track";
6
+ import { LavalinkPlayerVoiceOptions, SearchPlatform, SearchResult, LavaSearchType, LavaSearchResponse, LavaSrcSearchPlatformBase } from "./Utils";
7
7
  type PlayerDestroyReasons = "QueueEmpty" | "NodeDestroy" | "NodeDeleted" | "LavalinkNoVoice" | "NodeReconnectFail" | "PlayerReconnectFail" | "Disconnected" | "ChannelDeleted";
8
8
  export type DestroyReasonsType = PlayerDestroyReasons | string;
9
9
  export declare const DestroyReasons: Record<PlayerDestroyReasons, PlayerDestroyReasons>;
@@ -43,7 +43,7 @@ export interface PlayerOptions {
43
43
  }
44
44
  export interface PlayOptions {
45
45
  /** Which Track to play | don't provide, if it should pick from the Queue */
46
- track?: Track;
46
+ track?: Track | UnresolvedTrack;
47
47
  /** Encoded Track to use, instead of the queue system... */
48
48
  encodedTrack?: string | null;
49
49
  /** Encoded Track to use&search, instead of the queue system (yt only)... */
@@ -138,6 +138,11 @@ 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<SearchResult | LavaSearchResponse>;
141
146
  /**
142
147
  *
143
148
  * @param query Query for your data
@@ -17,7 +17,21 @@ export interface TrackInfo {
17
17
  }
18
18
  export interface PluginInfo {
19
19
  /** The Type provided by a plugin */
20
- type?: string;
20
+ type?: "album" | "playlist" | "artist" | "recommendations" | string;
21
+ /** The Identifier provided by a plugin */
22
+ albumName?: string;
23
+ /** The url of the album art */
24
+ albumArtUrl?: string;
25
+ /** The url of the artist */
26
+ artistUrl?: string;
27
+ /** The url of the artist artwork */
28
+ artistArtworkUrl?: string;
29
+ /** The url of the preview */
30
+ previewUrl?: string;
31
+ /** Whether the track is a preview */
32
+ isPreview?: boolean;
33
+ /** The total number of tracks in the playlist */
34
+ totalTracks?: number;
21
35
  /** The Identifier provided by a plugin */
22
36
  identifier?: string;
23
37
  /** The ArtworkUrl provided by a plugin */
@@ -7,13 +7,18 @@ export declare const TrackSymbol: unique symbol;
7
7
  export declare const UnresolvedTrackSymbol: unique symbol;
8
8
  export declare const QueueSymbol: unique symbol;
9
9
  export declare const NodeSymbol: unique symbol;
10
- export type LavalinkSearchPlatform = "ytsearch" | "ytmsearch" | "scsearch" | "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ymsearch" | "speak" | "tts" | "ftts";
10
+ export type LavaSrcSearchPlatformBase = "spsearch" | "sprec" | "amsearch" | "dzsearch" | "dzisrc" | "ymsearch";
11
+ export type LavaSrcSearchPlatform = LavaSrcSearchPlatformBase | "ftts";
12
+ export type DuncteSearchPlatform = "speak" | "tts";
13
+ export type LavalinkSearchPlatform = "ytsearch" | "ytmsearch" | "scsearch" | LavaSrcSearchPlatform | DuncteSearchPlatform;
11
14
  export type ClientSearchPlatform = "youtube" | "yt" | "youtube music" | "youtubemusic" | "ytm" | "soundcloud" | "sc" | "am" | "apple music" | "applemusic" | "apple" | "sp" | "spsuggestion" | "spotify" | "dz" | "deezer" | "yandex" | "yandex music" | "yandexmusic";
12
15
  export type SearchPlatform = LavalinkSearchPlatform | ClientSearchPlatform;
13
16
  export type SourcesRegex = "YoutubeRegex" | "YoutubeMusicRegex" | "SoundCloudRegex" | "SoundCloudMobileRegex" | "DeezerTrackRegex" | "DeezerArtistRegex" | "DeezerEpisodeRegex" | "DeezerMixesRegex" | "DeezerPageLinkRegex" | "DeezerPlaylistRegex" | "DeezerAlbumRegex" | "AllDeezerRegex" | "AllDeezerRegexWithoutPageLink" | "SpotifySongRegex" | "SpotifyPlaylistRegex" | "SpotifyArtistRegex" | "SpotifyEpisodeRegex" | "SpotifyShowRegex" | "SpotifyAlbumRegex" | "AllSpotifyRegex" | "mp3Url" | "m3uUrl" | "m3u8Url" | "mp4Url" | "m4aUrl" | "wavUrl" | "aacpUrl" | "tiktok" | "mixcloud" | "musicYandex" | "radiohost" | "bandcamp" | "appleMusic" | "TwitchTv" | "vimeo";
14
17
  export interface PlaylistInfo {
15
18
  /** The playlist title. */
16
19
  title: string;
20
+ /** The playlist name (if provided instead of title) */
21
+ name: string;
17
22
  /** The Playlist Author */
18
23
  author?: string;
19
24
  /** The Playlist Thumbnail */
@@ -72,7 +77,8 @@ export declare class ManagerUitls {
72
77
  * @param requester
73
78
  */
74
79
  buildUnresolvedTrack(query: UnresolvedQuery | UnresolvedTrack, requester: unknown): UnresolvedTrack;
75
- validatedQuery(queryString: string, node: LavalinkNode): void;
80
+ validatedQueryString(node: LavalinkNode, queryString: string): void;
81
+ validateSourceString(node: LavalinkNode, sourceString: SearchPlatform): void;
76
82
  }
77
83
  /**
78
84
  * @internal
@@ -282,4 +288,27 @@ export interface NodeMessage extends NodeStats {
282
288
  guildId: string;
283
289
  }
284
290
  export declare function queueTrackEnd(player: Player): Promise<Track>;
291
+ export type LavaSearchType = "track" | "album" | "artist" | "playlist" | "text" | "tracks" | "albums" | "artists" | "playlists" | "texts";
292
+ export interface LavaSearchFilteredResponse {
293
+ info: PlaylistInfo;
294
+ pluginInfo: PluginInfo;
295
+ tracks: Track[];
296
+ }
297
+ export interface LavaSearchResponse {
298
+ /** An array of tracks, only present if track is in types */
299
+ tracks: Track[];
300
+ /** An array of albums, only present if album is in types */
301
+ albums: LavaSearchFilteredResponse[];
302
+ /** An array of artists, only present if artist is in types */
303
+ artists: LavaSearchFilteredResponse[];
304
+ /** An array of playlists, only present if playlist is in types */
305
+ playlists: LavaSearchFilteredResponse[];
306
+ /** An array of text results, only present if text is in types */
307
+ texts: {
308
+ text: string;
309
+ pluginInfo: PluginInfo;
310
+ }[];
311
+ /** Addition result data provided by plugins */
312
+ pluginInfo: PluginInfo;
313
+ }
285
314
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavalink-client",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Easy and advanced lavalink client. Use it with lavalink plugins as well as latest lavalink versions",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",