lavalink-client 2.3.1 → 2.3.4

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.
Files changed (33) hide show
  1. package/README.md +35 -22
  2. package/dist/cjs/package.json +3 -0
  3. package/dist/cjs/structures/Constants.d.ts +44 -1
  4. package/dist/cjs/structures/Constants.js +45 -1
  5. package/dist/cjs/structures/LavalinkManager.js +80 -5
  6. package/dist/cjs/structures/Node.js +202 -20
  7. package/dist/cjs/structures/NodeManager.d.ts +1 -1
  8. package/dist/cjs/structures/NodeManager.js +2 -2
  9. package/dist/cjs/structures/Player.d.ts +0 -1
  10. package/dist/cjs/structures/Player.js +98 -8
  11. package/dist/cjs/structures/Queue.js +29 -2
  12. package/dist/cjs/structures/Types/Manager.d.ts +25 -0
  13. package/dist/cjs/structures/Types/Queue.d.ts +1 -1
  14. package/dist/cjs/structures/Utils.js +89 -23
  15. package/dist/esm/package.json +3 -0
  16. package/dist/esm/structures/Constants.d.ts +44 -1
  17. package/dist/esm/structures/Constants.js +44 -0
  18. package/dist/esm/structures/LavalinkManager.js +81 -6
  19. package/dist/esm/structures/Node.js +203 -21
  20. package/dist/esm/structures/NodeManager.d.ts +1 -1
  21. package/dist/esm/structures/NodeManager.js +1 -1
  22. package/dist/esm/structures/Player.d.ts +0 -1
  23. package/dist/esm/structures/Player.js +98 -8
  24. package/dist/esm/structures/Queue.js +29 -2
  25. package/dist/esm/structures/Types/Manager.d.ts +25 -0
  26. package/dist/esm/structures/Types/Queue.d.ts +1 -1
  27. package/dist/esm/structures/Utils.js +89 -23
  28. package/dist/types/structures/Constants.d.ts +44 -1
  29. package/dist/types/structures/NodeManager.d.ts +1 -1
  30. package/dist/types/structures/Player.d.ts +0 -1
  31. package/dist/types/structures/Types/Manager.d.ts +25 -0
  32. package/dist/types/structures/Types/Queue.d.ts +1 -1
  33. package/package.json +13 -6
@@ -1,6 +1,6 @@
1
1
  import { isAbsolute } from "path";
2
2
  import WebSocket from "ws";
3
- import { DestroyReasons, validSponsorBlocks } from "./Constants";
3
+ import { DebugEvents, DestroyReasons, validSponsorBlocks } from "./Constants";
4
4
  import { NodeSymbol, queueTrackEnd } from "./Utils";
5
5
  /**
6
6
  * Lavalink Node creator class
@@ -188,7 +188,8 @@ export class LavalinkNode {
188
188
  }
189
189
  let uri = `/loadtracks?identifier=`;
190
190
  if (/^https?:\/\//.test(Query.query) || ["http", "https", "link", "uri"].includes(Query.source)) { // if it's a link simply encode it
191
- uri += encodeURIComponent(Query.query);
191
+ const url = encodeURIComponent(Query.query);
192
+ uri += url;
192
193
  }
193
194
  else { // if not make a query out of it
194
195
  if (Query.source !== "local")
@@ -205,8 +206,16 @@ export class LavalinkNode {
205
206
  });
206
207
  // transform the data which can be Error, Track or Track[] to enfore [Track]
207
208
  const resTracks = res.loadType === "playlist" ? res.data?.tracks : res.loadType === "track" ? [res.data] : res.loadType === "search" ? Array.isArray(res.data) ? res.data : [res.data] : [];
208
- if (throwOnEmpty === true && (res.loadType === "empty" || !resTracks.length))
209
+ if (throwOnEmpty === true && (res.loadType === "empty" || !resTracks.length)) {
210
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
211
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.SearchNothingFound, {
212
+ state: "warn",
213
+ message: `Search found nothing for Request: "${Query.source ? `${Query.source}:` : ""}${Query.query}"`,
214
+ functionLayer: "(LavalinkNode > node | player) > search()",
215
+ });
216
+ }
209
217
  throw new Error("Nothing found");
218
+ }
210
219
  return {
211
220
  loadType: res.loadType,
212
221
  exception: res.loadType === "error" ? res.data : null,
@@ -250,8 +259,16 @@ export class LavalinkNode {
250
259
  throw new RangeError(`there is no lavasrc-plugin available in the lavalink node: ${this.id}`);
251
260
  const { request } = await this.rawRequest(`/loadsearch?query=${Query.source ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}${Query.types?.length ? `&types=${Query.types.join(",")}` : ""}`);
252
261
  const res = (request.status === 204 ? {} : await request.json());
253
- if (throwOnEmpty === true && !Object.entries(res).flat().filter(Boolean).length)
262
+ if (throwOnEmpty === true && !Object.entries(res).flat().filter(Boolean).length) {
263
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
264
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.LavaSearchNothingFound, {
265
+ state: "warn",
266
+ message: `LavaSearch found nothing for Request: "${Query.source ? `${Query.source}:` : ""}${Query.query}"`,
267
+ functionLayer: "(LavalinkNode > node | player) > lavaSearch()",
268
+ });
269
+ }
254
270
  throw new Error("Nothing found");
271
+ }
255
272
  return {
256
273
  tracks: res.tracks?.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) || [],
257
274
  albums: res.albums?.map(v => ({ info: v.info, pluginInfo: v?.plugin || v.pluginInfo, tracks: v.tracks.map(v => this.NodeManager.LavalinkManager.utils.buildTrack(v, requestUser)) })) || [],
@@ -287,6 +304,13 @@ export class LavalinkNode {
287
304
  r.path = url.pathname + url.search;
288
305
  }
289
306
  });
307
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
308
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.PlayerUpdateSuccess, {
309
+ state: "log",
310
+ message: `Player get's updated with following payload :: ${JSON.stringify(data.playerOptions, null, 3)}`,
311
+ functionLayer: "LavalinkNode > node > updatePlayer()",
312
+ });
313
+ }
290
314
  return this.syncPlayerData({}, res), res;
291
315
  }
292
316
  /**
@@ -319,8 +343,16 @@ export class LavalinkNode {
319
343
  * ```
320
344
  */
321
345
  connect(sessionId) {
322
- if (this.connected)
346
+ if (this.connected) {
347
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
348
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TryingConnectWhileConnected, {
349
+ state: "warn",
350
+ message: `Tryed to connect to node, but it's already connected!`,
351
+ functionLayer: "LavalinkNode > node > connect()",
352
+ });
353
+ }
323
354
  return;
355
+ }
324
356
  const headers = {
325
357
  Authorization: this.options.authorization,
326
358
  "User-Id": this.NodeManager.LavalinkManager.options.client.id,
@@ -338,11 +370,33 @@ export class LavalinkNode {
338
370
  // this.socket.on("ping", () => this.heartBeat("ping")); // lavalink doesn'T send ping periodically, therefore we use the stats message
339
371
  }
340
372
  heartBeat() {
373
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
374
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.HeartBeatTriggered, {
375
+ state: "log",
376
+ message: `Node Socket Heartbeat triggered, resetting old Timeout to 65000ms (should happen every 60s due to /stats event)`,
377
+ functionLayer: "LavalinkNode > nodeEvent > stats > heartBeat()",
378
+ });
379
+ }
341
380
  if (this.pingTimeout)
342
381
  clearTimeout(this.pingTimeout);
343
382
  this.pingTimeout = setTimeout(() => {
344
- if (!this.socket)
345
- return console.error("Node-Ping-Acknowledge-Timeout - Socket not available - maybe reconnecting?");
383
+ if (!this.socket) {
384
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
385
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.NoSocketOnDestroy, {
386
+ state: "error",
387
+ message: `Heartbeat registered a disconnect, but socket didn't exist therefore can't terminate`,
388
+ functionLayer: "LavalinkNode > nodeEvent > stats > heartBeat() > timeoutHit",
389
+ });
390
+ }
391
+ return;
392
+ }
393
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
394
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.SocketTerminateHeartBeatTimeout, {
395
+ state: "warn",
396
+ message: `Heartbeat registered a disconnect, because timeout wasn't resetted in time. Terminating Web-Socket`,
397
+ functionLayer: "LavalinkNode > nodeEvent > stats > heartBeat() > timeoutHit",
398
+ });
399
+ }
346
400
  this.isAlive = false;
347
401
  this.socket.terminate();
348
402
  }, 65000); // the stats endpoint get's sent every 60s. se wee add a 5s buffer to make sure we don't miss any stats message
@@ -834,8 +888,16 @@ export class LavalinkNode {
834
888
  case "playerUpdate":
835
889
  {
836
890
  const player = this.NodeManager.LavalinkManager.getPlayer(payload.guildId);
837
- if (!player)
891
+ if (!player) {
892
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
893
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.PlayerUpdateNoPlayer, {
894
+ state: "error",
895
+ message: `PlayerUpdate Event Triggered, but no player found of payload.guildId: ${payload.guildId}`,
896
+ functionLayer: "LavalinkNode > nodeEvent > playerUpdate",
897
+ });
898
+ }
838
899
  return;
900
+ }
839
901
  const oldPlayer = player?.toJSON();
840
902
  player.lastPositionChange = Date.now();
841
903
  player.lastPosition = payload.state.position || 0;
@@ -845,6 +907,13 @@ export class LavalinkNode {
845
907
  player.createdTimeStamp = payload.state.time;
846
908
  if (player.filterManager.filterUpdatedState === true && ((player.queue.current?.info?.duration || 0) <= (player.LavalinkManager.options.advancedOptions.maxFilterFixDuration || 600000) || isAbsolute(player.queue.current?.info?.uri))) {
847
909
  player.filterManager.filterUpdatedState = false;
910
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
911
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.PlayerUpdateFilterFixApply, {
912
+ state: "log",
913
+ message: `Fixing FilterState on "${player.guildId}" because player.options.instaUpdateFiltersFix === true`,
914
+ functionLayer: "LavalinkNode > nodeEvent > playerUpdate",
915
+ });
916
+ }
848
917
  await player.seek(player.position);
849
918
  }
850
919
  this.NodeManager.LavalinkManager.emit("playerUpdate", oldPlayer, player);
@@ -861,7 +930,14 @@ export class LavalinkNode {
861
930
  this.NodeManager.emit("resumed", this, payload, await this.fetchAllPlayers());
862
931
  }
863
932
  catch (e) {
864
- console.error("Failed to fetch players for resumed event, falling back without players array", e);
933
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
934
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.ResumingFetchingError, {
935
+ state: "error",
936
+ message: `Failed to fetch players for resumed event, falling back without players array`,
937
+ error: e,
938
+ functionLayer: "LavalinkNode > nodeEvent > resumed",
939
+ });
940
+ }
865
941
  this.NodeManager.emit("resumed", this, payload, []);
866
942
  }
867
943
  }
@@ -922,12 +998,30 @@ export class LavalinkNode {
922
998
  player.playing = true;
923
999
  player.paused = false;
924
1000
  // don't emit the event if previous track == new track aka track loop
925
- if (this.NodeManager.LavalinkManager.options?.emitNewSongsOnly === true && player.queue.previous[0]?.info?.identifier === track?.info?.identifier)
1001
+ if (this.NodeManager.LavalinkManager.options?.emitNewSongsOnly === true && player.queue.previous[0]?.info?.identifier === track?.info?.identifier) {
1002
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1003
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TrackStartNewSongsOnly, {
1004
+ state: "log",
1005
+ message: `TrackStart not Emitting, because playing the previous song again.`,
1006
+ functionLayer: "LavalinkNode > trackStart()",
1007
+ });
1008
+ }
926
1009
  return;
1010
+ }
927
1011
  if (!player.queue.current) {
928
1012
  player.queue.current = await this.getTrackOfPayload(payload);
929
- if (player.queue.current)
1013
+ if (player.queue.current) {
930
1014
  await player.queue.utils.save();
1015
+ }
1016
+ else {
1017
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1018
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TrackStartNoTrack, {
1019
+ state: "warn",
1020
+ message: `Trackstart emitted but there is no track on player.queue.current, trying to get the track of the payload failed too.`,
1021
+ functionLayer: "LavalinkNode > trackStart()",
1022
+ });
1023
+ }
1024
+ }
931
1025
  }
932
1026
  return this.NodeManager.LavalinkManager.emit("trackStart", player, player.queue.current, payload);
933
1027
  }
@@ -936,6 +1030,13 @@ export class LavalinkNode {
936
1030
  const trackToUse = track || await this.getTrackOfPayload(payload);
937
1031
  // If a track was forcibly played
938
1032
  if (payload.reason === "replaced") {
1033
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1034
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TrackEndReplaced, {
1035
+ state: "warn",
1036
+ message: `TrackEnd Event does not handle any playback, because the track was replaced.`,
1037
+ functionLayer: "LavalinkNode > trackEnd()",
1038
+ });
1039
+ }
939
1040
  return this.NodeManager.LavalinkManager.emit("trackEnd", player, trackToUse, payload);
940
1041
  }
941
1042
  // If there are no songs in the queue
@@ -961,10 +1062,10 @@ export class LavalinkNode {
961
1062
  player.queue.previous.splice(player.queue.options.maxPreviousTracks, player.queue.previous.length);
962
1063
  await player.queue.utils.save();
963
1064
  }
964
- player.set("internal_skipped", false);
965
1065
  // if no track available, end queue
966
1066
  if (!player.queue.current)
967
1067
  return this.queueEnd(player, trackToUse, payload);
1068
+ player.set("internal_skipped", false);
968
1069
  // fire event
969
1070
  this.NodeManager.LavalinkManager.emit("trackEnd", player, trackToUse, payload);
970
1071
  // play track if autoSkip is true
@@ -972,6 +1073,21 @@ export class LavalinkNode {
972
1073
  }
973
1074
  /** @private util function for handling trackStuck event */
974
1075
  async trackStuck(player, track, payload) {
1076
+ if (this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.threshold > 0 && this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.maxAmount >= 0) {
1077
+ const oldTimestamps = (player.get("internal_erroredTracksTimestamps") || [])
1078
+ .filter(v => Date.now() - v < this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.threshold);
1079
+ player.set("internal_erroredTracksTimestamps", [...oldTimestamps, Date.now()]);
1080
+ if (oldTimestamps.length > this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.maxAmount) {
1081
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1082
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TrackStuckMaxTracksErroredPerTime, {
1083
+ state: "log",
1084
+ message: `trackStuck Event was triggered too often within a given threshold (LavalinkManager.options.playerOptions.maxErrorsPerTime). Threshold: "${this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.threshold}ms", maxAmount: "${this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.maxAmount}"`,
1085
+ functionLayer: "LavalinkNode > trackStuck()",
1086
+ });
1087
+ }
1088
+ return player.destroy(DestroyReasons.TrackStuckMaxTracksErroredPerTime);
1089
+ }
1090
+ }
975
1091
  this.NodeManager.LavalinkManager.emit("trackStuck", player, track || await this.getTrackOfPayload(payload), payload);
976
1092
  // If there are no songs in the queue
977
1093
  if (!player.queue.tracks.length && (player.repeatMode === "off" || player.get("internal_stopPlaying")))
@@ -979,13 +1095,29 @@ export class LavalinkNode {
979
1095
  // remove the current track, and enqueue the next one
980
1096
  await queueTrackEnd(player);
981
1097
  // if no track available, end queue
982
- if (!player.queue.current)
1098
+ if (!player.queue.current) {
983
1099
  return this.queueEnd(player, track || await this.getTrackOfPayload(payload), payload);
1100
+ }
984
1101
  // play track if autoSkip is true
985
1102
  return (this.NodeManager.LavalinkManager.options.autoSkip && player.queue.current) && player.play({ noReplace: true });
986
1103
  }
987
1104
  /** @private util function for handling trackError event */
988
1105
  async trackError(player, track, payload) {
1106
+ if (this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.threshold > 0 && this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.maxAmount >= 0) {
1107
+ const oldTimestamps = (player.get("internal_erroredTracksTimestamps") || [])
1108
+ .filter(v => Date.now() - v < this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.threshold);
1109
+ player.set("internal_erroredTracksTimestamps", [...oldTimestamps, Date.now()]);
1110
+ if (oldTimestamps.length > this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.maxAmount) {
1111
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1112
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TrackErrorMaxTracksErroredPerTime, {
1113
+ state: "log",
1114
+ message: `TrackError Event was triggered too often within a given threshold (LavalinkManager.options.playerOptions.maxErrorsPerTime). Threshold: "${this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.threshold}ms", maxAmount: "${this.NodeManager.LavalinkManager.options.playerOptions.maxErrorsPerTime?.maxAmount}"`,
1115
+ functionLayer: "LavalinkNode > trackError()",
1116
+ });
1117
+ }
1118
+ return player.destroy(DestroyReasons.TrackErrorMaxTracksErroredPerTime);
1119
+ }
1120
+ }
989
1121
  this.NodeManager.LavalinkManager.emit("trackError", player, track || await this.getTrackOfPayload(payload), payload);
990
1122
  return; // get's handled by trackEnd
991
1123
  // If there are no songs in the queue
@@ -1069,6 +1201,13 @@ export class LavalinkNode {
1069
1201
  r.headers = { Authorization: this.options.authorization, 'Content-Type': 'application/json' };
1070
1202
  r.body = JSON.stringify(segments.map(v => v.toLowerCase()));
1071
1203
  });
1204
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1205
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.SetSponsorBlock, {
1206
+ state: "log",
1207
+ message: `SponsorBlock was set for Player: ${player.guildId} to: ${segments.map(v => `'${v.toLowerCase()}'`).join(", ")}`,
1208
+ functionLayer: "LavalinkNode > setSponsorBlock()",
1209
+ });
1210
+ }
1072
1211
  return;
1073
1212
  }
1074
1213
  /**
@@ -1090,6 +1229,13 @@ export class LavalinkNode {
1090
1229
  await this.request(`/sessions/${this.sessionId}/players/${player.guildId}/sponsorblock/categories`, (r) => {
1091
1230
  r.method = "DELETE";
1092
1231
  });
1232
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1233
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.DeleteSponsorBlock, {
1234
+ state: "log",
1235
+ message: `SponsorBlock was deleted for Player: ${player.guildId}`,
1236
+ functionLayer: "LavalinkNode > deleteSponsorBlock()",
1237
+ });
1238
+ }
1093
1239
  return;
1094
1240
  }
1095
1241
  /** private util function for handling the queue end event */
@@ -1098,17 +1244,46 @@ export class LavalinkNode {
1098
1244
  player.queue.current = null;
1099
1245
  player.playing = false;
1100
1246
  player.set("internal_stopPlaying", undefined);
1247
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1248
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.QueueEnded, {
1249
+ state: "log",
1250
+ message: `Queue Ended because no more Tracks were in the Queue, due to EventName: "${payload.type}"`,
1251
+ functionLayer: "LavalinkNode > queueEnd()",
1252
+ });
1253
+ }
1101
1254
  if (typeof this.NodeManager.LavalinkManager.options?.playerOptions?.onEmptyQueue?.autoPlayFunction === "function" && typeof player.get("internal_autoplayStopPlaying") === "undefined") {
1102
- await this.NodeManager.LavalinkManager.options?.playerOptions?.onEmptyQueue?.autoPlayFunction(player, track);
1103
- if (player.queue.tracks.length > 0)
1104
- await queueTrackEnd(player);
1105
- if (player.queue.current) {
1106
- if (payload.type === "TrackEndEvent")
1107
- this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
1108
- return player.play({ noReplace: true, paused: false });
1255
+ const previousAutoplayTime = player.get("internal_previousautoplay");
1256
+ const duration = previousAutoplayTime ? Date.now() - previousAutoplayTime : 0;
1257
+ if ((duration && duration > this.NodeManager.LavalinkManager.options.playerOptions.minAutoPlayMs) || !!player.get("internal_skipped")) {
1258
+ await this.NodeManager.LavalinkManager.options?.playerOptions?.onEmptyQueue?.autoPlayFunction(player, track);
1259
+ player.set("internal_previousautoplay", Date.now());
1260
+ if (player.queue.tracks.length > 0)
1261
+ await queueTrackEnd(player);
1262
+ else if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1263
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.AutoplayNoSongsAdded, {
1264
+ state: "warn",
1265
+ message: `Autoplay was triggered but no songs were added to the queue.`,
1266
+ functionLayer: "LavalinkNode > queueEnd() > autoplayFunction",
1267
+ });
1268
+ }
1269
+ if (player.queue.current) {
1270
+ if (payload.type === "TrackEndEvent")
1271
+ this.NodeManager.LavalinkManager.emit("trackEnd", player, track, payload);
1272
+ return player.play({ noReplace: true, paused: false });
1273
+ }
1274
+ }
1275
+ else {
1276
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1277
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.AutoplayThresholdSpamLimiter, {
1278
+ state: "warn",
1279
+ message: `Autoplay was triggered after the previousautoplay too early. Threshold is: ${this.NodeManager.LavalinkManager.options.playerOptions.minAutoPlayMs}ms and the Duration was ${duration}ms`,
1280
+ functionLayer: "LavalinkNode > queueEnd() > autoplayFunction",
1281
+ });
1282
+ }
1109
1283
  }
1110
1284
  }
1111
- player.set("internal_autoplayStopPlaying", undefined);
1285
+ player.set("internal_skipped", false);
1286
+ player.set("internal_autoplayStopPlaying", Date.now());
1112
1287
  if (track && !track?.pluginInfo?.clientData?.previousTrack) { // If there was a current Track already and repeatmode === true, add it to the queue.
1113
1288
  player.queue.previous.unshift(track);
1114
1289
  if (player.queue.previous.length > player.queue.options.maxPreviousTracks)
@@ -1122,6 +1297,13 @@ export class LavalinkNode {
1122
1297
  if (this.NodeManager.LavalinkManager.options.playerOptions.onEmptyQueue?.destroyAfterMs === 0)
1123
1298
  return player.destroy(DestroyReasons.QueueEmpty);
1124
1299
  else {
1300
+ if (this.NodeManager.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
1301
+ this.NodeManager.LavalinkManager.emit("debug", DebugEvents.TriggerQueueEmptyInterval, {
1302
+ state: "log",
1303
+ message: `Trigger Queue Empty Interval was Triggered because playerOptions.onEmptyQueue.destroyAfterMs is set to ${this.NodeManager.LavalinkManager.options.playerOptions.onEmptyQueue?.destroyAfterMs}ms`,
1304
+ functionLayer: "LavalinkNode > queueEnd() > destroyAfterMs",
1305
+ });
1306
+ }
1125
1307
  if (player.get("internal_queueempty")) {
1126
1308
  clearTimeout(player.get("internal_queueempty"));
1127
1309
  player.set("internal_queueempty", undefined);
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { EventEmitter } from "stream";
2
+ import { EventEmitter } from "events";
3
3
  import { LavalinkNode } from "./Node";
4
4
  import { MiniMap } from "./Utils";
5
5
  import type { LavalinkNodeIdentifier, LavalinkNodeOptions, NodeManagerEvents } from "./Types/Node";
@@ -1,4 +1,4 @@
1
- import { EventEmitter } from "stream";
1
+ import { EventEmitter } from "events";
2
2
  import { DestroyReasons } from "./Constants";
3
3
  import { LavalinkNode } from "./Node";
4
4
  import { MiniMap } from "./Utils";
@@ -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 { DebugEvents } from "./Constants";
1
2
  import { bandCampSearch } from "./CustomSearches/BandCampSearch";
2
3
  import { FilterManager } from "./Filters";
3
4
  import { Queue, QueueSaver } from "./Queue";
@@ -69,8 +70,17 @@ export class Player {
69
70
  this.guildId = this.options.guildId;
70
71
  this.voiceChannelId = this.options.voiceChannelId;
71
72
  this.textChannelId = this.options.textChannelId || null;
72
- this.node = typeof this.options.node === "string" ? this.LavalinkManager.nodeManager.nodes.get(this.options.node) : this.options.node;
73
+ this.node = typeof this.options.node === "string"
74
+ ? this.LavalinkManager.nodeManager.nodes.get(this.options.node)
75
+ : this.options.node;
73
76
  if (!this.node || typeof this.node.request !== "function") {
77
+ if (typeof this.options.node === "string" && this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
78
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerCreateNodeNotFound, {
79
+ state: "warn",
80
+ message: `Player was created with provided node Id: ${this.options.node}, but no node with that Id was found.`,
81
+ functionLayer: "Player > constructor()",
82
+ });
83
+ }
74
84
  const least = this.LavalinkManager.nodeManager.leastUsedNodes();
75
85
  this.node = least.filter(v => options.vcRegion ? v.options?.regions?.includes(options.vcRegion) : true)[0] || least[0] || null;
76
86
  }
@@ -125,13 +135,43 @@ export class Player {
125
135
  */
126
136
  async play(options = {}) {
127
137
  if (this.get("internal_queueempty")) {
138
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
139
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerPlayQueueEmptyTimeoutClear, {
140
+ state: "log",
141
+ message: `Player was called to play something, while there was a queueEmpty Timeout set, clearing the timeout.`,
142
+ functionLayer: "Player > play()",
143
+ });
144
+ }
128
145
  clearTimeout(this.get("internal_queueempty"));
129
146
  this.set("internal_queueempty", undefined);
130
147
  }
131
148
  // if clientTrack provided, override options.track object
132
149
  if (options?.clientTrack && (this.LavalinkManager.utils.isTrack(options?.clientTrack) || this.LavalinkManager.utils.isUnresolvedTrack(options.clientTrack))) {
133
- if (this.LavalinkManager.utils.isUnresolvedTrack(options.clientTrack))
134
- await options.clientTrack.resolve(this);
150
+ if (this.LavalinkManager.utils.isUnresolvedTrack(options.clientTrack)) {
151
+ try {
152
+ // resolve the unresolved track
153
+ await options.clientTrack.resolve(this);
154
+ }
155
+ catch (error) {
156
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
157
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerPlayUnresolvedTrackFailed, {
158
+ state: "error",
159
+ error: error,
160
+ message: `Player Play was called with clientTrack, Song is unresolved, but couldn't resolve it`,
161
+ functionLayer: "Player > play() > resolve currentTrack",
162
+ });
163
+ }
164
+ this.LavalinkManager.emit("trackError", this, this.queue.current, error);
165
+ if (options && "clientTrack" in options)
166
+ delete options.clientTrack;
167
+ if (options && "track" in options)
168
+ delete options.track;
169
+ // try to play the next track if possible
170
+ if (this.LavalinkManager.options?.autoSkipOnResolveError === true && this.queue.tracks[0])
171
+ return this.play(options);
172
+ return this;
173
+ }
174
+ }
135
175
  if ((typeof options.track?.userData === "object" || typeof options.clientTrack?.userData === "object") && options.clientTrack)
136
176
  options.clientTrack.userData = { ...(options?.clientTrack.userData || {}), ...(options.track?.userData || {}) };
137
177
  options.track = {
@@ -165,6 +205,13 @@ export class Player {
165
205
  ...(track.userData || {}),
166
206
  requester: this.LavalinkManager.utils.getTransformedRequester(options?.track?.requester || {})
167
207
  };
208
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
209
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerPlayWithTrackReplace, {
210
+ state: "log",
211
+ message: `Player was called to play something, with a specific track provided. Replacing the current Track and resolving the track on trackStart Event.`,
212
+ functionLayer: "Player > play()",
213
+ });
214
+ }
168
215
  return this.node.updatePlayer({
169
216
  guildId: this.guildId,
170
217
  noReplace: false,
@@ -182,6 +229,13 @@ export class Player {
182
229
  if (!this.queue.current && this.queue.tracks.length)
183
230
  await queueTrackEnd(this);
184
231
  if (this.queue.current && this.LavalinkManager.utils.isUnresolvedTrack(this.queue.current)) {
232
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
233
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerPlayUnresolvedTrack, {
234
+ state: "log",
235
+ message: `Player Play was called, current Queue Song is unresolved, resolving the track.`,
236
+ functionLayer: "Player > play()",
237
+ });
238
+ }
185
239
  try {
186
240
  // resolve the unresolved track
187
241
  await this.queue.current.resolve(this);
@@ -189,6 +243,14 @@ export class Player {
189
243
  this.queue.current.userData = { ...(this.queue.current?.userData || {}), ...(options.track?.userData || {}) };
190
244
  }
191
245
  catch (error) {
246
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
247
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerPlayUnresolvedTrackFailed, {
248
+ state: "error",
249
+ error: error,
250
+ message: `Player Play was called, current Queue Song is unresolved, but couldn't resolve it`,
251
+ functionLayer: "Player > play() > resolve currentTrack",
252
+ });
253
+ }
192
254
  this.LavalinkManager.emit("trackError", this, this.queue.current, error);
193
255
  if (options && "clientTrack" in options)
194
256
  delete options.clientTrack;
@@ -255,6 +317,13 @@ export class Player {
255
317
  : this.volume), 1000), 0));
256
318
  const now = performance.now();
257
319
  if (this.LavalinkManager.options.playerOptions.applyVolumeAsFilter) {
320
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
321
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerVolumeAsFilter, {
322
+ state: "log",
323
+ message: `Player Volume was set as a Filter, because LavalinkManager option "playerOptions.applyVolumeAsFilter" is true`,
324
+ functionLayer: "Player > setVolume()",
325
+ });
326
+ }
258
327
  await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { filters: { volume: this.lavalinkVolume / 100 } } });
259
328
  }
260
329
  else {
@@ -299,8 +368,16 @@ export class Player {
299
368
  */
300
369
  async search(query, requestUser, throwOnEmpty = false) {
301
370
  const Query = this.LavalinkManager.utils.transformQuery(query);
302
- if (["bcsearch", "bandcamp"].includes(Query.source) && !this.node.info.sourceManagers.includes("bandcamp"))
371
+ if (["bcsearch", "bandcamp"].includes(Query.source) && !this.node.info.sourceManagers.includes("bandcamp")) {
372
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
373
+ this.LavalinkManager.emit("debug", DebugEvents.BandcampSearchLokalEngine, {
374
+ state: "log",
375
+ message: `Player.search was called with a Bandcamp Query, but no bandcamp search was enabled on lavalink, searching with the custom Search Engine.`,
376
+ functionLayer: "Player > search()",
377
+ });
378
+ }
303
379
  return await bandCampSearch(this, Query.query, requestUser);
380
+ }
304
381
  return this.node.search(Query, requestUser, throwOnEmpty);
305
382
  }
306
383
  /**
@@ -359,7 +436,6 @@ export class Player {
359
436
  this.repeatMode = repeatMode;
360
437
  return this;
361
438
  }
362
- 1;
363
439
  /**
364
440
  * Skip the current song, or a specific amount of songs
365
441
  * @param amount provide the index of the next track to skip to
@@ -465,6 +541,13 @@ export class Player {
465
541
  if (this.LavalinkManager.options.advancedOptions?.debugOptions.playerDestroy.debugLog)
466
542
  console.log(`Lavalink-Client-Debug | PlayerDestroy [::] destroy Function, [guildId ${this.guildId}] - Destroy-Reason: ${String(reason)}`);
467
543
  if (this.get("internal_destroystatus") === true) {
544
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
545
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerDestroyingSomewhereElse, {
546
+ state: "warn",
547
+ message: `Player is already destroying somewhere else..`,
548
+ functionLayer: "Player > destroy()",
549
+ });
550
+ }
468
551
  if (this.LavalinkManager.options.advancedOptions?.debugOptions.playerDestroy.debugLog)
469
552
  console.log(`Lavalink-Client-Debug | PlayerDestroy [::] destroy Function, [guildId ${this.guildId}] - Already destroying somewhere else..`);
470
553
  return;
@@ -496,10 +579,19 @@ export class Player {
496
579
  const updateNode = typeof newNode === "string" ? this.LavalinkManager.nodeManager.nodes.get(newNode) : newNode;
497
580
  if (!updateNode)
498
581
  throw new Error("Could not find the new Node");
582
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
583
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerChangeNode, {
584
+ state: "log",
585
+ message: `Player.changeNode() was executed, trying to change from "${this.node.id}" to "${updateNode.id}"`,
586
+ functionLayer: "Player > changeNode()",
587
+ });
588
+ }
499
589
  const data = this.toJSON();
590
+ const currentTrack = this.queue.current;
500
591
  await this.node.destroyPlayer(this.guildId);
501
592
  this.node = updateNode;
502
593
  const now = performance.now();
594
+ await this.connect();
503
595
  await this.node.updatePlayer({
504
596
  guildId: this.guildId,
505
597
  noReplace: false,
@@ -508,9 +600,7 @@ export class Player {
508
600
  volume: Math.round(Math.max(Math.min(data.volume, 1000), 0)),
509
601
  paused: data.paused,
510
602
  filters: { ...data.filters, equalizer: data.equalizer },
511
- voice: this.voice,
512
- track: this.queue.current ?? undefined
513
- // track: this.queue.current,
603
+ track: currentTrack ?? undefined
514
604
  },
515
605
  });
516
606
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;