magmastream 2.6.0-beta.2 → 2.6.0-beta.3
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 +12 -16
- package/dist/index.d.ts +45 -9
- package/dist/structures/Manager.js +195 -0
- package/dist/structures/Node.js +48 -0
- package/dist/structures/Player.js +21 -15
- package/dist/structures/Queue.js +28 -4
- package/dist/structures/Rest.js +4 -0
- package/dist/structures/Utils.js +19 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://capsule-render.vercel.app/api?type=waving&color=gradient&height=200§ion=header&text=Magmastream&fontSize=80&fontAlignY=35&animation=twinkling&fontColor=gradient" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
6
|
+
[](#contributors-)
|
|
4
7
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
5
8
|
|
|
6
|
-
[](https://github.com/Magmastream-NPM/magmastream/blob/main/LICENSE)
|
|
7
|
-
[](https://github.com/Magmastream-NPM/magmastream/pulls)
|
|
8
|
-

|
|
9
|
-
[](https://github.com/Magmastream-NPM/magmastream/issues)
|
|
10
|
-
[](https://github.com/Magmastream-NPM/magmastream/pulls)
|
|
11
|
-
[](https://github.com/sponsors/Magmastream-NPM)
|
|
12
|
-
[](https://www.npmjs.com/package/magmastream)
|
|
13
9
|
|
|
14
10
|
**Magmastream** a lavalink wrapper for NodeJs. Magmastream only supports lavalink version 4.
|
|
15
11
|
|
|
@@ -55,12 +51,12 @@ If you want to add your own bot create a pull request with your bot added. Pleas
|
|
|
55
51
|
|
|
56
52
|
Special thanks to the all the contributors who have helped make Magmastream even better!
|
|
57
53
|
|
|
58
|
-
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
59
|
-
<!-- prettier-ignore-start -->
|
|
60
|
-
<!-- markdownlint-disable -->
|
|
61
|
-
<!-- markdownlint-restore -->
|
|
62
|
-
<!-- prettier-ignore-end -->
|
|
63
|
-
|
|
54
|
+
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
55
|
+
<!-- prettier-ignore-start -->
|
|
56
|
+
<!-- markdownlint-disable -->
|
|
57
|
+
<!-- markdownlint-restore -->
|
|
58
|
+
<!-- prettier-ignore-end -->
|
|
59
|
+
|
|
64
60
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
65
61
|
|
|
66
62
|
## Credits
|
package/dist/index.d.ts
CHANGED
|
@@ -133,6 +133,11 @@ declare class Queue extends Array<Track | UnresolvedTrack> {
|
|
|
133
133
|
current: Track | UnresolvedTrack | null;
|
|
134
134
|
/** The previous track */
|
|
135
135
|
previous: Track | UnresolvedTrack | null;
|
|
136
|
+
/** The Manager instance. */
|
|
137
|
+
manager: Manager;
|
|
138
|
+
/** The guild property. */
|
|
139
|
+
guild: string;
|
|
140
|
+
constructor(guild: string, manager: Manager);
|
|
136
141
|
/**
|
|
137
142
|
* Adds a track to the queue.
|
|
138
143
|
* @param track
|
|
@@ -224,7 +229,7 @@ declare class Player {
|
|
|
224
229
|
* @param query
|
|
225
230
|
* @param requester
|
|
226
231
|
*/
|
|
227
|
-
search(query: string | SearchQuery, requester?:
|
|
232
|
+
search<T = User | ClientUser>(query: string | SearchQuery, requester?: T): Promise<SearchResult>;
|
|
228
233
|
/** Connect to the voice channel. */
|
|
229
234
|
connect(): this;
|
|
230
235
|
/** Disconnect from the voice channel. */
|
|
@@ -242,7 +247,7 @@ declare class Player {
|
|
|
242
247
|
*/
|
|
243
248
|
setTextChannel(channel: string): this;
|
|
244
249
|
/** Sets the now playing message. */
|
|
245
|
-
setNowPlayingMessage(message:
|
|
250
|
+
setNowPlayingMessage<T = Message>(message: T): Message;
|
|
246
251
|
/** Plays the next track. */
|
|
247
252
|
play(): Promise<void>;
|
|
248
253
|
/**
|
|
@@ -272,7 +277,7 @@ declare class Player {
|
|
|
272
277
|
* @param track
|
|
273
278
|
* @param requester
|
|
274
279
|
*/
|
|
275
|
-
getRecommended(track: Track, requester?:
|
|
280
|
+
getRecommended<T = User | ClientUser>(track: Track, requester?: T): Promise<Track[]>;
|
|
276
281
|
/**
|
|
277
282
|
* Sets the player volume.
|
|
278
283
|
* @param volume
|
|
@@ -367,7 +372,7 @@ interface Track {
|
|
|
367
372
|
/** The thumbnail of the track or null if it's a unsupported source. */
|
|
368
373
|
readonly thumbnail: string | null;
|
|
369
374
|
/** The user that requested the track. */
|
|
370
|
-
readonly requester
|
|
375
|
+
readonly requester?: User | ClientUser;
|
|
371
376
|
/** Displays the track thumbnail with optional size or null if it's a unsupported source. */
|
|
372
377
|
displayThumbnail(size?: Sizes): string;
|
|
373
378
|
/** Additional track info provided by plugins. */
|
|
@@ -431,6 +436,8 @@ declare class Rest {
|
|
|
431
436
|
updatePlayer(options: playOptions): Promise<unknown>;
|
|
432
437
|
/** Sends a DELETE request to the server to destroy the player. */
|
|
433
438
|
destroyPlayer(guildId: string): Promise<unknown>;
|
|
439
|
+
/** Updates the session status for resuming. */
|
|
440
|
+
updateSession(resuming: boolean, timeout: number): Promise<unknown>;
|
|
434
441
|
private request;
|
|
435
442
|
get(endpoint: string): Promise<unknown>;
|
|
436
443
|
patch(endpoint: string, body: unknown): Promise<unknown>;
|
|
@@ -497,6 +504,12 @@ declare class Node {
|
|
|
497
504
|
* @param options
|
|
498
505
|
*/
|
|
499
506
|
constructor(options: NodeOptions);
|
|
507
|
+
/** Creates the sessionIds.json file if it doesn't exist. */
|
|
508
|
+
createSessionIdsFile(): void;
|
|
509
|
+
/** Loads session IDs from the sessionIds.json file. */
|
|
510
|
+
loadSessionIds(): void;
|
|
511
|
+
/** Updates the session ID in the sessionIds.json file. */
|
|
512
|
+
updateSessionId(): void;
|
|
500
513
|
/** Connects to the Node. */
|
|
501
514
|
connect(): void;
|
|
502
515
|
/** Destroys the Node and all players connected with it. */
|
|
@@ -665,13 +678,13 @@ declare abstract class TrackUtils {
|
|
|
665
678
|
* @param data
|
|
666
679
|
* @param requester
|
|
667
680
|
*/
|
|
668
|
-
static build(data: TrackData, requester?:
|
|
681
|
+
static build<T = User | ClientUser>(data: TrackData, requester?: T): Track;
|
|
669
682
|
/**
|
|
670
683
|
* Builds a UnresolvedTrack to be resolved before being played .
|
|
671
684
|
* @param query
|
|
672
685
|
* @param requester
|
|
673
686
|
*/
|
|
674
|
-
static buildUnresolved(query: string | UnresolvedQuery, requester?:
|
|
687
|
+
static buildUnresolved<T = User | ClientUser>(query: string | UnresolvedQuery, requester?: T): UnresolvedTrack;
|
|
675
688
|
static getClosestTrack(unresolvedTrack: UnresolvedTrack): Promise<Track>;
|
|
676
689
|
}
|
|
677
690
|
/** Gets or extends structures to extend the built in, or already extended, classes to add more functionality. */
|
|
@@ -862,6 +875,16 @@ declare class Manager extends EventEmitter {
|
|
|
862
875
|
/** The options that were set. */
|
|
863
876
|
readonly options: ManagerOptions;
|
|
864
877
|
private initiated;
|
|
878
|
+
/** Loads player states from the JSON file. */
|
|
879
|
+
loadPlayerStates(nodeId: string): void;
|
|
880
|
+
/** Gets each player's JSON file */
|
|
881
|
+
private getPlayerFilePath;
|
|
882
|
+
/** Saves player states to the JSON file. */
|
|
883
|
+
savePlayerState(guildId: string): void;
|
|
884
|
+
/** Serializes a Player instance to avoid circular references. */
|
|
885
|
+
private serializePlayer;
|
|
886
|
+
/** Check for players that are no longer active */
|
|
887
|
+
private cleanupInactivePlayers;
|
|
865
888
|
/** Returns the nodes that has the least load. */
|
|
866
889
|
private get leastLoadNode();
|
|
867
890
|
/** Returns the nodes that has the least amount of players. */
|
|
@@ -870,6 +893,18 @@ declare class Manager extends EventEmitter {
|
|
|
870
893
|
private get priorityNode();
|
|
871
894
|
/** Returns the node to use. */
|
|
872
895
|
get useableNodes(): Node;
|
|
896
|
+
/** work in progress */
|
|
897
|
+
private lastProcessedGuilds;
|
|
898
|
+
private lastSaveTimes;
|
|
899
|
+
private saveInterval;
|
|
900
|
+
private saveQueues;
|
|
901
|
+
/** Register savePlayerStates events */
|
|
902
|
+
private registerPlayerStateEvents;
|
|
903
|
+
private handleEvent;
|
|
904
|
+
/** Queues a player state save */
|
|
905
|
+
private queuePlayerStateSave;
|
|
906
|
+
/** Processes the next queued save for a specific guild */
|
|
907
|
+
private processNextQueue;
|
|
873
908
|
/**
|
|
874
909
|
* Initiates the Manager class.
|
|
875
910
|
* @param options
|
|
@@ -886,7 +921,7 @@ declare class Manager extends EventEmitter {
|
|
|
886
921
|
* @param requester
|
|
887
922
|
* @returns The search result.
|
|
888
923
|
*/
|
|
889
|
-
search(query: string | SearchQuery, requester?:
|
|
924
|
+
search<T = User | ClientUser>(query: string | SearchQuery, requester?: T): Promise<SearchResult>;
|
|
890
925
|
private parseYouTubeTitle;
|
|
891
926
|
private balanceBrackets;
|
|
892
927
|
private escapeRegExp;
|
|
@@ -970,6 +1005,7 @@ interface ManagerOptions {
|
|
|
970
1005
|
send(id: string, payload: Payload): void;
|
|
971
1006
|
}
|
|
972
1007
|
type SearchPlatform = "deezer" | "soundcloud" | "youtube music" | "youtube" | "spotify" | "jiosaavn" | "tidal" | "applemusic" | "bandcamp";
|
|
1008
|
+
type PlayerStateEventType = "connectionChange" | "playerCreate" | "playerDestroy" | "channelChange" | "volumeChange" | "pauseChange" | "queueChange" | "trackChange" | "repeatChange" | "autoplayChange";
|
|
973
1009
|
interface SearchQuery {
|
|
974
1010
|
/** The source to search from. */
|
|
975
1011
|
source?: SearchPlatform | string;
|
|
@@ -1019,7 +1055,7 @@ interface ManagerEvents {
|
|
|
1019
1055
|
nodeRaw: [payload: unknown];
|
|
1020
1056
|
playerCreate: [player: Player];
|
|
1021
1057
|
playerDestroy: [player: Player];
|
|
1022
|
-
playerStateUpdate: [oldPlayer: Player, newPlayer: Player];
|
|
1058
|
+
playerStateUpdate: [oldPlayer: Player, newPlayer: Player, changeType: PlayerStateEventType];
|
|
1023
1059
|
playerMove: [player: Player, initChannel: string, newChannel: string];
|
|
1024
1060
|
playerDisconnect: [player: Player, oldChannel: string];
|
|
1025
1061
|
queueEnd: [player: Player, track: Track | UnresolvedTrack, payload: TrackEndEvent];
|
|
@@ -1034,4 +1070,4 @@ interface ManagerEvents {
|
|
|
1034
1070
|
chaptersLoaded: [player: Player, track: Track | UnresolvedTrack, payload: SponsorBlockChaptersLoaded];
|
|
1035
1071
|
}
|
|
1036
1072
|
|
|
1037
|
-
export { type CPUStats, type EqualizerBand, type Exception, type Extendable, type FrameStats, type LavalinkInfo, type LavalinkResponse, type LoadType, Manager, type ManagerEvents, type ManagerOptions, type MemoryStats, Node, type NodeMessage, type NodeOptions, type NodeStats, type Payload, type PlayOptions, Player, type PlayerEvent, type PlayerEventType, type PlayerEvents, type PlayerOptions, type PlayerUpdate, type PlaylistData, type PlaylistRawData, Plugin, Queue, type SearchPlatform, type SearchQuery, type SearchResult, type Severity, type Sizes, type SponsorBlockChapterStarted, type SponsorBlockChaptersLoaded, type SponsorBlockSegment, type SponsorBlockSegmentEventType, type SponsorBlockSegmentEvents, type SponsorBlockSegmentSkipped, type SponsorBlockSegmentsLoaded, type State, Structure, type Track, type TrackData, type TrackDataInfo, type TrackEndEvent, type TrackEndReason, type TrackExceptionEvent, type TrackPluginInfo, type TrackSourceName, type TrackStartEvent, type TrackStuckEvent, TrackUtils, type UnresolvedQuery, type UnresolvedTrack, type VoicePacket, type VoiceServer, type VoiceState, type WebSocketClosedEvent, validSponsorBlocks };
|
|
1073
|
+
export { type CPUStats, type EqualizerBand, type Exception, type Extendable, type FrameStats, type LavalinkInfo, type LavalinkResponse, type LoadType, Manager, type ManagerEvents, type ManagerOptions, type MemoryStats, Node, type NodeMessage, type NodeOptions, type NodeStats, type Payload, type PlayOptions, Player, type PlayerEvent, type PlayerEventType, type PlayerEvents, type PlayerOptions, type PlayerStateEventType, type PlayerUpdate, type PlaylistData, type PlaylistRawData, Plugin, Queue, type SearchPlatform, type SearchQuery, type SearchResult, type Severity, type Sizes, type SponsorBlockChapterStarted, type SponsorBlockChaptersLoaded, type SponsorBlockSegment, type SponsorBlockSegmentEventType, type SponsorBlockSegmentEvents, type SponsorBlockSegmentSkipped, type SponsorBlockSegmentsLoaded, type State, Structure, type Track, type TrackData, type TrackDataInfo, type TrackEndEvent, type TrackEndReason, type TrackExceptionEvent, type TrackPluginInfo, type TrackSourceName, type TrackStartEvent, type TrackStuckEvent, TrackUtils, type UnresolvedQuery, type UnresolvedTrack, type VoicePacket, type VoiceServer, type VoiceState, type WebSocketClosedEvent, validSponsorBlocks };
|
|
@@ -8,6 +8,8 @@ const collection_1 = require("@discordjs/collection");
|
|
|
8
8
|
const events_1 = require("events");
|
|
9
9
|
const managerCheck_1 = tslib_1.__importDefault(require("../utils/managerCheck"));
|
|
10
10
|
const blockedWords_1 = require("../config/blockedWords");
|
|
11
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
12
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
11
13
|
/**
|
|
12
14
|
* The main hub for interacting with Lavalink and using Magmastream,
|
|
13
15
|
*/
|
|
@@ -33,6 +35,136 @@ class Manager extends events_1.EventEmitter {
|
|
|
33
35
|
/** The options that were set. */
|
|
34
36
|
options;
|
|
35
37
|
initiated = false;
|
|
38
|
+
/** Loads player states from the JSON file. */
|
|
39
|
+
loadPlayerStates(nodeId) {
|
|
40
|
+
const createTrackData = (song) => ({
|
|
41
|
+
encoded: song.track,
|
|
42
|
+
info: {
|
|
43
|
+
identifier: song.identifier,
|
|
44
|
+
isSeekable: song.isSeekable,
|
|
45
|
+
author: song.author,
|
|
46
|
+
length: song.duration,
|
|
47
|
+
isrc: song.isrc,
|
|
48
|
+
isStream: song.isStream,
|
|
49
|
+
title: song.title,
|
|
50
|
+
uri: song.uri,
|
|
51
|
+
artworkUrl: song.artworkUrl,
|
|
52
|
+
sourceName: song.sourceName,
|
|
53
|
+
},
|
|
54
|
+
pluginInfo: song.pluginInfo,
|
|
55
|
+
});
|
|
56
|
+
const playerStatesDir = path_1.default.join(process.cwd(), "node_modules", "magmastream", "dist", "sessionData", "players");
|
|
57
|
+
if (!fs_1.default.existsSync(playerStatesDir)) {
|
|
58
|
+
fs_1.default.mkdirSync(playerStatesDir, { recursive: true });
|
|
59
|
+
console.log(`Created directory at ${playerStatesDir}`);
|
|
60
|
+
}
|
|
61
|
+
const playerFiles = fs_1.default.readdirSync(playerStatesDir);
|
|
62
|
+
for (const file of playerFiles) {
|
|
63
|
+
const filePath = path_1.default.join(playerStatesDir, file);
|
|
64
|
+
const data = fs_1.default.readFileSync(filePath, "utf-8");
|
|
65
|
+
const state = JSON.parse(data);
|
|
66
|
+
if (state && typeof state === "object" && state.guild && state.node.options.identifier === nodeId) {
|
|
67
|
+
const playerOptions = {
|
|
68
|
+
guild: state.options.guild,
|
|
69
|
+
textChannel: state.options.textChannel,
|
|
70
|
+
voiceChannel: state.options.voiceChannel,
|
|
71
|
+
selfDeafen: state.options.selfDeafen,
|
|
72
|
+
volume: state.options.volume,
|
|
73
|
+
};
|
|
74
|
+
this.create(playerOptions);
|
|
75
|
+
}
|
|
76
|
+
const player = this.get(state.options.guild);
|
|
77
|
+
const tracks = [];
|
|
78
|
+
if (state.queue.current !== null) {
|
|
79
|
+
const currentTrack = state.queue.current;
|
|
80
|
+
tracks.push(Utils_1.TrackUtils.build(createTrackData(currentTrack), currentTrack.requester));
|
|
81
|
+
for (const key in state.queue) {
|
|
82
|
+
if (!isNaN(Number(key)) && key !== "current" && key !== "previous" && key !== "manager") {
|
|
83
|
+
const song = state.queue[key];
|
|
84
|
+
tracks.push(Utils_1.TrackUtils.build(createTrackData(song), song.requester));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
player.queue.add(tracks);
|
|
88
|
+
}
|
|
89
|
+
if (state.paused)
|
|
90
|
+
player.pause(true);
|
|
91
|
+
player.setTrackRepeat(state.trackRepeat);
|
|
92
|
+
player.setQueueRepeat(state.queueRepeat);
|
|
93
|
+
if (state.dynamicRepeat) {
|
|
94
|
+
player.setDynamicRepeat(state.dynamicRepeat, state.dynamicLoopInterval._idleTimeout);
|
|
95
|
+
}
|
|
96
|
+
if (state.isAutoplay) {
|
|
97
|
+
player.setAutoplay(state.isAutoplay, state.data.Internal_BotUser);
|
|
98
|
+
}
|
|
99
|
+
console.log(`Loaded player state for ${state.options.guild}.`);
|
|
100
|
+
}
|
|
101
|
+
console.log("Loaded player states from player files.");
|
|
102
|
+
}
|
|
103
|
+
/** Gets each player's JSON file */
|
|
104
|
+
getPlayerFilePath(guildId) {
|
|
105
|
+
const playerStateFilePath = path_1.default.join(process.cwd(), "node_modules", "magmastream", "dist", "sessionData", "players", `${guildId}.json`);
|
|
106
|
+
const configDir = path_1.default.dirname(playerStateFilePath);
|
|
107
|
+
if (!fs_1.default.existsSync(configDir)) {
|
|
108
|
+
fs_1.default.mkdirSync(configDir, { recursive: true });
|
|
109
|
+
console.log(`Created directory at: ${configDir}`);
|
|
110
|
+
}
|
|
111
|
+
return playerStateFilePath;
|
|
112
|
+
}
|
|
113
|
+
/** Saves player states to the JSON file. */
|
|
114
|
+
savePlayerState(guildId) {
|
|
115
|
+
const playerStateFilePath = this.getPlayerFilePath(guildId);
|
|
116
|
+
const player = this.players.get(guildId);
|
|
117
|
+
if (!player || player.state === "DISCONNECTED" || !player.voiceChannel)
|
|
118
|
+
return this.cleanupInactivePlayers();
|
|
119
|
+
const serializedPlayer = this.serializePlayer(player);
|
|
120
|
+
fs_1.default.writeFileSync(playerStateFilePath, JSON.stringify(serializedPlayer, null, 2), "utf-8");
|
|
121
|
+
console.log(`Saved ${guildId} player state to: ${playerStateFilePath}`);
|
|
122
|
+
}
|
|
123
|
+
/** Serializes a Player instance to avoid circular references. */
|
|
124
|
+
serializePlayer(player) {
|
|
125
|
+
const seen = new WeakSet();
|
|
126
|
+
const serialize = (obj) => {
|
|
127
|
+
if (obj && typeof obj === "object") {
|
|
128
|
+
if (seen.has(obj))
|
|
129
|
+
return; // Prevent circular references
|
|
130
|
+
seen.add(obj);
|
|
131
|
+
}
|
|
132
|
+
return obj;
|
|
133
|
+
};
|
|
134
|
+
const serializedPlayer = JSON.parse(JSON.stringify(player, (key, value) => {
|
|
135
|
+
if (key === "filters" || key === "manager") {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
if (key === "queue") {
|
|
139
|
+
return {
|
|
140
|
+
...value,
|
|
141
|
+
current: value.current || null,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// return value === undefined ? null : serialize(value);
|
|
145
|
+
return serialize(value);
|
|
146
|
+
}));
|
|
147
|
+
return serializedPlayer;
|
|
148
|
+
}
|
|
149
|
+
/** Check for players that are no longer active */
|
|
150
|
+
cleanupInactivePlayers() {
|
|
151
|
+
const playerStatesDir = path_1.default.join(process.cwd(), "node_modules", "magmastream", "dist", "sessionData", "players");
|
|
152
|
+
// Create the directory if it does not exist
|
|
153
|
+
if (!fs_1.default.existsSync(playerStatesDir)) {
|
|
154
|
+
fs_1.default.mkdirSync(playerStatesDir, { recursive: true });
|
|
155
|
+
console.log(`Created directory at ${playerStatesDir}`);
|
|
156
|
+
}
|
|
157
|
+
const playerFiles = fs_1.default.readdirSync(playerStatesDir);
|
|
158
|
+
const activeGuildIds = new Set(this.players.keys());
|
|
159
|
+
for (const file of playerFiles) {
|
|
160
|
+
const guildId = path_1.default.basename(file, ".json");
|
|
161
|
+
if (!activeGuildIds.has(guildId)) {
|
|
162
|
+
const filePath = path_1.default.join(playerStatesDir, file);
|
|
163
|
+
fs_1.default.unlinkSync(filePath);
|
|
164
|
+
console.log(`Deleted inactive player state file: ${filePath}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
36
168
|
/** Returns the nodes that has the least load. */
|
|
37
169
|
get leastLoadNode() {
|
|
38
170
|
return this.nodes
|
|
@@ -69,12 +201,74 @@ class Manager extends events_1.EventEmitter {
|
|
|
69
201
|
get useableNodes() {
|
|
70
202
|
return this.options.usePriority ? this.priorityNode : this.options.useNode === "leastLoad" ? this.leastLoadNode.first() : this.leastPlayersNode.first();
|
|
71
203
|
}
|
|
204
|
+
/** work in progress */
|
|
205
|
+
lastProcessedGuilds = new Set();
|
|
206
|
+
lastSaveTimes = new Map();
|
|
207
|
+
saveInterval = 1000;
|
|
208
|
+
saveQueues = new Map();
|
|
209
|
+
/** Register savePlayerStates events */
|
|
210
|
+
registerPlayerStateEvents() {
|
|
211
|
+
const events = ["playerStateUpdate", "playerDestroy"];
|
|
212
|
+
for (const event of events) {
|
|
213
|
+
this.on(event, (player) => this.handleEvent(event, player));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
handleEvent(event, player) {
|
|
217
|
+
switch (event) {
|
|
218
|
+
case "playerDestroy":
|
|
219
|
+
this.lastSaveTimes.delete(player.guild);
|
|
220
|
+
this.players.delete(player.guild);
|
|
221
|
+
this.cleanupInactivePlayers();
|
|
222
|
+
break;
|
|
223
|
+
case "playerStateUpdate":
|
|
224
|
+
this.queuePlayerStateSave(player);
|
|
225
|
+
break;
|
|
226
|
+
default:
|
|
227
|
+
this.savePlayerState(player.guild);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/** Queues a player state save */
|
|
232
|
+
queuePlayerStateSave(player) {
|
|
233
|
+
const guildId = player.guild;
|
|
234
|
+
// If the current guild is not being processed, save immediately
|
|
235
|
+
if (!this.lastProcessedGuilds.has(guildId)) {
|
|
236
|
+
this.lastProcessedGuilds.add(guildId);
|
|
237
|
+
this.savePlayerState(guildId);
|
|
238
|
+
setTimeout(() => {
|
|
239
|
+
this.lastProcessedGuilds.delete(guildId);
|
|
240
|
+
this.processNextQueue(guildId);
|
|
241
|
+
}, this.saveInterval);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
if (!this.saveQueues.has(guildId)) {
|
|
245
|
+
this.saveQueues.set(guildId, []);
|
|
246
|
+
}
|
|
247
|
+
this.saveQueues.get(guildId).push(player);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/** Processes the next queued save for a specific guild */
|
|
251
|
+
processNextQueue(guildId) {
|
|
252
|
+
const queue = this.saveQueues.get(guildId);
|
|
253
|
+
if (queue && queue.length > 0) {
|
|
254
|
+
const player = queue.shift();
|
|
255
|
+
this.savePlayerState(player.guild);
|
|
256
|
+
if (queue.length === 0) {
|
|
257
|
+
this.saveQueues.delete(guildId);
|
|
258
|
+
}
|
|
259
|
+
setTimeout(() => this.processNextQueue(guildId), this.saveInterval);
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
this.lastProcessedGuilds.delete(guildId);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
72
265
|
/**
|
|
73
266
|
* Initiates the Manager class.
|
|
74
267
|
* @param options
|
|
75
268
|
*/
|
|
76
269
|
constructor(options) {
|
|
77
270
|
super();
|
|
271
|
+
this.registerPlayerStateEvents();
|
|
78
272
|
(0, managerCheck_1.default)(options);
|
|
79
273
|
Utils_1.Structure.get("Player").init(this);
|
|
80
274
|
Utils_1.Structure.get("Node").init(this);
|
|
@@ -316,6 +510,7 @@ class Manager extends events_1.EventEmitter {
|
|
|
316
510
|
*/
|
|
317
511
|
destroy(guild) {
|
|
318
512
|
this.players.delete(guild);
|
|
513
|
+
this.cleanupInactivePlayers();
|
|
319
514
|
}
|
|
320
515
|
/**
|
|
321
516
|
* Creates a node or returns one if it already exists.
|
package/dist/structures/Node.js
CHANGED
|
@@ -6,7 +6,16 @@ const Utils_1 = require("./Utils");
|
|
|
6
6
|
const Rest_1 = require("./Rest");
|
|
7
7
|
const nodeCheck_1 = tslib_1.__importDefault(require("../utils/nodeCheck"));
|
|
8
8
|
const ws_1 = tslib_1.__importDefault(require("ws"));
|
|
9
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
10
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
9
11
|
exports.validSponsorBlocks = ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "filler"];
|
|
12
|
+
const sessionIdsFilePath = path_1.default.join(process.cwd(), "node_modules", "magmastream", "dist", "sessionData", "sessionIds.json");
|
|
13
|
+
let sessionIdsMap = new Map();
|
|
14
|
+
const configDir = path_1.default.dirname(sessionIdsFilePath);
|
|
15
|
+
if (!fs_1.default.existsSync(configDir)) {
|
|
16
|
+
fs_1.default.mkdirSync(configDir, { recursive: true });
|
|
17
|
+
console.log(`Created directory at ${configDir}`);
|
|
18
|
+
}
|
|
10
19
|
class Node {
|
|
11
20
|
options;
|
|
12
21
|
/** The socket for the node. */
|
|
@@ -88,6 +97,29 @@ class Node {
|
|
|
88
97
|
this.manager.nodes.set(this.options.identifier, this);
|
|
89
98
|
this.manager.emit("nodeCreate", this);
|
|
90
99
|
this.rest = new Rest_1.Rest(this);
|
|
100
|
+
this.createSessionIdsFile(); // Create the session IDs file on initialization
|
|
101
|
+
this.loadSessionIds(); // Load session IDs on initialization
|
|
102
|
+
}
|
|
103
|
+
/** Creates the sessionIds.json file if it doesn't exist. */
|
|
104
|
+
createSessionIdsFile() {
|
|
105
|
+
if (!fs_1.default.existsSync(sessionIdsFilePath)) {
|
|
106
|
+
fs_1.default.writeFileSync(sessionIdsFilePath, JSON.stringify({}), "utf-8");
|
|
107
|
+
console.log(`Created sessionIds.json at ${sessionIdsFilePath}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Loads session IDs from the sessionIds.json file. */
|
|
111
|
+
loadSessionIds() {
|
|
112
|
+
if (fs_1.default.existsSync(sessionIdsFilePath)) {
|
|
113
|
+
const sessionIdsData = fs_1.default.readFileSync(sessionIdsFilePath, "utf-8");
|
|
114
|
+
sessionIdsMap = new Map(Object.entries(JSON.parse(sessionIdsData)));
|
|
115
|
+
console.log(`Loaded session IDs from JSON file`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Updates the session ID in the sessionIds.json file. */
|
|
119
|
+
updateSessionId() {
|
|
120
|
+
sessionIdsMap.set(this.options.identifier, this.sessionId); // Store session ID
|
|
121
|
+
fs_1.default.writeFileSync(sessionIdsFilePath, JSON.stringify(Object.fromEntries(sessionIdsMap))); // Update JSON file
|
|
122
|
+
console.log(`Updated session ID for ${this.options.identifier} to ${this.sessionId}`);
|
|
91
123
|
}
|
|
92
124
|
/** Connects to the Node. */
|
|
93
125
|
connect() {
|
|
@@ -101,6 +133,11 @@ class Node {
|
|
|
101
133
|
if (this.sessionId) {
|
|
102
134
|
headers["Session-Id"] = this.sessionId;
|
|
103
135
|
}
|
|
136
|
+
else if (this.options.resumeStatus && sessionIdsMap.has(this.options.identifier)) {
|
|
137
|
+
this.sessionId = sessionIdsMap.get(this.options.identifier) || null;
|
|
138
|
+
headers["Session-Id"] = this.sessionId;
|
|
139
|
+
console.log(`Resuming session with ID: ${this.sessionId}`);
|
|
140
|
+
}
|
|
104
141
|
this.socket = new ws_1.default(`ws${this.options.secure ? "s" : ""}://${this.address}/v4/websocket`, { headers });
|
|
105
142
|
this.socket.on("open", this.open.bind(this));
|
|
106
143
|
this.socket.on("close", this.close.bind(this));
|
|
@@ -177,7 +214,14 @@ class Node {
|
|
|
177
214
|
case "ready":
|
|
178
215
|
this.rest.setSessionId(payload.sessionId);
|
|
179
216
|
this.sessionId = payload.sessionId;
|
|
217
|
+
this.updateSessionId(); // Call to update session ID
|
|
180
218
|
this.info = await this.fetchInfo();
|
|
219
|
+
// Log if the session was resumed successfully
|
|
220
|
+
if (payload.resumed) {
|
|
221
|
+
console.log(`Session resumed successfully for ${this.options.identifier}`);
|
|
222
|
+
// Load player states from the JSON file
|
|
223
|
+
await this.manager.loadPlayerStates(this.options.identifier);
|
|
224
|
+
}
|
|
181
225
|
if (this.options.resumeStatus) {
|
|
182
226
|
this.rest.patch(`/v4/sessions/${this.sessionId}`, {
|
|
183
227
|
resuming: this.options.resumeStatus,
|
|
@@ -237,12 +281,15 @@ class Node {
|
|
|
237
281
|
}
|
|
238
282
|
}
|
|
239
283
|
trackStart(player, track, payload) {
|
|
284
|
+
const oldPlayer = player;
|
|
240
285
|
player.playing = true;
|
|
241
286
|
player.paused = false;
|
|
242
287
|
this.manager.emit("trackStart", player, track, payload);
|
|
288
|
+
this.manager.emit("playerStateUpdate", oldPlayer, player, "trackChange");
|
|
243
289
|
}
|
|
244
290
|
async trackEnd(player, track, payload) {
|
|
245
291
|
const { reason } = payload;
|
|
292
|
+
const oldPlayer = player;
|
|
246
293
|
// If the track failed to load or was cleaned up
|
|
247
294
|
if (["loadFailed", "cleanup"].includes(reason)) {
|
|
248
295
|
this.handleFailedTrack(player, track, payload);
|
|
@@ -264,6 +311,7 @@ class Node {
|
|
|
264
311
|
else {
|
|
265
312
|
await this.queueEnd(player, track, payload);
|
|
266
313
|
}
|
|
314
|
+
this.manager.emit("playerStateUpdate", oldPlayer, player, "trackChange");
|
|
267
315
|
}
|
|
268
316
|
extractSpotifyTrackID(url) {
|
|
269
317
|
const regex = /https:\/\/open\.spotify\.com\/track\/([a-zA-Z0-9]+)/;
|
|
@@ -3,13 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Player = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const Filters_1 = require("./Filters");
|
|
6
|
+
const Queue_1 = require("./Queue");
|
|
6
7
|
const Utils_1 = require("./Utils");
|
|
7
8
|
const _ = tslib_1.__importStar(require("lodash"));
|
|
8
9
|
const playerCheck_1 = tslib_1.__importDefault(require("../utils/playerCheck"));
|
|
9
10
|
class Player {
|
|
10
11
|
options;
|
|
11
12
|
/** The Queue for the Player. */
|
|
12
|
-
queue
|
|
13
|
+
queue;
|
|
13
14
|
/** The filters applied to the audio. */
|
|
14
15
|
filters;
|
|
15
16
|
/** Whether the queue repeats the track. */
|
|
@@ -95,6 +96,8 @@ class Player {
|
|
|
95
96
|
this.node = node || this.manager.useableNodes;
|
|
96
97
|
if (!this.node)
|
|
97
98
|
throw new RangeError("No available nodes.");
|
|
99
|
+
// Initialize the queue with guild and manager
|
|
100
|
+
this.queue = new Queue_1.Queue(this.guild, this.manager);
|
|
98
101
|
this.manager.players.set(options.guild, this);
|
|
99
102
|
this.manager.emit("playerCreate", this);
|
|
100
103
|
this.setVolume(options.volume ?? 100);
|
|
@@ -124,7 +127,7 @@ class Player {
|
|
|
124
127
|
},
|
|
125
128
|
});
|
|
126
129
|
this.state = "CONNECTED";
|
|
127
|
-
this.manager.emit("playerStateUpdate", oldPlayer, this);
|
|
130
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this, "connectionChange");
|
|
128
131
|
return this;
|
|
129
132
|
}
|
|
130
133
|
/** Disconnect from the voice channel. */
|
|
@@ -145,11 +148,12 @@ class Player {
|
|
|
145
148
|
});
|
|
146
149
|
this.voiceChannel = null;
|
|
147
150
|
this.state = "DISCONNECTED";
|
|
148
|
-
this.manager.emit("playerStateUpdate", oldPlayer, this);
|
|
151
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this, "connectionChange");
|
|
149
152
|
return this;
|
|
150
153
|
}
|
|
151
154
|
/** Destroys the player. */
|
|
152
155
|
destroy(disconnect = true) {
|
|
156
|
+
const oldPlayer = { ...this };
|
|
153
157
|
this.state = "DESTROYING";
|
|
154
158
|
if (disconnect) {
|
|
155
159
|
this.disconnect();
|
|
@@ -157,6 +161,7 @@ class Player {
|
|
|
157
161
|
this.node.rest.destroyPlayer(this.guild);
|
|
158
162
|
this.manager.emit("playerDestroy", this);
|
|
159
163
|
this.manager.players.delete(this.guild);
|
|
164
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this, "playerDestroy");
|
|
160
165
|
}
|
|
161
166
|
/**
|
|
162
167
|
* Sets the player voice channel.
|
|
@@ -168,7 +173,7 @@ class Player {
|
|
|
168
173
|
const oldPlayer = { ...this };
|
|
169
174
|
this.voiceChannel = channel;
|
|
170
175
|
this.connect();
|
|
171
|
-
this.manager.emit("playerStateUpdate", oldPlayer, this);
|
|
176
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this, "channelChange");
|
|
172
177
|
return this;
|
|
173
178
|
}
|
|
174
179
|
/**
|
|
@@ -180,7 +185,7 @@ class Player {
|
|
|
180
185
|
throw new TypeError("Channel must be a non-empty string.");
|
|
181
186
|
const oldPlayer = { ...this };
|
|
182
187
|
this.textChannel = channel;
|
|
183
|
-
this.manager.emit("playerStateUpdate", oldPlayer, this);
|
|
188
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this, "channelChange");
|
|
184
189
|
return this;
|
|
185
190
|
}
|
|
186
191
|
/** Sets the now playing message. */
|
|
@@ -188,7 +193,8 @@ class Player {
|
|
|
188
193
|
if (!message) {
|
|
189
194
|
throw new TypeError("You must provide the message of the now playing message.");
|
|
190
195
|
}
|
|
191
|
-
|
|
196
|
+
this.nowPlayingMessage = message;
|
|
197
|
+
return this.nowPlayingMessage;
|
|
192
198
|
}
|
|
193
199
|
async play(optionsOrTrack, playOptions) {
|
|
194
200
|
if (typeof optionsOrTrack !== "undefined" && Utils_1.TrackUtils.validate(optionsOrTrack)) {
|
|
@@ -238,7 +244,7 @@ class Player {
|
|
|
238
244
|
const oldPlayer = { ...this };
|
|
239
245
|
this.isAutoplay = autoplayState;
|
|
240
246
|
this.set("Internal_BotUser", botUser);
|
|
241
|
-
this.manager.emit("playerStateUpdate", oldPlayer, this);
|
|
247
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this, "autoplayChange");
|
|
242
248
|
return this;
|
|
243
249
|
}
|
|
244
250
|
/**
|
|
@@ -326,7 +332,7 @@ class Player {
|
|
|
326
332
|
},
|
|
327
333
|
});
|
|
328
334
|
this.volume = volume;
|
|
329
|
-
this.manager.emit("playerStateUpdate", oldPlayer, this);
|
|
335
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this, "volumeChange");
|
|
330
336
|
return this;
|
|
331
337
|
}
|
|
332
338
|
/**
|
|
@@ -366,7 +372,7 @@ class Player {
|
|
|
366
372
|
this.queueRepeat = false;
|
|
367
373
|
this.dynamicRepeat = false;
|
|
368
374
|
}
|
|
369
|
-
this.manager.emit("playerStateUpdate", oldPlayer, this);
|
|
375
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this, "repeatChange");
|
|
370
376
|
return this;
|
|
371
377
|
}
|
|
372
378
|
/**
|
|
@@ -387,7 +393,7 @@ class Player {
|
|
|
387
393
|
this.queueRepeat = false;
|
|
388
394
|
this.dynamicRepeat = false;
|
|
389
395
|
}
|
|
390
|
-
this.manager.emit("playerStateUpdate", oldPlayer, this);
|
|
396
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this, "repeatChange");
|
|
391
397
|
return this;
|
|
392
398
|
}
|
|
393
399
|
/**
|
|
@@ -423,7 +429,7 @@ class Player {
|
|
|
423
429
|
this.queueRepeat = false;
|
|
424
430
|
this.dynamicRepeat = false;
|
|
425
431
|
}
|
|
426
|
-
this.manager.emit("playerStateUpdate", oldPlayer, this);
|
|
432
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this, "repeatChange");
|
|
427
433
|
return this;
|
|
428
434
|
}
|
|
429
435
|
/** Restarts the current track to the start. */
|
|
@@ -455,7 +461,7 @@ class Player {
|
|
|
455
461
|
encodedTrack: null,
|
|
456
462
|
},
|
|
457
463
|
});
|
|
458
|
-
this.manager.emit("playerStateUpdate", oldPlayer, this);
|
|
464
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this, "trackChange");
|
|
459
465
|
return this;
|
|
460
466
|
}
|
|
461
467
|
/**
|
|
@@ -476,7 +482,7 @@ class Player {
|
|
|
476
482
|
paused: pause,
|
|
477
483
|
},
|
|
478
484
|
});
|
|
479
|
-
this.manager.emit("playerStateUpdate", oldPlayer, this);
|
|
485
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this, "pauseChange");
|
|
480
486
|
return this;
|
|
481
487
|
}
|
|
482
488
|
/** Go back to the previous song. */
|
|
@@ -484,7 +490,7 @@ class Player {
|
|
|
484
490
|
const oldPlayer = { ...this };
|
|
485
491
|
this.queue.unshift(this.queue.previous);
|
|
486
492
|
this.stop();
|
|
487
|
-
this.manager.emit("playerStateUpdate", oldPlayer, this);
|
|
493
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this, "trackChange");
|
|
488
494
|
return this;
|
|
489
495
|
}
|
|
490
496
|
/**
|
|
@@ -508,7 +514,7 @@ class Player {
|
|
|
508
514
|
position: position,
|
|
509
515
|
},
|
|
510
516
|
});
|
|
511
|
-
this.manager.emit("playerStateUpdate", oldPlayer, this);
|
|
517
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this, "trackChange");
|
|
512
518
|
return this;
|
|
513
519
|
}
|
|
514
520
|
}
|
package/dist/structures/Queue.js
CHANGED
|
@@ -23,12 +23,22 @@ class Queue extends Array {
|
|
|
23
23
|
current = null;
|
|
24
24
|
/** The previous track */
|
|
25
25
|
previous = null;
|
|
26
|
+
/** The Manager instance. */
|
|
27
|
+
manager;
|
|
28
|
+
/** The guild property. */
|
|
29
|
+
guild;
|
|
30
|
+
constructor(guild, manager) {
|
|
31
|
+
super();
|
|
32
|
+
this.manager = manager; // Initialize the manager
|
|
33
|
+
this.guild = guild; // Initialize the guild property
|
|
34
|
+
}
|
|
26
35
|
/**
|
|
27
36
|
* Adds a track to the queue.
|
|
28
37
|
* @param track
|
|
29
38
|
* @param [offset=null]
|
|
30
39
|
*/
|
|
31
40
|
add(track, offset) {
|
|
41
|
+
const oldPlayer = { ...this.manager.players.get(this.guild) };
|
|
32
42
|
if (!Utils_1.TrackUtils.validate(track)) {
|
|
33
43
|
throw new RangeError('Track must be a "Track" or "Track[]".');
|
|
34
44
|
}
|
|
@@ -65,8 +75,10 @@ class Queue extends Array {
|
|
|
65
75
|
}
|
|
66
76
|
}
|
|
67
77
|
}
|
|
78
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this.manager.players.get(this.guild), "queueChange");
|
|
68
79
|
}
|
|
69
80
|
remove(startOrPosition = 0, end) {
|
|
81
|
+
const oldPlayer = { ...this.manager.players.get(this.guild) };
|
|
70
82
|
if (typeof end !== "undefined") {
|
|
71
83
|
if (isNaN(Number(startOrPosition)) || isNaN(Number(end))) {
|
|
72
84
|
throw new RangeError(`Missing "start" or "end" parameter.`);
|
|
@@ -74,22 +86,31 @@ class Queue extends Array {
|
|
|
74
86
|
if (startOrPosition >= end || startOrPosition >= this.length) {
|
|
75
87
|
throw new RangeError("Invalid start or end values.");
|
|
76
88
|
}
|
|
77
|
-
|
|
89
|
+
this.splice(startOrPosition, end - startOrPosition);
|
|
90
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this.manager.players.get(this.guild), "queueChange");
|
|
91
|
+
return;
|
|
78
92
|
}
|
|
79
|
-
|
|
93
|
+
this.splice(startOrPosition, 1);
|
|
94
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this.manager.players.get(this.guild), "queueChange");
|
|
95
|
+
return;
|
|
80
96
|
}
|
|
81
97
|
/** Clears the queue. */
|
|
82
98
|
clear() {
|
|
99
|
+
const oldPlayer = { ...this.manager.players.get(this.guild) };
|
|
83
100
|
this.splice(0);
|
|
101
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this.manager.players.get(this.guild), "queueChange");
|
|
84
102
|
}
|
|
85
103
|
/** Shuffles the queue. */
|
|
86
104
|
shuffle() {
|
|
105
|
+
const oldPlayer = { ...this.manager.players.get(this.guild) };
|
|
87
106
|
for (let i = this.length - 1; i > 0; i--) {
|
|
88
107
|
const j = Math.floor(Math.random() * (i + 1));
|
|
89
108
|
[this[i], this[j]] = [this[j], this[i]];
|
|
90
109
|
}
|
|
110
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this.manager.players.get(this.guild), "queueChange");
|
|
91
111
|
}
|
|
92
112
|
userBlockShuffle() {
|
|
113
|
+
const oldPlayer = { ...this.manager.players.get(this.guild) };
|
|
93
114
|
const userTracks = new Map();
|
|
94
115
|
this.forEach((track) => {
|
|
95
116
|
const user = track.requester.id;
|
|
@@ -107,10 +128,12 @@ class Queue extends Array {
|
|
|
107
128
|
}
|
|
108
129
|
});
|
|
109
130
|
}
|
|
110
|
-
this.
|
|
131
|
+
this.splice(0);
|
|
111
132
|
this.add(shuffledQueue);
|
|
133
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this.manager.players.get(this.guild), "queueChange");
|
|
112
134
|
}
|
|
113
135
|
roundRobinShuffle() {
|
|
136
|
+
const oldPlayer = { ...this.manager.players.get(this.guild) };
|
|
114
137
|
const userTracks = new Map();
|
|
115
138
|
this.forEach((track) => {
|
|
116
139
|
const user = track.requester.id;
|
|
@@ -137,8 +160,9 @@ class Queue extends Array {
|
|
|
137
160
|
}
|
|
138
161
|
}
|
|
139
162
|
}
|
|
140
|
-
this.
|
|
163
|
+
this.splice(0);
|
|
141
164
|
this.add(shuffledQueue);
|
|
165
|
+
this.manager.emit("playerStateUpdate", oldPlayer, this.manager.players.get(this.guild), "queueChange");
|
|
142
166
|
}
|
|
143
167
|
}
|
|
144
168
|
exports.Queue = Queue;
|
package/dist/structures/Rest.js
CHANGED
|
@@ -39,6 +39,10 @@ class Rest {
|
|
|
39
39
|
async destroyPlayer(guildId) {
|
|
40
40
|
return await this.delete(`/v4/sessions/${this.sessionId}/players/${guildId}`);
|
|
41
41
|
}
|
|
42
|
+
/** Updates the session status for resuming. */
|
|
43
|
+
async updateSession(resuming, timeout) {
|
|
44
|
+
return await this.patch(`/v4/sessions/${this.sessionId}`, { resuming, timeout });
|
|
45
|
+
}
|
|
42
46
|
/* Sends a GET request to the specified endpoint and returns the response data. */
|
|
43
47
|
async request(method, endpoint, body) {
|
|
44
48
|
const config = {
|
package/dist/structures/Utils.js
CHANGED
|
@@ -17,9 +17,23 @@ class TrackUtils {
|
|
|
17
17
|
static setTrackPartial(partial) {
|
|
18
18
|
if (!Array.isArray(partial) || !partial.every((str) => typeof str === "string"))
|
|
19
19
|
throw new Error("Provided partial is not an array or not a string array.");
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const defaultProperties = [
|
|
21
|
+
"encoded",
|
|
22
|
+
"pluginInfo",
|
|
23
|
+
"identifier",
|
|
24
|
+
"isSeekable",
|
|
25
|
+
"author",
|
|
26
|
+
"length",
|
|
27
|
+
"isrc",
|
|
28
|
+
"isStream",
|
|
29
|
+
"title",
|
|
30
|
+
"uri",
|
|
31
|
+
"artworkUrl",
|
|
32
|
+
"sourceName",
|
|
33
|
+
];
|
|
34
|
+
this.trackPartial = Array.from(new Set([...defaultProperties, ...partial]));
|
|
35
|
+
if (!this.trackPartial.includes("track"))
|
|
36
|
+
this.trackPartial.unshift("track");
|
|
23
37
|
}
|
|
24
38
|
/**
|
|
25
39
|
* Checks if the provided argument is a valid Track or UnresolvedTrack, if provided an array then every element will be checked.
|
|
@@ -81,7 +95,7 @@ class TrackUtils {
|
|
|
81
95
|
const finalSize = SIZES.find((s) => s === size) ?? "default";
|
|
82
96
|
return this.uri.includes("youtube") ? `https://img.youtube.com/vi/${data.info.identifier}/${finalSize}.jpg` : null;
|
|
83
97
|
},
|
|
84
|
-
requester,
|
|
98
|
+
requester: requester,
|
|
85
99
|
pluginInfo: data.pluginInfo,
|
|
86
100
|
customData: {},
|
|
87
101
|
};
|
|
@@ -112,7 +126,7 @@ class TrackUtils {
|
|
|
112
126
|
if (typeof query === "undefined")
|
|
113
127
|
throw new RangeError('Argument "query" must be present.');
|
|
114
128
|
let unresolvedTrack = {
|
|
115
|
-
requester,
|
|
129
|
+
requester: requester,
|
|
116
130
|
async resolve() {
|
|
117
131
|
const resolved = await TrackUtils.getClosestTrack(this);
|
|
118
132
|
Object.getOwnPropertyNames(this).forEach((prop) => delete this[prop]);
|