aqualink 1.9.0 → 2.0.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 +9 -71
- package/build/handlers/WebSocket.js +259 -0
- package/build/structures/Node.js +24 -22
- package/build/structures/Rest.js +46 -26
- package/package.json +2 -6
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ An Stable, performant, Recourse friendly and fast lavalink wrapper
|
|
|
4
4
|
This code is based in riffy, but its an 100% Rewrite made from scratch...
|
|
5
5
|
|
|
6
6
|
# Why use AquaLink
|
|
7
|
-
-
|
|
7
|
+
- Dependecy-free (0 dependencys), may change in future
|
|
8
8
|
- Very Low memory comsuption
|
|
9
9
|
- Built in Queue manager
|
|
10
10
|
- Lots of features to use
|
|
@@ -13,86 +13,24 @@ This code is based in riffy, but its an 100% Rewrite made from scratch...
|
|
|
13
13
|
- 1 Player created = ~1 - 0,5 mb per player
|
|
14
14
|
- Auto clean Up memory when song finishes / bot leave the vc (Now Options supported!)
|
|
15
15
|
- Plugin system
|
|
16
|
-
- Lavalink v4
|
|
16
|
+
- Lavalink v4.0.8 | v4.1.0 Support (Nodelink works, but only with play etc, more support soon)
|
|
17
17
|
- Youtube and Spotify support (Soundcloud, deezer, vimeo, etc also works...)
|
|
18
18
|
- Minimal Requests to the lavalink server (helps the lavalink recourses!)
|
|
19
|
-
- Easy player, node, aqua manager
|
|
20
|
-
- Fast responses from rest and node
|
|
21
19
|
- Playlist support (My mix playlists, youtube playlists, spotify playlists, etc)
|
|
22
20
|
- Lyrics Support by Lavalink
|
|
23
21
|
- https://github.com/topi314/LavaLyrics (RECOMMENDED)
|
|
24
22
|
- https://github.com/DRSchlaubi/lyrics.kt (?)
|
|
25
23
|
- https://github.com/DuncteBot/java-timed-lyrics (RECOMMENDED)
|
|
26
24
|
|
|
27
|
-
# Tralalero Tralala
|
|
28
|
-
**Whoa, lots of stuff to write here 😭**
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
### **Small changes on the `fetchImage` Handler**
|
|
33
|
-
- Improves the overall speed, less memory overhead.
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
### **Remade some stuff on `AQUA` module**
|
|
38
|
-
- This fixes some bugs related to destroying players.
|
|
39
|
-
- Faster node connection speeds.
|
|
40
|
-
- Uses an Array for getting the region instead (testing).
|
|
41
|
-
- Small change on the Voice Handler.
|
|
42
|
-
- Improved Error handling.
|
|
43
|
-
- Use `node.destroy()` method directly.
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
### **Remade `Connection` module**
|
|
48
|
-
- Removed lots of useless code.
|
|
49
|
-
- Improved joining voice channel speed.
|
|
50
|
-
- Improved configuration set/get speed.
|
|
51
|
-
- Improved overall checking.
|
|
52
|
-
- Improved debug messages.
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
### **Remade `Node` module (this one is good)**
|
|
57
|
-
- Fixed the `autoResume` system (now will actually work, for 60 seconds).
|
|
58
|
-
- New WebSocket System.
|
|
59
|
-
- Improved the events handling speed.
|
|
60
|
-
- Now does recalculation of the backoff time (for more efficiency on reconnect).
|
|
61
|
-
- Now avoids reconnecting if the WebSocket is already open (sorry, I forgot to add this before).
|
|
62
|
-
- Better cleaning system (improved, now removes listeners instead of setting to `null`).
|
|
63
|
-
- Avoids re-binding the functions every time `connect` is called (yay).
|
|
64
|
-
- This update also improves long-process running.
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
### **Remade the `Player` module (also a good one)**
|
|
69
|
-
- Remade every method.
|
|
70
|
-
- Fixed destroy system.
|
|
71
|
-
- Better event handling, I think.
|
|
72
|
-
- Made the events async.
|
|
73
|
-
- Removed `trackChange` (does not exist in Lavalink API, use `trackStart` instead).
|
|
74
|
-
- Uses a new listener system (way more efficient for creating/destroying players).
|
|
75
|
-
- Faster shuffle in V8 Engine (Math stuff).
|
|
76
|
-
- Improved overall configs (more precise).
|
|
77
|
-
- Use `pop()` instead of disabling the track 50 on the length.
|
|
78
|
-
- Improved overall speed on the check-ins and some stuff I forgot.
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
### **Remade the `Rest` module**
|
|
83
|
-
- Better speed (removed useless `buildEndpoint`).
|
|
84
|
-
- More compact code.
|
|
85
|
-
- Removed `stats/all` in the stats (correct by using the Lavalink API).
|
|
86
|
-
- Better `makeRequest`.
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
### **Small changes in `Track` module**
|
|
91
|
-
- More efficient final result (`author` + `track`).
|
|
92
|
-
|
|
25
|
+
# Tralalero Tralala 2.0.0 Released
|
|
93
26
|
---
|
|
94
27
|
|
|
95
|
-
|
|
28
|
+
### Now aqualink is 100% depedency free!
|
|
29
|
+
- Rewrite the rest to use https / http
|
|
30
|
+
- Removed undici usage
|
|
31
|
+
- Rewrite NODE to use built-in WebSocket (no need to upgrade to node 20 or more!)
|
|
32
|
+
- Uses my own made websocket system, so please report any bugs (i beg)
|
|
33
|
+
- more stuff soon, now bye
|
|
96
34
|
|
|
97
35
|
# Docs (Wiki)
|
|
98
36
|
- https://github.com/ToddyTheNoobDud/AquaLink/wiki
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
const https = require('node:https');
|
|
2
|
+
const http = require('node:http');
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const { EventEmitter } = require('node:events');
|
|
5
|
+
const { URL } = require('node:url');
|
|
6
|
+
const { Buffer } = require('node:buffer');
|
|
7
|
+
|
|
8
|
+
let nativeWs = null;
|
|
9
|
+
if (process.isBun) nativeWs = require('ws');
|
|
10
|
+
const frameHeaderPool = Buffer.alloc(10);
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BufferPool {
|
|
14
|
+
constructor(initialSize = 8192) {
|
|
15
|
+
this.buffer = Buffer.allocUnsafe(initialSize);
|
|
16
|
+
this.used = 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
append(data) {
|
|
20
|
+
const dataLength = data.length;
|
|
21
|
+
if (this.used + dataLength > this.buffer.length) {
|
|
22
|
+
const newSize = Math.max(this.buffer.length * 2, this.used + dataLength);
|
|
23
|
+
const newBuffer = Buffer.allocUnsafe(newSize);
|
|
24
|
+
this.buffer.copy(newBuffer, 0, 0, this.used);
|
|
25
|
+
this.buffer = newBuffer;
|
|
26
|
+
}
|
|
27
|
+
data.copy(this.buffer, this.used);
|
|
28
|
+
this.used += dataLength;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
consume(bytes) {
|
|
32
|
+
if (bytes === this.used) {
|
|
33
|
+
this.used = 0;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.buffer.copy(this.buffer, 0, bytes, this.used);
|
|
38
|
+
this.used -= bytes;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get data() {
|
|
42
|
+
return this.buffer.subarray(0, this.used);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class WebSocket extends EventEmitter {
|
|
47
|
+
constructor(url, options = {}) {
|
|
48
|
+
super();
|
|
49
|
+
this.url = url;
|
|
50
|
+
this.options = options;
|
|
51
|
+
this.socket = null;
|
|
52
|
+
this.bufferPool = new BufferPool();
|
|
53
|
+
this.frameInfo = null;
|
|
54
|
+
this.reconnectAttempts = 0;
|
|
55
|
+
this.maxReconnectAttempts = options.maxReconnectAttempts || 10;
|
|
56
|
+
this.baseReconnectDelay = options.baseReconnectDelay || 1000; // 1 second
|
|
57
|
+
this.connect();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
connect() {
|
|
61
|
+
const { hostname, protocol, port, pathname, search } = new URL(this.url);
|
|
62
|
+
const isSecure = protocol === 'wss:';
|
|
63
|
+
const agent = isSecure ? https : http;
|
|
64
|
+
const key = crypto.randomBytes(16).toString('base64');
|
|
65
|
+
|
|
66
|
+
const request = agent.request({
|
|
67
|
+
hostname,
|
|
68
|
+
port: port || (isSecure ? 443 : 80),
|
|
69
|
+
path: pathname + search,
|
|
70
|
+
timeout: this.options.timeout || 30000,
|
|
71
|
+
headers: {
|
|
72
|
+
'Sec-WebSocket-Key': key,
|
|
73
|
+
'Sec-WebSocket-Version': 13,
|
|
74
|
+
'Upgrade': 'websocket',
|
|
75
|
+
'Connection': 'Upgrade',
|
|
76
|
+
...this.options.headers
|
|
77
|
+
},
|
|
78
|
+
method: 'GET'
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
request.once('error', (err) => this._handleError(err));
|
|
82
|
+
request.once('upgrade', (res, socket, head) => this._handleUpgrade(res, socket, head, key));
|
|
83
|
+
request.end();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_handleUpgrade(res, socket, head, key) {
|
|
87
|
+
if (res.headers.upgrade.toLowerCase() !== 'websocket') {
|
|
88
|
+
return socket.destroy();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const expectedAccept = crypto.createHash('sha1')
|
|
92
|
+
.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
|
93
|
+
.digest('base64');
|
|
94
|
+
|
|
95
|
+
if (res.headers['sec-websocket-accept'] !== expectedAccept) {
|
|
96
|
+
return socket.destroy();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.reconnectAttempts = 0;
|
|
100
|
+
|
|
101
|
+
this.socket = socket;
|
|
102
|
+
this.socket.setNoDelay(true);
|
|
103
|
+
this.socket.setKeepAlive(true);
|
|
104
|
+
this.socket.on('data', (data) => this._processData(data));
|
|
105
|
+
this.socket.once('close', () => this._handleClose(1006));
|
|
106
|
+
this.socket.once('error', (err) => this._handleError(err));
|
|
107
|
+
if (head.length) this._processData(head);
|
|
108
|
+
this.emit('open');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
_processData(data) {
|
|
112
|
+
this.bufferPool.append(data);
|
|
113
|
+
|
|
114
|
+
while (this.bufferPool.used >= 2) {
|
|
115
|
+
const bufferData = this.bufferPool.data;
|
|
116
|
+
const lengthByte = bufferData[1] & 127;
|
|
117
|
+
let headerSize = 2 + ((bufferData[1] & 128) ? 4 : 0);
|
|
118
|
+
if (lengthByte === 126) headerSize += 2;
|
|
119
|
+
else if (lengthByte === 127) headerSize += 8;
|
|
120
|
+
if (this.bufferPool.used < headerSize) return;
|
|
121
|
+
|
|
122
|
+
const frame = this._parseFrame();
|
|
123
|
+
if (!frame) return;
|
|
124
|
+
this._handleFrame(frame);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
_parseFrame() {
|
|
129
|
+
const bufferData = this.bufferPool.data;
|
|
130
|
+
if (bufferData.length < 2) return null;
|
|
131
|
+
|
|
132
|
+
const fin = (bufferData[0] & 128) !== 0;
|
|
133
|
+
const opcode = bufferData[0] & 15;
|
|
134
|
+
let payloadLength = bufferData[1] & 127;
|
|
135
|
+
let offset = 2;
|
|
136
|
+
|
|
137
|
+
if (payloadLength === 126) {
|
|
138
|
+
if (bufferData.length < offset + 2) return null;
|
|
139
|
+
payloadLength = bufferData.readUInt16BE(offset);
|
|
140
|
+
offset += 2;
|
|
141
|
+
} else if (payloadLength === 127) {
|
|
142
|
+
if (bufferData.length < offset + 8) return null;
|
|
143
|
+
payloadLength = Number(bufferData.readBigUInt64BE(offset));
|
|
144
|
+
offset += 8;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const hasMask = (bufferData[1] & 128) !== 0;
|
|
148
|
+
const mask = hasMask ? bufferData.subarray(offset, offset + 4) : null;
|
|
149
|
+
offset += hasMask ? 4 : 0;
|
|
150
|
+
|
|
151
|
+
if (bufferData.length < offset + payloadLength) return null;
|
|
152
|
+
|
|
153
|
+
let payload = Buffer.from(bufferData.subarray(offset, offset + payloadLength));
|
|
154
|
+
|
|
155
|
+
if (mask) {
|
|
156
|
+
for (let i = 0; i < payload.length; i++) {
|
|
157
|
+
payload[i] = payload[i] ^ mask[i % 4];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const totalFrameSize = offset + payloadLength;
|
|
162
|
+
this.bufferPool.consume(totalFrameSize);
|
|
163
|
+
|
|
164
|
+
return { fin, opcode, payload };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
_handleFrame({ fin, opcode, payload }) {
|
|
168
|
+
if (opcode === 0x8) return this._handleClose(payload.length >= 2 ? payload.readUInt16BE(0) : 1006);
|
|
169
|
+
if (opcode === 0x9) return this._sendFrame(0xA, payload);
|
|
170
|
+
if (opcode === 0xA) return this.emit('pong', payload);
|
|
171
|
+
if (!fin) return;
|
|
172
|
+
|
|
173
|
+
if (opcode === 0x1) {
|
|
174
|
+
this.emit('message', payload.toString('utf-8'));
|
|
175
|
+
} else {
|
|
176
|
+
this.emit('message', payload);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
_sendFrame(opcode, payload = Buffer.alloc(0)) {
|
|
181
|
+
if (!this.socket || this.socket.destroyed) return;
|
|
182
|
+
const length = payload.length;
|
|
183
|
+
let headerSize = length < 126 ? 2 : length < 65536 ? 4 : 10;
|
|
184
|
+
|
|
185
|
+
frameHeaderPool[0] = 0x80 | opcode;
|
|
186
|
+
if (length < 126) {
|
|
187
|
+
frameHeaderPool[1] = length;
|
|
188
|
+
} else if (length < 65536) {
|
|
189
|
+
frameHeaderPool[1] = 126;
|
|
190
|
+
frameHeaderPool.writeUInt16BE(length, 2);
|
|
191
|
+
} else {
|
|
192
|
+
frameHeaderPool[1] = 127;
|
|
193
|
+
frameHeaderPool.writeBigUInt64BE(BigInt(length), 2);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
this.socket.write(frameHeaderPool.subarray(0, headerSize));
|
|
197
|
+
this.socket.write(payload);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
send(data) {
|
|
201
|
+
if (typeof data === 'string') {
|
|
202
|
+
data = Buffer.from(data, 'utf-8');
|
|
203
|
+
} else if (!Buffer.isBuffer(data)) {
|
|
204
|
+
throw new Error('Data must be a string or Buffer');
|
|
205
|
+
}
|
|
206
|
+
this._sendFrame(0x1, data);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
ping(data = '') {
|
|
210
|
+
const pingData = typeof data === 'string' ? Buffer.from(data, 'utf-8') : data;
|
|
211
|
+
this._sendFrame(0x9, pingData);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
close(code = 1000) {
|
|
215
|
+
const codeBuffer = Buffer.allocUnsafe(2);
|
|
216
|
+
codeBuffer.writeUInt16BE(code, 0);
|
|
217
|
+
this._sendFrame(0x8, codeBuffer);
|
|
218
|
+
setTimeout(() => this._handleClose(code), 100);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
_handleClose(code) {
|
|
222
|
+
if (this.socket) {
|
|
223
|
+
this.socket.destroy();
|
|
224
|
+
this.socket = null;
|
|
225
|
+
}
|
|
226
|
+
this.bufferPool = new BufferPool();
|
|
227
|
+
this.emit('close', code);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
_handleError(err) {
|
|
231
|
+
if (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT') {
|
|
232
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
233
|
+
this.reconnectAttempts++;
|
|
234
|
+
const delay = Math.min(
|
|
235
|
+
this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
|
|
236
|
+
30000
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const jitter = Math.random() * 0.3 * delay;
|
|
240
|
+
const reconnectDelay = delay + jitter;
|
|
241
|
+
|
|
242
|
+
setTimeout(() => this.connect(), reconnectDelay);
|
|
243
|
+
this.emit('reconnecting', {
|
|
244
|
+
attempt: this.reconnectAttempts,
|
|
245
|
+
delay: reconnectDelay,
|
|
246
|
+
error: err
|
|
247
|
+
});
|
|
248
|
+
} else {
|
|
249
|
+
this.emit('error', new Error(`Maximum reconnection attempts (${this.maxReconnectAttempts}) exceeded: ${err.message}`));
|
|
250
|
+
this._handleClose(1006);
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
this.emit('error', err);
|
|
254
|
+
this._handleClose(1006);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module.exports = nativeWs || WebSocket;
|
package/build/structures/Node.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const WebSocket = require(
|
|
2
|
+
const WebSocket = require('../handlers/WebSocket');
|
|
3
3
|
const Rest = require("./Rest");
|
|
4
4
|
|
|
5
5
|
class Node {
|
|
@@ -58,8 +58,6 @@ class Node {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
async connect() {
|
|
61
|
-
if (this.#ws && this.#ws.readyState === WebSocket.OPEN) return;
|
|
62
|
-
|
|
63
61
|
this.#ws = new WebSocket(this.wsUrl.href, {
|
|
64
62
|
headers: this.#constructHeaders(),
|
|
65
63
|
perMessageDeflate: false,
|
|
@@ -100,7 +98,9 @@ class Node {
|
|
|
100
98
|
|
|
101
99
|
async getStats() {
|
|
102
100
|
const stats = await this.rest.getStats();
|
|
103
|
-
this.stats
|
|
101
|
+
if (JSON.stringify(this.stats) !== JSON.stringify({ ...this.defaultStats, ...stats })) {
|
|
102
|
+
this.stats = { ...this.defaultStats, ...stats };
|
|
103
|
+
}
|
|
104
104
|
return this.stats;
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -109,7 +109,7 @@ class Node {
|
|
|
109
109
|
try {
|
|
110
110
|
payload = JSON.parse(msg);
|
|
111
111
|
} catch {
|
|
112
|
-
return;
|
|
112
|
+
return;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
const op = payload?.op;
|
|
@@ -141,13 +141,16 @@ class Node {
|
|
|
141
141
|
|
|
142
142
|
#updateStats(payload) {
|
|
143
143
|
if (!payload) return;
|
|
144
|
-
|
|
144
|
+
const newStats = {
|
|
145
145
|
...this.stats,
|
|
146
146
|
...payload,
|
|
147
147
|
memory: this.#updateMemoryStats(payload.memory),
|
|
148
148
|
cpu: this.#updateCpuStats(payload.cpu),
|
|
149
149
|
frameStats: this.#updateFrameStats(payload.frameStats)
|
|
150
|
-
};
|
|
150
|
+
};
|
|
151
|
+
if (JSON.stringify(this.stats) !== JSON.stringify(newStats)) {
|
|
152
|
+
this.stats = newStats;
|
|
153
|
+
}
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
#updateMemoryStats(memory = {}) {
|
|
@@ -192,7 +195,6 @@ class Node {
|
|
|
192
195
|
this.sessionId = payload.sessionId;
|
|
193
196
|
this.rest.setSessionId(payload.sessionId);
|
|
194
197
|
|
|
195
|
-
// Don't resume here - we'll handle resuming in the #onOpen method if autoResume is enabled
|
|
196
198
|
this.aqua.emit("nodeConnect", this);
|
|
197
199
|
}
|
|
198
200
|
|
|
@@ -213,9 +215,11 @@ class Node {
|
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
#reconnect() {
|
|
218
|
+
clearTimeout(this.#reconnectTimeoutId);
|
|
219
|
+
|
|
216
220
|
if (this.infiniteReconnects) {
|
|
217
|
-
this.aqua.emit("nodeReconnect", this, "Infinite reconnects enabled, trying
|
|
218
|
-
setTimeout(() => this.connect(), 10000);
|
|
221
|
+
this.aqua.emit("nodeReconnect", this, "Infinite reconnects enabled, trying again in 10 seconds");
|
|
222
|
+
this.#reconnectTimeoutId = setTimeout(() => this.connect(), 10000);
|
|
219
223
|
return;
|
|
220
224
|
}
|
|
221
225
|
|
|
@@ -226,19 +230,17 @@ class Node {
|
|
|
226
230
|
return;
|
|
227
231
|
}
|
|
228
232
|
|
|
229
|
-
|
|
230
|
-
const jitter = Math.random() *
|
|
231
|
-
const backoffTime = Math.min(
|
|
232
|
-
this.reconnectTimeout * Math.pow(Node.BACKOFF_MULTIPLIER, this.#reconnectAttempted) + jitter,
|
|
233
|
-
Node.MAX_BACKOFF
|
|
234
|
-
);
|
|
233
|
+
const baseBackoff = this.reconnectTimeout * Math.pow(Node.BACKOFF_MULTIPLIER, this.#reconnectAttempted);
|
|
234
|
+
const jitter = Math.random() * Math.min(2000, baseBackoff * 0.2);
|
|
235
|
+
const backoffTime = Math.min(baseBackoff + jitter, Node.MAX_BACKOFF);
|
|
235
236
|
|
|
237
|
+
this.#reconnectAttempted++;
|
|
238
|
+
this.aqua.emit("nodeReconnect", this, {
|
|
239
|
+
attempt: this.#reconnectAttempted,
|
|
240
|
+
backoffTime
|
|
241
|
+
});
|
|
242
|
+
|
|
236
243
|
this.#reconnectTimeoutId = setTimeout(() => {
|
|
237
|
-
this.#reconnectAttempted++;
|
|
238
|
-
this.aqua.emit("nodeReconnect", this, {
|
|
239
|
-
attempt: this.#reconnectAttempted,
|
|
240
|
-
backoffTime
|
|
241
|
-
});
|
|
242
244
|
this.connect();
|
|
243
245
|
}, backoffTime);
|
|
244
246
|
}
|
|
@@ -275,4 +277,4 @@ class Node {
|
|
|
275
277
|
}
|
|
276
278
|
}
|
|
277
279
|
|
|
278
|
-
module.exports = Node;
|
|
280
|
+
module.exports = Node;
|
package/build/structures/Rest.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const
|
|
2
|
+
const https = require("https");
|
|
3
|
+
const http = require("http");
|
|
3
4
|
|
|
4
5
|
class Rest {
|
|
5
6
|
constructor(aqua, { secure, host, port, sessionId, password }) {
|
|
6
7
|
this.aqua = aqua;
|
|
7
8
|
this.sessionId = sessionId;
|
|
8
9
|
this.version = "v4";
|
|
9
|
-
this.baseUrl =
|
|
10
|
+
this.baseUrl = `${secure ? "https" : "http"}://${host}:${port}`;
|
|
10
11
|
this.headers = {
|
|
11
12
|
"Content-Type": "application/json",
|
|
12
13
|
Authorization: password,
|
|
13
14
|
};
|
|
14
|
-
this.client =
|
|
15
|
+
this.client = secure ? https : http;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
setSessionId(sessionId) {
|
|
@@ -19,18 +20,33 @@ class Rest {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
async makeRequest(method, endpoint, body = null) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
const options = {
|
|
24
|
+
method,
|
|
25
|
+
headers: this.headers,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const req = this.client.request(`${this.baseUrl}${endpoint}`, options, (res) => {
|
|
30
|
+
let data = "";
|
|
31
|
+
res.setEncoding("utf8");
|
|
32
|
+
|
|
33
|
+
res.on("data", (chunk) => (data += chunk));
|
|
34
|
+
res.on("end", () => {
|
|
35
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
36
|
+
resolve(data ? JSON.parse(data) : null);
|
|
37
|
+
} else {
|
|
38
|
+
reject(new Error(`Request failed with status ${res.statusCode}: ${res.statusMessage}`));
|
|
39
|
+
}
|
|
40
|
+
});
|
|
28
41
|
});
|
|
29
42
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
req.on("error", (error) => reject(new Error(`Request failed (${method} ${endpoint}): ${error.message}`)));
|
|
44
|
+
|
|
45
|
+
if (body) {
|
|
46
|
+
req.write(JSON.stringify(body));
|
|
47
|
+
}
|
|
48
|
+
req.end();
|
|
49
|
+
});
|
|
34
50
|
}
|
|
35
51
|
|
|
36
52
|
validateSessionId() {
|
|
@@ -38,59 +54,63 @@ class Rest {
|
|
|
38
54
|
}
|
|
39
55
|
|
|
40
56
|
async updatePlayer({ guildId, data }) {
|
|
41
|
-
if (
|
|
57
|
+
if (data.track?.encoded && data.track?.identifier) {
|
|
42
58
|
throw new Error("Cannot provide both 'encoded' and 'identifier' for track");
|
|
43
59
|
}
|
|
44
60
|
this.validateSessionId();
|
|
45
61
|
return this.makeRequest("PATCH", `/${this.version}/sessions/${this.sessionId}/players/${guildId}?noReplace=false`, data);
|
|
46
62
|
}
|
|
47
63
|
|
|
48
|
-
|
|
64
|
+
getPlayers() {
|
|
49
65
|
this.validateSessionId();
|
|
50
66
|
return this.makeRequest("GET", `/${this.version}/sessions/${this.sessionId}/players`);
|
|
51
67
|
}
|
|
52
68
|
|
|
53
|
-
|
|
69
|
+
destroyPlayer(guildId) {
|
|
54
70
|
this.validateSessionId();
|
|
55
71
|
return this.makeRequest("DELETE", `/${this.version}/sessions/${this.sessionId}/players/${guildId}`);
|
|
56
72
|
}
|
|
57
73
|
|
|
58
|
-
|
|
74
|
+
getTracks(identifier) {
|
|
59
75
|
return this.makeRequest("GET", `/${this.version}/loadtracks?identifier=${encodeURIComponent(identifier)}`);
|
|
60
76
|
}
|
|
61
77
|
|
|
62
|
-
|
|
78
|
+
decodeTrack(track) {
|
|
63
79
|
return this.makeRequest("GET", `/${this.version}/decodetrack?encodedTrack=${encodeURIComponent(track)}`);
|
|
64
80
|
}
|
|
65
81
|
|
|
66
|
-
|
|
82
|
+
decodeTracks(tracks) {
|
|
67
83
|
return this.makeRequest("POST", `/${this.version}/decodetracks`, tracks);
|
|
68
84
|
}
|
|
69
85
|
|
|
70
|
-
|
|
86
|
+
getStats() {
|
|
71
87
|
return this.makeRequest("GET", `/${this.version}/stats`);
|
|
72
88
|
}
|
|
73
89
|
|
|
74
|
-
|
|
90
|
+
getInfo() {
|
|
75
91
|
return this.makeRequest("GET", `/${this.version}/info`);
|
|
76
92
|
}
|
|
77
93
|
|
|
78
|
-
|
|
94
|
+
getRoutePlannerStatus() {
|
|
79
95
|
return this.makeRequest("GET", `/${this.version}/routeplanner/status`);
|
|
80
96
|
}
|
|
81
97
|
|
|
82
|
-
|
|
98
|
+
getRoutePlannerAddress(address) {
|
|
83
99
|
return this.makeRequest("POST", `/${this.version}/routeplanner/free/address`, { address });
|
|
84
100
|
}
|
|
85
101
|
|
|
86
102
|
async getLyrics({ track }) {
|
|
87
103
|
if (track.search) {
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
try {
|
|
105
|
+
const res = await this.makeRequest("GET", `/${this.version}/lyrics/search?query=${encodeURIComponent(track.encoded.info.title)}&source=genius`);
|
|
106
|
+
if (res) return res;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error("Failed to fetch lyrics:", error.message);
|
|
109
|
+
}
|
|
90
110
|
}
|
|
91
111
|
this.validateSessionId();
|
|
92
112
|
return this.makeRequest("GET", `/${this.version}/sessions/${this.sessionId}/players/${track.guild_id}/track/lyrics?skipTrackSource=false`);
|
|
93
113
|
}
|
|
94
114
|
}
|
|
95
115
|
|
|
96
|
-
module.exports = Rest;
|
|
116
|
+
module.exports = Rest;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aqualink",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "An Lavalink
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "An Lavalink client, focused in pure performance and features",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"scripts": {
|
|
@@ -36,10 +36,6 @@
|
|
|
36
36
|
],
|
|
37
37
|
"author": "mushroom0162 (https://github.com/ToddyTheNoobDud)",
|
|
38
38
|
"license": "ISC",
|
|
39
|
-
"dependencies": {
|
|
40
|
-
"undici": "^7.4.0",
|
|
41
|
-
"@toddynnn/pwsl-mini": "github:ToddyTheNoobDud/websocket"
|
|
42
|
-
},
|
|
43
39
|
"repository": {
|
|
44
40
|
"type": "git",
|
|
45
41
|
"url": "https://github.com/ToddyTheNoobDud/AquaLink"
|