magmastream 2.9.3-dev.21 → 2.9.3-dev.23

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
@@ -859,7 +859,7 @@ declare abstract class Plugin {
859
859
  interface ManagerOptions {
860
860
  /** The state storage options.
861
861
  *
862
- * @default { type: StateStorageType.Collection, deleteInactivePlayers: true }
862
+ * @default { type: StateStorageType.Collection, deleteDestroyedPlayers: true }
863
863
  */
864
864
  stateStorage?: StateStorageOptions;
865
865
  /** Enable priority mode over least player count or load balancing?
@@ -945,7 +945,7 @@ interface StateStorageOptions {
945
945
  type: StateStorageType;
946
946
  redisConfig?: RedisConfig;
947
947
  jsonConfig?: JsonConfig;
948
- deleteInactivePlayers?: boolean;
948
+ deleteDestroyedPlayers?: boolean;
949
949
  }
950
950
  /**
951
951
  * Node Options
@@ -2435,6 +2435,11 @@ declare class Player {
2435
2435
  * @returns {Promise<void>} - A promise that resolves when the voice receiver error is handled.
2436
2436
  */
2437
2437
  private onVoiceReceiverError;
2438
+ /**
2439
+ * Updates the voice state for the player.
2440
+ * @returns {Promise<void>} - A promise that resolves when the voice state is updated.
2441
+ */
2442
+ updateVoice(): Promise<void>;
2438
2443
  }
2439
2444
 
2440
2445
  /** Handles the requests sent to the Lavalink REST API. */
@@ -3101,11 +3106,6 @@ declare class Manager extends EventEmitter {
3101
3106
  * @emits {playerDisconnect} - Emits a player disconnect event if the channel ID is null.
3102
3107
  */
3103
3108
  private handleVoiceStateUpdate;
3104
- /**
3105
- * Cleans up inactive players by removing their state files from the file system.
3106
- * This is done to prevent stale state files from accumulating on the file system.
3107
- */
3108
- cleanupInactivePlayers(): Promise<void>;
3109
3109
  /**
3110
3110
  * Cleans up an inactive player by removing its state data.
3111
3111
  * This is done to prevent stale state data from accumulating.
@@ -92,7 +92,7 @@ class Manager extends events_1.EventEmitter {
92
92
  stateStorage: {
93
93
  ...options.stateStorage,
94
94
  type: options.stateStorage?.type ?? Enums_1.StateStorageType.Memory,
95
- deleteInactivePlayers: options.stateStorage?.deleteInactivePlayers ?? true,
95
+ deleteDestroyedPlayers: options.stateStorage?.deleteDestroyedPlayers ?? true,
96
96
  },
97
97
  autoPlaySearchPlatforms: options.autoPlaySearchPlatforms ?? [Enums_1.AutoPlayPlatform.YouTube],
98
98
  send: this._send,
@@ -558,6 +558,7 @@ class Manager extends events_1.EventEmitter {
558
558
  };
559
559
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${state.guildId} from saved file: ${Utils_1.JSONUtils.safe(state.options, 2)}`);
560
560
  const player = this.create(playerOptions);
561
+ player.connect();
561
562
  await player.node.rest.updatePlayer({
562
563
  guildId: state.options.guildId,
563
564
  data: {
@@ -565,11 +566,10 @@ class Manager extends events_1.EventEmitter {
565
566
  token: state.voiceState.event.token,
566
567
  endpoint: state.voiceState.event.endpoint,
567
568
  sessionId: state.voiceState.sessionId,
568
- channelId: state.voiceState.event.channel_id,
569
+ channelId: state.voiceState.channelId,
569
570
  },
570
571
  },
571
572
  });
572
- player.connect();
573
573
  const tracks = [];
574
574
  const currentTrack = state.queue.current;
575
575
  const queueTracks = state.queue.tracks;
@@ -747,6 +747,7 @@ class Manager extends events_1.EventEmitter {
747
747
  };
748
748
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${guildId} from Redis`);
749
749
  const player = this.create(playerOptions);
750
+ player.connect();
750
751
  await player.node.rest.updatePlayer({
751
752
  guildId: state.options.guildId,
752
753
  data: {
@@ -754,11 +755,10 @@ class Manager extends events_1.EventEmitter {
754
755
  token: state.voiceState.event.token,
755
756
  endpoint: state.voiceState.event.endpoint,
756
757
  sessionId: state.voiceState.sessionId,
757
- channelId: state.voiceState.event.channel_id,
758
+ channelId: state.voiceState.channelId,
758
759
  },
759
760
  },
760
761
  });
761
- player.connect();
762
762
  // Rest of the player state restoration code (tracks, filters, etc.)
763
763
  const tracks = [];
764
764
  const currentTrack = state.queue.current;
@@ -949,8 +949,6 @@ class Manager extends events_1.EventEmitter {
949
949
  }
950
950
  });
951
951
  await Promise.allSettled(savePromises);
952
- if (this.options.stateStorage.deleteInactivePlayers)
953
- await this.cleanupInactivePlayers();
954
952
  setTimeout(() => {
955
953
  console.warn("\x1b[32m%s\x1b[0m", "MAGMASTREAM INFO: Shutting down complete, exiting...");
956
954
  process.exit(0);
@@ -1080,15 +1078,8 @@ class Manager extends events_1.EventEmitter {
1080
1078
  player.voiceState.event = update;
1081
1079
  const sessionId = player.voiceState.sessionId;
1082
1080
  const channelId = player.voiceState.channelId;
1083
- const token = update.token;
1084
- const endpoint = update.endpoint;
1085
- this.emit(Enums_1.ManagerEventTypes.Debug, `Updated voice server for player ${player.guildId} with token ${token} | endpoint ${endpoint} | sessionId ${sessionId} | channelId ${channelId}`);
1086
- if (!sessionId || !channelId)
1087
- return;
1088
- await player.node.rest.updatePlayer({
1089
- guildId: player.guildId,
1090
- data: { voice: { token, endpoint, sessionId, channelId } },
1091
- });
1081
+ this.emit(Enums_1.ManagerEventTypes.Debug, `Updated voice server for player ${player.guildId} with token ${update.token} | endpoint ${update.endpoint} | sessionId ${sessionId} | channelId ${channelId}`);
1082
+ await player.updateVoice();
1092
1083
  }
1093
1084
  /**
1094
1085
  * Handles a voice state update by updating the player's voice channel and session ID if provided, or by disconnecting and destroying the player if the channel ID is null.
@@ -1115,94 +1106,7 @@ class Manager extends events_1.EventEmitter {
1115
1106
  player.voiceState.channelId = update.channel_id;
1116
1107
  player.voiceChannelId = update.channel_id;
1117
1108
  player.options.voiceChannelId = update.channel_id;
1118
- const token = player.voiceState.event?.token;
1119
- const endpoint = player.voiceState.event?.endpoint;
1120
- if (!token || !endpoint)
1121
- return;
1122
- await player.node.rest.updatePlayer({
1123
- guildId: player.guildId,
1124
- data: { voice: { token, endpoint, sessionId: update.session_id, channelId: update.channel_id } },
1125
- });
1126
- }
1127
- /**
1128
- * Cleans up inactive players by removing their state files from the file system.
1129
- * This is done to prevent stale state files from accumulating on the file system.
1130
- */
1131
- async cleanupInactivePlayers() {
1132
- switch (this.options.stateStorage.type) {
1133
- case Enums_1.StateStorageType.JSON:
1134
- {
1135
- const playersBaseDir = Utils_1.PlayerUtils.getPlayersBaseDir();
1136
- try {
1137
- await promises_1.default.mkdir(playersBaseDir, { recursive: true });
1138
- const activeGuildIds = new Set(this.players.keys());
1139
- // Cleanup inactive guild directories inside playersBaseDir
1140
- const guildDirs = await promises_1.default.readdir(playersBaseDir, { withFileTypes: true });
1141
- for (const dirent of guildDirs) {
1142
- if (!dirent.isDirectory())
1143
- continue;
1144
- const guildId = dirent.name;
1145
- if (!activeGuildIds.has(guildId)) {
1146
- const guildPath = Utils_1.PlayerUtils.getGuildDir(guildId);
1147
- await promises_1.default.rm(guildPath, { recursive: true, force: true });
1148
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted inactive player data folder: ${guildId}`);
1149
- }
1150
- }
1151
- }
1152
- catch (err) {
1153
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error cleaning up inactive JSON players: ${err}`);
1154
- const error = err instanceof MagmastreamError_1.MagmaStreamError
1155
- ? err
1156
- : new MagmastreamError_1.MagmaStreamError({
1157
- code: Enums_1.MagmaStreamErrorCode.MANAGER_CLEANUP_INACTIVE_PLAYERS_FAILED,
1158
- message: "Error cleaning up inactive players.",
1159
- cause: err,
1160
- context: { stage: "CLEANUP_INACTIVE_PLAYERS" },
1161
- });
1162
- console.error(error);
1163
- }
1164
- }
1165
- break;
1166
- case Enums_1.StateStorageType.Redis:
1167
- {
1168
- const prefix = Utils_1.PlayerUtils.getRedisKey();
1169
- const pattern = `${prefix}queue:*:current`;
1170
- try {
1171
- const stream = this.redis.scanStream({
1172
- match: pattern,
1173
- count: 100,
1174
- });
1175
- for await (const keys of stream) {
1176
- for (const key of keys) {
1177
- // Extract guildId from queue key
1178
- const match = key.match(new RegExp(`^${prefix}queue:(.+):current$`));
1179
- if (!match)
1180
- continue;
1181
- const guildId = match[1];
1182
- // If player is not active in memory, clean up all keys
1183
- if (!this.players.has(guildId)) {
1184
- await this.redis.del(`${prefix}playerstore:${guildId}`, `${prefix}queue:${guildId}:current`, `${prefix}queue:${guildId}:tracks`, `${prefix}queue:${guildId}:previous`);
1185
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Cleaned inactive Redis player data: ${guildId}`);
1186
- }
1187
- }
1188
- }
1189
- }
1190
- catch (err) {
1191
- const error = err instanceof MagmastreamError_1.MagmaStreamError
1192
- ? err
1193
- : new MagmastreamError_1.MagmaStreamError({
1194
- code: Enums_1.MagmaStreamErrorCode.MANAGER_SHUTDOWN_FAILED,
1195
- message: "Error saving player state.",
1196
- cause: err,
1197
- context: { stage: "CLEANUP_INACTIVE_PLAYERS" },
1198
- });
1199
- console.error(error);
1200
- }
1201
- }
1202
- break;
1203
- default:
1204
- break;
1205
- }
1109
+ await player.updateVoice();
1206
1110
  }
1207
1111
  /**
1208
1112
  * Cleans up an inactive player by removing its state data.
@@ -99,9 +99,7 @@ class Node {
99
99
  this.createReadmeFile();
100
100
  break;
101
101
  case Enums_1.StateStorageType.Redis:
102
- this.redisPrefix = this.manager.options.stateStorage.redisConfig.prefix?.endsWith(":")
103
- ? this.manager.options.stateStorage.redisConfig.prefix
104
- : (this.manager.options.stateStorage.redisConfig.prefix ?? "magmastream:");
102
+ this.redisPrefix = Utils_1.PlayerUtils.getRedisKey();
105
103
  break;
106
104
  }
107
105
  }
@@ -125,7 +123,7 @@ class Node {
125
123
  return `${this.redisPrefix}node:sessionIds`;
126
124
  }
127
125
  getNodeSessionsDir() {
128
- return path_1.default.join(process.cwd(), "magmastream", "sessionData", "cluster", String(this.manager.options.clusterId), "nodeSessions");
126
+ return path_1.default.join(process.cwd(), "magmastream", "sessionData", "nodeSessions");
129
127
  }
130
128
  getNodeSessionPath() {
131
129
  const safeId = String(this.options.identifier).replace(/[^a-zA-Z0-9._-]/g, "_");
@@ -1173,8 +1171,12 @@ class Node {
1173
1171
  * @private
1174
1172
  */
1175
1173
  createReadmeFile() {
1176
- const readmeFilePath = path_1.default.join(process.cwd(), "magmastream", "README.md");
1174
+ const baseDir = path_1.default.join(process.cwd(), "magmastream");
1175
+ const readmeFilePath = path_1.default.join(baseDir, "README.md");
1177
1176
  const message = "Please do NOT delete the magmastream/ folder as it is used to store player data for autoresume etc.";
1177
+ if (!fs_1.default.existsSync(baseDir)) {
1178
+ fs_1.default.mkdirSync(baseDir, { recursive: true });
1179
+ }
1178
1180
  if (!fs_1.default.existsSync(readmeFilePath)) {
1179
1181
  fs_1.default.writeFileSync(readmeFilePath, message, "utf-8");
1180
1182
  this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE] Created README file at: ${readmeFilePath}`);
@@ -281,7 +281,7 @@ class Player {
281
281
  this.nowPlayingMessage = undefined;
282
282
  this.manager.emit(Enums_1.ManagerEventTypes.PlayerDestroy, this);
283
283
  const deleted = this.manager.players.delete(this.guildId);
284
- if (this.manager.options.stateStorage.deleteInactivePlayers) {
284
+ if (this.manager.options.stateStorage.deleteDestroyedPlayers) {
285
285
  await this.manager.cleanupInactivePlayer(this.guildId);
286
286
  }
287
287
  return deleted;
@@ -1144,5 +1144,26 @@ class Player {
1144
1144
  this.manager.emit(Enums_1.ManagerEventTypes.Debug, `VoiceReceiver error for player ${this.guildId}: ${error.message}`);
1145
1145
  this.manager.emit(Enums_1.ManagerEventTypes.VoiceReceiverError, this, error);
1146
1146
  }
1147
+ /**
1148
+ * Updates the voice state for the player.
1149
+ * @returns {Promise<void>} - A promise that resolves when the voice state is updated.
1150
+ */
1151
+ async updateVoice() {
1152
+ const vs = this.voiceState;
1153
+ const ev = vs?.event;
1154
+ if (!vs?.channelId || !vs?.sessionId || !ev?.token || !ev?.endpoint)
1155
+ return;
1156
+ await this.node.rest.updatePlayer({
1157
+ guildId: this.options.guildId,
1158
+ data: {
1159
+ voice: {
1160
+ token: ev.token,
1161
+ endpoint: ev.endpoint,
1162
+ sessionId: vs.sessionId,
1163
+ channelId: vs.channelId,
1164
+ },
1165
+ },
1166
+ });
1167
+ }
1147
1168
  }
1148
1169
  exports.Player = Player;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magmastream",
3
- "version": "2.9.3-dev.21",
3
+ "version": "2.9.3-dev.23",
4
4
  "description": "A user-friendly Lavalink client designed for NodeJS.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",