aqualink 2.9.13 → 2.10.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.
@@ -3,8 +3,7 @@
3
3
  const WebSocket = require('ws')
4
4
  const Rest = require('./Rest')
5
5
 
6
- const LYRICS_OP_REGEX = /^Lyrics/
7
- const JSON_START_REGEX = /^\s*[{[]/
6
+ const JSON_START_CHAR = /^[\s{[]/
8
7
  const WS_READY_STATES = Object.freeze({ OPEN: 1, CLOSED: 3 })
9
8
 
10
9
  class Node {
@@ -16,6 +15,16 @@ class Node {
16
15
  static JITTER_FACTOR = 0.2
17
16
  static WS_CLOSE_NORMAL = 1000
18
17
 
18
+ stats = {
19
+ players: 0,
20
+ playingPlayers: 0,
21
+ uptime: 0,
22
+ memory: { free: 0, used: 0, allocated: 0, reservable: 0 },
23
+ cpu: { cores: 0, systemLoad: 0, lavalinkLoad: 0 },
24
+ frameStats: { sent: 0, nulled: 0, deficit: 0 },
25
+ ping: 0
26
+ }
27
+
19
28
  constructor(aqua, connOptions, options = {}) {
20
29
  this.aqua = aqua
21
30
 
@@ -35,7 +44,7 @@ class Node {
35
44
  this.password = password
36
45
  this.sessionId = sessionId
37
46
  this.regions = regions
38
- this.secure = Boolean(secure)
47
+ this.secure = !!secure
39
48
  this.wsUrl = `ws${secure ? 's' : ''}://${host}:${port}/v4/websocket`
40
49
 
41
50
  this.rest = new Rest(aqua, this)
@@ -61,52 +70,15 @@ class Node {
61
70
  this.reconnectTimeoutId = null
62
71
  this.isDestroyed = false
63
72
 
64
- this._boundHandlers = this._createBoundHandlers()
65
73
  this._headers = this._buildHeaders()
66
- this._initStats()
67
- }
68
-
69
- _createBoundHandlers() {
70
- return {
71
- onOpen: () => this._handleOpen(),
72
- onError: error => this._handleError(error),
73
- onMessage: msg => this._handleMessage(msg),
74
- onClose: (code, reason) => this._handleClose(code, reason)
75
- }
76
74
  }
77
75
 
78
- _initStats() {
79
- this.stats = {
80
- players: 0,
81
- playingPlayers: 0,
82
- uptime: 0,
83
- memory: { free: 0, used: 0, allocated: 0, reservable: 0, freePercentage: 0, usedPercentage: 0 },
84
- cpu: { cores: 0, systemLoad: 0, lavalinkLoad: 0, lavalinkLoadPercentage: 0 },
85
- frameStats: { sent: 0, nulled: 0, deficit: 0 },
86
- ping: 0
87
- }
88
- }
89
-
90
- _buildHeaders() {
91
- const headers = {
92
- 'Authorization': this.password,
93
- 'User-Id': this.aqua.clientId,
94
- 'Client-Name': `Aqua/${this.aqua.version} (https://github.com/ToddyTheNoobDud/AquaLink)`
95
- }
96
-
97
- if (this.sessionId) {
98
- headers['Session-Id'] = this.sessionId
99
- }
100
-
101
- return headers
102
- }
103
-
104
- async _handleOpen() {
76
+ _handleOpen = async () => {
105
77
  this.connected = true
106
78
  this.reconnectAttempted = 0
107
79
  this._emitDebug('WebSocket connection established')
108
80
 
109
- if (this.aqua.bypassChecks?.nodeFetchInfo) return;
81
+ if (this.aqua.bypassChecks?.nodeFetchInfo) return
110
82
 
111
83
  try {
112
84
  this.info = await this.rest.makeRequest('GET', '/v4/info')
@@ -121,14 +93,14 @@ class Node {
121
93
  }
122
94
  }
123
95
 
124
- _handleError(error) {
96
+ _handleError = (error) => {
125
97
  this.aqua.emit('nodeError', this, error)
126
98
  }
127
99
 
128
- _handleMessage(msg) {
129
- if (!JSON_START_REGEX.test(msg)) {
100
+ _handleMessage = (msg) => {
101
+ if (!JSON_START_CHAR.test(msg)) {
130
102
  this._emitDebug(`Invalid JSON format: ${msg.slice(0, 100)}...`)
131
- return;
103
+ return
132
104
  }
133
105
 
134
106
  let payload
@@ -136,29 +108,55 @@ class Node {
136
108
  payload = JSON.parse(msg)
137
109
  } catch {
138
110
  this._emitDebug(`JSON parse failed: ${msg.slice(0, 100)}...`)
139
- return;
111
+ return
140
112
  }
141
113
 
114
+
142
115
  const { op, guildId } = payload
143
- if (!op) return;
116
+ if (!op) return
144
117
 
145
- switch (op) {
146
- case 'stats':
147
- this._updateStats(payload)
148
- break
149
- case 'ready':
150
- this._handleReady(payload)
151
- break
152
- default:
153
- this._handleCustomOp(op, guildId, payload)
118
+ if (typeof op === 'number') {
119
+ this._handleNumericOp(op, guildId, payload);
120
+ return;
121
+ }
122
+
123
+ if (op === 'stats') {
124
+ this._updateStats(payload)
125
+ } else if (op === 'ready') {
126
+ this._handleReady(payload)
127
+ } else {
128
+ this._handleCustomOp(op, guildId, payload)
129
+ }
130
+ }
131
+
132
+ _handleClose = (code, reason) => {
133
+ this.connected = false
134
+ const reasonStr = reason?.toString() || 'No reason provided'
135
+
136
+ this.aqua.emit('nodeDisconnect', this, { code, reason: reasonStr })
137
+ this.aqua.handleNodeFailover(this)
138
+ this._scheduleReconnect(code)
139
+ }
140
+
141
+ _buildHeaders() {
142
+ const headers = {
143
+ Authorization: this.password,
144
+ 'User-Id': this.aqua.clientId,
145
+ 'Client-Name': `Aqua/${this.aqua.version} (https://github.com/ToddyTheNoobDud/AquaLink)`
154
146
  }
147
+
148
+ if (this.sessionId) {
149
+ headers['Session-Id'] = this.sessionId
150
+ }
151
+
152
+ return headers
155
153
  }
156
154
 
157
155
  _handleCustomOp(op, guildId, payload) {
158
- if (LYRICS_OP_REGEX.test(op)) {
156
+ if (op.startsWith('Lyrics')) {
159
157
  const player = guildId ? this.aqua.players.get(guildId) : null
160
158
  this.aqua.emit(op, player, payload.track || null, payload)
161
- return;
159
+ return
162
160
  }
163
161
 
164
162
  if (guildId) {
@@ -167,13 +165,28 @@ class Node {
167
165
  }
168
166
  }
169
167
 
170
- _handleClose(code, reason) {
171
- this.connected = false
172
- const reasonStr = reason?.toString() || 'No reason provided'
168
+ _handleNumericOp(op, guildId, payload) {
169
+ const player = guildId ? this.aqua.players.get(guildId) : null;
173
170
 
174
- this.aqua.emit('nodeDisconnect', this, { code, reason: reasonStr })
175
- this.aqua.handleNodeFailover(this)
176
- this._scheduleReconnect(code)
171
+ switch (op) {
172
+ case 2:
173
+ if (player?.connection) {
174
+ player.connection.setServerUpdate(payload.d);
175
+ }
176
+ break;
177
+ case 5:
178
+ if (player?.connection) {
179
+ player.connection.updateSequence(payload.d.sequence);
180
+ }
181
+ break;
182
+
183
+ case 9:
184
+ this.aqua.emit('debug', `[Player ${guildId}] Voice resumed successfully`);
185
+ break;
186
+
187
+ default:
188
+ this.aqua.emit('debug', `Unknown numeric op ${op} for guild ${guildId}`);
189
+ }
177
190
  }
178
191
 
179
192
  _scheduleReconnect(code) {
@@ -181,19 +194,19 @@ class Node {
181
194
 
182
195
  if (code === Node.WS_CLOSE_NORMAL || this.isDestroyed) {
183
196
  this._emitDebug('WebSocket closed normally, not reconnecting')
184
- return;
197
+ return
185
198
  }
186
199
 
187
200
  if (this.infiniteReconnects) {
188
201
  this.aqua.emit('nodeReconnect', this, 'Infinite reconnects enabled, trying again in 10 seconds')
189
202
  this.reconnectTimeoutId = setTimeout(() => this.connect(), 10000)
190
- return;
203
+ return
191
204
  }
192
205
 
193
206
  if (this.reconnectAttempted >= this.reconnectTries) {
194
207
  this._emitError(new Error(`Max reconnection attempts reached (${this.reconnectTries})`))
195
208
  this.destroy(true)
196
- return;
209
+ return
197
210
  }
198
211
 
199
212
  const backoffTime = this._calcBackoff()
@@ -221,12 +234,12 @@ class Node {
221
234
  }
222
235
  }
223
236
 
224
- async connect() {
225
- if (this.isDestroyed) return;
237
+ connect() {
238
+ if (this.isDestroyed) return
226
239
 
227
240
  if (this.ws?.readyState === WS_READY_STATES.OPEN) {
228
241
  this._emitDebug('WebSocket already connected')
229
- return;
242
+ return
230
243
  }
231
244
 
232
245
  this._cleanup()
@@ -236,15 +249,14 @@ class Node {
236
249
  perMessageDeflate: false
237
250
  })
238
251
 
239
- const handlers = this._boundHandlers
240
- this.ws.once('open', handlers.onOpen)
241
- this.ws.once('error', handlers.onError)
242
- this.ws.on('message', handlers.onMessage)
243
- this.ws.once('close', handlers.onClose)
252
+ this.ws.once('open', this._handleOpen)
253
+ this.ws.once('error', this._handleError)
254
+ this.ws.on('message', this._handleMessage)
255
+ this.ws.once('close', this._handleClose)
244
256
  }
245
257
 
246
258
  _cleanup() {
247
- if (!this.ws) return;
259
+ if (!this.ws) return
248
260
 
249
261
  this.ws.removeAllListeners()
250
262
 
@@ -275,13 +287,13 @@ class Node {
275
287
  }
276
288
 
277
289
  async getStats() {
278
- if (this.connected && this.stats) {
290
+ if (this.connected) {
279
291
  return this.stats
280
292
  }
281
293
 
282
294
  try {
283
295
  const newStats = await this.rest.getStats()
284
- if (newStats && this.stats) {
296
+ if (newStats) {
285
297
  this._mergeStats(newStats)
286
298
  }
287
299
  return this.stats
@@ -313,26 +325,16 @@ class Node {
313
325
  }
314
326
 
315
327
  _updateStats(payload) {
316
- if (!payload) return;
328
+ if (!payload) return
317
329
 
318
330
  this.stats.players = payload.players
319
331
  this.stats.playingPlayers = payload.playingPlayers
320
332
  this.stats.uptime = payload.uptime
321
333
  this.stats.ping = payload.ping
322
334
 
323
- if (payload.memory) {
324
- Object.assign(this.stats.memory, payload.memory)
325
- this._calcMemoryPercentages()
326
- }
327
-
328
- if (payload.cpu) {
329
- Object.assign(this.stats.cpu, payload.cpu)
330
- this._calcCpuPercentages()
331
- }
332
-
333
- if (payload.frameStats) {
334
- Object.assign(this.stats.frameStats, payload.frameStats)
335
- }
335
+ if (payload.memory) Object.assign(this.stats.memory, payload.memory)
336
+ if (payload.cpu) Object.assign(this.stats.cpu, payload.cpu)
337
+ if (payload.frameStats) Object.assign(this.stats.frameStats, payload.frameStats)
336
338
  }
337
339
 
338
340
  _calcMemoryPercentages() {
@@ -354,12 +356,13 @@ class Node {
354
356
  _handleReady(payload) {
355
357
  if (!payload.sessionId) {
356
358
  this._emitError('Ready payload missing sessionId')
357
- return;
359
+ return
358
360
  }
359
361
 
360
362
  this.sessionId = payload.sessionId
361
363
  this.rest.setSessionId(payload.sessionId)
362
- this._headers = this._buildHeaders()
364
+ this._headers['Session-Id'] = payload.sessionId
365
+
363
366
  this.aqua.emit('nodeConnect', this)
364
367
  }
365
368
 
@@ -367,7 +370,7 @@ class Node {
367
370
  try {
368
371
  await this.rest.makeRequest('PATCH', `/v4/sessions/${this.sessionId}`, {
369
372
  resuming: true,
370
- timeout: 60000
373
+ timeout: this.resumeTimeout
371
374
  })
372
375
  await this.aqua.loadPlayers()
373
376
  this._emitDebug('Session resumed successfully')