magmastream 2.9.3-dev.2 → 2.9.3-dev.21
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 +196 -105
- package/dist/index.js +1 -1
- package/dist/statestorage/JsonQueue.js +28 -4
- package/dist/statestorage/MemoryQueue.js +51 -36
- package/dist/statestorage/RedisQueue.js +30 -9
- package/dist/structures/Enums.js +6 -0
- package/dist/structures/Filters.js +5 -4
- package/dist/structures/Manager.js +88 -71
- package/dist/structures/Node.js +91 -145
- package/dist/structures/Player.js +31 -124
- package/dist/structures/Rest.js +41 -21
- package/dist/structures/Utils.js +137 -76
- package/dist/wrappers/discord.js.js +19 -4
- package/dist/wrappers/discordeno.js +73 -0
- package/dist/wrappers/eris.js +20 -3
- package/dist/wrappers/oceanic.js +22 -4
- package/dist/wrappers/seyfert.js +25 -1
- package/package.json +30 -23
- package/dist/wrappers/detritus.js +0 -52
|
@@ -28,6 +28,8 @@ class Manager extends events_1.EventEmitter {
|
|
|
28
28
|
initiated = false;
|
|
29
29
|
redis;
|
|
30
30
|
_send;
|
|
31
|
+
_getUser;
|
|
32
|
+
_getGuild;
|
|
31
33
|
loadedPlugins = new Set();
|
|
32
34
|
/**
|
|
33
35
|
* Initiates the Manager class.
|
|
@@ -61,6 +63,10 @@ class Manager extends events_1.EventEmitter {
|
|
|
61
63
|
this.options.clusterId = options.clusterId;
|
|
62
64
|
if (options.send && !this._send)
|
|
63
65
|
this._send = options.send;
|
|
66
|
+
if (options.getUser && !this._getUser)
|
|
67
|
+
this._getUser = options.getUser;
|
|
68
|
+
if (options.getGuild && !this._getGuild)
|
|
69
|
+
this._getGuild = options.getGuild;
|
|
64
70
|
this.options = {
|
|
65
71
|
...options,
|
|
66
72
|
enabledPlugins: options.enabledPlugins ?? [],
|
|
@@ -177,11 +183,15 @@ class Manager extends events_1.EventEmitter {
|
|
|
177
183
|
db: config.db ?? 0,
|
|
178
184
|
});
|
|
179
185
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
const results = await Promise.allSettled([...this.nodes.values()].map(async (node) => {
|
|
187
|
+
await node.connect();
|
|
188
|
+
return node;
|
|
189
|
+
}));
|
|
190
|
+
for (let i = 0; i < results.length; i++) {
|
|
191
|
+
const result = results[i];
|
|
192
|
+
const node = [...this.nodes.values()][i];
|
|
193
|
+
if (result.status === "rejected") {
|
|
194
|
+
const err = result.reason;
|
|
185
195
|
const error = err instanceof MagmastreamError_1.MagmaStreamError
|
|
186
196
|
? err
|
|
187
197
|
: new MagmastreamError_1.MagmaStreamError({
|
|
@@ -277,9 +287,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
277
287
|
result.playlist.tracks = result.playlist.tracks.map(processTrack);
|
|
278
288
|
}
|
|
279
289
|
}
|
|
280
|
-
const summary = "tracks" in result
|
|
281
|
-
? result.tracks.map((t) => Object.fromEntries(Object.entries(t).filter(([key]) => key !== "requester")))
|
|
282
|
-
: [];
|
|
290
|
+
const summary = "tracks" in result ? result.tracks.map((t) => Object.fromEntries(Object.entries(t).filter(([key]) => key !== "requester"))) : [];
|
|
283
291
|
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Result search for ${_query.query}: ${Utils_1.JSONUtils.safe(summary, 2)}`);
|
|
284
292
|
return result;
|
|
285
293
|
}
|
|
@@ -466,9 +474,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
466
474
|
case Enums_1.StateStorageType.Redis:
|
|
467
475
|
{
|
|
468
476
|
try {
|
|
469
|
-
const redisKey = `${
|
|
470
|
-
? this.options.stateStorage.redisConfig.prefix
|
|
471
|
-
: this.options.stateStorage.redisConfig.prefix ?? "magmastream:"}playerstore:${guildId}`;
|
|
477
|
+
const redisKey = `${Utils_1.PlayerUtils.getRedisKey()}playerstore:${guildId}`;
|
|
472
478
|
await this.redis.set(redisKey, JSON.stringify(serializedPlayer));
|
|
473
479
|
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Player state saved to Redis: ${guildId}`);
|
|
474
480
|
}
|
|
@@ -530,10 +536,11 @@ class Manager extends events_1.EventEmitter {
|
|
|
530
536
|
await promises_1.default.access(stateFilePath);
|
|
531
537
|
const rawData = await promises_1.default.readFile(stateFilePath, "utf-8");
|
|
532
538
|
const state = JSON.parse(rawData);
|
|
533
|
-
if (state.clusterId !== this.options.clusterId)
|
|
534
|
-
continue;
|
|
535
539
|
if (!state.guildId || state.node?.options?.identifier !== nodeId)
|
|
536
540
|
continue;
|
|
541
|
+
const hasGuild = this.resolveGuild(state.guildId);
|
|
542
|
+
if (!hasGuild)
|
|
543
|
+
continue;
|
|
537
544
|
const lavaPlayer = await node.rest.getPlayer(state.guildId);
|
|
538
545
|
if (!lavaPlayer) {
|
|
539
546
|
await this.destroy(state.guildId);
|
|
@@ -558,6 +565,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
558
565
|
token: state.voiceState.event.token,
|
|
559
566
|
endpoint: state.voiceState.event.endpoint,
|
|
560
567
|
sessionId: state.voiceState.sessionId,
|
|
568
|
+
channelId: state.voiceState.event.channel_id,
|
|
561
569
|
},
|
|
562
570
|
},
|
|
563
571
|
});
|
|
@@ -579,7 +587,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
579
587
|
if (lavaPlayer.track) {
|
|
580
588
|
await player.queue.clear();
|
|
581
589
|
if (currentTrack) {
|
|
582
|
-
await player.queue.add(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester));
|
|
590
|
+
await player.queue.add(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester, currentTrack.isAutoplay));
|
|
583
591
|
}
|
|
584
592
|
const remainingQueue = queueTracks.filter((t) => t.uri !== lavaPlayer.track.info.uri);
|
|
585
593
|
if (remainingQueue.length > 0) {
|
|
@@ -682,31 +690,18 @@ class Manager extends events_1.EventEmitter {
|
|
|
682
690
|
filterActions[filter](true);
|
|
683
691
|
}
|
|
684
692
|
}
|
|
685
|
-
|
|
686
|
-
await this.sleep(1000);
|
|
687
|
-
}
|
|
688
|
-
catch (error) {
|
|
689
|
-
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error processing player state for guild ${guildId}: ${error}`);
|
|
690
|
-
continue;
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
// Cleanup old player state files from guild directories whose nodeId matches
|
|
694
|
-
for (const dirent of guildDirs) {
|
|
695
|
-
if (!dirent.isDirectory())
|
|
696
|
-
continue;
|
|
697
|
-
const guildId = dirent.name;
|
|
698
|
-
const stateFilePath = Utils_1.PlayerUtils.getPlayerStatePath(guildId);
|
|
699
|
-
try {
|
|
700
|
-
await promises_1.default.access(stateFilePath);
|
|
701
|
-
const data = await promises_1.default.readFile(stateFilePath, "utf-8");
|
|
702
|
-
const state = JSON.parse(data);
|
|
703
|
-
if (state && typeof state === "object" && state.node?.options?.identifier === nodeId) {
|
|
693
|
+
try {
|
|
704
694
|
await promises_1.default.rm(Utils_1.PlayerUtils.getPlayerStatePath(guildId), { force: true });
|
|
705
695
|
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted player state folder for guild ${guildId}`);
|
|
706
696
|
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error deleting player state for guild ${guildId}: ${error}`);
|
|
699
|
+
}
|
|
700
|
+
this.emit(Enums_1.ManagerEventTypes.PlayerRestored, player, node);
|
|
701
|
+
await this.sleep(1000);
|
|
707
702
|
}
|
|
708
703
|
catch (error) {
|
|
709
|
-
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error
|
|
704
|
+
this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error processing player state for guild ${guildId}: ${error}`);
|
|
710
705
|
continue;
|
|
711
706
|
}
|
|
712
707
|
}
|
|
@@ -720,9 +715,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
720
715
|
{
|
|
721
716
|
try {
|
|
722
717
|
// Get all keys matching our pattern
|
|
723
|
-
const redisKeyPattern = `${
|
|
724
|
-
? this.options.stateStorage.redisConfig.prefix
|
|
725
|
-
: this.options.stateStorage.redisConfig.prefix ?? "magmastream:"}playerstore:*`;
|
|
718
|
+
const redisKeyPattern = `${Utils_1.PlayerUtils.getRedisKey()}playerstore:*`;
|
|
726
719
|
const keys = await this.redis.keys(redisKeyPattern);
|
|
727
720
|
for (const key of keys) {
|
|
728
721
|
try {
|
|
@@ -730,11 +723,14 @@ class Manager extends events_1.EventEmitter {
|
|
|
730
723
|
if (!data)
|
|
731
724
|
continue;
|
|
732
725
|
const state = JSON.parse(data);
|
|
733
|
-
if (!state || typeof state !== "object"
|
|
726
|
+
if (!state || typeof state !== "object")
|
|
734
727
|
continue;
|
|
735
728
|
const guildId = key.split(":").pop();
|
|
736
729
|
if (!guildId || state.node.options.identifier !== nodeId)
|
|
737
730
|
continue;
|
|
731
|
+
const hasGuild = this.resolveGuild(guildId);
|
|
732
|
+
if (!hasGuild)
|
|
733
|
+
continue;
|
|
738
734
|
const lavaPlayer = await node.rest.getPlayer(state.guildId);
|
|
739
735
|
if (!lavaPlayer) {
|
|
740
736
|
await this.destroy(guildId);
|
|
@@ -753,7 +749,14 @@ class Manager extends events_1.EventEmitter {
|
|
|
753
749
|
const player = this.create(playerOptions);
|
|
754
750
|
await player.node.rest.updatePlayer({
|
|
755
751
|
guildId: state.options.guildId,
|
|
756
|
-
data: {
|
|
752
|
+
data: {
|
|
753
|
+
voice: {
|
|
754
|
+
token: state.voiceState.event.token,
|
|
755
|
+
endpoint: state.voiceState.event.endpoint,
|
|
756
|
+
sessionId: state.voiceState.sessionId,
|
|
757
|
+
channelId: state.voiceState.event.channel_id,
|
|
758
|
+
},
|
|
759
|
+
},
|
|
757
760
|
});
|
|
758
761
|
player.connect();
|
|
759
762
|
// Rest of the player state restoration code (tracks, filters, etc.)
|
|
@@ -774,7 +777,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
774
777
|
if (lavaPlayer.track) {
|
|
775
778
|
await player.queue.clear();
|
|
776
779
|
if (currentTrack) {
|
|
777
|
-
await player.queue.add(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester));
|
|
780
|
+
await player.queue.add(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester, currentTrack.isAutoplay));
|
|
778
781
|
}
|
|
779
782
|
const remainingQueue = queueTracks.filter((t) => t.uri !== lavaPlayer.track.info.uri);
|
|
780
783
|
if (remainingQueue.length > 0) {
|
|
@@ -916,11 +919,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
916
919
|
* @returns {Node} The node to use.
|
|
917
920
|
*/
|
|
918
921
|
get useableNode() {
|
|
919
|
-
return this.options.enablePriorityMode
|
|
920
|
-
? this.priorityNode
|
|
921
|
-
: this.options.useNode === Enums_1.UseNodeOptions.LeastLoad
|
|
922
|
-
? this.leastLoadNode.first()
|
|
923
|
-
: this.leastPlayersNode.first();
|
|
922
|
+
return this.options.enablePriorityMode ? this.priorityNode : this.options.useNode === Enums_1.UseNodeOptions.LeastLoad ? this.leastLoadNode.first() : this.leastPlayersNode.first();
|
|
924
923
|
}
|
|
925
924
|
/**
|
|
926
925
|
* Handles the shutdown of the process by saving all active players' states and optionally cleaning up inactive players.
|
|
@@ -1079,13 +1078,17 @@ class Manager extends events_1.EventEmitter {
|
|
|
1079
1078
|
*/
|
|
1080
1079
|
async handleVoiceServerUpdate(player, update) {
|
|
1081
1080
|
player.voiceState.event = update;
|
|
1082
|
-
const
|
|
1081
|
+
const sessionId = player.voiceState.sessionId;
|
|
1082
|
+
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;
|
|
1083
1088
|
await player.node.rest.updatePlayer({
|
|
1084
1089
|
guildId: player.guildId,
|
|
1085
|
-
data: { voice: { token, endpoint, sessionId } },
|
|
1090
|
+
data: { voice: { token, endpoint, sessionId, channelId } },
|
|
1086
1091
|
});
|
|
1087
|
-
this.emit(Enums_1.ManagerEventTypes.Debug, `Updated voice server for player ${player.guildId} with token ${token} and endpoint ${endpoint} and sessionId ${sessionId}`);
|
|
1088
|
-
return;
|
|
1089
1092
|
}
|
|
1090
1093
|
/**
|
|
1091
1094
|
* 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.
|
|
@@ -1096,22 +1099,30 @@ class Manager extends events_1.EventEmitter {
|
|
|
1096
1099
|
*/
|
|
1097
1100
|
async handleVoiceStateUpdate(player, update) {
|
|
1098
1101
|
this.emit(Enums_1.ManagerEventTypes.Debug, `Updated voice state for player ${player.guildId} with channel id ${update.channel_id} and session id ${update.session_id}`);
|
|
1099
|
-
if (update.channel_id) {
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
player.voiceState
|
|
1104
|
-
player.
|
|
1105
|
-
|
|
1102
|
+
if (!update.channel_id) {
|
|
1103
|
+
this.emit(Enums_1.ManagerEventTypes.PlayerDisconnect, player, player.voiceChannelId);
|
|
1104
|
+
player.voiceChannelId = null;
|
|
1105
|
+
player.state = Enums_1.StateTypes.Disconnected;
|
|
1106
|
+
player.voiceState = Object.assign({});
|
|
1107
|
+
if (player.options.pauseOnDisconnect)
|
|
1108
|
+
await player.pause(true);
|
|
1106
1109
|
return;
|
|
1107
1110
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
player.voiceState = Object.assign({});
|
|
1111
|
-
if (player.options.pauseOnDisconnect) {
|
|
1112
|
-
await player.pause(true);
|
|
1111
|
+
if (player.voiceChannelId !== update.channel_id) {
|
|
1112
|
+
this.emit(Enums_1.ManagerEventTypes.PlayerMove, player, player.voiceChannelId, update.channel_id);
|
|
1113
1113
|
}
|
|
1114
|
-
|
|
1114
|
+
player.voiceState.sessionId = update.session_id;
|
|
1115
|
+
player.voiceState.channelId = update.channel_id;
|
|
1116
|
+
player.voiceChannelId = update.channel_id;
|
|
1117
|
+
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
|
+
});
|
|
1115
1126
|
}
|
|
1116
1127
|
/**
|
|
1117
1128
|
* Cleans up inactive players by removing their state files from the file system.
|
|
@@ -1154,9 +1165,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
1154
1165
|
break;
|
|
1155
1166
|
case Enums_1.StateStorageType.Redis:
|
|
1156
1167
|
{
|
|
1157
|
-
const prefix =
|
|
1158
|
-
? this.options.stateStorage.redisConfig.prefix
|
|
1159
|
-
: this.options.stateStorage.redisConfig.prefix ?? "magmastream:";
|
|
1168
|
+
const prefix = Utils_1.PlayerUtils.getRedisKey();
|
|
1160
1169
|
const pattern = `${prefix}queue:*:current`;
|
|
1161
1170
|
try {
|
|
1162
1171
|
const stream = this.redis.scanStream({
|
|
@@ -1229,9 +1238,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
1229
1238
|
{
|
|
1230
1239
|
try {
|
|
1231
1240
|
if (!player) {
|
|
1232
|
-
const prefix =
|
|
1233
|
-
? this.options.stateStorage.redisConfig.prefix
|
|
1234
|
-
: `${this.options.stateStorage.redisConfig.prefix ?? "magmastream"}:`;
|
|
1241
|
+
const prefix = Utils_1.PlayerUtils.getRedisKey();
|
|
1235
1242
|
const keysToDelete = [
|
|
1236
1243
|
`${prefix}playerstore:${guildId}`,
|
|
1237
1244
|
`${prefix}queue:${guildId}:tracks`,
|
|
@@ -1339,9 +1346,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
1339
1346
|
break;
|
|
1340
1347
|
}
|
|
1341
1348
|
case Enums_1.StateStorageType.Redis: {
|
|
1342
|
-
const prefix =
|
|
1343
|
-
? this.options.stateStorage.redisConfig.prefix
|
|
1344
|
-
: this.options.stateStorage.redisConfig.prefix ?? "magmastream:";
|
|
1349
|
+
const prefix = Utils_1.PlayerUtils.getRedisKey();
|
|
1345
1350
|
const patterns = [`${prefix}playerstore:*`, `${prefix}queue:*`];
|
|
1346
1351
|
try {
|
|
1347
1352
|
for (const pattern of patterns) {
|
|
@@ -1440,6 +1445,9 @@ class Manager extends events_1.EventEmitter {
|
|
|
1440
1445
|
}
|
|
1441
1446
|
return this._send(packet);
|
|
1442
1447
|
}
|
|
1448
|
+
getUserFromCache(id) {
|
|
1449
|
+
return this._getUser?.(id);
|
|
1450
|
+
}
|
|
1443
1451
|
sendPacket(packet) {
|
|
1444
1452
|
return this.send(packet);
|
|
1445
1453
|
}
|
|
@@ -1454,5 +1462,14 @@ class Manager extends events_1.EventEmitter {
|
|
|
1454
1462
|
return { id: user }; // fallback by ID only
|
|
1455
1463
|
return user; // default: just return the portable user
|
|
1456
1464
|
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Resolves a Guild ID to a real guild object.
|
|
1467
|
+
* Can be overridden by wrapper managers to return wrapper-specific Guild classes.
|
|
1468
|
+
*/
|
|
1469
|
+
resolveGuild(guildId) {
|
|
1470
|
+
if (!guildId)
|
|
1471
|
+
return null;
|
|
1472
|
+
return this._getGuild?.(guildId);
|
|
1473
|
+
}
|
|
1457
1474
|
}
|
|
1458
1475
|
exports.Manager = Manager;
|
package/dist/structures/Node.js
CHANGED
|
@@ -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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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 =
|
|
164
|
-
this.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
189
|
-
this.manager.emit(Enums_1.ManagerEventTypes.Debug, `[NODE]
|
|
190
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
this.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|