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 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.4.2 is released, wow
29
-
30
- - Rewrited CONNECTION manager (This fixes Bad request, Fixes sessionId, json responses from lavalink, and other bugfixes)
31
- - Fixed skip in Player, Fixed Destroy, Fixed desconnection, Fixed stop, And added some optimizations
32
- - Added clean this.voice on Aqua manager
33
- - Fixed Destroy sending embeds / text if there is an queue
34
- - Fixed stop / destroy sending all the texts (songs in queue)
35
- - Optimized the track handling
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
- if (response.ok) {
27
- const json = await response.json();
28
- return json.thumbnail_url || null;
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;
@@ -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 || "v3";
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.send !== "function") throw new Error("Send function is required to initialize Aqua");
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 [...this.nodeMap.values()]
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
- if (packet.t === "VOICE_SERVER_UPDATE") {
68
- player.connection.setServerUpdate(packet.d);
69
- } else if (packet.t === "VOICE_STATE_UPDATE" && packet.d.user_id === this.clientId) {
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 || this.defaultSearchPlatform);
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 spotifyResponse = await rest.makeRequest("GET", `/v4/loadtracks?identifier=https://open.spotify.com/track/${query}`);
161
- if (["empty", "NO_MATCHES"].includes(spotifyResponse.loadType)) {
162
- return await rest.makeRequest("GET", `/v4/loadtracks?identifier=https://www.youtube.com/watch?v=${query}`);
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 spotifyResponse;
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
- switch (response.loadType) {
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 (response.data) {
182
- baseResponse.tracks.push(new Track(response.data, requester, requestNode));
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: response.data?.info?.name || response.data?.info?.title,
188
- ...response.data?.info,
190
+ name: data?.info?.name || data?.info?.title,
191
+ ...data?.info,
189
192
  };
190
- baseResponse.tracks = response.data?.tracks?.map(track => new Track(track, requester, requestNode)) || [];
193
+ baseResponse.tracks = (data?.tracks || []).map(track => new Track(track, requester, requestNode));
191
194
  break;
192
195
  case "search":
193
- baseResponse.tracks = response.data?.map(track => new Track(track, requester, requestNode)) || [];
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
- player.destroy();
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({ session_id, channel_id, self_deaf, self_mute }) {
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
- if (this.player.voiceChannel !== channel_id) {
43
- this.player.aqua.emit("playerMove", this.player.voiceChannel, channel_id);
44
- this.player.voiceChannel = channel_id;
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.selfDeaf = self_deaf;
49
- this.selfMute = self_mute;
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 };
@@ -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
- * Plays the next track in the queue.
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
- * @throws {Error} If the queue is empty.
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
- * @param {Object} options the options to use
96
- * @param {String} options.guildId the guild id to connect to
97
- * @param {String} options.voiceChannel the voice channel to connect to
98
- * @param {Boolean} [options.deaf=true] if the player should be deafened
99
- * @param {Boolean} [options.mute=false] if the player should be muted
100
- * @returns {Promise<Player>} The player instance.
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.disconnect()
122
- this.cleanup()
123
- this.clearData()
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 the player to a given position.
117
+ * Seeks to a position in the currently playing track.
142
118
  *
143
- * @param {Number} position the position to seek to in milliseconds
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 {Number} volume the volume to set, must be between 0 and 200
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 {String} mode the loop mode to set, must be 'none', 'track', or 'queue'
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 that the player will send messages to.
167
+ * Sets the text channel for the player.
198
168
  *
199
- * @param {String} channel the ID of the text channel to send messages to
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 that the player will connect to.
179
+ * Sets the voice channel for the player.
210
180
  *
211
- * @param {String} channel the ID of the voice channel to connect to
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 given channel.
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 || Buffer.from(data.track, "base64").toString("utf8");
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
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aqualink",
3
- "version": "1.4.2",
3
+ "version": "1.5.0",
4
4
  "description": "An Lavalink wrapper, focused in speed, performance, and features, Based in Riffy!",
5
5
  "main": "build/index.js",
6
6
  "scripts": {