lavalink-client 2.3.0 → 2.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +31 -0
  2. package/dist/cjs/index.d.ts +1 -0
  3. package/dist/cjs/index.js +1 -0
  4. package/dist/cjs/structures/Constants.d.ts +44 -1
  5. package/dist/cjs/structures/Constants.js +45 -1
  6. package/dist/cjs/structures/LavalinkManager.js +80 -5
  7. package/dist/cjs/structures/Node.js +202 -20
  8. package/dist/cjs/structures/Player.d.ts +0 -1
  9. package/dist/cjs/structures/Player.js +73 -6
  10. package/dist/cjs/structures/Queue.js +29 -2
  11. package/dist/cjs/structures/Types/Manager.d.ts +25 -0
  12. package/dist/cjs/structures/Types/Queue.d.ts +1 -1
  13. package/dist/cjs/structures/Utils.js +68 -18
  14. package/dist/esm/index.d.ts +1 -0
  15. package/dist/esm/index.js +1 -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/Player.d.ts +0 -1
  21. package/dist/esm/structures/Player.js +73 -6
  22. package/dist/esm/structures/Queue.js +29 -2
  23. package/dist/esm/structures/Types/Manager.d.ts +25 -0
  24. package/dist/esm/structures/Types/Queue.d.ts +1 -1
  25. package/dist/esm/structures/Utils.js +68 -18
  26. package/dist/types/index.d.ts +1 -0
  27. package/dist/types/structures/Constants.d.ts +44 -1
  28. package/dist/types/structures/Player.d.ts +0 -1
  29. package/dist/types/structures/Types/Manager.d.ts +25 -0
  30. package/dist/types/structures/Types/Queue.d.ts +1 -1
  31. package/package.json +1 -1
  32. package/dist/index.d.ts +0 -10
  33. package/dist/index.js +0 -13
  34. package/dist/structures/Filters.d.ts +0 -230
  35. package/dist/structures/Filters.js +0 -472
  36. package/dist/structures/LavalinkManager.d.ts +0 -47
  37. package/dist/structures/LavalinkManager.js +0 -36
  38. package/dist/structures/LavalinkManagerStatics.d.ts +0 -3
  39. package/dist/structures/LavalinkManagerStatics.js +0 -76
  40. package/dist/structures/Node.d.ts +0 -171
  41. package/dist/structures/Node.js +0 -462
  42. package/dist/structures/NodeManager.d.ts +0 -58
  43. package/dist/structures/NodeManager.js +0 -25
  44. package/dist/structures/Player.d.ts +0 -101
  45. package/dist/structures/Player.js +0 -232
  46. package/dist/structures/PlayerManager.d.ts +0 -62
  47. package/dist/structures/PlayerManager.js +0 -26
  48. package/dist/structures/Queue.d.ts +0 -93
  49. package/dist/structures/Queue.js +0 -160
  50. package/dist/structures/QueueManager.d.ts +0 -77
  51. package/dist/structures/QueueManager.js +0 -74
  52. package/dist/structures/Track.d.ts +0 -27
  53. package/dist/structures/Track.js +0 -2
  54. package/dist/structures/Utils.d.ts +0 -183
  55. package/dist/structures/Utils.js +0 -43
@@ -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);
@@ -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,6 +135,13 @@ export class Player {
125
135
  */
126
136
  async play(options = {}) {
127
137
  if (this.get("internal_queueempty")) {
138
+ if (typeof this.options.node === "string" && 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
  }
@@ -165,6 +182,13 @@ export class Player {
165
182
  ...(track.userData || {}),
166
183
  requester: this.LavalinkManager.utils.getTransformedRequester(options?.track?.requester || {})
167
184
  };
185
+ if (typeof this.options.node === "string" && this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
186
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerPlayWithTrackReplace, {
187
+ state: "log",
188
+ message: `Player was called to play something, with a specific track provided. Replacing the current Track and resolving the track on trackStart Event.`,
189
+ functionLayer: "Player > play()",
190
+ });
191
+ }
168
192
  return this.node.updatePlayer({
169
193
  guildId: this.guildId,
170
194
  noReplace: false,
@@ -182,6 +206,13 @@ export class Player {
182
206
  if (!this.queue.current && this.queue.tracks.length)
183
207
  await queueTrackEnd(this);
184
208
  if (this.queue.current && this.LavalinkManager.utils.isUnresolvedTrack(this.queue.current)) {
209
+ if (typeof this.options.node === "string" && this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
210
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerPlayUnresolvedTrack, {
211
+ state: "log",
212
+ message: `Player Play was called, current Queue Song is unresolved, resolving the track.`,
213
+ functionLayer: "Player > play()",
214
+ });
215
+ }
185
216
  try {
186
217
  // resolve the unresolved track
187
218
  await this.queue.current.resolve(this);
@@ -189,6 +220,14 @@ export class Player {
189
220
  this.queue.current.userData = { ...(this.queue.current?.userData || {}), ...(options.track?.userData || {}) };
190
221
  }
191
222
  catch (error) {
223
+ if (typeof this.options.node === "string" && this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
224
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerPlayUnresolvedTrackFailed, {
225
+ state: "error",
226
+ error: error,
227
+ message: `Player Play was called, current Queue Song is unresolved, but couldn't resolve it`,
228
+ functionLayer: "Player > play() > resolve currentTrack",
229
+ });
230
+ }
192
231
  this.LavalinkManager.emit("trackError", this, this.queue.current, error);
193
232
  if (options && "clientTrack" in options)
194
233
  delete options.clientTrack;
@@ -255,6 +294,13 @@ export class Player {
255
294
  : this.volume), 1000), 0));
256
295
  const now = performance.now();
257
296
  if (this.LavalinkManager.options.playerOptions.applyVolumeAsFilter) {
297
+ if (typeof this.options.node === "string" && this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
298
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerVolumeAsFilter, {
299
+ state: "log",
300
+ message: `Player Volume was set as a Filter, because LavalinkManager option "playerOptions.applyVolumeAsFilter" is true`,
301
+ functionLayer: "Player > setVolume()",
302
+ });
303
+ }
258
304
  await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { filters: { volume: this.lavalinkVolume / 100 } } });
259
305
  }
260
306
  else {
@@ -299,8 +345,16 @@ export class Player {
299
345
  */
300
346
  async search(query, requestUser, throwOnEmpty = false) {
301
347
  const Query = this.LavalinkManager.utils.transformQuery(query);
302
- if (["bcsearch", "bandcamp"].includes(Query.source) && !this.node.info.sourceManagers.includes("bandcamp"))
348
+ if (["bcsearch", "bandcamp"].includes(Query.source) && !this.node.info.sourceManagers.includes("bandcamp")) {
349
+ if (typeof this.options.node === "string" && this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
350
+ this.LavalinkManager.emit("debug", DebugEvents.BandcampSearchLokalEngine, {
351
+ state: "log",
352
+ message: `Player.search was called with a Bandcamp Query, but no bandcamp search was enabled on lavalink, searching with the custom Search Engine.`,
353
+ functionLayer: "Player > search()",
354
+ });
355
+ }
303
356
  return await bandCampSearch(this, Query.query, requestUser);
357
+ }
304
358
  return this.node.search(Query, requestUser, throwOnEmpty);
305
359
  }
306
360
  /**
@@ -359,7 +413,6 @@ export class Player {
359
413
  this.repeatMode = repeatMode;
360
414
  return this;
361
415
  }
362
- 1;
363
416
  /**
364
417
  * Skip the current song, or a specific amount of songs
365
418
  * @param amount provide the index of the next track to skip to
@@ -465,6 +518,13 @@ export class Player {
465
518
  if (this.LavalinkManager.options.advancedOptions?.debugOptions.playerDestroy.debugLog)
466
519
  console.log(`Lavalink-Client-Debug | PlayerDestroy [::] destroy Function, [guildId ${this.guildId}] - Destroy-Reason: ${String(reason)}`);
467
520
  if (this.get("internal_destroystatus") === true) {
521
+ if (this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
522
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerDestroyingSomewhereElse, {
523
+ state: "warn",
524
+ message: `Player is already destroying somewhere else..`,
525
+ functionLayer: "Player > destroy()",
526
+ });
527
+ }
468
528
  if (this.LavalinkManager.options.advancedOptions?.debugOptions.playerDestroy.debugLog)
469
529
  console.log(`Lavalink-Client-Debug | PlayerDestroy [::] destroy Function, [guildId ${this.guildId}] - Already destroying somewhere else..`);
470
530
  return;
@@ -496,10 +556,19 @@ export class Player {
496
556
  const updateNode = typeof newNode === "string" ? this.LavalinkManager.nodeManager.nodes.get(newNode) : newNode;
497
557
  if (!updateNode)
498
558
  throw new Error("Could not find the new Node");
559
+ if (typeof this.options.node === "string" && this.LavalinkManager.options?.advancedOptions?.enableDebugEvents) {
560
+ this.LavalinkManager.emit("debug", DebugEvents.PlayerChangeNode, {
561
+ state: "log",
562
+ message: `Player.changeNode() was executed, trying to change from "${this.node.id}" to "${updateNode.id}"`,
563
+ functionLayer: "Player > changeNode()",
564
+ });
565
+ }
499
566
  const data = this.toJSON();
567
+ const currentTrack = this.queue.current;
500
568
  await this.node.destroyPlayer(this.guildId);
501
569
  this.node = updateNode;
502
570
  const now = performance.now();
571
+ await this.connect();
503
572
  await this.node.updatePlayer({
504
573
  guildId: this.guildId,
505
574
  noReplace: false,
@@ -508,9 +577,7 @@ export class Player {
508
577
  volume: Math.round(Math.max(Math.min(data.volume, 1000), 0)),
509
578
  paused: data.paused,
510
579
  filters: { ...data.filters, equalizer: data.equalizer },
511
- voice: this.voice,
512
- track: this.queue.current ?? undefined
513
- // track: this.queue.current,
580
+ track: currentTrack ?? undefined
514
581
  },
515
582
  });
516
583
  this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
@@ -286,11 +286,18 @@ export class Queue {
286
286
  * ```
287
287
  */
288
288
  async remove(removeQueryTrack) {
289
+ const oldStored = typeof this.queueChanges?.tracksRemoved === "function" ? this.utils.toJSON() : null;
289
290
  if (typeof removeQueryTrack === "number") {
290
291
  const toRemove = this.tracks[removeQueryTrack];
291
292
  if (!toRemove)
292
293
  return null;
293
294
  const removed = this.tracks.splice(removeQueryTrack, 1);
295
+ // Log if available
296
+ if (typeof this.queueChanges?.tracksRemoved === "function")
297
+ try {
298
+ this.queueChanges.tracksRemoved(this.guildId, removed, removeQueryTrack, oldStored, this.utils.toJSON());
299
+ }
300
+ catch (e) { /* */ }
294
301
  await this.utils.save();
295
302
  return { removed };
296
303
  }
@@ -298,11 +305,18 @@ export class Queue {
298
305
  if (removeQueryTrack.every(v => typeof v === "number")) {
299
306
  const removed = [];
300
307
  for (const i of removeQueryTrack) {
301
- if (this.tracks[i])
308
+ if (this.tracks[i]) {
302
309
  removed.push(...this.tracks.splice(i, 1));
310
+ }
303
311
  }
304
312
  if (!removed.length)
305
313
  return null;
314
+ // Log if available
315
+ if (typeof this.queueChanges?.tracksRemoved === "function")
316
+ try {
317
+ this.queueChanges.tracksRemoved(this.guildId, removed, removeQueryTrack, oldStored, this.utils.toJSON());
318
+ }
319
+ catch (e) { /* */ }
306
320
  await this.utils.save();
307
321
  return { removed };
308
322
  }
@@ -317,9 +331,16 @@ export class Queue {
317
331
  return null;
318
332
  const removed = [];
319
333
  for (const { i } of tracksToRemove) {
320
- if (this.tracks[i])
334
+ if (this.tracks[i]) {
321
335
  removed.push(...this.tracks.splice(i, 1));
336
+ }
322
337
  }
338
+ // Log if available
339
+ if (typeof this.queueChanges?.tracksRemoved === "function")
340
+ try {
341
+ this.queueChanges.tracksRemoved(this.guildId, removed, tracksToRemove.map(v => v.i), oldStored, this.utils.toJSON());
342
+ }
343
+ catch (e) { /* */ }
323
344
  await this.utils.save();
324
345
  return { removed };
325
346
  }
@@ -332,6 +353,12 @@ export class Queue {
332
353
  if (toRemove < 0)
333
354
  return null;
334
355
  const removed = this.tracks.splice(toRemove, 1);
356
+ // Log if available
357
+ if (typeof this.queueChanges?.tracksRemoved === "function")
358
+ try {
359
+ this.queueChanges.tracksRemoved(this.guildId, removed, toRemove, oldStored, this.utils.toJSON());
360
+ }
361
+ catch (e) { /* */ }
335
362
  await this.utils.save();
336
363
  return { removed };
337
364
  }