lavalink-client 2.3.1 → 2.3.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.
@@ -1,3 +1,4 @@
1
+ import type { DebugEvents } from "../Constants";
1
2
  import type { Player } from "../Player";
2
3
  import type { LavalinkNodeOptions } from "./Node";
3
4
  import type { DestroyReasonsType, PlayerJson } from "./Player";
@@ -100,6 +101,19 @@ export interface LavalinkManagerEvents {
100
101
  * @event Manager#trackError
101
102
  */
102
103
  "ChaptersLoaded": (player: Player, track: Track | UnresolvedTrack | null, payload: SponsorBlockChaptersLoaded) => void;
104
+ /**
105
+ * Lavalink-Client Debug Event
106
+ * Emitted for several erros, and logs within lavalink-client, if managerOptions.advancedOptions.enableDebugEvents is true
107
+ * Useful for debugging the lavalink-client
108
+ *
109
+ * @event Manager#debug
110
+ */
111
+ "debug": (eventKey: DebugEvents, eventData: {
112
+ message: string;
113
+ state: "log" | "warn" | "error";
114
+ error?: Error | string;
115
+ functionLayer: string;
116
+ }) => void;
103
117
  }
104
118
  /**
105
119
  * The Bot client Options needed for the manager
@@ -131,6 +145,15 @@ export interface ManagerPlayerOptions {
131
145
  /** Instantly destroy player (overrides autoReconnect) | Don't provide == disable feature*/
132
146
  destroyPlayer?: boolean;
133
147
  };
148
+ /** Minimum time to play the song before autoPlayFunction is executed (prevents error spamming) Set to 0 to disable it @default 10000 */
149
+ minAutoPlayMs?: number;
150
+ /** Allows you to declare how many tracks are allowed to error/stuck within a time-frame before player is destroyed @default "{threshold: 35000, maxAmount: 3 }" */
151
+ maxErrorsPerTime?: {
152
+ /** The threshold time to count errors (recommended is 35s) */
153
+ threshold: number;
154
+ /** The max amount of errors within the threshold time which are allowed before destroying the player (when errors > maxAmount -> player.destroy()) */
155
+ maxAmount: number;
156
+ };
134
157
  onEmptyQueue?: {
135
158
  /** Get's executed onEmptyQueue -> You can do any track queue previous transformations, if you add a track to the queue -> it will play it, if not queueEnd will execute! */
136
159
  autoPlayFunction?: (player: Player, lastPlayedTrack: Track) => Promise<void>;
@@ -166,6 +189,8 @@ export interface ManagerOptions {
166
189
  advancedOptions?: {
167
190
  /** Max duration for that the filter fix duration works (in ms) - default is 8mins */
168
191
  maxFilterFixDuration?: number;
192
+ /** Enable Debug event */
193
+ enableDebugEvents?: boolean;
169
194
  /** optional */
170
195
  debugOptions?: {
171
196
  /** For logging custom searches */
@@ -28,7 +28,7 @@ export interface QueueChangesWatcher {
28
28
  /** get a Value (MUST RETURN UNPARSED!) */
29
29
  tracksAdd: (guildId: string, tracks: (Track | UnresolvedTrack)[], position: number, oldStoredQueue: StoredQueue, newStoredQueue: StoredQueue) => void;
30
30
  /** Set a value inside a guildId (MUST BE UNPARSED) */
31
- tracksRemoved: (guildId: string, tracks: (Track | UnresolvedTrack)[], position: number, oldStoredQueue: StoredQueue, newStoredQueue: StoredQueue) => void;
31
+ tracksRemoved: (guildId: string, tracks: (Track | UnresolvedTrack)[], position: number | number[], oldStoredQueue: StoredQueue, newStoredQueue: StoredQueue) => void;
32
32
  /** Set a value inside a guildId (MUST BE UNPARSED) */
33
33
  shuffled: (guildId: string, oldStoredQueue: StoredQueue, newStoredQueue: StoredQueue) => void;
34
34
  }
@@ -1,5 +1,6 @@
1
1
  import { URL } from "node:url";
2
2
  import { isRegExp } from "node:util/types";
3
+ import { DebugEvents } from "./Constants";
3
4
  import { DefaultSources, LavalinkPlugins, SourceLinksRegexes } from "./LavalinkManagerStatics";
4
5
  export const TrackSymbol = Symbol("LC-Track");
5
6
  export const UnresolvedTrackSymbol = Symbol("LC-Track-Unresolved");
@@ -71,6 +72,14 @@ export class ManagerUtils {
71
72
  return r;
72
73
  }
73
74
  catch (error) {
75
+ if (this.LavalinkManager?.options?.advancedOptions?.enableDebugEvents) {
76
+ this.LavalinkManager?.emit("debug", DebugEvents.BuildTrackError, {
77
+ error: error,
78
+ functionLayer: "ManagerUtils > buildTrack()",
79
+ message: "Error while building track",
80
+ state: "error",
81
+ });
82
+ }
74
83
  throw new RangeError(`Argument "data" is not a valid track: ${error.message}`);
75
84
  }
76
85
  }
@@ -129,7 +138,14 @@ export class ManagerUtils {
129
138
  : requester;
130
139
  }
131
140
  catch (e) {
132
- console.error("errored while transforming requester:", e);
141
+ if (this.LavalinkManager?.options?.advancedOptions?.enableDebugEvents) {
142
+ this.LavalinkManager?.emit("debug", DebugEvents.TransformRequesterFunctionFailed, {
143
+ error: e,
144
+ functionLayer: "ManagerUtils > getTransformedRequester()",
145
+ message: "Your custom transformRequesterFunction failed to execute, please check your function for errors.",
146
+ state: "error",
147
+ });
148
+ }
133
149
  return requester;
134
150
  }
135
151
  }
@@ -195,62 +211,96 @@ export class ManagerUtils {
195
211
  return typeof data === "object" && !("info" in data) && typeof data.title === "string";
196
212
  }
197
213
  async getClosestTrack(data, player) {
198
- return getClosestTrack(data, player);
214
+ try {
215
+ return getClosestTrack(data, player);
216
+ }
217
+ catch (e) {
218
+ if (this.LavalinkManager?.options?.advancedOptions?.enableDebugEvents) {
219
+ this.LavalinkManager?.emit("debug", DebugEvents.GetClosestTrackFailed, {
220
+ error: e,
221
+ functionLayer: "ManagerUtils > getClosestTrack()",
222
+ message: "Failed to resolve track because the getClosestTrack function failed.",
223
+ state: "error",
224
+ });
225
+ }
226
+ throw e;
227
+ }
199
228
  }
200
229
  validateQueryString(node, queryString, sourceString) {
201
230
  if (!node.info)
202
231
  throw new Error("No Lavalink Node was provided");
203
232
  if (!node.info.sourceManagers?.length)
204
233
  throw new Error("Lavalink Node, has no sourceManagers enabled");
234
+ if (!queryString.trim().length)
235
+ throw new Error(`Query string is empty, please provide a valid query string.`);
205
236
  if (sourceString === "speak" && queryString.length > 100)
206
- // checks for blacklisted links / domains / queries
207
- if (this.LavalinkManager.options?.linksBlacklist?.length > 0 && this.LavalinkManager.options?.linksBlacklist.some(v => (typeof v === "string" && (queryString.toLowerCase().includes(v.toLowerCase()) || v.toLowerCase().includes(queryString.toLowerCase()))) || isRegExp(v) && v.test(queryString))) {
237
+ throw new Error(`Query is speak, which is limited to 100 characters.`);
238
+ // checks for blacklisted links / domains / queries
239
+ if (this.LavalinkManager.options?.linksBlacklist?.length > 0) {
240
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
241
+ this.LavalinkManager.emit("debug", DebugEvents.ValidatingBlacklistLinks, {
242
+ state: "log",
243
+ message: `Validating Query against LavalinkManager.options.linksBlacklist, query: "${queryString}"`,
244
+ functionLayer: "(LavalinkNode > node | player) > search() > validateQueryString()",
245
+ });
246
+ }
247
+ if (this.LavalinkManager.options?.linksBlacklist.some(v => (typeof v === "string" && (queryString.toLowerCase().includes(v.toLowerCase()) || v.toLowerCase().includes(queryString.toLowerCase()))) || isRegExp(v) && v.test(queryString))) {
208
248
  throw new Error(`Query string contains a link / word which is blacklisted.`);
209
249
  }
250
+ }
210
251
  if (!/^https?:\/\//.test(queryString))
211
252
  return;
212
253
  else if (this.LavalinkManager.options?.linksAllowed === false)
213
254
  throw new Error("Using links to make a request is not allowed.");
214
255
  // checks for if the query is whitelisted (should only work for links, so it skips the check for no link queries)
215
- if (this.LavalinkManager.options?.linksWhitelist?.length > 0 && !this.LavalinkManager.options?.linksWhitelist.some(v => (typeof v === "string" && (queryString.toLowerCase().includes(v.toLowerCase()) || v.toLowerCase().includes(queryString.toLowerCase()))) || isRegExp(v) && v.test(queryString))) {
216
- throw new Error(`Query string contains a link / word which isn't whitelisted.`);
256
+ if (this.LavalinkManager.options?.linksWhitelist?.length > 0) {
257
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
258
+ this.LavalinkManager.emit("debug", DebugEvents.ValidatingWhitelistLinks, {
259
+ state: "log",
260
+ message: `Link was provided to the Query, validating against LavalinkManager.options.linksWhitelist, query: "${queryString}"`,
261
+ functionLayer: "(LavalinkNode > node | player) > search() > validateQueryString()",
262
+ });
263
+ }
264
+ if (!this.LavalinkManager.options?.linksWhitelist.some(v => (typeof v === "string" && (queryString.toLowerCase().includes(v.toLowerCase()) || v.toLowerCase().includes(queryString.toLowerCase()))) || isRegExp(v) && v.test(queryString))) {
265
+ throw new Error(`Query string contains a link / word which isn't whitelisted.`);
266
+ }
217
267
  }
218
268
  // missing links: beam.pro local getyarn.io clypit pornhub reddit ocreamix soundgasm
219
269
  if ((SourceLinksRegexes.YoutubeMusicRegex.test(queryString) || SourceLinksRegexes.YoutubeRegex.test(queryString)) && !node.info?.sourceManagers?.includes("youtube")) {
220
- throw new Error("Lavalink Node has not 'youtube' enabled");
270
+ throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'youtube' enabled");
221
271
  }
222
272
  if ((SourceLinksRegexes.SoundCloudMobileRegex.test(queryString) || SourceLinksRegexes.SoundCloudRegex.test(queryString)) && !node.info?.sourceManagers?.includes("soundcloud")) {
223
- throw new Error("Lavalink Node has not 'soundcloud' enabled");
273
+ throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'soundcloud' enabled");
224
274
  }
225
275
  if (SourceLinksRegexes.bandcamp.test(queryString) && !node.info?.sourceManagers?.includes("bandcamp")) {
226
- throw new Error("Lavalink Node has not 'bandcamp' enabled (introduced with lavaplayer 2.2.0 or lavalink 4.0.6)");
276
+ throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'bandcamp' enabled (introduced with lavaplayer 2.2.0 or lavalink 4.0.6)");
227
277
  }
228
278
  if (SourceLinksRegexes.TwitchTv.test(queryString) && !node.info?.sourceManagers?.includes("twitch")) {
229
- throw new Error("Lavalink Node has not 'twitch' enabled");
279
+ throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'twitch' enabled");
230
280
  }
231
281
  if (SourceLinksRegexes.vimeo.test(queryString) && !node.info?.sourceManagers?.includes("vimeo")) {
232
- throw new Error("Lavalink Node has not 'vimeo' enabled");
282
+ throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'vimeo' enabled");
233
283
  }
234
284
  if (SourceLinksRegexes.tiktok.test(queryString) && !node.info?.sourceManagers?.includes("tiktok")) {
235
- throw new Error("Lavalink Node has not 'tiktok' enabled");
285
+ throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'tiktok' enabled");
236
286
  }
237
287
  if (SourceLinksRegexes.mixcloud.test(queryString) && !node.info?.sourceManagers?.includes("mixcloud")) {
238
- throw new Error("Lavalink Node has not 'mixcloud' enabled");
288
+ throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'mixcloud' enabled");
239
289
  }
240
290
  if (SourceLinksRegexes.AllSpotifyRegex.test(queryString) && !node.info?.sourceManagers?.includes("spotify")) {
241
- throw new Error("Lavalink Node has not 'spotify' enabled");
291
+ throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'spotify' enabled");
242
292
  }
243
293
  if (SourceLinksRegexes.appleMusic.test(queryString) && !node.info?.sourceManagers?.includes("applemusic")) {
244
- throw new Error("Lavalink Node has not 'applemusic' enabled");
294
+ throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'applemusic' enabled");
245
295
  }
246
296
  if (SourceLinksRegexes.AllDeezerRegex.test(queryString) && !node.info?.sourceManagers?.includes("deezer")) {
247
- throw new Error("Lavalink Node has not 'deezer' enabled");
297
+ throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'deezer' enabled");
248
298
  }
249
299
  if (SourceLinksRegexes.AllDeezerRegex.test(queryString) && node.info?.sourceManagers?.includes("deezer") && !node.info?.sourceManagers?.includes("http")) {
250
- throw new Error("Lavalink Node has not 'http' enabled, which is required to have 'deezer' to work");
300
+ throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'http' enabled, which is required to have 'deezer' to work");
251
301
  }
252
302
  if (SourceLinksRegexes.musicYandex.test(queryString) && !node.info?.sourceManagers?.includes("yandexmusic")) {
253
- throw new Error("Lavalink Node has not 'yandexmusic' enabled");
303
+ throw new Error("Query / Link Provided for this Source but Lavalink Node has not 'yandexmusic' enabled");
254
304
  }
255
305
  return;
256
306
  }
@@ -1,4 +1,45 @@
1
1
  import type { AudioOutputs, ChannelMixFilter, EQBand } from "./Types/Filters";
2
+ export declare enum DebugEvents {
3
+ SetSponsorBlock = "SetSponsorBlock",
4
+ DeleteSponsorBlock = "DeleteSponsorBlock",
5
+ TrackEndReplaced = "TrackEndReplaced",
6
+ AutoplayNoSongsAdded = "AutoplayNoSongsAdded",
7
+ AutoplayThresholdSpamLimiter = "AutoplayThresholdSpamLimiter",
8
+ TriggerQueueEmptyInterval = "TriggerQueueEmptyInterval",
9
+ QueueEnded = "QueueEnded",
10
+ TrackStartNewSongsOnly = "TrackStartNewSongsOnly",
11
+ TrackStartNoTrack = "TrackStartNoTrack",
12
+ ResumingFetchingError = "ResumingFetchingError",
13
+ PlayerUpdateNoPlayer = "PlayerUpdateNoPlayer",
14
+ PlayerUpdateFilterFixApply = "PlayerUpdateFilterFixApply",
15
+ PlayerUpdateSuccess = "PlayerUpdateSuccess",
16
+ HeartBeatTriggered = "HeartBeatTriggered",
17
+ NoSocketOnDestroy = "NoSocketOnDestroy",
18
+ SocketTerminateHeartBeatTimeout = "SocketTerminateHeartBeatTimeout",
19
+ TryingConnectWhileConnected = "TryingConnectWhileConnected",
20
+ LavaSearchNothingFound = "LavaSearchNothingFound",
21
+ SearchNothingFound = "SearchNothingFound",
22
+ ValidatingBlacklistLinks = "ValidatingBlacklistLinks",
23
+ ValidatingWhitelistLinks = "ValidatingWhitelistLinks",
24
+ TrackErrorMaxTracksErroredPerTime = "TrackErrorMaxTracksErroredPerTime",
25
+ TrackStuckMaxTracksErroredPerTime = "TrackStuckMaxTracksErroredPerTime",
26
+ PlayerDestroyingSomewhereElse = "PlayerDestroyingSomewhereElse",
27
+ PlayerCreateNodeNotFound = "PlayerCreateNodeNotFound",
28
+ PlayerPlayQueueEmptyTimeoutClear = "PlayerPlayQueueEmptyTimeoutClear",
29
+ PlayerPlayWithTrackReplace = "PlayerPlayWithTrackReplace",
30
+ PlayerPlayUnresolvedTrack = "PlayerPlayUnresolvedTrack",
31
+ PlayerPlayUnresolvedTrackFailed = "PlayerPlayUnresolvedTrackFailed",
32
+ PlayerVolumeAsFilter = "PlayerVolumeAsFilter",
33
+ BandcampSearchLokalEngine = "BandcampSearchLokalEngine",
34
+ PlayerChangeNode = "PlayerChangeNode",
35
+ BuildTrackError = "BuildTrackError",
36
+ TransformRequesterFunctionFailed = "TransformRequesterFunctionFailed",
37
+ GetClosestTrackFailed = "GetClosestTrackFailed",
38
+ PlayerDeleteInsteadOfDestroy = "PlayerDeleteInsteadOfDestroy",
39
+ FailedToConnectToNodes = "FailedToConnectToNodes",
40
+ NoAudioDebug = "NoAudioDebug",
41
+ PlayerAutoReconnect = "PlayerAutoReconnect"
42
+ }
2
43
  export declare enum DestroyReasons {
3
44
  QueueEmpty = "QueueEmpty",
4
45
  NodeDestroy = "NodeDestroy",
@@ -9,7 +50,9 @@ export declare enum DestroyReasons {
9
50
  PlayerReconnectFail = "PlayerReconnectFail",
10
51
  ChannelDeleted = "ChannelDeleted",
11
52
  DisconnectAllNodes = "DisconnectAllNodes",
12
- ReconnectAllNodes = "ReconnectAllNodes"
53
+ ReconnectAllNodes = "ReconnectAllNodes",
54
+ TrackErrorMaxTracksErroredPerTime = "TrackErrorMaxTracksErroredPerTime",
55
+ TrackStuckMaxTracksErroredPerTime = "TrackStuckMaxTracksErroredPerTime"
13
56
  }
14
57
  export declare const validSponsorBlocks: string[];
15
58
  /** The audio Outputs Data map declaration */
@@ -134,7 +134,6 @@ export declare class Player {
134
134
  * @param repeatMode
135
135
  */
136
136
  setRepeatMode(repeatMode: RepeatMode): Promise<this>;
137
- 1: any;
138
137
  /**
139
138
  * Skip the current song, or a specific amount of songs
140
139
  * @param amount provide the index of the next track to skip to
@@ -1,3 +1,4 @@
1
+ import type { DebugEvents } from "../Constants";
1
2
  import type { Player } from "../Player";
2
3
  import type { LavalinkNodeOptions } from "./Node";
3
4
  import type { DestroyReasonsType, PlayerJson } from "./Player";
@@ -100,6 +101,19 @@ export interface LavalinkManagerEvents {
100
101
  * @event Manager#trackError
101
102
  */
102
103
  "ChaptersLoaded": (player: Player, track: Track | UnresolvedTrack | null, payload: SponsorBlockChaptersLoaded) => void;
104
+ /**
105
+ * Lavalink-Client Debug Event
106
+ * Emitted for several erros, and logs within lavalink-client, if managerOptions.advancedOptions.enableDebugEvents is true
107
+ * Useful for debugging the lavalink-client
108
+ *
109
+ * @event Manager#debug
110
+ */
111
+ "debug": (eventKey: DebugEvents, eventData: {
112
+ message: string;
113
+ state: "log" | "warn" | "error";
114
+ error?: Error | string;
115
+ functionLayer: string;
116
+ }) => void;
103
117
  }
104
118
  /**
105
119
  * The Bot client Options needed for the manager
@@ -131,6 +145,15 @@ export interface ManagerPlayerOptions {
131
145
  /** Instantly destroy player (overrides autoReconnect) | Don't provide == disable feature*/
132
146
  destroyPlayer?: boolean;
133
147
  };
148
+ /** Minimum time to play the song before autoPlayFunction is executed (prevents error spamming) Set to 0 to disable it @default 10000 */
149
+ minAutoPlayMs?: number;
150
+ /** Allows you to declare how many tracks are allowed to error/stuck within a time-frame before player is destroyed @default "{threshold: 35000, maxAmount: 3 }" */
151
+ maxErrorsPerTime?: {
152
+ /** The threshold time to count errors (recommended is 35s) */
153
+ threshold: number;
154
+ /** The max amount of errors within the threshold time which are allowed before destroying the player (when errors > maxAmount -> player.destroy()) */
155
+ maxAmount: number;
156
+ };
134
157
  onEmptyQueue?: {
135
158
  /** Get's executed onEmptyQueue -> You can do any track queue previous transformations, if you add a track to the queue -> it will play it, if not queueEnd will execute! */
136
159
  autoPlayFunction?: (player: Player, lastPlayedTrack: Track) => Promise<void>;
@@ -166,6 +189,8 @@ export interface ManagerOptions {
166
189
  advancedOptions?: {
167
190
  /** Max duration for that the filter fix duration works (in ms) - default is 8mins */
168
191
  maxFilterFixDuration?: number;
192
+ /** Enable Debug event */
193
+ enableDebugEvents?: boolean;
169
194
  /** optional */
170
195
  debugOptions?: {
171
196
  /** For logging custom searches */
@@ -28,7 +28,7 @@ export interface QueueChangesWatcher {
28
28
  /** get a Value (MUST RETURN UNPARSED!) */
29
29
  tracksAdd: (guildId: string, tracks: (Track | UnresolvedTrack)[], position: number, oldStoredQueue: StoredQueue, newStoredQueue: StoredQueue) => void;
30
30
  /** Set a value inside a guildId (MUST BE UNPARSED) */
31
- tracksRemoved: (guildId: string, tracks: (Track | UnresolvedTrack)[], position: number, oldStoredQueue: StoredQueue, newStoredQueue: StoredQueue) => void;
31
+ tracksRemoved: (guildId: string, tracks: (Track | UnresolvedTrack)[], position: number | number[], oldStoredQueue: StoredQueue, newStoredQueue: StoredQueue) => void;
32
32
  /** Set a value inside a guildId (MUST BE UNPARSED) */
33
33
  shuffled: (guildId: string, oldStoredQueue: StoredQueue, newStoredQueue: StoredQueue) => void;
34
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavalink-client",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
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",