aqualink 2.0.2 → 2.1.1

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.
@@ -30,14 +30,19 @@ class Player extends EventEmitter {
30
30
  this.guildId = options.guildId;
31
31
  this.textChannel = options.textChannel;
32
32
  this.voiceChannel = options.voiceChannel;
33
+
33
34
  this.connection = new Connection(this);
34
35
  this.filters = new Filters(this);
35
- this.volume = Math.min(Math.max(options.defaultVolume ?? 100, 0), 200);
36
+
37
+ const defaultVolume = options.defaultVolume ?? 100;
38
+ this.volume = defaultVolume > 200 ? 200 : (defaultVolume < 0 ? 0 : defaultVolume);
39
+
36
40
  this.loop = Player.validModes.has(options.loop) ? options.loop : Player.LOOP_MODES.NONE;
41
+
37
42
  this.queue = new Queue();
38
- this.previousTracks = [];
39
- this.shouldDeleteMessage = options.shouldDeleteMessage ?? false;
40
- this.leaveOnEnd = options.leaveOnEnd ?? false;
43
+ this.previousTracks = [];
44
+ this.shouldDeleteMessage = !!options.shouldDeleteMessage;
45
+ this.leaveOnEnd = !!options.leaveOnEnd;
41
46
 
42
47
  this.playing = false;
43
48
  this.paused = false;
@@ -47,37 +52,54 @@ class Player extends EventEmitter {
47
52
  this.ping = 0;
48
53
  this.nowPlayingMessage = null;
49
54
 
50
- this.on("playerUpdate", ({ state }) => {
51
- if (state) Object.assign(this, state);
52
- this.aqua.emit("playerUpdate", this, { state });
53
- });
54
-
55
- this.on("event", async (payload) => {
56
- const handler = Player.EVENT_HANDLERS[payload.type];
57
- if (handler && typeof this[handler] === "function") {
58
- await this[handler](this, this.current, payload);
59
- } else {
60
- this.handleUnknownEvent(payload);
61
- }
62
- });
55
+ this._handlePlayerUpdate = this._handlePlayerUpdate.bind(this);
56
+ this._handleEvent = this._handleEvent.bind(this);
57
+
58
+ this.on("playerUpdate", this._handlePlayerUpdate);
59
+ this.on("event", this._handleEvent);
60
+
61
+ this._dataStore = null;
63
62
  }
64
63
 
65
64
  get previous() {
66
65
  return this.previousTracks[0] || null;
67
66
  }
67
+
68
68
  get currenttrack() {
69
69
  return this.current;
70
70
  }
71
71
 
72
72
  addToPreviousTrack(track) {
73
- if (this.previousTracks.length >= 50) this.previousTracks.pop();
73
+ if (!track) return;
74
+
75
+ if (this.previousTracks.length >= 50) {
76
+ this.previousTracks.pop();
77
+ }
74
78
  this.previousTracks.unshift(track);
75
79
  }
76
80
 
81
+ _handlePlayerUpdate({ state }) {
82
+ if (state) {
83
+ if (state.position !== undefined) this.position = state.position;
84
+ if (state.timestamp !== undefined) this.timestamp = state.timestamp;
85
+ if (state.ping !== undefined) this.ping = state.ping;
86
+ }
87
+ this.aqua.emit("playerUpdate", this, { state });
88
+ }
89
+
90
+ async _handleEvent(payload) {
91
+ const handlerName = Player.EVENT_HANDLERS[payload.type];
92
+ if (handlerName && typeof this[handlerName] === "function") {
93
+ await this[handlerName](this, this.current, payload);
94
+ } else {
95
+ this.handleUnknownEvent(payload);
96
+ }
97
+ }
98
+
77
99
  async play() {
78
100
  if (!this.connected || !this.queue.length) return;
101
+
79
102
  const item = this.queue.shift();
80
-
81
103
  this.current = item.track ? item : await item.resolve(this.aqua);
82
104
  this.playing = true;
83
105
  this.position = 0;
@@ -88,12 +110,14 @@ class Player extends EventEmitter {
88
110
 
89
111
  connect({ guildId, voiceChannel, deaf = true, mute = false }) {
90
112
  if (this.connected) throw new Error("Player is already connected.");
113
+
91
114
  this.send({
92
115
  guild_id: guildId,
93
116
  channel_id: voiceChannel,
94
117
  self_deaf: deaf,
95
118
  self_mute: mute
96
119
  });
120
+
97
121
  this.connected = true;
98
122
  this.aqua.emit("debug", this.guildId, `Player connected to voice channel: ${voiceChannel}.`);
99
123
  return this;
@@ -101,8 +125,11 @@ class Player extends EventEmitter {
101
125
 
102
126
  destroy() {
103
127
  if (!this.connected) return this;
128
+
104
129
  this.disconnect();
105
- this.nowPlayingMessage?.delete().catch(() => { });
130
+
131
+ this.nowPlayingMessage?.delete().catch(() => {});
132
+
106
133
  this.aqua.destroyPlayer(this.guildId);
107
134
  this.nodes.rest.destroyPlayer(this.guildId);
108
135
  this.clearData();
@@ -112,6 +139,7 @@ class Player extends EventEmitter {
112
139
 
113
140
  pause(paused) {
114
141
  if (this.paused === paused) return this;
142
+
115
143
  this.paused = paused;
116
144
  this.updatePlayer({ paused });
117
145
  return this;
@@ -119,22 +147,25 @@ class Player extends EventEmitter {
119
147
 
120
148
  async searchLyrics(query) {
121
149
  if (!query) return null;
122
- return await this.nodes.rest.getLyrics({ track: { info: { title: query } }, search: true }) || null;
150
+ return this.nodes.rest.getLyrics({ track: { info: { title: query }, search: true } }) || null;
123
151
  }
124
152
 
125
153
  async lyrics() {
126
154
  if (!this.playing) return null;
127
- return await this.nodes.rest.getLyrics({ track: { encoded: this.current.track } }) || null;
155
+ return this.nodes.rest.getLyrics({ track: { encoded: this.current.track, guild_id: this.guildId } }) || null;
128
156
  }
129
157
 
130
158
  seek(position) {
131
159
  if (!this.playing) return this;
132
- this.updatePlayer({ position: (this.position += position) });
160
+
161
+ this.position += position;
162
+ this.updatePlayer({ position: this.position });
133
163
  return this;
134
164
  }
135
165
 
136
166
  stop() {
137
167
  if (!this.playing) return this;
168
+
138
169
  this.playing = false;
139
170
  this.position = 0;
140
171
  this.updatePlayer({ track: { encoded: null } });
@@ -143,13 +174,15 @@ class Player extends EventEmitter {
143
174
 
144
175
  setVolume(volume) {
145
176
  if (volume < 0 || volume > 200) throw new Error("Volume must be between 0 and 200.");
177
+
146
178
  this.volume = volume;
147
179
  this.updatePlayer({ volume });
148
180
  return this;
149
181
  }
150
182
 
151
183
  setLoop(mode) {
152
- if (!Player.validModes.includes(mode)) throw new Error("Loop mode must be 'none', 'track', or 'queue'.");
184
+ if (!Player.validModes.has(mode)) throw new Error("Loop mode must be 'none', 'track', or 'queue'.");
185
+
153
186
  this.loop = mode;
154
187
  this.updatePlayer({ loop: mode });
155
188
  return this;
@@ -163,9 +196,11 @@ class Player extends EventEmitter {
163
196
 
164
197
  setVoiceChannel(channel) {
165
198
  if (!channel?.length) throw new TypeError("Channel must be a non-empty string.");
199
+
166
200
  if (this.connected && channel === this.voiceChannel) {
167
201
  throw new ReferenceError(`Player already connected to ${channel}.`);
168
202
  }
203
+
169
204
  this.voiceChannel = channel;
170
205
  this.connect({
171
206
  deaf: this.deaf,
@@ -173,6 +208,7 @@ class Player extends EventEmitter {
173
208
  voiceChannel: channel,
174
209
  mute: this.mute
175
210
  });
211
+
176
212
  return this;
177
213
  }
178
214
 
@@ -183,10 +219,12 @@ class Player extends EventEmitter {
183
219
  this.aqua.emit("debug", this.guildId, "Player disconnected.");
184
220
  return this;
185
221
  }
222
+
186
223
  shuffle() {
187
- for (let i = this.queue.length - 1; i > 0; i--) {
224
+ const { queue } = this;
225
+ for (let i = queue.length - 1; i > 0; i--) {
188
226
  const j = Math.floor(Math.random() * (i + 1));
189
- [this.queue[i], this.queue[j]] = [this.queue[j], this.queue[i]];
227
+ [queue[i], queue[j]] = [queue[j], queue[i]];
190
228
  }
191
229
  return this;
192
230
  }
@@ -197,6 +235,7 @@ class Player extends EventEmitter {
197
235
 
198
236
  replay() {
199
237
  this.seek(-this.position);
238
+ return this;
200
239
  }
201
240
 
202
241
  skip() {
@@ -222,8 +261,9 @@ class Player extends EventEmitter {
222
261
  } catch {}
223
262
  }
224
263
 
225
- const reason = payload.reason?.replace("_", "").toLowerCase();
226
- if (["loadfailed", "cleanup"].includes(reason)) {
264
+ const reason = payload.reason?.toLowerCase().replace("_", "");
265
+
266
+ if (reason === "loadfailed" || reason === "cleanup") {
227
267
  if (!player.queue.length) {
228
268
  this.clearData();
229
269
  this.aqua.emit("queueEnd", player);
@@ -263,7 +303,9 @@ class Player extends EventEmitter {
263
303
  }
264
304
 
265
305
  async socketClosed(player, payload) {
266
- if (payload?.code === 4015 || payload?.code === 4009) {
306
+ const code = payload && payload.code;
307
+
308
+ if (code === 4015 || code === 4009) {
267
309
  this.send({
268
310
  guild_id: payload.guildId,
269
311
  channel_id: this.voiceChannel,
@@ -271,6 +313,7 @@ class Player extends EventEmitter {
271
313
  self_deaf: this.deaf,
272
314
  });
273
315
  }
316
+
274
317
  this.aqua.emit("socketClosed", player, payload);
275
318
  this.pause(true);
276
319
  this.aqua.emit("debug", this.guildId, "Player paused due to socket closure.");
@@ -280,19 +323,20 @@ class Player extends EventEmitter {
280
323
  this.aqua.send({ op: 4, d: data });
281
324
  }
282
325
 
283
- #dataStore = new WeakMap();
284
-
285
326
  set(key, value) {
286
- this.#dataStore.set(key, value);
327
+ if (!this._dataStore) {
328
+ this._dataStore = new WeakMap();
329
+ }
330
+ this._dataStore.set(key, value);
287
331
  }
288
332
 
289
333
  get(key) {
290
- return this.#dataStore.get(key);
334
+ return this._dataStore ? this._dataStore.get(key) : undefined;
291
335
  }
292
336
 
293
337
  clearData() {
294
338
  if (this.previousTracks) this.previousTracks.length = 0;
295
- this.#dataStore = new WeakMap();
339
+ this._dataStore = null;
296
340
  return this;
297
341
  }
298
342
 
@@ -317,4 +361,4 @@ class Player extends EventEmitter {
317
361
  }
318
362
  }
319
363
 
320
- module.exports = Player
364
+ module.exports = Player;
@@ -1,12 +1,8 @@
1
1
  "use strict";
2
2
  const https = require("https");
3
3
  const http = require("http");
4
- let http2;
5
- try {
6
- http2 = require("http2");
7
- } catch (e) {
8
- http2 = null;
9
- }
4
+
5
+ let http2 = null;
10
6
 
11
7
  class Rest {
12
8
  constructor(aqua, { secure, host, port, sessionId, password }) {
@@ -16,9 +12,31 @@ class Rest {
16
12
  this.baseUrl = `${secure ? "https" : "http"}://${host}:${port}`;
17
13
  this.headers = {
18
14
  "Content-Type": "application/json",
19
- Authorization: password,
15
+ "Authorization": password,
20
16
  };
21
- this.client = secure ? (http2 ? http2 : https) : http;
17
+
18
+ this.secure = secure;
19
+ }
20
+
21
+ getClient() {
22
+ if (this.client) return this.client;
23
+
24
+ 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;
37
+ }
38
+
39
+ return this.client;
22
40
  }
23
41
 
24
42
  setSessionId(sessionId) {
@@ -32,24 +50,37 @@ class Rest {
32
50
  };
33
51
 
34
52
  return new Promise((resolve, reject) => {
35
- const req = this.client.request(`${this.baseUrl}${endpoint}`, options, (res) => {
36
- let data = "";
37
- res.setEncoding("utf8");
38
-
39
- res.on("data", (chunk) => (data += chunk));
53
+ const client = this.getClient();
54
+ const url = `${this.baseUrl}${endpoint}`;
55
+
56
+ const req = client.request(url, options, (res) => {
57
+ const chunks = [];
58
+
59
+ res.on("data", (chunk) => chunks.push(chunk));
40
60
  res.on("end", () => {
41
61
  if (res.statusCode >= 200 && res.statusCode < 300) {
42
- resolve(data ? JSON.parse(data) : null);
62
+ if (chunks.length === 0) {
63
+ resolve(null);
64
+ return;
65
+ }
66
+
67
+ try {
68
+ const data = Buffer.concat(chunks).toString('utf8');
69
+ resolve(data ? JSON.parse(data) : null);
70
+ } catch (error) {
71
+ reject(new Error(`Failed to parse response: ${error.message}`));
72
+ }
43
73
  } else {
44
74
  reject(new Error(`Request failed with status ${res.statusCode}: ${res.statusMessage}`));
45
75
  }
46
76
  });
47
77
  });
48
78
 
49
- req.on("error", (error) => reject(new Error(`Request failed (${method} ${endpoint}): ${error.message}`)));
79
+ req.on("error", (error) => reject(new Error(`Request failed (${method} ${url}): ${error.message}`)));
50
80
 
51
81
  if (body) {
52
- req.write(JSON.stringify(body));
82
+ const jsonBody = JSON.stringify(body);
83
+ req.write(jsonBody);
53
84
  }
54
85
  req.end();
55
86
  });
@@ -64,7 +95,11 @@ class Rest {
64
95
  throw new Error("Cannot provide both 'encoded' and 'identifier' for track");
65
96
  }
66
97
  this.validateSessionId();
67
- return this.makeRequest("PATCH", `/${this.version}/sessions/${this.sessionId}/players/${guildId}?noReplace=false`, data);
98
+ return this.makeRequest(
99
+ "PATCH",
100
+ `/${this.version}/sessions/${this.sessionId}/players/${guildId}?noReplace=false`,
101
+ data
102
+ );
68
103
  }
69
104
 
70
105
  getPlayers() {
@@ -106,16 +141,25 @@ class Rest {
106
141
  }
107
142
 
108
143
  async getLyrics({ track }) {
144
+ if (!track) {
145
+ return null;
146
+ }
147
+
109
148
  if (track.search) {
110
149
  try {
111
- const res = await this.makeRequest("GET", `/${this.version}/lyrics/search?query=${encodeURIComponent(track.encoded.info.title)}&source=genius`);
150
+ const query = encodeURIComponent(track.info.title);
151
+ const res = await this.makeRequest("GET", `/${this.version}/lyrics/search?query=${query}&source=genius`);
112
152
  if (res) return res;
113
153
  } catch (error) {
114
154
  console.error("Failed to fetch lyrics:", error.message);
115
155
  }
116
156
  }
157
+
117
158
  this.validateSessionId();
118
- return this.makeRequest("GET", `/${this.version}/sessions/${this.sessionId}/players/${track.guild_id}/track/lyrics?skipTrackSource=false`);
159
+ return this.makeRequest(
160
+ "GET",
161
+ `/${this.version}/sessions/${this.sessionId}/players/${track.guild_id}/track/lyrics?skipTrackSource=false`
162
+ );
119
163
  }
120
164
  }
121
165
 
@@ -4,17 +4,19 @@ const { getImageUrl } = require("../handlers/fetchImage");
4
4
  class Track {
5
5
  constructor(data = {}, requester, nodes) {
6
6
  const { info = {}, encoded = null, playlist = null } = data;
7
+
7
8
  this.info = {
8
9
  identifier: info.identifier || '',
9
- isSeekable: !!info.isSeekable,
10
+ isSeekable: Boolean(info.isSeekable),
10
11
  author: info.author || '',
11
- length: info.length | 0,
12
- isStream: !!info.isStream,
12
+ length: info.length | 0,
13
+ isStream: Boolean(info.isStream),
13
14
  title: info.title || '',
14
15
  uri: info.uri || '',
15
16
  sourceName: info.sourceName || '',
16
17
  artworkUrl: info.artworkUrl || ''
17
18
  };
19
+
18
20
  this.track = encoded;
19
21
  this.playlist = playlist;
20
22
  this.requester = requester;
@@ -33,7 +35,9 @@ class Track {
33
35
  }
34
36
 
35
37
  try {
36
- const query = `${this.info.author} - ${this.info.title}`;
38
+ const { author, title } = this.info;
39
+ const query = `${author} - ${title}`;
40
+
37
41
  const result = await aqua.resolve({
38
42
  query,
39
43
  source: searchPlatform,
@@ -42,13 +46,14 @@ class Track {
42
46
  });
43
47
 
44
48
  if (!result?.tracks?.length) return null;
49
+
45
50
  const track = this._findMatchingTrack(result.tracks);
46
51
  if (!track) return null;
47
52
 
48
- // Update track info if a match is found
49
53
  this.info.identifier = track.info.identifier;
50
54
  this.track = track.track;
51
55
  this.playlist = track.playlist || null;
56
+
52
57
  return this;
53
58
  } catch (error) {
54
59
  console.error("Error resolving track:", error);
@@ -58,6 +63,7 @@ class Track {
58
63
 
59
64
  _findMatchingTrack(tracks) {
60
65
  const { author, title, length } = this.info;
66
+
61
67
  for (const track of tracks) {
62
68
  const tInfo = track.info;
63
69
  if (author === tInfo.author && title === tInfo.title) {
@@ -66,8 +72,9 @@ class Track {
66
72
  }
67
73
  }
68
74
  }
75
+
69
76
  return null;
70
77
  }
71
78
  }
72
79
 
73
- module.exports = Track;
80
+ module.exports = Track;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aqualink",
3
- "version": "2.0.2",
3
+ "version": "2.1.1",
4
4
  "description": "An Lavalink client, focused in pure performance and features",
5
5
  "main": "build/index.js",
6
6
  "types": "index.d.ts",