aqualink 2.17.0 → 2.17.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.
@@ -529,7 +529,7 @@ class Aqua extends EventEmitter {
529
529
  _handlePlayerDestroy(player) {
530
530
  player.nodes?.players?.delete?.(player)
531
531
  if (this.players.get(player.guildId) === player) this.players.delete(player.guildId)
532
- this.emit(AqualinkEvents.PlayerDestroy, player)
532
+ this.emit(AqualinkEvents.PlayerDestroyed, player)
533
533
  }
534
534
 
535
535
  async destroyPlayer(guildId) {
@@ -29,14 +29,13 @@ const AqualinkEvents = {
29
29
  Debug: 'debug',
30
30
  Error: 'error',
31
31
  PlayerCreate: 'playerCreate',
32
- PlayerDestroy: 'playerDestroy',
33
32
  PlayersRebuilt: 'playersRebuilt',
34
33
  VolumeChanged: 'volumeChanged',
35
34
  FiltersChanged: 'filtersChanged',
36
35
  Seek: 'seek',
37
36
  PlayerCreated: 'playerCreated',
38
37
  PlayerConnected: 'playerConnected',
39
- PlayerDestroyed: 'playerDestroyed',
38
+ PlayerDestroyed: 'playerDestroy',
40
39
  PlayerMigrated: 'playerMigrated',
41
40
  PauseEvent: 'pauseEvent'
42
41
  };
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const {AqualinkEvents} = require('./AqualinkEvents')
3
+ const { AqualinkEvents } = require('./AqualinkEvents')
4
4
 
5
5
  const POOL_SIZE = 12
6
6
  const UPDATE_TIMEOUT = 4000
@@ -17,15 +17,46 @@ const STATE = {
17
17
  VOICE_DATA_STALE: 512
18
18
  }
19
19
 
20
- const ENDPOINT_REGION_REGEX = /^([a-z-]+)\d*/i
21
-
22
20
  const _functions = {
23
- safeUnref: t => t?.unref?.(),
21
+ safeUnref: t => (typeof t?.unref === 'function' ? t.unref() : undefined),
24
22
  isValidNumber: n => typeof n === 'number' && n >= 0 && Number.isFinite(n),
25
- isNetworkError: e => e && (e.code === 'ECONNREFUSED' || e.code === 'ENOTFOUND' || e.code === 'ETIMEDOUT'),
23
+ isNetworkError: e => !!e && (e.code === 'ECONNREFUSED' || e.code === 'ENOTFOUND' || e.code === 'ETIMEDOUT'),
26
24
  extractRegion: endpoint => {
27
- const m = ENDPOINT_REGION_REGEX.exec(endpoint)
28
- return m ? m[1] : 'unknown'
25
+ if (typeof endpoint !== 'string') return 'unknown'
26
+ endpoint = endpoint.trim()
27
+ if (!endpoint) return 'unknown'
28
+
29
+ const proto = endpoint.indexOf('://')
30
+ if (proto !== -1) endpoint = endpoint.slice(proto + 3)
31
+
32
+ const slash = endpoint.indexOf('/')
33
+ if (slash !== -1) endpoint = endpoint.slice(0, slash)
34
+
35
+ const colon = endpoint.indexOf(':')
36
+ if (colon !== -1) endpoint = endpoint.slice(0, colon)
37
+
38
+ const dot = endpoint.indexOf('.')
39
+ const label = (dot === -1 ? endpoint : endpoint.slice(0, dot)).toLowerCase()
40
+ if (!label) return 'unknown'
41
+
42
+ let i = label.length - 1
43
+ while (i >= 0) {
44
+ const c = label.charCodeAt(i)
45
+ if (c >= 48 && c <= 57) i--
46
+ else break
47
+ }
48
+ return label.slice(0, i + 1) || 'unknown'
49
+ },
50
+ fillVoicePayload: (payload, guildId, conn, player, resume) => {
51
+ payload.guildId = guildId
52
+ const v = payload.data.voice
53
+ v.token = conn.token
54
+ v.endpoint = conn.endpoint
55
+ v.sessionId = conn.sessionId
56
+ v.resume = resume ? true : undefined
57
+ v.sequence = resume ? conn.sequence : undefined
58
+ payload.data.volume = player?.volume ?? 100
59
+ return payload
29
60
  }
30
61
  }
31
62
 
@@ -39,7 +70,7 @@ class PayloadPool {
39
70
  return {
40
71
  guildId: null,
41
72
  data: {
42
- voice: {token: null, endpoint: null, sessionId: null, resume: undefined, sequence: undefined},
73
+ voice: { token: null, endpoint: null, sessionId: null, resume: undefined, sequence: undefined },
43
74
  volume: null
44
75
  }
45
76
  }
@@ -69,21 +100,21 @@ const sharedPool = new PayloadPool()
69
100
 
70
101
  class Connection {
71
102
  constructor(player) {
72
- if (!player?.aqua?.clientId || !player.nodes?.rest) {
73
- throw new TypeError('Invalid player configuration')
74
- }
103
+ if (!player?.aqua?.clientId || !player.nodes?.rest) throw new TypeError('Invalid player configuration')
75
104
 
76
105
  this._player = player
77
106
  this._aqua = player.aqua
78
107
  this._rest = player.nodes.rest
79
108
  this._guildId = player.guildId
80
109
  this._clientId = player.aqua.clientId
110
+
81
111
  this.voiceChannel = player.voiceChannel
82
112
  this.sessionId = null
83
113
  this.endpoint = null
84
114
  this.token = null
85
115
  this.region = null
86
116
  this.sequence = 0
117
+
87
118
  this._lastEndpoint = null
88
119
  this._pendingUpdate = null
89
120
  this._stateFlags = 0
@@ -109,21 +140,29 @@ class Connection {
109
140
  return this._hasValidVoiceData() || !!this._player?._resuming
110
141
  }
111
142
 
143
+ _setReconnectTimer(delay) {
144
+ if (this._destroyed) return
145
+ this._clearReconnectTimer()
146
+ this._reconnectTimer = setTimeout(() => this._handleReconnect(), delay)
147
+ _functions.safeUnref(this._reconnectTimer)
148
+ }
149
+
112
150
  setServerUpdate(data) {
113
151
  if (this._destroyed || !data?.endpoint || !data.token) return
114
- const endpoint = typeof data.endpoint === 'string' && data.endpoint.trim()
152
+
153
+ const endpoint = typeof data.endpoint === 'string' ? data.endpoint.trim() : ''
115
154
  if (!endpoint || typeof data.token !== 'string' || !data.token) return
116
155
  if (this._lastEndpoint === endpoint && this.token === data.token) return
117
156
 
118
- const newRegion = _functions.extractRegion(endpoint)
119
157
  if (this._lastEndpoint !== endpoint) {
120
158
  this.sequence = 0
121
159
  this._lastEndpoint = endpoint
122
160
  this._reconnectAttempts = 0
123
161
  this._consecutiveFailures = 0
124
162
  }
163
+
125
164
  this.endpoint = endpoint
126
- this.region = newRegion
165
+ this.region = _functions.extractRegion(endpoint)
127
166
  this.token = data.token
128
167
  this._lastVoiceDataUpdate = Date.now()
129
168
  this._stateFlags &= ~STATE.VOICE_DATA_STALE
@@ -141,37 +180,35 @@ class Connection {
141
180
  setStateUpdate(data) {
142
181
  if (this._destroyed || !data || data.user_id !== this._clientId) return
143
182
 
144
- const {session_id: sessionId, channel_id: channelId, self_deaf: selfDeaf, self_mute: selfMute} = data
183
+ const { session_id: sessionId, channel_id: channelId, self_deaf: selfDeaf, self_mute: selfMute } = data
145
184
 
146
- if (channelId) {
147
- let needsUpdate = false
185
+ if (!channelId) return this._handleDisconnect()
148
186
 
149
- if (this.voiceChannel !== channelId) {
150
- this._aqua.emit(AqualinkEvents.PlayerMove, this.voiceChannel, channelId)
151
- this.voiceChannel = channelId
152
- this._player.voiceChannel = channelId
153
- needsUpdate = true
154
- }
187
+ let needsUpdate = false
155
188
 
156
- if (this.sessionId !== sessionId) {
157
- this.sessionId = sessionId
158
- this._lastVoiceDataUpdate = Date.now()
159
- this._stateFlags &= ~STATE.VOICE_DATA_STALE
160
- this._reconnectAttempts = 0
161
- this._consecutiveFailures = 0
162
- needsUpdate = true
163
- }
164
-
165
- this._player.connection.sessionId = sessionId || this._player.connection.sessionId
166
- this._player.self_deaf = this._player.selfDeaf = !!selfDeaf
167
- this._player.self_mute = this._player.selfMute = !!selfMute
168
- this._player.connected = true
169
- this._stateFlags |= STATE.CONNECTED
189
+ if (this.voiceChannel !== channelId) {
190
+ this._aqua.emit(AqualinkEvents.PlayerMove, this.voiceChannel, channelId)
191
+ this.voiceChannel = channelId
192
+ this._player.voiceChannel = channelId
193
+ needsUpdate = true
194
+ }
170
195
 
171
- if (needsUpdate) this._scheduleVoiceUpdate()
172
- } else {
173
- this._handleDisconnect()
196
+ if (this.sessionId !== sessionId) {
197
+ this.sessionId = sessionId
198
+ this._lastVoiceDataUpdate = Date.now()
199
+ this._stateFlags &= ~STATE.VOICE_DATA_STALE
200
+ this._reconnectAttempts = 0
201
+ this._consecutiveFailures = 0
202
+ needsUpdate = true
174
203
  }
204
+
205
+ this._player.connection.sessionId = sessionId || this._player.connection.sessionId
206
+ this._player.self_deaf = this._player.selfDeaf = !!selfDeaf
207
+ this._player.self_mute = this._player.selfMute = !!selfMute
208
+ this._player.connected = true
209
+ this._stateFlags |= STATE.CONNECTED
210
+
211
+ if (needsUpdate) this._scheduleVoiceUpdate()
175
212
  }
176
213
 
177
214
  _handleDisconnect() {
@@ -183,7 +220,8 @@ class Connection {
183
220
  this._clearReconnectTimer()
184
221
 
185
222
  this.voiceChannel = this.sessionId = null
186
- this.sequence = this._lastVoiceDataUpdate = 0
223
+ this.sequence = 0
224
+ this._lastVoiceDataUpdate = 0
187
225
  this._stateFlags |= STATE.VOICE_DATA_STALE
188
226
 
189
227
  try {
@@ -197,19 +235,18 @@ class Connection {
197
235
 
198
236
  _requestVoiceState() {
199
237
  try {
200
- if (typeof this._player?.send === 'function' && this._player.voiceChannel) {
201
- this._player.send({
202
- guild_id: this._guildId,
203
- channel_id: this._player.voiceChannel,
204
- self_deaf: this._player.deaf,
205
- self_mute: this._player.mute
206
- })
207
- this._reconnectTimer = setTimeout(() => this._handleReconnect(), 1500)
208
- _functions.safeUnref(this._reconnectTimer)
209
- return true
210
- }
211
- } catch {}
212
- return false
238
+ if (typeof this._player?.send !== 'function' || !this._player.voiceChannel) return false
239
+ this._player.send({
240
+ guild_id: this._guildId,
241
+ channel_id: this._player.voiceChannel,
242
+ self_deaf: this._player.deaf,
243
+ self_mute: this._player.mute
244
+ })
245
+ this._setReconnectTimer(1500)
246
+ return true
247
+ } catch {
248
+ return false
249
+ }
213
250
  }
214
251
 
215
252
  async attemptResume() {
@@ -240,21 +277,18 @@ class Connection {
240
277
 
241
278
  this._stateFlags |= STATE.ATTEMPTING_RESUME
242
279
  this._reconnectAttempts++
243
- this._aqua.emit(AqualinkEvents.Debug, `Attempt resume: guild=${this._guildId} endpoint=${this.endpoint} session=${this.sessionId}`)
280
+ this._aqua.emit(
281
+ AqualinkEvents.Debug,
282
+ `Attempt resume: guild=${this._guildId} endpoint=${this.endpoint} session=${this.sessionId}`
283
+ )
244
284
 
245
285
  const payload = sharedPool.acquire()
246
286
  try {
247
- payload.guildId = this._guildId
248
- const v = payload.data.voice
249
- v.token = this.token
250
- v.endpoint = this.endpoint
251
- v.sessionId = this.sessionId
252
- v.resume = true
253
- v.sequence = this.sequence
254
- payload.data.volume = this._player?.volume ?? 100
255
-
287
+ _functions.fillVoicePayload(payload, this._guildId, this, this._player, true)
256
288
  await this._sendUpdate(payload)
257
- this._reconnectAttempts = this._consecutiveFailures = 0
289
+
290
+ this._reconnectAttempts = 0
291
+ this._consecutiveFailures = 0
258
292
  if (this._player) this._player._resuming = false
259
293
  this._aqua.emit(AqualinkEvents.Debug, `Resume successful for guild ${this._guildId}`)
260
294
  return true
@@ -264,8 +298,7 @@ class Connection {
264
298
 
265
299
  if (this._reconnectAttempts < MAX_RECONNECT_ATTEMPTS && !this._destroyed && this._consecutiveFailures < 5) {
266
300
  const delay = Math.min(RECONNECT_DELAY * (1 << (this._reconnectAttempts - 1)), RESUME_BACKOFF_MAX)
267
- this._reconnectTimer = setTimeout(() => this._handleReconnect(), delay)
268
- _functions.safeUnref(this._reconnectTimer)
301
+ this._setReconnectTimer(delay)
269
302
  } else {
270
303
  this._aqua.emit(AqualinkEvents.Debug, `Max reconnect attempts or failures reached for guild ${this._guildId}`)
271
304
  if (this._player) this._player._resuming = false
@@ -288,10 +321,9 @@ class Connection {
288
321
  }
289
322
 
290
323
  _clearReconnectTimer() {
291
- if (this._reconnectTimer) {
292
- clearTimeout(this._reconnectTimer)
293
- this._reconnectTimer = null
294
- }
324
+ if (!this._reconnectTimer) return
325
+ clearTimeout(this._reconnectTimer)
326
+ this._reconnectTimer = null
295
327
  }
296
328
 
297
329
  _clearPendingUpdate() {
@@ -304,16 +336,11 @@ class Connection {
304
336
  if (this._destroyed || !this._hasValidVoiceData() || (this._stateFlags & STATE.UPDATE_SCHEDULED)) return
305
337
 
306
338
  this._clearPendingUpdate()
339
+
307
340
  const payload = sharedPool.acquire()
308
- payload.guildId = this._guildId
309
- const v = payload.data.voice
310
- v.token = this.token
311
- v.endpoint = this.endpoint
312
- v.sessionId = this.sessionId
313
- v.resume = v.sequence = undefined
314
- payload.data.volume = this._player.volume
341
+ _functions.fillVoicePayload(payload, this._guildId, this, this._player, false)
315
342
 
316
- this._pendingUpdate = {payload, timestamp: Date.now()}
343
+ this._pendingUpdate = { payload, timestamp: Date.now() }
317
344
  this._stateFlags |= STATE.UPDATE_SCHEDULED
318
345
  queueMicrotask(() => this._executeVoiceUpdate())
319
346
  }
@@ -358,7 +385,11 @@ class Connection {
358
385
 
359
386
  this._player = this._aqua = this._rest = null
360
387
  this.voiceChannel = this.sessionId = this.endpoint = this.token = this.region = this._lastEndpoint = null
361
- this._stateFlags = this.sequence = this._reconnectAttempts = this._consecutiveFailures = this._lastVoiceDataUpdate = 0
388
+ this._stateFlags = 0
389
+ this.sequence = 0
390
+ this._reconnectAttempts = 0
391
+ this._consecutiveFailures = 0
392
+ this._lastVoiceDataUpdate = 0
362
393
  }
363
394
  }
364
395
 
@@ -285,11 +285,37 @@ class Player extends EventEmitter {
285
285
  return this
286
286
  }
287
287
 
288
+ async _waitForConnection(timeout = RESUME_TIMEOUT) {
289
+ if (this.destroyed) return
290
+ if (this.connected) return
291
+ return new Promise((resolve, reject) => {
292
+ let timer
293
+ const cleanup = () => {
294
+ if (timer) { this._pendingTimers?.delete(timer); clearTimeout(timer) }
295
+ this.off('playerUpdate', onUpdate)
296
+ }
297
+ const onUpdate = payload => {
298
+ if (this.destroyed) { cleanup(); return reject(new Error('Player destroyed')) }
299
+ if (payload?.state?.connected || _functions.isNum(payload?.state?.time)) {
300
+ cleanup()
301
+ return resolve()
302
+ }
303
+ }
304
+ this.on('playerUpdate', onUpdate)
305
+ timer = this._createTimer(() => { cleanup(); reject(new Error('No connection confirmation')) }, timeout)
306
+ })
307
+ }
308
+
288
309
  async play() {
289
310
  if (this.destroyed || !this.queue.size) return this
290
- // most lazy fix i ever did lol
291
- if (this.nodes.isNodelink && !this.connected) { await this._delay(1000); if (!this.connected || this.destroyed) return this }
292
- if (!this.connected) return this
311
+ if (!this.connected) {
312
+ try {
313
+ await this._waitForConnection(RESUME_TIMEOUT)
314
+ if (!this.connected || this.destroyed) return this
315
+ } catch {
316
+ return this
317
+ }
318
+ }
293
319
 
294
320
  const item = this.queue.dequeue()
295
321
  if (!item) return this
@@ -313,7 +339,6 @@ class Player extends EventEmitter {
313
339
  if (!voiceChannel) throw new TypeError('Voice channel required')
314
340
  this.deaf = options.deaf !== undefined ? !!options.deaf : true
315
341
  this.mute = !!options.mute
316
- this.connected = true
317
342
  this.destroyed = false
318
343
  this.voiceChannel = voiceChannel
319
344
  this.send({ guild_id: this.guildId, channel_id: voiceChannel, self_deaf: this.deaf, self_mute: this.mute })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aqualink",
3
- "version": "2.17.0",
3
+ "version": "2.17.1",
4
4
  "description": "An Lavalink client, focused in pure performance and features",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",