aqualink 2.4.0 → 2.6.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.
@@ -233,7 +233,7 @@ class Aqua extends EventEmitter {
233
233
  const response = await requestNode.rest.makeRequest("GET", endpoint);
234
234
 
235
235
  if (["empty", "NO_MATCHES"].includes(response.loadType)) {
236
- return await this.handleNoMatches(requestNode.rest, query);
236
+ return await this.handleNoMatches(query);
237
237
  }
238
238
 
239
239
  return this.constructResponse(response, requester, requestNode);
@@ -244,33 +244,29 @@ class Aqua extends EventEmitter {
244
244
  throw new Error(`Failed to resolve track: ${error.message}`);
245
245
  }
246
246
  }
247
-
247
+
248
248
  getRequestNode(nodes) {
249
249
  if (!nodes) return this.leastUsedNodes[0];
250
250
 
251
251
  if (nodes instanceof Node) return nodes;
252
- if (typeof nodes === "string") return this.nodeMap.get(nodes) || this.leastUsedNodes[0];
252
+ if (typeof nodes === "string") {
253
+ const mappedNode = this.nodeMap.get(nodes);
254
+ return mappedNode || this.leastUsedNodes[0];
255
+ }
253
256
 
254
257
  throw new TypeError(`'nodes' must be a string or Node instance, received: ${typeof nodes}`);
255
258
  }
256
-
257
- async handleNoMatches(rest, query) {
258
- try {
259
- const ytEndpoint = `/v4/loadtracks?identifier=https://www.youtube.com/watch?v=${query}`;
260
- const youtubeResponse = await rest.makeRequest("GET", ytEndpoint);
261
-
262
- if (!["empty", "NO_MATCHES"].includes(youtubeResponse.loadType)) {
263
- return youtubeResponse;
264
- }
265
-
266
- const spotifyEndpoint = `/v4/loadtracks?identifier=https://open.spotify.com/track/${query}`;
267
- return await rest.makeRequest("GET", spotifyEndpoint);
268
- } catch (error) {
269
- console.error(`Failed to resolve track: ${error.message}`);
270
- throw error;
271
- }
259
+
260
+ async handleNoMatches(query) {
261
+ return {
262
+ loadType: "empty",
263
+ exception: null,
264
+ playlistInfo: null,
265
+ pluginInfo: {},
266
+ tracks: []
267
+ };
272
268
  }
273
-
269
+
274
270
  constructResponse(response, requester, requestNode) {
275
271
  const baseResponse = {
276
272
  loadType: response.loadType,
@@ -2,40 +2,44 @@
2
2
 
3
3
  class Connection {
4
4
  constructor(player) {
5
- this.playerRef = new WeakRef(player);
5
+ this.player = player;
6
6
 
7
7
  this.sessionId = null;
8
8
  this.endpoint = null;
9
9
  this.token = null;
10
-
11
10
  this.region = null;
12
- this.selfDeaf = false;
13
- this.selfMute = false;
11
+
14
12
  this.voiceChannel = player.voiceChannel;
15
13
  this.guildId = player.guildId;
16
-
17
14
  this.aqua = player.aqua;
18
15
  this.nodes = player.nodes;
16
+
17
+ this.selfDeaf = false;
18
+ this.selfMute = false;
19
+
20
+ this.hasDebugListeners = this.aqua.listenerCount('debug') > 0;
19
21
  }
20
22
 
21
23
  setServerUpdate(data) {
22
- if (!data?.endpoint) return;
24
+ if (!data || !data.endpoint) return;
23
25
 
24
26
  const { endpoint, token } = data;
25
- const dotIndex = endpoint.indexOf('.');
26
27
 
28
+ const dotIndex = endpoint.indexOf('.');
27
29
  if (dotIndex === -1) return;
30
+
28
31
  const newRegion = endpoint.substring(0, dotIndex);
29
32
 
30
33
  if (this.region !== newRegion) {
31
- const prevRegion = this.region;
32
- [this.endpoint, this.token, this.region] = [endpoint, token, newRegion];
34
+ this.endpoint = endpoint;
35
+ this.token = token;
36
+ this.region = newRegion;
33
37
 
34
- if (this.aqua.listenerCount('debug')) {
38
+ if (this.hasDebugListeners) {
35
39
  this.aqua.emit(
36
40
  "debug",
37
41
  `[Player ${this.guildId} - CONNECTION] Voice Server: ${
38
- prevRegion ? `Changed from ${prevRegion} to ${newRegion}` : newRegion
42
+ this.region ? `Changed from ${this.region} to ${newRegion}` : newRegion
39
43
  }`
40
44
  );
41
45
  }
@@ -45,10 +49,12 @@ class Connection {
45
49
  }
46
50
 
47
51
  setStateUpdate(data) {
48
- const { channel_id, session_id, self_deaf, self_mute } = data || {};
52
+ if (!data) return this.player?.destroy();
53
+
54
+ const { channel_id, session_id, self_deaf, self_mute } = data;
49
55
 
50
56
  if (!channel_id || !session_id) {
51
- this.playerRef.deref()?.destroy();
57
+ this.player?.destroy();
52
58
  return;
53
59
  }
54
60
 
@@ -57,36 +63,35 @@ class Connection {
57
63
  this.voiceChannel = channel_id;
58
64
  }
59
65
 
60
- this.selfDeaf = !!self_deaf;
61
- this.selfMute = !!self_mute;
66
+ this.selfDeaf = Boolean(self_deaf);
67
+ this.selfMute = Boolean(self_mute);
62
68
  this.sessionId = session_id;
63
69
  }
64
70
 
65
- async _updatePlayerVoiceData() {
66
- const player = this.playerRef.deref();
67
- if (!player) return;
71
+ _updatePlayerVoiceData() {
72
+ if (!this.player) return;
73
+
74
+ const voiceData = {
75
+ sessionId: this.sessionId,
76
+ endpoint: this.endpoint,
77
+ token: this.token
78
+ };
68
79
 
69
- try {
70
- await this.nodes.rest.updatePlayer({
71
- guildId: this.guildId,
72
- data: {
73
- voice: {
74
- sessionId: this.sessionId,
75
- endpoint: this.endpoint,
76
- token: this.token
77
- },
78
- volume: player.volume
79
- }
80
- });
81
- } catch (error) {
82
- if (this.aqua.listenerCount('apiError')) {
80
+ this.nodes.rest.updatePlayer({
81
+ guildId: this.guildId,
82
+ data: {
83
+ voice: voiceData,
84
+ volume: this.player.volume
85
+ }
86
+ }).catch(error => {
87
+ if (this.aqua.listenerCount('apiError') > 0) {
83
88
  this.aqua.emit("apiError", "updatePlayer", {
84
89
  error,
85
90
  guildId: this.guildId,
86
- voiceData: { ...this }
91
+ voiceData
87
92
  });
88
93
  }
89
- }
94
+ });
90
95
  }
91
96
  }
92
97
 
@@ -3,205 +3,165 @@
3
3
  class Filters {
4
4
  constructor(player, options = {}) {
5
5
  this.player = player;
6
- this.volume = options.volume || 1;
7
- this.equalizer = options.equalizer || [];
8
- this.karaoke = options.karaoke || null;
9
- this.timescale = options.timescale || null;
10
- this.tremolo = options.tremolo || null;
11
- this.vibrato = options.vibrato || null;
12
- this.rotation = options.rotation || null;
13
- this.distortion = options.distortion || null;
14
- this.channelMix = options.channelMix || null;
15
- this.lowPass = options.lowPass || null;
16
- this.bassboost = options.bassboost || null;
17
- this.slowmode = options.slowmode || null;
18
- this.nightcore = options.nightcore || null;
19
- this.vaporwave = options.vaporwave || null;
20
- this._8d = options._8d || null;
21
6
 
22
- this._filterDataTemplate = {
23
- volume: this.volume,
24
- equalizer: this.equalizer,
25
- karaoke: null,
26
- timescale: null,
27
- tremolo: null,
28
- vibrato: null,
29
- rotation: null,
30
- distortion: null,
31
- channelMix: null,
32
- lowPass: null
7
+ this.defaults = {
8
+ karaoke: { level: 1.0, monoLevel: 1.0, filterBand: 220.0, filterWidth: 100.0 },
9
+ timescale: { speed: 1.0, pitch: 1.0, rate: 1.0 },
10
+ tremolo: { frequency: 2.0, depth: 0.5 },
11
+ vibrato: { frequency: 2.0, depth: 0.5 },
12
+ rotation: { rotationHz: 0.0 },
13
+ distortion: { sinOffset: 0.0, sinScale: 1.0, cosOffset: 0.0, cosScale: 1.0, tanOffset: 0.0, tanScale: 1.0, offset: 0.0, scale: 1.0 },
14
+ channelMix: { leftToLeft: 1.0, leftToRight: 0.0, rightToLeft: 0.0, rightToRight: 1.0 },
15
+ lowPass: { smoothing: 20.0 }
33
16
  };
17
+
18
+ this.filters = {
19
+ volume: options.volume || 1,
20
+ equalizer: options.equalizer || [],
21
+ karaoke: options.karaoke || null,
22
+ timescale: options.timescale || null,
23
+ tremolo: options.tremolo || null,
24
+ vibrato: options.vibrato || null,
25
+ rotation: options.rotation || null,
26
+ distortion: options.distortion || null,
27
+ channelMix: options.channelMix || null,
28
+ lowPass: options.lowPass || null
29
+ };
30
+
31
+ this.presets = {
32
+ bassboost: options.bassboost !== undefined ? options.bassboost : null,
33
+ slowmode: options.slowmode !== undefined ? options.slowmode : null,
34
+ nightcore: options.nightcore !== undefined ? options.nightcore : null,
35
+ vaporwave: options.vaporwave !== undefined ? options.vaporwave : null,
36
+ _8d: options._8d !== undefined ? options._8d : null
37
+ };
38
+
39
+ this._pendingUpdate = false;
40
+ this._updateTimeout = null;
34
41
  }
35
42
 
36
- _setFilter(filterName, enabled, options, defaults) {
37
- if (!enabled) {
38
- this[filterName] = null;
39
- return this.updateFilters();
40
- }
43
+ _setFilter(filterName, enabled, options = {}, defaultKey = filterName) {
44
+ this.filters[filterName] = enabled ? { ...this.defaults[defaultKey], ...options } : null;
45
+ return this._scheduleUpdate();
46
+ }
47
+
48
+ _scheduleUpdate() {
49
+ this._pendingUpdate = true;
41
50
 
42
- const filterObj = {};
43
- for (const [key, defaultValue] of Object.entries(defaults)) {
44
- filterObj[key] = options[key] !== undefined ? options[key] : defaultValue;
51
+ if (this._updateTimeout) {
52
+ clearTimeout(this._updateTimeout);
45
53
  }
46
54
 
47
- this[filterName] = filterObj;
48
- return this.updateFilters();
55
+ this._updateTimeout = setTimeout(() => this.updateFilters(), 0);
56
+ return this;
49
57
  }
50
58
 
51
59
  setEqualizer(bands) {
52
- this.equalizer = bands;
53
- return this.updateFilters();
60
+ this.filters.equalizer = bands || [];
61
+ return this._scheduleUpdate();
54
62
  }
55
63
 
56
64
  setKaraoke(enabled, options = {}) {
57
- return this._setFilter('karaoke', enabled, options, {
58
- level: 1.0,
59
- monoLevel: 1.0,
60
- filterBand: 220.0,
61
- filterWidth: 100.0
62
- });
65
+ return this._setFilter('karaoke', enabled, options);
63
66
  }
64
67
 
65
68
  setTimescale(enabled, options = {}) {
66
- return this._setFilter('timescale', enabled, options, {
67
- speed: 1.0,
68
- pitch: 1.0,
69
- rate: 1.0
70
- });
69
+ return this._setFilter('timescale', enabled, options);
71
70
  }
72
71
 
73
72
  setTremolo(enabled, options = {}) {
74
- return this._setFilter('tremolo', enabled, options, {
75
- frequency: 2.0,
76
- depth: 0.5
77
- });
73
+ return this._setFilter('tremolo', enabled, options);
78
74
  }
79
75
 
80
76
  setVibrato(enabled, options = {}) {
81
- return this._setFilter('vibrato', enabled, options, {
82
- frequency: 2.0,
83
- depth: 0.5
84
- });
77
+ return this._setFilter('vibrato', enabled, options);
85
78
  }
86
79
 
87
80
  setRotation(enabled, options = {}) {
88
- return this._setFilter('rotation', enabled, options, {
89
- rotationHz: 0.0
90
- });
81
+ return this._setFilter('rotation', enabled, options);
91
82
  }
92
83
 
93
84
  setDistortion(enabled, options = {}) {
94
- return this._setFilter('distortion', enabled, options, {
95
- sinOffset: 0.0,
96
- sinScale: 1.0,
97
- cosOffset: 0.0,
98
- cosScale: 1.0,
99
- tanOffset: 0.0,
100
- tanScale: 1.0,
101
- offset: 0.0,
102
- scale: 1.0
103
- });
85
+ return this._setFilter('distortion', enabled, options);
104
86
  }
105
87
 
106
88
  setChannelMix(enabled, options = {}) {
107
- return this._setFilter('channelMix', enabled, options, {
108
- leftToLeft: 1.0,
109
- leftToRight: 0.0,
110
- rightToLeft: 0.0,
111
- rightToRight: 1.0
112
- });
89
+ return this._setFilter('channelMix', enabled, options);
113
90
  }
114
91
 
115
92
  setLowPass(enabled, options = {}) {
116
- return this._setFilter('lowPass', enabled, options, {
117
- smoothing: 20.0
118
- });
93
+ return this._setFilter('lowPass', enabled, options);
119
94
  }
120
95
 
121
96
  setBassboost(enabled, options = {}) {
122
97
  if (!enabled) {
123
- this.bassboost = null;
98
+ this.presets.bassboost = null;
124
99
  return this.setEqualizer([]);
125
100
  }
126
101
 
127
102
  const value = options.value || 5;
128
103
  if (value < 0 || value > 5) throw new Error("Bassboost value must be between 0 and 5");
129
104
 
130
- this.bassboost = value;
131
- const num = (value - 1) * (1.25 / 9) - 0.25;
105
+ this.presets.bassboost = value;
106
+ const gain = (value - 1) * (1.25 / 9) - 0.25;
132
107
 
133
- const eq = new Array(13);
134
- for (let i = 0; i < 13; i++) {
135
- eq[i] = { band: i, gain: num };
136
- }
108
+ const eq = Array.from({ length: 13 }, (_, i) => ({ band: i, gain }));
137
109
 
138
110
  return this.setEqualizer(eq);
139
111
  }
140
112
 
141
113
  setSlowmode(enabled, options = {}) {
142
- this.slowmode = enabled;
114
+ this.presets.slowmode = enabled;
143
115
  return this.setTimescale(enabled, { rate: enabled ? (options.rate || 0.8) : 1.0 });
144
116
  }
145
117
 
146
118
  setNightcore(enabled, options = {}) {
147
- this.nightcore = enabled;
119
+ this.presets.nightcore = enabled;
148
120
  return this.setTimescale(enabled, { rate: enabled ? (options.rate || 1.5) : 1.0 });
149
121
  }
150
122
 
151
123
  setVaporwave(enabled, options = {}) {
152
- this.vaporwave = enabled;
124
+ this.presets.vaporwave = enabled;
153
125
  return this.setTimescale(enabled, { pitch: enabled ? (options.pitch || 0.5) : 1.0 });
154
126
  }
155
127
 
156
128
  set8D(enabled, options = {}) {
157
- this._8d = enabled;
129
+ this.presets._8d = enabled;
158
130
  return this.setRotation(enabled, { rotationHz: enabled ? (options.rotationHz || 0.2) : 0.0 });
159
131
  }
160
132
 
161
133
  async clearFilters() {
162
- this.volume = 1;
163
- this.equalizer = [];
164
- this.karaoke = null;
165
- this.timescale = null;
166
- this.tremolo = null;
167
- this.vibrato = null;
168
- this.rotation = null;
169
- this.distortion = null;
170
- this.channelMix = null;
171
- this.lowPass = null;
172
- this.bassboost = null;
173
- this.slowmode = null;
174
- this.nightcore = null;
175
- this.vaporwave = null;
176
- this._8d = null;
134
+ Object.keys(this.filters).forEach(key => {
135
+ this.filters[key] = key === 'volume' ? 1 : (key === 'equalizer' ? [] : null);
136
+ });
137
+
138
+ Object.keys(this.presets).forEach(key => {
139
+ this.presets[key] = null;
140
+ });
177
141
 
178
- this._filterDataTemplate.volume = 1;
179
- this._filterDataTemplate.equalizer = [];
142
+ this._pendingUpdate = false;
143
+ if (this._updateTimeout) {
144
+ clearTimeout(this._updateTimeout);
145
+ this._updateTimeout = null;
146
+ }
180
147
 
181
148
  await this.updateFilters();
182
149
  return this;
183
150
  }
184
151
 
185
152
  async updateFilters() {
186
- const filterData = {
187
- ...this._filterDataTemplate,
188
- volume: this.volume,
189
- equalizer: this.equalizer,
190
- karaoke: this.karaoke,
191
- timescale: this.timescale,
192
- tremolo: this.tremolo,
193
- vibrato: this.vibrato,
194
- rotation: this.rotation,
195
- distortion: this.distortion,
196
- channelMix: this.channelMix,
197
- lowPass: this.lowPass
198
- };
199
-
200
- this._filterDataTemplate = { ...filterData };
201
-
153
+ if (!this._pendingUpdate && this._updateTimeout) {
154
+ clearTimeout(this._updateTimeout);
155
+ this._updateTimeout = null;
156
+ return this;
157
+ }
158
+
159
+ this._pendingUpdate = false;
160
+ this._updateTimeout = null;
161
+
202
162
  await this.player.nodes.rest.updatePlayer({
203
163
  guildId: this.player.guildId,
204
- data: { filters: filterData }
164
+ data: { filters: { ...this.filters } }
205
165
  });
206
166
 
207
167
  return this;
@@ -6,24 +6,45 @@ class Node {
6
6
  static BACKOFF_MULTIPLIER = 1.5;
7
7
  static MAX_BACKOFF = 60000;
8
8
  static WS_OPEN = WebSocket.OPEN;
9
+ static WS_CLOSE_NORMAL = 1000;
9
10
 
10
11
  constructor(aqua, connOptions, options = {}) {
11
12
  this.aqua = aqua;
12
- this.host = connOptions.host || "localhost";
13
- this.name = connOptions.name || this.host;
14
- this.port = connOptions.port || 2333;
15
- this.password = connOptions.password || "youshallnotpass";
16
- this.secure = !!connOptions.secure;
17
- this.sessionId = connOptions.sessionId || null;
18
- this.regions = connOptions.regions || [];
13
+
14
+ const {
15
+ host = "localhost",
16
+ name = host,
17
+ port = 2333,
18
+ password = "youshallnotpass",
19
+ secure = false,
20
+ sessionId = null,
21
+ regions = []
22
+ } = connOptions;
23
+
24
+ this.host = host;
25
+ this.name = name;
26
+ this.port = port;
27
+ this.password = password;
28
+ this.secure = !!secure;
29
+ this.sessionId = sessionId;
30
+ this.regions = regions;
19
31
 
20
32
  this.wsUrl = `ws${this.secure ? "s" : ""}://${this.host}:${this.port}/v4/websocket`;
21
33
  this.rest = new Rest(aqua, this);
22
- this.resumeTimeout = options.resumeTimeout || 60;
23
- this.autoResume = !!options.autoResume;
24
- this.reconnectTimeout = options.reconnectTimeout || 2000;
25
- this.reconnectTries = options.reconnectTries || 3;
26
- this.infiniteReconnects = !!options.infiniteReconnects;
34
+
35
+ const {
36
+ resumeTimeout = 60,
37
+ autoResume = false,
38
+ reconnectTimeout = 2000,
39
+ reconnectTries = 3,
40
+ infiniteReconnects = false
41
+ } = options;
42
+
43
+ this.resumeTimeout = resumeTimeout;
44
+ this.autoResume = autoResume;
45
+ this.reconnectTimeout = reconnectTimeout;
46
+ this.reconnectTries = reconnectTries;
47
+ this.infiniteReconnects = infiniteReconnects;
27
48
 
28
49
  this.connected = false;
29
50
  this.info = null;
@@ -31,6 +52,13 @@ class Node {
31
52
  this.reconnectAttempted = 0;
32
53
  this.reconnectTimeoutId = null;
33
54
 
55
+ this._boundOnOpen = this._onOpen.bind(this);
56
+ this._boundOnError = this._onError.bind(this);
57
+ this._boundOnMessage = this._onMessage.bind(this);
58
+ this._boundOnClose = this._onClose.bind(this);
59
+
60
+ this._headers = this._constructHeaders();
61
+
34
62
  this.initializeStats();
35
63
  }
36
64
 
@@ -50,7 +78,7 @@ class Node {
50
78
  const headers = {
51
79
  Authorization: this.password,
52
80
  "User-Id": this.aqua.clientId,
53
- "Client-Name": `Aqua/${this.aqua.version}`
81
+ "Client-Name": `Aqua/${this.aqua.version} (https://github.com/ToddyTheNoobDud/AquaLink)`
54
82
  };
55
83
 
56
84
  if (this.sessionId) {
@@ -60,24 +88,27 @@ class Node {
60
88
  return headers;
61
89
  }
62
90
 
63
- _onOpen() {
91
+ _updateHeaders() {
92
+ this._headers = this._constructHeaders();
93
+ }
94
+
95
+ async _onOpen() {
64
96
  this.connected = true;
65
97
  this.reconnectAttempted = 0;
66
98
  this.emitDebug(`Connected to ${this.wsUrl}`);
67
99
 
68
- this.rest.makeRequest("GET", "/v4/info")
69
- .then(info => {
70
- this.info = info;
71
- if (this.autoResume && this.sessionId) {
72
- return this.resumePlayers();
73
- }
74
- })
75
- .catch(err => {
76
- this.info = null;
77
- if (!this.aqua.bypassChecks?.nodeFetchInfo) {
78
- this.emitError(`Failed to fetch node info: ${err.message}`);
79
- }
80
- });
100
+ if (this.aqua.bypassChecks?.nodeFetchInfo) return;
101
+
102
+ try {
103
+ this.info = await this.rest.makeRequest("GET", "/v4/info");
104
+
105
+ if (this.autoResume && this.sessionId) {
106
+ await this.resumePlayers();
107
+ }
108
+ } catch (err) {
109
+ this.info = null;
110
+ this.emitError(`Failed to fetch node info: ${err.message}`);
111
+ }
81
112
  }
82
113
 
83
114
  _onError(error) {
@@ -107,6 +138,7 @@ class Node {
107
138
  const player = this.aqua.players.get(payload.guildId);
108
139
  if (player) player.emit(op, payload);
109
140
  }
141
+ break;
110
142
  }
111
143
  }
112
144
 
@@ -124,7 +156,7 @@ class Node {
124
156
  scheduleReconnect(code) {
125
157
  this.clearReconnectTimeout();
126
158
 
127
- if (code === 1000) {
159
+ if (code === Node.WS_CLOSE_NORMAL) {
128
160
  return;
129
161
  }
130
162
 
@@ -166,31 +198,41 @@ class Node {
166
198
 
167
199
  async connect() {
168
200
  this.cleanupExistingConnection();
169
- this.ws = new WebSocket(this.wsUrl, { headers: this._constructHeaders(), perMessageDeflate: false });
170
- this.ws.once("open", this._onOpen.bind(this));
171
- this.ws.once("error", this._onError.bind(this));
172
- this.ws.on("message", this._onMessage.bind(this));
173
- this.ws.once("close", this._onClose.bind(this));
201
+
202
+ if (this.sessionId && !this._headers["Session-Id"]) {
203
+ this._updateHeaders();
204
+ }
205
+
206
+ this.ws = new WebSocket(this.wsUrl, {
207
+ headers: this._headers,
208
+ perMessageDeflate: false
209
+ });
210
+
211
+ this.ws.once("open", this._boundOnOpen);
212
+ this.ws.once("error", this._boundOnError);
213
+ this.ws.on("message", this._boundOnMessage);
214
+ this.ws.once("close", this._boundOnClose);
174
215
  }
175
216
 
176
217
  cleanupExistingConnection() {
177
- if (this.ws) {
178
- this.ws.removeAllListeners();
179
-
180
- if (this.ws.readyState === Node.WS_OPEN) {
181
- try {
182
- this.ws.close();
183
- } catch (err) {
184
- this.emitDebug(`Error closing WebSocket: ${err.message}`);
185
- }
218
+ if (!this.ws) return;
219
+
220
+ this.ws.removeAllListeners();
221
+
222
+ if (this.ws.readyState === Node.WS_OPEN) {
223
+ try {
224
+ this.ws.close();
225
+ } catch (err) {
226
+ this.emitDebug(`Error closing WebSocket: ${err.message}`);
186
227
  }
187
-
188
- this.ws = null;
189
228
  }
229
+
230
+ this.ws = null;
190
231
  }
191
232
 
192
233
  destroy(clean = false) {
193
234
  this.clearReconnectTimeout();
235
+
194
236
  this.cleanupExistingConnection();
195
237
 
196
238
  if (clean) {
@@ -216,6 +258,7 @@ class Node {
216
258
  async getStats() {
217
259
  try {
218
260
  const newStats = await this.rest.getStats();
261
+
219
262
  Object.assign(this.stats, newStats);
220
263
  return this.stats;
221
264
  } catch (err) {
@@ -227,55 +270,46 @@ class Node {
227
270
  _updateStats(payload) {
228
271
  if (!payload) return;
229
272
 
230
- this._updateBasicStats(payload);
231
-
232
- this._updateMemoryStats(payload.memory);
273
+ this.stats.players = payload.players;
274
+ this.stats.playingPlayers = payload.playingPlayers;
275
+ this.stats.uptime = payload.uptime;
276
+ this.stats.ping = payload.ping;
233
277
 
234
- this._updateCpuStats(payload.cpu);
235
-
236
- this._updateFrameStats(payload.frameStats);
237
- }
238
-
239
- _updateBasicStats(payload) {
240
- this.stats.players = payload.players || this.stats.players;
241
- this.stats.playingPlayers = payload.playingPlayers || this.stats.playingPlayers;
242
- this.stats.uptime = payload.uptime || this.stats.uptime;
243
- this.stats.ping = payload.ping || this.stats.ping;
278
+ this._updateMemoryStats(payload.memory || {});
279
+ this._updateCpuStats(payload.cpu || {});
280
+ this._updateFrameStats(payload.frameStats || {});
244
281
  }
245
282
 
246
- _updateMemoryStats(memory = {}) {
247
- const allocated = memory.allocated || this.stats.memory.allocated;
248
- const free = memory.free || this.stats.memory.free;
249
- const used = memory.used || this.stats.memory.used;
283
+ _updateMemoryStats(memory) {
284
+ const memoryStats = this.stats.memory;
250
285
 
251
- this.stats.memory.free = free;
252
- this.stats.memory.used = used;
253
- this.stats.memory.allocated = allocated;
254
- this.stats.memory.reservable = memory.reservable || this.stats.memory.reservable;
286
+ memoryStats.free = memory.free;
287
+ memoryStats.used = memory.used;
288
+ memoryStats.allocated = memory.allocated;
289
+ memoryStats.reservable = memory.reservable;
255
290
 
256
- if (allocated) {
257
- this.stats.memory.freePercentage = (free / allocated) * 100;
258
- this.stats.memory.usedPercentage = (used / allocated) * 100;
259
- }
291
+ memoryStats.freePercentage = (memoryStats.free / memoryStats.allocated) * 100;
292
+ memoryStats.usedPercentage = (memoryStats.used / memoryStats.allocated) * 100;
260
293
  }
261
294
 
262
- _updateCpuStats(cpu = {}) {
263
- const cores = cpu.cores || this.stats.cpu.cores;
295
+ _updateCpuStats(cpu) {
296
+ const cpuStats = this.stats.cpu;
264
297
 
265
- this.stats.cpu.cores = cores;
266
- this.stats.cpu.systemLoad = cpu.systemLoad || this.stats.cpu.systemLoad;
267
- this.stats.cpu.lavalinkLoad = cpu.lavalinkLoad || this.stats.cpu.lavalinkLoad;
298
+ cpuStats.cores = cpu.cores;
299
+ cpuStats.systemLoad = cpu.systemLoad;
300
+ cpuStats.lavalinkLoad = cpu.lavalinkLoad;
268
301
 
269
- if (cores) {
270
- this.stats.cpu.lavalinkLoadPercentage = (cpu.lavalinkLoad / cores) * 100;
271
- }
302
+ cpuStats.lavalinkLoadPercentage = (cpuStats.lavalinkLoad / cpuStats.cores) * 100;
272
303
  }
273
304
 
274
- _updateFrameStats(frameStats = {}) {
305
+ _updateFrameStats(frameStats) {
306
+ const stats = this.stats.frameStats;
307
+
275
308
  if (!frameStats) return;
276
- this.stats.frameStats.sent = frameStats.sent || this.stats.frameStats.sent;
277
- this.stats.frameStats.nulled = frameStats.nulled || this.stats.frameStats.nulled;
278
- this.stats.frameStats.deficit = frameStats.deficit || this.stats.frameStats.deficit;
309
+
310
+ stats.sent = frameStats.sent;
311
+ stats.nulled = frameStats.nulled;
312
+ stats.deficit = frameStats.deficit;
279
313
  }
280
314
 
281
315
  _handleReadyOp(payload) {
@@ -286,6 +320,7 @@ class Node {
286
320
 
287
321
  this.sessionId = payload.sessionId;
288
322
  this.rest.setSessionId(payload.sessionId);
323
+ this._updateHeaders();
289
324
  this.aqua.emit("nodeConnect", this);
290
325
  }
291
326
 
@@ -35,10 +35,10 @@ class Player extends EventEmitter {
35
35
  this.filters = new Filters(this);
36
36
  this.queue = new Queue();
37
37
 
38
- this.volume = Math.max(0, Math.min(options.defaultVolume ?? 100, 200));
38
+ this.volume = Math.min(Math.max(options.defaultVolume ?? 100, 0), 200);
39
39
  this.loop = Player.validModes.has(options.loop) ? options.loop : Player.LOOP_MODES.NONE;
40
- this.shouldDeleteMessage = Boolean(options.shouldDeleteMessage);
41
- this.leaveOnEnd = Boolean(options.leaveOnEnd);
40
+ this.shouldDeleteMessage = !!this.aqua.options.shouldDeleteMessage;
41
+ this.leaveOnEnd = !!this.aqua.options.leaveOnEnd;
42
42
 
43
43
  this.previousTracks = new Array(50);
44
44
  this.previousTracksIndex = 0;
@@ -55,6 +55,9 @@ class Player extends EventEmitter {
55
55
  this.isAutoplayEnabled = false;
56
56
  this.isAutoplay = false;
57
57
 
58
+ this._pendingUpdates = {};
59
+ this._updateTimeout = null;
60
+
58
61
  this._boundHandlers = {
59
62
  playerUpdate: this._handlePlayerUpdate.bind(this),
60
63
  event: this._handleEvent.bind(this)
@@ -63,7 +66,7 @@ class Player extends EventEmitter {
63
66
  this.on("playerUpdate", this._boundHandlers.playerUpdate);
64
67
  this.on("event", this._boundHandlers.event);
65
68
 
66
- this._dataStore = null;
69
+ this._dataStore = new Map();
67
70
  }
68
71
 
69
72
  get previous() {
@@ -75,8 +78,32 @@ class Player extends EventEmitter {
75
78
  return this.current;
76
79
  }
77
80
 
81
+ batchUpdatePlayer(data, immediate = false) {
82
+ this._pendingUpdates = { ...this._pendingUpdates, ...data };
83
+
84
+ if (this._updateTimeout) {
85
+ clearTimeout(this._updateTimeout);
86
+ this._updateTimeout = null;
87
+ }
88
+
89
+ if (immediate || data.track) {
90
+ const updates = this._pendingUpdates;
91
+ this._pendingUpdates = {};
92
+ return this.updatePlayer(updates);
93
+ }
94
+
95
+ this._updateTimeout = setTimeout(() => {
96
+ const updates = this._pendingUpdates;
97
+ this._pendingUpdates = {};
98
+ this.updatePlayer(updates);
99
+ this._updateTimeout = null;
100
+ }, 50);
101
+
102
+ return Promise.resolve();
103
+ }
104
+
78
105
  async autoplay(player) {
79
- if (!player) throw new Error("Quick Fix: player.autoplay(player)");
106
+ if (!player) throw new Error("Player is undefined. const player = aqua.plaerers.get(guildId);");
80
107
  if (!this.isAutoplayEnabled) {
81
108
  this.aqua.emit("debug", this.guildId, "Autoplay is disabled.");
82
109
  return this;
@@ -113,7 +140,8 @@ class Player extends EventEmitter {
113
140
  const { query, source } = result;
114
141
  const response = await this.aqua.resolve({ query, source, requester });
115
142
 
116
- if (!response?.tracks?.length || ["error", "empty", "LOAD_FAILED", "NO_MATCHES"].includes(response.loadType)) {
143
+ const failTypes = new Set(["error", "empty", "LOAD_FAILED", "NO_MATCHES"]);
144
+ if (!response?.tracks?.length || failTypes.has(response.loadType)) {
117
145
  return this.stop();
118
146
  }
119
147
 
@@ -145,12 +173,13 @@ class Player extends EventEmitter {
145
173
  }
146
174
 
147
175
  _handlePlayerUpdate({ state }) {
148
- if (state) {
149
- const { position, timestamp, ping } = state;
150
- if (position !== undefined) this.position = position;
151
- if (timestamp !== undefined) this.timestamp = timestamp;
152
- if (ping !== undefined) this.ping = ping;
153
- }
176
+ if (!state) return;
177
+
178
+ const { position, timestamp, ping } = state;
179
+ if (position !== undefined) this.position = position;
180
+ if (timestamp !== undefined) this.timestamp = timestamp;
181
+ if (ping !== undefined) this.ping = ping;
182
+
154
183
  this.aqua.emit("playerUpdate", this, { state });
155
184
  }
156
185
 
@@ -172,7 +201,7 @@ class Player extends EventEmitter {
172
201
  this.position = 0;
173
202
 
174
203
  this.aqua.emit("debug", this.guildId, `Playing track: ${this.current.track}`);
175
- return this.updatePlayer({ track: { encoded: this.current.track } });
204
+ return this.batchUpdatePlayer({ track: { encoded: this.current.track } }, true);
176
205
  }
177
206
 
178
207
  connect({ voiceChannel, deaf = true, mute = false } = {}) {
@@ -192,6 +221,12 @@ class Player extends EventEmitter {
192
221
  destroy() {
193
222
  if (!this.connected) return this;
194
223
 
224
+ if (this._updateTimeout) {
225
+ clearTimeout(this._updateTimeout);
226
+ this._updateTimeout = null;
227
+ this._pendingUpdates = {};
228
+ }
229
+
195
230
  this.disconnect();
196
231
  if (this.nowPlayingMessage) {
197
232
  this.nowPlayingMessage.delete().catch(() => {});
@@ -220,22 +255,36 @@ class Player extends EventEmitter {
220
255
  pause(paused) {
221
256
  if (this.paused === paused) return this;
222
257
  this.paused = paused;
223
- this.updatePlayer({ paused });
258
+ this.batchUpdatePlayer({ paused });
224
259
  return this;
225
260
  }
226
261
 
262
+ async getLyrics(options = {}) {
263
+ const { query = null, useCurrentTrack = true } = options;
264
+
265
+ if (query) {
266
+ return this.nodes.rest.getLyrics({ track: { info: { title: query }, search: true } }) || null;
267
+ }
268
+
269
+ if (useCurrentTrack && this.playing) {
270
+ return this.nodes.rest.getLyrics({ track: { encoded: this.current.track, guild_id: this.guildId } }) || null;
271
+ }
272
+
273
+ return null;
274
+ }
275
+
227
276
  async searchLyrics(query) {
228
- return query ? this.nodes.rest.getLyrics({ track: { info: { title: query }, search: true } }) || null : null;
277
+ return this.getLyrics({ query });
229
278
  }
230
279
 
231
280
  async lyrics() {
232
- return this.playing ? this.nodes.rest.getLyrics({ track: { encoded: this.current.track, guild_id: this.guildId } }) || null : null;
281
+ return this.getLyrics({ useCurrentTrack: true });
233
282
  }
234
283
 
235
284
  seek(position) {
236
285
  if (!this.playing) return this;
237
286
  this.position += position;
238
- this.updatePlayer({ position: this.position });
287
+ this.batchUpdatePlayer({ position: this.position });
239
288
  return this;
240
289
  }
241
290
 
@@ -243,27 +292,27 @@ class Player extends EventEmitter {
243
292
  if (!this.playing) return this;
244
293
  this.playing = false;
245
294
  this.position = 0;
246
- this.updatePlayer({ track: { encoded: null } });
295
+ this.batchUpdatePlayer({ track: { encoded: null } }, true);
247
296
  return this;
248
297
  }
249
298
 
250
299
  setVolume(volume) {
251
300
  if (volume < 0 || volume > 200) throw new Error("Volume must be between 0 and 200.");
252
301
  this.volume = volume;
253
- this.updatePlayer({ volume });
302
+ this.batchUpdatePlayer({ volume });
254
303
  return this;
255
304
  }
256
305
 
257
306
  setLoop(mode) {
258
307
  if (!Player.validModes.has(mode)) throw new Error("Loop mode must be 'none', 'track', or 'queue'.");
259
308
  this.loop = mode;
260
- this.updatePlayer({ loop: mode });
309
+ this.batchUpdatePlayer({ loop: mode });
261
310
  return this;
262
311
  }
263
312
 
264
313
  setTextChannel(channel) {
265
314
  this.textChannel = channel;
266
- this.updatePlayer({ text_channel: channel });
315
+ this.batchUpdatePlayer({ text_channel: channel });
267
316
  return this;
268
317
  }
269
318
 
@@ -294,11 +343,14 @@ class Player extends EventEmitter {
294
343
  }
295
344
 
296
345
  shuffle() {
297
- const { queue } = this;
298
- for (let i = queue.length - 1; i > 0; i--) {
346
+ const queue = this.queue;
347
+ const length = queue.length;
348
+
349
+ for (let i = length - 1; i > 0; i--) {
299
350
  const j = Math.floor(Math.random() * (i + 1));
300
351
  [queue[i], queue[j]] = [queue[j], queue[i]];
301
352
  }
353
+
302
354
  return this;
303
355
  }
304
356
 
@@ -327,14 +379,16 @@ class Player extends EventEmitter {
327
379
  if (this.shouldDeleteMessage && this.nowPlayingMessage) {
328
380
  try {
329
381
  await this.nowPlayingMessage.delete();
330
- this.nowPlayingMessage = null;
331
382
  } catch (error) {
332
383
  console.error("Error deleting now playing message:", error);
384
+ } finally {
385
+ this.nowPlayingMessage = null;
333
386
  }
334
387
  }
335
388
 
336
389
  const reason = payload.reason;
337
- if (reason === "LOAD_FAILED" || reason === "CLEANUP") {
390
+ const failureReasons = new Set(["LOAD_FAILED", "CLEANUP"]);
391
+ if (failureReasons.has(reason)) {
338
392
  if (!player.queue.length) {
339
393
  this.clearData();
340
394
  this.aqua.emit("queueEnd", player);
@@ -345,10 +399,13 @@ class Player extends EventEmitter {
345
399
  return;
346
400
  }
347
401
 
348
- if (this.loop === Player.LOOP_MODES.TRACK) {
349
- player.queue.unshift(track);
350
- } else if (this.loop === Player.LOOP_MODES.QUEUE) {
351
- player.queue.push(track);
402
+ switch (this.loop) {
403
+ case Player.LOOP_MODES.TRACK:
404
+ player.queue.unshift(track);
405
+ break;
406
+ case Player.LOOP_MODES.QUEUE:
407
+ player.queue.push(track);
408
+ break;
352
409
  }
353
410
 
354
411
  if (player.queue.isEmpty()) {
@@ -381,7 +438,8 @@ class Player extends EventEmitter {
381
438
  async socketClosed(player, payload) {
382
439
  const { code, guildId } = payload || {};
383
440
 
384
- if (code === 4015 || code === 4009) {
441
+ const reconnectCodes = new Set([4015, 4009]);
442
+ if (reconnectCodes.has(code)) {
385
443
  this.send({
386
444
  guild_id: guildId,
387
445
  channel_id: this.voiceChannel,
@@ -400,17 +458,16 @@ class Player extends EventEmitter {
400
458
  }
401
459
 
402
460
  set(key, value) {
403
- if (!this._dataStore) this._dataStore = new Map();
404
461
  this._dataStore.set(key, value);
405
462
  }
406
463
 
407
464
  get(key) {
408
- return this._dataStore ? this._dataStore.get(key) : undefined;
465
+ return this._dataStore.get(key);
409
466
  }
410
467
 
411
468
  clearData() {
412
469
  if (this.previousTracks) this.previousTracksCount = 0;
413
- this._dataStore = null;
470
+ this._dataStore.clear();
414
471
  return this;
415
472
  }
416
473
 
@@ -38,23 +38,32 @@ class Queue extends Array {
38
38
  remove(track) {
39
39
  const index = this.indexOf(track);
40
40
  if (index !== -1) {
41
- this.splice(index, 1);
41
+ if (index === this.length - 1) {
42
+ this.pop();
43
+ } else {
44
+ this.splice(index, 1);
45
+ }
46
+ return true;
42
47
  }
48
+ return false;
43
49
  }
44
50
 
45
51
  // Clear all tracks from the queue
46
52
  clear() {
47
- this.length = 0; // More efficient memory handling
53
+ this.length = 0;
48
54
  }
49
55
 
50
56
  // Shuffle the tracks in the queue
51
57
  shuffle() {
52
- for (let i = this.length - 1; i > 0; i--) {
58
+ const length = this.length;
59
+ for (let i = length - 1; i > 0; i--) {
53
60
  const j = Math.floor(Math.random() * (i + 1));
54
- [this[i], this[j]] = [this[j], this[i]];
61
+ if (i !== j) {
62
+ [this[i], this[j]] = [this[j], this[i]];
63
+ }
55
64
  }
65
+ return this;
56
66
  }
57
-
58
67
  // Peek at the element at the front of the queue without removing it
59
68
  peek() {
60
69
  return this.first;
@@ -62,7 +71,7 @@ class Queue extends Array {
62
71
 
63
72
  // Get all tracks in the queue as an array
64
73
  toArray() {
65
- return [...this]; // Create a shallow copy of the queue
74
+ return this.slice();
66
75
  }
67
76
 
68
77
  /**
@@ -71,9 +80,8 @@ class Queue extends Array {
71
80
  * @returns {*} The track at the specified index or null if out of bounds.
72
81
  */
73
82
  at(index) {
74
- return this[index] || null; // Return null if index is out of bounds
83
+ return (index >= 0 && index < this.length) ? this[index] : null;
75
84
  }
76
-
77
85
  // Remove the first track from the queue
78
86
  dequeue() {
79
87
  return this.shift(); // Removes and returns the first element
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
+
2
3
  const https = require("https");
3
4
  const http = require("http");
4
5
 
5
6
  let http2;
7
+
6
8
  try {
7
9
  http2 = require("http2");
8
10
  } catch (e) {
@@ -20,14 +22,20 @@ class Rest {
20
22
  };
21
23
  this.secure = secure;
22
24
  this.timeout = timeout;
23
-
24
- this.client = secure ? (http2 || https) : http;
25
+
26
+ this.client = secure ? http2 || https : http;
25
27
  }
26
28
 
27
29
  setSessionId(sessionId) {
28
30
  this.sessionId = sessionId;
29
31
  }
30
32
 
33
+ validateSessionId() {
34
+ if (!this.sessionId) {
35
+ throw new Error("Session ID is required but not set.");
36
+ }
37
+ }
38
+
31
39
  async makeRequest(method, endpoint, body = null) {
32
40
  const url = `${this.baseUrl}${endpoint}`;
33
41
  const options = {
@@ -38,36 +46,36 @@ class Rest {
38
46
 
39
47
  return new Promise((resolve, reject) => {
40
48
  const req = this.client.request(url, options, (res) => {
41
- res.setEncoding('utf8');
42
-
43
- let data = '';
44
-
49
+ let data = "";
50
+
51
+ res.setEncoding("utf8");
52
+
45
53
  res.on("data", (chunk) => {
46
54
  data += chunk;
47
55
  });
48
-
56
+
49
57
  res.on("end", () => {
50
- if (res.statusCode >= 200 && res.statusCode < 300) {
58
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
51
59
  if (!data) {
52
60
  resolve(null);
53
61
  return;
54
62
  }
55
-
56
63
  try {
57
64
  resolve(JSON.parse(data));
58
- } catch (error) {
59
- reject(new Error(`Failed to parse response: ${error.message}`));
65
+ } catch (err) {
66
+ reject(new Error(`Failed to parse response: ${err.message}`));
60
67
  }
61
68
  } else {
62
- reject(new Error(`Request failed with status ${res.statusCode}: ${res.statusMessage || 'Unknown error'}`));
69
+ const errorMessage = `Request failed with status ${res.statusCode}: ${res.statusMessage || "Unknown error"}`;
70
+ reject(new Error(errorMessage));
63
71
  }
64
72
  });
65
73
  });
66
74
 
67
- req.on("error", (error) => reject(new Error(`Request failed (${method} ${url}): ${error.message}`)));
75
+ req.on("error", (err) => reject(new Error(`Request failed (${method} ${url}): ${err.message}`)));
68
76
  req.on("timeout", () => {
69
77
  req.destroy();
70
- reject(new Error(`Request timeout after ${this.timeout}ms (${method} ${url})`));
78
+ reject(new Error(`Request timed out after ${this.timeout}ms (${method} ${url})`));
71
79
  });
72
80
 
73
81
  if (body) {
@@ -77,79 +85,86 @@ class Rest {
77
85
  });
78
86
  }
79
87
 
80
- validateSessionId() {
81
- if (!this.sessionId) throw new Error("Session ID is required but not set.");
82
- }
83
-
84
88
  async updatePlayer({ guildId, data }) {
85
- if (data.track && data.track.encoded && data.track.identifier) {
86
- throw new Error("Cannot provide both 'encoded' and 'identifier' for track");
89
+ if (data.track?.encoded && data.track?.identifier) {
90
+ throw new Error("You cannot provide both 'encoded' and 'identifier' for a track.");
87
91
  }
88
-
92
+
89
93
  this.validateSessionId();
90
- return this.makeRequest(
91
- "PATCH",
92
- `/${this.version}/sessions/${this.sessionId}/players/${guildId}?noReplace=false`,
93
- data
94
- );
94
+
95
+ const endpoint = `/${this.version}/sessions/${this.sessionId}/players/${guildId}?noReplace=false`;
96
+ return this.makeRequest("PATCH", endpoint, data);
95
97
  }
96
98
 
97
99
  async getPlayers() {
98
100
  this.validateSessionId();
99
- return this.makeRequest("GET", `/${this.version}/sessions/${this.sessionId}/players`);
101
+ const endpoint = `/${this.version}/sessions/${this.sessionId}/players`;
102
+ return this.makeRequest("GET", endpoint);
100
103
  }
101
104
 
102
105
  async destroyPlayer(guildId) {
103
106
  this.validateSessionId();
104
- return this.makeRequest("DELETE", `/${this.version}/sessions/${this.sessionId}/players/${guildId}`);
107
+ const endpoint = `/${this.version}/sessions/${this.sessionId}/players/${guildId}`;
108
+ return this.makeRequest("DELETE", endpoint);
105
109
  }
106
110
 
107
111
  async getTracks(identifier) {
108
- return this.makeRequest("GET", `/${this.version}/loadtracks?identifier=${encodeURIComponent(identifier)}`);
112
+ const endpoint = `/${this.version}/loadtracks?identifier=${encodeURIComponent(identifier)}`;
113
+ return this.makeRequest("GET", endpoint);
109
114
  }
110
115
 
111
116
  async decodeTrack(track) {
112
- return this.makeRequest("GET", `/${this.version}/decodetrack?encodedTrack=${encodeURIComponent(track)}`);
117
+ const endpoint = `/${this.version}/decodetrack?encodedTrack=${encodeURIComponent(track)}`;
118
+ return this.makeRequest("GET", endpoint);
113
119
  }
114
120
 
115
121
  async decodeTracks(tracks) {
116
- return this.makeRequest("POST", `/${this.version}/decodetracks`, tracks);
122
+ const endpoint = `/${this.version}/decodetracks`;
123
+ return this.makeRequest("POST", endpoint, tracks);
117
124
  }
118
125
 
119
126
  async getStats() {
120
- return this.makeRequest("GET", `/${this.version}/stats`);
127
+ const endpoint = `/${this.version}/stats`;
128
+ return this.makeRequest("GET", endpoint);
121
129
  }
122
130
 
123
131
  async getInfo() {
124
- return this.makeRequest("GET", `/${this.version}/info`);
132
+ const endpoint = `/${this.version}/info`;
133
+ return this.makeRequest("GET", endpoint);
125
134
  }
126
135
 
127
136
  async getRoutePlannerStatus() {
128
- return this.makeRequest("GET", `/${this.version}/routeplanner/status`);
137
+ const endpoint = `/${this.version}/routeplanner/status`;
138
+ return this.makeRequest("GET", endpoint);
129
139
  }
130
140
 
131
141
  async getRoutePlannerAddress(address) {
132
- return this.makeRequest("POST", `/${this.version}/routeplanner/free/address`, { address });
142
+ const endpoint = `/${this.version}/routeplanner/free/address`;
143
+ return this.makeRequest("POST", endpoint, { address });
133
144
  }
134
145
 
135
146
  async getLyrics({ track }) {
136
147
  if (!track) return null;
137
-
148
+
138
149
  try {
139
150
  if (track.search) {
140
151
  const query = encodeURIComponent(track.info.title);
141
152
  try {
142
- const res = await this.makeRequest("GET", `/${this.version}/lyrics/search?query=${query}&source=genius`);
153
+ const res = await this.makeRequest(
154
+ "GET",
155
+ `/${this.version}/lyrics/search?query=${query}&source=genius`
156
+ );
143
157
  if (res) return res;
144
- } catch (err) {}
158
+ } catch (_) {
159
+ // Silently handle any errors.
160
+ }
145
161
  } else {
146
162
  this.validateSessionId();
147
163
  return await this.makeRequest(
148
- "GET",
164
+ "GET",
149
165
  `/${this.version}/sessions/${this.sessionId}/players/${track.guild_id}/track/lyrics?skipTrackSource=false`
150
166
  );
151
167
  }
152
-
153
168
  } catch (error) {
154
169
  console.error("Failed to fetch lyrics:", error.message);
155
170
  return null;
@@ -10,7 +10,8 @@ class Track {
10
10
  this.identifier = info.identifier || '';
11
11
  this.isSeekable = Boolean(info.isSeekable);
12
12
  this.author = info.author || '';
13
- this.length = info.length | 0;
13
+ this.length = info.length || 0;
14
+ this.duration = info.length || 0;
14
15
  this.isStream = Boolean(info.isStream);
15
16
  this.title = info.title || '';
16
17
  this.uri = info.uri || '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aqualink",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "description": "An Lavalink client, focused in pure performance and features",
5
5
  "main": "build/index.js",
6
6
  "types": "index.d.ts",