lavalink-client 2.11.0 → 2.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -414,6 +414,8 @@ This client powers various Discord bots:
414
414
  - [BeatDock](https://github.com/lazaroagomez/BeatDock) (@lazaroagomez)
415
415
  - [Nazha](https://top.gg/bot/1124681788070055967) (@Nazha-Team)
416
416
  - [Arii Music](https://arimusic.me/) (@friston_ae)
417
+ - [Listen](https://listen.ac) (@gabcaua)
418
+ - [NOTHING](https://discord.com/discovery/applications/1234592539324059709) (@ankush.ly)
417
419
 
418
420
  ---
419
421
 
package/dist/index.cjs CHANGED
@@ -1247,8 +1247,8 @@ var ManagerUtils = class {
1247
1247
  * @returns
1248
1248
  */
1249
1249
  isTrack(data) {
1250
- if (!data) return false;
1251
- if (data[TrackSymbol] === true) return true;
1250
+ if (!data || typeof data !== "object") return false;
1251
+ if (Reflect.get(data, TrackSymbol) === true) return true;
1252
1252
  return typeof data?.encoded === "string" && typeof data?.info === "object" && !("resolve" in data);
1253
1253
  }
1254
1254
  /**
@@ -1257,7 +1257,7 @@ var ManagerUtils = class {
1257
1257
  */
1258
1258
  isUnresolvedTrack(data) {
1259
1259
  if (!data) return false;
1260
- if (data[UnresolvedTrackSymbol] === true) return true;
1260
+ if (Reflect.get(data, UnresolvedTrackSymbol) === true) return true;
1261
1261
  return typeof data === "object" && ("info" in data && typeof data.info.title === "string" || typeof data.encoded === "string") && "resolve" in data && typeof data.resolve === "function";
1262
1262
  }
1263
1263
  /**
@@ -1298,7 +1298,7 @@ var ManagerUtils = class {
1298
1298
  if (node._checkForSources && !node.info.sourceManagers?.length) throw new Error("Lavalink Node, has no sourceManagers enabled");
1299
1299
  if (!queryString.trim().length) throw new Error(`Query string is empty, please provide a valid query string.`);
1300
1300
  if (sourceString === "speak" && queryString.length > 100) throw new Error(`Query is speak, which is limited to 100 characters.`);
1301
- if (this.LavalinkManager.options?.linksBlacklist?.length > 0) {
1301
+ if (this.LavalinkManager?.options?.linksBlacklist && this.LavalinkManager.options?.linksBlacklist.length > 0) {
1302
1302
  if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) this.LavalinkManager.emit("debug", "ValidatingBlacklistLinks", {
1303
1303
  state: "log",
1304
1304
  message: `Validating Query against LavalinkManager.options.linksBlacklist, query: "${queryString}"`,
@@ -1307,9 +1307,9 @@ var ManagerUtils = class {
1307
1307
  if (this.LavalinkManager.options?.linksBlacklist.some((v) => typeof v === "string" && queryString.toLowerCase().includes(v.toLowerCase()) || (0, node_util_types.isRegExp)(v) && v.test(queryString))) throw new Error(`Query string contains a link / word which is blacklisted.`);
1308
1308
  }
1309
1309
  if (!/^https?:\/\//.test(queryString)) return;
1310
- else if (this.LavalinkManager.options?.linksAllowed === false) throw new Error("Using links to make a request is not allowed.");
1311
- if (this.LavalinkManager.options?.linksWhitelist?.length > 0) {
1312
- if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) this.LavalinkManager.emit("debug", "ValidatingWhitelistLinks", {
1310
+ else if (this.LavalinkManager?.options?.linksAllowed === false) throw new Error("Using links to make a request is not allowed.");
1311
+ if (this.LavalinkManager?.options?.linksWhitelist && this.LavalinkManager.options?.linksWhitelist.length > 0) {
1312
+ if (this.LavalinkManager?.options?.advancedOptions?.enableDebugEvents) this.LavalinkManager.emit("debug", "ValidatingWhitelistLinks", {
1313
1313
  state: "log",
1314
1314
  message: `Link was provided to the Query, validating against LavalinkManager.options.linksWhitelist, query: "${queryString}"`,
1315
1315
  functionLayer: "(LavalinkNode > node | player) > search() > validateQueryString()"
@@ -1433,7 +1433,7 @@ var ManagerUtils = class {
1433
1433
  validateSourceString(node, sourceString) {
1434
1434
  if (!sourceString) throw new Error(`No SourceString was provided`);
1435
1435
  const source = this.SourcesRecord[sourceString.toLowerCase().trim()];
1436
- if (!source && !!this.LavalinkManager.options.playerOptions.allowCustomSources) throw new Error(`Lavalink-Client does not support SearchQuerySource: '${sourceString}'. You can disable this check by setting 'ManagerOptions.PlayerOptions.allowCustomSources' to true`);
1436
+ if (!source && !this.LavalinkManager.options.playerOptions.allowCustomSources) throw new Error(`Lavalink-Client does not support SearchQuerySource: '${sourceString}'. You can disable this check by setting 'ManagerOptions.PlayerOptions.allowCustomSources' to true`);
1437
1437
  if (!node.info) throw new Error("Lavalink Node does not have any info cached yet, not ready yet!");
1438
1438
  if (!node._checkForSources) return;
1439
1439
  if (source === "amsearch" && !node.info?.sourceManagers?.includes("applemusic")) throw new Error("Lavalink Node has not 'applemusic' enabled, which is required to have 'amsearch' work");
@@ -1994,7 +1994,9 @@ var LavalinkNode = class LavalinkNode {
1994
1994
  this.sessionId = this.options.sessionId || sessionId;
1995
1995
  }
1996
1996
  this.socket = new ws.default(`ws${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}/v4/websocket`, { headers });
1997
- this.socket.on("open", this.open.bind(this));
1997
+ this.socket.on("open", () => void this.open().catch((error) => {
1998
+ this.error(error);
1999
+ }));
1998
2000
  this.socket.on("close", (code, reason) => void this.close(code, reason?.toString()).catch((error) => {
1999
2001
  this.NodeManager.emit("error", this, error);
2000
2002
  }));
@@ -2854,7 +2856,9 @@ var LavalinkNode = class LavalinkNode {
2854
2856
  const player = this._LManager.getPlayer(data.guildId);
2855
2857
  if (!player) return;
2856
2858
  if (typeof res?.voice?.connected === "boolean" && res.voice.connected === false) {
2857
- player.destroy("LavalinkNoVoice");
2859
+ player.destroy("LavalinkNoVoice").catch((error) => {
2860
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
2861
+ });
2858
2862
  return;
2859
2863
  }
2860
2864
  player.ping.ws = res?.voice?.ping || player?.ping.ws;
@@ -3153,11 +3157,12 @@ var LavalinkNode = class LavalinkNode {
3153
3157
  player.playing = true;
3154
3158
  player.paused = false;
3155
3159
  }
3156
- if (this._LManager.options?.emitNewSongsOnly === true && player.queue.previous[0]?.info?.identifier === track?.info?.identifier) return this._emitDebugEvent("TrackStartNewSongsOnly", {
3160
+ if (this._LManager.options?.emitNewSongsOnly === true && player.getData("internal_lastEmittedTrackId") === track?.info?.identifier) return this._emitDebugEvent("TrackStartNewSongsOnly", {
3157
3161
  state: "log",
3158
3162
  message: `TrackStart not Emitting, because playing the previous song again.`,
3159
3163
  functionLayer: "LavalinkNode > trackStart()"
3160
3164
  });
3165
+ player.setData("internal_lastEmittedTrackId", track?.info?.identifier);
3161
3166
  if (!player.queue.current) {
3162
3167
  player.queue.current = this.getTrackOfPayload(payload);
3163
3168
  if (player.queue.current) await player.queue.utils.save();
@@ -3179,6 +3184,7 @@ var LavalinkNode = class LavalinkNode {
3179
3184
  message: `TrackEnd Event does not handle any playback, because the track was replaced.`,
3180
3185
  functionLayer: "LavalinkNode > trackEnd()"
3181
3186
  });
3187
+ player.setData("internal_lastEmittedTrackId", void 0);
3182
3188
  this._LManager.emit("trackEnd", player, trackToUse, payload);
3183
3189
  return;
3184
3190
  }
@@ -3188,7 +3194,9 @@ var LavalinkNode = class LavalinkNode {
3188
3194
  await queueTrackEnd(player);
3189
3195
  if (!player.queue.current) return this.queueEnd(player, trackToUse, payload);
3190
3196
  this._LManager.emit("trackEnd", player, trackToUse, payload);
3191
- if (this._LManager.options.autoSkip && player.queue.current) player.play({ noReplace: true });
3197
+ if (this._LManager.options.autoSkip && player.queue.current) player.play({ noReplace: true }).catch((error) => {
3198
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3199
+ });
3192
3200
  return;
3193
3201
  }
3194
3202
  if (player.repeatMode !== "track" || player.getData("internal_skipped")) await queueTrackEnd(player);
@@ -3200,7 +3208,9 @@ var LavalinkNode = class LavalinkNode {
3200
3208
  if (!player.queue.current) return this.queueEnd(player, trackToUse, payload);
3201
3209
  player.setData("internal_skipped", false);
3202
3210
  this._LManager.emit("trackEnd", player, trackToUse, payload);
3203
- if (this._LManager.options.autoSkip && player.queue.current) player.play({ noReplace: true });
3211
+ if (this._LManager.options.autoSkip && player.queue.current) player.play({ noReplace: true }).catch((error) => {
3212
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3213
+ });
3204
3214
  }
3205
3215
  /** @private util function for handling trackStuck event */
3206
3216
  async trackStuck(player, track, payload) {
@@ -3213,7 +3223,9 @@ var LavalinkNode = class LavalinkNode {
3213
3223
  message: `trackStuck Event was triggered too often within a given threshold (LavalinkManager.options.playerOptions.maxErrorsPerTime). Threshold: "${this._LManager.options.playerOptions.maxErrorsPerTime?.threshold}ms", maxAmount: "${this._LManager.options.playerOptions.maxErrorsPerTime?.maxAmount}"`,
3214
3224
  functionLayer: "LavalinkNode > trackStuck()"
3215
3225
  });
3216
- player.destroy("TrackStuckMaxTracksErroredPerTime");
3226
+ player.destroy("TrackStuckMaxTracksErroredPerTime").catch((error) => {
3227
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3228
+ });
3217
3229
  return;
3218
3230
  }
3219
3231
  }
@@ -3232,6 +3244,8 @@ var LavalinkNode = class LavalinkNode {
3232
3244
  if (this._LManager.options.autoSkip && player.queue.current) player.play({
3233
3245
  track: player.queue.current,
3234
3246
  noReplace: false
3247
+ }).catch((error) => {
3248
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3235
3249
  });
3236
3250
  }
3237
3251
  /** @private util function for handling trackError event */
@@ -3245,7 +3259,9 @@ var LavalinkNode = class LavalinkNode {
3245
3259
  message: `TrackError Event was triggered too often within a given threshold (LavalinkManager.options.playerOptions.maxErrorsPerTime). Threshold: "${this._LManager.options.playerOptions.maxErrorsPerTime?.threshold}ms", maxAmount: "${this._LManager.options.playerOptions.maxErrorsPerTime?.maxAmount}"`,
3246
3260
  functionLayer: "LavalinkNode > trackError()"
3247
3261
  });
3248
- player.destroy("TrackErrorMaxTracksErroredPerTime");
3262
+ player.destroy("TrackErrorMaxTracksErroredPerTime").catch((error) => {
3263
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3264
+ });
3249
3265
  return;
3250
3266
  }
3251
3267
  }
@@ -3345,6 +3361,7 @@ var LavalinkNode = class LavalinkNode {
3345
3361
  player.queue.current = null;
3346
3362
  player.playing = false;
3347
3363
  player.setData("internal_stopPlaying", void 0);
3364
+ player.setData("internal_lastEmittedTrackId", void 0);
3348
3365
  this._emitDebugEvent("QueueEnded", {
3349
3366
  state: "log",
3350
3367
  message: `Queue Ended because no more Tracks were in the Queue, due to EventName: "${payload.type}"`,
@@ -3373,6 +3390,8 @@ var LavalinkNode = class LavalinkNode {
3373
3390
  if (this._LManager.options.autoSkip) return player.play({
3374
3391
  noReplace: true,
3375
3392
  paused: false
3393
+ }).catch((error) => {
3394
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3376
3395
  });
3377
3396
  } else this._emitDebugEvent("AutoplayThresholdSpamLimiter", {
3378
3397
  state: "warn",
@@ -3390,7 +3409,9 @@ var LavalinkNode = class LavalinkNode {
3390
3409
  if (payload?.reason !== "stopped") await player.queue.utils.save();
3391
3410
  if (typeof this._LManager.options.playerOptions?.onEmptyQueue?.destroyAfterMs === "number" && !isNaN(this._LManager.options.playerOptions.onEmptyQueue?.destroyAfterMs) && this._LManager.options.playerOptions.onEmptyQueue?.destroyAfterMs >= 0) {
3392
3411
  if (this._LManager.options.playerOptions.onEmptyQueue?.destroyAfterMs === 0) {
3393
- player.destroy("QueueEmpty");
3412
+ player.destroy("QueueEmpty").catch((error) => {
3413
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3414
+ });
3394
3415
  return;
3395
3416
  } else {
3396
3417
  this._emitDebugEvent("TriggerQueueEmptyInterval", {
@@ -3404,7 +3425,9 @@ var LavalinkNode = class LavalinkNode {
3404
3425
  player.setData("internal_queueempty", void 0);
3405
3426
  if (player.queue.current) return this._LManager.emit("playerQueueEmptyCancel", player);
3406
3427
  this._LManager.emit("playerQueueEmptyEnd", player);
3407
- player.destroy("QueueEmpty");
3428
+ player.destroy("QueueEmpty").catch((error) => {
3429
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3430
+ });
3408
3431
  }, this._LManager.options.playerOptions.onEmptyQueue?.destroyAfterMs));
3409
3432
  }
3410
3433
  }
@@ -5018,6 +5041,14 @@ var FilterManager = class {
5018
5041
 
5019
5042
  //#endregion
5020
5043
  //#region src/structures/Queue.ts
5044
+ function tracksMatch(a, b) {
5045
+ if (a === b) return true;
5046
+ if (a.encoded) return a.encoded === b.encoded;
5047
+ if (a.info?.identifier) return a.info.identifier === b.info?.identifier;
5048
+ if (a.info?.uri) return a.info.uri === b.info?.uri;
5049
+ if (a.info?.isrc) return a.info.isrc === b.info?.isrc;
5050
+ return a.info?.title && a.info.title === b.info?.title || a.info?.artworkUrl && a.info.artworkUrl === b.info?.artworkUrl || false;
5051
+ }
5021
5052
  var QueueSaver = class {
5022
5053
  /**
5023
5054
  * The queue store manager
@@ -5148,14 +5179,14 @@ var Queue = class {
5148
5179
  */
5149
5180
  save: async () => {
5150
5181
  if (this.previous.length > this.options.maxPreviousTracks) this.previous.splice(this.options.maxPreviousTracks, this.previous.length);
5151
- return await this.QueueSaver.set(this.guildId, this.utils.toJSON());
5182
+ return await this.QueueSaver?.set(this.guildId, this.utils.toJSON());
5152
5183
  },
5153
5184
  /**
5154
5185
  * Sync the current queue database/server with the cached one
5155
5186
  * @returns {void}
5156
5187
  */
5157
5188
  sync: async (override = true, dontSyncCurrent = true) => {
5158
- const data = await this.QueueSaver.get(this.guildId);
5189
+ const data = await this.QueueSaver?.get(this.guildId);
5159
5190
  if (!data) throw new Error(`No data found to sync for guildId: ${this.guildId}`);
5160
5191
  if (!dontSyncCurrent && !this.current && this.managerUtils.isTrack(data.current)) this.current = data.current;
5161
5192
  if (Array.isArray(data.tracks) && data?.tracks.length && data.tracks.some((track) => this.managerUtils.isTrack(track) || this.managerUtils.isUnresolvedTrack(track))) this.tracks.splice(override ? 0 : this.tracks.length, override ? this.tracks.length : 0, ...data.tracks.filter((track) => this.managerUtils.isTrack(track) || this.managerUtils.isUnresolvedTrack(track)));
@@ -5163,7 +5194,7 @@ var Queue = class {
5163
5194
  await this.utils.save();
5164
5195
  },
5165
5196
  destroy: async () => {
5166
- return await this.QueueSaver.delete(this.guildId);
5197
+ return await this.QueueSaver?.delete(this.guildId);
5167
5198
  },
5168
5199
  /**
5169
5200
  * @returns {{current:Track|null, previous:Track[], tracks:Track[]}}The Queue, but in a raw State, which allows easier handling for the QueueStoreManager
@@ -5292,12 +5323,12 @@ var Queue = class {
5292
5323
  * @param {number} index Where to remove the Track
5293
5324
  * @param {number} amount How many Tracks to remove?
5294
5325
  * @param {Track | Track[]} TrackOrTracks Want to Add more Tracks?
5295
- * @returns {Track} Spliced Track
5326
+ * @returns {Track} Spliced (aka Removed) Track
5296
5327
  */
5297
5328
  async splice(index, amount, TrackOrTracks) {
5298
5329
  const oldStored = typeof this.queueChanges?.tracksAdd === "function" || typeof this.queueChanges?.tracksRemoved === "function" ? this.utils.toJSON() : null;
5299
5330
  if (!this.tracks.length) {
5300
- if (TrackOrTracks) return await this.add(TrackOrTracks);
5331
+ if (TrackOrTracks) await this.add(TrackOrTracks);
5301
5332
  return null;
5302
5333
  }
5303
5334
  if (TrackOrTracks && typeof this.queueChanges?.tracksAdd === "function") try {
@@ -5369,7 +5400,7 @@ var Queue = class {
5369
5400
  const tracksToRemove = this.tracks.map((v, i) => ({
5370
5401
  v,
5371
5402
  i
5372
- })).filter(({ v, i }) => removeQueryTrack.find((t) => typeof t === "number" && t === i || typeof t === "object" && (t.encoded && t.encoded === v.encoded || t.info?.identifier && t.info.identifier === v.info?.identifier || t.info?.uri && t.info.uri === v.info?.uri || t.info?.title && t.info.title === v.info?.title || t.info?.isrc && t.info.isrc === v.info?.isrc || t.info?.artworkUrl && t.info.artworkUrl === v.info?.artworkUrl)));
5403
+ })).filter(({ v, i }) => removeQueryTrack.find((t) => typeof t === "number" && t === i || typeof t === "object" && t !== null && tracksMatch(t, v)));
5373
5404
  if (!tracksToRemove.length) return null;
5374
5405
  const removed = [];
5375
5406
  tracksToRemove.sort((a, b) => b.i - a.i);
@@ -5380,7 +5411,7 @@ var Queue = class {
5380
5411
  await this.utils.save();
5381
5412
  return { removed };
5382
5413
  }
5383
- const toRemove = this.tracks.findIndex((v) => removeQueryTrack.encoded && removeQueryTrack.encoded === v.encoded || removeQueryTrack.info?.identifier && removeQueryTrack.info.identifier === v.info?.identifier || removeQueryTrack.info?.uri && removeQueryTrack.info.uri === v.info?.uri || removeQueryTrack.info?.title && removeQueryTrack.info.title === v.info?.title || removeQueryTrack.info?.isrc && removeQueryTrack.info.isrc === v.info?.isrc || removeQueryTrack.info?.artworkUrl && removeQueryTrack.info.artworkUrl === v.info?.artworkUrl);
5414
+ const toRemove = this.tracks.findIndex((v) => tracksMatch(removeQueryTrack, v));
5384
5415
  if (toRemove < 0) return null;
5385
5416
  const removed = this.tracks.splice(toRemove, 1);
5386
5417
  if (typeof this.queueChanges?.tracksRemoved === "function") try {
@@ -5751,7 +5782,9 @@ var Player = class {
5751
5782
  }
5752
5783
  if (options?.track?.encoded || options?.track?.identifier) {
5753
5784
  this.queue.current = options.clientTrack || null;
5754
- this.queue.utils.save();
5785
+ this.queue.utils.save().catch((error) => {
5786
+ if (!this.LavalinkManager.options.catchLifeCycleRejections) throw error;
5787
+ });
5755
5788
  if (typeof options?.volume === "number" && !isNaN(options?.volume)) {
5756
5789
  this.volume = Math.max(Math.min(options?.volume, 1e3), 0);
5757
5790
  let vol = Number(this.volume);
@@ -6020,7 +6053,12 @@ var Player = class {
6020
6053
  if (skipTo > this.queue.tracks.length) throw new RangeError("Can't skip more than the queue size");
6021
6054
  await this.queue.splice(0, skipTo - 1);
6022
6055
  }
6023
- if (!this.playing && !this.queue.current) return this.play(), this;
6056
+ if (!this.playing && !this.queue.current) {
6057
+ this.play().catch((error) => {
6058
+ if (!this.LavalinkManager.options.catchLifeCycleRejections) throw error;
6059
+ });
6060
+ return this;
6061
+ }
6024
6062
  const now = performance.now();
6025
6063
  this.setData("internal_skipped", true);
6026
6064
  await this.node.updatePlayer({
@@ -6231,6 +6269,7 @@ var Player = class {
6231
6269
  const currentTrack = this.queue.current;
6232
6270
  if (!this.voice.endpoint || !this.voice.sessionId || !this.voice.token) throw new Error("Voice Data is missing, can't change the node");
6233
6271
  this.setData("internal_nodeChanging", true);
6272
+ this.setData("internal_lastEmittedTrackId", void 0);
6234
6273
  if (this.node.connected) await this.node.destroyPlayer(this.guildId);
6235
6274
  this.node = updateNode;
6236
6275
  const now = performance.now();
@@ -6446,6 +6485,7 @@ var LavalinkManager = class LavalinkManager extends node_events.EventEmitter {
6446
6485
  linksAllowed: options?.linksAllowed ?? true,
6447
6486
  autoSkip: options?.autoSkip ?? true,
6448
6487
  autoSkipOnResolveError: options?.autoSkipOnResolveError ?? true,
6488
+ catchLifeCycleRejections: options?.catchLifeCycleRejections ?? false,
6449
6489
  emitNewSongsOnly: options?.emitNewSongsOnly ?? false,
6450
6490
  queueOptions: {
6451
6491
  maxPreviousTracks: options?.queueOptions?.maxPreviousTracks ?? 25,
@@ -6481,6 +6521,7 @@ var LavalinkManager = class LavalinkManager extends node_events.EventEmitter {
6481
6521
  if (typeof options?.sendToShard !== "function") throw new SyntaxError("ManagerOption.sendToShard was not provided, which is required!");
6482
6522
  if (options?.autoSkip && typeof options?.autoSkip !== "boolean") throw new SyntaxError("ManagerOption.autoSkip must be either false | true aka boolean");
6483
6523
  if (options?.autoSkipOnResolveError && typeof options?.autoSkipOnResolveError !== "boolean") throw new SyntaxError("ManagerOption.autoSkipOnResolveError must be either false | true aka boolean");
6524
+ if (options?.catchLifeCycleRejections && typeof options?.catchLifeCycleRejections !== "boolean") throw new SyntaxError("ManagerOption.catchLifeCycleRejections must be either false | true aka boolean");
6484
6525
  if (options?.emitNewSongsOnly && typeof options?.emitNewSongsOnly !== "boolean") throw new SyntaxError("ManagerOption.emitNewSongsOnly must be either false | true aka boolean");
6485
6526
  if (!options?.nodes || !Array.isArray(options?.nodes) || !options?.nodes.every((node) => node instanceof NodeLinkNode || node instanceof LavalinkNode || this.utils.isNodeOptions(node))) throw new SyntaxError("ManagerOption.nodes must be an Array of NodeOptions or the Node-Classes 'NodeLinkNode' or 'LavalinkNode' and is required of at least 1 Node");
6486
6527
  if (options?.queueOptions?.queueStore) {
@@ -6788,7 +6829,9 @@ var LavalinkManager = class LavalinkManager extends node_events.EventEmitter {
6788
6829
  const update = "d" in data ? data.d : data;
6789
6830
  if (!update.guild_id) return;
6790
6831
  const player = this.getPlayer(update.guild_id);
6791
- if (player && player.voiceChannelId === update.id) return void player.destroy("ChannelDeleted");
6832
+ if (player && player.voiceChannelId === update.id) return void player.destroy("ChannelDeleted").catch((error) => {
6833
+ if (!this.options.catchLifeCycleRejections) throw error;
6834
+ });
6792
6835
  }
6793
6836
  if (["VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE"].includes(data.t)) {
6794
6837
  const update = "d" in data ? data.d : data;
package/dist/index.d.cts CHANGED
@@ -668,7 +668,7 @@ declare class ManagerUtils {
668
668
  * @param data the Track to validate
669
669
  * @returns
670
670
  */
671
- isTrack(data: Track | UnresolvedTrack): data is Track;
671
+ isTrack(data?: Track | UnresolvedTrack | null): data is Track;
672
672
  /**
673
673
  * Checks if the provided argument is a valid UnresolvedTrack.
674
674
  * @param track
@@ -2342,13 +2342,13 @@ declare class Queue {
2342
2342
  /**
2343
2343
  * Save the current cached Queue on the database/server (overides the server)
2344
2344
  */
2345
- save: () => Promise<void | boolean>;
2345
+ save: () => Promise<void | boolean | undefined>;
2346
2346
  /**
2347
2347
  * Sync the current queue database/server with the cached one
2348
2348
  * @returns {void}
2349
2349
  */
2350
2350
  sync: (override?: boolean, dontSyncCurrent?: boolean) => Promise<void>;
2351
- destroy: () => Promise<void | boolean>;
2351
+ destroy: () => Promise<void | boolean | undefined>;
2352
2352
  /**
2353
2353
  * @returns {{current:Track|null, previous:Track[], tracks:Track[]}}The Queue, but in a raw State, which allows easier handling for the QueueStoreManager
2354
2354
  */
@@ -2441,15 +2441,15 @@ declare class Queue {
2441
2441
  * @param {number} index At what position to add the Track
2442
2442
  * @returns {number} Queue-Size (for the next Tracks)
2443
2443
  */
2444
- add(TrackOrTracks: Track | UnresolvedTrack | (Track | UnresolvedTrack)[], index?: number): any;
2444
+ add(TrackOrTracks: Track | UnresolvedTrack | (Track | UnresolvedTrack)[], index?: number): Promise<number | (Track | UnresolvedTrack)[] | Track | UnresolvedTrack | null>;
2445
2445
  /**
2446
2446
  * Splice the tracks in the Queue
2447
2447
  * @param {number} index Where to remove the Track
2448
2448
  * @param {number} amount How many Tracks to remove?
2449
2449
  * @param {Track | Track[]} TrackOrTracks Want to Add more Tracks?
2450
- * @returns {Track} Spliced Track
2450
+ * @returns {Track} Spliced (aka Removed) Track
2451
2451
  */
2452
- splice(index: number, amount: number, TrackOrTracks?: Track | UnresolvedTrack | (Track | UnresolvedTrack)[]): any;
2452
+ splice(index: number, amount: number, TrackOrTracks?: Track | UnresolvedTrack | (Track | UnresolvedTrack)[]): Promise<Track | UnresolvedTrack | (Track | UnresolvedTrack)[] | null>;
2453
2453
  /**
2454
2454
  * Remove stuff from the queue.tracks array
2455
2455
  * - single Track | UnresolvedTrack
@@ -4089,6 +4089,15 @@ interface ManagerOptions<CustomPlayerT extends Player = Player> {
4089
4089
  autoMove?: boolean;
4090
4090
  /** If it should skip to the next Track if track.resolve errors while trying to play a track. */
4091
4091
  autoSkipOnResolveError?: boolean;
4092
+ /**
4093
+ * If the library's internal fire-and-forget lifecycle calls (e.g. autoSkip `player.play()`,
4094
+ * `player.destroy()`, and the fire-and-forget queue `save()` inside `Player#play()`) should
4095
+ * catch their own rejections instead of letting them surface as unhandled rejections which
4096
+ * may crash the host application.
4097
+ * Default: false - rejections surface as unhandled rejections, exactly as if this option
4098
+ * did not exist.
4099
+ */
4100
+ catchLifeCycleRejections?: boolean;
4092
4101
  /** If it should emit only new (unique) songs and not when a looping track (or similar) is plaid, default false */
4093
4102
  emitNewSongsOnly?: boolean;
4094
4103
  /** Only allow link requests with links either matching some of that regExp or including some of that string */
package/dist/index.d.mts CHANGED
@@ -668,7 +668,7 @@ declare class ManagerUtils {
668
668
  * @param data the Track to validate
669
669
  * @returns
670
670
  */
671
- isTrack(data: Track | UnresolvedTrack): data is Track;
671
+ isTrack(data?: Track | UnresolvedTrack | null): data is Track;
672
672
  /**
673
673
  * Checks if the provided argument is a valid UnresolvedTrack.
674
674
  * @param track
@@ -2342,13 +2342,13 @@ declare class Queue {
2342
2342
  /**
2343
2343
  * Save the current cached Queue on the database/server (overides the server)
2344
2344
  */
2345
- save: () => Promise<void | boolean>;
2345
+ save: () => Promise<void | boolean | undefined>;
2346
2346
  /**
2347
2347
  * Sync the current queue database/server with the cached one
2348
2348
  * @returns {void}
2349
2349
  */
2350
2350
  sync: (override?: boolean, dontSyncCurrent?: boolean) => Promise<void>;
2351
- destroy: () => Promise<void | boolean>;
2351
+ destroy: () => Promise<void | boolean | undefined>;
2352
2352
  /**
2353
2353
  * @returns {{current:Track|null, previous:Track[], tracks:Track[]}}The Queue, but in a raw State, which allows easier handling for the QueueStoreManager
2354
2354
  */
@@ -2441,15 +2441,15 @@ declare class Queue {
2441
2441
  * @param {number} index At what position to add the Track
2442
2442
  * @returns {number} Queue-Size (for the next Tracks)
2443
2443
  */
2444
- add(TrackOrTracks: Track | UnresolvedTrack | (Track | UnresolvedTrack)[], index?: number): any;
2444
+ add(TrackOrTracks: Track | UnresolvedTrack | (Track | UnresolvedTrack)[], index?: number): Promise<number | (Track | UnresolvedTrack)[] | Track | UnresolvedTrack | null>;
2445
2445
  /**
2446
2446
  * Splice the tracks in the Queue
2447
2447
  * @param {number} index Where to remove the Track
2448
2448
  * @param {number} amount How many Tracks to remove?
2449
2449
  * @param {Track | Track[]} TrackOrTracks Want to Add more Tracks?
2450
- * @returns {Track} Spliced Track
2450
+ * @returns {Track} Spliced (aka Removed) Track
2451
2451
  */
2452
- splice(index: number, amount: number, TrackOrTracks?: Track | UnresolvedTrack | (Track | UnresolvedTrack)[]): any;
2452
+ splice(index: number, amount: number, TrackOrTracks?: Track | UnresolvedTrack | (Track | UnresolvedTrack)[]): Promise<Track | UnresolvedTrack | (Track | UnresolvedTrack)[] | null>;
2453
2453
  /**
2454
2454
  * Remove stuff from the queue.tracks array
2455
2455
  * - single Track | UnresolvedTrack
@@ -4089,6 +4089,15 @@ interface ManagerOptions<CustomPlayerT extends Player = Player> {
4089
4089
  autoMove?: boolean;
4090
4090
  /** If it should skip to the next Track if track.resolve errors while trying to play a track. */
4091
4091
  autoSkipOnResolveError?: boolean;
4092
+ /**
4093
+ * If the library's internal fire-and-forget lifecycle calls (e.g. autoSkip `player.play()`,
4094
+ * `player.destroy()`, and the fire-and-forget queue `save()` inside `Player#play()`) should
4095
+ * catch their own rejections instead of letting them surface as unhandled rejections which
4096
+ * may crash the host application.
4097
+ * Default: false - rejections surface as unhandled rejections, exactly as if this option
4098
+ * did not exist.
4099
+ */
4100
+ catchLifeCycleRejections?: boolean;
4092
4101
  /** If it should emit only new (unique) songs and not when a looping track (or similar) is plaid, default false */
4093
4102
  emitNewSongsOnly?: boolean;
4094
4103
  /** Only allow link requests with links either matching some of that regExp or including some of that string */
package/dist/index.d.ts CHANGED
@@ -668,7 +668,7 @@ declare class ManagerUtils {
668
668
  * @param data the Track to validate
669
669
  * @returns
670
670
  */
671
- isTrack(data: Track | UnresolvedTrack): data is Track;
671
+ isTrack(data?: Track | UnresolvedTrack | null): data is Track;
672
672
  /**
673
673
  * Checks if the provided argument is a valid UnresolvedTrack.
674
674
  * @param track
@@ -2342,13 +2342,13 @@ declare class Queue {
2342
2342
  /**
2343
2343
  * Save the current cached Queue on the database/server (overides the server)
2344
2344
  */
2345
- save: () => Promise<void | boolean>;
2345
+ save: () => Promise<void | boolean | undefined>;
2346
2346
  /**
2347
2347
  * Sync the current queue database/server with the cached one
2348
2348
  * @returns {void}
2349
2349
  */
2350
2350
  sync: (override?: boolean, dontSyncCurrent?: boolean) => Promise<void>;
2351
- destroy: () => Promise<void | boolean>;
2351
+ destroy: () => Promise<void | boolean | undefined>;
2352
2352
  /**
2353
2353
  * @returns {{current:Track|null, previous:Track[], tracks:Track[]}}The Queue, but in a raw State, which allows easier handling for the QueueStoreManager
2354
2354
  */
@@ -2441,15 +2441,15 @@ declare class Queue {
2441
2441
  * @param {number} index At what position to add the Track
2442
2442
  * @returns {number} Queue-Size (for the next Tracks)
2443
2443
  */
2444
- add(TrackOrTracks: Track | UnresolvedTrack | (Track | UnresolvedTrack)[], index?: number): any;
2444
+ add(TrackOrTracks: Track | UnresolvedTrack | (Track | UnresolvedTrack)[], index?: number): Promise<number | (Track | UnresolvedTrack)[] | Track | UnresolvedTrack | null>;
2445
2445
  /**
2446
2446
  * Splice the tracks in the Queue
2447
2447
  * @param {number} index Where to remove the Track
2448
2448
  * @param {number} amount How many Tracks to remove?
2449
2449
  * @param {Track | Track[]} TrackOrTracks Want to Add more Tracks?
2450
- * @returns {Track} Spliced Track
2450
+ * @returns {Track} Spliced (aka Removed) Track
2451
2451
  */
2452
- splice(index: number, amount: number, TrackOrTracks?: Track | UnresolvedTrack | (Track | UnresolvedTrack)[]): any;
2452
+ splice(index: number, amount: number, TrackOrTracks?: Track | UnresolvedTrack | (Track | UnresolvedTrack)[]): Promise<Track | UnresolvedTrack | (Track | UnresolvedTrack)[] | null>;
2453
2453
  /**
2454
2454
  * Remove stuff from the queue.tracks array
2455
2455
  * - single Track | UnresolvedTrack
@@ -4089,6 +4089,15 @@ interface ManagerOptions<CustomPlayerT extends Player = Player> {
4089
4089
  autoMove?: boolean;
4090
4090
  /** If it should skip to the next Track if track.resolve errors while trying to play a track. */
4091
4091
  autoSkipOnResolveError?: boolean;
4092
+ /**
4093
+ * If the library's internal fire-and-forget lifecycle calls (e.g. autoSkip `player.play()`,
4094
+ * `player.destroy()`, and the fire-and-forget queue `save()` inside `Player#play()`) should
4095
+ * catch their own rejections instead of letting them surface as unhandled rejections which
4096
+ * may crash the host application.
4097
+ * Default: false - rejections surface as unhandled rejections, exactly as if this option
4098
+ * did not exist.
4099
+ */
4100
+ catchLifeCycleRejections?: boolean;
4092
4101
  /** If it should emit only new (unique) songs and not when a looping track (or similar) is plaid, default false */
4093
4102
  emitNewSongsOnly?: boolean;
4094
4103
  /** Only allow link requests with links either matching some of that regExp or including some of that string */
package/dist/index.js CHANGED
@@ -1218,8 +1218,8 @@ var ManagerUtils = class {
1218
1218
  * @returns
1219
1219
  */
1220
1220
  isTrack(data) {
1221
- if (!data) return false;
1222
- if (data[TrackSymbol] === true) return true;
1221
+ if (!data || typeof data !== "object") return false;
1222
+ if (Reflect.get(data, TrackSymbol) === true) return true;
1223
1223
  return typeof data?.encoded === "string" && typeof data?.info === "object" && !("resolve" in data);
1224
1224
  }
1225
1225
  /**
@@ -1228,7 +1228,7 @@ var ManagerUtils = class {
1228
1228
  */
1229
1229
  isUnresolvedTrack(data) {
1230
1230
  if (!data) return false;
1231
- if (data[UnresolvedTrackSymbol] === true) return true;
1231
+ if (Reflect.get(data, UnresolvedTrackSymbol) === true) return true;
1232
1232
  return typeof data === "object" && ("info" in data && typeof data.info.title === "string" || typeof data.encoded === "string") && "resolve" in data && typeof data.resolve === "function";
1233
1233
  }
1234
1234
  /**
@@ -1269,7 +1269,7 @@ var ManagerUtils = class {
1269
1269
  if (node._checkForSources && !node.info.sourceManagers?.length) throw new Error("Lavalink Node, has no sourceManagers enabled");
1270
1270
  if (!queryString.trim().length) throw new Error(`Query string is empty, please provide a valid query string.`);
1271
1271
  if (sourceString === "speak" && queryString.length > 100) throw new Error(`Query is speak, which is limited to 100 characters.`);
1272
- if (this.LavalinkManager.options?.linksBlacklist?.length > 0) {
1272
+ if (this.LavalinkManager?.options?.linksBlacklist && this.LavalinkManager.options?.linksBlacklist.length > 0) {
1273
1273
  if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) this.LavalinkManager.emit("debug", "ValidatingBlacklistLinks", {
1274
1274
  state: "log",
1275
1275
  message: `Validating Query against LavalinkManager.options.linksBlacklist, query: "${queryString}"`,
@@ -1278,9 +1278,9 @@ var ManagerUtils = class {
1278
1278
  if (this.LavalinkManager.options?.linksBlacklist.some((v) => typeof v === "string" && queryString.toLowerCase().includes(v.toLowerCase()) || isRegExp(v) && v.test(queryString))) throw new Error(`Query string contains a link / word which is blacklisted.`);
1279
1279
  }
1280
1280
  if (!/^https?:\/\//.test(queryString)) return;
1281
- else if (this.LavalinkManager.options?.linksAllowed === false) throw new Error("Using links to make a request is not allowed.");
1282
- if (this.LavalinkManager.options?.linksWhitelist?.length > 0) {
1283
- if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) this.LavalinkManager.emit("debug", "ValidatingWhitelistLinks", {
1281
+ else if (this.LavalinkManager?.options?.linksAllowed === false) throw new Error("Using links to make a request is not allowed.");
1282
+ if (this.LavalinkManager?.options?.linksWhitelist && this.LavalinkManager.options?.linksWhitelist.length > 0) {
1283
+ if (this.LavalinkManager?.options?.advancedOptions?.enableDebugEvents) this.LavalinkManager.emit("debug", "ValidatingWhitelistLinks", {
1284
1284
  state: "log",
1285
1285
  message: `Link was provided to the Query, validating against LavalinkManager.options.linksWhitelist, query: "${queryString}"`,
1286
1286
  functionLayer: "(LavalinkNode > node | player) > search() > validateQueryString()"
@@ -1404,7 +1404,7 @@ var ManagerUtils = class {
1404
1404
  validateSourceString(node, sourceString) {
1405
1405
  if (!sourceString) throw new Error(`No SourceString was provided`);
1406
1406
  const source = this.SourcesRecord[sourceString.toLowerCase().trim()];
1407
- if (!source && !!this.LavalinkManager.options.playerOptions.allowCustomSources) throw new Error(`Lavalink-Client does not support SearchQuerySource: '${sourceString}'. You can disable this check by setting 'ManagerOptions.PlayerOptions.allowCustomSources' to true`);
1407
+ if (!source && !this.LavalinkManager.options.playerOptions.allowCustomSources) throw new Error(`Lavalink-Client does not support SearchQuerySource: '${sourceString}'. You can disable this check by setting 'ManagerOptions.PlayerOptions.allowCustomSources' to true`);
1408
1408
  if (!node.info) throw new Error("Lavalink Node does not have any info cached yet, not ready yet!");
1409
1409
  if (!node._checkForSources) return;
1410
1410
  if (source === "amsearch" && !node.info?.sourceManagers?.includes("applemusic")) throw new Error("Lavalink Node has not 'applemusic' enabled, which is required to have 'amsearch' work");
@@ -1965,7 +1965,9 @@ var LavalinkNode = class LavalinkNode {
1965
1965
  this.sessionId = this.options.sessionId || sessionId;
1966
1966
  }
1967
1967
  this.socket = new WebSocket(`ws${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}/v4/websocket`, { headers });
1968
- this.socket.on("open", this.open.bind(this));
1968
+ this.socket.on("open", () => void this.open().catch((error) => {
1969
+ this.error(error);
1970
+ }));
1969
1971
  this.socket.on("close", (code, reason) => void this.close(code, reason?.toString()).catch((error) => {
1970
1972
  this.NodeManager.emit("error", this, error);
1971
1973
  }));
@@ -2825,7 +2827,9 @@ var LavalinkNode = class LavalinkNode {
2825
2827
  const player = this._LManager.getPlayer(data.guildId);
2826
2828
  if (!player) return;
2827
2829
  if (typeof res?.voice?.connected === "boolean" && res.voice.connected === false) {
2828
- player.destroy("LavalinkNoVoice");
2830
+ player.destroy("LavalinkNoVoice").catch((error) => {
2831
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
2832
+ });
2829
2833
  return;
2830
2834
  }
2831
2835
  player.ping.ws = res?.voice?.ping || player?.ping.ws;
@@ -3124,11 +3128,12 @@ var LavalinkNode = class LavalinkNode {
3124
3128
  player.playing = true;
3125
3129
  player.paused = false;
3126
3130
  }
3127
- if (this._LManager.options?.emitNewSongsOnly === true && player.queue.previous[0]?.info?.identifier === track?.info?.identifier) return this._emitDebugEvent("TrackStartNewSongsOnly", {
3131
+ if (this._LManager.options?.emitNewSongsOnly === true && player.getData("internal_lastEmittedTrackId") === track?.info?.identifier) return this._emitDebugEvent("TrackStartNewSongsOnly", {
3128
3132
  state: "log",
3129
3133
  message: `TrackStart not Emitting, because playing the previous song again.`,
3130
3134
  functionLayer: "LavalinkNode > trackStart()"
3131
3135
  });
3136
+ player.setData("internal_lastEmittedTrackId", track?.info?.identifier);
3132
3137
  if (!player.queue.current) {
3133
3138
  player.queue.current = this.getTrackOfPayload(payload);
3134
3139
  if (player.queue.current) await player.queue.utils.save();
@@ -3150,6 +3155,7 @@ var LavalinkNode = class LavalinkNode {
3150
3155
  message: `TrackEnd Event does not handle any playback, because the track was replaced.`,
3151
3156
  functionLayer: "LavalinkNode > trackEnd()"
3152
3157
  });
3158
+ player.setData("internal_lastEmittedTrackId", void 0);
3153
3159
  this._LManager.emit("trackEnd", player, trackToUse, payload);
3154
3160
  return;
3155
3161
  }
@@ -3159,7 +3165,9 @@ var LavalinkNode = class LavalinkNode {
3159
3165
  await queueTrackEnd(player);
3160
3166
  if (!player.queue.current) return this.queueEnd(player, trackToUse, payload);
3161
3167
  this._LManager.emit("trackEnd", player, trackToUse, payload);
3162
- if (this._LManager.options.autoSkip && player.queue.current) player.play({ noReplace: true });
3168
+ if (this._LManager.options.autoSkip && player.queue.current) player.play({ noReplace: true }).catch((error) => {
3169
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3170
+ });
3163
3171
  return;
3164
3172
  }
3165
3173
  if (player.repeatMode !== "track" || player.getData("internal_skipped")) await queueTrackEnd(player);
@@ -3171,7 +3179,9 @@ var LavalinkNode = class LavalinkNode {
3171
3179
  if (!player.queue.current) return this.queueEnd(player, trackToUse, payload);
3172
3180
  player.setData("internal_skipped", false);
3173
3181
  this._LManager.emit("trackEnd", player, trackToUse, payload);
3174
- if (this._LManager.options.autoSkip && player.queue.current) player.play({ noReplace: true });
3182
+ if (this._LManager.options.autoSkip && player.queue.current) player.play({ noReplace: true }).catch((error) => {
3183
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3184
+ });
3175
3185
  }
3176
3186
  /** @private util function for handling trackStuck event */
3177
3187
  async trackStuck(player, track, payload) {
@@ -3184,7 +3194,9 @@ var LavalinkNode = class LavalinkNode {
3184
3194
  message: `trackStuck Event was triggered too often within a given threshold (LavalinkManager.options.playerOptions.maxErrorsPerTime). Threshold: "${this._LManager.options.playerOptions.maxErrorsPerTime?.threshold}ms", maxAmount: "${this._LManager.options.playerOptions.maxErrorsPerTime?.maxAmount}"`,
3185
3195
  functionLayer: "LavalinkNode > trackStuck()"
3186
3196
  });
3187
- player.destroy("TrackStuckMaxTracksErroredPerTime");
3197
+ player.destroy("TrackStuckMaxTracksErroredPerTime").catch((error) => {
3198
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3199
+ });
3188
3200
  return;
3189
3201
  }
3190
3202
  }
@@ -3203,6 +3215,8 @@ var LavalinkNode = class LavalinkNode {
3203
3215
  if (this._LManager.options.autoSkip && player.queue.current) player.play({
3204
3216
  track: player.queue.current,
3205
3217
  noReplace: false
3218
+ }).catch((error) => {
3219
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3206
3220
  });
3207
3221
  }
3208
3222
  /** @private util function for handling trackError event */
@@ -3216,7 +3230,9 @@ var LavalinkNode = class LavalinkNode {
3216
3230
  message: `TrackError Event was triggered too often within a given threshold (LavalinkManager.options.playerOptions.maxErrorsPerTime). Threshold: "${this._LManager.options.playerOptions.maxErrorsPerTime?.threshold}ms", maxAmount: "${this._LManager.options.playerOptions.maxErrorsPerTime?.maxAmount}"`,
3217
3231
  functionLayer: "LavalinkNode > trackError()"
3218
3232
  });
3219
- player.destroy("TrackErrorMaxTracksErroredPerTime");
3233
+ player.destroy("TrackErrorMaxTracksErroredPerTime").catch((error) => {
3234
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3235
+ });
3220
3236
  return;
3221
3237
  }
3222
3238
  }
@@ -3316,6 +3332,7 @@ var LavalinkNode = class LavalinkNode {
3316
3332
  player.queue.current = null;
3317
3333
  player.playing = false;
3318
3334
  player.setData("internal_stopPlaying", void 0);
3335
+ player.setData("internal_lastEmittedTrackId", void 0);
3319
3336
  this._emitDebugEvent("QueueEnded", {
3320
3337
  state: "log",
3321
3338
  message: `Queue Ended because no more Tracks were in the Queue, due to EventName: "${payload.type}"`,
@@ -3344,6 +3361,8 @@ var LavalinkNode = class LavalinkNode {
3344
3361
  if (this._LManager.options.autoSkip) return player.play({
3345
3362
  noReplace: true,
3346
3363
  paused: false
3364
+ }).catch((error) => {
3365
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3347
3366
  });
3348
3367
  } else this._emitDebugEvent("AutoplayThresholdSpamLimiter", {
3349
3368
  state: "warn",
@@ -3361,7 +3380,9 @@ var LavalinkNode = class LavalinkNode {
3361
3380
  if (payload?.reason !== "stopped") await player.queue.utils.save();
3362
3381
  if (typeof this._LManager.options.playerOptions?.onEmptyQueue?.destroyAfterMs === "number" && !isNaN(this._LManager.options.playerOptions.onEmptyQueue?.destroyAfterMs) && this._LManager.options.playerOptions.onEmptyQueue?.destroyAfterMs >= 0) {
3363
3382
  if (this._LManager.options.playerOptions.onEmptyQueue?.destroyAfterMs === 0) {
3364
- player.destroy("QueueEmpty");
3383
+ player.destroy("QueueEmpty").catch((error) => {
3384
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3385
+ });
3365
3386
  return;
3366
3387
  } else {
3367
3388
  this._emitDebugEvent("TriggerQueueEmptyInterval", {
@@ -3375,7 +3396,9 @@ var LavalinkNode = class LavalinkNode {
3375
3396
  player.setData("internal_queueempty", void 0);
3376
3397
  if (player.queue.current) return this._LManager.emit("playerQueueEmptyCancel", player);
3377
3398
  this._LManager.emit("playerQueueEmptyEnd", player);
3378
- player.destroy("QueueEmpty");
3399
+ player.destroy("QueueEmpty").catch((error) => {
3400
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3401
+ });
3379
3402
  }, this._LManager.options.playerOptions.onEmptyQueue?.destroyAfterMs));
3380
3403
  }
3381
3404
  }
@@ -4989,6 +5012,14 @@ var FilterManager = class {
4989
5012
 
4990
5013
  //#endregion
4991
5014
  //#region src/structures/Queue.ts
5015
+ function tracksMatch(a, b) {
5016
+ if (a === b) return true;
5017
+ if (a.encoded) return a.encoded === b.encoded;
5018
+ if (a.info?.identifier) return a.info.identifier === b.info?.identifier;
5019
+ if (a.info?.uri) return a.info.uri === b.info?.uri;
5020
+ if (a.info?.isrc) return a.info.isrc === b.info?.isrc;
5021
+ return a.info?.title && a.info.title === b.info?.title || a.info?.artworkUrl && a.info.artworkUrl === b.info?.artworkUrl || false;
5022
+ }
4992
5023
  var QueueSaver = class {
4993
5024
  /**
4994
5025
  * The queue store manager
@@ -5119,14 +5150,14 @@ var Queue = class {
5119
5150
  */
5120
5151
  save: async () => {
5121
5152
  if (this.previous.length > this.options.maxPreviousTracks) this.previous.splice(this.options.maxPreviousTracks, this.previous.length);
5122
- return await this.QueueSaver.set(this.guildId, this.utils.toJSON());
5153
+ return await this.QueueSaver?.set(this.guildId, this.utils.toJSON());
5123
5154
  },
5124
5155
  /**
5125
5156
  * Sync the current queue database/server with the cached one
5126
5157
  * @returns {void}
5127
5158
  */
5128
5159
  sync: async (override = true, dontSyncCurrent = true) => {
5129
- const data = await this.QueueSaver.get(this.guildId);
5160
+ const data = await this.QueueSaver?.get(this.guildId);
5130
5161
  if (!data) throw new Error(`No data found to sync for guildId: ${this.guildId}`);
5131
5162
  if (!dontSyncCurrent && !this.current && this.managerUtils.isTrack(data.current)) this.current = data.current;
5132
5163
  if (Array.isArray(data.tracks) && data?.tracks.length && data.tracks.some((track) => this.managerUtils.isTrack(track) || this.managerUtils.isUnresolvedTrack(track))) this.tracks.splice(override ? 0 : this.tracks.length, override ? this.tracks.length : 0, ...data.tracks.filter((track) => this.managerUtils.isTrack(track) || this.managerUtils.isUnresolvedTrack(track)));
@@ -5134,7 +5165,7 @@ var Queue = class {
5134
5165
  await this.utils.save();
5135
5166
  },
5136
5167
  destroy: async () => {
5137
- return await this.QueueSaver.delete(this.guildId);
5168
+ return await this.QueueSaver?.delete(this.guildId);
5138
5169
  },
5139
5170
  /**
5140
5171
  * @returns {{current:Track|null, previous:Track[], tracks:Track[]}}The Queue, but in a raw State, which allows easier handling for the QueueStoreManager
@@ -5263,12 +5294,12 @@ var Queue = class {
5263
5294
  * @param {number} index Where to remove the Track
5264
5295
  * @param {number} amount How many Tracks to remove?
5265
5296
  * @param {Track | Track[]} TrackOrTracks Want to Add more Tracks?
5266
- * @returns {Track} Spliced Track
5297
+ * @returns {Track} Spliced (aka Removed) Track
5267
5298
  */
5268
5299
  async splice(index, amount, TrackOrTracks) {
5269
5300
  const oldStored = typeof this.queueChanges?.tracksAdd === "function" || typeof this.queueChanges?.tracksRemoved === "function" ? this.utils.toJSON() : null;
5270
5301
  if (!this.tracks.length) {
5271
- if (TrackOrTracks) return await this.add(TrackOrTracks);
5302
+ if (TrackOrTracks) await this.add(TrackOrTracks);
5272
5303
  return null;
5273
5304
  }
5274
5305
  if (TrackOrTracks && typeof this.queueChanges?.tracksAdd === "function") try {
@@ -5340,7 +5371,7 @@ var Queue = class {
5340
5371
  const tracksToRemove = this.tracks.map((v, i) => ({
5341
5372
  v,
5342
5373
  i
5343
- })).filter(({ v, i }) => removeQueryTrack.find((t) => typeof t === "number" && t === i || typeof t === "object" && (t.encoded && t.encoded === v.encoded || t.info?.identifier && t.info.identifier === v.info?.identifier || t.info?.uri && t.info.uri === v.info?.uri || t.info?.title && t.info.title === v.info?.title || t.info?.isrc && t.info.isrc === v.info?.isrc || t.info?.artworkUrl && t.info.artworkUrl === v.info?.artworkUrl)));
5374
+ })).filter(({ v, i }) => removeQueryTrack.find((t) => typeof t === "number" && t === i || typeof t === "object" && t !== null && tracksMatch(t, v)));
5344
5375
  if (!tracksToRemove.length) return null;
5345
5376
  const removed = [];
5346
5377
  tracksToRemove.sort((a, b) => b.i - a.i);
@@ -5351,7 +5382,7 @@ var Queue = class {
5351
5382
  await this.utils.save();
5352
5383
  return { removed };
5353
5384
  }
5354
- const toRemove = this.tracks.findIndex((v) => removeQueryTrack.encoded && removeQueryTrack.encoded === v.encoded || removeQueryTrack.info?.identifier && removeQueryTrack.info.identifier === v.info?.identifier || removeQueryTrack.info?.uri && removeQueryTrack.info.uri === v.info?.uri || removeQueryTrack.info?.title && removeQueryTrack.info.title === v.info?.title || removeQueryTrack.info?.isrc && removeQueryTrack.info.isrc === v.info?.isrc || removeQueryTrack.info?.artworkUrl && removeQueryTrack.info.artworkUrl === v.info?.artworkUrl);
5385
+ const toRemove = this.tracks.findIndex((v) => tracksMatch(removeQueryTrack, v));
5355
5386
  if (toRemove < 0) return null;
5356
5387
  const removed = this.tracks.splice(toRemove, 1);
5357
5388
  if (typeof this.queueChanges?.tracksRemoved === "function") try {
@@ -5722,7 +5753,9 @@ var Player = class {
5722
5753
  }
5723
5754
  if (options?.track?.encoded || options?.track?.identifier) {
5724
5755
  this.queue.current = options.clientTrack || null;
5725
- this.queue.utils.save();
5756
+ this.queue.utils.save().catch((error) => {
5757
+ if (!this.LavalinkManager.options.catchLifeCycleRejections) throw error;
5758
+ });
5726
5759
  if (typeof options?.volume === "number" && !isNaN(options?.volume)) {
5727
5760
  this.volume = Math.max(Math.min(options?.volume, 1e3), 0);
5728
5761
  let vol = Number(this.volume);
@@ -5991,7 +6024,12 @@ var Player = class {
5991
6024
  if (skipTo > this.queue.tracks.length) throw new RangeError("Can't skip more than the queue size");
5992
6025
  await this.queue.splice(0, skipTo - 1);
5993
6026
  }
5994
- if (!this.playing && !this.queue.current) return this.play(), this;
6027
+ if (!this.playing && !this.queue.current) {
6028
+ this.play().catch((error) => {
6029
+ if (!this.LavalinkManager.options.catchLifeCycleRejections) throw error;
6030
+ });
6031
+ return this;
6032
+ }
5995
6033
  const now = performance.now();
5996
6034
  this.setData("internal_skipped", true);
5997
6035
  await this.node.updatePlayer({
@@ -6202,6 +6240,7 @@ var Player = class {
6202
6240
  const currentTrack = this.queue.current;
6203
6241
  if (!this.voice.endpoint || !this.voice.sessionId || !this.voice.token) throw new Error("Voice Data is missing, can't change the node");
6204
6242
  this.setData("internal_nodeChanging", true);
6243
+ this.setData("internal_lastEmittedTrackId", void 0);
6205
6244
  if (this.node.connected) await this.node.destroyPlayer(this.guildId);
6206
6245
  this.node = updateNode;
6207
6246
  const now = performance.now();
@@ -6417,6 +6456,7 @@ var LavalinkManager = class LavalinkManager extends EventEmitter {
6417
6456
  linksAllowed: options?.linksAllowed ?? true,
6418
6457
  autoSkip: options?.autoSkip ?? true,
6419
6458
  autoSkipOnResolveError: options?.autoSkipOnResolveError ?? true,
6459
+ catchLifeCycleRejections: options?.catchLifeCycleRejections ?? false,
6420
6460
  emitNewSongsOnly: options?.emitNewSongsOnly ?? false,
6421
6461
  queueOptions: {
6422
6462
  maxPreviousTracks: options?.queueOptions?.maxPreviousTracks ?? 25,
@@ -6452,6 +6492,7 @@ var LavalinkManager = class LavalinkManager extends EventEmitter {
6452
6492
  if (typeof options?.sendToShard !== "function") throw new SyntaxError("ManagerOption.sendToShard was not provided, which is required!");
6453
6493
  if (options?.autoSkip && typeof options?.autoSkip !== "boolean") throw new SyntaxError("ManagerOption.autoSkip must be either false | true aka boolean");
6454
6494
  if (options?.autoSkipOnResolveError && typeof options?.autoSkipOnResolveError !== "boolean") throw new SyntaxError("ManagerOption.autoSkipOnResolveError must be either false | true aka boolean");
6495
+ if (options?.catchLifeCycleRejections && typeof options?.catchLifeCycleRejections !== "boolean") throw new SyntaxError("ManagerOption.catchLifeCycleRejections must be either false | true aka boolean");
6455
6496
  if (options?.emitNewSongsOnly && typeof options?.emitNewSongsOnly !== "boolean") throw new SyntaxError("ManagerOption.emitNewSongsOnly must be either false | true aka boolean");
6456
6497
  if (!options?.nodes || !Array.isArray(options?.nodes) || !options?.nodes.every((node) => node instanceof NodeLinkNode || node instanceof LavalinkNode || this.utils.isNodeOptions(node))) throw new SyntaxError("ManagerOption.nodes must be an Array of NodeOptions or the Node-Classes 'NodeLinkNode' or 'LavalinkNode' and is required of at least 1 Node");
6457
6498
  if (options?.queueOptions?.queueStore) {
@@ -6759,7 +6800,9 @@ var LavalinkManager = class LavalinkManager extends EventEmitter {
6759
6800
  const update = "d" in data ? data.d : data;
6760
6801
  if (!update.guild_id) return;
6761
6802
  const player = this.getPlayer(update.guild_id);
6762
- if (player && player.voiceChannelId === update.id) return void player.destroy("ChannelDeleted");
6803
+ if (player && player.voiceChannelId === update.id) return void player.destroy("ChannelDeleted").catch((error) => {
6804
+ if (!this.options.catchLifeCycleRejections) throw error;
6805
+ });
6763
6806
  }
6764
6807
  if (["VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE"].includes(data.t)) {
6765
6808
  const update = "d" in data ? data.d : data;
package/dist/index.mjs CHANGED
@@ -1218,8 +1218,8 @@ var ManagerUtils = class {
1218
1218
  * @returns
1219
1219
  */
1220
1220
  isTrack(data) {
1221
- if (!data) return false;
1222
- if (data[TrackSymbol] === true) return true;
1221
+ if (!data || typeof data !== "object") return false;
1222
+ if (Reflect.get(data, TrackSymbol) === true) return true;
1223
1223
  return typeof data?.encoded === "string" && typeof data?.info === "object" && !("resolve" in data);
1224
1224
  }
1225
1225
  /**
@@ -1228,7 +1228,7 @@ var ManagerUtils = class {
1228
1228
  */
1229
1229
  isUnresolvedTrack(data) {
1230
1230
  if (!data) return false;
1231
- if (data[UnresolvedTrackSymbol] === true) return true;
1231
+ if (Reflect.get(data, UnresolvedTrackSymbol) === true) return true;
1232
1232
  return typeof data === "object" && ("info" in data && typeof data.info.title === "string" || typeof data.encoded === "string") && "resolve" in data && typeof data.resolve === "function";
1233
1233
  }
1234
1234
  /**
@@ -1269,7 +1269,7 @@ var ManagerUtils = class {
1269
1269
  if (node._checkForSources && !node.info.sourceManagers?.length) throw new Error("Lavalink Node, has no sourceManagers enabled");
1270
1270
  if (!queryString.trim().length) throw new Error(`Query string is empty, please provide a valid query string.`);
1271
1271
  if (sourceString === "speak" && queryString.length > 100) throw new Error(`Query is speak, which is limited to 100 characters.`);
1272
- if (this.LavalinkManager.options?.linksBlacklist?.length > 0) {
1272
+ if (this.LavalinkManager?.options?.linksBlacklist && this.LavalinkManager.options?.linksBlacklist.length > 0) {
1273
1273
  if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) this.LavalinkManager.emit("debug", "ValidatingBlacklistLinks", {
1274
1274
  state: "log",
1275
1275
  message: `Validating Query against LavalinkManager.options.linksBlacklist, query: "${queryString}"`,
@@ -1278,9 +1278,9 @@ var ManagerUtils = class {
1278
1278
  if (this.LavalinkManager.options?.linksBlacklist.some((v) => typeof v === "string" && queryString.toLowerCase().includes(v.toLowerCase()) || isRegExp(v) && v.test(queryString))) throw new Error(`Query string contains a link / word which is blacklisted.`);
1279
1279
  }
1280
1280
  if (!/^https?:\/\//.test(queryString)) return;
1281
- else if (this.LavalinkManager.options?.linksAllowed === false) throw new Error("Using links to make a request is not allowed.");
1282
- if (this.LavalinkManager.options?.linksWhitelist?.length > 0) {
1283
- if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) this.LavalinkManager.emit("debug", "ValidatingWhitelistLinks", {
1281
+ else if (this.LavalinkManager?.options?.linksAllowed === false) throw new Error("Using links to make a request is not allowed.");
1282
+ if (this.LavalinkManager?.options?.linksWhitelist && this.LavalinkManager.options?.linksWhitelist.length > 0) {
1283
+ if (this.LavalinkManager?.options?.advancedOptions?.enableDebugEvents) this.LavalinkManager.emit("debug", "ValidatingWhitelistLinks", {
1284
1284
  state: "log",
1285
1285
  message: `Link was provided to the Query, validating against LavalinkManager.options.linksWhitelist, query: "${queryString}"`,
1286
1286
  functionLayer: "(LavalinkNode > node | player) > search() > validateQueryString()"
@@ -1404,7 +1404,7 @@ var ManagerUtils = class {
1404
1404
  validateSourceString(node, sourceString) {
1405
1405
  if (!sourceString) throw new Error(`No SourceString was provided`);
1406
1406
  const source = this.SourcesRecord[sourceString.toLowerCase().trim()];
1407
- if (!source && !!this.LavalinkManager.options.playerOptions.allowCustomSources) throw new Error(`Lavalink-Client does not support SearchQuerySource: '${sourceString}'. You can disable this check by setting 'ManagerOptions.PlayerOptions.allowCustomSources' to true`);
1407
+ if (!source && !this.LavalinkManager.options.playerOptions.allowCustomSources) throw new Error(`Lavalink-Client does not support SearchQuerySource: '${sourceString}'. You can disable this check by setting 'ManagerOptions.PlayerOptions.allowCustomSources' to true`);
1408
1408
  if (!node.info) throw new Error("Lavalink Node does not have any info cached yet, not ready yet!");
1409
1409
  if (!node._checkForSources) return;
1410
1410
  if (source === "amsearch" && !node.info?.sourceManagers?.includes("applemusic")) throw new Error("Lavalink Node has not 'applemusic' enabled, which is required to have 'amsearch' work");
@@ -1965,7 +1965,9 @@ var LavalinkNode = class LavalinkNode {
1965
1965
  this.sessionId = this.options.sessionId || sessionId;
1966
1966
  }
1967
1967
  this.socket = new WebSocket(`ws${this.options.secure ? "s" : ""}://${this.options.host}:${this.options.port}/v4/websocket`, { headers });
1968
- this.socket.on("open", this.open.bind(this));
1968
+ this.socket.on("open", () => void this.open().catch((error) => {
1969
+ this.error(error);
1970
+ }));
1969
1971
  this.socket.on("close", (code, reason) => void this.close(code, reason?.toString()).catch((error) => {
1970
1972
  this.NodeManager.emit("error", this, error);
1971
1973
  }));
@@ -2825,7 +2827,9 @@ var LavalinkNode = class LavalinkNode {
2825
2827
  const player = this._LManager.getPlayer(data.guildId);
2826
2828
  if (!player) return;
2827
2829
  if (typeof res?.voice?.connected === "boolean" && res.voice.connected === false) {
2828
- player.destroy("LavalinkNoVoice");
2830
+ player.destroy("LavalinkNoVoice").catch((error) => {
2831
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
2832
+ });
2829
2833
  return;
2830
2834
  }
2831
2835
  player.ping.ws = res?.voice?.ping || player?.ping.ws;
@@ -3124,11 +3128,12 @@ var LavalinkNode = class LavalinkNode {
3124
3128
  player.playing = true;
3125
3129
  player.paused = false;
3126
3130
  }
3127
- if (this._LManager.options?.emitNewSongsOnly === true && player.queue.previous[0]?.info?.identifier === track?.info?.identifier) return this._emitDebugEvent("TrackStartNewSongsOnly", {
3131
+ if (this._LManager.options?.emitNewSongsOnly === true && player.getData("internal_lastEmittedTrackId") === track?.info?.identifier) return this._emitDebugEvent("TrackStartNewSongsOnly", {
3128
3132
  state: "log",
3129
3133
  message: `TrackStart not Emitting, because playing the previous song again.`,
3130
3134
  functionLayer: "LavalinkNode > trackStart()"
3131
3135
  });
3136
+ player.setData("internal_lastEmittedTrackId", track?.info?.identifier);
3132
3137
  if (!player.queue.current) {
3133
3138
  player.queue.current = this.getTrackOfPayload(payload);
3134
3139
  if (player.queue.current) await player.queue.utils.save();
@@ -3150,6 +3155,7 @@ var LavalinkNode = class LavalinkNode {
3150
3155
  message: `TrackEnd Event does not handle any playback, because the track was replaced.`,
3151
3156
  functionLayer: "LavalinkNode > trackEnd()"
3152
3157
  });
3158
+ player.setData("internal_lastEmittedTrackId", void 0);
3153
3159
  this._LManager.emit("trackEnd", player, trackToUse, payload);
3154
3160
  return;
3155
3161
  }
@@ -3159,7 +3165,9 @@ var LavalinkNode = class LavalinkNode {
3159
3165
  await queueTrackEnd(player);
3160
3166
  if (!player.queue.current) return this.queueEnd(player, trackToUse, payload);
3161
3167
  this._LManager.emit("trackEnd", player, trackToUse, payload);
3162
- if (this._LManager.options.autoSkip && player.queue.current) player.play({ noReplace: true });
3168
+ if (this._LManager.options.autoSkip && player.queue.current) player.play({ noReplace: true }).catch((error) => {
3169
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3170
+ });
3163
3171
  return;
3164
3172
  }
3165
3173
  if (player.repeatMode !== "track" || player.getData("internal_skipped")) await queueTrackEnd(player);
@@ -3171,7 +3179,9 @@ var LavalinkNode = class LavalinkNode {
3171
3179
  if (!player.queue.current) return this.queueEnd(player, trackToUse, payload);
3172
3180
  player.setData("internal_skipped", false);
3173
3181
  this._LManager.emit("trackEnd", player, trackToUse, payload);
3174
- if (this._LManager.options.autoSkip && player.queue.current) player.play({ noReplace: true });
3182
+ if (this._LManager.options.autoSkip && player.queue.current) player.play({ noReplace: true }).catch((error) => {
3183
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3184
+ });
3175
3185
  }
3176
3186
  /** @private util function for handling trackStuck event */
3177
3187
  async trackStuck(player, track, payload) {
@@ -3184,7 +3194,9 @@ var LavalinkNode = class LavalinkNode {
3184
3194
  message: `trackStuck Event was triggered too often within a given threshold (LavalinkManager.options.playerOptions.maxErrorsPerTime). Threshold: "${this._LManager.options.playerOptions.maxErrorsPerTime?.threshold}ms", maxAmount: "${this._LManager.options.playerOptions.maxErrorsPerTime?.maxAmount}"`,
3185
3195
  functionLayer: "LavalinkNode > trackStuck()"
3186
3196
  });
3187
- player.destroy("TrackStuckMaxTracksErroredPerTime");
3197
+ player.destroy("TrackStuckMaxTracksErroredPerTime").catch((error) => {
3198
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3199
+ });
3188
3200
  return;
3189
3201
  }
3190
3202
  }
@@ -3203,6 +3215,8 @@ var LavalinkNode = class LavalinkNode {
3203
3215
  if (this._LManager.options.autoSkip && player.queue.current) player.play({
3204
3216
  track: player.queue.current,
3205
3217
  noReplace: false
3218
+ }).catch((error) => {
3219
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3206
3220
  });
3207
3221
  }
3208
3222
  /** @private util function for handling trackError event */
@@ -3216,7 +3230,9 @@ var LavalinkNode = class LavalinkNode {
3216
3230
  message: `TrackError Event was triggered too often within a given threshold (LavalinkManager.options.playerOptions.maxErrorsPerTime). Threshold: "${this._LManager.options.playerOptions.maxErrorsPerTime?.threshold}ms", maxAmount: "${this._LManager.options.playerOptions.maxErrorsPerTime?.maxAmount}"`,
3217
3231
  functionLayer: "LavalinkNode > trackError()"
3218
3232
  });
3219
- player.destroy("TrackErrorMaxTracksErroredPerTime");
3233
+ player.destroy("TrackErrorMaxTracksErroredPerTime").catch((error) => {
3234
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3235
+ });
3220
3236
  return;
3221
3237
  }
3222
3238
  }
@@ -3316,6 +3332,7 @@ var LavalinkNode = class LavalinkNode {
3316
3332
  player.queue.current = null;
3317
3333
  player.playing = false;
3318
3334
  player.setData("internal_stopPlaying", void 0);
3335
+ player.setData("internal_lastEmittedTrackId", void 0);
3319
3336
  this._emitDebugEvent("QueueEnded", {
3320
3337
  state: "log",
3321
3338
  message: `Queue Ended because no more Tracks were in the Queue, due to EventName: "${payload.type}"`,
@@ -3344,6 +3361,8 @@ var LavalinkNode = class LavalinkNode {
3344
3361
  if (this._LManager.options.autoSkip) return player.play({
3345
3362
  noReplace: true,
3346
3363
  paused: false
3364
+ }).catch((error) => {
3365
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3347
3366
  });
3348
3367
  } else this._emitDebugEvent("AutoplayThresholdSpamLimiter", {
3349
3368
  state: "warn",
@@ -3361,7 +3380,9 @@ var LavalinkNode = class LavalinkNode {
3361
3380
  if (payload?.reason !== "stopped") await player.queue.utils.save();
3362
3381
  if (typeof this._LManager.options.playerOptions?.onEmptyQueue?.destroyAfterMs === "number" && !isNaN(this._LManager.options.playerOptions.onEmptyQueue?.destroyAfterMs) && this._LManager.options.playerOptions.onEmptyQueue?.destroyAfterMs >= 0) {
3363
3382
  if (this._LManager.options.playerOptions.onEmptyQueue?.destroyAfterMs === 0) {
3364
- player.destroy("QueueEmpty");
3383
+ player.destroy("QueueEmpty").catch((error) => {
3384
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3385
+ });
3365
3386
  return;
3366
3387
  } else {
3367
3388
  this._emitDebugEvent("TriggerQueueEmptyInterval", {
@@ -3375,7 +3396,9 @@ var LavalinkNode = class LavalinkNode {
3375
3396
  player.setData("internal_queueempty", void 0);
3376
3397
  if (player.queue.current) return this._LManager.emit("playerQueueEmptyCancel", player);
3377
3398
  this._LManager.emit("playerQueueEmptyEnd", player);
3378
- player.destroy("QueueEmpty");
3399
+ player.destroy("QueueEmpty").catch((error) => {
3400
+ if (!this._LManager.options.catchLifeCycleRejections) throw error;
3401
+ });
3379
3402
  }, this._LManager.options.playerOptions.onEmptyQueue?.destroyAfterMs));
3380
3403
  }
3381
3404
  }
@@ -4989,6 +5012,14 @@ var FilterManager = class {
4989
5012
 
4990
5013
  //#endregion
4991
5014
  //#region src/structures/Queue.ts
5015
+ function tracksMatch(a, b) {
5016
+ if (a === b) return true;
5017
+ if (a.encoded) return a.encoded === b.encoded;
5018
+ if (a.info?.identifier) return a.info.identifier === b.info?.identifier;
5019
+ if (a.info?.uri) return a.info.uri === b.info?.uri;
5020
+ if (a.info?.isrc) return a.info.isrc === b.info?.isrc;
5021
+ return a.info?.title && a.info.title === b.info?.title || a.info?.artworkUrl && a.info.artworkUrl === b.info?.artworkUrl || false;
5022
+ }
4992
5023
  var QueueSaver = class {
4993
5024
  /**
4994
5025
  * The queue store manager
@@ -5119,14 +5150,14 @@ var Queue = class {
5119
5150
  */
5120
5151
  save: async () => {
5121
5152
  if (this.previous.length > this.options.maxPreviousTracks) this.previous.splice(this.options.maxPreviousTracks, this.previous.length);
5122
- return await this.QueueSaver.set(this.guildId, this.utils.toJSON());
5153
+ return await this.QueueSaver?.set(this.guildId, this.utils.toJSON());
5123
5154
  },
5124
5155
  /**
5125
5156
  * Sync the current queue database/server with the cached one
5126
5157
  * @returns {void}
5127
5158
  */
5128
5159
  sync: async (override = true, dontSyncCurrent = true) => {
5129
- const data = await this.QueueSaver.get(this.guildId);
5160
+ const data = await this.QueueSaver?.get(this.guildId);
5130
5161
  if (!data) throw new Error(`No data found to sync for guildId: ${this.guildId}`);
5131
5162
  if (!dontSyncCurrent && !this.current && this.managerUtils.isTrack(data.current)) this.current = data.current;
5132
5163
  if (Array.isArray(data.tracks) && data?.tracks.length && data.tracks.some((track) => this.managerUtils.isTrack(track) || this.managerUtils.isUnresolvedTrack(track))) this.tracks.splice(override ? 0 : this.tracks.length, override ? this.tracks.length : 0, ...data.tracks.filter((track) => this.managerUtils.isTrack(track) || this.managerUtils.isUnresolvedTrack(track)));
@@ -5134,7 +5165,7 @@ var Queue = class {
5134
5165
  await this.utils.save();
5135
5166
  },
5136
5167
  destroy: async () => {
5137
- return await this.QueueSaver.delete(this.guildId);
5168
+ return await this.QueueSaver?.delete(this.guildId);
5138
5169
  },
5139
5170
  /**
5140
5171
  * @returns {{current:Track|null, previous:Track[], tracks:Track[]}}The Queue, but in a raw State, which allows easier handling for the QueueStoreManager
@@ -5263,12 +5294,12 @@ var Queue = class {
5263
5294
  * @param {number} index Where to remove the Track
5264
5295
  * @param {number} amount How many Tracks to remove?
5265
5296
  * @param {Track | Track[]} TrackOrTracks Want to Add more Tracks?
5266
- * @returns {Track} Spliced Track
5297
+ * @returns {Track} Spliced (aka Removed) Track
5267
5298
  */
5268
5299
  async splice(index, amount, TrackOrTracks) {
5269
5300
  const oldStored = typeof this.queueChanges?.tracksAdd === "function" || typeof this.queueChanges?.tracksRemoved === "function" ? this.utils.toJSON() : null;
5270
5301
  if (!this.tracks.length) {
5271
- if (TrackOrTracks) return await this.add(TrackOrTracks);
5302
+ if (TrackOrTracks) await this.add(TrackOrTracks);
5272
5303
  return null;
5273
5304
  }
5274
5305
  if (TrackOrTracks && typeof this.queueChanges?.tracksAdd === "function") try {
@@ -5340,7 +5371,7 @@ var Queue = class {
5340
5371
  const tracksToRemove = this.tracks.map((v, i) => ({
5341
5372
  v,
5342
5373
  i
5343
- })).filter(({ v, i }) => removeQueryTrack.find((t) => typeof t === "number" && t === i || typeof t === "object" && (t.encoded && t.encoded === v.encoded || t.info?.identifier && t.info.identifier === v.info?.identifier || t.info?.uri && t.info.uri === v.info?.uri || t.info?.title && t.info.title === v.info?.title || t.info?.isrc && t.info.isrc === v.info?.isrc || t.info?.artworkUrl && t.info.artworkUrl === v.info?.artworkUrl)));
5374
+ })).filter(({ v, i }) => removeQueryTrack.find((t) => typeof t === "number" && t === i || typeof t === "object" && t !== null && tracksMatch(t, v)));
5344
5375
  if (!tracksToRemove.length) return null;
5345
5376
  const removed = [];
5346
5377
  tracksToRemove.sort((a, b) => b.i - a.i);
@@ -5351,7 +5382,7 @@ var Queue = class {
5351
5382
  await this.utils.save();
5352
5383
  return { removed };
5353
5384
  }
5354
- const toRemove = this.tracks.findIndex((v) => removeQueryTrack.encoded && removeQueryTrack.encoded === v.encoded || removeQueryTrack.info?.identifier && removeQueryTrack.info.identifier === v.info?.identifier || removeQueryTrack.info?.uri && removeQueryTrack.info.uri === v.info?.uri || removeQueryTrack.info?.title && removeQueryTrack.info.title === v.info?.title || removeQueryTrack.info?.isrc && removeQueryTrack.info.isrc === v.info?.isrc || removeQueryTrack.info?.artworkUrl && removeQueryTrack.info.artworkUrl === v.info?.artworkUrl);
5385
+ const toRemove = this.tracks.findIndex((v) => tracksMatch(removeQueryTrack, v));
5355
5386
  if (toRemove < 0) return null;
5356
5387
  const removed = this.tracks.splice(toRemove, 1);
5357
5388
  if (typeof this.queueChanges?.tracksRemoved === "function") try {
@@ -5722,7 +5753,9 @@ var Player = class {
5722
5753
  }
5723
5754
  if (options?.track?.encoded || options?.track?.identifier) {
5724
5755
  this.queue.current = options.clientTrack || null;
5725
- this.queue.utils.save();
5756
+ this.queue.utils.save().catch((error) => {
5757
+ if (!this.LavalinkManager.options.catchLifeCycleRejections) throw error;
5758
+ });
5726
5759
  if (typeof options?.volume === "number" && !isNaN(options?.volume)) {
5727
5760
  this.volume = Math.max(Math.min(options?.volume, 1e3), 0);
5728
5761
  let vol = Number(this.volume);
@@ -5991,7 +6024,12 @@ var Player = class {
5991
6024
  if (skipTo > this.queue.tracks.length) throw new RangeError("Can't skip more than the queue size");
5992
6025
  await this.queue.splice(0, skipTo - 1);
5993
6026
  }
5994
- if (!this.playing && !this.queue.current) return this.play(), this;
6027
+ if (!this.playing && !this.queue.current) {
6028
+ this.play().catch((error) => {
6029
+ if (!this.LavalinkManager.options.catchLifeCycleRejections) throw error;
6030
+ });
6031
+ return this;
6032
+ }
5995
6033
  const now = performance.now();
5996
6034
  this.setData("internal_skipped", true);
5997
6035
  await this.node.updatePlayer({
@@ -6202,6 +6240,7 @@ var Player = class {
6202
6240
  const currentTrack = this.queue.current;
6203
6241
  if (!this.voice.endpoint || !this.voice.sessionId || !this.voice.token) throw new Error("Voice Data is missing, can't change the node");
6204
6242
  this.setData("internal_nodeChanging", true);
6243
+ this.setData("internal_lastEmittedTrackId", void 0);
6205
6244
  if (this.node.connected) await this.node.destroyPlayer(this.guildId);
6206
6245
  this.node = updateNode;
6207
6246
  const now = performance.now();
@@ -6417,6 +6456,7 @@ var LavalinkManager = class LavalinkManager extends EventEmitter {
6417
6456
  linksAllowed: options?.linksAllowed ?? true,
6418
6457
  autoSkip: options?.autoSkip ?? true,
6419
6458
  autoSkipOnResolveError: options?.autoSkipOnResolveError ?? true,
6459
+ catchLifeCycleRejections: options?.catchLifeCycleRejections ?? false,
6420
6460
  emitNewSongsOnly: options?.emitNewSongsOnly ?? false,
6421
6461
  queueOptions: {
6422
6462
  maxPreviousTracks: options?.queueOptions?.maxPreviousTracks ?? 25,
@@ -6452,6 +6492,7 @@ var LavalinkManager = class LavalinkManager extends EventEmitter {
6452
6492
  if (typeof options?.sendToShard !== "function") throw new SyntaxError("ManagerOption.sendToShard was not provided, which is required!");
6453
6493
  if (options?.autoSkip && typeof options?.autoSkip !== "boolean") throw new SyntaxError("ManagerOption.autoSkip must be either false | true aka boolean");
6454
6494
  if (options?.autoSkipOnResolveError && typeof options?.autoSkipOnResolveError !== "boolean") throw new SyntaxError("ManagerOption.autoSkipOnResolveError must be either false | true aka boolean");
6495
+ if (options?.catchLifeCycleRejections && typeof options?.catchLifeCycleRejections !== "boolean") throw new SyntaxError("ManagerOption.catchLifeCycleRejections must be either false | true aka boolean");
6455
6496
  if (options?.emitNewSongsOnly && typeof options?.emitNewSongsOnly !== "boolean") throw new SyntaxError("ManagerOption.emitNewSongsOnly must be either false | true aka boolean");
6456
6497
  if (!options?.nodes || !Array.isArray(options?.nodes) || !options?.nodes.every((node) => node instanceof NodeLinkNode || node instanceof LavalinkNode || this.utils.isNodeOptions(node))) throw new SyntaxError("ManagerOption.nodes must be an Array of NodeOptions or the Node-Classes 'NodeLinkNode' or 'LavalinkNode' and is required of at least 1 Node");
6457
6498
  if (options?.queueOptions?.queueStore) {
@@ -6759,7 +6800,9 @@ var LavalinkManager = class LavalinkManager extends EventEmitter {
6759
6800
  const update = "d" in data ? data.d : data;
6760
6801
  if (!update.guild_id) return;
6761
6802
  const player = this.getPlayer(update.guild_id);
6762
- if (player && player.voiceChannelId === update.id) return void player.destroy("ChannelDeleted");
6803
+ if (player && player.voiceChannelId === update.id) return void player.destroy("ChannelDeleted").catch((error) => {
6804
+ if (!this.options.catchLifeCycleRejections) throw error;
6805
+ });
6763
6806
  }
6764
6807
  if (["VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE"].includes(data.t)) {
6765
6808
  const update = "d" in data ? data.d : data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavalink-client",
3
- "version": "2.11.0",
3
+ "version": "2.11.1",
4
4
  "description": "Easy, flexible and feature-rich lavalink@v4 Client. Both for Beginners and Proficients. - Supports NodeLink@v3 too.",
5
5
  "keywords": [
6
6
  "advanced",