magmastream 2.9.1-dev.6 → 2.9.2-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -89,22 +89,9 @@
89
89
 
90
90
  ## Used By
91
91
 
92
- | Name | Creator |
93
- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
94
- | [Lava Jukebox](https://discord.com/api/oauth2/authorize?client_id=887651843742793779&permissions=-1&redirect_uri=https%3A%2F%2Fdiscord.gg%2F4ZaXbbYSTZ&response_type=code&scope=guilds.join%20bot%20applications.commands) | Abel Purnwasy |
95
- | [Stal](https://discord.com/oauth2/authorize?client_id=923938180263182356&scope=bot%20applications.commands&permissions=27648861246) | memte |
96
- | [Lunio](https://discord.com/api/oauth2/authorize?client_id=945030475779551415&permissions=61991952&scope=bot+applications.commands) | vexi |
97
- | [JukeDisc](https://discord.com/oauth2/authorize?client_id=1109751797549105176&permissions=968552214080&scope=bot+applications.commands) | Theo |
98
- | [Leo](https://discord.com/oauth2/authorize?client_id=923529398425096193&permissions=12888394808&scope=bot%20identify%20applications.commands) | Itz Random |
99
- | [Soundy](https://dsc.gg/sndy) | iaMJ |
100
- | [HamBot](https://discord.com/oauth2/authorize?client_id=1049314312776335390) | yanishamburger|
101
- | [Miyu](https://discord.com/oauth2/authorize?client_id=1277180179273482280&permissions=572851999731703&response_type=code&redirect_uri=https%3A%2F%2Fdiscord.gg%2Ftn3nbFB8nX&integration_type=0&scope=identify+applications.commands+bot) | Kenver |
102
- | [Savage Bot](https://discord.com/oauth2/authorize?client_id=823703707522433054&permissions=8&scope=bot%20applications.commands) | Savage |
103
- | [rive](https://discord.com/oauth2/authorize?client_id=1384158871207280651) | bob, xrm0s | | pomice |
104
- | [Pepper](https://discord.com/oauth2/authorize?client_id=871808444502540379&permissions=6575631696&integration_type=0&scope=bot+applications.commands) | murlee |
105
-
106
-
107
- Want to showcase your bot? Feel free to create a pull request and add it to our growing list of amazing bots powered by Magmastream!
92
+ The "Used By" section can be found [here](https://magmastream.com/usedby).
93
+
94
+ Want to showcase your bot? Feel free to create a pull request in [the docs repo](https://github.com/Magmastream-NPM/magmastream_documentation) and add it to our growing list of amazing bots powered by Magmastream!
108
95
 
109
96
  ## 👥 Contributors
110
97
 
package/dist/index.d.ts CHANGED
@@ -2136,14 +2136,13 @@ declare class Player {
2136
2136
  getRecommendedTracks(track: Track): Promise<Track[]>;
2137
2137
  /**
2138
2138
  * Sets the volume of the player.
2139
- * @param {number} volume - The new volume. Must be between 0 and 1000.
2139
+ * @param {number} volume - The new volume. Must be between 0 and 500 when using filter mode (100 = 100%).
2140
2140
  * @returns {Promise<Player>} - The updated player.
2141
2141
  * @throws {TypeError} If the volume is not a number.
2142
- * @throws {RangeError} If the volume is not between 0 and 1000.
2142
+ * @throws {RangeError} If the volume is not between 0 and 500 when using filter mode (100 = 100%).
2143
2143
  * @emits {PlayerStateUpdate} - Emitted when the volume is changed.
2144
2144
  * @example
2145
2145
  * player.setVolume(50);
2146
- * player.setVolume(50, { gradual: true, interval: 50, step: 5 });
2147
2146
  */
2148
2147
  setVolume(volume: number): Promise<this>;
2149
2148
  /**
@@ -2249,6 +2248,16 @@ declare class Player {
2249
2248
  * @returns {Promise<Player>} - The new player instance.
2250
2249
  */
2251
2250
  switchGuild(newOptions: PlayerOptions, force?: boolean): Promise<Player>;
2251
+ /**
2252
+ * Retrieves the data associated with the player.
2253
+ * @returns {Record<string, unknown>} - The data associated with the player.
2254
+ */
2255
+ getData(): Record<string, unknown>;
2256
+ /**
2257
+ * Retrieves the dynamic loop interval of the player.
2258
+ * @returns {NodeJS.Timeout | null} - The dynamic loop interval of the player.
2259
+ */
2260
+ getDynamicLoopIntervalPublic(): NodeJS.Timeout | null;
2252
2261
  /**
2253
2262
  * Retrieves the current lyrics for the playing track.
2254
2263
  * @param skipTrackSource - Indicates whether to skip the track source when fetching lyrics.
@@ -3513,6 +3522,7 @@ declare abstract class Structure {
3513
3522
  }
3514
3523
  declare abstract class JSONUtils {
3515
3524
  static safe<T>(obj: T, space?: number): string;
3525
+ static serializeTrack(track: Track): string;
3516
3526
  }
3517
3527
 
3518
3528
  /**
@@ -81,11 +81,17 @@ class JsonQueue {
81
81
  * @param track The track to add.
82
82
  */
83
83
  async addPrevious(track) {
84
+ const max = this.manager.options.maxPreviousTracks;
84
85
  const tracks = Array.isArray(track) ? track : [track];
85
86
  if (!tracks.length)
86
87
  return;
87
88
  const current = await this.getPrevious();
88
- await this.writeJSON(this.previousPath, [...tracks.reverse(), ...current]);
89
+ const newTracks = tracks.filter((t) => !current.some((p) => p.identifier === t.identifier));
90
+ if (!newTracks.length)
91
+ return;
92
+ const updated = [...newTracks.reverse(), ...current];
93
+ const trimmed = updated.slice(0, max);
94
+ await this.writeJSON(this.previousPath, trimmed);
89
95
  }
90
96
  /**
91
97
  * Clears the queue.
@@ -111,6 +111,7 @@ class MemoryQueue extends Array {
111
111
  * @param track The track or tracks to add. Can be a single `Track` or an array of `Track`s.
112
112
  */
113
113
  async addPrevious(track) {
114
+ const max = this.manager.options.maxPreviousTracks;
114
115
  if (Array.isArray(track)) {
115
116
  const newTracks = track.filter((t) => !this.previous.some((p) => p.identifier === t.identifier));
116
117
  this.previous.unshift(...newTracks);
@@ -121,6 +122,9 @@ class MemoryQueue extends Array {
121
122
  this.previous.unshift(track);
122
123
  }
123
124
  }
125
+ if (this.previous.length > max) {
126
+ this.previous = this.previous.slice(0, max);
127
+ }
124
128
  }
125
129
  /**
126
130
  * Clears the queue.
@@ -97,6 +97,8 @@ class RedisQueue {
97
97
  if (!serialized.length)
98
98
  return;
99
99
  await this.redis.lpush(this.previousKey, ...serialized.reverse());
100
+ const max = this.manager.options.maxPreviousTracks;
101
+ await this.redis.ltrim(this.previousKey, 0, max - 1);
100
102
  }
101
103
  /**
102
104
  * Clears the queue.
@@ -333,8 +335,11 @@ class RedisQueue {
333
335
  const tracks = Array.isArray(track) ? track : [track];
334
336
  if (!tracks.length)
335
337
  return;
336
- await this.redis.del(this.previousKey);
337
- await this.redis.rpush(this.previousKey, ...tracks.map(this.serialize));
338
+ await this.redis
339
+ .multi()
340
+ .del(this.previousKey)
341
+ .rpush(this.previousKey, ...tracks.map(this.serialize))
342
+ .exec();
338
343
  }
339
344
  /**
340
345
  * Shuffles the queue.
@@ -442,6 +447,7 @@ class RedisQueue {
442
447
  * Helper to serialize/deserialize Track
443
448
  */
444
449
  serialize(track) {
450
+ // return JSONUtils.serializeTrack(track);
445
451
  return Utils_1.JSONUtils.safe(track, 2);
446
452
  }
447
453
  }
@@ -385,18 +385,18 @@ class Manager extends events_1.EventEmitter {
385
385
  * @param {string} guildId - The guild ID of the player to save
386
386
  */
387
387
  async savePlayerState(guildId) {
388
+ const player = this.getPlayer(guildId);
389
+ if (!player || player.state === Enums_1.StateTypes.Disconnected || !player.voiceChannelId) {
390
+ this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Skipping save for inactive player: ${guildId}`);
391
+ return;
392
+ }
393
+ const serializedPlayer = await Utils_1.PlayerUtils.serializePlayer(player);
388
394
  switch (this.options.stateStorage.type) {
389
395
  case Enums_1.StateStorageType.Memory:
390
396
  case Enums_1.StateStorageType.JSON:
391
397
  {
392
398
  try {
393
399
  const playerStateFilePath = Utils_1.PlayerUtils.getPlayerStatePath(guildId);
394
- const player = this.getPlayer(guildId);
395
- if (!player || player.state === Enums_1.StateTypes.Disconnected || !player.voiceChannelId) {
396
- this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Skipping save for inactive player: ${guildId}`);
397
- return;
398
- }
399
- const serializedPlayer = await Utils_1.PlayerUtils.serializePlayer(player);
400
400
  await promises_1.default.mkdir(path_1.default.dirname(playerStateFilePath), { recursive: true });
401
401
  await promises_1.default.writeFile(playerStateFilePath, Utils_1.JSONUtils.safe(serializedPlayer, 2), "utf-8");
402
402
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Player state saved: ${guildId}`);
@@ -409,16 +409,10 @@ class Manager extends events_1.EventEmitter {
409
409
  case Enums_1.StateStorageType.Redis:
410
410
  {
411
411
  try {
412
- const player = this.getPlayer(guildId);
413
- if (!player || player.state === Enums_1.StateTypes.Disconnected || !player.voiceChannelId) {
414
- console.warn(`[MANAGER] Skipping save for inactive player: ${guildId}`);
415
- return;
416
- }
417
- const serializedPlayer = await Utils_1.PlayerUtils.serializePlayer(player);
418
412
  const redisKey = `${this.options.stateStorage.redisConfig.prefix?.endsWith(":")
419
413
  ? this.options.stateStorage.redisConfig.prefix
420
414
  : this.options.stateStorage.redisConfig.prefix ?? "magmastream:"}playerstore:${guildId}`;
421
- await this.redis.set(redisKey, Utils_1.JSONUtils.safe(serializedPlayer, 2));
415
+ await this.redis.set(redisKey, JSON.stringify(serializedPlayer));
422
416
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Player state saved to Redis: ${guildId}`);
423
417
  }
424
418
  catch (error) {
@@ -429,6 +423,9 @@ class Manager extends events_1.EventEmitter {
429
423
  default:
430
424
  return;
431
425
  }
426
+ await player.queue.clear();
427
+ await player.queue.clearPrevious();
428
+ await player.queue.setCurrent(null);
432
429
  }
433
430
  /**
434
431
  * Sleeps for a specified amount of time.
@@ -487,6 +484,7 @@ class Manager extends events_1.EventEmitter {
487
484
  selfDeafen: state.options.selfDeafen,
488
485
  volume: lavaPlayer.volume || state.options.volume,
489
486
  nodeIdentifier: nodeId,
487
+ applyVolumeAsFilter: state.options.applyVolumeAsFilter,
490
488
  };
491
489
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${state.guildId} from saved file: ${Utils_1.JSONUtils.safe(state.options, 2)}`);
492
490
  const player = this.create(playerOptions);
@@ -580,8 +578,8 @@ class Manager extends events_1.EventEmitter {
580
578
  player.setTrackRepeat(true);
581
579
  if (state.queueRepeat)
582
580
  player.setQueueRepeat(true);
583
- if (state.dynamicRepeat) {
584
- player.setDynamicRepeat(state.dynamicRepeat, state.dynamicLoopInterval._idleTimeout);
581
+ if (state.dynamicRepeat && state.dynamicLoopInterval) {
582
+ player.setDynamicRepeat(state.dynamicRepeat, state.dynamicLoopInterval);
585
583
  }
586
584
  if (state.data) {
587
585
  for (const [name, value] of Object.entries(state.data)) {
@@ -686,6 +684,7 @@ class Manager extends events_1.EventEmitter {
686
684
  selfDeafen: state.options.selfDeafen,
687
685
  volume: lavaPlayer?.volume || state.options.volume,
688
686
  nodeIdentifier: nodeId,
687
+ applyVolumeAsFilter: state.options.applyVolumeAsFilter,
689
688
  };
690
689
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Recreating player: ${guildId} from Redis`);
691
690
  const player = this.create(playerOptions);
@@ -783,8 +782,8 @@ class Manager extends events_1.EventEmitter {
783
782
  player.setTrackRepeat(true);
784
783
  if (state.queueRepeat)
785
784
  player.setQueueRepeat(true);
786
- if (state.dynamicRepeat) {
787
- player.setDynamicRepeat(state.dynamicRepeat, state.dynamicLoopInterval._idleTimeout);
785
+ if (state.dynamicRepeat && state.dynamicLoopInterval) {
786
+ player.setDynamicRepeat(state.dynamicRepeat, state.dynamicLoopInterval);
788
787
  }
789
788
  if (state.data) {
790
789
  for (const [name, value] of Object.entries(state.data)) {
@@ -833,12 +832,14 @@ class Manager extends events_1.EventEmitter {
833
832
  }
834
833
  }
835
834
  catch (error) {
835
+ console.log(error);
836
836
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error processing Redis key ${key}: ${error}`);
837
837
  continue;
838
838
  }
839
839
  }
840
840
  }
841
841
  catch (error) {
842
+ console.log(error);
842
843
  this.emit(Enums_1.ManagerEventTypes.Debug, `[MANAGER] Error loading player states from Redis: ${error}`);
843
844
  }
844
845
  }
@@ -874,7 +875,6 @@ class Manager extends events_1.EventEmitter {
874
875
  this.unloadPlugins();
875
876
  console.warn("\x1b[31m%s\x1b[0m", "MAGMASTREAM WARNING: Shutting down! Please wait, saving active players...");
876
877
  try {
877
- await this.clearAllStoredPlayers();
878
878
  const savePromises = Array.from(this.players.keys()).map(async (guildId) => {
879
879
  try {
880
880
  await this.savePlayerState(guildId);
@@ -883,9 +883,9 @@ class Manager extends events_1.EventEmitter {
883
883
  console.error(`[MANAGER] Error saving player state for guild ${guildId}:`, error);
884
884
  }
885
885
  });
886
+ await Promise.allSettled(savePromises);
886
887
  if (this.options.stateStorage.deleteInactivePlayers)
887
888
  await this.cleanupInactivePlayers();
888
- await Promise.allSettled(savePromises);
889
889
  setTimeout(() => {
890
890
  console.warn("\x1b[32m%s\x1b[0m", "MAGMASTREAM INFO: Shutting down complete, exiting...");
891
891
  process.exit(0);
@@ -636,11 +636,6 @@ class Node {
636
636
  const current = await player.queue.getCurrent();
637
637
  if (!skipFlag && (previous.length === 0 || (previous[0] && previous[0].track !== current?.track))) {
638
638
  await player.queue.addPrevious(current);
639
- const updated = await player.queue.getPrevious();
640
- if (updated.length > this.manager.options.maxPreviousTracks) {
641
- const trimmed = updated.slice(0, this.manager.options.maxPreviousTracks);
642
- await player.queue.setPrevious(trimmed);
643
- }
644
639
  }
645
640
  player.set("skipFlag", false);
646
641
  const oldPlayer = player;
@@ -407,28 +407,36 @@ class Player {
407
407
  }
408
408
  /**
409
409
  * Sets the volume of the player.
410
- * @param {number} volume - The new volume. Must be between 0 and 1000.
410
+ * @param {number} volume - The new volume. Must be between 0 and 500 when using filter mode (100 = 100%).
411
411
  * @returns {Promise<Player>} - The updated player.
412
412
  * @throws {TypeError} If the volume is not a number.
413
- * @throws {RangeError} If the volume is not between 0 and 1000.
413
+ * @throws {RangeError} If the volume is not between 0 and 500 when using filter mode (100 = 100%).
414
414
  * @emits {PlayerStateUpdate} - Emitted when the volume is changed.
415
415
  * @example
416
416
  * player.setVolume(50);
417
- * player.setVolume(50, { gradual: true, interval: 50, step: 5 });
418
417
  */
419
418
  async setVolume(volume) {
420
419
  if (isNaN(volume))
421
420
  throw new TypeError("Volume must be a number.");
422
- if (volume < 0 || volume > 1000)
423
- throw new RangeError("Volume must be between 0 and 1000.");
421
+ if (this.options.applyVolumeAsFilter) {
422
+ if (volume < 0 || volume > 500) {
423
+ throw new RangeError("Volume must be between 0 and 500 when using filter mode (100 = 100%).");
424
+ }
425
+ }
426
+ else {
427
+ if (volume < 0 || volume > 1000) {
428
+ throw new RangeError("Volume must be between 0 and 1000.");
429
+ }
430
+ }
424
431
  const oldVolume = this.volume;
425
432
  const oldPlayer = { ...this };
426
- const data = this.options.applyVolumeAsFilter ? { filters: { volume } } : { volume };
433
+ const data = this.options.applyVolumeAsFilter ? { filters: { volume: volume / 100 } } : { volume };
427
434
  await this.node.rest.updatePlayer({
428
435
  guildId: this.options.guildId,
429
436
  data,
430
437
  });
431
438
  this.volume = volume;
439
+ this.options.volume = volume;
432
440
  this.manager.emit(Enums_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this, {
433
441
  changeType: Enums_1.PlayerStateEventTypes.VolumeChange,
434
442
  details: {
@@ -917,6 +925,20 @@ class Player {
917
925
  // Return the cloned player
918
926
  return clonedPlayer;
919
927
  }
928
+ /**
929
+ * Retrieves the data associated with the player.
930
+ * @returns {Record<string, unknown>} - The data associated with the player.
931
+ */
932
+ getData() {
933
+ return this.data;
934
+ }
935
+ /**
936
+ * Retrieves the dynamic loop interval of the player.
937
+ * @returns {NodeJS.Timeout | null} - The dynamic loop interval of the player.
938
+ */
939
+ getDynamicLoopIntervalPublic() {
940
+ return this.dynamicLoopInterval;
941
+ }
920
942
  /**
921
943
  * Retrieves the current lyrics for the playing track.
922
944
  * @param skipTrackSource - Indicates whether to skip the track source when fetching lyrics.
@@ -612,52 +612,59 @@ class PlayerUtils {
612
612
  * @returns The serialized Player instance
613
613
  */
614
614
  static async serializePlayer(player) {
615
- const current = await player.queue.getCurrent();
616
- const tracks = Array.isArray(await player.queue.getTracks()) ? await player.queue.getTracks() : [];
617
- const previous = Array.isArray(await player.queue.getPrevious()) ? await player.queue.getPrevious() : [];
618
- const seen = new WeakSet();
619
- // The replacer function
620
- const replacer = (key, value) => {
621
- if (value && typeof value === "object") {
622
- if (seen.has(value))
623
- return;
624
- seen.add(value);
625
- }
626
- if (key === "manager")
627
- return null;
628
- if (key === "filters") {
629
- if (!value || typeof value !== "object")
615
+ try {
616
+ const current = await player.queue.getCurrent();
617
+ const tracks = await player.queue.getTracks();
618
+ const previous = await player.queue.getPrevious();
619
+ const serializeTrack = (track) => ({
620
+ ...track,
621
+ requester: track.requester ? { id: track.requester.id, username: track.requester.username } : null,
622
+ });
623
+ const safeNode = player.node
624
+ ? JSON.parse(JSON.stringify(player.node, (key, value) => {
625
+ if (key === "rest" || key === "players" || key === "shards" || key === "manager")
626
+ return undefined;
627
+ return value;
628
+ }))
629
+ : null;
630
+ return JSON.parse(JSON.stringify(player, (key, value) => {
631
+ if (key === "manager")
630
632
  return null;
631
- const filters = value;
632
- return {
633
- distortion: filters.distortion ?? null,
634
- equalizer: filters.equalizer ?? [],
635
- karaoke: filters.karaoke ?? null,
636
- rotation: filters.rotation ?? null,
637
- timescale: filters.timescale ?? null,
638
- vibrato: filters.vibrato ?? null,
639
- reverb: filters.reverb ?? null,
640
- volume: filters.volume ?? 1.0,
641
- bassBoostlevel: filters.bassBoostlevel ?? null,
642
- filterStatus: filters.filtersStatus ? { ...filters.filtersStatus } : {},
643
- };
644
- }
645
- if (key === "queue") {
646
- return { current, tracks, previous };
647
- }
648
- if (key === "data") {
649
- const data = value;
650
- const AutoplayUser = data?.Internal_AutoplayUser;
651
- const serializedUser = AutoplayUser ? { id: AutoplayUser.id, username: AutoplayUser.username } : null;
652
- return {
653
- clientUser: serializedUser,
654
- autoplayTries: data?.autoplayTries ?? null,
655
- };
656
- }
657
- return value;
658
- };
659
- const jsonString = (0, safe_stable_stringify_1.default)(player, replacer, 2);
660
- return JSON.parse(jsonString);
633
+ if (key === "node")
634
+ return safeNode;
635
+ if (key === "filters") {
636
+ return {
637
+ distortion: value?.distortion ?? null,
638
+ equalizer: value?.equalizer ?? [],
639
+ karaoke: value?.karaoke ?? null,
640
+ rotation: value?.rotation ?? null,
641
+ timescale: value?.timescale ?? null,
642
+ vibrato: value?.vibrato ?? null,
643
+ reverb: value?.reverb ?? null,
644
+ volume: value?.volume ?? 1.0,
645
+ bassBoostlevel: value?.bassBoostlevel ?? null,
646
+ filterStatus: value?.filtersStatus ? { ...value.filtersStatus } : {},
647
+ };
648
+ }
649
+ if (key === "queue") {
650
+ return {
651
+ current: current ? serializeTrack(current) : null,
652
+ tracks: tracks.map(serializeTrack),
653
+ previous: previous.map(serializeTrack),
654
+ };
655
+ }
656
+ if (key === "data") {
657
+ return {
658
+ clientUser: value?.Internal_BotUser ?? null,
659
+ };
660
+ }
661
+ return value;
662
+ }));
663
+ }
664
+ catch (error) {
665
+ console.log(error);
666
+ return null;
667
+ }
661
668
  }
662
669
  /**
663
670
  * Gets the base directory for player data.
@@ -727,6 +734,13 @@ class JSONUtils {
727
734
  static safe(obj, space) {
728
735
  return (0, safe_stable_stringify_1.default)(obj, null, space);
729
736
  }
737
+ static serializeTrack(track) {
738
+ const serialized = {
739
+ ...track,
740
+ requester: track.requester ? { id: track.requester.id, username: track.requester.username } : null,
741
+ };
742
+ return JSON.stringify(serialized);
743
+ }
730
744
  }
731
745
  exports.JSONUtils = JSONUtils;
732
746
  const structures = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magmastream",
3
- "version": "2.9.1-dev.6",
3
+ "version": "2.9.2-dev.1",
4
4
  "description": "A user-friendly Lavalink client designed for NodeJS.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",