magmastream 2.9.3-dev.2 → 2.9.3-dev.20

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.
@@ -31,7 +31,6 @@ class Node {
31
31
  reconnectTimeout;
32
32
  reconnectAttempts = 1;
33
33
  redisPrefix;
34
- sessionIdsFilePath;
35
34
  sessionIdsMap = new Map();
36
35
  /**
37
36
  * Creates an instance of Node.
@@ -97,18 +96,12 @@ class Node {
97
96
  switch (this.manager.options.stateStorage.type) {
98
97
  case Enums_1.StateStorageType.Memory:
99
98
  case Enums_1.StateStorageType.JSON:
100
- this.sessionIdsFilePath = path_1.default.join(process.cwd(), "magmastream", "sessionData", "sessionIds.json");
101
- const configDir = path_1.default.dirname(this.sessionIdsFilePath);
102
- if (!fs_1.default.existsSync(configDir)) {
103
- fs_1.default.mkdirSync(configDir, { recursive: true });
104
- }
105
- this.createSessionIdsFile();
106
99
  this.createReadmeFile();
107
100
  break;
108
101
  case Enums_1.StateStorageType.Redis:
109
102
  this.redisPrefix = this.manager.options.stateStorage.redisConfig.prefix?.endsWith(":")
110
103
  ? this.manager.options.stateStorage.redisConfig.prefix
111
- : this.manager.options.stateStorage.redisConfig.prefix ?? "magmastream:";
104
+ : (this.manager.options.stateStorage.redisConfig.prefix ?? "magmastream:");
112
105
  break;
113
106
  }
114
107
  }
@@ -125,16 +118,18 @@ class Node {
125
118
  get address() {
126
119
  return `${this.options.host}:${this.options.port}`;
127
120
  }
128
- /**
129
- * Creates the sessionIds.json file if it doesn't exist. This file is used to
130
- * store the session IDs for each node. The session IDs are used to identify
131
- * the node when resuming a session.
132
- */
133
- createSessionIdsFile() {
134
- if (!fs_1.default.existsSync(this.sessionIdsFilePath)) {
135
- this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Creating sessionId file at: ${this.sessionIdsFilePath}`);
136
- fs_1.default.writeFileSync(this.sessionIdsFilePath, Utils_1.JSONUtils.safe({}), "utf-8");
137
- }
121
+ getCompositeKey() {
122
+ return `${this.options.identifier}::${this.manager.options.clusterId}`;
123
+ }
124
+ getRedisSessionIdsKey() {
125
+ return `${this.redisPrefix}node:sessionIds`;
126
+ }
127
+ getNodeSessionsDir() {
128
+ return path_1.default.join(process.cwd(), "magmastream", "sessionData", "cluster", String(this.manager.options.clusterId), "nodeSessions");
129
+ }
130
+ getNodeSessionPath() {
131
+ const safeId = String(this.options.identifier).replace(/[^a-zA-Z0-9._-]/g, "_");
132
+ return path_1.default.join(this.getNodeSessionsDir(), `${safeId}.txt`);
138
133
  }
139
134
  /**
140
135
  * Loads session IDs from the sessionIds.json file if it exists.
@@ -148,49 +143,43 @@ class Node {
148
143
  switch (this.manager.options.stateStorage.type) {
149
144
  case Enums_1.StateStorageType.Memory:
150
145
  case Enums_1.StateStorageType.JSON: {
151
- if (fs_1.default.existsSync(this.sessionIdsFilePath)) {
152
- this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Loading sessionIds from file: ${this.sessionIdsFilePath}`);
153
- const sessionIdsData = fs_1.default.readFileSync(this.sessionIdsFilePath, "utf-8");
154
- this.sessionIdsMap = new Map(Object.entries(JSON.parse(sessionIdsData)));
155
- const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
156
- if (this.sessionIdsMap.has(compositeKey)) {
157
- this.sessionId = this.sessionIdsMap.get(compositeKey);
158
- }
146
+ const dir = this.getNodeSessionsDir();
147
+ const filePath = this.getNodeSessionPath();
148
+ if (!fs_1.default.existsSync(dir))
149
+ fs_1.default.mkdirSync(dir, { recursive: true });
150
+ if (!fs_1.default.existsSync(filePath)) {
151
+ this.sessionId = null;
152
+ return;
153
+ }
154
+ try {
155
+ const raw = fs_1.default.readFileSync(filePath, "utf-8").trim();
156
+ this.sessionId = raw.length ? raw : null;
157
+ if (this.sessionId)
158
+ this.sessionIdsMap.set(this.getCompositeKey(), this.sessionId);
159
+ }
160
+ catch {
161
+ this.sessionId = null;
159
162
  }
160
163
  break;
161
164
  }
162
- case Enums_1.StateStorageType.Redis:
163
- const key = `${this.redisPrefix}node:sessionIds`;
164
- this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Loading sessionIds from Redis key: ${key}`);
165
- const currentRaw = await this.manager.redis.get(key);
166
- if (currentRaw) {
167
- try {
168
- const sessionIds = JSON.parse(currentRaw);
169
- if (typeof sessionIds !== "object" || Array.isArray(sessionIds)) {
170
- throw new MagmastreamError_1.MagmaStreamError({
171
- code: Enums_1.MagmaStreamErrorCode.NODE_SESSION_IDS_LOAD_FAILED,
172
- message: "Invalid sessionIds data type from Redis.",
173
- context: { sessionIds },
174
- });
175
- }
176
- this.sessionIdsMap = new Map(Object.entries(sessionIds));
177
- const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
178
- if (this.sessionIdsMap.has(compositeKey)) {
179
- this.sessionId = this.sessionIdsMap.get(compositeKey) || null;
180
- this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Restored sessionId for ${compositeKey}: ${this.sessionId}`);
181
- }
182
- }
183
- catch (err) {
184
- this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Failed to parse Redis sessionIds: ${err.message}`);
185
- this.sessionIdsMap = new Map();
165
+ case Enums_1.StateStorageType.Redis: {
166
+ const key = this.getRedisSessionIdsKey();
167
+ const compositeKey = this.getCompositeKey();
168
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Loading sessionId from Redis hash: ${key} field: ${compositeKey}`);
169
+ try {
170
+ const sid = await this.manager.redis.hget(key, compositeKey);
171
+ this.sessionId = sid ?? null;
172
+ if (this.sessionId) {
173
+ this.sessionIdsMap.set(compositeKey, this.sessionId);
174
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Restored sessionId for ${compositeKey}: ${this.sessionId}`);
186
175
  }
187
176
  }
188
- else {
189
- this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] No sessionIds found in Redis creating new key.`);
190
- await this.manager.redis.set(key, Utils_1.JSONUtils.safe({}));
191
- this.sessionIdsMap = new Map();
177
+ catch (err) {
178
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Failed to load sessionId from Redis hash: ${err.message}`);
179
+ this.sessionId = null;
192
180
  }
193
181
  break;
182
+ }
194
183
  }
195
184
  }
196
185
  /**
@@ -207,84 +196,53 @@ class Node {
207
196
  async updateSessionId() {
208
197
  switch (this.manager.options.stateStorage.type) {
209
198
  case Enums_1.StateStorageType.Memory:
210
- case Enums_1.StateStorageType.JSON: {
211
- const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
212
- const filePath = this.sessionIdsFilePath;
213
- this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Updating sessionIds to file: ${filePath}`);
214
- let updated = false;
215
- let retries = 3;
216
- while (!updated && retries > 0) {
217
- try {
218
- let fileData = {};
219
- if (fs_1.default.existsSync(filePath)) {
220
- try {
221
- const raw = fs_1.default.readFileSync(filePath, "utf-8");
222
- fileData = raw.trim() ? JSON.parse(raw) : {};
223
- }
224
- catch (err) {
225
- this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Failed to read/parse sessionIds.json: ${err.message}`);
226
- fileData = {};
227
- }
228
- }
229
- fileData[compositeKey] = this.sessionId;
230
- const tmpPath = `${filePath}.tmp`;
231
- fs_1.default.writeFileSync(tmpPath, Utils_1.JSONUtils.safe(fileData, 2), "utf-8");
232
- fs_1.default.renameSync(tmpPath, filePath);
233
- this.sessionIdsMap = new Map(Object.entries(fileData));
234
- updated = true;
235
- }
236
- catch (err) {
237
- retries--;
238
- if (retries === 0) {
239
- throw new MagmastreamError_1.MagmaStreamError({
240
- code: Enums_1.MagmaStreamErrorCode.NODE_SESSION_IDS_UPDATE_FAILED,
241
- message: `Failed to update sessionIds after retries.`,
242
- cause: err instanceof Error ? err : undefined,
243
- context: { filePath, compositeKey, storage: "file" },
244
- });
245
- }
246
- await new Promise((r) => setTimeout(r, 50));
247
- }
248
- }
249
- break;
199
+ case Enums_1.StateStorageType.JSON:
200
+ return this.updateSessionIdFile();
201
+ case Enums_1.StateStorageType.Redis:
202
+ return this.updateSessionIdRedis();
203
+ }
204
+ }
205
+ async updateSessionIdFile() {
206
+ const dir = this.getNodeSessionsDir();
207
+ const filePath = this.getNodeSessionPath();
208
+ const tmpPath = `${filePath}.tmp`;
209
+ if (!fs_1.default.existsSync(dir))
210
+ fs_1.default.mkdirSync(dir, { recursive: true });
211
+ if (this.sessionId) {
212
+ fs_1.default.writeFileSync(tmpPath, this.sessionId, "utf-8");
213
+ fs_1.default.renameSync(tmpPath, filePath);
214
+ this.sessionIdsMap.set(this.getCompositeKey(), this.sessionId);
215
+ }
216
+ else {
217
+ try {
218
+ if (fs_1.default.existsSync(filePath))
219
+ fs_1.default.unlinkSync(filePath);
250
220
  }
251
- case Enums_1.StateStorageType.Redis: {
252
- const key = `${this.redisPrefix}node:sessionIds`;
253
- const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
254
- this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Updating sessionIds in Redis key: ${key}`);
255
- let sessionIds = {};
256
- try {
257
- const currentRaw = await this.manager.redis.get(key);
258
- if (currentRaw) {
259
- sessionIds = JSON.parse(currentRaw);
260
- if (typeof sessionIds !== "object" || Array.isArray(sessionIds)) {
261
- throw new Error("Invalid data type in Redis");
262
- }
263
- }
264
- else {
265
- this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Redis key not found — creating new sessionIds key.`);
266
- sessionIds = {};
267
- }
268
- }
269
- catch (err) {
270
- this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Corrupted Redis sessionIds, reinitializing: ${err.message}`);
271
- sessionIds = {};
272
- }
273
- try {
274
- sessionIds[compositeKey] = this.sessionId;
275
- this.sessionIdsMap = new Map(Object.entries(sessionIds));
276
- await this.manager.redis.set(key, Utils_1.JSONUtils.safe(sessionIds));
277
- }
278
- catch (err) {
279
- throw new MagmastreamError_1.MagmaStreamError({
280
- code: Enums_1.MagmaStreamErrorCode.NODE_SESSION_IDS_UPDATE_FAILED,
281
- message: `Failed to update sessionIds in Redis.`,
282
- cause: err instanceof Error ? err : undefined,
283
- context: { key, compositeKey, storage: "redis" },
284
- });
285
- }
286
- break;
221
+ catch { }
222
+ this.sessionIdsMap.delete(this.getCompositeKey());
223
+ }
224
+ }
225
+ async updateSessionIdRedis() {
226
+ const key = this.getRedisSessionIdsKey();
227
+ const compositeKey = this.getCompositeKey();
228
+ this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Updating sessionId in Redis hash: ${key} field: ${compositeKey}`);
229
+ try {
230
+ if (this.sessionId) {
231
+ await this.manager.redis.hset(key, compositeKey, this.sessionId);
232
+ this.sessionIdsMap.set(compositeKey, this.sessionId);
287
233
  }
234
+ else {
235
+ await this.manager.redis.hdel(key, compositeKey);
236
+ this.sessionIdsMap.delete(compositeKey);
237
+ }
238
+ }
239
+ catch (err) {
240
+ throw new MagmastreamError_1.MagmaStreamError({
241
+ code: Enums_1.MagmaStreamErrorCode.NODE_SESSION_IDS_UPDATE_FAILED,
242
+ message: "Failed to update sessionId in Redis hash.",
243
+ cause: err instanceof Error ? err : undefined,
244
+ context: { key, compositeKey, storage: "redis-hash" },
245
+ });
288
246
  }
289
247
  }
290
248
  /**
@@ -305,12 +263,7 @@ class Node {
305
263
  "User-Id": this.manager.options.clientId,
306
264
  "Client-Name": this.manager.options.clientName,
307
265
  };
308
- const compositeKey = `${this.options.identifier}::${this.manager.options.clusterId}`;
309
- if (this.sessionId) {
310
- headers["Session-Id"] = this.sessionId;
311
- }
312
- else if (this.options.enableSessionResumeOption && this.sessionIdsMap.has(compositeKey)) {
313
- this.sessionId = this.sessionIdsMap.get(compositeKey) || null;
266
+ if (typeof this.sessionId === "string" && this.sessionId.length > 0) {
314
267
  headers["Session-Id"] = this.sessionId;
315
268
  }
316
269
  this.socket = new ws_1.default(`ws${this.options.useSSL ? "s" : ""}://${this.address}/v4/websocket`, { headers });
@@ -356,9 +309,7 @@ class Node {
356
309
  // Automove all players connected to that node
357
310
  const players = this.manager.players.filter((p) => p.node == this);
358
311
  if (players.size) {
359
- for (const player of players.values()) {
360
- await player.autoMoveNode();
361
- }
312
+ await Promise.all(Array.from(players.values(), (player) => player.autoMoveNode()));
362
313
  }
363
314
  this.socket.close(1000, "destroy");
364
315
  this.socket.removeAllListeners();
@@ -632,12 +583,7 @@ class Node {
632
583
  player.playing = true;
633
584
  player.paused = false;
634
585
  this.manager.emit(Enums_1.ManagerEventTypes.TrackStart, player, track, payload);
635
- const AutoplayUser = player.get("Internal_AutoplayUser");
636
- if (!track.requester || !track.requester.id) {
637
- console.log(track);
638
- console.warn(`[WARN] Track requester missing for guild ${player.guildId}`, track);
639
- }
640
- if (AutoplayUser && track.requester && AutoplayUser.id === track.requester.id) {
586
+ if (track.isAutoplay) {
641
587
  this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, player, {
642
588
  changeType: Enums_1.PlayerStateEventTypes.TrackChange,
643
589
  details: {
@@ -82,7 +82,7 @@ class Player {
82
82
  message: "Manager instance is required.",
83
83
  });
84
84
  }
85
- this.clusterId = this.manager.options.clusterId || 0;
85
+ this.clusterId = this.manager.options.clusterId ?? 0;
86
86
  // Check the player options for errors.
87
87
  (0, playerCheck_1.default)(options);
88
88
  this.options = {
@@ -260,21 +260,30 @@ class Player {
260
260
  */
261
261
  async destroy(disconnect = true) {
262
262
  this.state = Enums_1.StateTypes.Destroying;
263
+ if (this.dynamicLoopInterval) {
264
+ clearInterval(this.dynamicLoopInterval);
265
+ this.dynamicLoopInterval = null;
266
+ }
267
+ if (this.voiceReceiverReconnectTimeout) {
268
+ clearTimeout(this.voiceReceiverReconnectTimeout);
269
+ this.voiceReceiverReconnectTimeout = null;
270
+ }
271
+ if (this.voiceReceiverWsClient) {
272
+ this.voiceReceiverWsClient.removeAllListeners();
273
+ this.voiceReceiverWsClient.close();
274
+ this.voiceReceiverWsClient = null;
275
+ }
263
276
  if (disconnect) {
264
- await this.disconnect().catch((err) => {
265
- console.warn(`[Player#destroy] Failed to disconnect player ${this.guildId}:`, err);
266
- });
277
+ await this.disconnect().catch(() => { });
267
278
  }
268
- await this.node.rest.destroyPlayer(this.guildId).catch((err) => {
269
- console.warn(`[Player#destroy] REST failed to destroy player ${this.guildId}:`, err);
270
- });
271
- await this.queue.clear();
272
- await this.queue.clearPrevious();
273
- await this.queue.setCurrent(null);
279
+ await this.node.rest.destroyPlayer(this.guildId).catch(() => { });
280
+ await this.queue.destroy();
281
+ this.nowPlayingMessage = undefined;
274
282
  this.manager.emit(Enums_1.ManagerEventTypes.PlayerDestroy, this);
275
283
  const deleted = this.manager.players.delete(this.guildId);
276
- if (this.manager.options.stateStorage.deleteInactivePlayers)
284
+ if (this.manager.options.stateStorage.deleteInactivePlayers) {
277
285
  await this.manager.cleanupInactivePlayer(this.guildId);
286
+ }
278
287
  return deleted;
279
288
  }
280
289
  /**
@@ -806,11 +815,8 @@ class Player {
806
815
  if (currentPlayingTrack) {
807
816
  await this.queue.add(currentPlayingTrack, 0);
808
817
  }
809
- await this.play(lastTrack);
810
- }
811
- else {
812
- await this.play(lastTrack);
813
818
  }
819
+ await this.play(lastTrack);
814
820
  this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
815
821
  changeType: Enums_1.PlayerStateEventTypes.TrackChange,
816
822
  details: {
@@ -922,7 +928,8 @@ class Player {
922
928
  const sessionId = this.voiceState?.sessionId;
923
929
  const token = this.voiceState?.event?.token;
924
930
  const endpoint = this.voiceState?.event?.endpoint;
925
- if (!sessionId || !token || !endpoint) {
931
+ const channelId = this.voiceState?.channelId;
932
+ if (!sessionId || !token || !endpoint || !channelId) {
926
933
  this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Voice state is not properly initialized for player ${this.guildId}. The bot might not be connected to a voice channel.`);
927
934
  throw new MagmastreamError_1.MagmaStreamError({
928
935
  code: Enums_1.MagmaStreamErrorCode.PLAYER_STATE_INVALID,
@@ -936,7 +943,7 @@ class Player {
936
943
  this.manager.players.set(this.guildId, this);
937
944
  await this.node.rest.updatePlayer({
938
945
  guildId: this.guildId,
939
- data: { paused: this.paused, volume: this.volume, position: playerPosition, encodedTrack: currentTrack?.track, voice: { token, endpoint, sessionId } },
946
+ data: { paused: this.paused, volume: this.volume, position: playerPosition, encodedTrack: currentTrack?.track, voice: { token, endpoint, sessionId, channelId } },
940
947
  });
941
948
  await this.filters.updateFilters();
942
949
  }
@@ -953,113 +960,6 @@ class Player {
953
960
  console.error(error);
954
961
  }
955
962
  }
956
- /**
957
- * Transfers the player to a new server. If the player already exists on the new server
958
- * and force is false, this method will return the existing player. Otherwise, a new player
959
- * will be created and the current player will be destroyed.
960
- * @param {PlayerOptions} newOptions - The new options for the player.
961
- * @param {boolean} force - Whether to force the creation of a new player.
962
- * @returns {Promise<Player>} - The new player instance.
963
- */
964
- async switchGuild(newOptions, force = false) {
965
- if (!newOptions.guildId) {
966
- throw new MagmastreamError_1.MagmaStreamError({
967
- code: Enums_1.MagmaStreamErrorCode.PLAYER_INVALID_CONFIG,
968
- message: "guildId is required for switchGuild",
969
- });
970
- }
971
- if (!newOptions.voiceChannelId) {
972
- throw new MagmastreamError_1.MagmaStreamError({
973
- code: Enums_1.MagmaStreamErrorCode.PLAYER_INVALID_CONFIG,
974
- message: "voiceChannelId is required for switchGuild",
975
- });
976
- }
977
- if (!newOptions.textChannelId) {
978
- throw new MagmastreamError_1.MagmaStreamError({
979
- code: Enums_1.MagmaStreamErrorCode.PLAYER_INVALID_CONFIG,
980
- message: "textChannelId is required for switchGuild",
981
- });
982
- }
983
- // Check if a player already exists for the new guild
984
- let newPlayer = this.manager.getPlayer(newOptions.guildId);
985
- // If the player already exists and force is false, return the existing player
986
- if (newPlayer && !force)
987
- return newPlayer;
988
- const oldPlayerProperties = {
989
- paused: this.paused,
990
- selfMute: this.options.selfMute,
991
- selfDeafen: this.options.selfDeafen,
992
- volume: this.volume,
993
- position: this.position,
994
- queue: {
995
- current: await this.queue.getCurrent(),
996
- tracks: [...(await this.queue.getTracks())],
997
- previous: [...(await this.queue.getPrevious())],
998
- },
999
- trackRepeat: this.trackRepeat,
1000
- queueRepeat: this.queueRepeat,
1001
- dynamicRepeat: this.dynamicRepeat,
1002
- dynamicRepeatIntervalMs: this.dynamicRepeatIntervalMs,
1003
- ClientUser: this.get("Internal_AutoplayUser"),
1004
- filters: this.filters,
1005
- nowPlayingMessage: this.nowPlayingMessage,
1006
- isAutoplay: this.isAutoplay,
1007
- applyVolumeAsFilter: this.options.applyVolumeAsFilter,
1008
- };
1009
- // If force is true, destroy the existing player for the new guild
1010
- if (force && newPlayer) {
1011
- await newPlayer.destroy();
1012
- }
1013
- newOptions.nodeIdentifier = newOptions.nodeIdentifier ?? this.options.nodeIdentifier;
1014
- newOptions.selfDeafen = newOptions.selfDeafen ?? oldPlayerProperties.selfDeafen;
1015
- newOptions.selfMute = newOptions.selfMute ?? oldPlayerProperties.selfMute;
1016
- newOptions.volume = newOptions.volume ?? oldPlayerProperties.volume;
1017
- newOptions.applyVolumeAsFilter = newOptions.applyVolumeAsFilter ?? oldPlayerProperties.applyVolumeAsFilter;
1018
- // Deep clone the current player
1019
- const clonedPlayer = this.manager.create(newOptions);
1020
- // Connect the cloned player to the new voice channel
1021
- clonedPlayer.connect();
1022
- // Update the player's state on the Lavalink node
1023
- await clonedPlayer.node.rest.updatePlayer({
1024
- guildId: clonedPlayer.guildId,
1025
- data: {
1026
- paused: oldPlayerProperties.paused,
1027
- volume: oldPlayerProperties.volume,
1028
- position: oldPlayerProperties.position,
1029
- encodedTrack: oldPlayerProperties.queue.current?.track,
1030
- },
1031
- });
1032
- await clonedPlayer.queue.setCurrent(oldPlayerProperties.queue.current);
1033
- await clonedPlayer.queue.addPrevious(oldPlayerProperties.queue.previous);
1034
- await clonedPlayer.queue.add(oldPlayerProperties.queue.tracks);
1035
- clonedPlayer.filters = oldPlayerProperties.filters;
1036
- clonedPlayer.isAutoplay = oldPlayerProperties.isAutoplay;
1037
- clonedPlayer.nowPlayingMessage = oldPlayerProperties.nowPlayingMessage;
1038
- clonedPlayer.trackRepeat = oldPlayerProperties.trackRepeat;
1039
- clonedPlayer.queueRepeat = oldPlayerProperties.queueRepeat;
1040
- clonedPlayer.dynamicRepeat = oldPlayerProperties.dynamicRepeat;
1041
- clonedPlayer.dynamicRepeatIntervalMs = oldPlayerProperties.dynamicRepeatIntervalMs;
1042
- clonedPlayer.set("Internal_AutoplayUser", oldPlayerProperties.ClientUser);
1043
- clonedPlayer.paused = oldPlayerProperties.paused;
1044
- // Update filters for the cloned player
1045
- await clonedPlayer.filters.updateFilters();
1046
- // Debug information
1047
- const debugInfo = {
1048
- success: true,
1049
- message: `Transferred ${await clonedPlayer.queue.size()} tracks successfully to <#${newOptions.voiceChannelId}> bound to <#${newOptions.textChannelId}>.`,
1050
- player: {
1051
- guildId: clonedPlayer.guildId,
1052
- voiceChannelId: clonedPlayer.voiceChannelId,
1053
- textChannelId: clonedPlayer.textChannelId,
1054
- volume: clonedPlayer.volume,
1055
- playing: clonedPlayer.playing,
1056
- queueSize: clonedPlayer.queue.size,
1057
- },
1058
- };
1059
- this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[PLAYER] Transferred player to a new server: ${Utils_1.JSONUtils.safe(debugInfo, 2)}.`);
1060
- // Return the cloned player
1061
- return clonedPlayer;
1062
- }
1063
963
  /**
1064
964
  * Retrieves the data associated with the player.
1065
965
  * @returns {Record<string, unknown>} - The data associated with the player.
@@ -1074,6 +974,13 @@ class Player {
1074
974
  getDynamicLoopIntervalPublic() {
1075
975
  return this.dynamicLoopInterval;
1076
976
  }
977
+ /**
978
+ * Retrieves the data associated with the player.
979
+ * @returns {Record<string, unknown>} - The data associated with the player.
980
+ */
981
+ getSerializableData() {
982
+ return { ...this.data };
983
+ }
1077
984
  /**
1078
985
  * Retrieves the current lyrics for the playing track.
1079
986
  * @param skipTrackSource - Indicates whether to skip the track source when fetching lyrics.
@@ -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.