aqualink 2.20.1 → 3.1.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/index.d.ts +1 -1
- package/build/structures/Aqua.js +235 -540
- package/build/structures/AquaRecovery.js +905 -0
- package/build/structures/Connection.js +84 -262
- package/build/structures/ConnectionRecovery.js +425 -0
- package/build/structures/Filters.js +96 -13
- package/build/structures/Node.js +175 -72
- package/build/structures/Player.js +344 -338
- package/build/structures/PlayerLifecycle.js +584 -0
- package/build/structures/PlayerLifecycleState.js +42 -0
- package/build/structures/Queue.js +5 -1
- package/build/structures/Reporting.js +32 -0
- package/build/structures/Rest.js +51 -11
- 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()
|
|
@@ -388,7 +321,9 @@ class Connection {
|
|
|
388
321
|
} catch (e) {
|
|
389
322
|
this._aqua?.emit?.(
|
|
390
323
|
AqualinkEvents.Debug,
|
|
391
|
-
new Error(
|
|
324
|
+
new Error(
|
|
325
|
+
`Player destroy failed (guild=${this._guildId}, sessionId=${this.sessionId || 'none'}): ${e?.message || e}`
|
|
326
|
+
)
|
|
392
327
|
)
|
|
393
328
|
} finally {
|
|
394
329
|
this._stateFlags &= ~STATE.DISCONNECTING
|
|
@@ -399,126 +334,30 @@ class Connection {
|
|
|
399
334
|
try {
|
|
400
335
|
const now = Date.now()
|
|
401
336
|
if (now - (this._lastStateReqAt || 0) < 1500) return false
|
|
402
|
-
this._lastStateReqAt = now
|
|
403
337
|
|
|
404
338
|
if (
|
|
405
339
|
typeof this._player?.send !== 'function' ||
|
|
406
340
|
!this._player.voiceChannel
|
|
407
341
|
)
|
|
408
342
|
return false
|
|
409
|
-
this._player.send({
|
|
343
|
+
const queued = this._player.send({
|
|
410
344
|
guild_id: this._guildId,
|
|
411
345
|
channel_id: this._player.voiceChannel,
|
|
412
346
|
self_deaf: this._player.deaf,
|
|
413
347
|
self_mute: this._player.mute
|
|
414
348
|
})
|
|
415
|
-
|
|
416
|
-
|
|
349
|
+
this._lastStateReqAt = now
|
|
350
|
+
return queued !== false
|
|
351
|
+
} catch (error) {
|
|
352
|
+
reportSuppressedError(this._aqua, 'connection.requestVoiceState', error, {
|
|
353
|
+
guildId: this._guildId
|
|
354
|
+
})
|
|
417
355
|
return false
|
|
418
356
|
}
|
|
419
357
|
}
|
|
420
358
|
|
|
421
359
|
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
|
-
}
|
|
360
|
+
return this._recovery.attemptResume()
|
|
522
361
|
}
|
|
523
362
|
|
|
524
363
|
_handleReconnect() {
|
|
@@ -556,20 +395,24 @@ class Connection {
|
|
|
556
395
|
|
|
557
396
|
_scheduleVoiceUpdate() {
|
|
558
397
|
if (this._destroyed) {
|
|
559
|
-
this._aqua?.
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
398
|
+
if (this._aqua?.debugTrace) {
|
|
399
|
+
this._aqua._trace('connection.update.skip', {
|
|
400
|
+
guildId: this._guildId,
|
|
401
|
+
reason: 'destroyed'
|
|
402
|
+
})
|
|
403
|
+
}
|
|
563
404
|
return
|
|
564
405
|
}
|
|
565
406
|
if (!this._hasValidVoiceData()) {
|
|
566
|
-
this._aqua?.
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
407
|
+
if (this._aqua?.debugTrace) {
|
|
408
|
+
this._aqua._trace('connection.update.skip', {
|
|
409
|
+
guildId: this._guildId,
|
|
410
|
+
reason: 'invalid_voice_data',
|
|
411
|
+
hasSessionId: !!this.sessionId,
|
|
412
|
+
hasEndpoint: !!this.endpoint,
|
|
413
|
+
hasToken: !!this.token
|
|
414
|
+
})
|
|
415
|
+
}
|
|
573
416
|
return
|
|
574
417
|
}
|
|
575
418
|
|
|
@@ -596,9 +439,11 @@ class Connection {
|
|
|
596
439
|
|
|
597
440
|
if (this._stateFlags & STATE.UPDATE_SCHEDULED) return
|
|
598
441
|
this._stateFlags |= STATE.UPDATE_SCHEDULED
|
|
599
|
-
this._aqua?.
|
|
600
|
-
|
|
601
|
-
|
|
442
|
+
if (this._aqua?.debugTrace) {
|
|
443
|
+
this._aqua._trace('connection.update.scheduled', {
|
|
444
|
+
guildId: this._guildId
|
|
445
|
+
})
|
|
446
|
+
}
|
|
602
447
|
|
|
603
448
|
this._voiceFlushTimer = setTimeout(
|
|
604
449
|
() => this._executeVoiceUpdate(),
|
|
@@ -609,6 +454,15 @@ class Connection {
|
|
|
609
454
|
|
|
610
455
|
_executeVoiceUpdate() {
|
|
611
456
|
if (this._destroyed) return
|
|
457
|
+
if (this._stateFlags & STATE.DISCONNECTING) {
|
|
458
|
+
this._stateFlags &= ~STATE.UPDATE_SCHEDULED
|
|
459
|
+
this._voiceFlushTimer = null
|
|
460
|
+
if (this._pendingUpdate) {
|
|
461
|
+
sharedPool.release(this._pendingUpdate.payload)
|
|
462
|
+
this._pendingUpdate = null
|
|
463
|
+
}
|
|
464
|
+
return
|
|
465
|
+
}
|
|
612
466
|
this._stateFlags &= ~STATE.UPDATE_SCHEDULED
|
|
613
467
|
this._voiceFlushTimer = null
|
|
614
468
|
|
|
@@ -629,53 +483,20 @@ class Connection {
|
|
|
629
483
|
this._lastSentVoiceKey = key
|
|
630
484
|
|
|
631
485
|
this._sendUpdate(pending.payload)
|
|
632
|
-
.catch(
|
|
486
|
+
.catch((error) =>
|
|
487
|
+
reportSuppressedError(this._aqua, 'connection.update.execute', error, {
|
|
488
|
+
guildId: this._guildId
|
|
489
|
+
})
|
|
490
|
+
)
|
|
633
491
|
.finally(() => sharedPool.release(pending.payload))
|
|
634
492
|
}
|
|
635
493
|
|
|
636
|
-
async
|
|
637
|
-
|
|
638
|
-
|
|
494
|
+
async _recoverMissingPlayer(isSessionError) {
|
|
495
|
+
return this._recovery.recoverMissingPlayer(isSessionError)
|
|
496
|
+
}
|
|
639
497
|
|
|
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
|
-
}
|
|
498
|
+
async _sendUpdate(payload) {
|
|
499
|
+
return this._recovery.sendUpdate(payload)
|
|
679
500
|
}
|
|
680
501
|
|
|
681
502
|
destroy() {
|
|
@@ -699,6 +520,7 @@ class Connection {
|
|
|
699
520
|
this._reconnectAttempts = 0
|
|
700
521
|
this._consecutiveFailures = 0
|
|
701
522
|
this._lastVoiceDataUpdate = 0
|
|
523
|
+
this._recovery = null
|
|
702
524
|
}
|
|
703
525
|
}
|
|
704
526
|
|