aqualink 1.4.2 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -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 +44 -103
- package/build/structures/Track.js +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,14 +25,15 @@ 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 || 1.5.1 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
|
+
- 1.5.1 : Fully fixed destroy and the queue handling for delete, also now it deletes from lavalink...
|
|
36
37
|
|
|
37
38
|
# How to install
|
|
38
39
|
|
|
@@ -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,60 @@ 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
|
-
|
|
122
|
-
this.
|
|
123
|
-
this.
|
|
97
|
+
if (!this.connected) return this;
|
|
98
|
+
await this.updatePlayer({ track: { encoded: null } });
|
|
99
|
+
this.playing = false;
|
|
100
|
+
this.position = 0;
|
|
101
|
+
this.send({ guild_id: this.guildId, channel_id: null });
|
|
102
|
+
this.connected = false;
|
|
103
|
+
return this;
|
|
124
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Pauses or resumes the player.
|
|
107
|
+
*
|
|
108
|
+
* @param {boolean} paused - If true, the player will be paused; if false, it will resume.
|
|
109
|
+
* @returns {Promise<Player>} The player instance.
|
|
110
|
+
*/
|
|
125
111
|
|
|
126
|
-
|
|
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
112
|
async pause(paused) {
|
|
135
113
|
this.paused = paused;
|
|
136
114
|
await this.updatePlayer({ paused });
|
|
@@ -138,11 +116,11 @@ class Player extends EventEmitter {
|
|
|
138
116
|
}
|
|
139
117
|
|
|
140
118
|
/**
|
|
141
|
-
* Seeks
|
|
119
|
+
* Seeks to a position in the currently playing track.
|
|
142
120
|
*
|
|
143
|
-
* @param {
|
|
144
|
-
* @returns {Promise<Player>} The player instance.
|
|
121
|
+
* @param {number} position - The position in milliseconds to seek to.
|
|
145
122
|
* @throws {Error} If the position is negative.
|
|
123
|
+
* @returns {Promise<Player>} The player instance.
|
|
146
124
|
*/
|
|
147
125
|
async seek(position) {
|
|
148
126
|
if (position < 0) throw new Error("Seek position cannot be negative.");
|
|
@@ -151,12 +129,6 @@ class Player extends EventEmitter {
|
|
|
151
129
|
return this;
|
|
152
130
|
}
|
|
153
131
|
|
|
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
132
|
async stop() {
|
|
161
133
|
if (!this.playing) return this;
|
|
162
134
|
await this.updatePlayer({ track: { encoded: null } });
|
|
@@ -168,9 +140,9 @@ class Player extends EventEmitter {
|
|
|
168
140
|
/**
|
|
169
141
|
* Sets the volume of the player.
|
|
170
142
|
*
|
|
171
|
-
* @param {
|
|
143
|
+
* @param {number} volume - The volume to set, between 0 and 200.
|
|
144
|
+
* @throws {Error} If the volume is out of range.
|
|
172
145
|
* @returns {Promise<Player>} The player instance.
|
|
173
|
-
* @throws {Error} If the volume is invalid.
|
|
174
146
|
*/
|
|
175
147
|
async setVolume(volume) {
|
|
176
148
|
if (volume < 0 || volume > 200) throw new Error("Volume must be between 0 and 200.");
|
|
@@ -182,9 +154,9 @@ class Player extends EventEmitter {
|
|
|
182
154
|
/**
|
|
183
155
|
* Sets the loop mode of the player.
|
|
184
156
|
*
|
|
185
|
-
* @param {
|
|
157
|
+
* @param {string} mode - The loop mode to set, either "none", "track", or "queue".
|
|
158
|
+
* @throws {Error} If the mode is not one of the above.
|
|
186
159
|
* @returns {Promise<Player>} The player instance.
|
|
187
|
-
* @throws {Error} If the loop mode is invalid.
|
|
188
160
|
*/
|
|
189
161
|
async setLoop(mode) {
|
|
190
162
|
if (!["none", "track", "queue"].includes(mode)) throw new Error("Loop mode must be 'none', 'track', or 'queue'.");
|
|
@@ -194,24 +166,24 @@ class Player extends EventEmitter {
|
|
|
194
166
|
}
|
|
195
167
|
|
|
196
168
|
/**
|
|
197
|
-
* Sets the text channel
|
|
169
|
+
* Sets the text channel for the player.
|
|
198
170
|
*
|
|
199
|
-
* @param {
|
|
171
|
+
* @param {string} channel - The ID of the text channel to set.
|
|
200
172
|
* @returns {Promise<Player>} The player instance.
|
|
201
|
-
* @throws {Error} If the channel is invalid.
|
|
202
173
|
*/
|
|
174
|
+
|
|
203
175
|
async setTextChannel(channel) {
|
|
204
176
|
await this.updatePlayer({ text_channel: channel });
|
|
205
177
|
return this;
|
|
206
178
|
}
|
|
207
179
|
|
|
208
180
|
/**
|
|
209
|
-
* Sets the voice channel
|
|
181
|
+
* Sets the voice channel for the player.
|
|
210
182
|
*
|
|
211
|
-
* @param {
|
|
212
|
-
* @returns {Promise<Player>} The player instance.
|
|
183
|
+
* @param {string} channel - The ID of the voice channel to set.
|
|
213
184
|
* @throws {TypeError} If the channel is not a non-empty string.
|
|
214
|
-
* @throws {ReferenceError} If the player is already connected to the
|
|
185
|
+
* @throws {ReferenceError} If the player is already connected to the channel.
|
|
186
|
+
* @returns {Promise<Player>} The player instance.
|
|
215
187
|
*/
|
|
216
188
|
async setVoiceChannel(channel) {
|
|
217
189
|
if (typeof channel !== "string") throw new TypeError("Channel must be a non-empty string.");
|
|
@@ -223,12 +195,6 @@ class Player extends EventEmitter {
|
|
|
223
195
|
return this;
|
|
224
196
|
}
|
|
225
197
|
|
|
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
198
|
async disconnect() {
|
|
233
199
|
await this.updatePlayer({ track: { encoded: null } });
|
|
234
200
|
this.connected = false;
|
|
@@ -236,11 +202,6 @@ class Player extends EventEmitter {
|
|
|
236
202
|
this.aqua.emit("debug", this.guildId, "Player disconnected from voice channel.");
|
|
237
203
|
}
|
|
238
204
|
|
|
239
|
-
/**
|
|
240
|
-
* Shuffles the queue using the Fisher-Yates shuffle algorithm.
|
|
241
|
-
*
|
|
242
|
-
* @returns {Promise<Player>} The player instance.
|
|
243
|
-
*/
|
|
244
205
|
async shuffle() {
|
|
245
206
|
for (let i = this.queue.length - 1; i > 0; i--) {
|
|
246
207
|
const j = Math.floor(Math.random() * (i + 1));
|
|
@@ -249,31 +210,16 @@ class Player extends EventEmitter {
|
|
|
249
210
|
return this;
|
|
250
211
|
}
|
|
251
212
|
|
|
252
|
-
/**
|
|
253
|
-
* Retrieves the current queue of tracks.
|
|
254
|
-
*
|
|
255
|
-
* @returns {Promise<Array<Track>>} The current queue of tracks.
|
|
256
|
-
*/
|
|
257
213
|
async getQueue() {
|
|
258
214
|
return this.queue;
|
|
259
215
|
}
|
|
260
216
|
|
|
261
|
-
/**
|
|
262
|
-
* Replays the current track from the start.
|
|
263
|
-
*
|
|
264
|
-
* @returns {Promise<Player>} The player instance.
|
|
265
|
-
*/
|
|
266
217
|
async replay() {
|
|
267
218
|
return this.seek(0);
|
|
268
219
|
}
|
|
269
220
|
|
|
270
|
-
/**
|
|
271
|
-
* Skips the current track and plays the next one in the queue.
|
|
272
|
-
*
|
|
273
|
-
* @returns {Promise<Player>} The player instance.
|
|
274
|
-
*/
|
|
275
221
|
async skip() {
|
|
276
|
-
await this.stop()
|
|
222
|
+
await this.stop();
|
|
277
223
|
if (this.playing) return this.play();
|
|
278
224
|
}
|
|
279
225
|
|
|
@@ -304,6 +250,7 @@ class Player extends EventEmitter {
|
|
|
304
250
|
this.handleUnknownEvent(player, track, payload);
|
|
305
251
|
}
|
|
306
252
|
}
|
|
253
|
+
|
|
307
254
|
trackStart(player, track, payload) {
|
|
308
255
|
this.playing = true;
|
|
309
256
|
this.paused = false;
|
|
@@ -312,7 +259,7 @@ class Player extends EventEmitter {
|
|
|
312
259
|
|
|
313
260
|
trackChange(player, track, payload) {
|
|
314
261
|
this.playing = true;
|
|
315
|
-
this.paused = false
|
|
262
|
+
this.paused = false;
|
|
316
263
|
this.aqua.emit("trackChange", player, track, payload);
|
|
317
264
|
}
|
|
318
265
|
|
|
@@ -320,37 +267,30 @@ class Player extends EventEmitter {
|
|
|
320
267
|
if (this.shouldDeleteMessage && this.nowPlayingMessage) {
|
|
321
268
|
this.nowPlayingMessage.delete().catch(console.error).finally(() => this.nowPlayingMessage = null);
|
|
322
269
|
}
|
|
323
|
-
|
|
324
270
|
const reason = payload.reason.replace("_", "").toLowerCase();
|
|
325
271
|
switch (reason) {
|
|
326
272
|
case "loadfailed":
|
|
327
273
|
case "cleanup":
|
|
328
274
|
return player.queue.isEmpty() ? this.aqua.emit("queueEnd", player) : player.play();
|
|
329
|
-
|
|
330
275
|
case "track":
|
|
331
276
|
this.aqua.emit("trackRepeat", player, track, payload);
|
|
332
277
|
player.queue.unshift(this.previous);
|
|
333
278
|
break;
|
|
334
|
-
|
|
335
279
|
case "queue":
|
|
336
280
|
this.aqua.emit("queueRepeat", player, track, payload);
|
|
337
281
|
player.queue.push(this.previous);
|
|
338
282
|
break;
|
|
339
|
-
|
|
340
283
|
default:
|
|
341
284
|
this.aqua.emit("trackEnd", player, track, payload);
|
|
342
285
|
await this.cleanup();
|
|
343
286
|
}
|
|
344
|
-
|
|
345
287
|
if (player.queue.length === 0) {
|
|
346
288
|
this.playing = false;
|
|
347
289
|
this.aqua.emit("queueEnd", player);
|
|
348
290
|
}
|
|
349
|
-
|
|
350
291
|
if (!player.playing) {
|
|
351
292
|
return this.cleanup();
|
|
352
293
|
}
|
|
353
|
-
|
|
354
294
|
return player.play();
|
|
355
295
|
}
|
|
356
296
|
|
|
@@ -409,8 +349,9 @@ class Player extends EventEmitter {
|
|
|
409
349
|
|
|
410
350
|
async cleanup() {
|
|
411
351
|
if (!this.playing && !this.paused && this.queue.isEmpty()) {
|
|
412
|
-
await this.destroy();
|
|
352
|
+
await this.destroy();
|
|
413
353
|
}
|
|
354
|
+
this.data.clear();
|
|
414
355
|
}
|
|
415
356
|
}
|
|
416
357
|
|
|
@@ -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
|
+
|