aqualink 2.2.0 → 2.3.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.
@@ -1,5 +1,4 @@
1
1
  "use strict";
2
-
3
2
  const { EventEmitter } = require("node:events");
4
3
  const Node = require("./Node");
5
4
  const Player = require("./Player");
@@ -14,14 +13,12 @@ class Aqua extends EventEmitter {
14
13
  if (!Array.isArray(nodes) || !nodes.length) {
15
14
  throw new Error(`Nodes must be a non-empty Array (Received ${typeof nodes})`);
16
15
  }
17
-
18
16
  this.client = client;
19
17
  this.nodes = nodes;
20
18
  this.nodeMap = new Map();
21
19
  this.players = new Map();
22
20
  this.clientId = null;
23
21
  this.initiated = false;
24
-
25
22
  this.shouldDeleteMessage = options.shouldDeleteMessage ?? false;
26
23
  this.defaultSearchPlatform = options.defaultSearchPlatform ?? 'ytsearch';
27
24
  this.leaveOnEnd = options.leaveOnEnd ?? true;
@@ -32,88 +29,72 @@ class Aqua extends EventEmitter {
32
29
  this.autoResume = options.autoResume ?? false;
33
30
  this.infiniteReconnects = options.infiniteReconnects ?? false;
34
31
  this.options = options;
35
-
36
32
  this.setMaxListeners(0);
37
33
  this._leastUsedCache = { nodes: [], timestamp: 0 };
38
34
  }
39
35
 
40
-
41
36
  defaultSendFunction(payload) {
42
37
  const guild = this.client.guilds.cache.get(payload.d.guild_id);
43
38
  if (guild) guild.shard.send(payload);
44
39
  }
45
40
 
46
- validateInputs(client, nodes) {
47
- if (!client) throw new Error("Client is required to initialize Aqua");
48
- if (!Array.isArray(nodes) || !nodes.length) {
49
- throw new Error(`Nodes must be a non-empty Array (Received ${typeof nodes})`);
50
- }
51
- }
52
-
53
41
  get leastUsedNodes() {
54
42
  const now = Date.now();
55
43
  if (now - this._leastUsedCache.timestamp < 50) return this._leastUsedCache.nodes;
56
-
57
- const nodes = [];
58
- for (const node of this.nodeMap.values()) {
59
- if (node.connected) nodes.push(node);
60
- }
44
+ const nodes = Array.from(this.nodeMap.values()).filter(node => node.connected);
61
45
  nodes.sort((a, b) => a.rest.calls - b.rest.calls);
62
-
63
46
  this._leastUsedCache = { nodes, timestamp: now };
64
47
  return nodes;
65
48
  }
66
- init(clientId) {
67
- if (this.initiated) return this;
68
49
 
50
+ async init(clientId) {
51
+ if (this.initiated) return this;
69
52
  this.clientId = clientId;
70
-
71
53
  try {
72
- for (let i = 0; i < this.nodes.length; i++) { this.createNode(this.nodes[i]); }
73
- if (this.plugins.length > 0) { for (let i = 0; i < this.plugins.length; i++) { this.plugins[i].load(this); } }
74
-
54
+ for (let i = 0; i < this.nodes.length; i++) {
55
+ const node = this.nodes[i];
56
+ await this.createNode(node);
57
+ }
58
+ for (let i = 0; i < this.plugins.length; i++) {
59
+ const plugin = this.plugins[i];
60
+ plugin.load(this);
61
+ }
75
62
  this.initiated = true;
76
63
  } catch (error) {
77
64
  this.initiated = false;
78
65
  throw error;
79
66
  }
80
-
81
67
  return this;
82
68
  }
83
69
 
84
- createNode(options) {
70
+ async createNode(options) {
85
71
  const nodeId = options.name || options.host;
86
72
  this.destroyNode(nodeId);
87
-
88
73
  const node = new Node(this, options, this.options);
89
74
  this.nodeMap.set(nodeId, node);
90
75
  this._leastUsedCache.timestamp = 0;
91
-
92
- node.connect()
93
- .then(() => this.emit("nodeCreate", node))
94
- .catch(error => {
95
- this.nodeMap.delete(nodeId);
96
- console.error("Failed to connect node:", error);
97
- throw error;
98
- });
99
-
76
+ try {
77
+ await node.connect();
78
+ this.emit("nodeCreate", node);
79
+ } catch (error) {
80
+ this.nodeMap.delete(nodeId);
81
+ console.error("Failed to connect node:", error);
82
+ throw error;
83
+ }
100
84
  return node;
101
85
  }
102
86
 
103
87
  destroyNode(identifier) {
104
88
  const node = this.nodeMap.get(identifier);
105
89
  if (!node) return;
106
-
107
90
  node.destroy();
108
91
  this.nodeMap.delete(identifier);
109
92
  this.emit("nodeDestroy", node);
110
93
  }
111
94
 
112
-
113
95
  updateVoiceState({ d, t }) {
114
96
  const player = this.players.get(d.guild_id);
115
97
  if (!player) return;
116
-
117
98
  const updateMethod = t === "VOICE_SERVER_UPDATE" ? "setServerUpdate" : "setStateUpdate";
118
99
  if (t === "VOICE_SERVER_UPDATE" || (t === "VOICE_STATE_UPDATE" && d.user_id === this.clientId)) {
119
100
  if (player.connection && typeof player.connection[updateMethod] === "function") {
@@ -127,14 +108,12 @@ class Aqua extends EventEmitter {
127
108
 
128
109
  fetchRegion(region) {
129
110
  if (!region) return this.leastUsedNodes;
130
-
131
111
  const lowerRegion = region.toLowerCase();
132
- const regionNodes = Array.from(this.nodeMap.values()).filter(node =>
112
+ const nodes = Array.from(this.nodeMap.values()).filter(node =>
133
113
  node.connected && node.regions?.includes(lowerRegion)
134
114
  );
135
- regionNodes.sort((a, b) => this.calculateLoad(a) - this.calculateLoad(b));
136
-
137
- return regionNodes;
115
+ nodes.sort((a, b) => this.calculateLoad(a) - this.calculateLoad(b));
116
+ return nodes;
138
117
  }
139
118
 
140
119
  calculateLoad(node) {
@@ -147,11 +126,9 @@ class Aqua extends EventEmitter {
147
126
  this.ensureInitialized();
148
127
  const existingPlayer = this.players.get(options.guildId);
149
128
  if (existingPlayer && existingPlayer.voiceChannel) return existingPlayer;
150
-
151
129
  const availableNodes = options.region ? this.fetchRegion(options.region) : this.leastUsedNodes;
152
130
  const node = availableNodes[0];
153
131
  if (!node) throw new Error("No nodes are available");
154
-
155
132
  return this.createPlayer(node, options);
156
133
  }
157
134
 
@@ -159,11 +136,9 @@ class Aqua extends EventEmitter {
159
136
  this.destroyPlayer(options.guildId);
160
137
  const player = new Player(this, node, options);
161
138
  this.players.set(options.guildId, player);
162
-
163
139
  player.once("destroy", () => {
164
140
  this.players.delete(options.guildId);
165
141
  });
166
-
167
142
  player.connect(options);
168
143
  this.emit("playerCreate", player);
169
144
  return player;
@@ -172,7 +147,6 @@ class Aqua extends EventEmitter {
172
147
  async destroyPlayer(guildId) {
173
148
  const player = this.players.get(guildId);
174
149
  if (!player) return;
175
-
176
150
  try {
177
151
  await player.clearData();
178
152
  player.removeAllListeners();
@@ -187,7 +161,6 @@ class Aqua extends EventEmitter {
187
161
  this.ensureInitialized();
188
162
  const requestNode = this.getRequestNode(nodes);
189
163
  const formattedQuery = this.formatQuery(query, source);
190
-
191
164
  try {
192
165
  const response = await requestNode.rest.makeRequest("GET", `/v4/loadtracks?identifier=${encodeURIComponent(formattedQuery)}`);
193
166
  if (["empty", "NO_MATCHES"].includes(response.loadType)) {
@@ -204,11 +177,9 @@ class Aqua extends EventEmitter {
204
177
 
205
178
  getRequestNode(nodes) {
206
179
  if (!nodes) return this.leastUsedNodes[0];
207
-
208
180
  if (!(typeof nodes === "string" || nodes instanceof Node)) {
209
181
  throw new TypeError(`'nodes' must be a string or Node instance, received: ${typeof nodes}`);
210
182
  }
211
-
212
183
  return (typeof nodes === "string" ? this.nodeMap.get(nodes) : nodes) ?? this.leastUsedNodes[0];
213
184
  }
214
185
 
@@ -224,11 +195,9 @@ class Aqua extends EventEmitter {
224
195
  try {
225
196
  const ytIdentifier = `/v4/loadtracks?identifier=https://www.youtube.com/watch?v=${query}`;
226
197
  const youtubeResponse = await rest.makeRequest("GET", ytIdentifier);
227
-
228
198
  if (!["empty", "NO_MATCHES"].includes(youtubeResponse.loadType)) {
229
199
  return youtubeResponse;
230
200
  }
231
-
232
201
  const spotifyIdentifier = `/v4/loadtracks?identifier=https://open.spotify.com/track/${query}`;
233
202
  return await rest.makeRequest("GET", spotifyIdentifier);
234
203
  } catch (error) {
@@ -245,14 +214,11 @@ class Aqua extends EventEmitter {
245
214
  pluginInfo: response.pluginInfo ?? {},
246
215
  tracks: []
247
216
  };
248
-
249
217
  if (response.loadType === "error" || response.loadType === "LOAD_FAILED") {
250
218
  baseResponse.exception = response.data ?? response.exception;
251
219
  return baseResponse;
252
220
  }
253
-
254
221
  const trackFactory = (trackData) => new Track(trackData, requester, requestNode);
255
-
256
222
  switch (response.loadType) {
257
223
  case "track":
258
224
  if (response.data) {
@@ -289,7 +255,6 @@ class Aqua extends EventEmitter {
289
255
  }
290
256
  break;
291
257
  }
292
-
293
258
  return baseResponse;
294
259
  }
295
260
 
@@ -301,7 +266,6 @@ class Aqua extends EventEmitter {
301
266
 
302
267
  async search(query, requester, source = this.defaultSearchPlatform) {
303
268
  if (!query || !requester) return null;
304
-
305
269
  try {
306
270
  const { tracks } = await this.resolve({ query, source, requester });
307
271
  return tracks || null;
@@ -311,11 +275,33 @@ class Aqua extends EventEmitter {
311
275
  }
312
276
  }
313
277
 
314
- cleanupPlayer(player) {
315
- if (player && this.players.has(player.guildId)) {
278
+ async cleanupPlayer(player) {
279
+ if (!player) return;
280
+ try {
281
+ if (player.connection) {
282
+ try {
283
+ await player.connection.disconnect();
284
+ player.connection = null;
285
+ } catch (error) {
286
+ console.error(`Error disconnecting player connection: ${error.message}`);
287
+ }
288
+ }
289
+ if (player.queue) {
290
+ player.queue.clear();
291
+ }
292
+ if (typeof player.stop === 'function') {
293
+ try {
294
+ await player.stop();
295
+ } catch (error) {
296
+ console.error(`Error stopping player: ${error.message}`);
297
+ }
298
+ }
299
+ player.removeAllListeners();
316
300
  this.players.delete(player.guildId);
301
+ this.emit("playerCleanup", player.guildId);
302
+ } catch (error) {
303
+ console.error(`Error during player cleanup: ${error.message}`);
317
304
  }
318
305
  }
319
306
  }
320
-
321
307
  module.exports = Aqua;