aqualink 1.8.1-beta2 → 1.8.1-beta4

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
@@ -31,8 +31,13 @@ This code is based in riffy, but its an 100% Rewrite made from scratch...
31
31
 
32
32
  # Brick by brick, 1.8.0 Update (yay)
33
33
 
34
- - Misc changes on FetchImage (improves the overall checking and speed)
34
+ ### 1.8.1-beta4 Update:
35
+ - Use pool for connections (Experimental, help me improve it. Undici pool)
36
+ - Default will not leave the VC anymore (leaveOnEnd: false default)
37
+ - Misc optimizations on node and player
35
38
 
39
+ ### 1.8.0
40
+ - Misc changes on FetchImage (improves the overall checking and speed)
36
41
  - Rewrite `AQUA` module
37
42
  - Remade the resolve logic (improves the speed by a lot)
38
43
  - Fixes many memory usages related to nodes
@@ -109,7 +114,7 @@ const nodes = [
109
114
  }
110
115
  ];
111
116
 
112
- const aqua = new Aqua(client, nodes, {
117
+ const aqua = Aqua(client, nodes, {
113
118
  defaultSearchPlatform: "ytsearch",
114
119
  restVersion: "v4",
115
120
  autoResume: false,
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
 
3
3
  const { EventEmitter } = require("node:events");
4
- const Node = require("./Node");
5
- const Player = require("./Player");
6
- const Track = require("./Track");
4
+ const Node = require("./Node");
5
+ const Player = require("./Player");
6
+ const Track = require("./Track");
7
7
  const { version: pkgVersion } = require("../../package.json");
8
8
  const URL_REGEX = /^https?:\/\//;
9
9
 
@@ -37,7 +37,7 @@ class Connection {
37
37
  : `Voice Server: ${newRegion}`;
38
38
 
39
39
  this.aqua.emit("debug", `[Player ${this.guildId} - CONNECTION] ${message}`);
40
- this._updatePlayerVoiceData();
40
+ this._updatePlayerVoiceData();
41
41
  }
42
42
  }
43
43
 
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
-
3
2
  const WebSocket = require("ws");
4
- const Rest = require("./Rest");
3
+ const Rest = require("./Rest");
5
4
 
6
5
  class Node {
7
6
  #ws = null;
8
- #lastStatsRequest = 0;
9
7
  #reconnectAttempted = 0;
10
8
  #reconnectTimeoutId = null;
9
+ static BACKOFF_MULTIPLIER = 1.5;
10
+ static MAX_BACKOFF = 60000;
11
11
 
12
12
  constructor(aqua, connOptions, options = {}) {
13
13
  const {
@@ -38,15 +38,15 @@ class Node {
38
38
  this.infiniteReconnects = options.infiniteReconnects || false;
39
39
  this.connected = false;
40
40
  this.info = null;
41
- this.stats = this.#createStats();
42
-
41
+ this.defaultStats = this.#createDefaultStats();
42
+ this.stats = { ...this.defaultStats };
43
43
  this._onOpen = this.#onOpen.bind(this);
44
44
  this._onError = this.#onError.bind(this);
45
45
  this._onMessage = this.#onMessage.bind(this);
46
46
  this._onClose = this.#onClose.bind(this);
47
47
  }
48
48
 
49
- #createStats() {
49
+ #createDefaultStats() {
50
50
  return {
51
51
  players: 0,
52
52
  playingPlayers: 0,
@@ -62,14 +62,11 @@ class Node {
62
62
  this.#ws = new WebSocket(this.wsUrl.href, {
63
63
  headers: this.#constructHeaders(),
64
64
  perMessageDeflate: false,
65
- handshakeTimeout: 30000
66
65
  });
67
-
68
66
  this.#ws.once("open", this._onOpen);
69
67
  this.#ws.once("error", this._onError);
70
68
  this.#ws.on("message", this._onMessage);
71
69
  this.#ws.once("close", this._onClose);
72
- this.aqua.emit("debug", this.name, "Connecting...");
73
70
  }
74
71
 
75
72
  #constructHeaders() {
@@ -86,30 +83,46 @@ class Node {
86
83
  this.connected = true;
87
84
  this.#reconnectAttempted = 0;
88
85
  this.aqua.emit("debug", this.name, `Connected to ${this.wsUrl.href}`);
89
- try {
90
- this.info = await this.rest.makeRequest("GET", "/v4/info");
91
- if (this.autoResume) await this.resumePlayers();
92
- } catch (err) {
93
- this.info = null;
94
- if (!this.aqua.bypassChecks?.nodeFetchInfo) {
95
- this.aqua.emit("error", `Failed to fetch node info: ${err.message}`);
86
+
87
+ if (this.autoResume) {
88
+ try {
89
+ this.info = await this.rest.makeRequest("GET", "/v4/info");
90
+ await this.resumePlayers();
91
+ } catch (err) {
92
+ this.info = null;
93
+ if (!this.aqua.bypassChecks?.nodeFetchInfo) {
94
+ this.aqua.emit("error", `Failed to fetch node info: ${err.message}`);
95
+ }
96
96
  }
97
97
  }
98
98
  }
99
99
 
100
100
  async getStats() {
101
- if (!this.connected) return this.stats;
102
- const now = Date.now();
103
- const STATS_COOLDOWN = 60000;
104
- if (now - this.#lastStatsRequest < STATS_COOLDOWN) return this.stats;
101
+ const stats = await this.rest.makeRequest("GET", "/v4/stats");
102
+ this.stats = { ...this.defaultStats, ...stats };
103
+ return this.stats;
104
+ }
105
+
106
+ async #onMessage(msg) {
107
+ let payload;
105
108
  try {
106
- const stats = await this.rest.makeRequest("GET", "/v4/stats");
107
- this.stats = { ...this.#createStats(), ...stats };
108
- this.#lastStatsRequest = now;
109
- } catch (err) {
110
- this.aqua.emit("debug", `Stats fetch error: ${err.message}`);
109
+ payload = JSON.parse(msg);
110
+ } catch {
111
+ return;
112
+ }
113
+ const op = payload?.op;
114
+ if (!op) return;
115
+
116
+ switch (op) {
117
+ case "stats":
118
+ this.#updateStats(payload);
119
+ break;
120
+ case "ready":
121
+ this.#handleReadyOp(payload);
122
+ break;
123
+ default:
124
+ this.#handlePlayerOp(payload);
111
125
  }
112
- return this.stats;
113
126
  }
114
127
 
115
128
  #updateStats(payload) {
@@ -120,7 +133,7 @@ class Node {
120
133
  memory: this.#updateMemoryStats(payload.memory),
121
134
  cpu: this.#updateCpuStats(payload.cpu),
122
135
  frameStats: this.#updateFrameStats(payload.frameStats)
123
- };
136
+ };
124
137
  }
125
138
 
126
139
  #updateMemoryStats(memory = {}) {
@@ -156,30 +169,6 @@ class Node {
156
169
  };
157
170
  }
158
171
 
159
- #onMessage(msg) {
160
- let payload;
161
- try {
162
- payload = JSON.parse(msg);
163
- } catch {
164
- return;
165
- }
166
-
167
- const op = payload?.op;
168
- if (!op) return;
169
-
170
- // Use switch for better performance with multiple conditions
171
- switch (op) {
172
- case "stats":
173
- this.#updateStats(payload);
174
- break;
175
- case "ready":
176
- this.#handleReadyOp(payload);
177
- break;
178
- default:
179
- this.#handlePlayerOp(payload);
180
- }
181
- }
182
-
183
172
  #handleReadyOp(payload) {
184
173
  if (this.sessionId !== payload.sessionId) {
185
174
  this.sessionId = payload.sessionId;
@@ -209,22 +198,18 @@ class Node {
209
198
  setTimeout(() => this.connect(), 10000);
210
199
  return;
211
200
  }
212
-
213
201
  if (this.#reconnectAttempted >= this.reconnectTries) {
214
202
  this.aqua.emit("nodeError", this,
215
203
  new Error(`Max reconnection attempts reached (${this.reconnectTries})`));
216
204
  this.destroy(true);
217
205
  return;
218
206
  }
219
-
220
207
  clearTimeout(this.#reconnectTimeoutId);
221
-
222
208
  const jitter = Math.random() * 10000;
223
209
  const backoffTime = Math.min(
224
210
  this.reconnectTimeout * Math.pow(Node.BACKOFF_MULTIPLIER, this.#reconnectAttempted) + jitter,
225
211
  Node.MAX_BACKOFF
226
212
  );
227
-
228
213
  this.#reconnectTimeoutId = setTimeout(() => {
229
214
  this.#reconnectAttempted++;
230
215
  this.aqua.emit("nodeReconnect", {
@@ -236,22 +221,6 @@ class Node {
236
221
  }, backoffTime);
237
222
  }
238
223
 
239
- get penalties() {
240
- if (!this.connected) return Number.MAX_SAFE_INTEGER;
241
-
242
- let penalties = this.stats.players;
243
-
244
- const { cpu, frameStats } = this.stats;
245
- if (cpu?.systemLoad) {
246
- penalties += Math.round(Math.pow(1.05, 100 * cpu.systemLoad) * 10 - 10);
247
- }
248
- if (frameStats) {
249
- penalties += frameStats.deficit + (frameStats.nulled * 2);
250
- }
251
-
252
- return penalties;
253
- }
254
-
255
224
  destroy(clean = false) {
256
225
  if (clean) {
257
226
  this.aqua.emit("nodeDestroy", this);
@@ -269,9 +238,7 @@ class Node {
269
238
  this.aqua.nodeMap.delete(this.name);
270
239
  this.aqua.emit("nodeDestroy", this);
271
240
  this.info = null;
272
- this.#lastStatsRequest = 0;
273
- this.stats = this.#createStats();
274
241
  }
275
242
  }
276
243
 
277
- module.exports = Node
244
+ module.exports = Node;
@@ -45,11 +45,15 @@ class Player extends EventEmitter {
45
45
  this.nowPlayingMessage = null;
46
46
  this.previousTracks = [];
47
47
  this.shouldDeleteMessage = options.shouldDeleteMessage ?? false;
48
- this.leaveOnEnd = options.leaveOnEnd ?? true;
48
+ this.leaveOnEnd = options.leaveOnEnd ?? false;
49
49
 
50
50
  this.onPlayerUpdate = ({ state } = {}) => {
51
51
  if (!state) return;
52
- Object.assign(this, state);
52
+ for (const key in state) {
53
+ if (state.hasOwnProperty(key)) {
54
+ this[key] = state[key];
55
+ }
56
+ }
53
57
  this.aqua.emit("playerUpdate", this, { state });
54
58
  };
55
59
  this.handleEvent = async (payload) => {
@@ -64,7 +68,6 @@ class Player extends EventEmitter {
64
68
  };
65
69
  this.on("playerUpdate", this.onPlayerUpdate);
66
70
  this.on("event", this.handleEvent);
67
- this.#dataStore = new Map();
68
71
  }
69
72
 
70
73
  get previous() {
@@ -308,8 +311,7 @@ class Player extends EventEmitter {
308
311
  this.aqua.send({ op: 4, d: data });
309
312
  }
310
313
 
311
- // Optimize data storage
312
- #dataStore;
314
+ #dataStore = new Map();
313
315
 
314
316
  set(key, value) {
315
317
  this.#dataStore.set(key, value);
@@ -324,11 +326,8 @@ class Player extends EventEmitter {
324
326
  return this;
325
327
  }
326
328
 
327
- async updatePlayer(data) {
328
- return this.nodes.rest.updatePlayer({
329
- guildId: this.guildId,
330
- data,
331
- });
329
+ updatePlayer(data) {
330
+ return this.nodes.rest.updatePlayer({ guildId: this.guildId, data });
332
331
  }
333
332
 
334
333
  handleUnknownEvent(payload) {
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
- const { request } = require("undici");
2
+ const { Pool } = require("undici");
3
3
 
4
4
  class Rest {
5
- constructor(aqua, { secure, host, port, sessionId, password }) {
5
+ constructor(aqua, { secure, host, port, sessionId, password,}) {
6
6
  this.aqua = aqua;
7
7
  this.sessionId = sessionId;
8
8
  this.version = "v4";
@@ -11,6 +11,9 @@ class Rest {
11
11
  "Content-Type": "application/json",
12
12
  Authorization: password,
13
13
  };
14
+ this.client = new Pool(this.baseUrl, {
15
+ pipelining: 1,
16
+ });
14
17
  }
15
18
 
16
19
  setSessionId(sessionId) {
@@ -19,21 +22,15 @@ class Rest {
19
22
 
20
23
  async makeRequest(method, endpoint, body = null) {
21
24
  const options = {
25
+ path: endpoint,
22
26
  method,
23
27
  headers: this.headers,
24
- body: body ? JSON.stringify(body) : undefined,
28
+ ...(body && { body: JSON.stringify(body) }),
25
29
  };
26
-
27
30
  try {
28
- const { statusCode, headers, body: responseBody } = await request(`${this.baseUrl}${endpoint}`, options);
29
- this.aqua.emit("apiResponse", endpoint, { status: statusCode, headers: headers });
30
-
31
- if (statusCode === 204) {
32
- return null;
33
- }
34
-
35
- const data = await responseBody.text();
36
- return data ? JSON.parse(data) : null;
31
+ const response = await this.client.request(options);
32
+ const { statusCode } = response;
33
+ return statusCode === 204 ? null : await response.body.json();
37
34
  } catch (error) {
38
35
  throw new Error(`Request to ${endpoint} failed: ${error.message}`);
39
36
  }
@@ -43,16 +40,21 @@ class Rest {
43
40
  const validSegments = segments.filter(segment => segment && segment.trim());
44
41
  return '/' + validSegments.join('/');
45
42
  }
43
+
46
44
  validateSessionId() {
47
45
  if (!this.sessionId) {
48
46
  throw new Error("Session ID is not set.");
49
47
  }
50
48
  }
51
49
 
52
- updatePlayer({ guildId, data }) {
53
- if ((data.track?.encoded && data.track?.identifier) || (data.encodedTrack && data.identifier)) {
50
+ async updatePlayer({ guildId, data }) {
51
+ const hasEncodedTrack = data.track?.encoded && data.track?.identifier;
52
+ const hasEncodedTrackAlt = data.encodedTrack && data.identifier;
53
+
54
+ if (hasEncodedTrack || hasEncodedTrackAlt) {
54
55
  throw new Error("Cannot provide both 'encoded' and 'identifier' for track");
55
56
  }
57
+
56
58
  this.validateSessionId();
57
59
  const endpoint = this.buildEndpoint(this.version, "sessions", this.sessionId, "players", guildId) + "?noReplace=false";
58
60
  return this.makeRequest("PATCH", endpoint, data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aqualink",
3
- "version": "1.8.1-beta2",
3
+ "version": "1.8.1-beta4",
4
4
  "description": "An Lavalink wrapper, focused in speed, performance, and features, Based in Riffy!",
5
5
  "main": "build/index.js",
6
6
  "types": "index.d.ts",
@@ -38,7 +38,7 @@
38
38
  "license": "ISC",
39
39
  "dependencies": {
40
40
  "undici": "^7.3.0",
41
- "ws": "^8.18.0"
41
+ "ws": "^8.18.1"
42
42
  },
43
43
  "repository": {
44
44
  "type": "git",