aqualink 1.4.2 → 1.5.0
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 +8 -8
- package/build/handlers/fetchImage.js +4 -5
- package/build/structures/Aqua.js +55 -41
- package/build/structures/Connection.js +37 -19
- package/build/structures/Node.js +6 -1
- package/build/structures/Player.js +41 -102
- package/build/structures/Track.js +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,14 +25,14 @@ This code is based in riffy, but its an 100% Rewrite made from scratch...
|
|
|
25
25
|
|
|
26
26
|
- Example bot: https://github.com/ToddyTheNoobDud/Thorium-Music
|
|
27
27
|
|
|
28
|
-
# Yay, Version 1.
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
28
|
+
# Yay, Version 1.5.0 is released, wow
|
|
29
|
+
|
|
30
|
+
- Optimized memory usage in `FetchImage` by removing unnecessary code and saving, addressing some memory leaks.
|
|
31
|
+
- Updated `AQUA` with additional cleanup options, faster response arrays, Remade `ConstructResponse` and `resolve`, and improved the `UpdateVoice` handler.
|
|
32
|
+
- Updated `CONNECTION` to fix bugs, improve the cleanup system, and Improve speed.
|
|
33
|
+
- Improved cleanup in the `NODE` manager and fixed issues for VPS.
|
|
34
|
+
- Rewrited the `TRACK` handler for better speed by removing redundant checks and code.
|
|
35
|
+
- REMADE the `PLAYER` system to fix bugs, resolve message sending issues, Fixes `EventEmitter` memory leaks (also Fixes `AQUA`), remove unnecessary JSDoc comments, rewrite some methods, and enhance cleanup.
|
|
36
36
|
|
|
37
37
|
# How to install
|
|
38
38
|
|
|
@@ -23,11 +23,10 @@ async function getImageUrl(info) {
|
|
|
23
23
|
async function fetchThumbnail(url) {
|
|
24
24
|
try {
|
|
25
25
|
const response = await request(url, { method: "GET" });
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return null;
|
|
26
|
+
const json = await response.json();
|
|
27
|
+
response.body.destroy();
|
|
28
|
+
|
|
29
|
+
return json.thumbnail_url || null;
|
|
31
30
|
} catch (error) {
|
|
32
31
|
console.error(`Error fetching ${url}:`, error);
|
|
33
32
|
return null;
|
package/build/structures/Aqua.js
CHANGED
|
@@ -5,6 +5,16 @@ const { Track } = require("./Track");
|
|
|
5
5
|
const { version: pkgVersion } = require("../../package.json");
|
|
6
6
|
|
|
7
7
|
class Aqua extends EventEmitter {
|
|
8
|
+
/**
|
|
9
|
+
* @param {Object} client - The client instance.
|
|
10
|
+
* @param {Array<Object>} nodes - An array of node configurations.
|
|
11
|
+
* @param {Object} options - Configuration options for Aqua.
|
|
12
|
+
* @param {Function} options.send - Function to send data.
|
|
13
|
+
* @param {string} [options.defaultSearchPlatform="ytsearch"] - Default search platform.
|
|
14
|
+
* @param {string} [options.restVersion="v4"] - Version of the REST API.
|
|
15
|
+
* @param {Array<Object>} [options.plugins=[]] - Plugins to load.
|
|
16
|
+
* @param {string} [options.shouldDeleteMessage='none'] - Should delete your message? (true, false)
|
|
17
|
+
*/
|
|
8
18
|
constructor(client, nodes, options) {
|
|
9
19
|
super();
|
|
10
20
|
this.validateInputs(client, nodes, options);
|
|
@@ -16,21 +26,22 @@ class Aqua extends EventEmitter {
|
|
|
16
26
|
this.initiated = false;
|
|
17
27
|
this.shouldDeleteMessage = options.shouldDeleteMessage || false;
|
|
18
28
|
this.defaultSearchPlatform = options.defaultSearchPlatform || "ytsearch";
|
|
19
|
-
this.restVersion = options.restVersion || "
|
|
29
|
+
this.restVersion = options.restVersion || "v4";
|
|
20
30
|
this.plugins = options.plugins || [];
|
|
21
31
|
this.version = pkgVersion;
|
|
22
32
|
this.options = options;
|
|
23
33
|
this.send = options.send;
|
|
34
|
+
this.setMaxListeners(0);
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
validateInputs(client, nodes, options) {
|
|
27
38
|
if (!client) throw new Error("Client is required to initialize Aqua");
|
|
28
39
|
if (!Array.isArray(nodes) || nodes.length === 0) throw new Error(`Nodes must be a non-empty Array (Received ${typeof nodes})`);
|
|
29
|
-
if (typeof options
|
|
40
|
+
if (typeof options?.send !== "function") throw new Error("Send function is required to initialize Aqua");
|
|
30
41
|
}
|
|
31
42
|
|
|
32
43
|
get leastUsedNodes() {
|
|
33
|
-
return
|
|
44
|
+
return Array.from(this.nodeMap.values())
|
|
34
45
|
.filter(node => node.connected)
|
|
35
46
|
.sort((a, b) => a.rest.calls - b.rest.calls);
|
|
36
47
|
}
|
|
@@ -63,14 +74,10 @@ class Aqua extends EventEmitter {
|
|
|
63
74
|
|
|
64
75
|
updateVoiceState(packet) {
|
|
65
76
|
const player = this.players.get(packet.d.guild_id);
|
|
66
|
-
if (player) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
player.connection.setStateUpdate(packet.d);
|
|
71
|
-
if (packet.d.status === "disconnected") {
|
|
72
|
-
this.cleanupPlayer(player); // Cleanup when disconnected
|
|
73
|
-
}
|
|
77
|
+
if (player && (packet.t === "VOICE_SERVER_UPDATE" || packet.t === "VOICE_STATE_UPDATE" && packet.d.user_id === this.clientId)) {
|
|
78
|
+
player.connection[packet.t === "VOICE_SERVER_UPDATE" ? "setServerUpdate" : "setStateUpdate"](packet.d);
|
|
79
|
+
if (packet.d.status === "disconnected") {
|
|
80
|
+
this.cleanupPlayer(player); // Cleanup when disconnected
|
|
74
81
|
}
|
|
75
82
|
}
|
|
76
83
|
}
|
|
@@ -98,6 +105,7 @@ class Aqua extends EventEmitter {
|
|
|
98
105
|
createPlayer(node, options) {
|
|
99
106
|
const player = new Player(this, node, options);
|
|
100
107
|
this.players.set(options.guildId, player);
|
|
108
|
+
player.setMaxListeners(0);
|
|
101
109
|
player.connect(options);
|
|
102
110
|
player.on("destroy", () => this.cleanupPlayer(player)); // Listen for player destruction
|
|
103
111
|
this.emit("playerCreate", player);
|
|
@@ -114,20 +122,10 @@ class Aqua extends EventEmitter {
|
|
|
114
122
|
}
|
|
115
123
|
}
|
|
116
124
|
|
|
117
|
-
|
|
118
|
-
* Resolve a track into an array of {@link Track} objects.
|
|
119
|
-
*
|
|
120
|
-
* @param {Object} options - The options for the resolve operation.
|
|
121
|
-
* @param {string} options.query - The query to search for.
|
|
122
|
-
* @param {string} [options.source] - The source to use for the search. Defaults to the bot's default search platform.
|
|
123
|
-
* @param {GuildMember} options.requester - The member who requested the track.
|
|
124
|
-
* @param {Node[]} [options.nodes] - The nodes to prioritize for the search. Defaults to all nodes.
|
|
125
|
-
* @returns {Promise<Track[]>} The resolved tracks.
|
|
126
|
-
*/
|
|
127
|
-
async resolve({ query, source, requester, nodes }) {
|
|
125
|
+
async resolve({ query, source = this.defaultSearchPlatform, requester, nodes }) {
|
|
128
126
|
this.ensureInitialized();
|
|
129
127
|
const requestNode = this.getRequestNode(nodes);
|
|
130
|
-
const formattedQuery = this.formatQuery(query, source
|
|
128
|
+
const formattedQuery = this.formatQuery(query, source);
|
|
131
129
|
try {
|
|
132
130
|
let response = await requestNode.rest.makeRequest("GET", `/v4/loadtracks?identifier=${encodeURIComponent(formattedQuery)}`);
|
|
133
131
|
if (["empty", "NO_MATCHES"].includes(response.loadType)) {
|
|
@@ -157,11 +155,11 @@ class Aqua extends EventEmitter {
|
|
|
157
155
|
|
|
158
156
|
async handleNoMatches(rest, query) {
|
|
159
157
|
try {
|
|
160
|
-
const
|
|
161
|
-
if (["empty", "NO_MATCHES"].includes(
|
|
162
|
-
return await rest.makeRequest("GET", `/v4/loadtracks?identifier=https://
|
|
158
|
+
const youtubeResponse = await rest.makeRequest("GET", `/v4/loadtracks?identifier=https://www.youtube.com/watch?v=${query}`);
|
|
159
|
+
if (["empty", "NO_MATCHES"].includes(youtubeResponse.loadType)) {
|
|
160
|
+
return await rest.makeRequest("GET", `/v4/loadtracks?identifier=https://open.spotify.com/track/${query}`);
|
|
163
161
|
}
|
|
164
|
-
return
|
|
162
|
+
return youtubeResponse;
|
|
165
163
|
} catch (error) {
|
|
166
164
|
console.error("Error handling no matches:", error);
|
|
167
165
|
throw new Error("Failed to handle no matches");
|
|
@@ -176,26 +174,28 @@ class Aqua extends EventEmitter {
|
|
|
176
174
|
pluginInfo: response.pluginInfo || {},
|
|
177
175
|
tracks: [],
|
|
178
176
|
};
|
|
179
|
-
|
|
177
|
+
const { loadType, data } = response;
|
|
178
|
+
if (loadType === "error" || loadType === "LOAD_FAILED") {
|
|
179
|
+
baseResponse.exception = data || response.exception;
|
|
180
|
+
return baseResponse;
|
|
181
|
+
}
|
|
182
|
+
switch (loadType) {
|
|
180
183
|
case "track":
|
|
181
|
-
if (
|
|
182
|
-
baseResponse.tracks.push(new Track(
|
|
184
|
+
if (data) {
|
|
185
|
+
baseResponse.tracks.push(new Track(data, requester, requestNode));
|
|
183
186
|
}
|
|
184
187
|
break;
|
|
185
188
|
case "playlist":
|
|
186
189
|
baseResponse.playlistInfo = {
|
|
187
|
-
name:
|
|
188
|
-
...
|
|
190
|
+
name: data?.info?.name || data?.info?.title,
|
|
191
|
+
...data?.info,
|
|
189
192
|
};
|
|
190
|
-
baseResponse.tracks =
|
|
193
|
+
baseResponse.tracks = (data?.tracks || []).map(track => new Track(track, requester, requestNode));
|
|
191
194
|
break;
|
|
192
195
|
case "search":
|
|
193
|
-
baseResponse.tracks =
|
|
196
|
+
baseResponse.tracks = (data || []).map(track => new Track(track, requester, requestNode));
|
|
194
197
|
break;
|
|
195
198
|
}
|
|
196
|
-
if (response.loadType === "error" || response.loadType === "LOAD_FAILED") {
|
|
197
|
-
baseResponse.exception = response.loadType.data || response.loadType.exception;
|
|
198
|
-
}
|
|
199
199
|
return baseResponse;
|
|
200
200
|
}
|
|
201
201
|
|
|
@@ -208,9 +208,7 @@ class Aqua extends EventEmitter {
|
|
|
208
208
|
cleanupIdle() {
|
|
209
209
|
for (const [guildId, player] of this.players) {
|
|
210
210
|
if (!player.playing && !player.paused && player.queue.isEmpty()) {
|
|
211
|
-
|
|
212
|
-
this.players.delete(guildId);
|
|
213
|
-
this.emit("playerDestroy", player);
|
|
211
|
+
this.cleanupPlayer(player);
|
|
214
212
|
}
|
|
215
213
|
}
|
|
216
214
|
}
|
|
@@ -219,11 +217,27 @@ class Aqua extends EventEmitter {
|
|
|
219
217
|
if (player) {
|
|
220
218
|
player.clearData();
|
|
221
219
|
player.destroy();
|
|
222
|
-
this.voice = null
|
|
223
220
|
this.players.delete(player.guildId);
|
|
224
221
|
this.emit("playerDestroy", player);
|
|
225
222
|
}
|
|
226
223
|
}
|
|
224
|
+
|
|
225
|
+
cleanup() {
|
|
226
|
+
for (const player of this.players.values()) {
|
|
227
|
+
this.cleanupPlayer(player);
|
|
228
|
+
}
|
|
229
|
+
for (const node of this.nodeMap.values()) {
|
|
230
|
+
this.destroyNode(node.name || node.host);
|
|
231
|
+
}
|
|
232
|
+
this.nodeMap.clear();
|
|
233
|
+
this.players.clear();
|
|
234
|
+
this.client = null;
|
|
235
|
+
this.nodes = null;
|
|
236
|
+
this.plugins = null;
|
|
237
|
+
this.options = null;
|
|
238
|
+
this.send = null;
|
|
239
|
+
this.version = null;
|
|
240
|
+
}
|
|
227
241
|
}
|
|
228
242
|
|
|
229
243
|
module.exports = { Aqua };
|
|
@@ -8,11 +8,24 @@ class Connection {
|
|
|
8
8
|
this.voiceChannel = player.voiceChannel;
|
|
9
9
|
this.lastUpdateTime = 0;
|
|
10
10
|
this.updateThrottle = 1000;
|
|
11
|
+
|
|
12
|
+
// Bind event listeners
|
|
13
|
+
this.onPlayerMove = this.onPlayerMove.bind(this);
|
|
14
|
+
this.onPlayerLeave = this.onPlayerLeave.bind(this);
|
|
15
|
+
this.player.aqua.on("playerMove", this.onPlayerMove);
|
|
16
|
+
this.player.aqua.on("playerLeave", this.onPlayerLeave);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
onPlayerMove(oldChannel, newChannel) {
|
|
20
|
+
// Handle player movement
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
onPlayerLeave(channelId) {
|
|
24
|
+
// Handle player leaving
|
|
11
25
|
}
|
|
12
26
|
|
|
13
27
|
setServerUpdate({ endpoint, token }) {
|
|
14
28
|
if (!endpoint) throw new Error("Missing 'endpoint' property in VOICE_SERVER_UPDATE");
|
|
15
|
-
|
|
16
29
|
const newRegion = endpoint.split('.')[0].replace(/[0-9]/g, "");
|
|
17
30
|
if (this.region !== newRegion) {
|
|
18
31
|
this.updateRegion(newRegion, endpoint, token);
|
|
@@ -25,52 +38,57 @@ class Connection {
|
|
|
25
38
|
this.region = newRegion;
|
|
26
39
|
this.voice.endpoint = endpoint;
|
|
27
40
|
this.voice.token = token;
|
|
28
|
-
|
|
29
41
|
this.player.aqua.emit("debug", `[Player ${this.player.guildId} - CONNECTION] ${previousVoiceRegion ? `Changed Voice Region from ${previousVoiceRegion} to ${this.region}` : `Voice Server: ${this.region}`}`);
|
|
30
|
-
|
|
31
42
|
if (this.player.paused) {
|
|
32
43
|
this.player.pause(false);
|
|
33
44
|
}
|
|
34
45
|
}
|
|
35
46
|
|
|
36
|
-
setStateUpdate(
|
|
37
|
-
if (!channel_id || !session_id) {
|
|
47
|
+
setStateUpdate(data) {
|
|
48
|
+
if (!data.channel_id || !data.session_id) {
|
|
38
49
|
this.cleanup();
|
|
39
50
|
return;
|
|
40
51
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.player.
|
|
44
|
-
this.
|
|
45
|
-
this.voiceChannel = channel_id;
|
|
52
|
+
if (this.player.voiceChannel !== data.channel_id) {
|
|
53
|
+
this.player.aqua.emit("playerMove", this.player.voiceChannel, data.channel_id);
|
|
54
|
+
this.player.voiceChannel = data.channel_id;
|
|
55
|
+
this.voiceChannel = data.channel_id;
|
|
46
56
|
}
|
|
47
|
-
|
|
48
|
-
this.
|
|
49
|
-
this.
|
|
50
|
-
this.voice.sessionId = session_id;
|
|
57
|
+
this.selfDeaf = data.self_deaf;
|
|
58
|
+
this.selfMute = data.self_mute;
|
|
59
|
+
this.voice.sessionId = data.session_id;
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
updatePlayerVoiceData() {
|
|
54
63
|
const currentTime = Date.now();
|
|
55
64
|
if (currentTime - this.lastUpdateTime >= this.updateThrottle) {
|
|
56
65
|
this.lastUpdateTime = currentTime;
|
|
66
|
+
const data = ({
|
|
67
|
+
voice: this.voice,
|
|
68
|
+
volume: this.player.volume,
|
|
69
|
+
});
|
|
57
70
|
this.player.nodes.rest.updatePlayer({
|
|
58
71
|
guildId: this.player.guildId,
|
|
59
|
-
data
|
|
60
|
-
voice: this.voice,
|
|
61
|
-
volume: this.player.volume
|
|
62
|
-
}
|
|
72
|
+
data,
|
|
63
73
|
});
|
|
64
74
|
}
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
cleanup() {
|
|
78
|
+
this.player.aqua.off("playerMove", this.onPlayerMove);
|
|
79
|
+
this.player.aqua.off("playerLeave", this.onPlayerLeave);
|
|
68
80
|
this.player.aqua.emit("playerLeave", this.player.voiceChannel);
|
|
69
81
|
this.player.voiceChannel = null;
|
|
70
82
|
this.voiceChannel = null;
|
|
71
83
|
this.player.destroy();
|
|
72
84
|
this.player.aqua.emit("playerDestroy", this.player);
|
|
85
|
+
this.player = null;
|
|
86
|
+
this.voice = null;
|
|
87
|
+
this.region = null;
|
|
88
|
+
this.selfDeaf = null;
|
|
89
|
+
this.selfMute = null;
|
|
90
|
+
this.voiceChannel = null;
|
|
73
91
|
}
|
|
74
92
|
}
|
|
75
93
|
|
|
76
|
-
module.exports = { Connection };
|
|
94
|
+
module.exports = { Connection };
|
package/build/structures/Node.js
CHANGED
|
@@ -235,6 +235,11 @@ class Node {
|
|
|
235
235
|
this.aqua.emit("nodeDestroy", this);
|
|
236
236
|
this.aqua.nodeMap.delete(this.name);
|
|
237
237
|
this.connected = false;
|
|
238
|
+
if (this.ws) {
|
|
239
|
+
this.ws.removeAllListeners();
|
|
240
|
+
this.ws.close();
|
|
241
|
+
this.ws = null;
|
|
242
|
+
}
|
|
238
243
|
}
|
|
239
244
|
|
|
240
245
|
disconnect() {
|
|
@@ -259,4 +264,4 @@ class Node {
|
|
|
259
264
|
}
|
|
260
265
|
}
|
|
261
266
|
|
|
262
|
-
module.exports = { Node };
|
|
267
|
+
module.exports = { Node };
|
|
@@ -4,21 +4,6 @@ const { Queue } = require("./Queue");
|
|
|
4
4
|
const { Filters } = require("./Filters");
|
|
5
5
|
|
|
6
6
|
class Player extends EventEmitter {
|
|
7
|
-
/**
|
|
8
|
-
* Player constructor
|
|
9
|
-
* @param {Aqua} aqua the Aqua client instance
|
|
10
|
-
* @param {Array<Node>} nodes the nodes to connect to
|
|
11
|
-
* @param {Object} options the options to use
|
|
12
|
-
* @param {String} options.guildId the guild id to play in
|
|
13
|
-
* @param {String} options.textChannel the text channel to send messages in
|
|
14
|
-
* @param {String} options.voiceChannel the voice channel to join
|
|
15
|
-
* @param {Boolean} options.mute if the player should be muted
|
|
16
|
-
* @param {Boolean} options.deaf if the player should be deafened
|
|
17
|
-
* @param {Number} options.defaultVolume the default volume to use
|
|
18
|
-
* @param {String} options.loop the loop mode to use
|
|
19
|
-
* @param {Map} options.data the data to use
|
|
20
|
-
* @param {Boolean} options.shouldDeleteMessage if the player should delete the now playing message
|
|
21
|
-
*/
|
|
22
7
|
constructor(aqua, nodes, options = {}) {
|
|
23
8
|
super();
|
|
24
9
|
this.aqua = aqua;
|
|
@@ -32,7 +17,7 @@ class Player extends EventEmitter {
|
|
|
32
17
|
this.deaf = options.deaf ?? false;
|
|
33
18
|
this.volume = options.defaultVolume ?? 100;
|
|
34
19
|
this.loop = options.loop ?? "none";
|
|
35
|
-
this.data = new Map()
|
|
20
|
+
this.data = new Map();
|
|
36
21
|
this.queue = new Queue();
|
|
37
22
|
this.position = 0;
|
|
38
23
|
this.current = null;
|
|
@@ -70,67 +55,58 @@ class Player extends EventEmitter {
|
|
|
70
55
|
}
|
|
71
56
|
|
|
72
57
|
/**
|
|
73
|
-
*
|
|
58
|
+
* Play the next track in the queue.
|
|
74
59
|
*
|
|
75
|
-
* @returns {Promise<Player>} The player instance.
|
|
76
60
|
* @throws {Error} If the player is not connected.
|
|
77
|
-
* @
|
|
61
|
+
* @returns {Promise<Player>} The player instance.
|
|
78
62
|
*/
|
|
79
63
|
async play() {
|
|
80
64
|
if (!this.connected) throw new Error("Player must be connected first.");
|
|
81
65
|
if (!this.queue.length) return;
|
|
82
|
-
|
|
83
66
|
this.current = this.queue.shift();
|
|
84
67
|
this.current = this.current.track ? this.current : await this.current.resolve(this.aqua);
|
|
85
68
|
this.playing = true;
|
|
86
69
|
this.position = 0;
|
|
87
|
-
|
|
88
70
|
this.aqua.emit("debug", this.guildId, `Playing track: ${this.current.track}`);
|
|
89
71
|
await this.updatePlayer({ track: { encoded: this.current.track } });
|
|
90
72
|
return this;
|
|
91
73
|
}
|
|
92
74
|
|
|
93
75
|
/**
|
|
94
|
-
* Connects the player to a voice channel.
|
|
95
|
-
*
|
|
96
|
-
* @param {
|
|
97
|
-
* @param {
|
|
98
|
-
* @param {
|
|
99
|
-
* @param {
|
|
100
|
-
* @
|
|
76
|
+
* Connects the player to a specified voice channel.
|
|
77
|
+
*
|
|
78
|
+
* @param {Object} options - Options for connecting the player.
|
|
79
|
+
* @param {string} options.guildId - The ID of the guild.
|
|
80
|
+
* @param {string} options.voiceChannel - The ID of the voice channel to connect to.
|
|
81
|
+
* @param {boolean} [options.deaf=true] - Whether the player should be self-deafened.
|
|
82
|
+
* @param {boolean} [options.mute=false] - Whether the player should be self-muted.
|
|
101
83
|
* @throws {Error} If the player is already connected.
|
|
84
|
+
* @returns {Promise<Player>} The player instance.
|
|
102
85
|
*/
|
|
86
|
+
|
|
103
87
|
async connect(options) {
|
|
104
88
|
if (this.connected) throw new Error("Player is already connected.");
|
|
105
89
|
const { guildId, voiceChannel, deaf = true, mute = false } = options;
|
|
106
|
-
|
|
107
90
|
this.send({ guild_id: guildId, channel_id: voiceChannel, self_deaf: deaf, self_mute: mute });
|
|
108
91
|
this.connected = true;
|
|
109
92
|
this.aqua.emit("debug", this.guildId, `Player connected to voice channel: ${voiceChannel}.`);
|
|
110
93
|
return this;
|
|
111
94
|
}
|
|
112
95
|
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Destroys the player.
|
|
116
|
-
*
|
|
117
|
-
* @returns {Promise<Player>} The player instance.
|
|
118
|
-
* @throws {Error} If the player is not connected.
|
|
119
|
-
*/
|
|
120
96
|
async destroy() {
|
|
121
|
-
await this.
|
|
122
|
-
this.
|
|
123
|
-
this.
|
|
97
|
+
await this.updatePlayer({ track: { encoded: null } });
|
|
98
|
+
await this.disconnect();
|
|
99
|
+
this.cleanup();
|
|
100
|
+
this.clearData();
|
|
124
101
|
}
|
|
125
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Pauses or resumes the player.
|
|
105
|
+
*
|
|
106
|
+
* @param {boolean} paused - If true, the player will be paused; if false, it will resume.
|
|
107
|
+
* @returns {Promise<Player>} The player instance.
|
|
108
|
+
*/
|
|
126
109
|
|
|
127
|
-
/**
|
|
128
|
-
* Pauses or unpauses the player.
|
|
129
|
-
*
|
|
130
|
-
* @param {Boolean} paused whether to pause or not
|
|
131
|
-
* @returns {Promise<Player>} The player instance.
|
|
132
|
-
* @throws {Error} If the player is not connected.
|
|
133
|
-
*/
|
|
134
110
|
async pause(paused) {
|
|
135
111
|
this.paused = paused;
|
|
136
112
|
await this.updatePlayer({ paused });
|
|
@@ -138,11 +114,11 @@ class Player extends EventEmitter {
|
|
|
138
114
|
}
|
|
139
115
|
|
|
140
116
|
/**
|
|
141
|
-
* Seeks
|
|
117
|
+
* Seeks to a position in the currently playing track.
|
|
142
118
|
*
|
|
143
|
-
* @param {
|
|
144
|
-
* @returns {Promise<Player>} The player instance.
|
|
119
|
+
* @param {number} position - The position in milliseconds to seek to.
|
|
145
120
|
* @throws {Error} If the position is negative.
|
|
121
|
+
* @returns {Promise<Player>} The player instance.
|
|
146
122
|
*/
|
|
147
123
|
async seek(position) {
|
|
148
124
|
if (position < 0) throw new Error("Seek position cannot be negative.");
|
|
@@ -151,12 +127,6 @@ class Player extends EventEmitter {
|
|
|
151
127
|
return this;
|
|
152
128
|
}
|
|
153
129
|
|
|
154
|
-
/**
|
|
155
|
-
* Stops the player and resets its state.
|
|
156
|
-
*
|
|
157
|
-
* @returns {Promise<Player>} The player instance.
|
|
158
|
-
* @throws {Error} If the player is not connected.
|
|
159
|
-
*/
|
|
160
130
|
async stop() {
|
|
161
131
|
if (!this.playing) return this;
|
|
162
132
|
await this.updatePlayer({ track: { encoded: null } });
|
|
@@ -168,9 +138,9 @@ class Player extends EventEmitter {
|
|
|
168
138
|
/**
|
|
169
139
|
* Sets the volume of the player.
|
|
170
140
|
*
|
|
171
|
-
* @param {
|
|
141
|
+
* @param {number} volume - The volume to set, between 0 and 200.
|
|
142
|
+
* @throws {Error} If the volume is out of range.
|
|
172
143
|
* @returns {Promise<Player>} The player instance.
|
|
173
|
-
* @throws {Error} If the volume is invalid.
|
|
174
144
|
*/
|
|
175
145
|
async setVolume(volume) {
|
|
176
146
|
if (volume < 0 || volume > 200) throw new Error("Volume must be between 0 and 200.");
|
|
@@ -182,9 +152,9 @@ class Player extends EventEmitter {
|
|
|
182
152
|
/**
|
|
183
153
|
* Sets the loop mode of the player.
|
|
184
154
|
*
|
|
185
|
-
* @param {
|
|
155
|
+
* @param {string} mode - The loop mode to set, either "none", "track", or "queue".
|
|
156
|
+
* @throws {Error} If the mode is not one of the above.
|
|
186
157
|
* @returns {Promise<Player>} The player instance.
|
|
187
|
-
* @throws {Error} If the loop mode is invalid.
|
|
188
158
|
*/
|
|
189
159
|
async setLoop(mode) {
|
|
190
160
|
if (!["none", "track", "queue"].includes(mode)) throw new Error("Loop mode must be 'none', 'track', or 'queue'.");
|
|
@@ -194,24 +164,24 @@ class Player extends EventEmitter {
|
|
|
194
164
|
}
|
|
195
165
|
|
|
196
166
|
/**
|
|
197
|
-
* Sets the text channel
|
|
167
|
+
* Sets the text channel for the player.
|
|
198
168
|
*
|
|
199
|
-
* @param {
|
|
169
|
+
* @param {string} channel - The ID of the text channel to set.
|
|
200
170
|
* @returns {Promise<Player>} The player instance.
|
|
201
|
-
* @throws {Error} If the channel is invalid.
|
|
202
171
|
*/
|
|
172
|
+
|
|
203
173
|
async setTextChannel(channel) {
|
|
204
174
|
await this.updatePlayer({ text_channel: channel });
|
|
205
175
|
return this;
|
|
206
176
|
}
|
|
207
177
|
|
|
208
178
|
/**
|
|
209
|
-
* Sets the voice channel
|
|
179
|
+
* Sets the voice channel for the player.
|
|
210
180
|
*
|
|
211
|
-
* @param {
|
|
212
|
-
* @returns {Promise<Player>} The player instance.
|
|
181
|
+
* @param {string} channel - The ID of the voice channel to set.
|
|
213
182
|
* @throws {TypeError} If the channel is not a non-empty string.
|
|
214
|
-
* @throws {ReferenceError} If the player is already connected to the
|
|
183
|
+
* @throws {ReferenceError} If the player is already connected to the channel.
|
|
184
|
+
* @returns {Promise<Player>} The player instance.
|
|
215
185
|
*/
|
|
216
186
|
async setVoiceChannel(channel) {
|
|
217
187
|
if (typeof channel !== "string") throw new TypeError("Channel must be a non-empty string.");
|
|
@@ -223,12 +193,6 @@ class Player extends EventEmitter {
|
|
|
223
193
|
return this;
|
|
224
194
|
}
|
|
225
195
|
|
|
226
|
-
/**
|
|
227
|
-
* Disconnects the player from the voice channel and clears the current track.
|
|
228
|
-
*
|
|
229
|
-
* @returns {Promise<void>} Resolves when the player is disconnected.
|
|
230
|
-
* @throws {Error} If the player is not connected to a voice channel.
|
|
231
|
-
*/
|
|
232
196
|
async disconnect() {
|
|
233
197
|
await this.updatePlayer({ track: { encoded: null } });
|
|
234
198
|
this.connected = false;
|
|
@@ -236,11 +200,6 @@ class Player extends EventEmitter {
|
|
|
236
200
|
this.aqua.emit("debug", this.guildId, "Player disconnected from voice channel.");
|
|
237
201
|
}
|
|
238
202
|
|
|
239
|
-
/**
|
|
240
|
-
* Shuffles the queue using the Fisher-Yates shuffle algorithm.
|
|
241
|
-
*
|
|
242
|
-
* @returns {Promise<Player>} The player instance.
|
|
243
|
-
*/
|
|
244
203
|
async shuffle() {
|
|
245
204
|
for (let i = this.queue.length - 1; i > 0; i--) {
|
|
246
205
|
const j = Math.floor(Math.random() * (i + 1));
|
|
@@ -249,31 +208,16 @@ class Player extends EventEmitter {
|
|
|
249
208
|
return this;
|
|
250
209
|
}
|
|
251
210
|
|
|
252
|
-
/**
|
|
253
|
-
* Retrieves the current queue of tracks.
|
|
254
|
-
*
|
|
255
|
-
* @returns {Promise<Array<Track>>} The current queue of tracks.
|
|
256
|
-
*/
|
|
257
211
|
async getQueue() {
|
|
258
212
|
return this.queue;
|
|
259
213
|
}
|
|
260
214
|
|
|
261
|
-
/**
|
|
262
|
-
* Replays the current track from the start.
|
|
263
|
-
*
|
|
264
|
-
* @returns {Promise<Player>} The player instance.
|
|
265
|
-
*/
|
|
266
215
|
async replay() {
|
|
267
216
|
return this.seek(0);
|
|
268
217
|
}
|
|
269
218
|
|
|
270
|
-
/**
|
|
271
|
-
* Skips the current track and plays the next one in the queue.
|
|
272
|
-
*
|
|
273
|
-
* @returns {Promise<Player>} The player instance.
|
|
274
|
-
*/
|
|
275
219
|
async skip() {
|
|
276
|
-
await this.stop()
|
|
220
|
+
await this.stop();
|
|
277
221
|
if (this.playing) return this.play();
|
|
278
222
|
}
|
|
279
223
|
|
|
@@ -304,6 +248,7 @@ class Player extends EventEmitter {
|
|
|
304
248
|
this.handleUnknownEvent(player, track, payload);
|
|
305
249
|
}
|
|
306
250
|
}
|
|
251
|
+
|
|
307
252
|
trackStart(player, track, payload) {
|
|
308
253
|
this.playing = true;
|
|
309
254
|
this.paused = false;
|
|
@@ -312,7 +257,7 @@ class Player extends EventEmitter {
|
|
|
312
257
|
|
|
313
258
|
trackChange(player, track, payload) {
|
|
314
259
|
this.playing = true;
|
|
315
|
-
this.paused = false
|
|
260
|
+
this.paused = false;
|
|
316
261
|
this.aqua.emit("trackChange", player, track, payload);
|
|
317
262
|
}
|
|
318
263
|
|
|
@@ -320,37 +265,30 @@ class Player extends EventEmitter {
|
|
|
320
265
|
if (this.shouldDeleteMessage && this.nowPlayingMessage) {
|
|
321
266
|
this.nowPlayingMessage.delete().catch(console.error).finally(() => this.nowPlayingMessage = null);
|
|
322
267
|
}
|
|
323
|
-
|
|
324
268
|
const reason = payload.reason.replace("_", "").toLowerCase();
|
|
325
269
|
switch (reason) {
|
|
326
270
|
case "loadfailed":
|
|
327
271
|
case "cleanup":
|
|
328
272
|
return player.queue.isEmpty() ? this.aqua.emit("queueEnd", player) : player.play();
|
|
329
|
-
|
|
330
273
|
case "track":
|
|
331
274
|
this.aqua.emit("trackRepeat", player, track, payload);
|
|
332
275
|
player.queue.unshift(this.previous);
|
|
333
276
|
break;
|
|
334
|
-
|
|
335
277
|
case "queue":
|
|
336
278
|
this.aqua.emit("queueRepeat", player, track, payload);
|
|
337
279
|
player.queue.push(this.previous);
|
|
338
280
|
break;
|
|
339
|
-
|
|
340
281
|
default:
|
|
341
282
|
this.aqua.emit("trackEnd", player, track, payload);
|
|
342
283
|
await this.cleanup();
|
|
343
284
|
}
|
|
344
|
-
|
|
345
285
|
if (player.queue.length === 0) {
|
|
346
286
|
this.playing = false;
|
|
347
287
|
this.aqua.emit("queueEnd", player);
|
|
348
288
|
}
|
|
349
|
-
|
|
350
289
|
if (!player.playing) {
|
|
351
290
|
return this.cleanup();
|
|
352
291
|
}
|
|
353
|
-
|
|
354
292
|
return player.play();
|
|
355
293
|
}
|
|
356
294
|
|
|
@@ -409,8 +347,9 @@ class Player extends EventEmitter {
|
|
|
409
347
|
|
|
410
348
|
async cleanup() {
|
|
411
349
|
if (!this.playing && !this.paused && this.queue.isEmpty()) {
|
|
412
|
-
await this.destroy();
|
|
350
|
+
await this.destroy();
|
|
413
351
|
}
|
|
352
|
+
this.data.clear();
|
|
414
353
|
}
|
|
415
354
|
}
|
|
416
355
|
|
|
@@ -14,7 +14,7 @@ class Track {
|
|
|
14
14
|
this.info = data.info;
|
|
15
15
|
this.requester = requester;
|
|
16
16
|
this.nodes = nodes;
|
|
17
|
-
this.track = data.encoded ||
|
|
17
|
+
this.track = data.encoded || null
|
|
18
18
|
this.playlist = data.playlist || null;
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -87,4 +87,5 @@ class Track {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
module.exports = { Track };
|
|
90
|
+
module.exports = { Track };
|
|
91
|
+
|