aqualink 1.0.2 → 1.0.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/build/structures/Aqua.js +110 -29
- package/build/structures/Connection.js +6 -3
- package/build/structures/Player.js +56 -50
- package/build/structures/Queue.js +13 -3
- package/build/structures/Rest.js +42 -9
- package/build/structures/Track.js +16 -38
- package/package.json +1 -1
package/build/structures/Aqua.js
CHANGED
|
@@ -5,9 +5,17 @@ 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
|
+
*/
|
|
8
17
|
constructor(client, nodes, options) {
|
|
9
|
-
|
|
10
|
-
// Input validation
|
|
18
|
+
super();
|
|
11
19
|
if (!client) throw new Error("Client is required to initialize Aqua");
|
|
12
20
|
if (!Array.isArray(nodes) || nodes.length === 0) throw new Error(`Nodes must be a non-empty Array (Received ${typeof nodes})`);
|
|
13
21
|
if (typeof options.send !== "function") throw new Error("Send function is required to initialize Aqua");
|
|
@@ -30,22 +38,36 @@ class Aqua extends EventEmitter {
|
|
|
30
38
|
this.options = options;
|
|
31
39
|
this.send = options.send || null;
|
|
32
40
|
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Gets the least used nodes based on call count.
|
|
44
|
+
* @returns {Array<Node>} Array of least used nodes.
|
|
45
|
+
*/
|
|
33
46
|
get leastUsedNodes() {
|
|
34
47
|
return [...this.nodeMap.values()]
|
|
35
48
|
.filter(node => node.connected)
|
|
36
49
|
.sort((a, b) => a.rest.calls - b.rest.calls);
|
|
37
50
|
}
|
|
38
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Initializes Aqua with the provided client ID.
|
|
54
|
+
* @param {string} clientId - The client ID.
|
|
55
|
+
* @returns {Aqua} The Aqua instance.
|
|
56
|
+
*/
|
|
39
57
|
init(clientId) {
|
|
40
58
|
if (this.initiated) return this;
|
|
41
|
-
|
|
42
59
|
this.clientId = clientId;
|
|
43
60
|
this.nodes.forEach(nodeConfig => this.createNode(nodeConfig));
|
|
44
61
|
this.initiated = true;
|
|
45
|
-
|
|
46
62
|
this.plugins.forEach(plugin => plugin.load(this));
|
|
63
|
+
return this;
|
|
47
64
|
}
|
|
48
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Creates a new node with the specified options.
|
|
68
|
+
* @param {Object} options - The configuration for the node.
|
|
69
|
+
* @returns {Node} The created node instance.
|
|
70
|
+
*/
|
|
49
71
|
createNode(options) {
|
|
50
72
|
const node = new Node(this, options, this.options);
|
|
51
73
|
this.nodeMap.set(options.name || options.host, node);
|
|
@@ -54,29 +76,33 @@ class Aqua extends EventEmitter {
|
|
|
54
76
|
return node;
|
|
55
77
|
}
|
|
56
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Destroys a node identified by the given identifier.
|
|
81
|
+
* @param {string} identifier - The identifier of the node to destroy.
|
|
82
|
+
*/
|
|
57
83
|
destroyNode(identifier) {
|
|
58
84
|
const node = this.nodeMap.get(identifier);
|
|
59
85
|
if (!node) return;
|
|
60
|
-
|
|
61
86
|
node.disconnect();
|
|
62
87
|
this.nodeMap.delete(identifier);
|
|
63
88
|
this.emit("nodeDestroy", node);
|
|
64
89
|
}
|
|
65
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Updates the voice state based on the received packet.
|
|
93
|
+
* @param {Object} packet - The packet containing voice state information.
|
|
94
|
+
*/
|
|
66
95
|
updateVoiceState(packet) {
|
|
67
|
-
if (!["VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE"].includes(packet.t)) return;
|
|
68
|
-
|
|
69
96
|
const player = this.players.get(packet.d.guild_id);
|
|
70
97
|
if (!player) return;
|
|
71
|
-
|
|
72
|
-
if (packet.t === "
|
|
73
|
-
player.connection.setServerUpdate(packet.d);
|
|
74
|
-
} else if (packet.t === "VOICE_STATE_UPDATE") {
|
|
75
|
-
if (packet.d.user_id !== this.clientId) return;
|
|
76
|
-
player.connection.setStateUpdate(packet.d);
|
|
77
|
-
}
|
|
98
|
+
if (packet.t === "VOICE_SERVER_UPDATE") player.connection.setServerUpdate(packet.d);
|
|
99
|
+
else if (packet.t === "VOICE_STATE_UPDATE" && packet.d.user_id === this.clientId) player.connection.setStateUpdate(packet.d);
|
|
78
100
|
}
|
|
79
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Fetches nodes by the specified region.
|
|
103
|
+
* @param {string} region - The region to filter nodes by.
|
|
104
|
+
* @returns {Array<Node>} Array of nodes in the specified region.
|
|
105
|
+
*/
|
|
80
106
|
fetchRegion(region) {
|
|
81
107
|
const nodesByRegion = [...this.nodeMap.values()]
|
|
82
108
|
.filter(node => node.connected && node.regions?.includes(region?.toLowerCase()))
|
|
@@ -85,38 +111,54 @@ class Aqua extends EventEmitter {
|
|
|
85
111
|
const bLoad = b.stats.cpu ? (b.stats.cpu.systemLoad / b.stats.cpu.cores) * 100 : 0;
|
|
86
112
|
return aLoad - bLoad;
|
|
87
113
|
});
|
|
88
|
-
|
|
89
114
|
return nodesByRegion;
|
|
90
115
|
}
|
|
91
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Creates a connection for a player.
|
|
119
|
+
* @param {Object} options - Connection options.
|
|
120
|
+
* @param {string} options.guildId - The ID of the guild.
|
|
121
|
+
* @param {string} [options.region] - The region to connect to.
|
|
122
|
+
* @returns {Player} The created player instance.
|
|
123
|
+
*/
|
|
92
124
|
createConnection(options) {
|
|
93
125
|
if (!this.initiated) throw new Error("BRO! Get aqua on before !!!");
|
|
94
|
-
if (this.leastUsedNodes.length
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
: this.leastUsedNodes[0];
|
|
126
|
+
if (!this.leastUsedNodes.length) throw new Error("No nodes are available");
|
|
127
|
+
|
|
128
|
+
const node = (options.region ? this.fetchRegion(options.region) : this.leastUsedNodes)[0];
|
|
98
129
|
if (!node) throw new Error("No nodes are available");
|
|
130
|
+
|
|
99
131
|
return this.createPlayer(node, options);
|
|
100
132
|
}
|
|
101
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Creates a player using the specified node.
|
|
135
|
+
* @param {Node} node - The node to create the player with.
|
|
136
|
+
* @param {Object} options - The player options.
|
|
137
|
+
* @returns {Player} The created player instance.
|
|
138
|
+
*/
|
|
102
139
|
createPlayer(node, options) {
|
|
103
140
|
const player = new Player(this, node, options);
|
|
104
141
|
this.players.set(options.guildId, player);
|
|
105
142
|
player.connect(options);
|
|
106
|
-
|
|
107
143
|
this.emit("playerCreate", player);
|
|
108
144
|
return player;
|
|
109
145
|
}
|
|
110
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Destroys the player associated with the given guild ID.
|
|
148
|
+
* @param {string} guildId - The ID of the guild.
|
|
149
|
+
*/
|
|
111
150
|
destroyPlayer(guildId) {
|
|
112
151
|
const player = this.players.get(guildId);
|
|
113
152
|
if (!player) return;
|
|
114
|
-
|
|
115
153
|
player.destroy();
|
|
116
154
|
this.players.delete(guildId);
|
|
117
155
|
this.emit("playerDestroy", player);
|
|
118
156
|
}
|
|
119
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Removes the connection for the specified guild ID.
|
|
160
|
+
* @param {string} guildId - The ID of the guild.
|
|
161
|
+
*/
|
|
120
162
|
removeConnection(guildId) {
|
|
121
163
|
const player = this.players.get(guildId);
|
|
122
164
|
if (player) {
|
|
@@ -125,16 +167,23 @@ class Aqua extends EventEmitter {
|
|
|
125
167
|
}
|
|
126
168
|
}
|
|
127
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Resolves a query to tracks using the available nodes.
|
|
172
|
+
* @param {Object} options - The options for resolving tracks.
|
|
173
|
+
* @param {string} options.query - The query string to resolve.
|
|
174
|
+
* @param {string} [options.source] - The source of the query.
|
|
175
|
+
* @param {Object} [options.requester] - The requester of the query.
|
|
176
|
+
* @param {string|Node} [options.nodes] - Specific nodes to use for the request.
|
|
177
|
+
* @returns {Promise<Object>} The resolved tracks and related information.
|
|
178
|
+
*/
|
|
128
179
|
async resolve({ query, source, requester, nodes }) {
|
|
129
180
|
if (!this.initiated) throw new Error("Aqua must be initialized before resolving");
|
|
130
|
-
|
|
131
181
|
if (nodes && (typeof nodes !== "string" && !(nodes instanceof Node))) {
|
|
132
182
|
throw new Error(`'nodes' must be a string or Node instance, but received: ${typeof nodes}`);
|
|
133
183
|
}
|
|
134
184
|
|
|
135
185
|
const searchSources = source || this.defaultSearchPlatform;
|
|
136
186
|
const requestNode = (typeof nodes === 'string' ? this.nodeMap.get(nodes) : nodes) || this.leastUsedNodes[0];
|
|
137
|
-
|
|
138
187
|
if (!requestNode) throw new Error("No nodes are available.");
|
|
139
188
|
|
|
140
189
|
const formattedQuery = /^https?:\/\//.test(query) ? query : `${searchSources}:${query}`;
|
|
@@ -149,6 +198,12 @@ class Aqua extends EventEmitter {
|
|
|
149
198
|
return this.constructResponse();
|
|
150
199
|
}
|
|
151
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Handles cases where no matches were found for a query.
|
|
203
|
+
* @param {Object} rest - The REST client for making requests.
|
|
204
|
+
* @param {string} query - The original query string.
|
|
205
|
+
* @returns {Promise<Object>} The response object from the request.
|
|
206
|
+
*/
|
|
152
207
|
async handleNoMatches(rest, query) {
|
|
153
208
|
let response = await rest.makeRequest("GET", `/v4/loadtracks?identifier=https://open.spotify.com/track/${query}`);
|
|
154
209
|
if (["empty", "NO_MATCHES"].includes(response.loadType)) {
|
|
@@ -157,6 +212,12 @@ class Aqua extends EventEmitter {
|
|
|
157
212
|
return response;
|
|
158
213
|
}
|
|
159
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Loads tracks from the resolved response.
|
|
217
|
+
* @param {Object} response - The response from the track resolution.
|
|
218
|
+
* @param {Object} requester - The requester of the tracks.
|
|
219
|
+
* @param {Node} requestNode - The node that handled the request.
|
|
220
|
+
*/
|
|
160
221
|
loadTracks(response, requester, requestNode) {
|
|
161
222
|
this.tracks = [];
|
|
162
223
|
if (response.loadType === "track") {
|
|
@@ -169,11 +230,14 @@ class Aqua extends EventEmitter {
|
|
|
169
230
|
} else if (response.loadType === "search") {
|
|
170
231
|
this.tracks = response.data.map(track => new Track(track, requester, requestNode));
|
|
171
232
|
}
|
|
172
|
-
|
|
173
233
|
this.loadType = response.loadType;
|
|
174
234
|
this.pluginInfo = response.pluginInfo || {};
|
|
175
235
|
}
|
|
176
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Constructs the response object for the resolved tracks.
|
|
239
|
+
* @returns {Object} The constructed response.
|
|
240
|
+
*/
|
|
177
241
|
constructResponse() {
|
|
178
242
|
return {
|
|
179
243
|
loadType: this.loadType,
|
|
@@ -184,13 +248,30 @@ class Aqua extends EventEmitter {
|
|
|
184
248
|
};
|
|
185
249
|
}
|
|
186
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Gets the player associated with the specified guild ID.
|
|
253
|
+
* @param {string} guildId - The ID of the guild.
|
|
254
|
+
* @returns {Player} The player instance.
|
|
255
|
+
* @throws {Error} If the player is not found.
|
|
256
|
+
*/
|
|
187
257
|
get(guildId) {
|
|
188
258
|
const player = this.players.get(guildId);
|
|
189
259
|
if (!player) throw new Error(`Player not found for guild ID: ${guildId}`);
|
|
190
260
|
return player;
|
|
191
261
|
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Cleans up idle players and nodes to free up resources.
|
|
265
|
+
*/
|
|
266
|
+
cleanupIdle() {
|
|
267
|
+
for (const [guildId, player] of this.players) {
|
|
268
|
+
if (!player.playing && !player.paused && player.queue.isEmpty()) {
|
|
269
|
+
player.destroy();
|
|
270
|
+
this.players.delete(guildId);
|
|
271
|
+
this.emit("playerDestroy", player);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
192
275
|
}
|
|
193
276
|
|
|
194
277
|
module.exports = { Aqua };
|
|
195
|
-
|
|
196
|
-
|
|
@@ -22,7 +22,7 @@ class Connection {
|
|
|
22
22
|
if (!endpoint) {
|
|
23
23
|
throw new Error("Missing 'endpoint' property in VOICE_SERVER_UPDATE packet/payload. Please wait or disconnect the bot from the voice channel and try again.");
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
const previousVoiceRegion = this.region;
|
|
27
27
|
this.voice.endpoint = endpoint;
|
|
28
28
|
this.voice.token = token;
|
|
@@ -45,7 +45,10 @@ class Connection {
|
|
|
45
45
|
* @param {boolean} data.self_mute The self-muted status of the player.
|
|
46
46
|
*/
|
|
47
47
|
setStateUpdate({ session_id, channel_id, self_deaf, self_mute }) {
|
|
48
|
-
if (channel_id == null) {
|
|
48
|
+
if (channel_id == null || session_id == null) {
|
|
49
|
+
this.player.aqua.emit("playerLeave", this.player.voiceChannel);
|
|
50
|
+
this.player.voiceChannel = null;
|
|
51
|
+
this.voiceChannel = null;
|
|
49
52
|
this.player.destroy();
|
|
50
53
|
this.player.aqua.emit("playerDestroy", this.player);
|
|
51
54
|
return;
|
|
@@ -59,7 +62,7 @@ class Connection {
|
|
|
59
62
|
|
|
60
63
|
this.selfDeaf = self_deaf;
|
|
61
64
|
this.selfMute = self_mute;
|
|
62
|
-
this.voice.sessionId = session_id
|
|
65
|
+
this.voice.sessionId = session_id;
|
|
63
66
|
|
|
64
67
|
this.updatePlayerVoiceData();
|
|
65
68
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
const { EventEmitter } = require("events");
|
|
2
2
|
const { Connection } = require("./Connection");
|
|
3
3
|
const { Queue } = require("./Queue");
|
|
4
|
-
const {Filters} = require("./Filters");
|
|
4
|
+
const { Filters } = require("./Filters");
|
|
5
5
|
|
|
6
6
|
class Player extends EventEmitter {
|
|
7
7
|
/**
|
|
8
8
|
* @param {Object} aqua - The Aqua instance.
|
|
9
|
-
* @param {Object}
|
|
9
|
+
* @param {Object} nodes - The node instances.
|
|
10
10
|
* @param {Object} options - Configuration options for the player.
|
|
11
11
|
* @param {string} options.guildId - The ID of the guild.
|
|
12
12
|
* @param {string} options.textChannel - The ID of the text channel.
|
|
@@ -40,24 +40,33 @@ class Player extends EventEmitter {
|
|
|
40
40
|
this.timestamp = 0;
|
|
41
41
|
this.ping = 0;
|
|
42
42
|
this.isAutoplay = false;
|
|
43
|
+
|
|
43
44
|
this.setupEventListeners();
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Sets up event listeners for player events.
|
|
49
|
+
*/
|
|
46
50
|
setupEventListeners() {
|
|
47
|
-
this.on("playerUpdate", this.onPlayerUpdate);
|
|
51
|
+
this.on("playerUpdate", this.onPlayerUpdate.bind(this));
|
|
48
52
|
this.on("event", this.handleEvent.bind(this));
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Handles player update events.
|
|
57
|
+
* @param {Object} packet - The packet containing the player update data.
|
|
58
|
+
*/
|
|
51
59
|
onPlayerUpdate(packet) {
|
|
52
60
|
const { state } = packet;
|
|
53
61
|
this.connected = state.connected;
|
|
54
62
|
this.position = state.position;
|
|
55
63
|
this.ping = state.ping;
|
|
56
64
|
this.timestamp = state.time;
|
|
65
|
+
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
/**
|
|
60
|
-
*
|
|
69
|
+
* Gets the previous track.
|
|
61
70
|
* @returns {Object|null} The previous track or null if none exists.
|
|
62
71
|
*/
|
|
63
72
|
get previous() {
|
|
@@ -65,7 +74,7 @@ class Player extends EventEmitter {
|
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
/**
|
|
68
|
-
*
|
|
77
|
+
* Adds a track to the previous tracks list.
|
|
69
78
|
* @param {Object} track - The track object to add.
|
|
70
79
|
*/
|
|
71
80
|
addToPreviousTrack(track) {
|
|
@@ -73,13 +82,12 @@ class Player extends EventEmitter {
|
|
|
73
82
|
}
|
|
74
83
|
|
|
75
84
|
/**
|
|
76
|
-
*
|
|
85
|
+
* Plays the next track in the queue.
|
|
77
86
|
* @returns {Promise<Player>} The player instance.
|
|
78
87
|
* @throws {Error} If the player is not connected.
|
|
79
88
|
*/
|
|
80
89
|
async play() {
|
|
81
90
|
if (!this.connected) throw new Error("Player connection is not established. Please connect first.");
|
|
82
|
-
|
|
83
91
|
this.current = this.queue.shift();
|
|
84
92
|
if (!this.current) return this;
|
|
85
93
|
|
|
@@ -90,19 +98,12 @@ class Player extends EventEmitter {
|
|
|
90
98
|
this.playing = true;
|
|
91
99
|
this.position = 0;
|
|
92
100
|
|
|
93
|
-
// Log info for debugging
|
|
94
101
|
this.aqua.emit("debug", this.guildId, `Playing track: ${this.current.track}`);
|
|
95
|
-
|
|
96
|
-
await this.nodes.rest.updatePlayer({
|
|
97
|
-
guildId: this.guildId,
|
|
98
|
-
data: {
|
|
99
|
-
track: { encoded: this.current.track },
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
+
await this.updatePlayer({ track: { encoded: this.current.track } });
|
|
102
103
|
return this;
|
|
103
104
|
}
|
|
104
105
|
/**
|
|
105
|
-
*
|
|
106
|
+
* Connects the player to the voice channel.
|
|
106
107
|
* @param {Object} [options=this] - Connection options.
|
|
107
108
|
* @param {string} options.guildId - The ID of the guild.
|
|
108
109
|
* @param {string} options.voiceChannel - The ID of the voice channel.
|
|
@@ -121,18 +122,20 @@ class Player extends EventEmitter {
|
|
|
121
122
|
this.connected = true;
|
|
122
123
|
this.aqua.emit("debug", this.guildId, `Player has connected to voice channel: ${voiceChannel}.`);
|
|
123
124
|
}
|
|
125
|
+
|
|
124
126
|
/**
|
|
125
|
-
*
|
|
127
|
+
* Disconnects the player from the voice channel and cleans up resources.
|
|
126
128
|
* @returns {Promise<Player>} The player instance.
|
|
127
129
|
*/
|
|
128
130
|
async destroy() {
|
|
129
131
|
await this.updatePlayer({ track: null });
|
|
130
132
|
this.connected = false;
|
|
133
|
+
this.clearData(); // Clear data when destroyed
|
|
131
134
|
this.aqua.emit("debug", this.guildId, "Player has disconnected from voice channel.");
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
/**
|
|
135
|
-
*
|
|
138
|
+
* Pauses or resumes the player.
|
|
136
139
|
* @param {boolean} paused - Whether to pause the player.
|
|
137
140
|
* @returns {Promise<Player>} The player instance.
|
|
138
141
|
*/
|
|
@@ -143,7 +146,7 @@ class Player extends EventEmitter {
|
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
/**
|
|
146
|
-
*
|
|
149
|
+
* Seeks to a specific position in the current track.
|
|
147
150
|
* @param {number} position - The position in milliseconds to seek to.
|
|
148
151
|
* @returns {Promise<Player>} The player instance.
|
|
149
152
|
*/
|
|
@@ -154,7 +157,7 @@ class Player extends EventEmitter {
|
|
|
154
157
|
}
|
|
155
158
|
|
|
156
159
|
/**
|
|
157
|
-
*
|
|
160
|
+
* Stops playback and clears the current track.
|
|
158
161
|
* @returns {Promise<Player>} The player instance.
|
|
159
162
|
*/
|
|
160
163
|
async stop() {
|
|
@@ -163,7 +166,7 @@ class Player extends EventEmitter {
|
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
/**
|
|
166
|
-
*
|
|
169
|
+
* Sets the volume of the player.
|
|
167
170
|
* @param {number} volume - The volume level (0-200).
|
|
168
171
|
* @returns {Promise<Player>} The player instance.
|
|
169
172
|
* @throws {Error} If the volume is out of bounds.
|
|
@@ -176,7 +179,7 @@ class Player extends EventEmitter {
|
|
|
176
179
|
}
|
|
177
180
|
|
|
178
181
|
/**
|
|
179
|
-
*
|
|
182
|
+
* Sets the loop mode for the player.
|
|
180
183
|
* @param {string} mode - The loop mode ('none', 'track', 'queue').
|
|
181
184
|
* @returns {Promise<Player>} The player instance.
|
|
182
185
|
* @throws {Error} If the loop mode is invalid.
|
|
@@ -189,7 +192,7 @@ class Player extends EventEmitter {
|
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
/**
|
|
192
|
-
*
|
|
195
|
+
* Sends data to the player.
|
|
193
196
|
* @param {Object} data - The data to send.
|
|
194
197
|
* @returns {Promise<Player>} The player instance.
|
|
195
198
|
*/
|
|
@@ -199,7 +202,7 @@ class Player extends EventEmitter {
|
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
/**
|
|
202
|
-
*
|
|
205
|
+
* Sets the text channel for the player.
|
|
203
206
|
* @param {string} channel - The ID of the text channel.
|
|
204
207
|
* @returns {Promise<Player>} The player instance.
|
|
205
208
|
*/
|
|
@@ -209,7 +212,7 @@ class Player extends EventEmitter {
|
|
|
209
212
|
}
|
|
210
213
|
|
|
211
214
|
/**
|
|
212
|
-
*
|
|
215
|
+
* Sets the voice channel for the player.
|
|
213
216
|
* @param {string} channel - The ID of the voice channel.
|
|
214
217
|
* @returns {Promise<Player>} The player instance.
|
|
215
218
|
* @throws {TypeError} If the channel is not a string.
|
|
@@ -221,18 +224,12 @@ class Player extends EventEmitter {
|
|
|
221
224
|
throw new ReferenceError(`Player is already connected to ${channel}.`);
|
|
222
225
|
}
|
|
223
226
|
this.voiceChannel = channel;
|
|
224
|
-
await this.connect({
|
|
225
|
-
deaf: this.deaf,
|
|
226
|
-
guildId: this.guildId,
|
|
227
|
-
voiceChannel: this.voiceChannel,
|
|
228
|
-
textChannel: this.textChannel,
|
|
229
|
-
mute: this.mute,
|
|
230
|
-
});
|
|
227
|
+
await this.connect({ deaf: this.deaf, guildId: this.guildId, voiceChannel: this.voiceChannel, mute: this.mute });
|
|
231
228
|
return this;
|
|
232
229
|
}
|
|
233
230
|
|
|
234
231
|
/**
|
|
235
|
-
*
|
|
232
|
+
* Disconnects the player from the voice channel.
|
|
236
233
|
* @returns {Promise<Player>} The player instance.
|
|
237
234
|
*/
|
|
238
235
|
async disconnect() {
|
|
@@ -242,12 +239,13 @@ class Player extends EventEmitter {
|
|
|
242
239
|
}
|
|
243
240
|
|
|
244
241
|
/**
|
|
245
|
-
*
|
|
242
|
+
* Handles events from the player.
|
|
246
243
|
* @param {Object} payload - The event payload.
|
|
247
244
|
*/
|
|
248
245
|
async handleEvent(payload) {
|
|
249
246
|
const player = this.aqua.players.get(payload.guildId);
|
|
250
247
|
if (!player) return;
|
|
248
|
+
|
|
251
249
|
switch (payload.type) {
|
|
252
250
|
case "TrackStartEvent":
|
|
253
251
|
this.trackStart(player, payload);
|
|
@@ -271,7 +269,7 @@ class Player extends EventEmitter {
|
|
|
271
269
|
}
|
|
272
270
|
|
|
273
271
|
/**
|
|
274
|
-
*
|
|
272
|
+
* Handles track start events.
|
|
275
273
|
* @param {Object} player - The player instance.
|
|
276
274
|
* @param {Object} payload - The event payload.
|
|
277
275
|
*/
|
|
@@ -282,7 +280,7 @@ class Player extends EventEmitter {
|
|
|
282
280
|
}
|
|
283
281
|
|
|
284
282
|
/**
|
|
285
|
-
*
|
|
283
|
+
* Handles track end events.
|
|
286
284
|
* @param {Object} player - The player instance.
|
|
287
285
|
* @param {Object} payload - The event payload.
|
|
288
286
|
*/
|
|
@@ -315,7 +313,7 @@ class Player extends EventEmitter {
|
|
|
315
313
|
}
|
|
316
314
|
|
|
317
315
|
/**
|
|
318
|
-
*
|
|
316
|
+
* Handles track error events.
|
|
319
317
|
* @param {Object} player - The player instance.
|
|
320
318
|
* @param {Object} payload - The event payload.
|
|
321
319
|
*/
|
|
@@ -325,7 +323,7 @@ class Player extends EventEmitter {
|
|
|
325
323
|
}
|
|
326
324
|
|
|
327
325
|
/**
|
|
328
|
-
*
|
|
326
|
+
* Handles track stuck events.
|
|
329
327
|
* @param {Object} player - The player instance.
|
|
330
328
|
* @param {Object} payload - The event payload.
|
|
331
329
|
*/
|
|
@@ -335,7 +333,7 @@ class Player extends EventEmitter {
|
|
|
335
333
|
}
|
|
336
334
|
|
|
337
335
|
/**
|
|
338
|
-
*
|
|
336
|
+
* Handles socket closed events.
|
|
339
337
|
* @param {Object} player - The player instance.
|
|
340
338
|
* @param {Object} payload - The event payload.
|
|
341
339
|
*/
|
|
@@ -354,7 +352,7 @@ class Player extends EventEmitter {
|
|
|
354
352
|
}
|
|
355
353
|
|
|
356
354
|
/**
|
|
357
|
-
*
|
|
355
|
+
* Sends data to the Aqua instance.
|
|
358
356
|
* @param {Object} data - The data to send.
|
|
359
357
|
*/
|
|
360
358
|
send(data) {
|
|
@@ -362,34 +360,34 @@ class Player extends EventEmitter {
|
|
|
362
360
|
}
|
|
363
361
|
|
|
364
362
|
/**
|
|
365
|
-
*
|
|
363
|
+
* Sets a custom value in the player's data.
|
|
366
364
|
* @param {string} key - The key of the data.
|
|
367
365
|
* @param {any} value - The value to set.
|
|
368
366
|
*/
|
|
369
367
|
set(key, value) {
|
|
370
|
-
this.data
|
|
368
|
+
this.data.set(key, value); // Use WeakMap to set data
|
|
371
369
|
}
|
|
372
370
|
|
|
373
371
|
/**
|
|
374
|
-
*
|
|
372
|
+
* Gets a custom value from the player's data.
|
|
375
373
|
* @param {string} key - The key of the data.
|
|
376
374
|
* @returns {any} The value associated with the key.
|
|
377
375
|
*/
|
|
378
376
|
get(key) {
|
|
379
|
-
return this.data
|
|
377
|
+
return this.data.get(key); // Use WeakMap to get data
|
|
380
378
|
}
|
|
381
379
|
|
|
382
380
|
/**
|
|
383
|
-
*
|
|
381
|
+
* Clears all custom data set on the player.
|
|
384
382
|
* @returns {Player} The player instance.
|
|
385
383
|
*/
|
|
386
384
|
clearData() {
|
|
387
|
-
this.data = {}
|
|
385
|
+
this.data = {} // Clear all custom data efficiently
|
|
388
386
|
return this;
|
|
389
387
|
}
|
|
390
388
|
|
|
391
389
|
/**
|
|
392
|
-
*
|
|
390
|
+
* Updates the player with new data.
|
|
393
391
|
* @param {Object} data - The data to update the player with.
|
|
394
392
|
* @returns {Promise<void>}
|
|
395
393
|
*/
|
|
@@ -401,15 +399,23 @@ class Player extends EventEmitter {
|
|
|
401
399
|
}
|
|
402
400
|
|
|
403
401
|
/**
|
|
404
|
-
*
|
|
402
|
+
* Handles unknown events from the node.
|
|
405
403
|
* @param {Object} payload - The event payload.
|
|
406
404
|
*/
|
|
407
405
|
handleUnknownEvent(payload) {
|
|
408
406
|
const error = new Error(`Node encountered an unknown event: '${payload.type}'`);
|
|
409
407
|
this.aqua.emit("nodeError", this, error);
|
|
410
408
|
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Cleans up the player when idle.
|
|
412
|
+
* @returns {Promise<void>}
|
|
413
|
+
*/
|
|
414
|
+
async cleanup() {
|
|
415
|
+
if (!this.playing && !this.paused && this.queue.isEmpty()) {
|
|
416
|
+
await this.destroy();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
411
419
|
}
|
|
412
420
|
|
|
413
421
|
module.exports = { Player };
|
|
414
|
-
|
|
415
|
-
|
|
@@ -26,9 +26,8 @@ class Queue extends Array {
|
|
|
26
26
|
* @param {*} track - The track to add.
|
|
27
27
|
*/
|
|
28
28
|
add(track) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
29
|
+
this.push(track);
|
|
30
|
+
return this;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
/**
|
|
@@ -79,6 +78,15 @@ class Queue extends Array {
|
|
|
79
78
|
return this.shift(); // Removes and returns the first element
|
|
80
79
|
}
|
|
81
80
|
|
|
81
|
+
// Check if the queue is empty
|
|
82
|
+
/**
|
|
83
|
+
* Check if the queue is empty.
|
|
84
|
+
* @returns {boolean} Whether the queue is empty.
|
|
85
|
+
*/
|
|
86
|
+
isEmpty() {
|
|
87
|
+
return this.length === 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
82
90
|
/**
|
|
83
91
|
* Add multiple tracks to the queue.
|
|
84
92
|
* @param {Array} tracks - The tracks to add.
|
|
@@ -88,6 +96,8 @@ class Queue extends Array {
|
|
|
88
96
|
this.push(...tracks);
|
|
89
97
|
}
|
|
90
98
|
}
|
|
99
|
+
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
module.exports = { Queue };
|
|
103
|
+
|
package/build/structures/Rest.js
CHANGED
|
@@ -9,6 +9,10 @@ class Rest {
|
|
|
9
9
|
this.password = options.password;
|
|
10
10
|
this.version = options.restVersion;
|
|
11
11
|
this.calls = 0;
|
|
12
|
+
this.queue = [];
|
|
13
|
+
this.maxQueueSize = options.maxQueueSize || 100;
|
|
14
|
+
this.maxConcurrentRequests = options.maxConcurrentRequests || 5;
|
|
15
|
+
this.activeRequests = 0;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
setSessionId(sessionId) {
|
|
@@ -20,24 +24,20 @@ class Rest {
|
|
|
20
24
|
"Content-Type": "application/json",
|
|
21
25
|
Authorization: this.password,
|
|
22
26
|
};
|
|
23
|
-
|
|
24
27
|
const requestOptions = {
|
|
25
28
|
method,
|
|
26
29
|
headers,
|
|
27
30
|
body: body ? JSON.stringify(body) : null,
|
|
28
31
|
};
|
|
29
|
-
|
|
30
32
|
try {
|
|
31
33
|
const response = await undiciFetch(`${this.url}${endpoint}`, requestOptions);
|
|
32
34
|
this.calls++;
|
|
33
35
|
const data = await this.parseResponse(response);
|
|
34
|
-
|
|
35
36
|
this.aqua.emit("apiResponse", endpoint, response);
|
|
36
37
|
this.aqua.emit(
|
|
37
38
|
"debug",
|
|
38
39
|
`[Rest] ${method} ${endpoint} ${body ? `body: ${JSON.stringify(body)}` : ""} -> Status Code: ${response.status} Response: ${nodeUtil.inspect(data)}`
|
|
39
40
|
);
|
|
40
|
-
|
|
41
41
|
return includeHeaders ? { data, headers: response.headers } : data;
|
|
42
42
|
} catch (error) {
|
|
43
43
|
throw new Error(`Network error during request: ${method} ${this.url}${endpoint}`, { cause: error });
|
|
@@ -50,18 +50,15 @@ class Rest {
|
|
|
50
50
|
|
|
51
51
|
async updatePlayer(options) {
|
|
52
52
|
const requestBody = { ...options.data };
|
|
53
|
-
|
|
54
53
|
if ((requestBody.track && requestBody.track.encoded && requestBody.track.identifier) ||
|
|
55
54
|
(requestBody.encodedTrack && requestBody.identifier)) {
|
|
56
55
|
throw new Error(`Cannot provide both 'encoded' and 'identifier' for track in Update Player Endpoint`);
|
|
57
56
|
}
|
|
58
|
-
|
|
59
57
|
if (this.version === "v3" && options.data?.track) {
|
|
60
58
|
const { track } = requestBody;
|
|
61
59
|
delete requestBody.track;
|
|
62
60
|
Object.assign(requestBody, track.encoded ? { encodedTrack: track.encoded } : { identifier: track.identifier });
|
|
63
61
|
}
|
|
64
|
-
|
|
65
62
|
return this.makeRequest("PATCH", `/${this.version}/sessions/${this.sessionId}/players/${options.guildId}?noReplace=false`, requestBody);
|
|
66
63
|
}
|
|
67
64
|
|
|
@@ -85,7 +82,6 @@ class Rest {
|
|
|
85
82
|
if (this.version === "v3") {
|
|
86
83
|
return this.makeRequest("GET", `/${this.version}/stats`);
|
|
87
84
|
}
|
|
88
|
-
|
|
89
85
|
return this.makeRequest("GET", `/${this.version}/stats/all`);
|
|
90
86
|
}
|
|
91
87
|
|
|
@@ -105,7 +101,6 @@ class Rest {
|
|
|
105
101
|
if (response.status === 204) {
|
|
106
102
|
return null;
|
|
107
103
|
}
|
|
108
|
-
|
|
109
104
|
try {
|
|
110
105
|
const contentType = response.headers.get("Content-Type");
|
|
111
106
|
return await response[contentType.includes("text/plain") ? "text" : "json"]();
|
|
@@ -114,6 +109,44 @@ class Rest {
|
|
|
114
109
|
return null;
|
|
115
110
|
}
|
|
116
111
|
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Adds a request to the queue and processes it.
|
|
115
|
+
* @param {function} requestFunction - The request function to execute.
|
|
116
|
+
*/
|
|
117
|
+
async queueRequest(requestFunction) {
|
|
118
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
119
|
+
this.aqua.emit("debug", "[Rest] Queue is full, discarding oldest request.");
|
|
120
|
+
this.queue.shift();
|
|
121
|
+
}
|
|
122
|
+
this.queue.push(requestFunction);
|
|
123
|
+
this.processQueue();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Processes the queue of requests with concurrency control.
|
|
128
|
+
*/
|
|
129
|
+
async processQueue() {
|
|
130
|
+
while (this.activeRequests < this.maxConcurrentRequests && this.queue.length > 0) {
|
|
131
|
+
const requestFunction = this.queue.shift();
|
|
132
|
+
this.activeRequests++;
|
|
133
|
+
try {
|
|
134
|
+
await requestFunction();
|
|
135
|
+
} catch (error) {
|
|
136
|
+
this.aqua.emit("error", error);
|
|
137
|
+
} finally {
|
|
138
|
+
this.activeRequests--;
|
|
139
|
+
this.processQueue();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Cleans up resources related to the queue.
|
|
146
|
+
*/
|
|
147
|
+
cleanupQueue() {
|
|
148
|
+
this.queue = [];
|
|
149
|
+
}
|
|
117
150
|
}
|
|
118
151
|
|
|
119
152
|
module.exports = { Rest };
|
|
@@ -16,12 +16,7 @@ class Track {
|
|
|
16
16
|
this.info = data.info;
|
|
17
17
|
this.requester = requester;
|
|
18
18
|
this.nodes = nodes;
|
|
19
|
-
|
|
20
|
-
if (data.encoded) {
|
|
21
|
-
this.track = data.encoded;
|
|
22
|
-
} else {
|
|
23
|
-
this.track = Buffer.from(data.track, "base64").toString("utf8");
|
|
24
|
-
}
|
|
19
|
+
this.track = data.encoded || Buffer.from(data.track, "base64").toString("utf8");
|
|
25
20
|
}
|
|
26
21
|
|
|
27
22
|
/**
|
|
@@ -29,8 +24,7 @@ class Track {
|
|
|
29
24
|
* @returns {string|null}
|
|
30
25
|
*/
|
|
31
26
|
resolveThumbnail(thumbnail) {
|
|
32
|
-
|
|
33
|
-
return thumbnail.startsWith("http") ? thumbnail : getImageUrl(thumbnail, this.nodes);
|
|
27
|
+
return thumbnail ? (thumbnail.startsWith("http") ? thumbnail : getImageUrl(thumbnail, this.nodes)) : null;
|
|
34
28
|
}
|
|
35
29
|
|
|
36
30
|
/**
|
|
@@ -40,16 +34,9 @@ class Track {
|
|
|
40
34
|
async resolve(aqua) {
|
|
41
35
|
const query = `${this.info.author} - ${this.info.title}`;
|
|
42
36
|
const result = await aqua.resolve({ query, source: aqua.options.defaultSearchPlatform, requester: this.requester, node: this.nodes });
|
|
43
|
-
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
const matchedTrack = this.findBestMatch(result.tracks);
|
|
47
|
-
if (matchedTrack) {
|
|
48
|
-
this.updateTrackInfo(matchedTrack);
|
|
49
|
-
return this;
|
|
50
|
-
}
|
|
51
|
-
this.updateTrackInfo(result.tracks[0]);
|
|
52
|
-
return this;
|
|
37
|
+
const matchedTrack = result?.tracks?.length ? this.findBestMatch(result.tracks) || result.tracks[0] : null;
|
|
38
|
+
if (matchedTrack) this.updateTrackInfo(matchedTrack);
|
|
39
|
+
return matchedTrack ? this : null;
|
|
53
40
|
}
|
|
54
41
|
|
|
55
42
|
/**
|
|
@@ -57,23 +44,16 @@ class Track {
|
|
|
57
44
|
* @returns {Track|null}
|
|
58
45
|
*/
|
|
59
46
|
findBestMatch(tracks) {
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (this.info.length) {
|
|
71
|
-
return tracks.find(track =>
|
|
72
|
-
track.info.length >= (this.info.length - 2000) && track.info.length <= (this.info.length + 2000)
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return null;
|
|
47
|
+
const { title, author, length } = this.info;
|
|
48
|
+
const titleLower = title.toLowerCase();
|
|
49
|
+
const authorLower = author.toLowerCase();
|
|
50
|
+
return tracks.find(track => {
|
|
51
|
+
const { author, title, length: tLength } = track.info;
|
|
52
|
+
return (author.toLowerCase() === authorLower && title.toLowerCase() === titleLower) ||
|
|
53
|
+
(author.toLowerCase() === authorLower) ||
|
|
54
|
+
(title.toLowerCase() === titleLower) ||
|
|
55
|
+
(length && tLength >= (length - 2000) && tLength <= (length + 2000));
|
|
56
|
+
});
|
|
77
57
|
}
|
|
78
58
|
|
|
79
59
|
/**
|
|
@@ -88,9 +68,7 @@ class Track {
|
|
|
88
68
|
* @private
|
|
89
69
|
*/
|
|
90
70
|
cleanup() {
|
|
91
|
-
this.rawData = null;
|
|
92
|
-
this.track = null;
|
|
93
|
-
this.info = null;
|
|
71
|
+
this.rawData = this.track = this.info = null;
|
|
94
72
|
}
|
|
95
73
|
}
|
|
96
74
|
|