magmastream 2.9.3-dev.3 → 2.9.3-dev.30

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