magmastream 2.9.3-dev.9 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/config/blockedWords.d.ts +1 -0
  2. package/dist/index.d.ts +19 -3700
  3. package/dist/index.js +1 -1
  4. package/dist/statestorage/JsonQueue.d.ts +173 -0
  5. package/dist/statestorage/JsonQueue.js +32 -4
  6. package/dist/statestorage/MemoryQueue.d.ts +154 -0
  7. package/dist/statestorage/MemoryQueue.js +56 -36
  8. package/dist/statestorage/RedisQueue.d.ts +178 -0
  9. package/dist/statestorage/RedisQueue.js +29 -7
  10. package/dist/structures/Enums.d.ts +310 -0
  11. package/dist/structures/Enums.js +4 -0
  12. package/dist/structures/Filters.d.ts +352 -0
  13. package/dist/structures/Filters.js +5 -4
  14. package/dist/structures/MagmastreamError.d.ts +14 -0
  15. package/dist/structures/Manager.d.ts +259 -0
  16. package/dist/structures/Manager.js +285 -542
  17. package/dist/structures/Node.d.ts +390 -0
  18. package/dist/structures/Node.js +100 -145
  19. package/dist/structures/Player.d.ts +347 -0
  20. package/dist/structures/Player.js +54 -128
  21. package/dist/structures/Plugin.d.ts +23 -0
  22. package/dist/structures/Rest.d.ts +93 -0
  23. package/dist/structures/Types.d.ts +1315 -0
  24. package/dist/structures/Utils.d.ts +169 -0
  25. package/dist/structures/Utils.js +107 -56
  26. package/dist/utils/filtersEqualizers.d.ts +16 -0
  27. package/dist/utils/managerCheck.d.ts +7 -0
  28. package/dist/utils/nodeCheck.d.ts +7 -0
  29. package/dist/utils/playerCheck.d.ts +7 -0
  30. package/dist/wrappers/discord.js.d.ts +15 -0
  31. package/dist/wrappers/discord.js.js +19 -4
  32. package/dist/wrappers/discordeno.d.ts +19 -0
  33. package/dist/wrappers/discordeno.js +77 -0
  34. package/dist/wrappers/eris.d.ts +15 -0
  35. package/dist/wrappers/eris.js +20 -3
  36. package/dist/wrappers/oceanic.d.ts +15 -0
  37. package/dist/wrappers/oceanic.js +22 -4
  38. package/dist/wrappers/seyfert.d.ts +37 -0
  39. package/dist/wrappers/seyfert.js +25 -1
  40. package/package.json +107 -102
  41. 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 ?? [],
@@ -86,9 +92,10 @@ class Manager extends events_1.EventEmitter {
86
92
  stateStorage: {
87
93
  ...options.stateStorage,
88
94
  type: options.stateStorage?.type ?? Enums_1.StateStorageType.Memory,
89
- deleteInactivePlayers: options.stateStorage?.deleteInactivePlayers ?? true,
95
+ deleteDestroyedPlayers: options.stateStorage?.deleteDestroyedPlayers ?? true,
90
96
  },
91
97
  autoPlaySearchPlatforms: options.autoPlaySearchPlatforms ?? [Enums_1.AutoPlayPlatform.YouTube],
98
+ listenToSIGEvents: options.listenToSIGEvents ?? true,
92
99
  send: this._send,
93
100
  };
94
101
  Utils_1.AutoPlayUtils.init(this);
@@ -96,49 +103,51 @@ class Manager extends events_1.EventEmitter {
96
103
  for (const nodeOptions of this.options.nodes)
97
104
  new Node_1.Node(this, nodeOptions);
98
105
  }
99
- process.on("SIGINT", async () => {
100
- console.warn("\x1b[33mSIGINT received! Graceful shutdown initiated...\x1b[0m");
101
- try {
102
- await this.handleShutdown();
103
- console.warn("\x1b[32mShutdown complete. Waiting for Node.js event loop to empty...\x1b[0m");
104
- // Prevent forced exit by Windows
105
- setTimeout(() => {
106
+ if (this.options.listenToSIGEvents) {
107
+ process.on("SIGINT", async () => {
108
+ console.warn("\x1b[33mSIGINT received! Graceful shutdown initiated...\x1b[0m");
109
+ try {
110
+ await this.handleShutdown();
111
+ console.warn("\x1b[32mShutdown complete. Waiting for Node.js event loop to empty...\x1b[0m");
112
+ // Prevent forced exit by Windows
113
+ setTimeout(() => {
114
+ process.exit(0);
115
+ }, 2000);
116
+ }
117
+ catch (err) {
118
+ const error = err instanceof MagmastreamError_1.MagmaStreamError
119
+ ? err
120
+ : new MagmastreamError_1.MagmaStreamError({
121
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_SHUTDOWN_FAILED,
122
+ message: "An unknown error occurred.",
123
+ cause: err,
124
+ context: { stage: "SIGINT" },
125
+ });
126
+ console.error(error);
127
+ process.exit(1);
128
+ }
129
+ });
130
+ process.on("SIGTERM", async () => {
131
+ console.warn("\x1b[33mSIGTERM received! Graceful shutdown initiated...\x1b[0m");
132
+ try {
133
+ await this.handleShutdown();
134
+ console.warn("\x1b[32mShutdown complete. Exiting now...\x1b[0m");
106
135
  process.exit(0);
107
- }, 2000);
108
- }
109
- catch (err) {
110
- const error = err instanceof MagmastreamError_1.MagmaStreamError
111
- ? err
112
- : new MagmastreamError_1.MagmaStreamError({
113
- code: Enums_1.MagmaStreamErrorCode.MANAGER_SHUTDOWN_FAILED,
114
- message: "An unknown error occurred.",
115
- cause: err,
116
- context: { stage: "SIGINT" },
117
- });
118
- console.error(error);
119
- process.exit(1);
120
- }
121
- });
122
- process.on("SIGTERM", async () => {
123
- console.warn("\x1b[33mSIGTERM received! Graceful shutdown initiated...\x1b[0m");
124
- try {
125
- await this.handleShutdown();
126
- console.warn("\x1b[32mShutdown complete. Exiting now...\x1b[0m");
127
- process.exit(0);
128
- }
129
- catch (err) {
130
- const error = err instanceof MagmastreamError_1.MagmaStreamError
131
- ? err
132
- : new MagmastreamError_1.MagmaStreamError({
133
- code: Enums_1.MagmaStreamErrorCode.MANAGER_SHUTDOWN_FAILED,
134
- message: "An unknown error occurred.",
135
- cause: err,
136
- context: { stage: "SIGTERM" },
137
- });
138
- console.error(error);
139
- process.exit(1);
140
- }
141
- });
136
+ }
137
+ catch (err) {
138
+ const error = err instanceof MagmastreamError_1.MagmaStreamError
139
+ ? err
140
+ : new MagmastreamError_1.MagmaStreamError({
141
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_SHUTDOWN_FAILED,
142
+ message: "An unknown error occurred.",
143
+ cause: err,
144
+ context: { stage: "SIGTERM" },
145
+ });
146
+ console.error(error);
147
+ process.exit(1);
148
+ }
149
+ });
150
+ }
142
151
  }
143
152
  /**
144
153
  * Initiates the Manager.
@@ -468,9 +477,7 @@ class Manager extends events_1.EventEmitter {
468
477
  case Enums_1.StateStorageType.Redis:
469
478
  {
470
479
  try {
471
- const redisKey = `${this.options.stateStorage.redisConfig.prefix?.endsWith(":")
472
- ? this.options.stateStorage.redisConfig.prefix
473
- : (this.options.stateStorage.redisConfig.prefix ?? "magmastream:")}playerstore:${guildId}`;
480
+ const redisKey = `${Utils_1.PlayerUtils.getRedisKey()}playerstore:${guildId}`;
474
481
  await this.redis.set(redisKey, JSON.stringify(serializedPlayer));
475
482
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Player state saved to Redis: ${guildId}`);
476
483
  }
@@ -494,6 +501,154 @@ class Manager extends events_1.EventEmitter {
494
501
  async sleep(ms) {
495
502
  return new Promise((resolve) => setTimeout(resolve, ms));
496
503
  }
504
+ async restorePlayerFromState(node, nodeId, guildId, state, cleanup) {
505
+ if (!state.guildId || state.node?.options?.identifier !== nodeId)
506
+ return;
507
+ const hasGuild = this.resolveGuild(state.guildId);
508
+ if (!hasGuild)
509
+ return;
510
+ const lavaPlayer = (await node.rest.get(`/v4/sessions/${state.node.sessionId}/players/${state.guildId}`));
511
+ if (!lavaPlayer)
512
+ return;
513
+ const playerOptions = {
514
+ guildId: state.options.guildId,
515
+ textChannelId: state.options.textChannelId,
516
+ voiceChannelId: state.options.voiceChannelId,
517
+ selfDeafen: state.options.selfDeafen,
518
+ volume: lavaPlayer.volume || state.options.volume,
519
+ nodeIdentifier: nodeId,
520
+ applyVolumeAsFilter: state.options.applyVolumeAsFilter,
521
+ pauseOnDisconnect: state.options.pauseOnDisconnect,
522
+ };
523
+ const player = this.create(playerOptions);
524
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${guildId}`);
525
+ if (state.isAutoplay) {
526
+ const savedUser = state.data?.clientUser;
527
+ if (savedUser) {
528
+ const autoPlayUser = await player.manager.resolveUser(savedUser);
529
+ player.setAutoplay(true, autoPlayUser, state.autoplayTries);
530
+ }
531
+ }
532
+ const savedNowPlayingMessage = state.data?.nowPlayingMessage;
533
+ if (savedNowPlayingMessage) {
534
+ player.setNowPlayingMessage(savedNowPlayingMessage);
535
+ }
536
+ await this.restoreQueue(node, player, state, lavaPlayer);
537
+ await this.restorePreviousQueue(player, state);
538
+ this.restoreRepeatState(player, state);
539
+ this.restorePlayerData(player, state);
540
+ this.restoreFilters(player, state);
541
+ player.connect();
542
+ if (lavaPlayer.track && state.clusterId !== player.clusterId) {
543
+ const currentTrack = state.queue.current;
544
+ await player.play(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester, currentTrack.isAutoplay), { startTime: lavaPlayer.state.position ?? 0 });
545
+ await node.rest.delete(`/v4/sessions/${state.node.sessionId}/players/${state.guildId}`);
546
+ }
547
+ await player.pause(state.paused);
548
+ await cleanup();
549
+ this.emit(Enums_1.ManagerEventTypes.PlayerRestored, player, node);
550
+ await this.sleep(1000);
551
+ }
552
+ async restoreQueue(node, player, state, lavaPlayer) {
553
+ const currentTrack = state.queue.current;
554
+ const queueTracks = state.queue.tracks;
555
+ if (lavaPlayer.track) {
556
+ await player.queue.clear();
557
+ if (currentTrack) {
558
+ await player.queue.add(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester, currentTrack.isAutoplay));
559
+ }
560
+ const remainingQueue = queueTracks.filter((t) => t.uri !== lavaPlayer.track.info.uri);
561
+ if (remainingQueue.length > 0)
562
+ await player.queue.add(remainingQueue);
563
+ player.playing = !lavaPlayer.paused;
564
+ return;
565
+ }
566
+ // No active lavalink track
567
+ if (currentTrack) {
568
+ if (queueTracks.length > 0) {
569
+ await player.queue.clear();
570
+ await player.queue.add(queueTracks);
571
+ }
572
+ await node.trackEnd(player, currentTrack, {
573
+ reason: Enums_1.TrackEndReasonTypes.Finished,
574
+ type: "TrackEndEvent",
575
+ });
576
+ return;
577
+ }
578
+ // No current track either — check previous
579
+ const previousQueue = await player.queue.getPrevious();
580
+ const lastTrack = previousQueue?.at(-1);
581
+ if (queueTracks.length > 0) {
582
+ await player.queue.clear();
583
+ await player.queue.add(queueTracks);
584
+ }
585
+ if (lastTrack || queueTracks.length > 0) {
586
+ await node.trackEnd(player, lastTrack, {
587
+ reason: Enums_1.TrackEndReasonTypes.Finished,
588
+ type: "TrackEndEvent",
589
+ });
590
+ }
591
+ }
592
+ async restorePreviousQueue(player, state) {
593
+ if (state.queue.previous.length > 0) {
594
+ const validPrevious = state.queue.previous.filter((t) => t !== null && typeof t.identifier === "string");
595
+ if (validPrevious.length > 0)
596
+ await player.queue.addPrevious(validPrevious);
597
+ }
598
+ else {
599
+ await player.queue.clearPrevious();
600
+ }
601
+ }
602
+ restoreRepeatState(player, state) {
603
+ if (state.trackRepeat)
604
+ player.setTrackRepeat(true);
605
+ if (state.queueRepeat)
606
+ player.setQueueRepeat(true);
607
+ if (state.dynamicRepeat && state.dynamicLoopInterval) {
608
+ player.setDynamicRepeat(state.dynamicRepeat, state.dynamicLoopInterval);
609
+ }
610
+ }
611
+ restorePlayerData(player, state) {
612
+ if (!state.data)
613
+ return;
614
+ for (const [name, value] of Object.entries(state.data)) {
615
+ player.set(name, value);
616
+ }
617
+ }
618
+ restoreFilters(player, state) {
619
+ const filterActions = {
620
+ bassboost: () => player.filters.bassBoost(state.filters.bassBoostlevel),
621
+ distort: (e) => player.filters.distort(e),
622
+ setDistortion: () => player.filters.setDistortion(state.filters.distortion),
623
+ eightD: (e) => player.filters.eightD(e),
624
+ setKaraoke: () => player.filters.setKaraoke(state.filters.karaoke),
625
+ nightcore: (e) => player.filters.nightcore(e),
626
+ slowmo: (e) => player.filters.slowmo(e),
627
+ soft: (e) => player.filters.soft(e),
628
+ trebleBass: (e) => player.filters.trebleBass(e),
629
+ setTimescale: () => player.filters.setTimescale(state.filters.timescale),
630
+ tv: (e) => player.filters.tv(e),
631
+ vibrato: () => player.filters.setVibrato(state.filters.vibrato),
632
+ vaporwave: (e) => player.filters.vaporwave(e),
633
+ pop: (e) => player.filters.pop(e),
634
+ party: (e) => player.filters.party(e),
635
+ earrape: (e) => player.filters.earrape(e),
636
+ electronic: (e) => player.filters.electronic(e),
637
+ radio: (e) => player.filters.radio(e),
638
+ setRotation: () => player.filters.setRotation(state.filters.rotation),
639
+ tremolo: (e) => player.filters.tremolo(e),
640
+ china: (e) => player.filters.china(e),
641
+ chipmunk: (e) => player.filters.chipmunk(e),
642
+ darthvader: (e) => player.filters.darthvader(e),
643
+ daycore: (e) => player.filters.daycore(e),
644
+ doubletime: (e) => player.filters.doubletime(e),
645
+ demon: (e) => player.filters.demon(e),
646
+ };
647
+ for (const [filter, isEnabled] of Object.entries(state.filters.filterStatus)) {
648
+ if (isEnabled && filterActions[filter])
649
+ filterActions[filter](true);
650
+ }
651
+ }
497
652
  /**
498
653
  * Loads player states from the JSON file.
499
654
  * @param nodeId The ID of the node to load player states from.
@@ -503,7 +658,6 @@ class Manager extends events_1.EventEmitter {
503
658
  this.emit(Enums_1.ManagerEventTypes.Debug, "[MANAGER] Loading saved players.");
504
659
  const node = this.nodes.get(nodeId);
505
660
  if (!node) {
506
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Tried to load non-existent node: ${nodeId}`);
507
661
  throw new MagmastreamError_1.MagmaStreamError({
508
662
  code: Enums_1.MagmaStreamErrorCode.MANAGER_NODE_NOT_FOUND,
509
663
  message: "Node not found.",
@@ -512,400 +666,65 @@ class Manager extends events_1.EventEmitter {
512
666
  }
513
667
  switch (this.options.stateStorage.type) {
514
668
  case Enums_1.StateStorageType.Memory:
515
- case Enums_1.StateStorageType.JSON:
516
- {
517
- const playersBaseDir = Utils_1.PlayerUtils.getPlayersBaseDir();
518
- try {
519
- // Ensure base players directory exists
520
- await promises_1.default.access(playersBaseDir).catch(async () => {
521
- await promises_1.default.mkdir(playersBaseDir, { recursive: true });
522
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Created directory: ${playersBaseDir}`);
523
- });
524
- // Read guild directories inside players base dir
525
- const guildDirs = await promises_1.default.readdir(playersBaseDir, { withFileTypes: true });
526
- for (const dirent of guildDirs) {
527
- if (!dirent.isDirectory())
528
- continue;
529
- const guildId = dirent.name;
530
- const stateFilePath = Utils_1.PlayerUtils.getPlayerStatePath(guildId);
531
- try {
532
- await promises_1.default.access(stateFilePath);
533
- const rawData = await promises_1.default.readFile(stateFilePath, "utf-8");
534
- const state = JSON.parse(rawData);
535
- if (state.clusterId !== this.options.clusterId)
536
- continue;
537
- if (!state.guildId || state.node?.options?.identifier !== nodeId)
538
- continue;
539
- const lavaPlayer = await node.rest.getPlayer(state.guildId);
540
- if (!lavaPlayer) {
541
- await this.destroy(state.guildId);
542
- continue;
543
- }
544
- const playerOptions = {
545
- guildId: state.options.guildId,
546
- textChannelId: state.options.textChannelId,
547
- voiceChannelId: state.options.voiceChannelId,
548
- selfDeafen: state.options.selfDeafen,
549
- volume: lavaPlayer.volume || state.options.volume,
550
- nodeIdentifier: nodeId,
551
- applyVolumeAsFilter: state.options.applyVolumeAsFilter,
552
- pauseOnDisconnect: state.options.pauseOnDisconnect,
553
- };
554
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${state.guildId} from saved file: ${Utils_1.JSONUtils.safe(state.options, 2)}`);
555
- const player = this.create(playerOptions);
556
- await player.node.rest.updatePlayer({
557
- guildId: state.options.guildId,
558
- data: {
559
- voice: {
560
- token: state.voiceState.event.token,
561
- endpoint: state.voiceState.event.endpoint,
562
- sessionId: state.voiceState.sessionId,
563
- },
564
- },
565
- });
566
- player.connect();
567
- const tracks = [];
568
- const currentTrack = state.queue.current;
569
- const queueTracks = state.queue.tracks;
570
- if (state.isAutoplay) {
571
- const savedUser = state.data.clientUser;
572
- if (savedUser) {
573
- const autoPlayUser = await player.manager.resolveUser(savedUser);
574
- player.setAutoplay(true, autoPlayUser, state.autoplayTries);
575
- }
576
- }
577
- const savedNowPlayingMessage = state.data?.nowPlayingMessage;
578
- if (savedNowPlayingMessage) {
579
- player.setNowPlayingMessage(savedNowPlayingMessage);
580
- }
581
- if (lavaPlayer.track) {
582
- await player.queue.clear();
583
- if (currentTrack) {
584
- await player.queue.add(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester, currentTrack.isAutoplay));
585
- }
586
- const remainingQueue = queueTracks.filter((t) => t.uri !== lavaPlayer.track.info.uri);
587
- if (remainingQueue.length > 0) {
588
- await player.queue.add(remainingQueue);
589
- }
590
- player.playing = !lavaPlayer.paused;
591
- }
592
- else {
593
- if (currentTrack) {
594
- if (queueTracks.length > 0) {
595
- tracks.push(...queueTracks);
596
- await player.queue.clear();
597
- await player.queue.add(tracks);
598
- }
599
- await node.trackEnd(player, currentTrack, {
600
- reason: Enums_1.TrackEndReasonTypes.Finished,
601
- type: "TrackEndEvent",
602
- });
603
- }
604
- else {
605
- const previousQueue = await player.queue.getPrevious();
606
- const lastTrack = previousQueue?.at(-1);
607
- if (lastTrack) {
608
- if (queueTracks.length === 0) {
609
- await node.trackEnd(player, lastTrack, {
610
- reason: Enums_1.TrackEndReasonTypes.Finished,
611
- type: "TrackEndEvent",
612
- });
613
- }
614
- else {
615
- tracks.push(...queueTracks);
616
- if (tracks.length > 0) {
617
- await player.queue.clear();
618
- await player.queue.add(tracks);
619
- }
620
- }
621
- }
622
- else if (queueTracks.length > 0) {
623
- tracks.push(...queueTracks);
624
- if (tracks.length > 0) {
625
- await player.queue.clear();
626
- await player.queue.add(tracks);
627
- }
628
- await node.trackEnd(player, lastTrack, {
629
- reason: Enums_1.TrackEndReasonTypes.Finished,
630
- type: "TrackEndEvent",
631
- });
632
- }
633
- }
634
- }
635
- if (state.queue.previous.length > 0) {
636
- await player.queue.addPrevious(state.queue.previous);
637
- }
638
- else {
639
- await player.queue.clearPrevious();
640
- }
641
- await player.pause(state.paused);
642
- if (state.trackRepeat)
643
- player.setTrackRepeat(true);
644
- if (state.queueRepeat)
645
- player.setQueueRepeat(true);
646
- if (state.dynamicRepeat && state.dynamicLoopInterval) {
647
- player.setDynamicRepeat(state.dynamicRepeat, state.dynamicLoopInterval);
648
- }
649
- if (state.data) {
650
- for (const [name, value] of Object.entries(state.data)) {
651
- player.set(name, value);
652
- }
653
- }
654
- const filterActions = {
655
- bassboost: () => player.filters.bassBoost(state.filters.bassBoostlevel),
656
- distort: (enabled) => player.filters.distort(enabled),
657
- setDistortion: () => player.filters.setDistortion(state.filters.distortion),
658
- eightD: (enabled) => player.filters.eightD(enabled),
659
- setKaraoke: () => player.filters.setKaraoke(state.filters.karaoke),
660
- nightcore: (enabled) => player.filters.nightcore(enabled),
661
- slowmo: (enabled) => player.filters.slowmo(enabled),
662
- soft: (enabled) => player.filters.soft(enabled),
663
- trebleBass: (enabled) => player.filters.trebleBass(enabled),
664
- setTimescale: () => player.filters.setTimescale(state.filters.timescale),
665
- tv: (enabled) => player.filters.tv(enabled),
666
- vibrato: () => player.filters.setVibrato(state.filters.vibrato),
667
- vaporwave: (enabled) => player.filters.vaporwave(enabled),
668
- pop: (enabled) => player.filters.pop(enabled),
669
- party: (enabled) => player.filters.party(enabled),
670
- earrape: (enabled) => player.filters.earrape(enabled),
671
- electronic: (enabled) => player.filters.electronic(enabled),
672
- radio: (enabled) => player.filters.radio(enabled),
673
- setRotation: () => player.filters.setRotation(state.filters.rotation),
674
- tremolo: (enabled) => player.filters.tremolo(enabled),
675
- china: (enabled) => player.filters.china(enabled),
676
- chipmunk: (enabled) => player.filters.chipmunk(enabled),
677
- darthvader: (enabled) => player.filters.darthvader(enabled),
678
- daycore: (enabled) => player.filters.daycore(enabled),
679
- doubletime: (enabled) => player.filters.doubletime(enabled),
680
- demon: (enabled) => player.filters.demon(enabled),
681
- };
682
- for (const [filter, isEnabled] of Object.entries(state.filters.filterStatus)) {
683
- if (isEnabled && filterActions[filter]) {
684
- filterActions[filter](true);
685
- }
686
- }
687
- this.emit(Enums_1.ManagerEventTypes.PlayerRestored, player, node);
688
- await this.sleep(1000);
689
- }
690
- catch (error) {
691
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error processing player state for guild ${guildId}: ${error}`);
692
- continue;
693
- }
669
+ case Enums_1.StateStorageType.JSON: {
670
+ const playersBaseDir = Utils_1.PlayerUtils.getPlayersBaseDir();
671
+ try {
672
+ await promises_1.default.access(playersBaseDir).catch(async () => {
673
+ await promises_1.default.mkdir(playersBaseDir, { recursive: true });
674
+ });
675
+ const guildDirs = await promises_1.default.readdir(playersBaseDir, { withFileTypes: true });
676
+ for (const file of guildDirs) {
677
+ if (!file.isDirectory())
678
+ continue;
679
+ const guildId = file.name;
680
+ const stateFilePath = Utils_1.PlayerUtils.getPlayerStatePath(guildId);
681
+ try {
682
+ await promises_1.default.access(stateFilePath);
683
+ const state = JSON.parse(await promises_1.default.readFile(stateFilePath, "utf-8"));
684
+ await this.restorePlayerFromState(node, nodeId, guildId, state, async () => {
685
+ await promises_1.default.rm(stateFilePath, { force: true });
686
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted state for guild ${guildId}`);
687
+ });
694
688
  }
695
- // Cleanup old player state files from guild directories whose nodeId matches
696
- for (const dirent of guildDirs) {
697
- if (!dirent.isDirectory())
698
- continue;
699
- const guildId = dirent.name;
700
- const stateFilePath = Utils_1.PlayerUtils.getPlayerStatePath(guildId);
701
- try {
702
- await promises_1.default.access(stateFilePath);
703
- const data = await promises_1.default.readFile(stateFilePath, "utf-8");
704
- const state = JSON.parse(data);
705
- if (state && typeof state === "object" && state.node?.options?.identifier === nodeId) {
706
- await promises_1.default.rm(Utils_1.PlayerUtils.getPlayerStatePath(guildId), { force: true });
707
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted player state folder for guild ${guildId}`);
708
- }
709
- }
710
- catch (error) {
711
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error deleting player state for guild ${guildId}: ${error}`);
712
- continue;
713
- }
689
+ catch (error) {
690
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error processing guild ${guildId}: ${error}`);
714
691
  }
715
692
  }
716
- catch (error) {
717
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error loading player states: ${error}`);
718
- }
693
+ }
694
+ catch (error) {
695
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error loading player states: ${error}`);
719
696
  }
720
697
  break;
721
- case Enums_1.StateStorageType.Redis:
722
- {
723
- try {
724
- // Get all keys matching our pattern
725
- const redisKeyPattern = `${this.options.stateStorage.redisConfig.prefix?.endsWith(":")
726
- ? this.options.stateStorage.redisConfig.prefix
727
- : (this.options.stateStorage.redisConfig.prefix ?? "magmastream:")}playerstore:*`;
728
- const keys = await this.redis.keys(redisKeyPattern);
729
- for (const key of keys) {
730
- try {
731
- const data = await this.redis.get(key);
732
- if (!data)
733
- continue;
734
- const state = JSON.parse(data);
735
- if (!state || typeof state !== "object" || state.clusterId !== this.options.clusterId)
736
- continue;
737
- const guildId = key.split(":").pop();
738
- if (!guildId || state.node.options.identifier !== nodeId)
739
- continue;
740
- const lavaPlayer = await node.rest.getPlayer(state.guildId);
741
- if (!lavaPlayer) {
742
- await this.destroy(guildId);
743
- }
744
- const playerOptions = {
745
- guildId: state.options.guildId,
746
- textChannelId: state.options.textChannelId,
747
- voiceChannelId: state.options.voiceChannelId,
748
- selfDeafen: state.options.selfDeafen,
749
- volume: lavaPlayer.volume || state.options.volume,
750
- nodeIdentifier: nodeId,
751
- applyVolumeAsFilter: state.options.applyVolumeAsFilter,
752
- pauseOnDisconnect: state.options.pauseOnDisconnect,
753
- };
754
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${guildId} from Redis`);
755
- const player = this.create(playerOptions);
756
- await player.node.rest.updatePlayer({
757
- guildId: state.options.guildId,
758
- data: { voice: { token: state.voiceState.event.token, endpoint: state.voiceState.event.endpoint, sessionId: state.voiceState.sessionId } },
759
- });
760
- player.connect();
761
- // Rest of the player state restoration code (tracks, filters, etc.)
762
- const tracks = [];
763
- const currentTrack = state.queue.current;
764
- const queueTracks = state.queue.tracks;
765
- if (state.isAutoplay) {
766
- const savedUser = state.data.clientUser;
767
- if (savedUser) {
768
- const autoPlayUser = await player.manager.resolveUser(savedUser);
769
- player.setAutoplay(true, autoPlayUser, state.autoplayTries);
770
- }
771
- }
772
- const savedNowPlayingMessage = state.data?.nowPlayingMessage;
773
- if (savedNowPlayingMessage) {
774
- player.setNowPlayingMessage(savedNowPlayingMessage);
775
- }
776
- if (lavaPlayer.track) {
777
- await player.queue.clear();
778
- if (currentTrack) {
779
- await player.queue.add(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester, currentTrack.isAutoplay));
780
- }
781
- const remainingQueue = queueTracks.filter((t) => t.uri !== lavaPlayer.track.info.uri);
782
- if (remainingQueue.length > 0) {
783
- await player.queue.add(remainingQueue);
784
- }
785
- player.playing = !lavaPlayer.paused;
786
- }
787
- else {
788
- // LavaPlayer missing track or lavaPlayer is falsy
789
- if (currentTrack) {
790
- if (queueTracks.length > 0) {
791
- tracks.push(...queueTracks);
792
- await player.queue.clear();
793
- await player.queue.add(tracks);
794
- }
795
- await node.trackEnd(player, currentTrack, {
796
- reason: Enums_1.TrackEndReasonTypes.Finished,
797
- type: "TrackEndEvent",
798
- });
799
- }
800
- else {
801
- // No current track, check previous queue for last track
802
- const previousQueue = await player.queue.getPrevious();
803
- const lastTrack = previousQueue?.at(-1);
804
- if (lastTrack) {
805
- if (queueTracks.length === 0) {
806
- // If no tracks in queue, end last track
807
- await node.trackEnd(player, lastTrack, {
808
- reason: Enums_1.TrackEndReasonTypes.Finished,
809
- type: "TrackEndEvent",
810
- });
811
- }
812
- else {
813
- // If there are queued tracks, add them
814
- tracks.push(...queueTracks);
815
- if (tracks.length > 0) {
816
- await player.queue.clear();
817
- await player.queue.add(tracks);
818
- }
819
- }
820
- }
821
- else {
822
- if (queueTracks.length > 0) {
823
- tracks.push(...queueTracks);
824
- if (tracks.length > 0) {
825
- await player.queue.clear();
826
- await player.queue.add(tracks);
827
- }
828
- await node.trackEnd(player, lastTrack, {
829
- reason: Enums_1.TrackEndReasonTypes.Finished,
830
- type: "TrackEndEvent",
831
- });
832
- }
833
- }
834
- }
835
- }
836
- if (state.queue.previous.length > 0) {
837
- await player.queue.addPrevious(state.queue.previous);
838
- }
839
- else {
840
- await player.queue.clearPrevious();
841
- }
842
- await player.pause(state.paused);
843
- if (state.trackRepeat)
844
- player.setTrackRepeat(true);
845
- if (state.queueRepeat)
846
- player.setQueueRepeat(true);
847
- if (state.dynamicRepeat && state.dynamicLoopInterval) {
848
- player.setDynamicRepeat(state.dynamicRepeat, state.dynamicLoopInterval);
849
- }
850
- if (state.data) {
851
- for (const [name, value] of Object.entries(state.data)) {
852
- player.set(name, value);
853
- }
854
- }
855
- const filterActions = {
856
- bassboost: () => player.filters.bassBoost(state.filters.bassBoostlevel),
857
- distort: (enabled) => player.filters.distort(enabled),
858
- setDistortion: () => player.filters.setDistortion(state.filters.distortion),
859
- eightD: (enabled) => player.filters.eightD(enabled),
860
- setKaraoke: () => player.filters.setKaraoke(state.filters.karaoke),
861
- nightcore: (enabled) => player.filters.nightcore(enabled),
862
- slowmo: (enabled) => player.filters.slowmo(enabled),
863
- soft: (enabled) => player.filters.soft(enabled),
864
- trebleBass: (enabled) => player.filters.trebleBass(enabled),
865
- setTimescale: () => player.filters.setTimescale(state.filters.timescale),
866
- tv: (enabled) => player.filters.tv(enabled),
867
- vibrato: () => player.filters.setVibrato(state.filters.vibrato),
868
- vaporwave: (enabled) => player.filters.vaporwave(enabled),
869
- pop: (enabled) => player.filters.pop(enabled),
870
- party: (enabled) => player.filters.party(enabled),
871
- earrape: (enabled) => player.filters.earrape(enabled),
872
- electronic: (enabled) => player.filters.electronic(enabled),
873
- radio: (enabled) => player.filters.radio(enabled),
874
- setRotation: () => player.filters.setRotation(state.filters.rotation),
875
- tremolo: (enabled) => player.filters.tremolo(enabled),
876
- china: (enabled) => player.filters.china(enabled),
877
- chipmunk: (enabled) => player.filters.chipmunk(enabled),
878
- darthvader: (enabled) => player.filters.darthvader(enabled),
879
- daycore: (enabled) => player.filters.daycore(enabled),
880
- doubletime: (enabled) => player.filters.doubletime(enabled),
881
- demon: (enabled) => player.filters.demon(enabled),
882
- };
883
- // Iterate through filterStatus and apply the enabled filters
884
- for (const [filter, isEnabled] of Object.entries(state.filters.filterStatus)) {
885
- if (isEnabled && filterActions[filter]) {
886
- filterActions[filter](true);
887
- }
888
- }
889
- // After processing, delete the Redis key
890
- await this.redis.del(key);
891
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted player state from Redis: ${key}`);
892
- this.emit(Enums_1.ManagerEventTypes.PlayerRestored, player, node);
893
- await this.sleep(1000);
894
- }
895
- catch (error) {
896
- console.log(error);
897
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error processing Redis key ${key}: ${error}`);
698
+ }
699
+ case Enums_1.StateStorageType.Redis: {
700
+ try {
701
+ const keys = await this.redis.keys(`${Utils_1.PlayerUtils.getRedisKey()}playerstore:*`);
702
+ for (const key of keys) {
703
+ try {
704
+ const data = await this.redis.get(key);
705
+ if (!data)
898
706
  continue;
899
- }
707
+ const state = JSON.parse(data);
708
+ if (!state || typeof state !== "object")
709
+ continue;
710
+ const guildId = key.split(":").pop();
711
+ if (!guildId)
712
+ continue;
713
+ await this.restorePlayerFromState(node, nodeId, guildId, state, async () => {
714
+ await this.redis.del(key);
715
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted Redis state: ${key}`);
716
+ });
717
+ }
718
+ catch (error) {
719
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error processing Redis key ${key}: ${error}`);
900
720
  }
901
- }
902
- catch (error) {
903
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error loading player states from Redis: ${error}`);
904
721
  }
905
722
  }
723
+ catch (error) {
724
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error loading player states from Redis: ${error}`);
725
+ }
906
726
  break;
907
- default:
908
- break;
727
+ }
909
728
  }
910
729
  this.emit(Enums_1.ManagerEventTypes.Debug, "[MANAGER] Finished loading saved players.");
911
730
  this.emit(Enums_1.ManagerEventTypes.RestoreComplete, node);
@@ -948,8 +767,6 @@ class Manager extends events_1.EventEmitter {
948
767
  }
949
768
  });
950
769
  await Promise.allSettled(savePromises);
951
- if (this.options.stateStorage.deleteInactivePlayers)
952
- await this.cleanupInactivePlayers();
953
770
  setTimeout(() => {
954
771
  console.warn("\x1b[32m%s\x1b[0m", "MAGMASTREAM INFO: Shutting down complete, exiting...");
955
772
  process.exit(0);
@@ -1077,13 +894,10 @@ class Manager extends events_1.EventEmitter {
1077
894
  */
1078
895
  async handleVoiceServerUpdate(player, update) {
1079
896
  player.voiceState.event = update;
1080
- const { sessionId, event: { token, endpoint }, } = player.voiceState;
1081
- await player.node.rest.updatePlayer({
1082
- guildId: player.guildId,
1083
- data: { voice: { token, endpoint, sessionId } },
1084
- });
1085
- this.emit(Enums_1.ManagerEventTypes.Debug, `Updated voice server for player ${player.guildId} with token ${token} and endpoint ${endpoint} and sessionId ${sessionId}`);
1086
- return;
897
+ const sessionId = player.voiceState.sessionId;
898
+ const channelId = player.voiceState.channelId;
899
+ this.emit(Enums_1.ManagerEventTypes.Debug, `Updated voice server for player ${player.guildId} with token ${update.token} | endpoint ${update.endpoint} | sessionId ${sessionId} | channelId ${channelId}`);
900
+ await player.updateVoice();
1087
901
  }
1088
902
  /**
1089
903
  * 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.
@@ -1094,105 +908,23 @@ class Manager extends events_1.EventEmitter {
1094
908
  */
1095
909
  async handleVoiceStateUpdate(player, update) {
1096
910
  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}`);
1097
- if (update.channel_id) {
1098
- if (player.voiceChannelId !== update.channel_id) {
1099
- this.emit(Enums_1.ManagerEventTypes.PlayerMove, player, player.voiceChannelId, update.channel_id);
1100
- }
1101
- player.voiceState.sessionId = update.session_id;
1102
- player.voiceChannelId = update.channel_id;
1103
- player.options.voiceChannelId = update.channel_id;
911
+ if (!update.channel_id) {
912
+ this.emit(Enums_1.ManagerEventTypes.PlayerDisconnect, player, player.voiceChannelId);
913
+ player.voiceChannelId = null;
914
+ player.state = Enums_1.StateTypes.Disconnected;
915
+ player.voiceState = Object.assign({});
916
+ if (player.options.pauseOnDisconnect)
917
+ await player.pause(true);
1104
918
  return;
1105
919
  }
1106
- this.emit(Enums_1.ManagerEventTypes.PlayerDisconnect, player, player.voiceChannelId);
1107
- player.voiceChannelId = null;
1108
- player.state = Enums_1.StateTypes.Disconnected;
1109
- player.voiceState = Object.assign({});
1110
- if (player.options.pauseOnDisconnect) {
1111
- await player.pause(true);
1112
- }
1113
- return;
1114
- }
1115
- /**
1116
- * Cleans up inactive players by removing their state files from the file system.
1117
- * This is done to prevent stale state files from accumulating on the file system.
1118
- */
1119
- async cleanupInactivePlayers() {
1120
- switch (this.options.stateStorage.type) {
1121
- case Enums_1.StateStorageType.JSON:
1122
- {
1123
- const playersBaseDir = Utils_1.PlayerUtils.getPlayersBaseDir();
1124
- try {
1125
- await promises_1.default.mkdir(playersBaseDir, { recursive: true });
1126
- const activeGuildIds = new Set(this.players.keys());
1127
- // Cleanup inactive guild directories inside playersBaseDir
1128
- const guildDirs = await promises_1.default.readdir(playersBaseDir, { withFileTypes: true });
1129
- for (const dirent of guildDirs) {
1130
- if (!dirent.isDirectory())
1131
- continue;
1132
- const guildId = dirent.name;
1133
- if (!activeGuildIds.has(guildId)) {
1134
- const guildPath = Utils_1.PlayerUtils.getGuildDir(guildId);
1135
- await promises_1.default.rm(guildPath, { recursive: true, force: true });
1136
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted inactive player data folder: ${guildId}`);
1137
- }
1138
- }
1139
- }
1140
- catch (err) {
1141
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error cleaning up inactive JSON players: ${err}`);
1142
- const error = err instanceof MagmastreamError_1.MagmaStreamError
1143
- ? err
1144
- : new MagmastreamError_1.MagmaStreamError({
1145
- code: Enums_1.MagmaStreamErrorCode.MANAGER_CLEANUP_INACTIVE_PLAYERS_FAILED,
1146
- message: "Error cleaning up inactive players.",
1147
- cause: err,
1148
- context: { stage: "CLEANUP_INACTIVE_PLAYERS" },
1149
- });
1150
- console.error(error);
1151
- }
1152
- }
1153
- break;
1154
- case Enums_1.StateStorageType.Redis:
1155
- {
1156
- const prefix = this.options.stateStorage.redisConfig.prefix?.endsWith(":")
1157
- ? this.options.stateStorage.redisConfig.prefix
1158
- : (this.options.stateStorage.redisConfig.prefix ?? "magmastream:");
1159
- const pattern = `${prefix}queue:*:current`;
1160
- try {
1161
- const stream = this.redis.scanStream({
1162
- match: pattern,
1163
- count: 100,
1164
- });
1165
- for await (const keys of stream) {
1166
- for (const key of keys) {
1167
- // Extract guildId from queue key
1168
- const match = key.match(new RegExp(`^${prefix}queue:(.+):current$`));
1169
- if (!match)
1170
- continue;
1171
- const guildId = match[1];
1172
- // If player is not active in memory, clean up all keys
1173
- if (!this.players.has(guildId)) {
1174
- await this.redis.del(`${prefix}playerstore:${guildId}`, `${prefix}queue:${guildId}:current`, `${prefix}queue:${guildId}:tracks`, `${prefix}queue:${guildId}:previous`);
1175
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Cleaned inactive Redis player data: ${guildId}`);
1176
- }
1177
- }
1178
- }
1179
- }
1180
- catch (err) {
1181
- const error = err instanceof MagmastreamError_1.MagmaStreamError
1182
- ? err
1183
- : new MagmastreamError_1.MagmaStreamError({
1184
- code: Enums_1.MagmaStreamErrorCode.MANAGER_SHUTDOWN_FAILED,
1185
- message: "Error saving player state.",
1186
- cause: err,
1187
- context: { stage: "CLEANUP_INACTIVE_PLAYERS" },
1188
- });
1189
- console.error(error);
1190
- }
1191
- }
1192
- break;
1193
- default:
1194
- break;
920
+ if (player.voiceChannelId !== update.channel_id) {
921
+ this.emit(Enums_1.ManagerEventTypes.PlayerMove, player, player.voiceChannelId, update.channel_id);
1195
922
  }
923
+ player.voiceState.sessionId = update.session_id;
924
+ player.voiceState.channelId = update.channel_id;
925
+ player.voiceChannelId = update.channel_id;
926
+ player.options.voiceChannelId = update.channel_id;
927
+ await player.updateVoice();
1196
928
  }
1197
929
  /**
1198
930
  * Cleans up an inactive player by removing its state data.
@@ -1228,9 +960,7 @@ class Manager extends events_1.EventEmitter {
1228
960
  {
1229
961
  try {
1230
962
  if (!player) {
1231
- const prefix = this.options.stateStorage.redisConfig.prefix?.endsWith(":")
1232
- ? this.options.stateStorage.redisConfig.prefix
1233
- : `${this.options.stateStorage.redisConfig.prefix ?? "magmastream"}:`;
963
+ const prefix = Utils_1.PlayerUtils.getRedisKey();
1234
964
  const keysToDelete = [
1235
965
  `${prefix}playerstore:${guildId}`,
1236
966
  `${prefix}queue:${guildId}:tracks`,
@@ -1338,9 +1068,7 @@ class Manager extends events_1.EventEmitter {
1338
1068
  break;
1339
1069
  }
1340
1070
  case Enums_1.StateStorageType.Redis: {
1341
- const prefix = this.options.stateStorage.redisConfig.prefix?.endsWith(":")
1342
- ? this.options.stateStorage.redisConfig.prefix
1343
- : (this.options.stateStorage.redisConfig.prefix ?? "magmastream:");
1071
+ const prefix = Utils_1.PlayerUtils.getRedisKey();
1344
1072
  const patterns = [`${prefix}playerstore:*`, `${prefix}queue:*`];
1345
1073
  try {
1346
1074
  for (const pattern of patterns) {
@@ -1439,6 +1167,12 @@ class Manager extends events_1.EventEmitter {
1439
1167
  }
1440
1168
  return this._send(packet);
1441
1169
  }
1170
+ getUserFromCache(id) {
1171
+ return this._getUser?.(id);
1172
+ }
1173
+ getGuildFromCache(id) {
1174
+ return this._getGuild?.(id);
1175
+ }
1442
1176
  sendPacket(packet) {
1443
1177
  return this.send(packet);
1444
1178
  }
@@ -1453,5 +1187,14 @@ class Manager extends events_1.EventEmitter {
1453
1187
  return { id: user }; // fallback by ID only
1454
1188
  return user; // default: just return the portable user
1455
1189
  }
1190
+ /**
1191
+ * Resolves a Guild ID to a real guild object.
1192
+ * Can be overridden by wrapper managers to return wrapper-specific Guild classes.
1193
+ */
1194
+ resolveGuild(guildId) {
1195
+ if (!guildId)
1196
+ return null;
1197
+ return this._getGuild?.(guildId);
1198
+ }
1456
1199
  }
1457
1200
  exports.Manager = Manager;