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
package/build/structures/Node.js
CHANGED
|
@@ -80,6 +80,16 @@ const _functions = {
|
|
|
80
80
|
|
|
81
81
|
errMsg(err) {
|
|
82
82
|
return err?.message || String(err)
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
statusCode(err) {
|
|
86
|
+
return (
|
|
87
|
+
err?.statusCode ||
|
|
88
|
+
err?.status ||
|
|
89
|
+
err?.response?.statusCode ||
|
|
90
|
+
err?.response?.status ||
|
|
91
|
+
null
|
|
92
|
+
)
|
|
83
93
|
}
|
|
84
94
|
}
|
|
85
95
|
|
|
@@ -102,10 +112,10 @@ class Node {
|
|
|
102
112
|
this.host = connOptions.host || 'localhost'
|
|
103
113
|
this.name = connOptions.name || this.host
|
|
104
114
|
this.port = connOptions.port || 2333
|
|
105
|
-
this.auth = connOptions.auth || 'youshallnotpass'
|
|
115
|
+
this.auth = connOptions.auth || connOptions.password || 'youshallnotpass'
|
|
106
116
|
this.sessionId = connOptions.sessionId || null
|
|
107
117
|
this.regions = connOptions.regions || []
|
|
108
|
-
this.ssl = !!connOptions.ssl
|
|
118
|
+
this.ssl = !!connOptions.ssl || !!connOptions.secure || false
|
|
109
119
|
this.wsUrl = _functions.buildWsUrl(this.host, this.port, this.ssl)
|
|
110
120
|
|
|
111
121
|
this.rest = new Rest(aqua, this)
|
|
@@ -187,10 +197,12 @@ class Node {
|
|
|
187
197
|
this._isConnecting = false
|
|
188
198
|
this.reconnectAttempted = 0
|
|
189
199
|
this._emitDebug('WebSocket connection established')
|
|
190
|
-
this.aqua?.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
200
|
+
if (this.aqua?.debugTrace) {
|
|
201
|
+
this.aqua._trace('node.ws.open', {
|
|
202
|
+
node: this.name,
|
|
203
|
+
reconnectAttempted: this.reconnectAttempted
|
|
204
|
+
})
|
|
205
|
+
}
|
|
194
206
|
|
|
195
207
|
if (!this.aqua?.bypassChecks?.nodeFetchInfo && !this.info) {
|
|
196
208
|
const timeoutId = setTimeout(() => {
|
|
@@ -203,7 +215,15 @@ class Node {
|
|
|
203
215
|
this.isNodelink = !!this.info?.isNodelink
|
|
204
216
|
} catch (err) {
|
|
205
217
|
this.info = null
|
|
206
|
-
|
|
218
|
+
if (_functions.statusCode(err) === 404) {
|
|
219
|
+
this._emitDebug(
|
|
220
|
+
'Node info endpoint unavailable (HTTP 404); continuing without remote info'
|
|
221
|
+
)
|
|
222
|
+
} else {
|
|
223
|
+
this._emitError(
|
|
224
|
+
`Failed to fetch node info: ${_functions.errMsg(err)}`
|
|
225
|
+
)
|
|
226
|
+
}
|
|
207
227
|
} finally {
|
|
208
228
|
clearTimeout(timeoutId)
|
|
209
229
|
}
|
|
@@ -272,12 +292,14 @@ class Node {
|
|
|
272
292
|
code,
|
|
273
293
|
reason: _functions.reasonToString(reason)
|
|
274
294
|
})
|
|
275
|
-
this.aqua?.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
295
|
+
if (this.aqua?.debugTrace) {
|
|
296
|
+
this.aqua._trace('node.ws.close', {
|
|
297
|
+
node: this.name,
|
|
298
|
+
code,
|
|
299
|
+
reason: _functions.reasonToString(reason),
|
|
300
|
+
hasSessionId: !!this.sessionId
|
|
301
|
+
})
|
|
302
|
+
}
|
|
281
303
|
|
|
282
304
|
if (this.isDestroyed) return
|
|
283
305
|
|
|
@@ -311,11 +333,13 @@ class Node {
|
|
|
311
333
|
this._clearReconnectTimeout()
|
|
312
334
|
|
|
313
335
|
const attempt = ++this.reconnectAttempted
|
|
314
|
-
this.aqua?.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
336
|
+
if (this.aqua?.debugTrace) {
|
|
337
|
+
this.aqua._trace('node.ws.reconnect.scheduled', {
|
|
338
|
+
node: this.name,
|
|
339
|
+
attempt,
|
|
340
|
+
infinite: !!this.infiniteReconnects
|
|
341
|
+
})
|
|
342
|
+
}
|
|
319
343
|
|
|
320
344
|
if (this.infiniteReconnects) {
|
|
321
345
|
this.aqua.emit(AqualinkEvents.NodeReconnect, this, {
|
|
@@ -384,10 +408,12 @@ class Node {
|
|
|
384
408
|
this._isConnecting = true
|
|
385
409
|
this.state = NODE_STATE.CONNECTING
|
|
386
410
|
this._cleanup()
|
|
387
|
-
this.aqua?.
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
411
|
+
if (this.aqua?.debugTrace) {
|
|
412
|
+
this.aqua._trace('node.ws.connect', {
|
|
413
|
+
node: this.name,
|
|
414
|
+
url: this.wsUrl
|
|
415
|
+
})
|
|
416
|
+
}
|
|
391
417
|
|
|
392
418
|
try {
|
|
393
419
|
const h = this._boundHandlers
|
|
@@ -453,7 +479,7 @@ class Node {
|
|
|
453
479
|
|
|
454
480
|
const ws = new WebSocketImpl(this.wsUrl, {
|
|
455
481
|
headers: this._headers,
|
|
456
|
-
perMessageDeflate:
|
|
482
|
+
perMessageDeflate: false,
|
|
457
483
|
handshakeTimeout: this.timeout,
|
|
458
484
|
maxPayload: this.maxPayload,
|
|
459
485
|
skipUTF8Validation: this.skipUTF8Validation
|
|
@@ -584,36 +610,66 @@ class Node {
|
|
|
584
610
|
}
|
|
585
611
|
|
|
586
612
|
const oldSessionId = this.sessionId
|
|
587
|
-
const
|
|
588
|
-
|
|
613
|
+
const sessionInvalidated = !payload.resumed && !!oldSessionId
|
|
614
|
+
const sessionChanged = sessionInvalidated && oldSessionId !== sessionId
|
|
589
615
|
|
|
590
616
|
this.sessionId = sessionId
|
|
591
|
-
this.aqua?.
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
617
|
+
if (this.aqua?.debugTrace) {
|
|
618
|
+
this.aqua._trace('node.ready.packet', {
|
|
619
|
+
node: this.name,
|
|
620
|
+
resumed: !!payload.resumed,
|
|
621
|
+
oldSessionId,
|
|
622
|
+
newSessionId: sessionId
|
|
623
|
+
})
|
|
624
|
+
}
|
|
597
625
|
this.rest.setSessionId(sessionId)
|
|
598
626
|
this._headers['Session-Id'] = sessionId
|
|
599
627
|
|
|
600
|
-
if (
|
|
628
|
+
if (sessionInvalidated && this.aqua?.players) {
|
|
601
629
|
this._emitDebug(
|
|
602
|
-
`Session
|
|
630
|
+
`Session invalidated (resumed=${!!payload.resumed}, old=${oldSessionId}, new=${sessionId}), invalidating stale players`
|
|
603
631
|
)
|
|
632
|
+
try {
|
|
633
|
+
await this.aqua._storeBrokenPlayers?.(this)
|
|
634
|
+
} catch (e) {
|
|
635
|
+
this._emitDebug(
|
|
636
|
+
`Failed to snapshot stale players before invalidation: ${e?.message || e}`
|
|
637
|
+
)
|
|
638
|
+
}
|
|
639
|
+
for (const [, player] of this.aqua.players) {
|
|
640
|
+
if (player?.nodes === this || player?.nodes?.name === this.name) {
|
|
641
|
+
if (player.connection) {
|
|
642
|
+
player.connection._lastEndpoint = null
|
|
643
|
+
player.connection._stateFlags |= 512
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
604
648
|
const playersToDestroy = []
|
|
605
649
|
for (const [guildId, player] of this.aqua.players) {
|
|
606
650
|
if (player?.nodes === this || player?.nodes?.name === this.name) {
|
|
607
|
-
playersToDestroy.push(guildId)
|
|
651
|
+
playersToDestroy.push({ guildId, player })
|
|
608
652
|
}
|
|
609
653
|
}
|
|
610
|
-
for (const guildId of playersToDestroy) {
|
|
654
|
+
for (const { guildId, player } of playersToDestroy) {
|
|
611
655
|
try {
|
|
612
|
-
this._emitDebug(
|
|
613
|
-
|
|
656
|
+
this._emitDebug(
|
|
657
|
+
`Invalidating stale player for guild ${guildId} without voice disconnect`
|
|
658
|
+
)
|
|
659
|
+
player?.destroy?.({
|
|
660
|
+
preserveClient: true,
|
|
661
|
+
skipRemote: true,
|
|
662
|
+
preserveMessage: true,
|
|
663
|
+
preserveTracks: true,
|
|
664
|
+
preserveReconnecting: true
|
|
665
|
+
})
|
|
666
|
+
if (this.aqua.players.get(String(guildId)) === player) {
|
|
667
|
+
this.aqua.players.delete(String(guildId))
|
|
668
|
+
}
|
|
669
|
+
this.players?.delete?.(player)
|
|
614
670
|
} catch (e) {
|
|
615
671
|
this._emitDebug(
|
|
616
|
-
`Failed to
|
|
672
|
+
`Failed to invalidate stale player ${guildId}: ${e?.message || e}`
|
|
617
673
|
)
|
|
618
674
|
}
|
|
619
675
|
}
|
|
@@ -621,7 +677,8 @@ class Node {
|
|
|
621
677
|
|
|
622
678
|
this.aqua.emit(AqualinkEvents.NodeReady, this, {
|
|
623
679
|
resumed: !!payload.resumed,
|
|
624
|
-
sessionChanged
|
|
680
|
+
sessionChanged,
|
|
681
|
+
sessionInvalidated
|
|
625
682
|
})
|
|
626
683
|
|
|
627
684
|
if (this.autoResume) {
|
|
@@ -635,55 +692,101 @@ class Node {
|
|
|
635
692
|
|
|
636
693
|
async _resumePlayers() {
|
|
637
694
|
if (!this.sessionId) return
|
|
638
|
-
this.aqua?.
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
695
|
+
if (this.aqua?.debugTrace) {
|
|
696
|
+
this.aqua._trace('node.resume.begin', {
|
|
697
|
+
node: this.name,
|
|
698
|
+
sessionId: this.sessionId,
|
|
699
|
+
players: this.aqua?.players?.size || 0
|
|
700
|
+
})
|
|
701
|
+
}
|
|
643
702
|
|
|
703
|
+
let resumeSupported = true
|
|
644
704
|
try {
|
|
645
705
|
await this.rest.makeRequest('PATCH', `/v4/sessions/${this.sessionId}`, {
|
|
646
706
|
resuming: true,
|
|
647
707
|
timeout: this.resumeTimeout
|
|
648
708
|
})
|
|
709
|
+
} catch (err) {
|
|
710
|
+
if (_functions.statusCode(err) === 404) {
|
|
711
|
+
resumeSupported = false
|
|
712
|
+
this._emitDebug(
|
|
713
|
+
'Session resume endpoint unavailable (HTTP 404); falling back without server-side session resume'
|
|
714
|
+
)
|
|
715
|
+
} else {
|
|
716
|
+
if (this.aqua?.debugTrace) {
|
|
717
|
+
this.aqua._trace('node.resume.error', {
|
|
718
|
+
node: this.name,
|
|
719
|
+
error: _functions.errMsg(err)
|
|
720
|
+
})
|
|
721
|
+
}
|
|
722
|
+
this._emitError(`Failed to resume session: ${_functions.errMsg(err)}`)
|
|
723
|
+
throw err
|
|
724
|
+
}
|
|
725
|
+
}
|
|
649
726
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
727
|
+
if (!resumeSupported) {
|
|
728
|
+
try {
|
|
729
|
+
const existingPlayers = await this.rest.getPlayers()
|
|
730
|
+
if (Array.isArray(existingPlayers) && existingPlayers.length === 0) {
|
|
731
|
+
this._emitDebug(
|
|
732
|
+
'No players found on Lavalink for this session; will rejoin voice only'
|
|
733
|
+
)
|
|
734
|
+
}
|
|
735
|
+
} catch (_) {
|
|
736
|
+
// getPlayers may also 404 — that's fine, we already know session is invalid
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (this.aqua?.players) {
|
|
741
|
+
const PLAYER_BATCH_SIZE = 20
|
|
742
|
+
const playersToResume = []
|
|
743
|
+
for (const [guildId, player] of this.aqua.players) {
|
|
744
|
+
if (
|
|
745
|
+
(player?.nodes === this || player?.nodes?.name === this.name) &&
|
|
746
|
+
player.voiceChannel &&
|
|
747
|
+
!player.destroyed
|
|
748
|
+
) {
|
|
749
|
+
playersToResume.push({ guildId, player })
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
for (let i = 0; i < playersToResume.length; i += PLAYER_BATCH_SIZE) {
|
|
754
|
+
const batch = playersToResume.slice(i, i + PLAYER_BATCH_SIZE)
|
|
755
|
+
await Promise.allSettled(
|
|
756
|
+
batch.map(async ({ guildId, player }) => {
|
|
656
757
|
try {
|
|
758
|
+
const recoveryToken = player._claimVoiceRecovery?.(
|
|
759
|
+
resumeSupported
|
|
760
|
+
? 'node_resume_rejoin'
|
|
761
|
+
: 'node_rejoin_after_resume_404'
|
|
762
|
+
)
|
|
657
763
|
this._emitDebug(`Rejoining voice for guild ${guildId} on resume`)
|
|
658
|
-
this.aqua?.
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
764
|
+
if (this.aqua?.debugTrace) {
|
|
765
|
+
this.aqua._trace('node.resume.rejoin', {
|
|
766
|
+
node: this.name,
|
|
767
|
+
guildId,
|
|
768
|
+
voiceChannel: player.voiceChannel,
|
|
769
|
+
resumeSupported
|
|
770
|
+
})
|
|
771
|
+
}
|
|
772
|
+
if (player._isVoiceRecoveryActive?.(recoveryToken))
|
|
773
|
+
player.connect({
|
|
774
|
+
voiceChannel: player.voiceChannel,
|
|
775
|
+
deaf: player.deaf,
|
|
776
|
+
mute: player.mute
|
|
777
|
+
})
|
|
668
778
|
} catch (e) {
|
|
669
779
|
this._emitDebug(
|
|
670
780
|
`Failed to rejoin voice for ${guildId}: ${e?.message || e}`
|
|
671
781
|
)
|
|
672
782
|
}
|
|
673
|
-
}
|
|
674
|
-
|
|
783
|
+
})
|
|
784
|
+
)
|
|
675
785
|
}
|
|
786
|
+
}
|
|
676
787
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
}
|
|
680
|
-
} catch (err) {
|
|
681
|
-
this.aqua?._trace?.('node.resume.error', {
|
|
682
|
-
node: this.name,
|
|
683
|
-
error: _functions.errMsg(err)
|
|
684
|
-
})
|
|
685
|
-
this._emitError(`Failed to resume session: ${_functions.errMsg(err)}`)
|
|
686
|
-
throw err
|
|
788
|
+
if (this.aqua.loadPlayers && this.aqua.players.size === 0) {
|
|
789
|
+
await this.aqua.loadPlayers()
|
|
687
790
|
}
|
|
688
791
|
}
|
|
689
792
|
|