aqualink 2.20.1 → 3.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 +174 -184
- package/build/handlers/autoplay.js +5 -1
- package/build/structures/Aqua.js +226 -539
- package/build/structures/AquaRecovery.js +901 -0
- package/build/structures/Connection.js +72 -261
- package/build/structures/ConnectionRecovery.js +398 -0
- package/build/structures/Filters.js +93 -12
- package/build/structures/Node.js +93 -54
- package/build/structures/Player.js +283 -326
- package/build/structures/PlayerLifecycle.js +575 -0
- package/build/structures/PlayerLifecycleState.js +42 -0
- package/build/structures/Reporting.js +32 -0
- package/build/structures/Rest.js +25 -2
- package/build/structures/Track.js +2 -2
- package/package.json +1 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const { AqualinkEvents } = require('./AqualinkEvents')
|
|
2
|
+
const ConnectionRecovery = require('./ConnectionRecovery')
|
|
3
|
+
const { reportSuppressedError } = require('./Reporting')
|
|
2
4
|
|
|
3
5
|
const POOL_SIZE = 12
|
|
4
6
|
const UPDATE_TIMEOUT = 4000
|
|
@@ -156,8 +158,18 @@ class Connection {
|
|
|
156
158
|
this.isWaitingForDisconnect = false
|
|
157
159
|
|
|
158
160
|
this._lastStateReqAt = 0
|
|
161
|
+
this._lastResumeBlockedLogAt = 0
|
|
159
162
|
this._stateGeneration = 0
|
|
160
163
|
this._regionMigrationAttempted = false
|
|
164
|
+
this._missingPlayerRecovering = false
|
|
165
|
+
this._lastMissingPlayerRecoverAt = 0
|
|
166
|
+
this._recovery = new ConnectionRecovery(this, {
|
|
167
|
+
_functions,
|
|
168
|
+
STATE,
|
|
169
|
+
RECONNECT_DELAY,
|
|
170
|
+
MAX_RECONNECT_ATTEMPTS,
|
|
171
|
+
RESUME_BACKOFF_MAX
|
|
172
|
+
})
|
|
161
173
|
}
|
|
162
174
|
|
|
163
175
|
_hasValidVoiceData() {
|
|
@@ -191,96 +203,11 @@ class Connection {
|
|
|
191
203
|
}
|
|
192
204
|
|
|
193
205
|
setServerUpdate(data) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const endpoint =
|
|
197
|
-
typeof data.endpoint === 'string' ? data.endpoint.trim() : ''
|
|
198
|
-
if (!endpoint) return
|
|
199
|
-
|
|
200
|
-
if (this._lastEndpoint === endpoint && this.token === data.token) return
|
|
201
|
-
|
|
202
|
-
if (data.txId && data.txId < this.txId) return
|
|
203
|
-
|
|
204
|
-
this._stateGeneration++
|
|
205
|
-
|
|
206
|
-
if (this._lastEndpoint !== endpoint) {
|
|
207
|
-
this.sequence = 0
|
|
208
|
-
this._lastEndpoint = endpoint
|
|
209
|
-
this._reconnectAttempts = 0
|
|
210
|
-
this._consecutiveFailures = 0
|
|
211
|
-
this._regionMigrationAttempted = false
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
this.endpoint = endpoint
|
|
215
|
-
this.region = _functions.extractRegion(endpoint)
|
|
216
|
-
this.token = data.token
|
|
217
|
-
this.channelId = data.channel_id || this.channelId || this.voiceChannel
|
|
218
|
-
this._lastVoiceDataUpdate = Date.now()
|
|
219
|
-
this._aqua?._trace?.('connection.serverUpdate', {
|
|
220
|
-
guildId: this._guildId,
|
|
221
|
-
endpoint: this.endpoint,
|
|
222
|
-
region: this.region,
|
|
223
|
-
txId: data.txId || null
|
|
224
|
-
})
|
|
225
|
-
this._stateFlags &= ~STATE.VOICE_DATA_STALE
|
|
226
|
-
|
|
227
|
-
if (this._player?.paused) this._player.pause(false)
|
|
228
|
-
const migrated = this._checkRegionMigration()
|
|
229
|
-
if (migrated) return
|
|
230
|
-
this._scheduleVoiceUpdate()
|
|
231
|
-
this._player?._flushDeferredPlay?.()
|
|
206
|
+
return this._recovery.setServerUpdate(data)
|
|
232
207
|
}
|
|
233
208
|
|
|
234
209
|
_checkRegionMigration() {
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
!this._aqua?.autoRegionMigrate ||
|
|
238
|
-
!this.region ||
|
|
239
|
-
this.region === 'unknown'
|
|
240
|
-
)
|
|
241
|
-
return false
|
|
242
|
-
const player = this._player
|
|
243
|
-
if (!player || player.destroyed || player._resuming || player._reconnecting)
|
|
244
|
-
return false
|
|
245
|
-
|
|
246
|
-
const currentNode = player.nodes
|
|
247
|
-
if (!currentNode) return false
|
|
248
|
-
|
|
249
|
-
const currentRegions = Array.isArray(currentNode.regions)
|
|
250
|
-
? currentNode.regions
|
|
251
|
-
: []
|
|
252
|
-
const alreadyMatching = currentRegions.some((r) =>
|
|
253
|
-
this._aqua._regionMatches?.(r, this.region)
|
|
254
|
-
)
|
|
255
|
-
if (alreadyMatching) {
|
|
256
|
-
this._regionMigrationAttempted = true
|
|
257
|
-
return false
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const targetNode = this._aqua._findBestNodeForRegion?.(this.region)
|
|
261
|
-
if (!targetNode || targetNode === currentNode) return false
|
|
262
|
-
|
|
263
|
-
this._regionMigrationAttempted = true
|
|
264
|
-
this._aqua?._trace?.('connection.region.migrate', {
|
|
265
|
-
guildId: this._guildId,
|
|
266
|
-
region: this.region,
|
|
267
|
-
from: currentNode?.name || currentNode?.host,
|
|
268
|
-
to: targetNode?.name || targetNode?.host
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
queueMicrotask(() => {
|
|
272
|
-
this._aqua
|
|
273
|
-
.movePlayerToNode?.(this._guildId, targetNode, 'region')
|
|
274
|
-
.catch((err) => {
|
|
275
|
-
this._regionMigrationAttempted = false
|
|
276
|
-
this._aqua?._trace?.('connection.region.migrate.error', {
|
|
277
|
-
guildId: this._guildId,
|
|
278
|
-
region: this.region,
|
|
279
|
-
error: err?.message || String(err)
|
|
280
|
-
})
|
|
281
|
-
})
|
|
282
|
-
})
|
|
283
|
-
return true
|
|
210
|
+
return this._recovery.checkRegionMigration()
|
|
284
211
|
}
|
|
285
212
|
|
|
286
213
|
resendVoiceUpdate(force = false) {
|
|
@@ -306,10 +233,12 @@ class Connection {
|
|
|
306
233
|
if (data.txId && data.txId < this.txId) return
|
|
307
234
|
|
|
308
235
|
if (!channelId) {
|
|
309
|
-
this._aqua?.
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
236
|
+
if (this._aqua?.debugTrace) {
|
|
237
|
+
this._aqua._trace('connection.stateUpdate.nullChannel', {
|
|
238
|
+
guildId: this._guildId,
|
|
239
|
+
txId: data.txId || null
|
|
240
|
+
})
|
|
241
|
+
}
|
|
313
242
|
this.isWaitingForDisconnect = true
|
|
314
243
|
if (!this._nullChannelTimer) {
|
|
315
244
|
this._nullChannelTimer = setTimeout(() => {
|
|
@@ -322,12 +251,14 @@ class Connection {
|
|
|
322
251
|
}
|
|
323
252
|
|
|
324
253
|
this.isWaitingForDisconnect = false
|
|
325
|
-
this._aqua?.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
254
|
+
if (this._aqua?.debugTrace) {
|
|
255
|
+
this._aqua._trace('connection.stateUpdate', {
|
|
256
|
+
guildId: this._guildId,
|
|
257
|
+
channelId,
|
|
258
|
+
sessionId,
|
|
259
|
+
txId: data.txId || null
|
|
260
|
+
})
|
|
261
|
+
}
|
|
331
262
|
|
|
332
263
|
if (p && p.txId > this.txId) this.txId = p.txId
|
|
333
264
|
|
|
@@ -368,9 +299,11 @@ class Connection {
|
|
|
368
299
|
|
|
369
300
|
this._stateFlags =
|
|
370
301
|
(this._stateFlags | STATE.DISCONNECTING) & ~STATE.CONNECTED
|
|
371
|
-
this._aqua?.
|
|
372
|
-
|
|
373
|
-
|
|
302
|
+
if (this._aqua?.debugTrace) {
|
|
303
|
+
this._aqua._trace('connection.disconnect', {
|
|
304
|
+
guildId: this._guildId
|
|
305
|
+
})
|
|
306
|
+
}
|
|
374
307
|
this._clearNullChannelTimer()
|
|
375
308
|
this._clearPendingUpdate()
|
|
376
309
|
this._clearReconnectTimer()
|
|
@@ -399,126 +332,30 @@ class Connection {
|
|
|
399
332
|
try {
|
|
400
333
|
const now = Date.now()
|
|
401
334
|
if (now - (this._lastStateReqAt || 0) < 1500) return false
|
|
402
|
-
this._lastStateReqAt = now
|
|
403
335
|
|
|
404
336
|
if (
|
|
405
337
|
typeof this._player?.send !== 'function' ||
|
|
406
338
|
!this._player.voiceChannel
|
|
407
339
|
)
|
|
408
340
|
return false
|
|
409
|
-
this._player.send({
|
|
341
|
+
const queued = this._player.send({
|
|
410
342
|
guild_id: this._guildId,
|
|
411
343
|
channel_id: this._player.voiceChannel,
|
|
412
344
|
self_deaf: this._player.deaf,
|
|
413
345
|
self_mute: this._player.mute
|
|
414
346
|
})
|
|
415
|
-
|
|
416
|
-
|
|
347
|
+
this._lastStateReqAt = now
|
|
348
|
+
return queued !== false
|
|
349
|
+
} catch (error) {
|
|
350
|
+
reportSuppressedError(this._aqua, 'connection.requestVoiceState', error, {
|
|
351
|
+
guildId: this._guildId
|
|
352
|
+
})
|
|
417
353
|
return false
|
|
418
354
|
}
|
|
419
355
|
}
|
|
420
356
|
|
|
421
357
|
async attemptResume() {
|
|
422
|
-
|
|
423
|
-
this._aqua?._trace?.('connection.resume.attempt', {
|
|
424
|
-
guildId: this._guildId,
|
|
425
|
-
reconnectAttempts: this._reconnectAttempts,
|
|
426
|
-
hasSessionId: !!this.sessionId,
|
|
427
|
-
hasEndpoint: !!this.endpoint,
|
|
428
|
-
hasToken: !!this.token
|
|
429
|
-
})
|
|
430
|
-
|
|
431
|
-
const currentGen = this._stateGeneration
|
|
432
|
-
|
|
433
|
-
if (
|
|
434
|
-
!this.sessionId ||
|
|
435
|
-
!this.endpoint ||
|
|
436
|
-
!this.token ||
|
|
437
|
-
this._stateFlags & STATE.VOICE_DATA_STALE
|
|
438
|
-
) {
|
|
439
|
-
this._aqua.emit(
|
|
440
|
-
AqualinkEvents.Debug,
|
|
441
|
-
`Resume blocked: missing voice data for guild ${this._guildId}, requesting voice state`
|
|
442
|
-
)
|
|
443
|
-
this._requestVoiceState()
|
|
444
|
-
return false
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
this.txId = this._player.txId || this.txId
|
|
448
|
-
this._stateFlags |= STATE.ATTEMPTING_RESUME
|
|
449
|
-
this._reconnectAttempts++
|
|
450
|
-
this._aqua.emit(
|
|
451
|
-
AqualinkEvents.Debug,
|
|
452
|
-
`Attempt resume: guild=${this._guildId} endpoint=${this.endpoint} session=${this.sessionId}`
|
|
453
|
-
)
|
|
454
|
-
|
|
455
|
-
const payload = sharedPool.acquire()
|
|
456
|
-
try {
|
|
457
|
-
_functions.fillVoicePayload(
|
|
458
|
-
payload,
|
|
459
|
-
this._guildId,
|
|
460
|
-
this,
|
|
461
|
-
this._player,
|
|
462
|
-
true
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
if (this._stateGeneration !== currentGen) {
|
|
466
|
-
this._aqua.emit(
|
|
467
|
-
AqualinkEvents.Debug,
|
|
468
|
-
`Resume aborted: State changed during attempt for guild ${this._guildId}`
|
|
469
|
-
)
|
|
470
|
-
return false
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
await this._sendUpdate(payload)
|
|
474
|
-
this._aqua?._trace?.('connection.resume.success', {
|
|
475
|
-
guildId: this._guildId
|
|
476
|
-
})
|
|
477
|
-
|
|
478
|
-
this._reconnectAttempts = 0
|
|
479
|
-
this._consecutiveFailures = 0
|
|
480
|
-
if (this._player) this._player._resuming = false
|
|
481
|
-
|
|
482
|
-
this._aqua.emit(
|
|
483
|
-
AqualinkEvents.Debug,
|
|
484
|
-
`Resume PATCH sent for guild ${this._guildId}`
|
|
485
|
-
)
|
|
486
|
-
return true
|
|
487
|
-
} catch (e) {
|
|
488
|
-
if (this._destroyed || !this._aqua) throw e
|
|
489
|
-
this._consecutiveFailures++
|
|
490
|
-
this._aqua.emit(
|
|
491
|
-
AqualinkEvents.Debug,
|
|
492
|
-
`Resume failed for guild ${this._guildId}: ${e?.message || e}`
|
|
493
|
-
)
|
|
494
|
-
this._aqua?._trace?.('connection.resume.error', {
|
|
495
|
-
guildId: this._guildId,
|
|
496
|
-
error: e?.message || String(e)
|
|
497
|
-
})
|
|
498
|
-
|
|
499
|
-
if (
|
|
500
|
-
this._reconnectAttempts < MAX_RECONNECT_ATTEMPTS &&
|
|
501
|
-
!this._destroyed &&
|
|
502
|
-
this._consecutiveFailures < 5
|
|
503
|
-
) {
|
|
504
|
-
const delay = Math.min(
|
|
505
|
-
RECONNECT_DELAY * (1 << (this._reconnectAttempts - 1)),
|
|
506
|
-
RESUME_BACKOFF_MAX
|
|
507
|
-
)
|
|
508
|
-
this._setReconnectTimer(delay)
|
|
509
|
-
} else {
|
|
510
|
-
this._aqua.emit(
|
|
511
|
-
AqualinkEvents.Debug,
|
|
512
|
-
`Max reconnect attempts/failures reached for guild ${this._guildId}`
|
|
513
|
-
)
|
|
514
|
-
if (this._player) this._player._resuming = false
|
|
515
|
-
this._handleDisconnect()
|
|
516
|
-
}
|
|
517
|
-
return false
|
|
518
|
-
} finally {
|
|
519
|
-
this._stateFlags &= ~STATE.ATTEMPTING_RESUME
|
|
520
|
-
sharedPool.release(payload)
|
|
521
|
-
}
|
|
358
|
+
return this._recovery.attemptResume()
|
|
522
359
|
}
|
|
523
360
|
|
|
524
361
|
_handleReconnect() {
|
|
@@ -556,20 +393,24 @@ class Connection {
|
|
|
556
393
|
|
|
557
394
|
_scheduleVoiceUpdate() {
|
|
558
395
|
if (this._destroyed) {
|
|
559
|
-
this._aqua?.
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
396
|
+
if (this._aqua?.debugTrace) {
|
|
397
|
+
this._aqua._trace('connection.update.skip', {
|
|
398
|
+
guildId: this._guildId,
|
|
399
|
+
reason: 'destroyed'
|
|
400
|
+
})
|
|
401
|
+
}
|
|
563
402
|
return
|
|
564
403
|
}
|
|
565
404
|
if (!this._hasValidVoiceData()) {
|
|
566
|
-
this._aqua?.
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
405
|
+
if (this._aqua?.debugTrace) {
|
|
406
|
+
this._aqua._trace('connection.update.skip', {
|
|
407
|
+
guildId: this._guildId,
|
|
408
|
+
reason: 'invalid_voice_data',
|
|
409
|
+
hasSessionId: !!this.sessionId,
|
|
410
|
+
hasEndpoint: !!this.endpoint,
|
|
411
|
+
hasToken: !!this.token
|
|
412
|
+
})
|
|
413
|
+
}
|
|
573
414
|
return
|
|
574
415
|
}
|
|
575
416
|
|
|
@@ -596,9 +437,11 @@ class Connection {
|
|
|
596
437
|
|
|
597
438
|
if (this._stateFlags & STATE.UPDATE_SCHEDULED) return
|
|
598
439
|
this._stateFlags |= STATE.UPDATE_SCHEDULED
|
|
599
|
-
this._aqua?.
|
|
600
|
-
|
|
601
|
-
|
|
440
|
+
if (this._aqua?.debugTrace) {
|
|
441
|
+
this._aqua._trace('connection.update.scheduled', {
|
|
442
|
+
guildId: this._guildId
|
|
443
|
+
})
|
|
444
|
+
}
|
|
602
445
|
|
|
603
446
|
this._voiceFlushTimer = setTimeout(
|
|
604
447
|
() => this._executeVoiceUpdate(),
|
|
@@ -629,53 +472,20 @@ class Connection {
|
|
|
629
472
|
this._lastSentVoiceKey = key
|
|
630
473
|
|
|
631
474
|
this._sendUpdate(pending.payload)
|
|
632
|
-
.catch(
|
|
475
|
+
.catch((error) =>
|
|
476
|
+
reportSuppressedError(this._aqua, 'connection.update.execute', error, {
|
|
477
|
+
guildId: this._guildId
|
|
478
|
+
})
|
|
479
|
+
)
|
|
633
480
|
.finally(() => sharedPool.release(pending.payload))
|
|
634
481
|
}
|
|
635
482
|
|
|
636
|
-
async
|
|
637
|
-
|
|
638
|
-
|
|
483
|
+
async _recoverMissingPlayer(isSessionError) {
|
|
484
|
+
return this._recovery.recoverMissingPlayer(isSessionError)
|
|
485
|
+
}
|
|
639
486
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
guildId: this._guildId,
|
|
643
|
-
hasSessionId: !!this._rest?.sessionId,
|
|
644
|
-
hasVoice:
|
|
645
|
-
!!payload?.data?.voice?.sessionId && !!payload?.data?.voice?.endpoint
|
|
646
|
-
})
|
|
647
|
-
await this._rest.updatePlayer(payload)
|
|
648
|
-
this._aqua?._trace?.('connection.update.ok', {
|
|
649
|
-
guildId: this._guildId
|
|
650
|
-
})
|
|
651
|
-
} catch (e) {
|
|
652
|
-
this._aqua?._trace?.('connection.update.error', {
|
|
653
|
-
guildId: this._guildId,
|
|
654
|
-
statusCode: e?.statusCode || e?.response?.statusCode || null,
|
|
655
|
-
error: e?.message || String(e)
|
|
656
|
-
})
|
|
657
|
-
if (e.statusCode === 404 || e.response?.statusCode === 404) {
|
|
658
|
-
const isSessionError = e.body?.message?.includes('sessionId') || false
|
|
659
|
-
if (this._aqua) {
|
|
660
|
-
this._aqua.emit(
|
|
661
|
-
AqualinkEvents.Debug,
|
|
662
|
-
`[Aqua/Connection] Player ${this._guildId} not found (404)${isSessionError ? ' - Session invalid' : ''}. Destroying.`
|
|
663
|
-
)
|
|
664
|
-
if (isSessionError && this._player?.nodes?._clearSession) {
|
|
665
|
-
this._player.nodes._clearSession()
|
|
666
|
-
}
|
|
667
|
-
await this._aqua.destroyPlayer(this._guildId)
|
|
668
|
-
}
|
|
669
|
-
throw e
|
|
670
|
-
}
|
|
671
|
-
if (!_functions.isNetworkError(e)) {
|
|
672
|
-
this._aqua.emit(
|
|
673
|
-
AqualinkEvents.Debug,
|
|
674
|
-
new Error(`Voice update failed: ${e?.message || e}`)
|
|
675
|
-
)
|
|
676
|
-
}
|
|
677
|
-
throw e
|
|
678
|
-
}
|
|
487
|
+
async _sendUpdate(payload) {
|
|
488
|
+
return this._recovery.sendUpdate(payload)
|
|
679
489
|
}
|
|
680
490
|
|
|
681
491
|
destroy() {
|
|
@@ -699,6 +509,7 @@ class Connection {
|
|
|
699
509
|
this._reconnectAttempts = 0
|
|
700
510
|
this._consecutiveFailures = 0
|
|
701
511
|
this._lastVoiceDataUpdate = 0
|
|
512
|
+
this._recovery = null
|
|
702
513
|
}
|
|
703
514
|
}
|
|
704
515
|
|