magmastream 2.9.2-dev.1 → 2.9.2-dev.2

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.
@@ -14,6 +14,7 @@ const path_1 = tslib_1.__importDefault(require("path"));
14
14
  const ioredis_1 = tslib_1.__importDefault(require("ioredis"));
15
15
  const Enums_1 = require("./Enums");
16
16
  const package_json_1 = require("../../package.json");
17
+ const MagmastreamError_1 = require("./MagmastreamError");
17
18
  /**
18
19
  * The main hub for interacting with Lavalink and using Magmastream.
19
20
  */
@@ -105,8 +106,16 @@ class Manager extends events_1.EventEmitter {
105
106
  process.exit(0);
106
107
  }, 2000);
107
108
  }
108
- catch (error) {
109
- console.error(`[MANAGER] Error during shutdown: ${error}`);
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);
110
119
  process.exit(1);
111
120
  }
112
121
  });
@@ -117,8 +126,16 @@ class Manager extends events_1.EventEmitter {
117
126
  console.warn("\x1b[32mShutdown complete. Exiting now...\x1b[0m");
118
127
  process.exit(0);
119
128
  }
120
- catch (error) {
121
- console.error(`[MANAGER] Error during SIGTERM shutdown: ${error}`);
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);
122
139
  process.exit(1);
123
140
  }
124
141
  });
@@ -136,7 +153,11 @@ class Manager extends events_1.EventEmitter {
136
153
  const { clientId, clusterId = 0 } = options;
137
154
  if (clientId !== undefined) {
138
155
  if (typeof clientId !== "string" || !/^\d+$/.test(clientId)) {
139
- throw new Error('"clientId" must be a valid Discord client ID.');
156
+ throw new MagmastreamError_1.MagmaStreamError({
157
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_INIT_FAILED,
158
+ message: '"clientId" must be a valid Discord client ID.',
159
+ context: { clientId },
160
+ });
140
161
  }
141
162
  this.options.clientId = clientId;
142
163
  }
@@ -161,7 +182,15 @@ class Manager extends events_1.EventEmitter {
161
182
  await node.connect();
162
183
  }
163
184
  catch (err) {
164
- this.emit(Enums_1.ManagerEventTypes.NodeError, node, err);
185
+ const error = err instanceof MagmastreamError_1.MagmaStreamError
186
+ ? err
187
+ : new MagmastreamError_1.MagmaStreamError({
188
+ code: Enums_1.MagmaStreamErrorCode.NODE_CONNECT_FAILED,
189
+ message: `Failed to connect node "${node.options.identifier}".`,
190
+ cause: err instanceof Error ? err : undefined,
191
+ context: { nodeId: node.options.identifier },
192
+ });
193
+ this.emit(Enums_1.ManagerEventTypes.NodeError, node, error);
165
194
  }
166
195
  }
167
196
  this.loadPlugins();
@@ -176,8 +205,13 @@ class Manager extends events_1.EventEmitter {
176
205
  */
177
206
  async search(query, requester) {
178
207
  const node = this.useableNode;
179
- if (!node)
180
- throw new Error("No available nodes.");
208
+ if (!node) {
209
+ throw new MagmastreamError_1.MagmaStreamError({
210
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_NO_NODES,
211
+ message: "No available nodes to perform the search.",
212
+ context: { query, requester },
213
+ });
214
+ }
181
215
  const _query = typeof query === "string" ? { query } : query;
182
216
  const _source = _query.source ?? this.options.defaultSearchPlatform;
183
217
  const isUrl = /^https?:\/\//.test(_query.query);
@@ -185,8 +219,13 @@ class Manager extends events_1.EventEmitter {
185
219
  this.emit(Enums_1.ManagerEventTypes.Debug, isUrl ? `[MANAGER] Performing search for: ${_query.query}` : `[MANAGER] Performing ${_source} search for: ${_query.query}`);
186
220
  try {
187
221
  const res = (await node.rest.get(`/v4/loadtracks?identifier=${encodeURIComponent(search)}`));
188
- if (!res)
189
- throw new Error("Query not found.");
222
+ if (!res) {
223
+ throw new MagmastreamError_1.MagmaStreamError({
224
+ code: Enums_1.MagmaStreamErrorCode.REST_REQUEST_FAILED,
225
+ message: `No results returned from Lavalink for query "${search}".`,
226
+ context: { query: search, requester },
227
+ });
228
+ }
190
229
  let result;
191
230
  switch (res.loadType) {
192
231
  case Enums_1.LoadTypes.Search: {
@@ -245,7 +284,14 @@ class Manager extends events_1.EventEmitter {
245
284
  return result;
246
285
  }
247
286
  catch (err) {
248
- throw new Error(`An error occurred while searching: ${err}`);
287
+ throw err instanceof MagmastreamError_1.MagmaStreamError
288
+ ? err
289
+ : new MagmastreamError_1.MagmaStreamError({
290
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_SEARCH_FAILED,
291
+ message: `An error occurred while searching: ${err instanceof Error ? err.message : String(err)}`,
292
+ cause: err instanceof Error ? err : undefined,
293
+ context: { query, requester },
294
+ });
249
295
  }
250
296
  }
251
297
  /**
@@ -311,7 +357,11 @@ class Manager extends events_1.EventEmitter {
311
357
  const node = this.nodes.get(identifier);
312
358
  if (!node) {
313
359
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Tried to destroy non-existent node: ${identifier}`);
314
- return;
360
+ throw new MagmastreamError_1.MagmaStreamError({
361
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_NODE_NOT_FOUND,
362
+ message: "Node not found.",
363
+ context: { identifier },
364
+ });
315
365
  }
316
366
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Destroying node: ${identifier}`);
317
367
  this.nodes.delete(identifier);
@@ -359,12 +409,19 @@ class Manager extends events_1.EventEmitter {
359
409
  async decodeTracks(tracks) {
360
410
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Decoding tracks: ${Utils_1.JSONUtils.safe(tracks, 2)}`);
361
411
  return new Promise(async (resolve, reject) => {
362
- const node = this.nodes.first();
363
- if (!node)
364
- throw new Error("No available nodes.");
412
+ const node = this.useableNode;
413
+ if (!node) {
414
+ throw new MagmastreamError_1.MagmaStreamError({
415
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_NO_NODES,
416
+ message: "No available nodes to decode tracks.",
417
+ });
418
+ }
365
419
  const res = (await node.rest.post("/v4/decodetracks", Utils_1.JSONUtils.safe(tracks, 2)).catch((err) => reject(err)));
366
420
  if (!res) {
367
- return reject(new Error("No data returned from query."));
421
+ throw new MagmastreamError_1.MagmaStreamError({
422
+ code: Enums_1.MagmaStreamErrorCode.REST_REQUEST_FAILED,
423
+ message: "No decoded tracks returned from node.",
424
+ });
368
425
  }
369
426
  return resolve(res);
370
427
  });
@@ -443,9 +500,14 @@ class Manager extends events_1.EventEmitter {
443
500
  async loadPlayerStates(nodeId) {
444
501
  this.emit(Enums_1.ManagerEventTypes.Debug, "[MANAGER] Loading saved players.");
445
502
  const node = this.nodes.get(nodeId);
446
- if (!node)
447
- throw new Error(`Could not find node: ${nodeId}`);
448
- const info = (await node.rest.getAllPlayers());
503
+ if (!node) {
504
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Tried to load non-existent node: ${nodeId}`);
505
+ throw new MagmastreamError_1.MagmaStreamError({
506
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_NODE_NOT_FOUND,
507
+ message: "Node not found.",
508
+ context: { nodeId },
509
+ });
510
+ }
449
511
  switch (this.options.stateStorage.type) {
450
512
  case Enums_1.StateStorageType.Memory:
451
513
  case Enums_1.StateStorageType.JSON:
@@ -472,7 +534,7 @@ class Manager extends events_1.EventEmitter {
472
534
  continue;
473
535
  if (!state.guildId || state.node?.options?.identifier !== nodeId)
474
536
  continue;
475
- const lavaPlayer = info.find((player) => player.guildId === state.guildId);
537
+ const lavaPlayer = await node.rest.getPlayer(state.guildId);
476
538
  if (!lavaPlayer) {
477
539
  await this.destroy(state.guildId);
478
540
  continue;
@@ -509,15 +571,16 @@ class Manager extends events_1.EventEmitter {
509
571
  player.setAutoplay(true, autoPlayUser, state.autoplayTries);
510
572
  }
511
573
  }
512
- if (lavaPlayer?.track) {
513
- tracks.push(...queueTracks);
514
- if (currentTrack && currentTrack.uri === lavaPlayer.track.info.uri) {
515
- await player.queue.setCurrent(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester));
574
+ if (lavaPlayer.track) {
575
+ await player.queue.clear();
576
+ if (currentTrack) {
577
+ await player.queue.add(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester));
516
578
  }
517
- if (tracks.length > 0) {
518
- await player.queue.clear();
519
- await player.queue.add(tracks);
579
+ const remainingQueue = queueTracks.filter((t) => t.uri !== lavaPlayer.track.info.uri);
580
+ if (remainingQueue.length > 0) {
581
+ await player.queue.add(remainingQueue);
520
582
  }
583
+ player.playing = !lavaPlayer.paused;
521
584
  }
522
585
  else {
523
586
  if (currentTrack) {
@@ -568,12 +631,7 @@ class Manager extends events_1.EventEmitter {
568
631
  else {
569
632
  await player.queue.clearPrevious();
570
633
  }
571
- if (state.paused) {
572
- await player.pause(true);
573
- }
574
- else {
575
- player.paused = false;
576
- }
634
+ await player.pause(state.paused);
577
635
  if (state.trackRepeat)
578
636
  player.setTrackRepeat(true);
579
637
  if (state.queueRepeat)
@@ -670,166 +728,159 @@ class Manager extends events_1.EventEmitter {
670
728
  if (!state || typeof state !== "object" || state.clusterId !== this.options.clusterId)
671
729
  continue;
672
730
  const guildId = key.split(":").pop();
673
- if (!guildId)
731
+ if (!guildId || state.node.options.identifier !== nodeId)
674
732
  continue;
675
- if (state.node?.options?.identifier === nodeId) {
676
- const lavaPlayer = info.find((player) => player.guildId === guildId);
677
- if (!lavaPlayer) {
678
- await this.destroy(guildId);
679
- }
680
- const playerOptions = {
681
- guildId: state.options.guildId,
682
- textChannelId: state.options.textChannelId,
683
- voiceChannelId: state.options.voiceChannelId,
684
- selfDeafen: state.options.selfDeafen,
685
- volume: lavaPlayer?.volume || state.options.volume,
686
- nodeIdentifier: nodeId,
687
- applyVolumeAsFilter: state.options.applyVolumeAsFilter,
688
- };
689
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${guildId} from Redis`);
690
- const player = this.create(playerOptions);
691
- await player.node.rest.updatePlayer({
692
- guildId: state.options.guildId,
693
- data: { voice: { token: state.voiceState.event.token, endpoint: state.voiceState.event.endpoint, sessionId: state.voiceState.sessionId } },
694
- });
733
+ const lavaPlayer = await node.rest.getPlayer(state.guildId);
734
+ if (!lavaPlayer) {
735
+ await this.destroy(guildId);
736
+ }
737
+ const playerOptions = {
738
+ guildId: state.options.guildId,
739
+ textChannelId: state.options.textChannelId,
740
+ voiceChannelId: state.options.voiceChannelId,
741
+ selfDeafen: state.options.selfDeafen,
742
+ volume: lavaPlayer.volume || state.options.volume,
743
+ nodeIdentifier: nodeId,
744
+ applyVolumeAsFilter: state.options.applyVolumeAsFilter,
745
+ };
746
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${guildId} from Redis`);
747
+ const player = this.create(playerOptions);
748
+ await player.node.rest.updatePlayer({
749
+ guildId: state.options.guildId,
750
+ data: { voice: { token: state.voiceState.event.token, endpoint: state.voiceState.event.endpoint, sessionId: state.voiceState.sessionId } },
751
+ });
752
+ if (!lavaPlayer || !lavaPlayer.state.connected) {
695
753
  player.connect();
696
- // Rest of the player state restoration code (tracks, filters, etc.)
697
- const tracks = [];
698
- const currentTrack = state.queue.current;
699
- const queueTracks = state.queue.tracks;
700
- if (state.isAutoplay) {
701
- const savedUser = state.data.clientUser;
702
- if (savedUser) {
703
- const autoPlayUser = await player.manager.resolveUser(savedUser);
704
- player.setAutoplay(true, autoPlayUser, state.autoplayTries);
705
- }
754
+ }
755
+ // Rest of the player state restoration code (tracks, filters, etc.)
756
+ const tracks = [];
757
+ const currentTrack = state.queue.current;
758
+ const queueTracks = state.queue.tracks;
759
+ if (state.isAutoplay) {
760
+ const savedUser = state.data.clientUser;
761
+ if (savedUser) {
762
+ const autoPlayUser = await player.manager.resolveUser(savedUser);
763
+ player.setAutoplay(true, autoPlayUser, state.autoplayTries);
706
764
  }
707
- if (lavaPlayer?.track) {
708
- // If lavaPlayer has a track, push all queue tracks
709
- tracks.push(...queueTracks);
710
- // Set current track if matches lavaPlayer's track URI
711
- if (currentTrack && currentTrack.uri === lavaPlayer.track.info.uri) {
712
- await player.queue.setCurrent(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester));
713
- }
714
- // Add tracks to queue
715
- if (tracks.length > 0) {
765
+ }
766
+ if (lavaPlayer.track) {
767
+ await player.queue.clear();
768
+ if (currentTrack) {
769
+ await player.queue.add(Utils_1.TrackUtils.build(lavaPlayer.track, currentTrack.requester));
770
+ }
771
+ const remainingQueue = queueTracks.filter((t) => t.uri !== lavaPlayer.track.info.uri);
772
+ if (remainingQueue.length > 0) {
773
+ await player.queue.add(remainingQueue);
774
+ }
775
+ player.playing = !lavaPlayer.paused;
776
+ }
777
+ else {
778
+ // LavaPlayer missing track or lavaPlayer is falsy
779
+ if (currentTrack) {
780
+ if (queueTracks.length > 0) {
781
+ tracks.push(...queueTracks);
716
782
  await player.queue.clear();
717
783
  await player.queue.add(tracks);
718
784
  }
785
+ await node.trackEnd(player, currentTrack, {
786
+ reason: Enums_1.TrackEndReasonTypes.Finished,
787
+ type: "TrackEndEvent",
788
+ });
719
789
  }
720
790
  else {
721
- // LavaPlayer missing track or lavaPlayer is falsy
722
- if (currentTrack) {
723
- if (queueTracks.length > 0) {
791
+ // No current track, check previous queue for last track
792
+ const previousQueue = await player.queue.getPrevious();
793
+ const lastTrack = previousQueue?.at(-1);
794
+ if (lastTrack) {
795
+ if (queueTracks.length === 0) {
796
+ // If no tracks in queue, end last track
797
+ await node.trackEnd(player, lastTrack, {
798
+ reason: Enums_1.TrackEndReasonTypes.Finished,
799
+ type: "TrackEndEvent",
800
+ });
801
+ }
802
+ else {
803
+ // If there are queued tracks, add them
724
804
  tracks.push(...queueTracks);
725
- await player.queue.clear();
726
- await player.queue.add(tracks);
805
+ if (tracks.length > 0) {
806
+ await player.queue.clear();
807
+ await player.queue.add(tracks);
808
+ }
727
809
  }
728
- await node.trackEnd(player, currentTrack, {
729
- reason: Enums_1.TrackEndReasonTypes.Finished,
730
- type: "TrackEndEvent",
731
- });
732
810
  }
733
811
  else {
734
- // No current track, check previous queue for last track
735
- const previousQueue = await player.queue.getPrevious();
736
- const lastTrack = previousQueue?.at(-1);
737
- if (lastTrack) {
738
- if (queueTracks.length === 0) {
739
- // If no tracks in queue, end last track
740
- await node.trackEnd(player, lastTrack, {
741
- reason: Enums_1.TrackEndReasonTypes.Finished,
742
- type: "TrackEndEvent",
743
- });
744
- }
745
- else {
746
- // If there are queued tracks, add them
747
- tracks.push(...queueTracks);
748
- if (tracks.length > 0) {
749
- await player.queue.clear();
750
- await player.queue.add(tracks);
751
- }
752
- }
753
- }
754
- else {
755
- if (queueTracks.length > 0) {
756
- tracks.push(...queueTracks);
757
- if (tracks.length > 0) {
758
- await player.queue.clear();
759
- await player.queue.add(tracks);
760
- }
761
- await node.trackEnd(player, lastTrack, {
762
- reason: Enums_1.TrackEndReasonTypes.Finished,
763
- type: "TrackEndEvent",
764
- });
812
+ if (queueTracks.length > 0) {
813
+ tracks.push(...queueTracks);
814
+ if (tracks.length > 0) {
815
+ await player.queue.clear();
816
+ await player.queue.add(tracks);
765
817
  }
818
+ await node.trackEnd(player, lastTrack, {
819
+ reason: Enums_1.TrackEndReasonTypes.Finished,
820
+ type: "TrackEndEvent",
821
+ });
766
822
  }
767
823
  }
768
824
  }
769
- if (state.queue.previous.length > 0) {
770
- await player.queue.addPrevious(state.queue.previous);
771
- }
772
- else {
773
- await player.queue.clearPrevious();
774
- }
775
- if (state.paused) {
776
- await player.pause(true);
777
- }
778
- else {
779
- player.paused = false;
780
- }
781
- if (state.trackRepeat)
782
- player.setTrackRepeat(true);
783
- if (state.queueRepeat)
784
- player.setQueueRepeat(true);
785
- if (state.dynamicRepeat && state.dynamicLoopInterval) {
786
- player.setDynamicRepeat(state.dynamicRepeat, state.dynamicLoopInterval);
787
- }
788
- if (state.data) {
789
- for (const [name, value] of Object.entries(state.data)) {
790
- player.set(name, value);
791
- }
825
+ }
826
+ if (state.queue.previous.length > 0) {
827
+ await player.queue.addPrevious(state.queue.previous);
828
+ }
829
+ else {
830
+ await player.queue.clearPrevious();
831
+ }
832
+ await player.pause(state.paused);
833
+ if (state.trackRepeat)
834
+ player.setTrackRepeat(true);
835
+ if (state.queueRepeat)
836
+ player.setQueueRepeat(true);
837
+ if (state.dynamicRepeat && state.dynamicLoopInterval) {
838
+ player.setDynamicRepeat(state.dynamicRepeat, state.dynamicLoopInterval);
839
+ }
840
+ if (state.data) {
841
+ for (const [name, value] of Object.entries(state.data)) {
842
+ player.set(name, value);
792
843
  }
793
- const filterActions = {
794
- bassboost: () => player.filters.bassBoost(state.filters.bassBoostlevel),
795
- distort: (enabled) => player.filters.distort(enabled),
796
- setDistortion: () => player.filters.setDistortion(state.filters.distortion),
797
- eightD: (enabled) => player.filters.eightD(enabled),
798
- setKaraoke: () => player.filters.setKaraoke(state.filters.karaoke),
799
- nightcore: (enabled) => player.filters.nightcore(enabled),
800
- slowmo: (enabled) => player.filters.slowmo(enabled),
801
- soft: (enabled) => player.filters.soft(enabled),
802
- trebleBass: (enabled) => player.filters.trebleBass(enabled),
803
- setTimescale: () => player.filters.setTimescale(state.filters.timescale),
804
- tv: (enabled) => player.filters.tv(enabled),
805
- vibrato: () => player.filters.setVibrato(state.filters.vibrato),
806
- vaporwave: (enabled) => player.filters.vaporwave(enabled),
807
- pop: (enabled) => player.filters.pop(enabled),
808
- party: (enabled) => player.filters.party(enabled),
809
- earrape: (enabled) => player.filters.earrape(enabled),
810
- electronic: (enabled) => player.filters.electronic(enabled),
811
- radio: (enabled) => player.filters.radio(enabled),
812
- setRotation: () => player.filters.setRotation(state.filters.rotation),
813
- tremolo: (enabled) => player.filters.tremolo(enabled),
814
- china: (enabled) => player.filters.china(enabled),
815
- chipmunk: (enabled) => player.filters.chipmunk(enabled),
816
- darthvader: (enabled) => player.filters.darthvader(enabled),
817
- daycore: (enabled) => player.filters.daycore(enabled),
818
- doubletime: (enabled) => player.filters.doubletime(enabled),
819
- demon: (enabled) => player.filters.demon(enabled),
820
- };
821
- // Iterate through filterStatus and apply the enabled filters
822
- for (const [filter, isEnabled] of Object.entries(state.filters.filterStatus)) {
823
- if (isEnabled && filterActions[filter]) {
824
- filterActions[filter](true);
825
- }
844
+ }
845
+ const filterActions = {
846
+ bassboost: () => player.filters.bassBoost(state.filters.bassBoostlevel),
847
+ distort: (enabled) => player.filters.distort(enabled),
848
+ setDistortion: () => player.filters.setDistortion(state.filters.distortion),
849
+ eightD: (enabled) => player.filters.eightD(enabled),
850
+ setKaraoke: () => player.filters.setKaraoke(state.filters.karaoke),
851
+ nightcore: (enabled) => player.filters.nightcore(enabled),
852
+ slowmo: (enabled) => player.filters.slowmo(enabled),
853
+ soft: (enabled) => player.filters.soft(enabled),
854
+ trebleBass: (enabled) => player.filters.trebleBass(enabled),
855
+ setTimescale: () => player.filters.setTimescale(state.filters.timescale),
856
+ tv: (enabled) => player.filters.tv(enabled),
857
+ vibrato: () => player.filters.setVibrato(state.filters.vibrato),
858
+ vaporwave: (enabled) => player.filters.vaporwave(enabled),
859
+ pop: (enabled) => player.filters.pop(enabled),
860
+ party: (enabled) => player.filters.party(enabled),
861
+ earrape: (enabled) => player.filters.earrape(enabled),
862
+ electronic: (enabled) => player.filters.electronic(enabled),
863
+ radio: (enabled) => player.filters.radio(enabled),
864
+ setRotation: () => player.filters.setRotation(state.filters.rotation),
865
+ tremolo: (enabled) => player.filters.tremolo(enabled),
866
+ china: (enabled) => player.filters.china(enabled),
867
+ chipmunk: (enabled) => player.filters.chipmunk(enabled),
868
+ darthvader: (enabled) => player.filters.darthvader(enabled),
869
+ daycore: (enabled) => player.filters.daycore(enabled),
870
+ doubletime: (enabled) => player.filters.doubletime(enabled),
871
+ demon: (enabled) => player.filters.demon(enabled),
872
+ };
873
+ // Iterate through filterStatus and apply the enabled filters
874
+ for (const [filter, isEnabled] of Object.entries(state.filters.filterStatus)) {
875
+ if (isEnabled && filterActions[filter]) {
876
+ filterActions[filter](true);
826
877
  }
827
- // After processing, delete the Redis key
828
- await this.redis.del(key);
829
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted player state from Redis: ${key}`);
830
- this.emit(Enums_1.ManagerEventTypes.PlayerRestored, player, node);
831
- await this.sleep(1000);
832
878
  }
879
+ // After processing, delete the Redis key
880
+ await this.redis.del(key);
881
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted player state from Redis: ${key}`);
882
+ this.emit(Enums_1.ManagerEventTypes.PlayerRestored, player, node);
883
+ await this.sleep(1000);
833
884
  }
834
885
  catch (error) {
835
886
  console.log(error);
@@ -839,7 +890,6 @@ class Manager extends events_1.EventEmitter {
839
890
  }
840
891
  }
841
892
  catch (error) {
842
- console.log(error);
843
893
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error loading player states from Redis: ${error}`);
844
894
  }
845
895
  }
@@ -879,8 +929,16 @@ class Manager extends events_1.EventEmitter {
879
929
  try {
880
930
  await this.savePlayerState(guildId);
881
931
  }
882
- catch (error) {
883
- console.error(`[MANAGER] Error saving player state for guild ${guildId}:`, error);
932
+ catch (err) {
933
+ const error = err instanceof MagmastreamError_1.MagmaStreamError
934
+ ? err
935
+ : new MagmastreamError_1.MagmaStreamError({
936
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_SHUTDOWN_FAILED,
937
+ message: "Error saving player state.",
938
+ cause: err,
939
+ context: { guildId },
940
+ });
941
+ console.error(error);
884
942
  }
885
943
  });
886
944
  await Promise.allSettled(savePromises);
@@ -891,8 +949,16 @@ class Manager extends events_1.EventEmitter {
891
949
  process.exit(0);
892
950
  }, 500);
893
951
  }
894
- catch (error) {
895
- console.error(`[MANAGER] Unexpected error during shutdown:`, error);
952
+ catch (err) {
953
+ const error = err instanceof MagmastreamError_1.MagmaStreamError
954
+ ? err
955
+ : new MagmastreamError_1.MagmaStreamError({
956
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_SHUTDOWN_FAILED,
957
+ message: "Error saving player state.",
958
+ cause: err,
959
+ context: { stage: "SHUTDOWN" },
960
+ });
961
+ console.error(error);
896
962
  process.exit(1);
897
963
  }
898
964
  }
@@ -1062,10 +1128,18 @@ class Manager extends events_1.EventEmitter {
1062
1128
  }
1063
1129
  }
1064
1130
  }
1065
- catch (error) {
1066
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error cleaning up inactive JSON players: ${error}`);
1131
+ catch (err) {
1132
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error cleaning up inactive JSON players: ${err}`);
1133
+ const error = err instanceof MagmastreamError_1.MagmaStreamError
1134
+ ? err
1135
+ : new MagmastreamError_1.MagmaStreamError({
1136
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_CLEANUP_INACTIVE_PLAYERS_FAILED,
1137
+ message: "Error cleaning up inactive players.",
1138
+ cause: err,
1139
+ context: { stage: "CLEANUP_INACTIVE_PLAYERS" },
1140
+ });
1141
+ console.error(error);
1067
1142
  }
1068
- return;
1069
1143
  }
1070
1144
  break;
1071
1145
  case Enums_1.StateStorageType.Redis:
@@ -1074,25 +1148,37 @@ class Manager extends events_1.EventEmitter {
1074
1148
  ? this.options.stateStorage.redisConfig.prefix
1075
1149
  : this.options.stateStorage.redisConfig.prefix ?? "magmastream:";
1076
1150
  const pattern = `${prefix}queue:*:current`;
1077
- const stream = this.redis.scanStream({
1078
- match: pattern,
1079
- count: 100,
1080
- });
1081
- for await (const keys of stream) {
1082
- for (const key of keys) {
1083
- // Extract guildId from queue key
1084
- const match = key.match(new RegExp(`^${prefix}queue:(.+):current$`));
1085
- if (!match)
1086
- continue;
1087
- const guildId = match[1];
1088
- // If player is not active in memory, clean up all keys
1089
- if (!this.players.has(guildId)) {
1090
- await this.redis.del(`${prefix}playerstore:${guildId}`, `${prefix}queue:${guildId}:current`, `${prefix}queue:${guildId}:tracks`, `${prefix}queue:${guildId}:previous`);
1091
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Cleaned inactive Redis player data: ${guildId}`);
1151
+ try {
1152
+ const stream = this.redis.scanStream({
1153
+ match: pattern,
1154
+ count: 100,
1155
+ });
1156
+ for await (const keys of stream) {
1157
+ for (const key of keys) {
1158
+ // Extract guildId from queue key
1159
+ const match = key.match(new RegExp(`^${prefix}queue:(.+):current$`));
1160
+ if (!match)
1161
+ continue;
1162
+ const guildId = match[1];
1163
+ // If player is not active in memory, clean up all keys
1164
+ if (!this.players.has(guildId)) {
1165
+ await this.redis.del(`${prefix}playerstore:${guildId}`, `${prefix}queue:${guildId}:current`, `${prefix}queue:${guildId}:tracks`, `${prefix}queue:${guildId}:previous`);
1166
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Cleaned inactive Redis player data: ${guildId}`);
1167
+ }
1092
1168
  }
1093
1169
  }
1094
1170
  }
1095
- return;
1171
+ catch (err) {
1172
+ const error = err instanceof MagmastreamError_1.MagmaStreamError
1173
+ ? err
1174
+ : new MagmastreamError_1.MagmaStreamError({
1175
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_SHUTDOWN_FAILED,
1176
+ message: "Error saving player state.",
1177
+ cause: err,
1178
+ context: { stage: "CLEANUP_INACTIVE_PLAYERS" },
1179
+ });
1180
+ console.error(error);
1181
+ }
1096
1182
  }
1097
1183
  break;
1098
1184
  default:
@@ -1105,38 +1191,57 @@ class Manager extends events_1.EventEmitter {
1105
1191
  * @param guildId The guild ID of the player to clean up.
1106
1192
  */
1107
1193
  async cleanupInactivePlayer(guildId) {
1194
+ const player = this.getPlayer(guildId);
1108
1195
  switch (this.options.stateStorage.type) {
1109
1196
  case Enums_1.StateStorageType.JSON:
1110
1197
  {
1111
1198
  try {
1112
- if (!this.players.has(guildId)) {
1199
+ if (!player) {
1113
1200
  const guildDir = Utils_1.PlayerUtils.getGuildDir(guildId);
1114
1201
  await promises_1.default.rm(guildDir, { recursive: true, force: true });
1115
1202
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted inactive player data folder: ${guildId}`);
1116
1203
  }
1117
1204
  }
1118
- catch (error) {
1119
- if (error.code !== "ENOENT") {
1120
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error deleting player files for ${guildId}: ${error}`);
1121
- }
1205
+ catch (err) {
1206
+ const error = err instanceof MagmastreamError_1.MagmaStreamError
1207
+ ? err
1208
+ : new MagmastreamError_1.MagmaStreamError({
1209
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_CLEANUP_INACTIVE_PLAYERS_FAILED,
1210
+ message: "Error cleaning up inactive player.",
1211
+ cause: err,
1212
+ context: { guildId },
1213
+ });
1214
+ console.error(error);
1122
1215
  }
1123
1216
  }
1124
1217
  break;
1125
1218
  case Enums_1.StateStorageType.Redis:
1126
1219
  {
1127
- const player = this.getPlayer(guildId);
1128
- if (!player) {
1129
- const prefix = this.options.stateStorage.redisConfig.prefix?.endsWith(":")
1130
- ? this.options.stateStorage.redisConfig.prefix
1131
- : `${this.options.stateStorage.redisConfig.prefix ?? "magmastream"}:`;
1132
- const keysToDelete = [
1133
- `${prefix}playerstore:${guildId}`,
1134
- `${prefix}queue:${guildId}:tracks`,
1135
- `${prefix}queue:${guildId}:current`,
1136
- `${prefix}queue:${guildId}:previous`,
1137
- ];
1138
- await this.redis.del(...keysToDelete);
1139
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted Redis player and queue data for: ${guildId}`);
1220
+ try {
1221
+ if (!player) {
1222
+ const prefix = this.options.stateStorage.redisConfig.prefix?.endsWith(":")
1223
+ ? this.options.stateStorage.redisConfig.prefix
1224
+ : `${this.options.stateStorage.redisConfig.prefix ?? "magmastream"}:`;
1225
+ const keysToDelete = [
1226
+ `${prefix}playerstore:${guildId}`,
1227
+ `${prefix}queue:${guildId}:tracks`,
1228
+ `${prefix}queue:${guildId}:current`,
1229
+ `${prefix}queue:${guildId}:previous`,
1230
+ ];
1231
+ await this.redis.del(...keysToDelete);
1232
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Deleted Redis player and queue data for: ${guildId}`);
1233
+ }
1234
+ }
1235
+ catch (err) {
1236
+ const error = err instanceof MagmastreamError_1.MagmaStreamError
1237
+ ? err
1238
+ : new MagmastreamError_1.MagmaStreamError({
1239
+ code: Enums_1.MagmaStreamErrorCode.MANAGER_CLEANUP_INACTIVE_PLAYERS_FAILED,
1240
+ message: "Error cleaning up inactive player.",
1241
+ cause: err,
1242
+ context: { guildId },
1243
+ });
1244
+ console.error(error);
1140
1245
  }
1141
1246
  }
1142
1247
  break;
@@ -1151,8 +1256,13 @@ class Manager extends events_1.EventEmitter {
1151
1256
  if (!Array.isArray(this.options.enabledPlugins))
1152
1257
  return;
1153
1258
  for (const [index, plugin] of this.options.enabledPlugins.entries()) {
1259
+ // Validate plugin class
1154
1260
  if (!(plugin instanceof __1.Plugin)) {
1155
- throw new RangeError(`Plugin at index ${index} does not extend Plugin.`);
1261
+ throw new MagmastreamError_1.MagmaStreamError({
1262
+ code: Enums_1.MagmaStreamErrorCode.PLUGIN_LOAD_FAILED,
1263
+ message: `Plugin at index ${index} does not extend Plugin.`,
1264
+ context: { index, plugin },
1265
+ });
1156
1266
  }
1157
1267
  try {
1158
1268
  plugin.load(this);
@@ -1160,7 +1270,15 @@ class Manager extends events_1.EventEmitter {
1160
1270
  this.emit(Enums_1.ManagerEventTypes.Debug, `[PLUGIN] Loaded plugin: ${plugin.name}`);
1161
1271
  }
1162
1272
  catch (err) {
1163
- this.emit(Enums_1.ManagerEventTypes.Debug, `[PLUGIN] Failed to load plugin "${plugin.name}": ${err}`);
1273
+ const error = err instanceof MagmastreamError_1.MagmaStreamError
1274
+ ? err
1275
+ : new MagmastreamError_1.MagmaStreamError({
1276
+ code: Enums_1.MagmaStreamErrorCode.PLUGIN_RUNTIME_ERROR,
1277
+ message: `Failed to load plugin "${plugin.name}".`,
1278
+ cause: err instanceof Error ? err : undefined,
1279
+ context: { pluginName: plugin.name, index },
1280
+ });
1281
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[PLUGIN] ${error.name}: ${error.message}`);
1164
1282
  }
1165
1283
  }
1166
1284
  }
@@ -1174,7 +1292,15 @@ class Manager extends events_1.EventEmitter {
1174
1292
  this.emit(Enums_1.ManagerEventTypes.Debug, `[PLUGIN] Unloaded plugin: ${plugin.name}`);
1175
1293
  }
1176
1294
  catch (err) {
1177
- this.emit(Enums_1.ManagerEventTypes.Debug, `[PLUGIN] Failed to unload plugin "${plugin.name}": ${err}`);
1295
+ const error = err instanceof MagmastreamError_1.MagmaStreamError
1296
+ ? err
1297
+ : new MagmastreamError_1.MagmaStreamError({
1298
+ code: Enums_1.MagmaStreamErrorCode.PLUGIN_RUNTIME_ERROR,
1299
+ message: `Failed to unload plugin "${plugin.name}".`,
1300
+ cause: err instanceof Error ? err : undefined,
1301
+ context: { pluginName: plugin.name },
1302
+ });
1303
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[PLUGIN] ${error.name}: ${error.message}`);
1178
1304
  }
1179
1305
  }
1180
1306
  this.loadedPlugins.clear();