aqualink 2.1.3 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,6 +26,7 @@ class Player extends EventEmitter {
26
26
 
27
27
  constructor(aqua, nodes, options = {}) {
28
28
  super();
29
+
29
30
  this.aqua = aqua;
30
31
  this.nodes = nodes;
31
32
  this.guildId = options.guildId;
@@ -35,47 +36,41 @@ class Player extends EventEmitter {
35
36
  this.connection = new Connection(this);
36
37
  this.filters = new Filters(this);
37
38
 
38
- const defaultVolume = options.defaultVolume ?? 100;
39
- this.volume = defaultVolume > 200 ? 200 : (defaultVolume < 0 ? 0 : defaultVolume);
40
-
39
+ this.volume = Math.max(0, Math.min(options.defaultVolume ?? 100, 200));
41
40
  this.loop = Player.validModes.has(options.loop) ? options.loop : Player.LOOP_MODES.NONE;
42
41
 
43
42
  this.queue = new Queue();
44
- this.previousTracks = [];
45
- this.shouldDeleteMessage = !!options.shouldDeleteMessage;
46
- this.leaveOnEnd = !!options.leaveOnEnd;
43
+ this.previousTracks = new Array(50);
44
+ this.previousTracksIndex = 0;
45
+ this.previousTracksCount = 0;
46
+
47
+ this.shouldDeleteMessage = Boolean(options.shouldDeleteMessage);
48
+ this.leaveOnEnd = Boolean(options.leaveOnEnd);
47
49
 
48
50
  this.playing = false;
49
51
  this.paused = false;
50
52
  this.connected = false;
51
53
  this.current = null;
54
+ this.position = 0;
52
55
  this.timestamp = 0;
53
56
  this.ping = 0;
54
57
  this.nowPlayingMessage = null;
55
-
58
+ this.isAutoplayEnabled = false;
56
59
  this.isAutoplay = false;
57
- this._autoplay = {
58
- _addToHistory: (track) => {
59
- if (this.previousTracks.length >= 50) {
60
- this.previousTracks.pop();
61
- }
62
- this.previousTracks.unshift(track);
63
- },
64
- _cleanup: () => {
65
- this.previousTracks = [];
66
- this.isAutoplay = false;
67
- }
60
+
61
+ this._boundHandlers = {
62
+ playerUpdate: this._handlePlayerUpdate.bind(this),
63
+ event: this._handleEvent.bind(this)
68
64
  };
69
-
70
- this._handlePlayerUpdate = this._handlePlayerUpdate.bind(this);
71
- this._handleEvent = this._handleEvent.bind(this);
72
65
 
73
- this.on("playerUpdate", this._handlePlayerUpdate);
74
- this.on("event", this._handleEvent);
66
+ this.on("playerUpdate", this._boundHandlers.playerUpdate);
67
+ this.on("event", this._boundHandlers.event);
68
+
69
+ this._dataStore = null;
75
70
  }
76
71
 
77
72
  get previous() {
78
- return this.previousTracks[0] || null;
73
+ return this.previousTracksCount > 0 ? this.previousTracks[this.previousTracksIndex] : null;
79
74
  }
80
75
 
81
76
  get currenttrack() {
@@ -99,26 +94,38 @@ class Player extends EventEmitter {
99
94
 
100
95
  let query, source, response;
101
96
 
102
- switch (sourceName) {
103
- case "youtube":
104
- query = `https://www.youtube.com/watch?v=${identifier}&list=RD${identifier}`;
105
- source = "ytmsearch";
106
- break;
107
- case "soundcloud":
97
+ const sourceHandlers = {
98
+ youtube: async () => {
99
+ return {
100
+ query: `https://www.youtube.com/watch?v=${identifier}&list=RD${identifier}`,
101
+ source: "ytmsearch"
102
+ };
103
+ },
104
+ soundcloud: async () => {
108
105
  const scResults = await scAutoPlay(uri);
109
- if (!scResults?.length) return this;
110
- query = scResults[0];
111
- source = "scsearch";
112
- break;
113
- case "spotify":
106
+ if (!scResults?.length) return null;
107
+ return {
108
+ query: scResults[0],
109
+ source: "scsearch"
110
+ };
111
+ },
112
+ spotify: async () => {
114
113
  const spResult = await spAutoPlay(identifier);
115
- if (!spResult) return this;
116
- query = `https://open.spotify.com/track/${spResult}`;
117
- source = "spotify";
118
- break;
119
- default:
120
- return this;
121
- }
114
+ if (!spResult) return null;
115
+ return {
116
+ query: `https://open.spotify.com/track/${spResult}`,
117
+ source: "spotify"
118
+ };
119
+ }
120
+ };
121
+
122
+ const handler = sourceHandlers[sourceName];
123
+ if (!handler) return this;
124
+
125
+ const result = await handler();
126
+ if (!result) return this;
127
+
128
+ ({ query, source } = result);
122
129
 
123
130
  response = await this.aqua.resolve({ query, source, requester });
124
131
 
@@ -147,24 +154,27 @@ class Player extends EventEmitter {
147
154
  setAutoplay(enabled) {
148
155
  this.isAutoplayEnabled = Boolean(enabled);
149
156
  this.aqua.emit("debug", this.guildId, `Autoplay has been ${enabled ? "enabled" : "disabled"}.`);
157
+ return this;
150
158
  }
151
159
 
152
-
153
-
154
160
  addToPreviousTrack(track) {
155
161
  if (!track) return;
156
162
 
157
- if (this.previousTracks.length >= 50) {
158
- this.previousTracks.pop();
163
+ this.previousTracks[this.previousTracksIndex] = track;
164
+ this.previousTracksIndex = (this.previousTracksIndex + 1) % 50;
165
+
166
+ if (this.previousTracksCount < 50) {
167
+ this.previousTracksCount++;
159
168
  }
160
- this.previousTracks.unshift(track);
161
169
  }
162
170
 
163
171
  _handlePlayerUpdate({ state }) {
164
172
  if (state) {
165
- if (state.position !== undefined) this.position = state.position;
166
- if (state.timestamp !== undefined) this.timestamp = state.timestamp;
167
- if (state.ping !== undefined) this.ping = state.ping;
173
+ const { position, timestamp, ping } = state;
174
+
175
+ if (position !== undefined) this.position = position;
176
+ if (timestamp !== undefined) this.timestamp = timestamp;
177
+ if (ping !== undefined) this.ping = ping;
168
178
  }
169
179
  this.aqua.emit("playerUpdate", this, { state });
170
180
  }
@@ -193,12 +203,14 @@ class Player extends EventEmitter {
193
203
  connect({ guildId, voiceChannel, deaf = true, mute = false }) {
194
204
  if (this.connected) throw new Error("Player is already connected.");
195
205
 
196
- this.send({
206
+ const payload = {
197
207
  guild_id: guildId,
198
208
  channel_id: voiceChannel,
199
209
  self_deaf: deaf,
200
210
  self_mute: mute
201
- });
211
+ };
212
+
213
+ this.send(payload);
202
214
 
203
215
  this.connected = true;
204
216
  this.aqua.emit("debug", this.guildId, `Player connected to voice channel: ${voiceChannel}.`);
@@ -210,18 +222,34 @@ class Player extends EventEmitter {
210
222
 
211
223
  this.disconnect();
212
224
 
213
- this.nowPlayingMessage?.delete().catch(() => {});
225
+ this._cleanupNowPlayingMessage();
214
226
 
215
- if (this._autoplay) {
216
- this._autoplay._cleanup();
217
- }
227
+ this.isAutoplay = false;
218
228
 
229
+ this.off("playerUpdate", this._boundHandlers.playerUpdate);
230
+ this.off("event", this._boundHandlers.event);
231
+
219
232
  this.aqua.destroyPlayer(this.guildId);
220
233
  this.nodes.rest.destroyPlayer(this.guildId);
234
+
221
235
  this.clearData();
222
236
  this.removeAllListeners();
237
+
238
+ this._boundHandlers = null;
239
+ this.queue = null;
240
+ this.previousTracks = null;
241
+ this.connection = null;
242
+ this.filters = null;
243
+
223
244
  return this;
224
245
  }
246
+
247
+ _cleanupNowPlayingMessage() {
248
+ if (this.nowPlayingMessage) {
249
+ this.nowPlayingMessage.delete().catch(() => {});
250
+ this.nowPlayingMessage = null;
251
+ }
252
+ }
225
253
 
226
254
  pause(paused) {
227
255
  if (this.paused === paused) return this;
@@ -287,6 +315,7 @@ class Player extends EventEmitter {
287
315
  }
288
316
 
289
317
  this.voiceChannel = channel;
318
+
290
319
  this.connect({
291
320
  deaf: this.deaf,
292
321
  guildId: this.guildId,
@@ -298,6 +327,8 @@ class Player extends EventEmitter {
298
327
  }
299
328
 
300
329
  disconnect() {
330
+ if (!this.connected) return this;
331
+
301
332
  this.connected = false;
302
333
  this.send({ guild_id: this.guildId, channel_id: null });
303
334
  this.voiceChannel = null;
@@ -329,17 +360,14 @@ class Player extends EventEmitter {
329
360
  }
330
361
 
331
362
  async trackStart(player, track) {
332
- this.updateTrackState(true, false);
363
+ this.playing = true;
364
+ this.paused = false;
333
365
  this.aqua.emit("trackStart", player, track);
334
366
  }
335
367
 
336
- async trackChange(player, track) {
337
- this.updateTrackState(true, false);
338
- this.aqua.emit("trackChange", player, track);
339
- }
340
-
341
368
  async trackEnd(player, track, payload) {
342
369
  this.addToPreviousTrack(track);
370
+
343
371
  if (this.shouldDeleteMessage && this.nowPlayingMessage) {
344
372
  try {
345
373
  await this.nowPlayingMessage.delete();
@@ -348,37 +376,47 @@ class Player extends EventEmitter {
348
376
  console.error("Error deleting now playing message:", error);
349
377
  }
350
378
  }
351
- const reason = payload.reason?.toLowerCase().replace("_", "");
352
- if (reason === "loadfailed" || reason === "cleanup") {
379
+
380
+ const reason = payload.reason;
381
+ if (reason === "LOAD_FAILED" || reason === "CLEANUP") {
353
382
  if (!player.queue.length) {
354
383
  this.clearData();
355
384
  this.aqua.emit("queueEnd", player);
356
385
  } else {
386
+ this.aqua.emit("trackEnd", player, track, reason);
357
387
  await player.play();
358
388
  }
359
389
  return;
360
390
  }
361
391
 
392
+ await this._handleTrackLooping(player, track);
393
+
394
+ if (player.queue.isEmpty()) {
395
+ await this._handleEmptyQueue(player);
396
+ } else {
397
+ this.aqua.emit("trackEnd", player, track, reason);
398
+ await player.play();
399
+ }
400
+ }
401
+
402
+ async _handleTrackLooping(player, track) {
362
403
  if (this.loop === Player.LOOP_MODES.TRACK) {
363
404
  player.queue.unshift(track);
364
405
  } else if (this.loop === Player.LOOP_MODES.QUEUE) {
365
406
  player.queue.push(track);
366
407
  }
367
-
368
- if (player.queue.isEmpty()) {
369
- if (this.isAutoplayEnabled) {
370
- await player.autoplay(player, track);
371
- } else {
372
- this.playing = false;
373
- if (this.leaveOnEnd) {
374
- this.clearData();
375
- this.cleanup();
376
- }
377
- this.aqua.emit("queueEnd", player);
378
- }
379
- return;
408
+ }
409
+
410
+ async _handleEmptyQueue(player) {
411
+ if (this.isAutoplayEnabled) {
412
+ await player.autoplay(player);
380
413
  } else {
381
- await player.play();
414
+ this.playing = false;
415
+ if (this.leaveOnEnd) {
416
+ this.clearData();
417
+ this.cleanup();
418
+ }
419
+ this.aqua.emit("queueEnd", player);
382
420
  }
383
421
  }
384
422
 
@@ -393,11 +431,11 @@ class Player extends EventEmitter {
393
431
  }
394
432
 
395
433
  async socketClosed(player, payload) {
396
- const code = payload && payload.code;
434
+ const { code, guildId } = payload || {};
397
435
 
398
436
  if (code === 4015 || code === 4009) {
399
437
  this.send({
400
- guild_id: payload.guildId,
438
+ guild_id: guildId,
401
439
  channel_id: this.voiceChannel,
402
440
  self_mute: this.mute,
403
441
  self_deaf: this.deaf,
@@ -415,7 +453,7 @@ class Player extends EventEmitter {
415
453
 
416
454
  set(key, value) {
417
455
  if (!this._dataStore) {
418
- this._dataStore = new WeakMap();
456
+ this._dataStore = new Map();
419
457
  }
420
458
  this._dataStore.set(key, value);
421
459
  }
@@ -425,7 +463,9 @@ class Player extends EventEmitter {
425
463
  }
426
464
 
427
465
  clearData() {
428
- if (this.previousTracks) this.previousTracks.length = 0;
466
+ if (this.previousTracks) {
467
+ this.previousTracksCount = 0;
468
+ }
429
469
  this._dataStore = null;
430
470
  return this;
431
471
  }
@@ -444,11 +484,6 @@ class Player extends EventEmitter {
444
484
  this.destroy();
445
485
  }
446
486
  }
447
-
448
- updateTrackState(playing, paused) {
449
- this.playing = playing;
450
- this.paused = paused;
451
- }
452
487
  }
453
488
 
454
489
  module.exports = Player;
@@ -2,10 +2,14 @@
2
2
  const https = require("https");
3
3
  const http = require("http");
4
4
 
5
- let http2 = null;
5
+ let http2;
6
+ try {
7
+ http2 = require("http2");
8
+ } catch (e) {
9
+ }
6
10
 
7
11
  class Rest {
8
- constructor(aqua, { secure, host, port, sessionId, password }) {
12
+ constructor(aqua, { secure, host, port, sessionId, password, timeout = 30000 }) {
9
13
  this.aqua = aqua;
10
14
  this.sessionId = sessionId;
11
15
  this.version = "v4";
@@ -14,29 +18,17 @@ class Rest {
14
18
  "Content-Type": "application/json",
15
19
  "Authorization": password,
16
20
  };
17
-
18
21
  this.secure = secure;
22
+ this.timeout = timeout;
23
+
24
+ this.client = this.initializeClient();
19
25
  }
20
26
 
21
- getClient() {
22
- if (this.client) return this.client;
23
-
27
+ initializeClient() {
24
28
  if (this.secure) {
25
- if (!http2) {
26
- try {
27
- http2 = require("http2");
28
- this.client = http2;
29
- } catch (e) {
30
- this.client = https;
31
- }
32
- } else {
33
- this.client = http2;
34
- }
35
- } else {
36
- this.client = http;
29
+ return http2 || https;
37
30
  }
38
-
39
- return this.client;
31
+ return http;
40
32
  }
41
33
 
42
34
  setSessionId(sessionId) {
@@ -50,22 +42,24 @@ class Rest {
50
42
  };
51
43
 
52
44
  return new Promise((resolve, reject) => {
53
- const client = this.getClient();
54
45
  const url = `${this.baseUrl}${endpoint}`;
55
-
56
- const req = client.request(url, options, (res) => {
57
- const chunks = [];
46
+ const req = this.client.request(url, options, (res) => {
47
+ res.setEncoding('utf8');
48
+
49
+ let data = '';
50
+
51
+ res.on("data", (chunk) => {
52
+ data += chunk;
53
+ });
58
54
 
59
- res.on("data", (chunk) => chunks.push(chunk));
60
55
  res.on("end", () => {
61
56
  if (res.statusCode >= 200 && res.statusCode < 300) {
62
- if (chunks.length === 0) {
57
+ if (!data) {
63
58
  resolve(null);
64
59
  return;
65
60
  }
66
61
 
67
62
  try {
68
- const data = Buffer.concat(chunks).toString('utf8');
69
63
  resolve(data ? JSON.parse(data) : null);
70
64
  } catch (error) {
71
65
  reject(new Error(`Failed to parse response: ${error.message}`));
@@ -141,25 +135,27 @@ class Rest {
141
135
  }
142
136
 
143
137
  async getLyrics({ track }) {
144
- if (!track) {
145
- return null;
146
- }
147
-
148
- if (track.search) {
149
- try {
150
- const query = encodeURIComponent(track.info.title);
151
- const res = await this.makeRequest("GET", `/${this.version}/lyrics/search?query=${query}&source=genius`);
152
- if (res) return res;
153
- } catch (error) {
154
- console.error("Failed to fetch lyrics:", error.message);
138
+ if (!track) return null;
139
+
140
+ try {
141
+ if (track.search) {
142
+ const query = track.info.title;
143
+ try {
144
+ const res = await this.makeRequest("GET", `/${this.version}/lyrics/search?query=${query}&source=genius`);
145
+ if (res) return res;
146
+ } catch (err) {}
147
+ } else {
148
+ this.validateSessionId();
149
+ return await this.makeRequest(
150
+ "GET",
151
+ `/${this.version}/sessions/${this.sessionId}/players/${track.guild_id}/track/lyrics?skipTrackSource=false`
152
+ );
155
153
  }
154
+
155
+ } catch (error) {
156
+ console.error("Failed to fetch lyrics:", error.message);
157
+ return null;
156
158
  }
157
-
158
- this.validateSessionId();
159
- return this.makeRequest(
160
- "GET",
161
- `/${this.version}/sessions/${this.sessionId}/players/${track.guild_id}/track/lyrics?skipTrackSource=false`
162
- );
163
159
  }
164
160
  }
165
161
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aqualink",
3
- "version": "2.1.3",
3
+ "version": "2.3.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",