magmastream 2.9.3-dev.1 → 2.9.3-dev.10

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/dist/index.d.ts CHANGED
@@ -170,7 +170,9 @@ declare enum TrackPartial {
170
170
  /** The plugin info of the track */
171
171
  PluginInfo = "pluginInfo",
172
172
  /** The custom data of the track */
173
- CustomData = "customData"
173
+ CustomData = "customData",
174
+ /** Whether the track got autoplayed */
175
+ IsAutoPlay = "isAutoplay"
174
176
  }
175
177
  /**
176
178
  * Manager Event Types Enum
@@ -1189,6 +1191,8 @@ interface Track {
1189
1191
  pluginInfo: TrackPluginInfo;
1190
1192
  /** Add your own data to the track. */
1191
1193
  customData: Record<string, unknown>;
1194
+ /** If the track got added by autoplay. */
1195
+ readonly isAutoplay: boolean;
1192
1196
  }
1193
1197
  /**
1194
1198
  * Track Plugin Info
@@ -3462,18 +3466,30 @@ declare abstract class TrackUtils {
3462
3466
  static setTrackPartial(partial: TrackPartial[]): void;
3463
3467
  /**
3464
3468
  * Checks if the provided argument is a valid Track.
3465
- * If provided an array then every element will be checked.
3466
- * @param trackOrTracks The Track or array of Tracks to check.
3469
+ * @param value The value to check.
3467
3470
  * @returns {boolean} Whether the provided argument is a valid Track.
3468
3471
  */
3469
- static validate(trackOrTracks: unknown): boolean;
3472
+ static isTrack(track: unknown): track is Track;
3473
+ /**
3474
+ * Checks if the provided argument is a valid Track array.
3475
+ * @param value The value to check.
3476
+ * @returns {boolean} Whether the provided argument is a valid Track array.
3477
+ */
3478
+ static isTrackArray(value: unknown): value is Track[];
3479
+ /**
3480
+ * Checks if the provided argument is a valid Track or Track array.
3481
+ * @param value The value to check.
3482
+ * @returns {boolean} Whether the provided argument is a valid Track or Track array.
3483
+ */
3484
+ static validate(value: unknown): value is Track | Track[];
3470
3485
  /**
3471
3486
  * Builds a Track from the raw data from Lavalink and a optional requester.
3472
3487
  * @param data The raw data from Lavalink to build the Track from.
3473
3488
  * @param requester The user who requested the track, if any.
3489
+ * @param isAutoPlay Whether the track is autoplayed. Defaults to false.
3474
3490
  * @returns The built Track.
3475
3491
  */
3476
- static build<T = AnyUser>(data: TrackData, requester?: T): Track;
3492
+ static build<T = AnyUser>(data: TrackData, requester?: T, isAutoplay?: boolean): Track;
3477
3493
  /**
3478
3494
  * Validates a search result.
3479
3495
  * @param result The search result to validate.
@@ -56,8 +56,7 @@ class JsonQueue {
56
56
  this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[JSONQUEUE] Added ${tracks.length} track(s) to queue`);
57
57
  if (this.manager.players.has(this.guildId) && this.manager.players.get(this.guildId).isAutoplay) {
58
58
  if (!isArray) {
59
- const AutoplayUser = (await this.manager.players.get(this.guildId).get("Internal_AutoplayUser"));
60
- if (AutoplayUser && AutoplayUser.id === track.requester.id) {
59
+ if (track.isAutoplay) {
61
60
  this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
62
61
  changeType: Enums_1.PlayerStateEventTypes.QueueChange,
63
62
  details: {
@@ -84,8 +84,7 @@ class MemoryQueue extends Array {
84
84
  }
85
85
  if (this.manager.players.has(this.guildId) && this.manager.players.get(this.guildId).isAutoplay) {
86
86
  if (!isArray) {
87
- const AutoplayUser = this.manager.players.get(this.guildId).get("Internal_AutoplayUser");
88
- if (AutoplayUser && AutoplayUser.id === track.requester.id) {
87
+ if (track.isAutoplay) {
89
88
  this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
90
89
  changeType: Enums_1.PlayerStateEventTypes.QueueChange,
91
90
  details: {
@@ -81,8 +81,7 @@ class RedisQueue {
81
81
  // Autoplay logic
82
82
  if (this.manager.players.has(this.guildId) && this.manager.players.get(this.guildId).isAutoplay) {
83
83
  if (!Array.isArray(track)) {
84
- const AutoplayUser = (await this.manager.players.get(this.guildId).get("Internal_AutoplayUser"));
85
- if (AutoplayUser && AutoplayUser.id === track.requester.id) {
84
+ if (track.isAutoplay) {
86
85
  this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
87
86
  changeType: Enums_1.PlayerStateEventTypes.QueueChange,
88
87
  details: {
@@ -163,6 +163,8 @@ var TrackPartial;
163
163
  TrackPartial["PluginInfo"] = "pluginInfo";
164
164
  /** The custom data of the track */
165
165
  TrackPartial["CustomData"] = "customData";
166
+ /** Whether the track got autoplayed */
167
+ TrackPartial["IsAutoPlay"] = "isAutoplay";
166
168
  })(TrackPartial || (exports.TrackPartial = TrackPartial = {}));
167
169
  /**
168
170
  * Manager Event Types Enum
@@ -177,11 +177,15 @@ class Manager extends events_1.EventEmitter {
177
177
  db: config.db ?? 0,
178
178
  });
179
179
  }
180
- for (const node of this.nodes.values()) {
181
- try {
182
- await node.connect();
183
- }
184
- catch (err) {
180
+ const results = await Promise.allSettled([...this.nodes.values()].map(async (node) => {
181
+ await node.connect();
182
+ return node;
183
+ }));
184
+ for (let i = 0; i < results.length; i++) {
185
+ const result = results[i];
186
+ const node = [...this.nodes.values()][i];
187
+ if (result.status === "rejected") {
188
+ const err = result.reason;
185
189
  const error = err instanceof MagmastreamError_1.MagmaStreamError
186
190
  ? err
187
191
  : new MagmastreamError_1.MagmaStreamError({
@@ -277,9 +281,7 @@ class Manager extends events_1.EventEmitter {
277
281
  result.playlist.tracks = result.playlist.tracks.map(processTrack);
278
282
  }
279
283
  }
280
- const summary = "tracks" in result
281
- ? result.tracks.map((t) => Object.fromEntries(Object.entries(t).filter(([key]) => key !== "requester")))
282
- : [];
284
+ const summary = "tracks" in result ? result.tracks.map((t) => Object.fromEntries(Object.entries(t).filter(([key]) => key !== "requester"))) : [];
283
285
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Result search for ${_query.query}: ${Utils_1.JSONUtils.safe(summary, 2)}`);
284
286
  return result;
285
287
  }
@@ -468,7 +470,7 @@ class Manager extends events_1.EventEmitter {
468
470
  try {
469
471
  const redisKey = `${this.options.stateStorage.redisConfig.prefix?.endsWith(":")
470
472
  ? this.options.stateStorage.redisConfig.prefix
471
- : this.options.stateStorage.redisConfig.prefix ?? "magmastream:"}playerstore:${guildId}`;
473
+ : (this.options.stateStorage.redisConfig.prefix ?? "magmastream:")}playerstore:${guildId}`;
472
474
  await this.redis.set(redisKey, JSON.stringify(serializedPlayer));
473
475
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Player state saved to Redis: ${guildId}`);
474
476
  }
@@ -547,6 +549,7 @@ class Manager extends events_1.EventEmitter {
547
549
  volume: lavaPlayer.volume || state.options.volume,
548
550
  nodeIdentifier: nodeId,
549
551
  applyVolumeAsFilter: state.options.applyVolumeAsFilter,
552
+ pauseOnDisconnect: state.options.pauseOnDisconnect,
550
553
  };
551
554
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${state.guildId} from saved file: ${Utils_1.JSONUtils.safe(state.options, 2)}`);
552
555
  const player = this.create(playerOptions);
@@ -578,7 +581,7 @@ class Manager extends events_1.EventEmitter {
578
581
  if (lavaPlayer.track) {
579
582
  await player.queue.clear();
580
583
  if (currentTrack) {
581
- await player.queue.add(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester));
584
+ await player.queue.add(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester, currentTrack.isAutoplay));
582
585
  }
583
586
  const remainingQueue = queueTracks.filter((t) => t.uri !== lavaPlayer.track.info.uri);
584
587
  if (remainingQueue.length > 0) {
@@ -721,7 +724,7 @@ class Manager extends events_1.EventEmitter {
721
724
  // Get all keys matching our pattern
722
725
  const redisKeyPattern = `${this.options.stateStorage.redisConfig.prefix?.endsWith(":")
723
726
  ? this.options.stateStorage.redisConfig.prefix
724
- : this.options.stateStorage.redisConfig.prefix ?? "magmastream:"}playerstore:*`;
727
+ : (this.options.stateStorage.redisConfig.prefix ?? "magmastream:")}playerstore:*`;
725
728
  const keys = await this.redis.keys(redisKeyPattern);
726
729
  for (const key of keys) {
727
730
  try {
@@ -746,6 +749,7 @@ class Manager extends events_1.EventEmitter {
746
749
  volume: lavaPlayer.volume || state.options.volume,
747
750
  nodeIdentifier: nodeId,
748
751
  applyVolumeAsFilter: state.options.applyVolumeAsFilter,
752
+ pauseOnDisconnect: state.options.pauseOnDisconnect,
749
753
  };
750
754
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${guildId} from Redis`);
751
755
  const player = this.create(playerOptions);
@@ -772,7 +776,7 @@ class Manager extends events_1.EventEmitter {
772
776
  if (lavaPlayer.track) {
773
777
  await player.queue.clear();
774
778
  if (currentTrack) {
775
- await player.queue.add(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester));
779
+ await player.queue.add(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester, currentTrack.isAutoplay));
776
780
  }
777
781
  const remainingQueue = queueTracks.filter((t) => t.uri !== lavaPlayer.track.info.uri);
778
782
  if (remainingQueue.length > 0) {
@@ -914,11 +918,7 @@ class Manager extends events_1.EventEmitter {
914
918
  * @returns {Node} The node to use.
915
919
  */
916
920
  get useableNode() {
917
- return this.options.enablePriorityMode
918
- ? this.priorityNode
919
- : this.options.useNode === Enums_1.UseNodeOptions.LeastLoad
920
- ? this.leastLoadNode.first()
921
- : this.leastPlayersNode.first();
921
+ return this.options.enablePriorityMode ? this.priorityNode : this.options.useNode === Enums_1.UseNodeOptions.LeastLoad ? this.leastLoadNode.first() : this.leastPlayersNode.first();
922
922
  }
923
923
  /**
924
924
  * Handles the shutdown of the process by saving all active players' states and optionally cleaning up inactive players.
@@ -1105,6 +1105,7 @@ class Manager extends events_1.EventEmitter {
1105
1105
  }
1106
1106
  this.emit(Enums_1.ManagerEventTypes.PlayerDisconnect, player, player.voiceChannelId);
1107
1107
  player.voiceChannelId = null;
1108
+ player.state = Enums_1.StateTypes.Disconnected;
1108
1109
  player.voiceState = Object.assign({});
1109
1110
  if (player.options.pauseOnDisconnect) {
1110
1111
  await player.pause(true);
@@ -1154,7 +1155,7 @@ class Manager extends events_1.EventEmitter {
1154
1155
  {
1155
1156
  const prefix = this.options.stateStorage.redisConfig.prefix?.endsWith(":")
1156
1157
  ? this.options.stateStorage.redisConfig.prefix
1157
- : this.options.stateStorage.redisConfig.prefix ?? "magmastream:";
1158
+ : (this.options.stateStorage.redisConfig.prefix ?? "magmastream:");
1158
1159
  const pattern = `${prefix}queue:*:current`;
1159
1160
  try {
1160
1161
  const stream = this.redis.scanStream({
@@ -1339,7 +1340,7 @@ class Manager extends events_1.EventEmitter {
1339
1340
  case Enums_1.StateStorageType.Redis: {
1340
1341
  const prefix = this.options.stateStorage.redisConfig.prefix?.endsWith(":")
1341
1342
  ? this.options.stateStorage.redisConfig.prefix
1342
- : this.options.stateStorage.redisConfig.prefix ?? "magmastream:";
1343
+ : (this.options.stateStorage.redisConfig.prefix ?? "magmastream:");
1343
1344
  const patterns = [`${prefix}playerstore:*`, `${prefix}queue:*`];
1344
1345
  try {
1345
1346
  for (const pattern of patterns) {
@@ -108,7 +108,7 @@ class Node {
108
108
  case Enums_1.StateStorageType.Redis:
109
109
  this.redisPrefix = this.manager.options.stateStorage.redisConfig.prefix?.endsWith(":")
110
110
  ? this.manager.options.stateStorage.redisConfig.prefix
111
- : this.manager.options.stateStorage.redisConfig.prefix ?? "magmastream:";
111
+ : (this.manager.options.stateStorage.redisConfig.prefix ?? "magmastream:");
112
112
  break;
113
113
  }
114
114
  }
@@ -632,14 +632,13 @@ class Node {
632
632
  player.playing = true;
633
633
  player.paused = false;
634
634
  this.manager.emit(Enums_1.ManagerEventTypes.TrackStart, player, track, payload);
635
- const AutoplayUser = player.get("Internal_AutoplayUser");
636
- if (AutoplayUser && AutoplayUser.id === track.requester.id) {
635
+ if (track.isAutoplay) {
637
636
  this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
638
637
  changeType: Enums_1.PlayerStateEventTypes.TrackChange,
639
638
  details: {
640
639
  type: "track",
641
640
  action: "autoPlay",
642
- track: track,
641
+ track,
643
642
  },
644
643
  });
645
644
  return;
@@ -649,7 +648,7 @@ class Node {
649
648
  details: {
650
649
  type: "track",
651
650
  action: "start",
652
- track: track,
651
+ track,
653
652
  },
654
653
  });
655
654
  }
@@ -260,6 +260,9 @@ class Player {
260
260
  */
261
261
  async destroy(disconnect = true) {
262
262
  this.state = Enums_1.StateTypes.Destroying;
263
+ await this.queue.clear();
264
+ await this.queue.clearPrevious();
265
+ await this.queue.setCurrent(null);
263
266
  if (disconnect) {
264
267
  await this.disconnect().catch((err) => {
265
268
  console.warn(`[Player#destroy] Failed to disconnect player ${this.guildId}:`, err);
@@ -268,9 +271,6 @@ class Player {
268
271
  await this.node.rest.destroyPlayer(this.guildId).catch((err) => {
269
272
  console.warn(`[Player#destroy] REST failed to destroy player ${this.guildId}:`, err);
270
273
  });
271
- await this.queue.clear();
272
- await this.queue.clearPrevious();
273
- await this.queue.setCurrent(null);
274
274
  this.manager.emit(Enums_1.ManagerEventTypes.PlayerDestroy, this);
275
275
  const deleted = this.manager.players.delete(this.guildId);
276
276
  if (this.manager.options.stateStorage.deleteInactivePlayers)
@@ -806,11 +806,8 @@ class Player {
806
806
  if (currentPlayingTrack) {
807
807
  await this.queue.add(currentPlayingTrack, 0);
808
808
  }
809
- await this.play(lastTrack);
810
- }
811
- else {
812
- await this.play(lastTrack);
813
809
  }
810
+ await this.play(lastTrack);
814
811
  this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
815
812
  changeType: Enums_1.PlayerStateEventTypes.TrackChange,
816
813
  details: {
@@ -1005,6 +1002,7 @@ class Player {
1005
1002
  nowPlayingMessage: this.nowPlayingMessage,
1006
1003
  isAutoplay: this.isAutoplay,
1007
1004
  applyVolumeAsFilter: this.options.applyVolumeAsFilter,
1005
+ pauseOnDisconnect: this.options.pauseOnDisconnect,
1008
1006
  };
1009
1007
  // If force is true, destroy the existing player for the new guild
1010
1008
  if (force && newPlayer) {
@@ -1015,6 +1013,7 @@ class Player {
1015
1013
  newOptions.selfMute = newOptions.selfMute ?? oldPlayerProperties.selfMute;
1016
1014
  newOptions.volume = newOptions.volume ?? oldPlayerProperties.volume;
1017
1015
  newOptions.applyVolumeAsFilter = newOptions.applyVolumeAsFilter ?? oldPlayerProperties.applyVolumeAsFilter;
1016
+ newOptions.pauseOnDisconnect = newOptions.pauseOnDisconnect ?? oldPlayerProperties.pauseOnDisconnect;
1018
1017
  // Deep clone the current player
1019
1018
  const clonedPlayer = this.manager.create(newOptions);
1020
1019
  // Connect the cloned player to the new voice channel
@@ -104,35 +104,55 @@ class Rest {
104
104
  },
105
105
  data: body,
106
106
  timeout: this.node.options.apiRequestTimeoutMs,
107
+ validateStatus: () => true,
107
108
  };
109
+ let response;
108
110
  try {
109
- const response = await (0, axios_1.default)(config);
110
- return response.data;
111
+ response = await (0, axios_1.default)(config);
111
112
  }
112
113
  catch (err) {
113
- const error = err;
114
- if (!error.response) {
115
- throw new MagmastreamError_1.MagmaStreamError({
116
- code: Enums_1.MagmaStreamErrorCode.REST_REQUEST_FAILED,
117
- message: `No response from node ${this.node.options.identifier}: ${error.message}`,
118
- });
119
- }
120
- const data = error.response.data;
121
- if (data?.message === "Guild not found") {
122
- return [];
123
- }
124
- if (error.response.status === 401) {
125
- throw new MagmastreamError_1.MagmaStreamError({
126
- code: Enums_1.MagmaStreamErrorCode.REST_UNAUTHORIZED,
127
- message: `Unauthorized access to node ${this.node.options.identifier}`,
128
- });
129
- }
130
- const dataMessage = typeof data === "string" ? data : data?.message ? data.message : Utils_1.JSONUtils.safe(data, 2);
114
+ const message = err instanceof Error ? err.message : "Unknown error";
131
115
  throw new MagmastreamError_1.MagmaStreamError({
132
116
  code: Enums_1.MagmaStreamErrorCode.REST_REQUEST_FAILED,
133
- message: `Request to node ${this.node.options.identifier} failed with status ${error.response.status}: ${dataMessage}`,
117
+ message: `No response from node ${this.node.options.identifier}: ${message}`,
134
118
  });
135
119
  }
120
+ const { status, data } = response;
121
+ if (status >= 200 && status < 300) {
122
+ return data;
123
+ }
124
+ // Lavalink sometimes returns "Guild not found" for inactive players
125
+ if (status === 404 && data?.message === "Guild not found") {
126
+ return [];
127
+ }
128
+ if (status === 401) {
129
+ throw new MagmastreamError_1.MagmaStreamError({
130
+ code: Enums_1.MagmaStreamErrorCode.REST_UNAUTHORIZED,
131
+ message: `Unauthorized access to node ${this.node.options.identifier}`,
132
+ });
133
+ }
134
+ if (status >= 400 && status < 500) {
135
+ const message = typeof data === "string"
136
+ ? data
137
+ : typeof data === "object" && data !== null && "message" in data && typeof data.message === "string"
138
+ ? data.message
139
+ : "Unknown client error";
140
+ return {
141
+ status,
142
+ error: true,
143
+ message,
144
+ data,
145
+ };
146
+ }
147
+ const safeMessage = typeof data === "string"
148
+ ? data
149
+ : typeof data === "object" && data !== null && "message" in data && typeof data.message === "string"
150
+ ? data.message
151
+ : Utils_1.JSONUtils.safe(data, 2);
152
+ throw new MagmastreamError_1.MagmaStreamError({
153
+ code: Enums_1.MagmaStreamErrorCode.REST_REQUEST_FAILED,
154
+ message: `Request to node ${this.node.options.identifier} failed (${status}): ${safeMessage}`,
155
+ });
136
156
  }
137
157
  /**
138
158
  * Sends a GET request to the specified endpoint and returns the response data.
@@ -9,9 +9,11 @@ const Enums_1 = require("./Enums");
9
9
  const path_1 = tslib_1.__importDefault(require("path"));
10
10
  const safe_stable_stringify_1 = tslib_1.__importDefault(require("safe-stable-stringify"));
11
11
  const MagmastreamError_1 = require("./MagmastreamError");
12
+ const lodash_1 = require("lodash");
12
13
  // import playwright from "playwright";
13
14
  /** @hidden */
14
15
  const SIZES = ["0", "1", "2", "3", "default", "mqdefault", "hqdefault", "maxresdefault"];
16
+ const REQUIRED_TRACK_KEYS = ["track", "title", "uri"];
15
17
  class TrackUtils {
16
18
  static trackPartial = null;
17
19
  static manager;
@@ -39,19 +41,12 @@ class TrackUtils {
39
41
  const defaultProperties = [
40
42
  Enums_1.TrackPartial.Track,
41
43
  Enums_1.TrackPartial.Title,
42
- Enums_1.TrackPartial.Identifier,
43
44
  Enums_1.TrackPartial.Author,
44
45
  Enums_1.TrackPartial.Duration,
45
- Enums_1.TrackPartial.Isrc,
46
- Enums_1.TrackPartial.IsSeekable,
47
- Enums_1.TrackPartial.IsStream,
48
46
  Enums_1.TrackPartial.Uri,
49
- Enums_1.TrackPartial.ArtworkUrl,
50
47
  Enums_1.TrackPartial.SourceName,
51
- Enums_1.TrackPartial.ThumbNail,
48
+ Enums_1.TrackPartial.ArtworkUrl,
52
49
  Enums_1.TrackPartial.Requester,
53
- Enums_1.TrackPartial.PluginInfo,
54
- Enums_1.TrackPartial.CustomData,
55
50
  ];
56
51
  /** The array of property names that will be removed from the Track class */
57
52
  this.trackPartial = Array.from(new Set([...defaultProperties, ...partial]));
@@ -61,33 +56,39 @@ class TrackUtils {
61
56
  }
62
57
  /**
63
58
  * Checks if the provided argument is a valid Track.
64
- * If provided an array then every element will be checked.
65
- * @param trackOrTracks The Track or array of Tracks to check.
59
+ * @param value The value to check.
66
60
  * @returns {boolean} Whether the provided argument is a valid Track.
67
61
  */
68
- static validate(trackOrTracks) {
69
- if (typeof trackOrTracks !== "object" || trackOrTracks === null) {
62
+ static isTrack(track) {
63
+ if (typeof track !== "object" || track === null)
70
64
  return false;
71
- }
72
- const isValidTrack = (track) => {
73
- if (typeof track !== "object" || track === null) {
74
- return false;
75
- }
76
- const t = track;
77
- return (typeof t.track === "string" && typeof t.title === "string" && typeof t.identifier === "string" && typeof t.isrc === "string" && typeof t.uri === "string");
78
- };
79
- if (Array.isArray(trackOrTracks)) {
80
- return trackOrTracks.every(isValidTrack);
81
- }
82
- return isValidTrack(trackOrTracks);
65
+ const t = track;
66
+ return REQUIRED_TRACK_KEYS.every((key) => typeof t[key] === "string");
67
+ }
68
+ /**
69
+ * Checks if the provided argument is a valid Track array.
70
+ * @param value The value to check.
71
+ * @returns {boolean} Whether the provided argument is a valid Track array.
72
+ */
73
+ static isTrackArray(value) {
74
+ return Array.isArray(value) && value.every(this.isTrack);
75
+ }
76
+ /**
77
+ * Checks if the provided argument is a valid Track or Track array.
78
+ * @param value The value to check.
79
+ * @returns {boolean} Whether the provided argument is a valid Track or Track array.
80
+ */
81
+ static validate(value) {
82
+ return this.isTrack(value) || this.isTrackArray(value);
83
83
  }
84
84
  /**
85
85
  * Builds a Track from the raw data from Lavalink and a optional requester.
86
86
  * @param data The raw data from Lavalink to build the Track from.
87
87
  * @param requester The user who requested the track, if any.
88
+ * @param isAutoPlay Whether the track is autoplayed. Defaults to false.
88
89
  * @returns The built Track.
89
90
  */
90
- static build(data, requester) {
91
+ static build(data, requester, isAutoplay = false) {
91
92
  if (typeof data === "undefined") {
92
93
  throw new MagmastreamError_1.MagmaStreamError({
93
94
  code: Enums_1.MagmaStreamErrorCode.UTILS_TRACK_BUILD_FAILED,
@@ -138,6 +139,7 @@ class TrackUtils {
138
139
  requester: requester,
139
140
  pluginInfo: data.pluginInfo,
140
141
  customData: {},
142
+ isAutoplay: isAutoplay,
141
143
  };
142
144
  track.displayThumbnail = track.displayThumbnail.bind(track);
143
145
  if (this.trackPartial) {
@@ -542,8 +544,7 @@ class AutoPlayUtils {
542
544
  return typeof data === "object" && data !== null && "encoded" in data && "info" in data;
543
545
  }
544
546
  static isTrackDataArray(data) {
545
- return (Array.isArray(data) &&
546
- data.every((track) => typeof track === "object" && track !== null && "encoded" in track && "info" in track && typeof track.encoded === "string"));
547
+ return (Array.isArray(data) && data.every((track) => typeof track === "object" && track !== null && "encoded" in track && "info" in track && typeof track.encoded === "string"));
547
548
  }
548
549
  static buildTracksFromResponse(recommendedResult, requester) {
549
550
  if (!recommendedResult)
@@ -560,7 +561,7 @@ class AutoPlayUtils {
560
561
  context: { recommendedResult },
561
562
  });
562
563
  }
563
- return [TrackUtils.build(data, requester)];
564
+ return [TrackUtils.build(data, requester, true)];
564
565
  }
565
566
  case Enums_1.LoadTypes.Short:
566
567
  case Enums_1.LoadTypes.Search: {
@@ -572,7 +573,7 @@ class AutoPlayUtils {
572
573
  context: { recommendedResult },
573
574
  });
574
575
  }
575
- return data.map((d) => TrackUtils.build(d, requester));
576
+ return data.map((d) => TrackUtils.build(d, requester, true));
576
577
  }
577
578
  case Enums_1.LoadTypes.Album:
578
579
  case Enums_1.LoadTypes.Artist:
@@ -582,7 +583,7 @@ class AutoPlayUtils {
582
583
  case Enums_1.LoadTypes.Playlist: {
583
584
  const data = recommendedResult.data;
584
585
  if (this.isPlaylistRawData(data)) {
585
- return data.tracks.map((d) => TrackUtils.build(d, requester));
586
+ return data.tracks.map((d) => TrackUtils.build(d, requester, true));
586
587
  }
587
588
  throw new MagmastreamError_1.MagmaStreamError({
588
589
  code: Enums_1.MagmaStreamErrorCode.UTILS_AUTOPLAY_BUILD_FAILED,
@@ -622,65 +623,89 @@ class PlayerUtils {
622
623
  * @returns The serialized Player instance
623
624
  */
624
625
  static async serializePlayer(player) {
625
- try {
626
- const current = await player.queue.getCurrent();
627
- const tracks = await player.queue.getTracks();
628
- const previous = await player.queue.getPrevious();
629
- const serializeTrack = (track) => ({
630
- ...track,
631
- requester: track.requester ? { id: track.requester.id, username: track.requester.username } : null,
632
- });
633
- const safeNode = player.node
634
- ? JSON.parse(JSON.stringify(player.node, (key, value) => {
635
- if (key === "rest" || key === "players" || key === "shards" || key === "manager")
636
- return undefined;
637
- return value;
638
- }))
639
- : null;
640
- return JSON.parse(JSON.stringify(player, (key, value) => {
641
- if (key === "manager")
642
- return null;
643
- if (key === "node")
644
- return safeNode;
645
- if (key === "filters") {
646
- return {
647
- distortion: value?.distortion ?? null,
648
- equalizer: value?.equalizer ?? [],
649
- karaoke: value?.karaoke ?? null,
650
- rotation: value?.rotation ?? null,
651
- timescale: value?.timescale ?? null,
652
- vibrato: value?.vibrato ?? null,
653
- reverb: value?.reverb ?? null,
654
- volume: value?.volume ?? 1.0,
655
- bassBoostlevel: value?.bassBoostlevel ?? null,
656
- filterStatus: value?.filtersStatus ? { ...value.filtersStatus } : {},
657
- };
658
- }
659
- if (key === "queue") {
660
- return {
661
- current: current ? serializeTrack(current) : null,
662
- tracks: tracks.map(serializeTrack),
663
- previous: previous.map(serializeTrack),
664
- };
665
- }
666
- if (key === "data") {
667
- return {
668
- clientUser: value?.Internal_AutoplayUser ?? null,
669
- nowPlayingMessage: value?.nowPlayingMessage ?? null,
670
- };
671
- }
626
+ const current = await player.queue.getCurrent();
627
+ const tracks = await player.queue.getTracks();
628
+ const previous = await player.queue.getPrevious();
629
+ const serializeTrack = (track) => ({
630
+ ...track,
631
+ requester: track.requester ? { id: track.requester.id, username: track.requester.username } : null,
632
+ });
633
+ const safeNode = player.node
634
+ ? JSON.parse(JSON.stringify(player.node, (key, value) => {
635
+ if (key === "rest" || key === "players" || key === "shards" || key === "manager")
636
+ return undefined;
672
637
  return value;
673
- }));
638
+ }))
639
+ : null;
640
+ const isNonSerializable = (value) => {
641
+ if (typeof value === "function" || typeof value === "symbol")
642
+ return true;
643
+ if (typeof value === "object" && value !== null) {
644
+ const ctorName = value.constructor?.name ?? "";
645
+ return (value instanceof Map ||
646
+ value instanceof Set ||
647
+ value instanceof WeakMap ||
648
+ value instanceof WeakSet ||
649
+ ctorName === "Timeout" ||
650
+ ctorName === "Socket" ||
651
+ ctorName === "TLSSocket" ||
652
+ ctorName === "EventEmitter");
653
+ }
654
+ return false;
655
+ };
656
+ const safeReplacer = (key, value) => {
657
+ if (isNonSerializable(value))
658
+ return undefined;
659
+ if (key === "manager")
660
+ return null;
661
+ if (key === "node")
662
+ return safeNode;
663
+ if (key === "filters") {
664
+ const filters = { ...value };
665
+ delete filters.player;
666
+ return {
667
+ distortion: filters["distortion"] ?? null,
668
+ equalizer: filters["equalizer"] ?? [],
669
+ karaoke: filters["karaoke"] ?? null,
670
+ rotation: filters["rotation"] ?? null,
671
+ timescale: value["timescale"] ?? null,
672
+ vibrato: value["vibrato"] ?? null,
673
+ reverb: value["reverb"] ?? null,
674
+ volume: value["volume"] ?? 1.0,
675
+ bassBoostlevel: value["bassBoostlevel"] ?? null,
676
+ filterStatus: (0, lodash_1.isPlainObject)(value["filtersStatus"]) ? { ...value["filtersStatus"] } : {},
677
+ };
678
+ }
679
+ if (key === "queue") {
680
+ return {
681
+ current: current ? serializeTrack(current) : null,
682
+ tracks: tracks.map(serializeTrack),
683
+ previous: previous.map(serializeTrack),
684
+ };
685
+ }
686
+ if (key === "data" && (0, lodash_1.isPlainObject)(value)) {
687
+ return {
688
+ clientUser: value["Internal_AutoplayUser"] ?? null,
689
+ nowPlayingMessage: value["nowPlayingMessage"] ?? null,
690
+ };
691
+ }
692
+ return value;
693
+ };
694
+ let serialized;
695
+ try {
696
+ serialized = JSON.stringify(player, safeReplacer);
674
697
  }
675
698
  catch (err) {
676
- throw err instanceof MagmastreamError_1.MagmaStreamError
699
+ const error = err instanceof MagmastreamError_1.MagmaStreamError
677
700
  ? err
678
701
  : new MagmastreamError_1.MagmaStreamError({
679
702
  code: Enums_1.MagmaStreamErrorCode.MANAGER_SEARCH_FAILED,
680
703
  message: `An error occurred while searching: ${err instanceof Error ? err.message : String(err)}`,
681
704
  cause: err instanceof Error ? err : undefined,
682
705
  });
706
+ console.error(error);
683
707
  }
708
+ return JSON.parse(serialized);
684
709
  }
685
710
  /**
686
711
  * Gets the base directory for player data.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magmastream",
3
- "version": "2.9.3-dev.1",
3
+ "version": "2.9.3-dev.10",
4
4
  "description": "A user-friendly Lavalink client designed for NodeJS.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,45 +10,48 @@
10
10
  "scripts": {
11
11
  "build": "tsc",
12
12
  "types": "rtb --dist dist",
13
- "lint": "eslint src",
14
- "lint:fix": "eslint --fix src",
15
- "ci": "run-s lint:fix lint build types",
16
- "release:dev": "npm run ci && npm version prerelease --preid=dev && npm run ci && npm publish --tag dev"
13
+ "format": "prettier --write .",
14
+ "format:check": "prettier --check .",
15
+ "lint": "eslint \"src/**/*.{ts,js}\"",
16
+ "lint:fix": "eslint --fix \"src/**/*.{ts,js}\"",
17
+ "ci": "run-s format:check lint build types",
18
+ "release:dev": "npm run format && npm run lint:fix && npm run ci && npm version prerelease --preid=dev && npm publish --tag dev"
17
19
  },
18
20
  "devDependencies": {
19
21
  "@favware/rollup-type-bundler": "^4.0.0",
20
- "@types/jsdom": "^21.1.7",
21
- "@types/lodash": "^4.17.20",
22
- "@types/node": "^22.16.5",
22
+ "@types/jsdom": "^27.0.0",
23
+ "@types/lodash": "^4.17.23",
24
+ "@types/node": "^25.0.9",
23
25
  "@types/ws": "^8.18.1",
24
- "@typescript-eslint/eslint-plugin": "^8.37.0",
25
- "@typescript-eslint/parser": "^8.37.0",
26
- "eslint": "^9.31.0",
26
+ "@typescript-eslint/eslint-plugin": "^8.53.0",
27
+ "@typescript-eslint/parser": "^8.53.0",
28
+ "eslint": "^9.39.2",
27
29
  "npm-run-all": "^4.1.5",
28
- "typedoc": "^0.27.9",
30
+ "prettier": "^3.8.0",
31
+ "typedoc": "^0.28.16",
29
32
  "typedoc-plugin-no-inherit": "^1.6.1",
30
- "typescript": "^5.8.3"
33
+ "typescript": "^5.9.3"
31
34
  },
32
35
  "dependencies": {
33
36
  "@discordjs/collection": "^2.1.1",
34
- "axios": "^1.10.0",
37
+ "axios": "^1.13.2",
35
38
  "events": "^3.3.0",
36
- "ioredis": "^5.6.1",
37
- "jsdom": "^26.1.0",
39
+ "ioredis": "^5.9.2",
40
+ "jsdom": "^27.4.0",
38
41
  "lodash": "^4.17.21",
39
42
  "safe-stable-stringify": "^2.5.0",
40
43
  "tslib": "^2.8.1",
41
- "ws": "^8.18.3"
44
+ "ws": "^8.19.0"
42
45
  },
43
46
  "optionalDependencies": {
44
47
  "detritus-client": "0.16.x",
45
48
  "discord.js": "14.x",
46
49
  "eris": "0.18.x",
47
- "oceanic.js": "1.12.0",
48
- "seyfert": "3.2.x"
50
+ "oceanic.js": "^1.13.0",
51
+ "seyfert": "4.0.x"
49
52
  },
50
53
  "engines": {
51
- "node": ">=16.0.0"
54
+ "node": ">=20.19.0"
52
55
  },
53
56
  "eslintConfig": {
54
57
  "root": true,
@@ -92,7 +95,8 @@
92
95
  "magmastream"
93
96
  ],
94
97
  "repository": {
95
- "url": "git+https://github.com/Blackfort-Hosting/magmastream.git#main"
98
+ "type": "git",
99
+ "url": "git+https://gitryx.com/MagmaStream/magmastream.git#main"
96
100
  },
97
101
  "homepage": "https://docs.magmastream.com",
98
102
  "author": "Abel Purnwasy",