aqualink 2.6.2 → 2.6.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.
@@ -349,26 +349,19 @@ class Aqua extends EventEmitter {
349
349
 
350
350
  async savePlayer(filePath = "./AquaPlayers.json") {
351
351
  const data = Array.from(this.players.values()).map(player => ({
352
- guildId: player.guildId,
353
- textChannel: player.textChannel,
354
- voiceChannel: player.voiceChannel,
355
- track: player.current ? {
356
- identifier: player.current.identifier,
357
- author: player.current.author,
358
- title: player.current.title,
359
- uri: player.current.uri,
360
- sourceName: player.current.sourceName,
361
- artworkUrl: player.current.artworkUrl,
362
- duration: player.current.duration,
363
- position: player.position,
364
- } : null,
365
- requester: player.requester || player.current?.requester,
366
- volume: player.volume,
367
- paused: player.paused
352
+ g: player.guildId,
353
+ t: player.textChannel,
354
+ v: player.voiceChannel,
355
+ u: player.current?.uri || null,
356
+ p: player.position || 0,
357
+ ts: player.timestamp || 0,
358
+ q: player.queue?.tracks?.map(tr => tr.uri).slice(0, 5) || [],
359
+ r: player.requester || player.current?.requester,
360
+ vol: player.volume,
361
+ pa: player.paused
368
362
  }));
369
- console.log(`Saving ${data.length} players to ${filePath}`);
370
- await fs.writeJSON(filePath, data, { spaces: 2 });
371
- this.emit("debug", "Aqua", `Saved players to ${filePath}`);
363
+ await fs.writeFile(filePath, JSON.stringify(data), "utf8");
364
+ this.emit("debug", "Aqua", `Saved ${data.length} players to ${filePath}`);
372
365
  }
373
366
 
374
367
  async waitForFirstNode() {
@@ -386,45 +379,60 @@ class Aqua extends EventEmitter {
386
379
  }
387
380
 
388
381
  async loadPlayers(filePath = "./AquaPlayers.json") {
389
- if (!fs.existsSync(filePath)) {
390
- this.emit("debug", "Aqua", `No player data found at ${filePath}`);
382
+ try {
383
+ await fs.promises.access(filePath);
384
+ } catch {
385
+ this.emit("debug", "Aqua", `File ${filePath} does not exist, skipping load.`);
391
386
  return;
392
387
  }
393
388
  try {
394
389
  await this.waitForFirstNode();
395
- const data = await fs.readJSON(filePath);
396
- for (const playerData of data) {
397
- const { guildId, textChannel, voiceChannel, track, volume, paused, requester } = playerData;
398
- let player = this.players.get(guildId);
399
-
390
+ const data = JSON.parse(await fs.readFile(filePath, "utf8"));
391
+ for (const p of data) {
392
+ let player = this.players.get(p.g);
400
393
  if (!player) {
401
394
  player = await this.createConnection({
402
- guildId: guildId,
403
- textChannel: textChannel,
404
- voiceChannel: voiceChannel,
405
- defaultVolume: volume || 65,
395
+ guildId: p.g,
396
+ textChannel: p.t,
397
+ voiceChannel: p.v,
398
+ defaultVolume: p.vol || 65,
406
399
  deaf: true
407
400
  });
408
401
  }
409
402
 
410
- if (track && player) {
411
- const resolved = await this.resolve({ query: track.uri, requester });
403
+ if (p.u && player) {
404
+ const resolved = await this.resolve({ query: p.u, requester: p.r });
412
405
  if (resolved.tracks && resolved.tracks.length > 0) {
413
406
  player.queue.add(resolved.tracks[0]);
414
- player.position = track.position || 0;
415
- } else {
416
- this.emit("debug", "Aqua", `Could not resolve track for guild ${guildId}: ${track.uri}`);
407
+ player.position = p.p || 0;
408
+ if (typeof p.ts === "number") {
409
+ player.timestamp = p.ts;
410
+ }
411
+ }
412
+ }
413
+ if (Array.isArray(p.q) && player) {
414
+ for (const uri of p.q) {
415
+ if (!p.u || uri !== p.u) {
416
+ const resolved = await this.resolve({ query: uri, requester: p.r });
417
+ if (resolved.tracks && resolved.tracks.length > 0) {
418
+ player.queue.add(resolved.tracks[0]);
419
+ player.position = p.p || 0;
420
+ if (typeof p.ts === "number") {
421
+ player.timestamp = p.ts;
422
+ }
423
+ }
424
+ }
417
425
  }
418
426
  }
419
-
420
427
  if (player) {
421
- player.paused = paused || false;
428
+ player.paused = !!p.pa;
422
429
  if (!player.playing && !player.paused && player.queue.size > 0) {
423
430
  player.play();
424
431
  }
425
432
  }
426
433
  }
427
- this.emit("debug", "Aqua", `Loaded players from ${filePath}`);
434
+ await fs.writeFile(filePath, "[]", "utf8");
435
+ this.emit("debug", "Aqua", `Loaded players from ${filePath} and cleared its content.`);
428
436
  } catch (error) {
429
437
  console.error(`Failed to load players from ${filePath}:`, error);
430
438
  }
@@ -14,29 +14,27 @@ class Connection {
14
14
  this.region = null;
15
15
  this.selfDeaf = false;
16
16
  this.selfMute = false;
17
-
18
17
  }
19
18
 
20
19
  setServerUpdate(data) {
21
- if (!data || !data.endpoint) return;
20
+ if (!data?.endpoint) return;
22
21
 
23
22
  const { endpoint, token } = data;
24
-
25
- const [newRegion] = endpoint.split('.');
23
+ const newRegion = endpoint.split('.')[0];
26
24
  if (!newRegion) return;
27
25
 
28
26
  if (this.region !== newRegion) {
27
+ const oldRegion = this.region;
29
28
  this.endpoint = endpoint;
30
29
  this.token = token;
31
30
  this.region = newRegion;
32
31
 
33
- this.aqua.emit(
34
- "debug",
35
- `[Player ${this.guildId} - CONNECTION] Voice Server: ${
36
- this.region ? `Changed from ${this.region} to ${newRegion}` : newRegion
37
- }`
38
- );
39
-
32
+ this.aqua.emit(
33
+ "debug",
34
+ `[Player ${this.guildId} - CONNECTION] Voice Server: ${
35
+ oldRegion ? `Changed from ${oldRegion} to ${newRegion}` : newRegion
36
+ }`
37
+ );
40
38
 
41
39
  this._updatePlayerVoiceData();
42
40
  }
@@ -49,7 +47,6 @@ class Connection {
49
47
  }
50
48
 
51
49
  const { channel_id, session_id, self_deaf, self_mute } = data;
52
-
53
50
  if (!channel_id || !session_id) {
54
51
  this.player?.destroy();
55
52
  return;
@@ -60,8 +57,8 @@ class Connection {
60
57
  this.voiceChannel = channel_id;
61
58
  }
62
59
 
63
- this.selfDeaf = !!self_deaf;
64
- this.selfMute = !!self_mute;
60
+ this.selfDeaf = Boolean(self_deaf);
61
+ this.selfMute = Boolean(self_mute);
65
62
  this.sessionId = session_id;
66
63
  }
67
64
 
@@ -73,6 +70,7 @@ class Connection {
73
70
  endpoint: this.endpoint,
74
71
  token: this.token
75
72
  };
73
+
76
74
  try {
77
75
  this.nodes.rest.updatePlayer({
78
76
  guildId: this.guildId,
@@ -82,15 +80,13 @@ class Connection {
82
80
  }
83
81
  });
84
82
  } catch (error) {
85
- if (this.aqua.listenerCount('apiError') > 0) {
86
- this.aqua.emit("apiError", "updatePlayer", {
87
- error,
88
- guildId: this.guildId,
89
- voiceData
90
- });
91
- }
83
+ this.aqua.emit("apiError", "updatePlayer", {
84
+ error,
85
+ guildId: this.guildId,
86
+ voiceData
87
+ });
92
88
  }
93
89
  }
94
90
  }
95
91
 
96
- module.exports = Connection;
92
+ module.exports = Connection;
@@ -6,21 +6,75 @@ const Queue = require("./Queue");
6
6
  const Filters = require("./Filters");
7
7
  const { spAutoPlay, scAutoPlay } = require('../handlers/autoplay');
8
8
 
9
- class Player extends EventEmitter {
10
- static LOOP_MODES = Object.freeze({
11
- NONE: "none", TRACK: "track", QUEUE: "queue"
12
- });
9
+ const LOOP_MODES = Object.freeze({
10
+ NONE: "none", TRACK: "track", QUEUE: "queue"
11
+ });
12
+
13
+ const EVENT_HANDLERS = Object.freeze({
14
+ TrackStartEvent: "trackStart",
15
+ TrackEndEvent: "trackEnd",
16
+ TrackExceptionEvent: "trackError",
17
+ TrackStuckEvent: "trackStuck",
18
+ TrackChangeEvent: "trackChange",
19
+ WebSocketClosedEvent: "socketClosed",
20
+ });
21
+
22
+ const VALID_MODES = new Set(Object.values(LOOP_MODES));
23
+ const FAILURE_REASONS = new Set(["LOAD_FAILED", "CLEANUP"]);
24
+ const RECONNECT_CODES = new Set([4015, 4009]);
25
+ const FAIL_LOAD_TYPES = new Set(["error", "empty", "LOAD_FAILED", "NO_MATCHES"]);
26
+
27
+ class UpdateBatcher {
28
+ constructor(player) {
29
+ this.player = player;
30
+ this.pendingUpdates = {};
31
+ this.timeout = null;
32
+ this.hasUpdates = false;
33
+ }
34
+
35
+ batch(data, immediate = false) {
36
+ Object.assign(this.pendingUpdates, data);
37
+ this.hasUpdates = true;
38
+
39
+ if (this.timeout) {
40
+ clearTimeout(this.timeout);
41
+ this.timeout = null;
42
+ }
43
+
44
+ if (immediate || data.track) {
45
+ const updates = this.pendingUpdates;
46
+ this.pendingUpdates = {};
47
+ this.hasUpdates = false;
48
+ return this.player.updatePlayer(updates);
49
+ }
50
+
51
+ this.timeout = setTimeout(() => {
52
+ if (this.hasUpdates) {
53
+ const updates = this.pendingUpdates;
54
+ this.pendingUpdates = {};
55
+ this.hasUpdates = false;
56
+ this.player.updatePlayer(updates);
57
+ }
58
+ this.timeout = null;
59
+ }, 32);
60
+
61
+ return Promise.resolve();
62
+ }
13
63
 
14
- static EVENT_HANDLERS = Object.freeze({
15
- TrackStartEvent: "trackStart",
16
- TrackEndEvent: "trackEnd",
17
- TrackExceptionEvent: "trackError",
18
- TrackStuckEvent: "trackStuck",
19
- TrackChangeEvent: "trackChange",
20
- WebSocketClosedEvent: "socketClosed"
21
- });
64
+ destroy() {
65
+ if (this.timeout) {
66
+ clearTimeout(this.timeout);
67
+ this.timeout = null;
68
+ }
69
+ this.pendingUpdates = {};
70
+ this.hasUpdates = false;
71
+ }
72
+ }
22
73
 
23
- static validModes = new Set(Object.values(Player.LOOP_MODES));
74
+ class Player extends EventEmitter {
75
+ static LOOP_MODES = LOOP_MODES;
76
+ static EVENT_HANDLERS = EVENT_HANDLERS;
77
+ static validModes = VALID_MODES;
24
78
 
25
79
  constructor(aqua, nodes, options = {}) {
26
80
  super();
@@ -34,10 +88,12 @@ class Player extends EventEmitter {
34
88
  this.filters = new Filters(this);
35
89
  this.queue = new Queue();
36
90
 
37
- this.volume = Math.min(Math.max(options.defaultVolume ?? 100, 0), 200);
38
- this.loop = Player.validModes.has(options.loop) ? options.loop : Player.LOOP_MODES.NONE;
39
- this.shouldDeleteMessage = !!this.aqua.options.shouldDeleteMessage;
40
- this.leaveOnEnd = !!this.aqua.options.leaveOnEnd;
91
+ const vol = options.defaultVolume ?? 100;
92
+ this.volume = vol < 0 ? 0 : vol > 200 ? 200 : vol;
93
+
94
+ this.loop = VALID_MODES.has(options.loop) ? options.loop : LOOP_MODES.NONE;
95
+ this.shouldDeleteMessage = Boolean(this.aqua.options.shouldDeleteMessage);
96
+ this.leaveOnEnd = Boolean(this.aqua.options.leaveOnEnd);
41
97
 
42
98
  this.previousTracks = new Array(50);
43
99
  this.previousTracksIndex = 0;
@@ -54,32 +110,36 @@ class Player extends EventEmitter {
54
110
  this.isAutoplayEnabled = false;
55
111
  this.isAutoplay = false;
56
112
 
57
- this._pendingUpdates = {};
58
- this._updateTimeout = null;
113
+ this._updateBatcher = new UpdateBatcher(this);
59
114
  this._dataStore = new Map();
60
115
 
61
- this.on("playerUpdate", (packet) => {
62
- this.position = packet.state.position;
63
- this.connected = packet.state.connected;
64
- this.ping = packet.state.ping;
65
- this.timestamp = packet.state.timestamp;
116
+ this._handlePlayerUpdate = this._handlePlayerUpdate.bind(this);
117
+ this._handleEvent = this._handleEvent.bind(this);
66
118
 
67
- this.aqua.emit("playerUpdate", this, packet);
68
- });
119
+ this.on("playerUpdate", this._handlePlayerUpdate);
120
+ this.on("event", this._handleEvent);
121
+ }
69
122
 
70
- this.on("event", async (payload) => {
71
- try {
72
- const handlerName = Player.EVENT_HANDLERS[payload.type];
73
- if (handlerName && typeof this[handlerName] === "function") {
74
- await this[handlerName](this, this.current, payload);
75
- } else {
76
- this.aqua.emit("nodeError", this, new Error(`Node encountered an unknown event: '${payload.type}'`));
77
- }
78
- } catch (error) {
79
- console.error(`Error handling event ${payload.type}:`, error);
80
- this.aqua.emit("error", error);
123
+ _handlePlayerUpdate(packet) {
124
+ this.position = packet.state.position;
125
+ this.connected = packet.state.connected;
126
+ this.ping = packet.state.ping;
127
+ this.timestamp = packet.state.time;
128
+ this.aqua.emit("playerUpdate", this, packet);
129
+ }
130
+
131
+ async _handleEvent(payload) {
132
+ try {
133
+ const handlerName = EVENT_HANDLERS[payload.type];
134
+ if (handlerName && typeof this[handlerName] === "function") {
135
+ await this[handlerName](this, this.current, payload);
136
+ } else {
137
+ this.aqua.emit("nodeError", this, new Error(`Node encountered an unknown event: '${payload.type}'`));
81
138
  }
82
- });
139
+ } catch (error) {
140
+ console.error(`Error handling event ${payload.type}:`, error);
141
+ this.aqua.emit("error", error);
142
+ }
83
143
  }
84
144
 
85
145
  get previous() {
@@ -91,23 +151,7 @@ class Player extends EventEmitter {
91
151
  }
92
152
 
93
153
  batchUpdatePlayer(data, immediate = false) {
94
- this._pendingUpdates = { ...this._pendingUpdates, ...data };
95
- if (this._updateTimeout) clearTimeout(this._updateTimeout);
96
-
97
- if (immediate || data.track) {
98
- const updates = this._pendingUpdates;
99
- this._pendingUpdates = {};
100
- return this.updatePlayer(updates);
101
- }
102
-
103
- this._updateTimeout = setTimeout(() => {
104
- const updates = this._pendingUpdates;
105
- this._pendingUpdates = {};
106
- this.updatePlayer(updates);
107
- this._updateTimeout = null;
108
- }, 50);
109
-
110
- return Promise.resolve();
154
+ return this._updateBatcher.batch(data, immediate);
111
155
  }
112
156
 
113
157
  async autoplay(player) {
@@ -124,36 +168,37 @@ class Player extends EventEmitter {
124
168
  const { sourceName, identifier, uri, requester } = this.previous.info;
125
169
  this.aqua.emit("debug", this.guildId, `Attempting autoplay for ${sourceName}`);
126
170
 
127
- const sourceHandlers = {
128
- youtube: async () => ({
129
- query: `https://www.youtube.com/watch?v=${identifier}&list=RD${identifier}`,
130
- source: "ytmsearch"
131
- }),
132
- soundcloud: async () => {
171
+ let query, source;
172
+
173
+ switch (sourceName) {
174
+ case "youtube":
175
+ query = `https://www.youtube.com/watch?v=${identifier}&list=RD${identifier}`;
176
+ source = "ytmsearch";
177
+ break;
178
+ case "soundcloud":
133
179
  const scResults = await scAutoPlay(uri);
134
- return scResults?.length ? { query: scResults[0], source: "scsearch" } : null;
135
- },
136
- spotify: async () => {
180
+ if (!scResults?.length) return this;
181
+ query = scResults[0];
182
+ source = "scsearch";
183
+ break;
184
+ case "spotify":
137
185
  const spResult = await spAutoPlay(identifier);
138
- return spResult ? { query: `https://open.spotify.com/track/${spResult}`, source: "spotify" } : null;
139
- }
140
- };
141
-
142
- const handler = sourceHandlers[sourceName];
143
- if (!handler) return this;
144
-
145
- const result = await handler();
146
- if (!result) return this;
186
+ if (!spResult) return this;
187
+ query = `https://open.spotify.com/track/${spResult}`;
188
+ source = "spotify";
189
+ break;
190
+ default:
191
+ return this;
192
+ }
147
193
 
148
- const { query, source } = result;
149
194
  const response = await this.aqua.resolve({ query, source, requester });
150
195
 
151
- const failTypes = new Set(["error", "empty", "LOAD_FAILED", "NO_MATCHES"]);
152
- if (!response?.tracks?.length || failTypes.has(response.loadType)) {
196
+ if (!response?.tracks?.length || FAIL_LOAD_TYPES.has(response.loadType)) {
153
197
  return this.stop();
154
198
  }
155
199
 
156
- const track = response.tracks[Math.floor(Math.random() * response.tracks.length)];
200
+ const tracks = response.tracks;
201
+ const track = tracks[Math.floor(Math.random() * tracks.length)];
157
202
  if (!track?.info?.title) throw new Error("Invalid track object: missing title or info.");
158
203
 
159
204
  track.requester = this.previous.requester || { id: "Unknown" };
@@ -204,11 +249,7 @@ class Player extends EventEmitter {
204
249
  if (!this.connected) return this;
205
250
 
206
251
  const voiceChannelId = this.voiceChannel ? this.voiceChannel.id || this.voiceChannel : null;
207
- if (this._updateTimeout) {
208
- clearTimeout(this._updateTimeout);
209
- this._updateTimeout = null;
210
- this._pendingUpdates = {};
211
- }
252
+ this._updateBatcher.destroy();
212
253
 
213
254
  this.send({ guild_id: this.guildId, channel_id: null });
214
255
  this._lastVoiceChannel = voiceChannelId;
@@ -244,9 +285,9 @@ class Player extends EventEmitter {
244
285
  }
245
286
 
246
287
  async getLyrics(options = {}) {
247
- const { query = null, useCurrentTrack = true } = options;
248
- if (query) return this.nodes.rest.getLyrics({ track: { info: { title: query }, search: true } }) || null;
249
- if (useCurrentTrack && this.playing) return this.nodes.rest.getLyrics({ track: { encoded: this.current.track, guild_id: this.guildId } }) || null;
288
+ const { query = null, useCurrentTrack = true, skipTrackSource = false } = options;
289
+ if (query) return this.nodes.rest.getLyrics({ track: { info: { title: query }, search: true }, skipTrackSource }) || null;
290
+ if (useCurrentTrack && this.playing) return this.nodes.rest.getLyrics({ track: { encoded: this.current.track, guild_id: this.guildId }, skipTrackSource }) || null;
250
291
  return null;
251
292
  }
252
293
 
@@ -273,7 +314,7 @@ class Player extends EventEmitter {
273
314
  }
274
315
 
275
316
  setLoop(mode) {
276
- if (!Player.validModes.has(mode)) throw new Error("Loop mode must be 'none', 'track', or 'queue'.");
317
+ if (!VALID_MODES.has(mode)) throw new Error("Loop mode must be 'none', 'track', or 'queue'.");
277
318
  this.loop = mode;
278
319
  this.batchUpdatePlayer({ loop: mode });
279
320
  return this;
@@ -310,11 +351,33 @@ class Player extends EventEmitter {
310
351
 
311
352
  shuffle() {
312
353
  const queue = this.queue;
313
- for (let i = queue.length - 1; i > 0; i--) {
354
+ const len = queue.length;
355
+
356
+ if (len <= 1) return this;
357
+
358
+ if (len < 200) {
359
+ for (let i = len - 1; i > 0; i--) {
360
+ const j = Math.floor(Math.random() * (i + 1));
361
+ [queue[i], queue[j]] = [queue[j], queue[i]];
362
+ }
363
+ } else {
364
+ this._shuffleAsync(queue, len - 1);
365
+ }
366
+
367
+ return this;
368
+ }
369
+
370
+ _shuffleAsync(queue, i, chunkSize = 100) {
371
+ const end = Math.max(0, i - chunkSize);
372
+
373
+ for (; i > end; i--) {
314
374
  const j = Math.floor(Math.random() * (i + 1));
315
375
  [queue[i], queue[j]] = [queue[j], queue[i]];
316
376
  }
317
- return this;
377
+
378
+ if (i > 0) {
379
+ setImmediate(() => this._shuffleAsync(queue, i, chunkSize));
380
+ }
318
381
  }
319
382
 
320
383
  getQueue() {
@@ -354,8 +417,7 @@ class Player extends EventEmitter {
354
417
  }
355
418
 
356
419
  const reason = payload.reason;
357
- const failureReasons = new Set(["LOAD_FAILED", "CLEANUP"]);
358
- if (failureReasons.has(reason)) {
420
+ if (FAILURE_REASONS.has(reason)) {
359
421
  if (!player.queue.length) {
360
422
  this.previousTracksCount = 0;
361
423
  this._dataStore.clear();
@@ -367,15 +429,13 @@ class Player extends EventEmitter {
367
429
  return;
368
430
  }
369
431
 
370
- switch (this.loop) {
371
- case Player.LOOP_MODES.TRACK:
372
- player.queue.unshift(track);
373
- break;
374
- case Player.LOOP_MODES.QUEUE:
375
- player.queue.push(track);
376
- break;
432
+ if (this.loop === LOOP_MODES.TRACK) {
433
+ player.queue.unshift(track);
434
+ } else if (this.loop === LOOP_MODES.QUEUE) {
435
+ player.queue.push(track);
377
436
  }
378
437
 
438
+
379
439
  if (player.queue.isEmpty()) {
380
440
  if (this.isAutoplayEnabled) {
381
441
  await player.autoplay(player);
@@ -406,8 +466,7 @@ class Player extends EventEmitter {
406
466
 
407
467
  async socketClosed(player, payload) {
408
468
  const { code, guildId } = payload || {};
409
- const reconnectCodes = new Set([4015, 4009]);
410
- if (reconnectCodes.has(code)) {
469
+ if (RECONNECT_CODES.has(code)) {
411
470
  this.send({
412
471
  guild_id: guildId,
413
472
  channel_id: this.voiceChannel,
@@ -34,28 +34,48 @@ class Rest {
34
34
  }
35
35
  }
36
36
 
37
- async makeRequest(method, endpoint, body = null) {
37
+ async makeRequest(method, endpoint, body = null) {
38
38
  const url = `${this.baseUrl}${endpoint}`;
39
+
40
+ if (!this.agent) {
41
+ const AgentClass = this.secure ? https.Agent : http.Agent;
42
+ this.agent = new AgentClass({
43
+ keepAlive: true,
44
+ maxSockets: 10,
45
+ maxFreeSockets: 5,
46
+ timeout: this.timeout,
47
+ freeSocketTimeout: 30000
48
+ });
49
+ }
50
+
39
51
  const options = {
40
52
  method,
41
53
  headers: this.headers,
42
54
  timeout: this.timeout,
43
- keepAlive: true,
55
+ agent: this.agent
44
56
  };
45
57
 
46
58
  return new Promise((resolve, reject) => {
47
- const req = this.client.request(url, options, (res) => {
59
+ const client = this.secure ? https : http;
60
+ const req = client.request(url, options, (res) => {
61
+ if (res.statusCode === 204) return;
62
+
48
63
  const chunks = [];
49
- res.setEncoding("utf8");
50
- res.on("data", (chunk) => {
64
+ let totalLength = 0;
65
+
66
+ res.on('data', (chunk) => {
51
67
  chunks.push(chunk);
68
+ totalLength += chunk.length;
52
69
  });
53
- res.on("end", () => {
54
- const data = chunks.join("");
55
- if (!data) {
56
- resolve(null);
57
- return;
70
+
71
+ res.on('end', () => {
72
+ if (totalLength === 0) {
73
+ return resolve(null);
58
74
  }
75
+
76
+ const buffer = Buffer.concat(chunks, totalLength);
77
+ const data = buffer.toString('utf8');
78
+
59
79
  try {
60
80
  resolve(JSON.parse(data));
61
81
  } catch (err) {
@@ -64,15 +84,20 @@ class Rest {
64
84
  });
65
85
  });
66
86
 
67
- req.on("error", (err) => reject(new Error(`Request failed (${method} ${url}): ${err.message}`)));
68
- req.on("timeout", () => {
87
+ req.on('error', (err) => {
88
+ reject(new Error(`Request failed (${method} ${url}): ${err.message}`));
89
+ });
90
+
91
+ req.on('timeout', () => {
69
92
  req.destroy();
70
93
  reject(new Error(`Request timed out after ${this.timeout}ms (${method} ${url})`));
71
94
  });
72
95
 
73
96
  if (body) {
74
- req.write(JSON.stringify(body));
97
+ const bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
98
+ req.write(bodyStr);
75
99
  }
100
+
76
101
  req.end();
77
102
  });
78
103
  }
@@ -134,27 +159,30 @@ class Rest {
134
159
  const endpoint = `/${this.version}/routeplanner/free/address`;
135
160
  return this.makeRequest("POST", endpoint, { address });
136
161
  }
137
-
138
162
  async getLyrics({ track }) {
139
163
  if (!track) return null;
140
164
 
165
+
141
166
  try {
142
167
  if (track.search) {
143
168
  const query = encodeURIComponent(track.info.title);
144
169
  try {
145
170
  const res = await this.makeRequest(
146
- "GET",
171
+ "GET",
147
172
  `/${this.version}/lyrics/search?query=${query}&source=genius`
148
173
  );
174
+
149
175
  if (res) return res;
150
176
  } catch (_) {
151
177
  }
152
178
  } else {
153
179
  this.validateSessionId();
154
- return await this.makeRequest(
180
+ const res = await this.makeRequest(
155
181
  "GET",
156
- `/${this.version}/sessions/${this.sessionId}/players/${track.guild_id}/track/lyrics?skipTrackSource=true`
182
+ `/${this.version}/sessions/${this.sessionId}/players/${track.guild_id}/lyrics`
157
183
  );
184
+ console.log(res);
185
+ return res;
158
186
  }
159
187
  } catch (error) {
160
188
  console.error("Failed to fetch lyrics:", error.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aqualink",
3
- "version": "2.6.2",
3
+ "version": "2.6.3",
4
4
  "description": "An Lavalink client, focused in pure performance and features",
5
5
  "main": "build/index.js",
6
6
  "types": "index.d.ts",