aqualink 3.0.0 → 3.2.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/build/handlers/autoplay.js +67 -66
- package/build/index.d.ts +171 -134
- package/build/index.js +3 -3
- package/build/structures/Aqua.js +76 -44
- package/build/structures/AquaRecovery.js +48 -13
- package/build/structures/AqualinkEvents.js +2 -0
- package/build/structures/Connection.js +21 -2
- package/build/structures/ConnectionRecovery.js +36 -44
- package/build/structures/Filters.js +3 -1
- package/build/structures/Node.js +266 -124
- package/build/structures/Player.js +146 -66
- package/build/structures/PlayerLifecycle.js +24 -6
- package/build/structures/Queue.js +5 -1
- package/build/structures/Reporting.js +12 -0
- package/build/structures/Rest.js +74 -51
- package/build/structures/Track.js +10 -3
- package/package.json +70 -70
package/build/index.js
CHANGED
|
@@ -51,9 +51,9 @@ const VoiceRegion = Object.freeze({
|
|
|
51
51
|
CanadaToronto: 'yyz',
|
|
52
52
|
CanadaMontreal: 'ymq',
|
|
53
53
|
|
|
54
|
-
Brazil: 'gru'
|
|
55
|
-
Chile: 'scl'
|
|
56
|
-
Argentina: 'eze'
|
|
54
|
+
Brazil: 'gru', // i only know this endpoint lol.
|
|
55
|
+
Chile: 'scl',
|
|
56
|
+
Argentina: 'eze'
|
|
57
57
|
})
|
|
58
58
|
|
|
59
59
|
module.exports = {
|
package/build/structures/Aqua.js
CHANGED
|
@@ -7,7 +7,7 @@ const AquaRecovery = require('./AquaRecovery')
|
|
|
7
7
|
const Node = require('./Node')
|
|
8
8
|
const Player = require('./Player')
|
|
9
9
|
const Track = require('./Track')
|
|
10
|
-
const { reportSuppressedError } = require('./Reporting')
|
|
10
|
+
const { emitOperationalError, reportSuppressedError } = require('./Reporting')
|
|
11
11
|
const { version: pkgVersion } = require('../../package.json')
|
|
12
12
|
|
|
13
13
|
const SEARCH_PREFIX = ':'
|
|
@@ -73,10 +73,12 @@ const _functions = {
|
|
|
73
73
|
isUrl: (query) => {
|
|
74
74
|
if (typeof query !== 'string' || query.length <= 8) return false
|
|
75
75
|
const q = query.trimStart()
|
|
76
|
-
return
|
|
76
|
+
return (
|
|
77
|
+
q.startsWith('http://') || q.startsWith('https://') || q.includes(':')
|
|
78
|
+
)
|
|
77
79
|
},
|
|
78
80
|
formatQuery(query, source) {
|
|
79
|
-
return
|
|
81
|
+
return _functions.isUrl(query) ? query : `${source}${SEARCH_PREFIX}${query}`
|
|
80
82
|
},
|
|
81
83
|
makeTrack: (t, requester, node) => new Track(t, requester, node),
|
|
82
84
|
safeCall(fn) {
|
|
@@ -191,7 +193,10 @@ class Aqua extends EventEmitter {
|
|
|
191
193
|
|
|
192
194
|
_trace(event, data = null) {
|
|
193
195
|
if (!this.debugTrace) return
|
|
194
|
-
if (
|
|
196
|
+
if (
|
|
197
|
+
!this._traceBuffer ||
|
|
198
|
+
this._traceBuffer.length !== this.traceMaxEntries
|
|
199
|
+
) {
|
|
195
200
|
this._traceBuffer = new Array(this.traceMaxEntries)
|
|
196
201
|
this._traceBufferCount = 0
|
|
197
202
|
this._traceBufferIndex = 0
|
|
@@ -346,9 +351,14 @@ class Aqua extends EventEmitter {
|
|
|
346
351
|
this._invalidateCache()
|
|
347
352
|
queueMicrotask(() => {
|
|
348
353
|
this._storeBrokenPlayers(node).catch((error) =>
|
|
349
|
-
reportSuppressedError(
|
|
350
|
-
|
|
351
|
-
|
|
354
|
+
reportSuppressedError(
|
|
355
|
+
this,
|
|
356
|
+
'aqua.nodeDisconnect.storeBrokenPlayers',
|
|
357
|
+
error,
|
|
358
|
+
{
|
|
359
|
+
node: node?.name || node?.host
|
|
360
|
+
}
|
|
361
|
+
)
|
|
352
362
|
)
|
|
353
363
|
this._performCleanup()
|
|
354
364
|
})
|
|
@@ -484,12 +494,7 @@ class Aqua extends EventEmitter {
|
|
|
484
494
|
(stats.memory ? stats.memory.used / reservable : 0) * 40 +
|
|
485
495
|
(node.rest?.calls || 0) * 0.001
|
|
486
496
|
if (this._nodeLoadCache.size >= MAX_CACHE_SIZE) {
|
|
487
|
-
|
|
488
|
-
while (this._nodeLoadCache.size >= MAX_CACHE_SIZE) {
|
|
489
|
-
const oldest = iterator.next().value
|
|
490
|
-
if (!oldest) break
|
|
491
|
-
this._nodeLoadCache.delete(oldest)
|
|
492
|
-
}
|
|
497
|
+
this._nodeLoadCache.delete(this._nodeLoadCache.keys().next().value)
|
|
493
498
|
}
|
|
494
499
|
this._nodeLoadCache.set(id, { load, time: now })
|
|
495
500
|
return load
|
|
@@ -541,17 +546,12 @@ class Aqua extends EventEmitter {
|
|
|
541
546
|
attempts: 0,
|
|
542
547
|
lastAttempt: 0
|
|
543
548
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
return node
|
|
551
|
-
} catch (error) {
|
|
552
|
-
this._cleanupNode(id)
|
|
553
|
-
throw error
|
|
554
|
-
}
|
|
549
|
+
await node.connect()
|
|
550
|
+
this._failoverState[id].connected = true
|
|
551
|
+
this._failoverState[id].failoverInProgress = false
|
|
552
|
+
this._invalidateCache()
|
|
553
|
+
this.emit(AqualinkEvents.NodeCreate, node)
|
|
554
|
+
return node
|
|
555
555
|
}
|
|
556
556
|
|
|
557
557
|
_destroyNode(id) {
|
|
@@ -754,9 +754,8 @@ class Aqua extends EventEmitter {
|
|
|
754
754
|
query,
|
|
755
755
|
source || this.defaultSearchPlatform
|
|
756
756
|
)
|
|
757
|
-
const endpoint = `/${this.restVersion}/loadtracks?identifier=${encodeURIComponent(formatted)}`
|
|
758
757
|
try {
|
|
759
|
-
const response = await node.rest.
|
|
758
|
+
const response = await node.rest.loadTracks(formatted)
|
|
760
759
|
if (
|
|
761
760
|
!response ||
|
|
762
761
|
response.loadType === 'empty' ||
|
|
@@ -765,11 +764,13 @@ class Aqua extends EventEmitter {
|
|
|
765
764
|
return EMPTY_TRACKS_RESPONSE
|
|
766
765
|
return this._constructResponse(response, requester, node)
|
|
767
766
|
} catch (error) {
|
|
768
|
-
throw new Error(
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
)
|
|
767
|
+
if (error?.name === 'AbortError') throw new Error('Request timeout')
|
|
768
|
+
const err = new Error(`Resolve failed: ${error?.message || error}`)
|
|
769
|
+
if (error?.statusCode != null) err.statusCode = error.statusCode
|
|
770
|
+
if (error?.body) err.body = error.body
|
|
771
|
+
if (error?.url) err.url = error.url
|
|
772
|
+
err.cause = error
|
|
773
|
+
throw err
|
|
773
774
|
}
|
|
774
775
|
}
|
|
775
776
|
|
|
@@ -874,11 +875,35 @@ class Aqua extends EventEmitter {
|
|
|
874
875
|
const lockFile = `${filePath}.lock`
|
|
875
876
|
const tempFile = `${filePath}.tmp`
|
|
876
877
|
let ws = null
|
|
878
|
+
let lockAcquired = false
|
|
877
879
|
try {
|
|
878
880
|
await fs.promises.writeFile(lockFile, String(process.pid), { flag: 'wx' })
|
|
881
|
+
lockAcquired = true
|
|
879
882
|
ws = fs.createWriteStream(tempFile, { encoding: 'utf8', flags: 'w' })
|
|
883
|
+
let streamError = null
|
|
884
|
+
ws.on('error', (error) => {
|
|
885
|
+
streamError = error
|
|
886
|
+
})
|
|
880
887
|
const buffer = []
|
|
881
|
-
|
|
888
|
+
const write = (chunk) => {
|
|
889
|
+
if (ws.write(chunk)) return Promise.resolve()
|
|
890
|
+
return new Promise((resolve, reject) => {
|
|
891
|
+
const onDrain = () => {
|
|
892
|
+
cleanup()
|
|
893
|
+
resolve()
|
|
894
|
+
}
|
|
895
|
+
const onError = (error) => {
|
|
896
|
+
cleanup()
|
|
897
|
+
reject(error)
|
|
898
|
+
}
|
|
899
|
+
const cleanup = () => {
|
|
900
|
+
ws.off('drain', onDrain)
|
|
901
|
+
ws.off('error', onError)
|
|
902
|
+
}
|
|
903
|
+
ws.once('drain', onDrain)
|
|
904
|
+
ws.once('error', onError)
|
|
905
|
+
})
|
|
906
|
+
}
|
|
882
907
|
|
|
883
908
|
const nodeSessions = {}
|
|
884
909
|
for (const node of this.nodeMap.values()) {
|
|
@@ -910,29 +935,36 @@ class Aqua extends EventEmitter {
|
|
|
910
935
|
if (buffer.length >= WRITE_BUFFER_SIZE) {
|
|
911
936
|
const chunk = `${buffer.join('\n')}\n`
|
|
912
937
|
buffer.length = 0
|
|
913
|
-
|
|
914
|
-
drainPromise = drainPromise.then(
|
|
915
|
-
() => new Promise((r) => ws.once('drain', r))
|
|
916
|
-
)
|
|
917
|
-
}
|
|
938
|
+
await write(chunk)
|
|
918
939
|
}
|
|
919
940
|
}
|
|
920
941
|
|
|
921
|
-
if (buffer.length)
|
|
922
|
-
|
|
923
|
-
await new Promise((resolve, reject) =>
|
|
924
|
-
|
|
925
|
-
|
|
942
|
+
if (buffer.length) await write(`${buffer.join('\n')}\n`)
|
|
943
|
+
if (streamError) throw streamError
|
|
944
|
+
await new Promise((resolve, reject) => {
|
|
945
|
+
const onError = (error) => {
|
|
946
|
+
ws.off('finish', onFinish)
|
|
947
|
+
reject(error)
|
|
948
|
+
}
|
|
949
|
+
const onFinish = () => {
|
|
950
|
+
ws.off('error', onError)
|
|
951
|
+
resolve()
|
|
952
|
+
}
|
|
953
|
+
ws.once('error', onError)
|
|
954
|
+
ws.once('finish', onFinish)
|
|
955
|
+
ws.end()
|
|
956
|
+
})
|
|
926
957
|
ws = null
|
|
927
958
|
await fs.promises.rename(tempFile, filePath)
|
|
928
959
|
} catch (error) {
|
|
929
960
|
console.error(`[Aqua/Autoresume]Error saving players:`, error)
|
|
930
|
-
this
|
|
961
|
+
emitOperationalError(this, null, error)
|
|
931
962
|
if (ws) _functions.safeCall(() => ws.destroy())
|
|
932
963
|
await fs.promises.unlink(tempFile).catch(_functions.noop)
|
|
933
964
|
} finally {
|
|
934
965
|
if (ws) _functions.safeCall(() => ws.destroy())
|
|
935
|
-
|
|
966
|
+
if (lockAcquired)
|
|
967
|
+
await fs.promises.unlink(lockFile).catch(_functions.noop)
|
|
936
968
|
}
|
|
937
969
|
}
|
|
938
970
|
|
|
@@ -2,7 +2,7 @@ const fs = require('node:fs')
|
|
|
2
2
|
const path = require('node:path')
|
|
3
3
|
const readline = require('node:readline')
|
|
4
4
|
const { AqualinkEvents } = require('./AqualinkEvents')
|
|
5
|
-
const { reportSuppressedError } = require('./Reporting')
|
|
5
|
+
const { emitOperationalError, reportSuppressedError } = require('./Reporting')
|
|
6
6
|
|
|
7
7
|
class AquaRecovery {
|
|
8
8
|
constructor(aqua, deps) {
|
|
@@ -112,7 +112,10 @@ class AquaRecovery {
|
|
|
112
112
|
const batch = rebuilds.slice(i, i + this.MAX_CONCURRENT_OPS)
|
|
113
113
|
const results = await Promise.allSettled(
|
|
114
114
|
batch.map((state) =>
|
|
115
|
-
this.restorePlayer(state, node).then((ok) => ({
|
|
115
|
+
this.restorePlayer(state, node).then((ok) => ({
|
|
116
|
+
ok,
|
|
117
|
+
guildId: state.g
|
|
118
|
+
}))
|
|
116
119
|
)
|
|
117
120
|
)
|
|
118
121
|
for (let j = 0; j < results.length; j++) {
|
|
@@ -218,7 +221,7 @@ class AquaRecovery {
|
|
|
218
221
|
this.performCleanup()
|
|
219
222
|
}
|
|
220
223
|
} catch (error) {
|
|
221
|
-
this.aqua
|
|
224
|
+
emitOperationalError(this.aqua, null, error)
|
|
222
225
|
} finally {
|
|
223
226
|
state.failoverInProgress = false
|
|
224
227
|
}
|
|
@@ -264,12 +267,32 @@ class AquaRecovery {
|
|
|
264
267
|
async () => {
|
|
265
268
|
const state = this.capturePlayerState(player)
|
|
266
269
|
if (!state) throw new Error('Failed to capture state')
|
|
270
|
+
const oldMessage = player.nowPlayingMessage || null
|
|
271
|
+
const oldVoice = player.connection
|
|
272
|
+
? {
|
|
273
|
+
sid: player.connection.sessionId,
|
|
274
|
+
ep: player.connection.endpoint,
|
|
275
|
+
tok: player.connection.token,
|
|
276
|
+
reg: player.connection.region,
|
|
277
|
+
cid: player.connection.channelId
|
|
278
|
+
}
|
|
279
|
+
: null
|
|
280
|
+
player.destroy({
|
|
281
|
+
preserveClient: true,
|
|
282
|
+
skipRemote: true,
|
|
283
|
+
preserveMessage: true,
|
|
284
|
+
preserveTracks: true,
|
|
285
|
+
preserveReconnecting: true
|
|
286
|
+
})
|
|
267
287
|
const { maxRetries, retryDelay } = this.aqua.failoverOptions
|
|
268
288
|
for (let retry = 0; retry < maxRetries; retry++) {
|
|
289
|
+
let newPlayer = null
|
|
269
290
|
try {
|
|
270
291
|
const targetNode = pickNode()
|
|
271
|
-
|
|
292
|
+
newPlayer = this.createPlayerOnNode(targetNode, state)
|
|
293
|
+
this._applyVoiceBootstrap(newPlayer, oldVoice)
|
|
272
294
|
await this.restorePlayerState(newPlayer, state)
|
|
295
|
+
if (oldMessage) newPlayer.nowPlayingMessage = oldMessage
|
|
273
296
|
this.aqua.emit(
|
|
274
297
|
AqualinkEvents.PlayerMigrated,
|
|
275
298
|
player,
|
|
@@ -278,6 +301,13 @@ class AquaRecovery {
|
|
|
278
301
|
)
|
|
279
302
|
return newPlayer
|
|
280
303
|
} catch (error) {
|
|
304
|
+
newPlayer?.destroy?.({
|
|
305
|
+
preserveClient: true,
|
|
306
|
+
skipRemote: true,
|
|
307
|
+
preserveMessage: true,
|
|
308
|
+
preserveTracks: true,
|
|
309
|
+
preserveReconnecting: true
|
|
310
|
+
})
|
|
281
311
|
if (retry === maxRetries - 1) throw error
|
|
282
312
|
await this._functions.delay(retryDelay * 1.5 ** retry)
|
|
283
313
|
}
|
|
@@ -443,12 +473,13 @@ class AquaRecovery {
|
|
|
443
473
|
if (!player || !guildId || !(position > 0)) return
|
|
444
474
|
const seekOnce = (startedPlayer) => {
|
|
445
475
|
if (startedPlayer.guildId !== guildId) return
|
|
476
|
+
this.aqua.off(AqualinkEvents.TrackStart, seekOnce)
|
|
477
|
+
player.removeListener('destroy', cleanup)
|
|
446
478
|
this._functions.unrefTimeout(() => player.seek?.(position), delay)
|
|
447
479
|
}
|
|
448
|
-
this.aqua.
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
)
|
|
480
|
+
const cleanup = () => this.aqua.off(AqualinkEvents.TrackStart, seekOnce)
|
|
481
|
+
this.aqua.on(AqualinkEvents.TrackStart, seekOnce)
|
|
482
|
+
player.once('destroy', cleanup)
|
|
452
483
|
}
|
|
453
484
|
|
|
454
485
|
async restorePlayerState(newPlayer, state) {
|
|
@@ -484,10 +515,12 @@ class AquaRecovery {
|
|
|
484
515
|
this.aqua._loading = true
|
|
485
516
|
const lockFile = `${filePath}.lock`
|
|
486
517
|
let stream = null,
|
|
487
|
-
rl = null
|
|
518
|
+
rl = null,
|
|
519
|
+
lockAcquired = false
|
|
488
520
|
try {
|
|
489
521
|
await fs.promises.access(filePath)
|
|
490
522
|
await fs.promises.writeFile(lockFile, String(process.pid), { flag: 'wx' })
|
|
523
|
+
lockAcquired = true
|
|
491
524
|
await this.waitForFirstNode()
|
|
492
525
|
|
|
493
526
|
stream = fs.createReadStream(filePath, { encoding: 'utf8' })
|
|
@@ -533,13 +566,14 @@ class AquaRecovery {
|
|
|
533
566
|
} catch (error) {
|
|
534
567
|
if (error.code !== 'ENOENT') {
|
|
535
568
|
console.error(`[Aqua/Autoresume]Error loading players:`, error)
|
|
536
|
-
this.aqua
|
|
569
|
+
emitOperationalError(this.aqua, null, error)
|
|
537
570
|
}
|
|
538
571
|
} finally {
|
|
539
572
|
this.aqua._loading = false
|
|
540
573
|
if (rl) this._functions.safeCall(() => rl.close())
|
|
541
574
|
if (stream) this._functions.safeCall(() => stream.destroy())
|
|
542
|
-
|
|
575
|
+
if (lockAcquired)
|
|
576
|
+
await fs.promises.unlink(lockFile).catch(this._functions.noop)
|
|
543
577
|
}
|
|
544
578
|
}
|
|
545
579
|
|
|
@@ -551,8 +585,9 @@ class AquaRecovery {
|
|
|
551
585
|
if (existing?.playing && !existing.destroyed) return true
|
|
552
586
|
if (existing?.destroyed) this.aqua.players.delete(gId)
|
|
553
587
|
|
|
554
|
-
const targetNode =
|
|
555
|
-
|
|
588
|
+
const targetNode = preferredNode?.connected
|
|
589
|
+
? preferredNode
|
|
590
|
+
: this.aqua.leastUsedNodes[0]
|
|
556
591
|
if (!targetNode?.connected) {
|
|
557
592
|
throw new Error(`No connected node available to restore guild ${gId}`)
|
|
558
593
|
}
|
|
@@ -36,6 +36,8 @@ const AqualinkEvents = {
|
|
|
36
36
|
PlayerDestroyed: 'playerDestroy',
|
|
37
37
|
PlayerMigrated: 'playerMigrated',
|
|
38
38
|
PauseEvent: 'pauseEvent',
|
|
39
|
+
PlayerReconnectingEvent: 'PlayerReconnectingEvent',
|
|
40
|
+
PlayerReconnect: 'playerReconnect',
|
|
39
41
|
MixStarted: 'mixStarted',
|
|
40
42
|
MixEnded: 'mixEnded'
|
|
41
43
|
}
|
|
@@ -76,6 +76,7 @@ const _functions = {
|
|
|
76
76
|
v.endpoint = conn.endpoint
|
|
77
77
|
v.sessionId = conn.sessionId
|
|
78
78
|
v.channelId = player.voiceChannel
|
|
79
|
+
v.resuming = player?._resuming ?? false
|
|
79
80
|
payload.data.volume = player?.volume ?? 100
|
|
80
81
|
return payload
|
|
81
82
|
}
|
|
@@ -168,7 +169,8 @@ class Connection {
|
|
|
168
169
|
STATE,
|
|
169
170
|
RECONNECT_DELAY,
|
|
170
171
|
MAX_RECONNECT_ATTEMPTS,
|
|
171
|
-
RESUME_BACKOFF_MAX
|
|
172
|
+
RESUME_BACKOFF_MAX,
|
|
173
|
+
sharedPool
|
|
172
174
|
})
|
|
173
175
|
}
|
|
174
176
|
|
|
@@ -240,6 +242,12 @@ class Connection {
|
|
|
240
242
|
})
|
|
241
243
|
}
|
|
242
244
|
this.isWaitingForDisconnect = true
|
|
245
|
+
if (this.voiceChannel !== null) {
|
|
246
|
+
const oldChannel = this.voiceChannel
|
|
247
|
+
this.voiceChannel = null
|
|
248
|
+
if (p) p.voiceChannel = null
|
|
249
|
+
this._aqua.emit(AqualinkEvents.PlayerMove, p, oldChannel, null)
|
|
250
|
+
}
|
|
243
251
|
if (!this._nullChannelTimer) {
|
|
244
252
|
this._nullChannelTimer = setTimeout(() => {
|
|
245
253
|
this._nullChannelTimer = null
|
|
@@ -321,7 +329,9 @@ class Connection {
|
|
|
321
329
|
} catch (e) {
|
|
322
330
|
this._aqua?.emit?.(
|
|
323
331
|
AqualinkEvents.Debug,
|
|
324
|
-
new Error(
|
|
332
|
+
new Error(
|
|
333
|
+
`Player destroy failed (guild=${this._guildId}, sessionId=${this.sessionId || 'none'}): ${e?.message || e}`
|
|
334
|
+
)
|
|
325
335
|
)
|
|
326
336
|
} finally {
|
|
327
337
|
this._stateFlags &= ~STATE.DISCONNECTING
|
|
@@ -452,6 +462,15 @@ class Connection {
|
|
|
452
462
|
|
|
453
463
|
_executeVoiceUpdate() {
|
|
454
464
|
if (this._destroyed) return
|
|
465
|
+
if (this._stateFlags & STATE.DISCONNECTING) {
|
|
466
|
+
this._stateFlags &= ~STATE.UPDATE_SCHEDULED
|
|
467
|
+
this._voiceFlushTimer = null
|
|
468
|
+
if (this._pendingUpdate) {
|
|
469
|
+
sharedPool.release(this._pendingUpdate.payload)
|
|
470
|
+
this._pendingUpdate = null
|
|
471
|
+
}
|
|
472
|
+
return
|
|
473
|
+
}
|
|
455
474
|
this._stateFlags &= ~STATE.UPDATE_SCHEDULED
|
|
456
475
|
this._voiceFlushTimer = null
|
|
457
476
|
|
|
@@ -9,6 +9,7 @@ class ConnectionRecovery {
|
|
|
9
9
|
this.RECONNECT_DELAY = deps.RECONNECT_DELAY
|
|
10
10
|
this.MAX_RECONNECT_ATTEMPTS = deps.MAX_RECONNECT_ATTEMPTS
|
|
11
11
|
this.RESUME_BACKOFF_MAX = deps.RESUME_BACKOFF_MAX
|
|
12
|
+
this.sharedPool = deps.sharedPool
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
setServerUpdate(data) {
|
|
@@ -153,7 +154,7 @@ class ConnectionRecovery {
|
|
|
153
154
|
`Attempt resume: guild=${conn._guildId} endpoint=${conn.endpoint} session=${conn.sessionId}`
|
|
154
155
|
)
|
|
155
156
|
|
|
156
|
-
const payload = sharedPool.acquire()
|
|
157
|
+
const payload = this.sharedPool.acquire()
|
|
157
158
|
try {
|
|
158
159
|
this._functions.fillVoicePayload(
|
|
159
160
|
payload,
|
|
@@ -163,6 +164,14 @@ class ConnectionRecovery {
|
|
|
163
164
|
true
|
|
164
165
|
)
|
|
165
166
|
|
|
167
|
+
if (conn._destroyed || !conn._player || conn._player.destroyed) {
|
|
168
|
+
conn._aqua.emit(
|
|
169
|
+
AqualinkEvents.Debug,
|
|
170
|
+
`Resume aborted: player destroyed during attempt for guild ${conn._guildId}`
|
|
171
|
+
)
|
|
172
|
+
return false
|
|
173
|
+
}
|
|
174
|
+
|
|
166
175
|
if (conn._stateGeneration !== currentGen) {
|
|
167
176
|
conn._aqua.emit(
|
|
168
177
|
AqualinkEvents.Debug,
|
|
@@ -178,6 +187,10 @@ class ConnectionRecovery {
|
|
|
178
187
|
})
|
|
179
188
|
}
|
|
180
189
|
|
|
190
|
+
if (conn._destroyed || conn._player?.destroyed) {
|
|
191
|
+
return false
|
|
192
|
+
}
|
|
193
|
+
|
|
181
194
|
conn._reconnectAttempts = 0
|
|
182
195
|
conn._consecutiveFailures = 0
|
|
183
196
|
if (conn._player) conn._player._resuming = false
|
|
@@ -189,10 +202,17 @@ class ConnectionRecovery {
|
|
|
189
202
|
return true
|
|
190
203
|
} catch (error) {
|
|
191
204
|
if (conn._destroyed || !conn._aqua) throw error
|
|
205
|
+
if (conn._player?.destroyed) {
|
|
206
|
+
conn._aqua.emit(
|
|
207
|
+
AqualinkEvents.Debug,
|
|
208
|
+
`Resume aborted: player destroyed during retry for guild ${conn._guildId}`
|
|
209
|
+
)
|
|
210
|
+
return false
|
|
211
|
+
}
|
|
192
212
|
conn._consecutiveFailures++
|
|
193
213
|
conn._aqua.emit(
|
|
194
214
|
AqualinkEvents.Debug,
|
|
195
|
-
`Resume failed for guild ${conn._guildId}: ${error?.message || error}`
|
|
215
|
+
`Resume failed for guild ${conn._guildId} (sessionId=${conn.sessionId || 'none'}, endpoint=${conn.endpoint || 'none'}): ${error?.message || error}`
|
|
196
216
|
)
|
|
197
217
|
if (conn._aqua?.debugTrace) {
|
|
198
218
|
conn._aqua._trace('connection.resume.error', {
|
|
@@ -204,7 +224,8 @@ class ConnectionRecovery {
|
|
|
204
224
|
if (
|
|
205
225
|
conn._reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS &&
|
|
206
226
|
!conn._destroyed &&
|
|
207
|
-
conn._consecutiveFailures < 5
|
|
227
|
+
conn._consecutiveFailures < 5 &&
|
|
228
|
+
!conn._player?.destroyed
|
|
208
229
|
) {
|
|
209
230
|
const delay = Math.min(
|
|
210
231
|
this.RECONNECT_DELAY * (1 << (conn._reconnectAttempts - 1)),
|
|
@@ -222,7 +243,7 @@ class ConnectionRecovery {
|
|
|
222
243
|
return false
|
|
223
244
|
} finally {
|
|
224
245
|
conn._stateFlags &= ~this.STATE.ATTEMPTING_RESUME
|
|
225
|
-
sharedPool.release(payload)
|
|
246
|
+
this.sharedPool.release(payload)
|
|
226
247
|
}
|
|
227
248
|
}
|
|
228
249
|
|
|
@@ -265,7 +286,10 @@ class ConnectionRecovery {
|
|
|
265
286
|
return false
|
|
266
287
|
})
|
|
267
288
|
if (resumed) {
|
|
268
|
-
conn._player?._clearVoiceRecovery?.(
|
|
289
|
+
conn._player?._clearVoiceRecovery?.(
|
|
290
|
+
recoveryToken,
|
|
291
|
+
'missing_player_resumed'
|
|
292
|
+
)
|
|
269
293
|
} else if (conn._player?._isVoiceRecoveryActive?.(recoveryToken)) {
|
|
270
294
|
conn.resendVoiceUpdate(true)
|
|
271
295
|
}
|
|
@@ -306,8 +330,12 @@ class ConnectionRecovery {
|
|
|
306
330
|
|
|
307
331
|
async sendUpdate(payload) {
|
|
308
332
|
const conn = this.connection
|
|
309
|
-
if (conn._destroyed)
|
|
310
|
-
|
|
333
|
+
if (conn._destroyed)
|
|
334
|
+
throw new Error(`Connection destroyed (guild=${conn._guildId})`)
|
|
335
|
+
if (!conn._rest)
|
|
336
|
+
throw new Error(
|
|
337
|
+
`REST interface unavailable (guild=${conn._guildId}, sessionId=${conn.sessionId || 'none'})`
|
|
338
|
+
)
|
|
311
339
|
|
|
312
340
|
try {
|
|
313
341
|
if (conn._aqua?.debugTrace) {
|
|
@@ -342,7 +370,7 @@ class ConnectionRecovery {
|
|
|
342
370
|
if (conn._aqua) {
|
|
343
371
|
conn._aqua.emit(
|
|
344
372
|
AqualinkEvents.Debug,
|
|
345
|
-
`[Aqua/Connection] Player ${conn._guildId} not found (404)${isSessionError ? ' - Session invalid' : ''}. Recovery failed, destroying.`
|
|
373
|
+
`[Aqua/Connection] Player ${conn._guildId} not found (404, sessionId=${conn.sessionId || 'none'}, endpoint=${conn.endpoint || 'none'})${isSessionError ? ' - Session invalid' : ''}. Recovery failed, destroying.`
|
|
346
374
|
)
|
|
347
375
|
await conn._aqua.destroyPlayer(conn._guildId)
|
|
348
376
|
}
|
|
@@ -359,40 +387,4 @@ class ConnectionRecovery {
|
|
|
359
387
|
}
|
|
360
388
|
}
|
|
361
389
|
|
|
362
|
-
class PayloadPool {
|
|
363
|
-
constructor() {
|
|
364
|
-
this._pool = []
|
|
365
|
-
this._size = 0
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
_create() {
|
|
369
|
-
return {
|
|
370
|
-
guildId: null,
|
|
371
|
-
data: {
|
|
372
|
-
voice: {
|
|
373
|
-
token: null,
|
|
374
|
-
endpoint: null,
|
|
375
|
-
sessionId: null
|
|
376
|
-
},
|
|
377
|
-
volume: null
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
acquire() {
|
|
383
|
-
return this._size > 0 ? this._pool[--this._size] : this._create()
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
release(payload) {
|
|
387
|
-
if (!payload || this._size >= 12) return
|
|
388
|
-
payload.guildId = null
|
|
389
|
-
const v = payload.data.voice
|
|
390
|
-
v.token = v.endpoint = v.sessionId = null
|
|
391
|
-
payload.data.volume = null
|
|
392
|
-
this._pool[this._size++] = payload
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const sharedPool = new PayloadPool()
|
|
397
|
-
|
|
398
390
|
module.exports = ConnectionRecovery
|
|
@@ -202,7 +202,8 @@ class Filters {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
_scheduleUpdate() {
|
|
205
|
-
if (this._pendingUpdate || !this.player
|
|
205
|
+
if (this._pendingUpdate || !this.player || this.player.destroyed)
|
|
206
|
+
return this
|
|
206
207
|
this._pendingUpdate = true
|
|
207
208
|
queueMicrotask(() => {
|
|
208
209
|
this._pendingUpdate = false
|
|
@@ -398,6 +399,7 @@ class Filters {
|
|
|
398
399
|
}
|
|
399
400
|
|
|
400
401
|
async updateFilters() {
|
|
402
|
+
this._pendingUpdate = false
|
|
401
403
|
if (!this.player || !this._dirty.size) return this
|
|
402
404
|
|
|
403
405
|
const dirtyKeys = [...this._dirty]
|