magmastream 2.9.0-dev.0 → 2.9.0-dev.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +178 -68
- package/dist/storage/CollectionPlayerStore.js +80 -0
- package/dist/storage/RedisPlayerStore.js +159 -0
- package/dist/structures/Manager.js +77 -40
- package/dist/structures/Node.js +67 -238
- package/dist/structures/Player.js +45 -152
- package/dist/structures/Queue.js +100 -47
- package/dist/structures/RedisQueue.js +301 -0
- package/dist/structures/Rest.js +1 -1
- package/dist/structures/Utils.js +345 -344
- package/dist/utils/logExecutionTime.js +11 -0
- package/dist/utils/managerCheck.js +24 -21
- package/dist/utils/nodeCheck.js +25 -25
- package/package.json +6 -5
|
@@ -9,6 +9,7 @@ const Queue_1 = require("./Queue");
|
|
|
9
9
|
const Utils_1 = require("./Utils");
|
|
10
10
|
const _ = tslib_1.__importStar(require("lodash"));
|
|
11
11
|
const playerCheck_1 = tslib_1.__importDefault(require("../utils/playerCheck"));
|
|
12
|
+
const RedisQueue_1 = require("./RedisQueue");
|
|
12
13
|
class Player {
|
|
13
14
|
options;
|
|
14
15
|
/** The Queue for the Player. */
|
|
@@ -50,7 +51,7 @@ class Player {
|
|
|
50
51
|
/** The autoplay state of the player. */
|
|
51
52
|
isAutoplay = false;
|
|
52
53
|
/** The number of times to try autoplay before emitting queueEnd. */
|
|
53
|
-
autoplayTries =
|
|
54
|
+
autoplayTries = 3;
|
|
54
55
|
static _manager;
|
|
55
56
|
data = {};
|
|
56
57
|
dynamicLoopInterval = null;
|
|
@@ -67,10 +68,6 @@ class Player {
|
|
|
67
68
|
this.manager = Utils_1.Structure.get("Player")._manager;
|
|
68
69
|
if (!this.manager)
|
|
69
70
|
throw new RangeError("Manager has not been initiated.");
|
|
70
|
-
// If a player with the same guild ID already exists, return it.
|
|
71
|
-
if (this.manager.players.has(options.guildId)) {
|
|
72
|
-
return this.manager.players.get(options.guildId);
|
|
73
|
-
}
|
|
74
71
|
// Check the player options for errors.
|
|
75
72
|
(0, playerCheck_1.default)(options);
|
|
76
73
|
// Set the guild ID and voice state.
|
|
@@ -91,8 +88,15 @@ class Player {
|
|
|
91
88
|
if (!this.node)
|
|
92
89
|
throw new RangeError("No available nodes.");
|
|
93
90
|
// Initialize the queue with the guild ID and manager.
|
|
94
|
-
this.
|
|
95
|
-
|
|
91
|
+
if (this.manager.options.stateStorage.type === Manager_1.StateStorageType.Redis) {
|
|
92
|
+
this.queue = new RedisQueue_1.RedisQueue(this.guildId, this.manager);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
this.queue = new Queue_1.Queue(this.guildId, this.manager);
|
|
96
|
+
}
|
|
97
|
+
if (this.queue instanceof Queue_1.Queue) {
|
|
98
|
+
this.queue.previous = [];
|
|
99
|
+
}
|
|
96
100
|
// Add the player to the manager's player collection.
|
|
97
101
|
this.manager.players.set(options.guildId, this);
|
|
98
102
|
// Set the initial volume.
|
|
@@ -225,7 +229,7 @@ class Player {
|
|
|
225
229
|
await this.disconnect();
|
|
226
230
|
}
|
|
227
231
|
await this.node.rest.destroyPlayer(this.guildId);
|
|
228
|
-
this.queue.clear();
|
|
232
|
+
await this.queue.clear();
|
|
229
233
|
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, null, {
|
|
230
234
|
changeType: Manager_1.PlayerStateEventTypes.PlayerDestroy,
|
|
231
235
|
});
|
|
@@ -307,9 +311,9 @@ class Player {
|
|
|
307
311
|
}
|
|
308
312
|
async play(optionsOrTrack, playOptions) {
|
|
309
313
|
if (typeof optionsOrTrack !== "undefined" && Utils_1.TrackUtils.validate(optionsOrTrack)) {
|
|
310
|
-
this.queue.
|
|
314
|
+
await this.queue.setCurrent(optionsOrTrack);
|
|
311
315
|
}
|
|
312
|
-
if (!this.queue.
|
|
316
|
+
if (!(await this.queue.getCurrent()))
|
|
313
317
|
throw new RangeError("No current track.");
|
|
314
318
|
const finalOptions = playOptions
|
|
315
319
|
? playOptions
|
|
@@ -319,7 +323,7 @@ class Player {
|
|
|
319
323
|
await this.node.rest.updatePlayer({
|
|
320
324
|
guildId: this.guildId,
|
|
321
325
|
data: {
|
|
322
|
-
encodedTrack: this.queue.
|
|
326
|
+
encodedTrack: (await this.queue.getCurrent()).track,
|
|
323
327
|
...finalOptions,
|
|
324
328
|
},
|
|
325
329
|
});
|
|
@@ -375,119 +379,9 @@ class Player {
|
|
|
375
379
|
* @returns {Promise<Track[]>} - Array of recommended tracks.
|
|
376
380
|
*/
|
|
377
381
|
async getRecommendedTracks(track) {
|
|
378
|
-
const tracks = await Utils_1.AutoPlayUtils.getRecommendedTracks(
|
|
382
|
+
const tracks = await Utils_1.AutoPlayUtils.getRecommendedTracks(track);
|
|
379
383
|
return tracks;
|
|
380
|
-
// const node = this.manager.useableNode;
|
|
381
|
-
// if (!node) {
|
|
382
|
-
// throw new Error("No available nodes.");
|
|
383
|
-
// }
|
|
384
|
-
// if (!TrackUtils.validate(track)) {
|
|
385
|
-
// throw new RangeError('"Track must be a "Track" or "Track[]');
|
|
386
|
-
// }
|
|
387
|
-
// // Get the Last.fm API key and the available source managers
|
|
388
|
-
// const apiKey = this.manager.options.lastFmApiKey;
|
|
389
|
-
// const enabledSources = node.info.sourceManagers;
|
|
390
|
-
// // Determine if YouTube should be used
|
|
391
|
-
// if (!apiKey && enabledSources.includes("youtube")) {
|
|
392
|
-
// // Use YouTube-based autoplay
|
|
393
|
-
// return await this.handleYouTubeRecommendations(track);
|
|
394
|
-
// }
|
|
395
|
-
// if (!apiKey) return [];
|
|
396
|
-
// // Handle Last.fm-based autoplay (or other platforms)
|
|
397
|
-
// const selectedSource = node.selectPlatform(enabledSources);
|
|
398
|
-
// if (selectedSource) {
|
|
399
|
-
// // Use the selected source to handle autoplay
|
|
400
|
-
// return await this.handlePlatformAutoplay(track, selectedSource, apiKey);
|
|
401
|
-
// }
|
|
402
|
-
// // If no source is available, return false
|
|
403
|
-
// return [];
|
|
404
384
|
}
|
|
405
|
-
// /**
|
|
406
|
-
// * Handles YouTube-based recommendations.
|
|
407
|
-
// * @param {Track} track - The track to find recommendations for.
|
|
408
|
-
// * @returns {Promise<Track[]>} - Array of recommended tracks.
|
|
409
|
-
// */
|
|
410
|
-
// private async handleYouTubeRecommendations(track: Track): Promise<Track[]> {
|
|
411
|
-
// // Check if the previous track has a YouTube URL
|
|
412
|
-
// const hasYouTubeURL = ["youtube.com", "youtu.be"].some((url) => track.uri.includes(url));
|
|
413
|
-
// // Get the video ID from the previous track's URL
|
|
414
|
-
// let videoID: string | null = null;
|
|
415
|
-
// if (hasYouTubeURL) {
|
|
416
|
-
// videoID = track.uri.split("=").pop();
|
|
417
|
-
// } else {
|
|
418
|
-
// const searchResult = await this.manager.search({ query: `${track.author} - ${track.title}`, source: SearchPlatform.YouTube }, track.requester);
|
|
419
|
-
// videoID = searchResult.tracks[0]?.uri.split("=").pop();
|
|
420
|
-
// }
|
|
421
|
-
// // If the video ID is not found, return false
|
|
422
|
-
// if (!videoID) return [];
|
|
423
|
-
// // Get a random video index between 2 and 24
|
|
424
|
-
// let randomIndex: number;
|
|
425
|
-
// let searchURI: string;
|
|
426
|
-
// do {
|
|
427
|
-
// // Generate a random index between 2 and 24
|
|
428
|
-
// randomIndex = Math.floor(Math.random() * 23) + 2;
|
|
429
|
-
// // Build the search URI
|
|
430
|
-
// searchURI = `https://www.youtube.com/watch?v=${videoID}&list=RD${videoID}&index=${randomIndex}`;
|
|
431
|
-
// } while (track.uri.includes(searchURI));
|
|
432
|
-
// // Search for the video and return false if the search fails
|
|
433
|
-
// const res = await this.manager.search({ query: searchURI, source: SearchPlatform.YouTube }, track.requester);
|
|
434
|
-
// if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) return [];
|
|
435
|
-
// // Return all track titles that do not have the same URI as the track.uri from before
|
|
436
|
-
// return res.tracks.filter((t) => t.uri !== track.uri);
|
|
437
|
-
// }
|
|
438
|
-
// /**
|
|
439
|
-
// * Handles Last.fm-based autoplay (or other platforms).
|
|
440
|
-
// * @param {Track} track - The track to find recommendations for.
|
|
441
|
-
// * @param {SearchPlatform} source - The selected search platform.
|
|
442
|
-
// * @param {string} apiKey - The Last.fm API key.
|
|
443
|
-
// * @returns {Promise<Track[]>} - Array of recommended tracks.
|
|
444
|
-
// */
|
|
445
|
-
// private async handlePlatformAutoplay(track: Track, source: SearchPlatform, apiKey: string): Promise<Track[]> {
|
|
446
|
-
// let { author: artist } = track;
|
|
447
|
-
// const { title } = track;
|
|
448
|
-
// if (!artist || !title) {
|
|
449
|
-
// if (!title) {
|
|
450
|
-
// // No title provided, search for the artist's top tracks
|
|
451
|
-
// const noTitleUrl = `https://ws.audioscrobbler.com/2.0/?method=artist.getTopTracks&artist=${artist}&autocorrect=1&api_key=${apiKey}&format=json`;
|
|
452
|
-
// const response = await axios.get(noTitleUrl);
|
|
453
|
-
// if (response.data.error || !response.data.toptracks?.track?.length) return [];
|
|
454
|
-
// const randomTrack = response.data.toptracks.track[Math.floor(Math.random() * response.data.toptracks.track.length)];
|
|
455
|
-
// const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: source }, track.requester);
|
|
456
|
-
// if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) return [];
|
|
457
|
-
// const filteredTracks = res.tracks.filter((t) => t.uri !== track.uri);
|
|
458
|
-
// if (!filteredTracks) return [];
|
|
459
|
-
// return filteredTracks;
|
|
460
|
-
// }
|
|
461
|
-
// if (!artist) {
|
|
462
|
-
// // No artist provided, search for the track title
|
|
463
|
-
// const noArtistUrl = `https://ws.audioscrobbler.com/2.0/?method=track.search&track=${title}&api_key=${apiKey}&format=json`;
|
|
464
|
-
// const response = await axios.get(noArtistUrl);
|
|
465
|
-
// artist = response.data.results.trackmatches?.track?.[0]?.artist;
|
|
466
|
-
// if (!artist) return [];
|
|
467
|
-
// }
|
|
468
|
-
// }
|
|
469
|
-
// // Search for similar tracks to the current track
|
|
470
|
-
// const url = `https://ws.audioscrobbler.com/2.0/?method=track.getSimilar&artist=${artist}&track=${title}&limit=10&autocorrect=1&api_key=${apiKey}&format=json`;
|
|
471
|
-
// let response: axios.AxiosResponse;
|
|
472
|
-
// try {
|
|
473
|
-
// response = await axios.get(url);
|
|
474
|
-
// } catch (error) {
|
|
475
|
-
// if (error) return [];
|
|
476
|
-
// }
|
|
477
|
-
// if (response.data.error || !response.data.similartracks?.track?.length) {
|
|
478
|
-
// // Retry the request if the first attempt fails
|
|
479
|
-
// const retryUrl = `https://ws.audioscrobbler.com/2.0/?method=artist.getTopTracks&artist=${artist}&autocorrect=1&api_key=${apiKey}&format=json`;
|
|
480
|
-
// const retryResponse = await axios.get(retryUrl);
|
|
481
|
-
// if (retryResponse.data.error || !retryResponse.data.toptracks?.track?.length) return [];
|
|
482
|
-
// const randomTrack = retryResponse.data.toptracks.track[Math.floor(Math.random() * retryResponse.data.toptracks.track.length)];
|
|
483
|
-
// const res = await this.manager.search({ query: `${randomTrack.artist.name} - ${randomTrack.name}`, source: source }, track.requester);
|
|
484
|
-
// if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) return [];
|
|
485
|
-
// const filteredTracks = res.tracks.filter((t) => t.uri !== track.uri);
|
|
486
|
-
// if (!filteredTracks) return [];
|
|
487
|
-
// return filteredTracks;
|
|
488
|
-
// }
|
|
489
|
-
// return response.data.similartracks.track.filter((t: { uri: string }) => t.uri !== track.uri);
|
|
490
|
-
// }
|
|
491
385
|
/**
|
|
492
386
|
* Sets the volume of the player.
|
|
493
387
|
* @param {number} volume - The new volume. Must be between 0 and 1000.
|
|
@@ -619,13 +513,13 @@ class Player {
|
|
|
619
513
|
* @throws {TypeError} If the repeat parameter is not a boolean.
|
|
620
514
|
* @throws {RangeError} If the queue size is less than or equal to 1.
|
|
621
515
|
*/
|
|
622
|
-
setDynamicRepeat(repeat, ms) {
|
|
516
|
+
async setDynamicRepeat(repeat, ms) {
|
|
623
517
|
// Validate the repeat parameter
|
|
624
518
|
if (typeof repeat !== "boolean") {
|
|
625
519
|
throw new TypeError('Repeat can only be "true" or "false".');
|
|
626
520
|
}
|
|
627
521
|
// Ensure the queue has more than one track for dynamic repeat
|
|
628
|
-
if (this.queue.size <= 1) {
|
|
522
|
+
if ((await this.queue.size()) <= 1) {
|
|
629
523
|
throw new RangeError("The queue size must be greater than 1.");
|
|
630
524
|
}
|
|
631
525
|
// Clone the current player state for comparison
|
|
@@ -636,15 +530,14 @@ class Player {
|
|
|
636
530
|
this.queueRepeat = false;
|
|
637
531
|
this.dynamicRepeat = true;
|
|
638
532
|
// Set an interval to shuffle the queue periodically
|
|
639
|
-
this.dynamicLoopInterval = setInterval(() => {
|
|
533
|
+
this.dynamicLoopInterval = setInterval(async () => {
|
|
640
534
|
if (!this.dynamicRepeat)
|
|
641
535
|
return;
|
|
642
536
|
// Shuffle the queue and replace it with the shuffled tracks
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
});
|
|
537
|
+
const tracks = await this.queue.getTracks();
|
|
538
|
+
const shuffled = _.shuffle(tracks);
|
|
539
|
+
await this.queue.clear();
|
|
540
|
+
await this.queue.add(shuffled);
|
|
648
541
|
}, ms);
|
|
649
542
|
// Store the ms value
|
|
650
543
|
this.dynamicRepeatIntervalMs = ms;
|
|
@@ -675,9 +568,9 @@ class Player {
|
|
|
675
568
|
*/
|
|
676
569
|
async restart() {
|
|
677
570
|
// Check if there is a current track in the queue
|
|
678
|
-
if (!this.queue.
|
|
571
|
+
if (!(await this.queue.getCurrent())?.track) {
|
|
679
572
|
// If the queue has tracks, play the next one
|
|
680
|
-
if (this.queue.
|
|
573
|
+
if (await this.queue.size())
|
|
681
574
|
await this.play();
|
|
682
575
|
return this;
|
|
683
576
|
}
|
|
@@ -686,7 +579,7 @@ class Player {
|
|
|
686
579
|
guildId: this.guildId,
|
|
687
580
|
data: {
|
|
688
581
|
position: 0,
|
|
689
|
-
encodedTrack: this.queue.
|
|
582
|
+
encodedTrack: (await this.queue.getCurrent())?.track,
|
|
690
583
|
},
|
|
691
584
|
});
|
|
692
585
|
return this;
|
|
@@ -701,10 +594,10 @@ class Player {
|
|
|
701
594
|
const oldPlayer = { ...this };
|
|
702
595
|
let removedTracks = [];
|
|
703
596
|
if (typeof amount === "number" && amount > 1) {
|
|
704
|
-
if (amount > this.queue.
|
|
597
|
+
if (amount > (await this.queue.size()))
|
|
705
598
|
throw new RangeError("Cannot skip more than the queue length.");
|
|
706
|
-
removedTracks = this.queue.
|
|
707
|
-
this.queue.
|
|
599
|
+
removedTracks = await this.queue.getSlice(0, amount - 1);
|
|
600
|
+
await this.queue.modifyAt(0, amount - 1);
|
|
708
601
|
}
|
|
709
602
|
this.node.rest.updatePlayer({
|
|
710
603
|
guildId: this.guildId,
|
|
@@ -764,7 +657,7 @@ class Player {
|
|
|
764
657
|
*/
|
|
765
658
|
async previous() {
|
|
766
659
|
// Check if there are previous tracks in the queue.
|
|
767
|
-
if (!this.queue.
|
|
660
|
+
if (!(await this.queue.getPrevious()).length) {
|
|
768
661
|
throw new Error("No previous track available.");
|
|
769
662
|
}
|
|
770
663
|
// Capture the current state of the player before making changes.
|
|
@@ -772,7 +665,7 @@ class Player {
|
|
|
772
665
|
// Store the current track before changing it.
|
|
773
666
|
// let currentTrackBeforeChange: Track | null = this.queue.current ? (this.queue.current as Track) : null;
|
|
774
667
|
// Get the last played track and remove it from the history
|
|
775
|
-
const lastTrack = this.queue.
|
|
668
|
+
const lastTrack = (await this.queue.getPrevious()).pop();
|
|
776
669
|
// Set the skip flag to true to prevent the onTrackEnd event from playing the next track.
|
|
777
670
|
this.set("skipFlag", true);
|
|
778
671
|
await this.play(lastTrack);
|
|
@@ -798,7 +691,7 @@ class Player {
|
|
|
798
691
|
* @emits {PlayerStateUpdate} - With {@link PlayerStateEventTypes.TrackChange} as the change type.
|
|
799
692
|
*/
|
|
800
693
|
async seek(position) {
|
|
801
|
-
if (!this.queue.
|
|
694
|
+
if (!(await this.queue.getCurrent()))
|
|
802
695
|
return undefined;
|
|
803
696
|
position = Number(position);
|
|
804
697
|
// Check if the position is valid.
|
|
@@ -808,8 +701,8 @@ class Player {
|
|
|
808
701
|
// Get the old player state.
|
|
809
702
|
const oldPlayer = this ? { ...this } : null;
|
|
810
703
|
// Clamp the position to ensure it is within the valid range.
|
|
811
|
-
if (position < 0 || position > this.queue.
|
|
812
|
-
position = Math.max(Math.min(position, this.queue.
|
|
704
|
+
if (position < 0 || position > (await this.queue.getCurrent()).duration) {
|
|
705
|
+
position = Math.max(Math.min(position, (await this.queue.getCurrent()).duration), 0);
|
|
813
706
|
}
|
|
814
707
|
// Update the player's position.
|
|
815
708
|
this.position = position;
|
|
@@ -874,7 +767,7 @@ class Player {
|
|
|
874
767
|
try {
|
|
875
768
|
const playerPosition = this.position;
|
|
876
769
|
const { sessionId, event: { token, endpoint }, } = this.voiceState;
|
|
877
|
-
const currentTrack = this.queue.
|
|
770
|
+
const currentTrack = (await this.queue.getCurrent()) ? await this.queue.getCurrent() : null;
|
|
878
771
|
await this.node.rest.destroyPlayer(this.guildId).catch(() => { });
|
|
879
772
|
this.manager.players.delete(this.guildId);
|
|
880
773
|
this.node = node;
|
|
@@ -905,7 +798,7 @@ class Player {
|
|
|
905
798
|
if (!newOptions.textChannelId)
|
|
906
799
|
throw new Error("Text channel ID is required");
|
|
907
800
|
// Check if a player already exists for the new guild
|
|
908
|
-
let newPlayer = this.manager.
|
|
801
|
+
let newPlayer = await this.manager.getPlayer(newOptions.guildId);
|
|
909
802
|
// If the player already exists and force is false, return the existing player
|
|
910
803
|
if (newPlayer && !force)
|
|
911
804
|
return newPlayer;
|
|
@@ -916,9 +809,9 @@ class Player {
|
|
|
916
809
|
volume: this.volume,
|
|
917
810
|
position: this.position,
|
|
918
811
|
queue: {
|
|
919
|
-
current: this.queue.
|
|
920
|
-
tracks: [...this.queue],
|
|
921
|
-
previous: [...this.queue.
|
|
812
|
+
current: await this.queue.getCurrent(),
|
|
813
|
+
tracks: [...(await this.queue.getTracks())],
|
|
814
|
+
previous: [...(await this.queue.getPrevious())],
|
|
922
815
|
},
|
|
923
816
|
trackRepeat: this.trackRepeat,
|
|
924
817
|
queueRepeat: this.queueRepeat,
|
|
@@ -938,7 +831,7 @@ class Player {
|
|
|
938
831
|
newOptions.selfMute = newOptions.selfMute ?? oldPlayerProperties.selfMute;
|
|
939
832
|
newOptions.volume = newOptions.volume ?? oldPlayerProperties.volume;
|
|
940
833
|
// Deep clone the current player
|
|
941
|
-
const clonedPlayer = this.manager.create(newOptions);
|
|
834
|
+
const clonedPlayer = await this.manager.create(newOptions);
|
|
942
835
|
// Connect the cloned player to the new voice channel
|
|
943
836
|
clonedPlayer.connect();
|
|
944
837
|
// Update the player's state on the Lavalink node
|
|
@@ -951,9 +844,9 @@ class Player {
|
|
|
951
844
|
encodedTrack: oldPlayerProperties.queue.current?.track,
|
|
952
845
|
},
|
|
953
846
|
});
|
|
954
|
-
clonedPlayer.queue.
|
|
955
|
-
clonedPlayer.queue.
|
|
956
|
-
clonedPlayer.queue.add(oldPlayerProperties.queue.tracks);
|
|
847
|
+
await clonedPlayer.queue.setCurrent(oldPlayerProperties.queue.current);
|
|
848
|
+
await clonedPlayer.queue.addPrevious(oldPlayerProperties.queue.previous);
|
|
849
|
+
await clonedPlayer.queue.add(oldPlayerProperties.queue.tracks);
|
|
957
850
|
clonedPlayer.filters = oldPlayerProperties.filters;
|
|
958
851
|
clonedPlayer.isAutoplay = oldPlayerProperties.isAutoplay;
|
|
959
852
|
clonedPlayer.nowPlayingMessage = oldPlayerProperties.nowPlayingMessage;
|
|
@@ -968,7 +861,7 @@ class Player {
|
|
|
968
861
|
// Debug information
|
|
969
862
|
const debugInfo = {
|
|
970
863
|
success: true,
|
|
971
|
-
message: `Transferred ${clonedPlayer.queue.
|
|
864
|
+
message: `Transferred ${await clonedPlayer.queue.size()} tracks successfully to <#${newOptions.voiceChannelId}> bound to <#${newOptions.textChannelId}>.`,
|
|
972
865
|
player: {
|
|
973
866
|
guildId: clonedPlayer.guildId,
|
|
974
867
|
voiceChannelId: clonedPlayer.voiceChannelId,
|
|
@@ -995,7 +888,7 @@ class Player {
|
|
|
995
888
|
throw new RangeError(`There is no lavalyrics-plugin available in the Lavalink node: ${this.node.options.identifier}`);
|
|
996
889
|
}
|
|
997
890
|
// Fetch the lyrics for the current track from the Lavalink node
|
|
998
|
-
let result = (await this.node.getLyrics(this.queue.
|
|
891
|
+
let result = (await this.node.getLyrics(await this.queue.getCurrent(), skipTrackSource));
|
|
999
892
|
// If no lyrics are found, return a default empty lyrics object
|
|
1000
893
|
if (!result) {
|
|
1001
894
|
result = {
|
package/dist/structures/Queue.js
CHANGED
|
@@ -6,14 +6,52 @@ const Manager_1 = require("./Manager"); // Import Manager to access emit method
|
|
|
6
6
|
* The player's queue, the `current` property is the currently playing track, think of the rest as the up-coming tracks.
|
|
7
7
|
*/
|
|
8
8
|
class Queue extends Array {
|
|
9
|
+
/** The current track */
|
|
10
|
+
current = null;
|
|
11
|
+
/** The previous tracks */
|
|
12
|
+
previous = [];
|
|
13
|
+
/** The Manager instance. */
|
|
14
|
+
manager;
|
|
15
|
+
/** The guild ID property. */
|
|
16
|
+
guildId;
|
|
17
|
+
/**
|
|
18
|
+
* Constructs a new Queue.
|
|
19
|
+
* @param guildId The guild ID.
|
|
20
|
+
* @param manager The Manager instance.
|
|
21
|
+
*/
|
|
22
|
+
constructor(guildId, manager) {
|
|
23
|
+
super();
|
|
24
|
+
/** The Manager instance. */
|
|
25
|
+
this.manager = manager;
|
|
26
|
+
/** The guild property. */
|
|
27
|
+
this.guildId = guildId;
|
|
28
|
+
}
|
|
29
|
+
async getCurrent() {
|
|
30
|
+
return this.current;
|
|
31
|
+
}
|
|
32
|
+
async setCurrent(track) {
|
|
33
|
+
this.current = track;
|
|
34
|
+
}
|
|
35
|
+
async getPrevious() {
|
|
36
|
+
return this.previous;
|
|
37
|
+
}
|
|
38
|
+
async addPrevious(track) {
|
|
39
|
+
if (Array.isArray(track)) {
|
|
40
|
+
this.previous.unshift(...track);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
this.previous.unshift(track);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async clearPrevious() {
|
|
47
|
+
this.previous = [];
|
|
48
|
+
}
|
|
9
49
|
/**
|
|
10
50
|
* The total duration of the queue in milliseconds.
|
|
11
51
|
* This includes the duration of the currently playing track.
|
|
12
52
|
*/
|
|
13
|
-
|
|
14
|
-
// Get the duration of the currently playing track, or 0 if there is none.
|
|
53
|
+
async duration() {
|
|
15
54
|
const current = this.current?.duration ?? 0;
|
|
16
|
-
// Return the sum of all durations in the queue including the current track.
|
|
17
55
|
return this.reduce((acc, cur) => acc + (cur.duration || 0), current);
|
|
18
56
|
}
|
|
19
57
|
/**
|
|
@@ -21,7 +59,7 @@ class Queue extends Array {
|
|
|
21
59
|
* This includes the current track if it is not null.
|
|
22
60
|
* @returns The total size of tracks in the queue including the current track.
|
|
23
61
|
*/
|
|
24
|
-
|
|
62
|
+
async totalSize() {
|
|
25
63
|
return this.length + (this.current ? 1 : 0);
|
|
26
64
|
}
|
|
27
65
|
/**
|
|
@@ -29,40 +67,20 @@ class Queue extends Array {
|
|
|
29
67
|
* This does not include the currently playing track.
|
|
30
68
|
* @returns The size of tracks in the queue.
|
|
31
69
|
*/
|
|
32
|
-
|
|
70
|
+
async size() {
|
|
33
71
|
return this.length;
|
|
34
72
|
}
|
|
35
|
-
/** The current track */
|
|
36
|
-
current = null;
|
|
37
|
-
/** The previous tracks */
|
|
38
|
-
previous = [];
|
|
39
|
-
/** The Manager instance. */
|
|
40
|
-
manager;
|
|
41
|
-
/** The guild ID property. */
|
|
42
|
-
guildId;
|
|
43
|
-
/**
|
|
44
|
-
* Constructs a new Queue.
|
|
45
|
-
* @param guildId The guild ID.
|
|
46
|
-
* @param manager The Manager instance.
|
|
47
|
-
*/
|
|
48
|
-
constructor(guildId, manager) {
|
|
49
|
-
super();
|
|
50
|
-
/** The Manager instance. */
|
|
51
|
-
this.manager = manager;
|
|
52
|
-
/** The guild property. */
|
|
53
|
-
this.guildId = guildId;
|
|
54
|
-
}
|
|
55
73
|
/**
|
|
56
74
|
* Adds a track to the queue.
|
|
57
75
|
* @param track The track or tracks to add. Can be a single `Track` or an array of `Track`s.
|
|
58
76
|
* @param [offset=null] The position to add the track(s) at. If not provided, the track(s) will be added at the end of the queue.
|
|
59
77
|
*/
|
|
60
|
-
add(track, offset) {
|
|
78
|
+
async add(track, offset) {
|
|
61
79
|
// Get the track info as a string
|
|
62
80
|
const trackInfo = Array.isArray(track) ? track.map((t) => JSON.stringify(t, null, 2)).join(", ") : JSON.stringify(track, null, 2);
|
|
63
81
|
// Emit a debug message
|
|
64
82
|
this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] Added ${Array.isArray(track) ? track.length : 1} track(s) to queue: ${trackInfo}`);
|
|
65
|
-
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
83
|
+
const oldPlayer = (await this.manager.players.get(this.guildId)) ? { ...(await this.manager.players.get(this.guildId)) } : null;
|
|
66
84
|
// If the track is valid, add it to the queue
|
|
67
85
|
// If the queue is empty, set the track as the current track
|
|
68
86
|
if (!this.current) {
|
|
@@ -103,11 +121,11 @@ class Queue extends Array {
|
|
|
103
121
|
}
|
|
104
122
|
}
|
|
105
123
|
}
|
|
106
|
-
if (this.manager.players.has(this.guildId) && this.manager.players.get(this.guildId).isAutoplay) {
|
|
124
|
+
if ((await this.manager.players.has(this.guildId)) && (await this.manager.players.get(this.guildId)).isAutoplay) {
|
|
107
125
|
if (!Array.isArray(track)) {
|
|
108
|
-
const botUser = this.manager.players.get(this.guildId).get("Internal_BotUser");
|
|
126
|
+
const botUser = (await (await this.manager.players.get(this.guildId)).get("Internal_BotUser"));
|
|
109
127
|
if (botUser && botUser.id === track.requester.id) {
|
|
110
|
-
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
128
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, await this.manager.players.get(this.guildId), {
|
|
111
129
|
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
112
130
|
details: {
|
|
113
131
|
changeType: "autoPlayAdd",
|
|
@@ -119,7 +137,7 @@ class Queue extends Array {
|
|
|
119
137
|
}
|
|
120
138
|
}
|
|
121
139
|
// Emit a player state update event with the added track(s)
|
|
122
|
-
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
140
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, await this.manager.players.get(this.guildId), {
|
|
123
141
|
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
124
142
|
details: {
|
|
125
143
|
changeType: "add",
|
|
@@ -127,8 +145,8 @@ class Queue extends Array {
|
|
|
127
145
|
},
|
|
128
146
|
});
|
|
129
147
|
}
|
|
130
|
-
remove(startOrPosition = 0, end) {
|
|
131
|
-
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
148
|
+
async remove(startOrPosition = 0, end) {
|
|
149
|
+
const oldPlayer = (await this.manager.players.get(this.guildId)) ? { ...(await this.manager.players.get(this.guildId)) } : null;
|
|
132
150
|
if (typeof end !== "undefined") {
|
|
133
151
|
// Validate input for `start` and `end`
|
|
134
152
|
if (isNaN(Number(startOrPosition)) || isNaN(Number(end))) {
|
|
@@ -139,7 +157,7 @@ class Queue extends Array {
|
|
|
139
157
|
}
|
|
140
158
|
const removedTracks = this.splice(startOrPosition, end - startOrPosition);
|
|
141
159
|
this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] Removed ${removedTracks.length} track(s) from player: ${this.guildId} from position ${startOrPosition} to ${end}.`);
|
|
142
|
-
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
160
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, await this.manager.players.get(this.guildId), {
|
|
143
161
|
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
144
162
|
details: {
|
|
145
163
|
changeType: "remove",
|
|
@@ -153,7 +171,7 @@ class Queue extends Array {
|
|
|
153
171
|
this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] Removed 1 track from player: ${this.guildId} from position ${startOrPosition}: ${JSON.stringify(removedTrack[0], null, 2)}`);
|
|
154
172
|
// Ensure removedTrack is an array for consistency
|
|
155
173
|
const tracksToEmit = removedTrack.length > 0 ? removedTrack : [];
|
|
156
|
-
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
174
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, await this.manager.players.get(this.guildId), {
|
|
157
175
|
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
158
176
|
details: {
|
|
159
177
|
changeType: "remove",
|
|
@@ -166,13 +184,13 @@ class Queue extends Array {
|
|
|
166
184
|
* Clears the queue.
|
|
167
185
|
* This will remove all tracks from the queue and emit a state update event.
|
|
168
186
|
*/
|
|
169
|
-
clear() {
|
|
187
|
+
async clear() {
|
|
170
188
|
// Capture the current state of the player for event emission.
|
|
171
|
-
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
189
|
+
const oldPlayer = (await this.manager.players.get(this.guildId)) ? { ...(await this.manager.players.get(this.guildId)) } : null;
|
|
172
190
|
// Remove all items from the queue.
|
|
173
191
|
this.splice(0);
|
|
174
192
|
// Emit an event to update the player state indicating the queue has been cleared.
|
|
175
|
-
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
193
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, await this.manager.players.get(this.guildId), {
|
|
176
194
|
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
177
195
|
details: {
|
|
178
196
|
changeType: "clear",
|
|
@@ -186,16 +204,16 @@ class Queue extends Array {
|
|
|
186
204
|
* Shuffles the queue.
|
|
187
205
|
* This will randomize the order of the tracks in the queue and emit a state update event.
|
|
188
206
|
*/
|
|
189
|
-
shuffle() {
|
|
207
|
+
async shuffle() {
|
|
190
208
|
// Capture the current state of the player for event emission.
|
|
191
|
-
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
209
|
+
const oldPlayer = (await this.manager.players.get(this.guildId)) ? { ...(await this.manager.players.get(this.guildId)) } : null;
|
|
192
210
|
// Shuffle the queue.
|
|
193
211
|
for (let i = this.length - 1; i > 0; i--) {
|
|
194
212
|
const j = Math.floor(Math.random() * (i + 1));
|
|
195
213
|
[this[i], this[j]] = [this[j], this[i]];
|
|
196
214
|
}
|
|
197
215
|
// Emit an event to update the player state indicating the queue has been shuffled.
|
|
198
|
-
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
216
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, await this.manager.players.get(this.guildId), {
|
|
199
217
|
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
200
218
|
details: {
|
|
201
219
|
changeType: "shuffle",
|
|
@@ -207,9 +225,9 @@ class Queue extends Array {
|
|
|
207
225
|
/**
|
|
208
226
|
* Shuffles the queue to play tracks requested by each user one block at a time.
|
|
209
227
|
*/
|
|
210
|
-
userBlockShuffle() {
|
|
228
|
+
async userBlockShuffle() {
|
|
211
229
|
// Capture the current state of the player for event emission.
|
|
212
|
-
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
230
|
+
const oldPlayer = (await this.manager.players.get(this.guildId)) ? { ...(await this.manager.players.get(this.guildId)) } : null;
|
|
213
231
|
// Group the tracks in the queue by the user that requested them.
|
|
214
232
|
const userTracks = new Map();
|
|
215
233
|
this.forEach((track) => {
|
|
@@ -235,7 +253,7 @@ class Queue extends Array {
|
|
|
235
253
|
this.splice(0);
|
|
236
254
|
this.add(shuffledQueue);
|
|
237
255
|
// Emit an event to update the player state indicating the queue has been shuffled.
|
|
238
|
-
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
256
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, await this.manager.players.get(this.guildId), {
|
|
239
257
|
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
240
258
|
details: {
|
|
241
259
|
changeType: "userBlock",
|
|
@@ -247,8 +265,8 @@ class Queue extends Array {
|
|
|
247
265
|
/**
|
|
248
266
|
* Shuffles the queue to play tracks requested by each user one by one.
|
|
249
267
|
*/
|
|
250
|
-
roundRobinShuffle() {
|
|
251
|
-
const oldPlayer = this.manager.players.get(this.guildId) ? { ...this.manager.players.get(this.guildId) } : null;
|
|
268
|
+
async roundRobinShuffle() {
|
|
269
|
+
const oldPlayer = (await this.manager.players.get(this.guildId)) ? { ...(await this.manager.players.get(this.guildId)) } : null;
|
|
252
270
|
const userTracks = new Map();
|
|
253
271
|
// Group the tracks in the queue by the user that requested them.
|
|
254
272
|
this.forEach((track) => {
|
|
@@ -283,7 +301,7 @@ class Queue extends Array {
|
|
|
283
301
|
this.splice(0);
|
|
284
302
|
this.add(shuffledQueue);
|
|
285
303
|
// Emit an event to update the player state indicating the queue has been shuffled.
|
|
286
|
-
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, this.manager.players.get(this.guildId), {
|
|
304
|
+
this.manager.emit(Manager_1.ManagerEventTypes.PlayerStateUpdate, oldPlayer, await this.manager.players.get(this.guildId), {
|
|
287
305
|
changeType: Manager_1.PlayerStateEventTypes.QueueChange,
|
|
288
306
|
details: {
|
|
289
307
|
changeType: "roundRobin",
|
|
@@ -292,5 +310,40 @@ class Queue extends Array {
|
|
|
292
310
|
// Emit a debug message indicating the queue has been shuffled for a specific guild ID.
|
|
293
311
|
this.manager.emit(Manager_1.ManagerEventTypes.Debug, `[QUEUE] roundRobinShuffled the queue for: ${this.guildId}`);
|
|
294
312
|
}
|
|
313
|
+
async dequeue() {
|
|
314
|
+
return super.shift();
|
|
315
|
+
}
|
|
316
|
+
async enqueueFront(track) {
|
|
317
|
+
if (Array.isArray(track)) {
|
|
318
|
+
this.unshift(...track);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
this.unshift(track);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async getTracks() {
|
|
325
|
+
return [...this]; // clone to avoid direct mutation
|
|
326
|
+
}
|
|
327
|
+
async getSlice(start, end) {
|
|
328
|
+
return this.slice(start, end); // Native sync method, still wrapped in a Promise
|
|
329
|
+
}
|
|
330
|
+
async modifyAt(start, deleteCount = 0, ...items) {
|
|
331
|
+
return super.splice(start, deleteCount, ...items);
|
|
332
|
+
}
|
|
333
|
+
async mapAsync(callback) {
|
|
334
|
+
return this.map(callback);
|
|
335
|
+
}
|
|
336
|
+
async filterAsync(callback) {
|
|
337
|
+
return this.filter(callback);
|
|
338
|
+
}
|
|
339
|
+
async findAsync(callback) {
|
|
340
|
+
return this.find(callback);
|
|
341
|
+
}
|
|
342
|
+
async someAsync(callback) {
|
|
343
|
+
return this.some(callback);
|
|
344
|
+
}
|
|
345
|
+
async everyAsync(callback) {
|
|
346
|
+
return this.every(callback);
|
|
347
|
+
}
|
|
295
348
|
}
|
|
296
349
|
exports.Queue = Queue;
|