aqualink 2.1.3 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,50 +22,47 @@ This code is based in riffy, but its an 100% Rewrite made from scratch...
22
22
  - https://github.com/topi314/LavaLyrics (RECOMMENDED)
23
23
  - https://github.com/DRSchlaubi/lyrics.kt (?)
24
24
  - https://github.com/DuncteBot/java-timed-lyrics (RECOMMENDED)
25
-
26
- # Tralalero Tralala 2.1.0 Released
25
+
26
+
27
+ # Tralalero Tralala 2.2.0 Released
27
28
  ---
28
29
  - Improved the `AQUA` module
29
- - Faster nodes loading
30
- - Faster plugin loading
31
- - Better listeners for player
32
- - Faster resolving system for playlists
33
-
34
- - Remade the `Connection` system
35
- - Less overheard now.
36
- - Faster connections
37
- - Improved the checkings
38
- - Improved error handling
39
- - Fixed creating useless Objects and arrays
40
-
41
- - Fully rewrite the `Node` system
42
- - Way faster connections
43
- - More stable (i think so)
44
- - Faster events / messages / payloads handling
45
- - Better stats handling (reusing, creating, destroyin)
46
- - Some more bug fixes and stuff i forgot.
47
-
48
- - Remade the `Player` module
49
- - Now support Lazy Loading by default
50
- - Better State Updates
51
- - Improved Garbage Collection
52
- - Rewrite to use direct comparasions
30
+ - Added Fast path in getRequestNode ( Reduces unnecessary type checks )
31
+ - Early return in handleNoMatches ( Avoids unnecessary Spotify requests )
32
+ - Rewrite to use manual loops on constructResponse ( faster than Array.prototype.map, makes the playlists and tracks load way faster and less recourses )
33
+ - Pre-allocated arrays ( Avoids dynamic resizing )
34
+ - Also fixed it sending double requests to lavalink.
53
35
 
54
- - Improved the `Rest` module
55
- - Lazy loading of http2
56
- - Faster request chunks
57
- - Some overall upgrades
58
36
 
59
- - Improved `Track` module
60
- - Faster track looking
61
- - More micro optimizations (use Boolean instead of !!)
37
+ - Remade the `Player` module
38
+ - More efficient track addition, void Array re-call
39
+ - faster event handling with direct states
40
+ - Faster autoplay system and more efficient by map()
41
+ - Reduced Object Creation
42
+ - Rewrite destroy() method
43
+ - Also improved Resource Cleanup
44
+ - Now emit TrackEnd and queueEnd correctly
45
+
46
+ - Rewrite the `autoplay` system
47
+ - Added redirect handling
48
+ - More efficient regex processing
49
+ - Set for unique URLs to avoid duplicate
50
+ - Use array chunks for better performance
51
+ - About ~30%-40% faster for resolving now.
52
+
53
+ - Rewrite the Filter system
54
+ - Uses Direct Assigments
55
+ - Avoid recreating objects on each update
56
+ - Property reuse in updateFilters()
57
+ - Uses traditional for loop
62
58
 
63
- - Remade the INDEX.D.TS File: Added more 1000 lines of code. Added autocomplete, options, and documented everything.
64
59
 
65
60
  # Docs (Wiki)
66
- - https://github.com/ToddyTheNoobDud/AquaLink/wiki
61
+ - https://toddythenoobdud.github.io/aqualink.github.io/
62
+
63
+ - Example bot: https://github.com/ToddyTheNoobDud/Kenium-Music
67
64
 
68
- - Example bot: https://github.com/ToddyTheNoobDud/Thorium-Music
65
+ - Discord support: https://discord.com/invite/K4CVv84VBC
69
66
 
70
67
 
71
68
  # How to install
@@ -147,7 +144,9 @@ client.on("messageCreate", async (message) => {
147
144
 
148
145
  if (resolve.loadType === 'playlist') {
149
146
  await message.channel.send(`Added ${resolve.tracks.length} songs from ${resolve.playlistInfo.name} playlist.`);
150
- player.queue.add(resolve.tracks);
147
+ for (const track of result.tracks) {
148
+ player.queue.add(track);
149
+ }
151
150
  if (!player.playing && !player.paused) return player.play();
152
151
 
153
152
  } else if (resolve.loadType === 'search' || resolve.loadType === 'track') {
@@ -3,15 +3,30 @@ const https = require('https');
3
3
  function fetch(url, options = {}) {
4
4
  return new Promise((resolve, reject) => {
5
5
  const req = https.get(url, options, (res) => {
6
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
7
+ return fetch(res.headers.location, options).then(resolve).catch(reject);
8
+ }
9
+
6
10
  if (res.statusCode !== 200) {
11
+ res.resume();
7
12
  reject(new Error(`Request failed. Status code: ${res.statusCode}`));
8
13
  return;
9
14
  }
10
- let data = '';
11
- res.on('data', (chunk) => data += chunk);
12
- res.on('end', () => resolve(data));
15
+
16
+ const chunks = [];
17
+ res.on('data', chunk => chunks.push(chunk));
18
+ res.on('end', () => resolve(Buffer.concat(chunks).toString()));
19
+ });
20
+
21
+ req.setTimeout(10000, () => {
22
+ req.destroy();
23
+ reject(new Error('Request timeout'));
24
+ });
25
+
26
+ req.on('error', err => {
27
+ reject(err);
13
28
  });
14
- req.on('error', reject);
29
+
15
30
  req.end();
16
31
  });
17
32
  }
@@ -20,15 +35,23 @@ async function scAutoPlay(url) {
20
35
  try {
21
36
  const html = await fetch(`${url}/recommended`);
22
37
 
23
- const matches = [...html.matchAll(/<a itemprop="url" href="(\/.*?)"/g)];
38
+ const regex = /<a itemprop="url" href="(\/.*?)"/g;
39
+ const hrefs = new Set();
40
+ let match;
24
41
 
25
- const hrefs = [...new Set(matches.map(match => `https://soundcloud.com${match[1]}`))];
42
+ while ((match = regex.exec(html)) !== null) {
43
+ hrefs.add(`https://soundcloud.com${match[1]}`);
44
+ }
26
45
 
27
- if (hrefs.length === 0) {
46
+ if (hrefs.size === 0) {
28
47
  throw new Error("No recommended tracks found on SoundCloud.");
29
48
  }
30
49
 
31
- const shuffledHrefs = hrefs.sort(() => Math.random() - 0.5);
50
+ const shuffledHrefs = Array.from(hrefs);
51
+ for (let i = shuffledHrefs.length - 1; i > 0; i--) {
52
+ const j = Math.floor(Math.random() * (i + 1));
53
+ [shuffledHrefs[i], shuffledHrefs[j]] = [shuffledHrefs[j], shuffledHrefs[i]];
54
+ }
32
55
 
33
56
  return shuffledHrefs;
34
57
  } catch (error) {
@@ -40,21 +63,28 @@ async function scAutoPlay(url) {
40
63
  async function spAutoPlay(track_id) {
41
64
  try {
42
65
  const tokenResponse = await fetch("https://open.spotify.com/get_access_token?reason=transport&productType=embed");
43
- const { accessToken } = JSON.parse(tokenResponse);
66
+ const tokenData = JSON.parse(tokenResponse);
67
+ const accessToken = tokenData?.accessToken;
44
68
 
45
69
  if (!accessToken) throw new Error("Failed to retrieve Spotify access token");
46
70
 
47
- const recommendationsResponse = await fetch(`https://api.spotify.com/v1/recommendations?limit=10&seed_tracks=${track_id}`, {
48
- headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' }
49
- });
71
+ const recommendationsResponse = await fetch(
72
+ `https://api.spotify.com/v1/recommendations?limit=5&seed_tracks=${track_id}&fields=tracks.id`,
73
+ {
74
+ headers: {
75
+ Authorization: `Bearer ${accessToken}`,
76
+ 'Content-Type': 'application/json'
77
+ }
78
+ }
79
+ );
50
80
 
51
- const { tracks } = JSON.parse(recommendationsResponse);
81
+ const data = JSON.parse(recommendationsResponse);
82
+ const tracks = data?.tracks || [];
52
83
 
53
- if (!tracks || tracks.length === 0) {
84
+ if (tracks.length === 0) {
54
85
  throw new Error("No recommended tracks found on Spotify.");
55
86
  }
56
87
 
57
- // Return a random track ID
58
88
  return tracks[Math.floor(Math.random() * tracks.length)].id;
59
89
  } catch (error) {
60
90
  console.error("Error fetching Spotify recommendations:", error);
@@ -21,7 +21,7 @@ class Aqua extends EventEmitter {
21
21
  this.players = new Map();
22
22
  this.clientId = null;
23
23
  this.initiated = false;
24
-
24
+
25
25
  this.shouldDeleteMessage = options.shouldDeleteMessage ?? false;
26
26
  this.defaultSearchPlatform = options.defaultSearchPlatform ?? 'ytsearch';
27
27
  this.leaveOnEnd = options.leaveOnEnd ?? true;
@@ -32,7 +32,7 @@ class Aqua extends EventEmitter {
32
32
  this.autoResume = options.autoResume ?? false;
33
33
  this.infiniteReconnects = options.infiniteReconnects ?? false;
34
34
  this.options = options;
35
-
35
+
36
36
  this.setMaxListeners(0);
37
37
  this._leastUsedCache = { nodes: [], timestamp: 0 };
38
38
  }
@@ -67,11 +67,11 @@ class Aqua extends EventEmitter {
67
67
  if (this.initiated) return this;
68
68
 
69
69
  this.clientId = clientId;
70
-
70
+
71
71
  try {
72
72
  for (let i = 0; i < this.nodes.length; i++) { this.createNode(this.nodes[i]); }
73
73
  if (this.plugins.length > 0) { for (let i = 0; i < this.plugins.length; i++) { this.plugins[i].load(this); } }
74
-
74
+
75
75
  this.initiated = true;
76
76
  } catch (error) {
77
77
  this.initiated = false;
@@ -129,7 +129,7 @@ class Aqua extends EventEmitter {
129
129
  if (!region) return this.leastUsedNodes;
130
130
 
131
131
  const lowerRegion = region.toLowerCase();
132
- const regionNodes = Array.from(this.nodeMap.values()).filter(node =>
132
+ const regionNodes = Array.from(this.nodeMap.values()).filter(node =>
133
133
  node.connected && node.regions?.includes(lowerRegion)
134
134
  );
135
135
  regionNodes.sort((a, b) => this.calculateLoad(a) - this.calculateLoad(b));
@@ -151,7 +151,7 @@ class Aqua extends EventEmitter {
151
151
  const availableNodes = options.region ? this.fetchRegion(options.region) : this.leastUsedNodes;
152
152
  const node = availableNodes[0];
153
153
  if (!node) throw new Error("No nodes are available");
154
-
154
+
155
155
  return this.createPlayer(node, options);
156
156
  }
157
157
 
@@ -159,11 +159,11 @@ class Aqua extends EventEmitter {
159
159
  this.destroyPlayer(options.guildId);
160
160
  const player = new Player(this, node, options);
161
161
  this.players.set(options.guildId, player);
162
-
162
+
163
163
  player.once("destroy", () => {
164
164
  this.players.delete(options.guildId);
165
165
  });
166
-
166
+
167
167
  player.connect(options);
168
168
  this.emit("playerCreate", player);
169
169
  return player;
@@ -193,7 +193,7 @@ class Aqua extends EventEmitter {
193
193
  if (["empty", "NO_MATCHES"].includes(response.loadType)) {
194
194
  return await this.handleNoMatches(requestNode.rest, query);
195
195
  }
196
- return this.constructorResponse(response, requester, requestNode);
196
+ return this.constructResponse(response, requester, requestNode);
197
197
  } catch (error) {
198
198
  if (error.name === "AbortError") {
199
199
  throw new Error("Request timed out");
@@ -203,9 +203,12 @@ class Aqua extends EventEmitter {
203
203
  }
204
204
 
205
205
  getRequestNode(nodes) {
206
- if (nodes && !(typeof nodes === "string" || nodes instanceof Node)) {
206
+ if (!nodes) return this.leastUsedNodes[0];
207
+
208
+ if (!(typeof nodes === "string" || nodes instanceof Node)) {
207
209
  throw new TypeError(`'nodes' must be a string or Node instance, received: ${typeof nodes}`);
208
210
  }
211
+
209
212
  return (typeof nodes === "string" ? this.nodeMap.get(nodes) : nodes) ?? this.leastUsedNodes[0];
210
213
  }
211
214
 
@@ -221,17 +224,20 @@ class Aqua extends EventEmitter {
221
224
  try {
222
225
  const ytIdentifier = `/v4/loadtracks?identifier=https://www.youtube.com/watch?v=${query}`;
223
226
  const youtubeResponse = await rest.makeRequest("GET", ytIdentifier);
224
- if (["empty", "NO_MATCHES"].includes(youtubeResponse.loadType)) {
225
- const spotifyIdentifier = `/v4/loadtracks?identifier=https://open.spotify.com/track/${query}`;
226
- return await rest.makeRequest("GET", spotifyIdentifier);
227
+
228
+ if (!["empty", "NO_MATCHES"].includes(youtubeResponse.loadType)) {
229
+ return youtubeResponse;
227
230
  }
228
- return youtubeResponse;
231
+
232
+ const spotifyIdentifier = `/v4/loadtracks?identifier=https://open.spotify.com/track/${query}`;
233
+ return await rest.makeRequest("GET", spotifyIdentifier);
229
234
  } catch (error) {
230
235
  console.error(`Failed to resolve track: ${error.message}`);
236
+ throw error;
231
237
  }
232
238
  }
233
239
 
234
- constructorResponse(response, requester, requestNode) {
240
+ constructResponse(response, requester, requestNode) {
235
241
  const baseResponse = {
236
242
  loadType: response.loadType,
237
243
  exception: null,
@@ -245,8 +251,7 @@ class Aqua extends EventEmitter {
245
251
  return baseResponse;
246
252
  }
247
253
 
248
- // Inline the track factory for better performance
249
- const trackFactory = trackData => new Track(trackData, requester, requestNode);
254
+ const trackFactory = (trackData) => new Track(trackData, requester, requestNode);
250
255
 
251
256
  switch (response.loadType) {
252
257
  case "track":
@@ -254,6 +259,7 @@ class Aqua extends EventEmitter {
254
259
  baseResponse.tracks.push(trackFactory(response.data));
255
260
  }
256
261
  break;
262
+
257
263
  case "playlist":
258
264
  if (response.data?.info) {
259
265
  baseResponse.playlistInfo = {
@@ -261,26 +267,32 @@ class Aqua extends EventEmitter {
261
267
  ...response.data.info
262
268
  };
263
269
  }
270
+
264
271
  const tracks = response.data?.tracks;
265
272
  if (tracks?.length) {
266
- baseResponse.tracks = new Array(tracks.length);
267
- for (let i = 0; i < tracks.length; i++) {
273
+ const len = tracks.length;
274
+ baseResponse.tracks = new Array(len);
275
+ for (let i = 0; i < len; i++) {
268
276
  baseResponse.tracks[i] = trackFactory(tracks[i]);
269
277
  }
270
278
  }
271
279
  break;
280
+
272
281
  case "search":
273
282
  const searchData = response.data ?? [];
274
283
  if (searchData.length) {
275
- baseResponse.tracks = new Array(searchData.length);
276
- for (let i = 0; i < searchData.length; i++) {
284
+ const len = searchData.length;
285
+ baseResponse.tracks = new Array(len);
286
+ for (let i = 0; i < len; i++) {
277
287
  baseResponse.tracks[i] = trackFactory(searchData[i]);
278
288
  }
279
289
  }
280
290
  break;
281
291
  }
292
+
282
293
  return baseResponse;
283
294
  }
295
+
284
296
  get(guildId) {
285
297
  const player = this.players.get(guildId);
286
298
  if (!player) throw new Error(`Player not found for guild ID: ${guildId}`);
@@ -3,21 +3,49 @@
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;
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
+
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
33
+ };
34
+ }
35
+
36
+ _setFilter(filterName, enabled, options, defaults) {
37
+ if (!enabled) {
38
+ this[filterName] = null;
39
+ return this.updateFilters();
40
+ }
41
+
42
+ const filterObj = {};
43
+ for (const [key, defaultValue] of Object.entries(defaults)) {
44
+ filterObj[key] = options[key] !== undefined ? options[key] : defaultValue;
45
+ }
46
+
47
+ this[filterName] = filterObj;
48
+ return this.updateFilters();
21
49
  }
22
50
 
23
51
  setEqualizer(bands) {
@@ -26,127 +54,137 @@ class Filters {
26
54
  }
27
55
 
28
56
  setKaraoke(enabled, options = {}) {
29
- this.karaoke = enabled ? {
30
- level: options.level ?? 1.0,
31
- monoLevel: options.monoLevel ?? 1.0,
32
- filterBand: options.filterBand ?? 220.0,
33
- filterWidth: options.filterWidth ?? 100.0
34
- } : null;
35
- return this.updateFilters();
57
+ return this._setFilter('karaoke', enabled, options, {
58
+ level: 1.0,
59
+ monoLevel: 1.0,
60
+ filterBand: 220.0,
61
+ filterWidth: 100.0
62
+ });
36
63
  }
37
64
 
38
65
  setTimescale(enabled, options = {}) {
39
- this.timescale = enabled ? {
40
- speed: options.speed ?? 1.0,
41
- pitch: options.pitch ?? 1.0,
42
- rate: options.rate ?? 1.0
43
- } : null;
44
- return this.updateFilters();
66
+ return this._setFilter('timescale', enabled, options, {
67
+ speed: 1.0,
68
+ pitch: 1.0,
69
+ rate: 1.0
70
+ });
45
71
  }
46
72
 
47
73
  setTremolo(enabled, options = {}) {
48
- this.tremolo = enabled ? {
49
- frequency: options.frequency ?? 2.0,
50
- depth: options.depth ?? 0.5
51
- } : null;
52
- return this.updateFilters();
74
+ return this._setFilter('tremolo', enabled, options, {
75
+ frequency: 2.0,
76
+ depth: 0.5
77
+ });
53
78
  }
54
79
 
55
80
  setVibrato(enabled, options = {}) {
56
- this.vibrato = enabled ? {
57
- frequency: options.frequency ?? 2.0,
58
- depth: options.depth ?? 0.5
59
- } : null;
60
- return this.updateFilters();
81
+ return this._setFilter('vibrato', enabled, options, {
82
+ frequency: 2.0,
83
+ depth: 0.5
84
+ });
61
85
  }
62
86
 
63
87
  setRotation(enabled, options = {}) {
64
- this.rotation = enabled ? {
65
- rotationHz: options.rotationHz ?? 0.0
66
- } : null;
67
- return this.updateFilters();
88
+ return this._setFilter('rotation', enabled, options, {
89
+ rotationHz: 0.0
90
+ });
68
91
  }
69
92
 
70
93
  setDistortion(enabled, options = {}) {
71
- this.distortion = enabled ? {
72
- sinOffset: options.sinOffset ?? 0.0,
73
- sinScale: options.sinScale ?? 1.0,
74
- cosOffset: options.cosOffset ?? 0.0,
75
- cosScale: options.cosScale ?? 1.0,
76
- tanOffset: options.tanOffset ?? 0.0,
77
- tanScale: options.tanScale ?? 1.0,
78
- offset: options.offset ?? 0.0,
79
- scale: options.scale ?? 1.0
80
- } : null;
81
- return this.updateFilters();
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
+ });
82
104
  }
83
105
 
84
106
  setChannelMix(enabled, options = {}) {
85
- this.channelMix = enabled ? {
86
- leftToLeft: options.leftToLeft ?? 1.0,
87
- leftToRight: options.leftToRight ?? 0.0,
88
- rightToLeft: options.rightToLeft ?? 0.0,
89
- rightToRight: options.rightToRight ?? 1.0
90
- } : null;
91
- return this.updateFilters();
107
+ return this._setFilter('channelMix', enabled, options, {
108
+ leftToLeft: 1.0,
109
+ leftToRight: 0.0,
110
+ rightToLeft: 0.0,
111
+ rightToRight: 1.0
112
+ });
92
113
  }
93
114
 
94
115
  setLowPass(enabled, options = {}) {
95
- this.lowPass = enabled ? {
96
- smoothing: options.smoothing ?? 20.0
97
- } : null;
98
- return this.updateFilters();
116
+ return this._setFilter('lowPass', enabled, options, {
117
+ smoothing: 20.0
118
+ });
99
119
  }
100
120
 
101
121
  setBassboost(enabled, options = {}) {
102
- if (enabled) {
103
- const value = options.value ?? 5;
104
- if (value < 0 || value > 5) throw new Error("Bassboost value must be between 0 and 5");
105
- this.bassboost = value;
106
- const num = (value - 1) * (1.25 / 9) - 0.25;
107
- return this.setEqualizer(Array(13).fill(0).map((_, i) => ({
108
- band: i,
109
- gain: num
110
- })));
122
+ if (!enabled) {
123
+ this.bassboost = null;
124
+ return this.setEqualizer([]);
111
125
  }
112
- this.bassboost = null;
113
- return this.setEqualizer([]);
126
+
127
+ const value = options.value || 5;
128
+ if (value < 0 || value > 5) throw new Error("Bassboost value must be between 0 and 5");
129
+
130
+ this.bassboost = value;
131
+ const num = (value - 1) * (1.25 / 9) - 0.25;
132
+
133
+ const eq = new Array(13);
134
+ for (let i = 0; i < 13; i++) {
135
+ eq[i] = { band: i, gain: num };
136
+ }
137
+
138
+ return this.setEqualizer(eq);
114
139
  }
115
140
 
116
141
  setSlowmode(enabled, options = {}) {
117
142
  this.slowmode = enabled;
118
- return this.setTimescale(enabled, { rate: enabled ? options.rate ?? 0.8 : 1.0 });
143
+ return this.setTimescale(enabled, { rate: enabled ? (options.rate || 0.8) : 1.0 });
119
144
  }
120
145
 
121
146
  setNightcore(enabled, options = {}) {
122
147
  this.nightcore = enabled;
123
- if (enabled) {
124
- return this.setTimescale(true, { rate: options.rate ?? 1.5 });
125
- }
126
- return this.setTimescale(false);
148
+ return this.setTimescale(enabled, { rate: enabled ? (options.rate || 1.5) : 1.0 });
127
149
  }
128
150
 
129
151
  setVaporwave(enabled, options = {}) {
130
152
  this.vaporwave = enabled;
131
- if (enabled) {
132
- return this.setTimescale(true, { pitch: options.pitch ?? 0.5 });
133
- }
134
- return this.setTimescale(false);
153
+ return this.setTimescale(enabled, { pitch: enabled ? (options.pitch || 0.5) : 1.0 });
135
154
  }
136
155
 
137
156
  set8D(enabled, options = {}) {
138
157
  this._8d = enabled;
139
- return this.setRotation(enabled, { rotationHz: enabled ? options.rotationHz ?? 0.2 : 0.0 });
158
+ return this.setRotation(enabled, { rotationHz: enabled ? (options.rotationHz || 0.2) : 0.0 });
140
159
  }
141
160
 
142
161
  async clearFilters() {
143
- Object.assign(this, new Filters(this.player));
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;
177
+
178
+ this._filterDataTemplate.volume = 1;
179
+ this._filterDataTemplate.equalizer = [];
180
+
144
181
  await this.updateFilters();
145
182
  return this;
146
183
  }
147
184
 
148
185
  async updateFilters() {
149
186
  const filterData = {
187
+ ...this._filterDataTemplate,
150
188
  volume: this.volume,
151
189
  equalizer: this.equalizer,
152
190
  karaoke: this.karaoke,
@@ -159,6 +197,8 @@ class Filters {
159
197
  lowPass: this.lowPass
160
198
  };
161
199
 
200
+ this._filterDataTemplate = { ...filterData };
201
+
162
202
  await this.player.nodes.rest.updatePlayer({
163
203
  guildId: this.player.guildId,
164
204
  data: { filters: filterData }
@@ -168,4 +208,4 @@ class Filters {
168
208
  }
169
209
  }
170
210
 
171
- module.exports = Filters
211
+ module.exports = Filters;
@@ -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,38 +36,26 @@ 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 = Array(50);
44
+ this.previousTracksCount = 0;
45
+ this.shouldDeleteMessage = Boolean(options.shouldDeleteMessage);
46
+ this.leaveOnEnd = Boolean(options.leaveOnEnd);
47
47
 
48
48
  this.playing = false;
49
49
  this.paused = false;
50
50
  this.connected = false;
51
51
  this.current = null;
52
+ this.position = 0;
52
53
  this.timestamp = 0;
53
54
  this.ping = 0;
54
55
  this.nowPlayingMessage = null;
55
-
56
+ this.isAutoplayEnabled = false;
56
57
  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
- }
68
- };
69
-
58
+
70
59
  this._handlePlayerUpdate = this._handlePlayerUpdate.bind(this);
71
60
  this._handleEvent = this._handleEvent.bind(this);
72
61
 
@@ -75,7 +64,7 @@ class Player extends EventEmitter {
75
64
  }
76
65
 
77
66
  get previous() {
78
- return this.previousTracks[0] || null;
67
+ return this.previousTracksCount > 0 ? this.previousTracks[0] : null;
79
68
  }
80
69
 
81
70
  get currenttrack() {
@@ -99,26 +88,38 @@ class Player extends EventEmitter {
99
88
 
100
89
  let query, source, response;
101
90
 
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":
91
+ const sourceHandlers = {
92
+ youtube: async () => {
93
+ return {
94
+ query: `https://www.youtube.com/watch?v=${identifier}&list=RD${identifier}`,
95
+ source: "ytmsearch"
96
+ };
97
+ },
98
+ soundcloud: async () => {
108
99
  const scResults = await scAutoPlay(uri);
109
- if (!scResults?.length) return this;
110
- query = scResults[0];
111
- source = "scsearch";
112
- break;
113
- case "spotify":
100
+ if (!scResults?.length) return null;
101
+ return {
102
+ query: scResults[0],
103
+ source: "scsearch"
104
+ };
105
+ },
106
+ spotify: async () => {
114
107
  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
- }
108
+ if (!spResult) return null;
109
+ return {
110
+ query: `https://open.spotify.com/track/${spResult}`,
111
+ source: "spotify"
112
+ };
113
+ }
114
+ };
115
+
116
+ const handler = sourceHandlers[sourceName];
117
+ if (!handler) return this;
118
+
119
+ const result = await handler();
120
+ if (!result) return this;
121
+
122
+ ({ query, source } = result);
122
123
 
123
124
  response = await this.aqua.resolve({ query, source, requester });
124
125
 
@@ -147,24 +148,33 @@ class Player extends EventEmitter {
147
148
  setAutoplay(enabled) {
148
149
  this.isAutoplayEnabled = Boolean(enabled);
149
150
  this.aqua.emit("debug", this.guildId, `Autoplay has been ${enabled ? "enabled" : "disabled"}.`);
151
+ return this;
150
152
  }
151
153
 
152
-
153
-
154
154
  addToPreviousTrack(track) {
155
155
  if (!track) return;
156
156
 
157
- if (this.previousTracks.length >= 50) {
158
- this.previousTracks.pop();
157
+ if (this.previousTracksCount < 50) {
158
+ for (let i = this.previousTracksCount; i > 0; i--) {
159
+ this.previousTracks[i] = this.previousTracks[i-1];
160
+ }
161
+ this.previousTracks[0] = track;
162
+ this.previousTracksCount++;
163
+ } else {
164
+ for (let i = 49; i > 0; i--) {
165
+ this.previousTracks[i] = this.previousTracks[i-1];
166
+ }
167
+ this.previousTracks[0] = track;
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,11 +222,12 @@ class Player extends EventEmitter {
210
222
 
211
223
  this.disconnect();
212
224
 
213
- this.nowPlayingMessage?.delete().catch(() => {});
214
-
215
- if (this._autoplay) {
216
- this._autoplay._cleanup();
225
+ if (this.nowPlayingMessage) {
226
+ this.nowPlayingMessage.delete().catch(() => {});
227
+ this.nowPlayingMessage = null;
217
228
  }
229
+
230
+ this.isAutoplay = false;
218
231
 
219
232
  this.aqua.destroyPlayer(this.guildId);
220
233
  this.nodes.rest.destroyPlayer(this.guildId);
@@ -287,6 +300,7 @@ class Player extends EventEmitter {
287
300
  }
288
301
 
289
302
  this.voiceChannel = channel;
303
+
290
304
  this.connect({
291
305
  deaf: this.deaf,
292
306
  guildId: this.guildId,
@@ -329,17 +343,14 @@ class Player extends EventEmitter {
329
343
  }
330
344
 
331
345
  async trackStart(player, track) {
332
- this.updateTrackState(true, false);
346
+ this.playing = true;
347
+ this.paused = false;
333
348
  this.aqua.emit("trackStart", player, track);
334
349
  }
335
350
 
336
- async trackChange(player, track) {
337
- this.updateTrackState(true, false);
338
- this.aqua.emit("trackChange", player, track);
339
- }
340
-
341
351
  async trackEnd(player, track, payload) {
342
352
  this.addToPreviousTrack(track);
353
+
343
354
  if (this.shouldDeleteMessage && this.nowPlayingMessage) {
344
355
  try {
345
356
  await this.nowPlayingMessage.delete();
@@ -348,12 +359,14 @@ class Player extends EventEmitter {
348
359
  console.error("Error deleting now playing message:", error);
349
360
  }
350
361
  }
351
- const reason = payload.reason?.toLowerCase().replace("_", "");
352
- if (reason === "loadfailed" || reason === "cleanup") {
362
+
363
+ const reason = payload.reason;
364
+ if (reason === "LOAD_FAILED" || reason === "CLEANUP") {
353
365
  if (!player.queue.length) {
354
366
  this.clearData();
355
367
  this.aqua.emit("queueEnd", player);
356
368
  } else {
369
+ this.aqua.emit("trackEnd", player, track, reason);
357
370
  await player.play();
358
371
  }
359
372
  return;
@@ -367,7 +380,7 @@ class Player extends EventEmitter {
367
380
 
368
381
  if (player.queue.isEmpty()) {
369
382
  if (this.isAutoplayEnabled) {
370
- await player.autoplay(player, track);
383
+ await player.autoplay(player);
371
384
  } else {
372
385
  this.playing = false;
373
386
  if (this.leaveOnEnd) {
@@ -376,8 +389,8 @@ class Player extends EventEmitter {
376
389
  }
377
390
  this.aqua.emit("queueEnd", player);
378
391
  }
379
- return;
380
392
  } else {
393
+ this.aqua.emit("trackEnd", player, track, reason);
381
394
  await player.play();
382
395
  }
383
396
  }
@@ -393,11 +406,11 @@ class Player extends EventEmitter {
393
406
  }
394
407
 
395
408
  async socketClosed(player, payload) {
396
- const code = payload && payload.code;
409
+ const { code, guildId } = payload || {};
397
410
 
398
411
  if (code === 4015 || code === 4009) {
399
412
  this.send({
400
- guild_id: payload.guildId,
413
+ guild_id: guildId,
401
414
  channel_id: this.voiceChannel,
402
415
  self_mute: this.mute,
403
416
  self_deaf: this.deaf,
@@ -415,7 +428,7 @@ class Player extends EventEmitter {
415
428
 
416
429
  set(key, value) {
417
430
  if (!this._dataStore) {
418
- this._dataStore = new WeakMap();
431
+ this._dataStore = new Map();
419
432
  }
420
433
  this._dataStore.set(key, value);
421
434
  }
@@ -425,7 +438,9 @@ class Player extends EventEmitter {
425
438
  }
426
439
 
427
440
  clearData() {
428
- if (this.previousTracks) this.previousTracks.length = 0;
441
+ if (this.previousTracks) {
442
+ this.previousTracksCount = 0;
443
+ }
429
444
  this._dataStore = null;
430
445
  return this;
431
446
  }
@@ -444,11 +459,6 @@ class Player extends EventEmitter {
444
459
  this.destroy();
445
460
  }
446
461
  }
447
-
448
- updateTrackState(playing, paused) {
449
- this.playing = playing;
450
- this.paused = paused;
451
- }
452
462
  }
453
463
 
454
464
  module.exports = Player;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aqualink",
3
- "version": "2.1.3",
3
+ "version": "2.2.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",