aqualink 3.1.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 +170 -133
- package/build/index.js +3 -3
- package/build/structures/Aqua.js +65 -41
- package/build/structures/AquaRecovery.js +41 -10
- package/build/structures/AqualinkEvents.js +2 -0
- package/build/structures/Connection.js +9 -1
- package/build/structures/ConnectionRecovery.js +3 -38
- package/build/structures/Node.js +158 -80
- package/build/structures/Player.js +54 -23
- package/build/structures/PlayerLifecycle.js +10 -1
- package/build/structures/Reporting.js +12 -0
- package/build/structures/Rest.js +48 -42
- 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
const fs = require('node:fs')
|
|
2
2
|
const path = require('node:path')
|
|
3
3
|
const _readline = require('node:readline')
|
|
4
4
|
const { EventEmitter } = require('node:events')
|
|
@@ -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) {
|
|
@@ -492,12 +494,7 @@ class Aqua extends EventEmitter {
|
|
|
492
494
|
(stats.memory ? stats.memory.used / reservable : 0) * 40 +
|
|
493
495
|
(node.rest?.calls || 0) * 0.001
|
|
494
496
|
if (this._nodeLoadCache.size >= MAX_CACHE_SIZE) {
|
|
495
|
-
|
|
496
|
-
while (this._nodeLoadCache.size >= MAX_CACHE_SIZE) {
|
|
497
|
-
const oldest = iterator.next().value
|
|
498
|
-
if (!oldest) break
|
|
499
|
-
this._nodeLoadCache.delete(oldest)
|
|
500
|
-
}
|
|
497
|
+
this._nodeLoadCache.delete(this._nodeLoadCache.keys().next().value)
|
|
501
498
|
}
|
|
502
499
|
this._nodeLoadCache.set(id, { load, time: now })
|
|
503
500
|
return load
|
|
@@ -549,17 +546,12 @@ class Aqua extends EventEmitter {
|
|
|
549
546
|
attempts: 0,
|
|
550
547
|
lastAttempt: 0
|
|
551
548
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
return node
|
|
559
|
-
} catch (error) {
|
|
560
|
-
this._cleanupNode(id)
|
|
561
|
-
throw error
|
|
562
|
-
}
|
|
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
|
|
563
555
|
}
|
|
564
556
|
|
|
565
557
|
_destroyNode(id) {
|
|
@@ -762,9 +754,8 @@ class Aqua extends EventEmitter {
|
|
|
762
754
|
query,
|
|
763
755
|
source || this.defaultSearchPlatform
|
|
764
756
|
)
|
|
765
|
-
const endpoint = `/${this.restVersion}/loadtracks?identifier=${encodeURIComponent(formatted)}`
|
|
766
757
|
try {
|
|
767
|
-
const response = await node.rest.
|
|
758
|
+
const response = await node.rest.loadTracks(formatted)
|
|
768
759
|
if (
|
|
769
760
|
!response ||
|
|
770
761
|
response.loadType === 'empty' ||
|
|
@@ -773,11 +764,13 @@ class Aqua extends EventEmitter {
|
|
|
773
764
|
return EMPTY_TRACKS_RESPONSE
|
|
774
765
|
return this._constructResponse(response, requester, node)
|
|
775
766
|
} catch (error) {
|
|
776
|
-
throw new Error(
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
)
|
|
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
|
|
781
774
|
}
|
|
782
775
|
}
|
|
783
776
|
|
|
@@ -882,11 +875,35 @@ class Aqua extends EventEmitter {
|
|
|
882
875
|
const lockFile = `${filePath}.lock`
|
|
883
876
|
const tempFile = `${filePath}.tmp`
|
|
884
877
|
let ws = null
|
|
878
|
+
let lockAcquired = false
|
|
885
879
|
try {
|
|
886
880
|
await fs.promises.writeFile(lockFile, String(process.pid), { flag: 'wx' })
|
|
881
|
+
lockAcquired = true
|
|
887
882
|
ws = fs.createWriteStream(tempFile, { encoding: 'utf8', flags: 'w' })
|
|
883
|
+
let streamError = null
|
|
884
|
+
ws.on('error', (error) => {
|
|
885
|
+
streamError = error
|
|
886
|
+
})
|
|
888
887
|
const buffer = []
|
|
889
|
-
|
|
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
|
+
}
|
|
890
907
|
|
|
891
908
|
const nodeSessions = {}
|
|
892
909
|
for (const node of this.nodeMap.values()) {
|
|
@@ -918,29 +935,36 @@ class Aqua extends EventEmitter {
|
|
|
918
935
|
if (buffer.length >= WRITE_BUFFER_SIZE) {
|
|
919
936
|
const chunk = `${buffer.join('\n')}\n`
|
|
920
937
|
buffer.length = 0
|
|
921
|
-
|
|
922
|
-
drainPromise = drainPromise.then(
|
|
923
|
-
() => new Promise((r) => ws.once('drain', r))
|
|
924
|
-
)
|
|
925
|
-
}
|
|
938
|
+
await write(chunk)
|
|
926
939
|
}
|
|
927
940
|
}
|
|
928
941
|
|
|
929
|
-
if (buffer.length)
|
|
930
|
-
|
|
931
|
-
await new Promise((resolve, reject) =>
|
|
932
|
-
|
|
933
|
-
|
|
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
|
+
})
|
|
934
957
|
ws = null
|
|
935
958
|
await fs.promises.rename(tempFile, filePath)
|
|
936
959
|
} catch (error) {
|
|
937
960
|
console.error(`[Aqua/Autoresume]Error saving players:`, error)
|
|
938
|
-
this
|
|
961
|
+
emitOperationalError(this, null, error)
|
|
939
962
|
if (ws) _functions.safeCall(() => ws.destroy())
|
|
940
963
|
await fs.promises.unlink(tempFile).catch(_functions.noop)
|
|
941
964
|
} finally {
|
|
942
965
|
if (ws) _functions.safeCall(() => ws.destroy())
|
|
943
|
-
|
|
966
|
+
if (lockAcquired)
|
|
967
|
+
await fs.promises.unlink(lockFile).catch(_functions.noop)
|
|
944
968
|
}
|
|
945
969
|
}
|
|
946
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) {
|
|
@@ -221,7 +221,7 @@ class AquaRecovery {
|
|
|
221
221
|
this.performCleanup()
|
|
222
222
|
}
|
|
223
223
|
} catch (error) {
|
|
224
|
-
this.aqua
|
|
224
|
+
emitOperationalError(this.aqua, null, error)
|
|
225
225
|
} finally {
|
|
226
226
|
state.failoverInProgress = false
|
|
227
227
|
}
|
|
@@ -267,12 +267,32 @@ class AquaRecovery {
|
|
|
267
267
|
async () => {
|
|
268
268
|
const state = this.capturePlayerState(player)
|
|
269
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
|
+
})
|
|
270
287
|
const { maxRetries, retryDelay } = this.aqua.failoverOptions
|
|
271
288
|
for (let retry = 0; retry < maxRetries; retry++) {
|
|
289
|
+
let newPlayer = null
|
|
272
290
|
try {
|
|
273
291
|
const targetNode = pickNode()
|
|
274
|
-
|
|
292
|
+
newPlayer = this.createPlayerOnNode(targetNode, state)
|
|
293
|
+
this._applyVoiceBootstrap(newPlayer, oldVoice)
|
|
275
294
|
await this.restorePlayerState(newPlayer, state)
|
|
295
|
+
if (oldMessage) newPlayer.nowPlayingMessage = oldMessage
|
|
276
296
|
this.aqua.emit(
|
|
277
297
|
AqualinkEvents.PlayerMigrated,
|
|
278
298
|
player,
|
|
@@ -281,6 +301,13 @@ class AquaRecovery {
|
|
|
281
301
|
)
|
|
282
302
|
return newPlayer
|
|
283
303
|
} catch (error) {
|
|
304
|
+
newPlayer?.destroy?.({
|
|
305
|
+
preserveClient: true,
|
|
306
|
+
skipRemote: true,
|
|
307
|
+
preserveMessage: true,
|
|
308
|
+
preserveTracks: true,
|
|
309
|
+
preserveReconnecting: true
|
|
310
|
+
})
|
|
284
311
|
if (retry === maxRetries - 1) throw error
|
|
285
312
|
await this._functions.delay(retryDelay * 1.5 ** retry)
|
|
286
313
|
}
|
|
@@ -446,12 +473,13 @@ class AquaRecovery {
|
|
|
446
473
|
if (!player || !guildId || !(position > 0)) return
|
|
447
474
|
const seekOnce = (startedPlayer) => {
|
|
448
475
|
if (startedPlayer.guildId !== guildId) return
|
|
476
|
+
this.aqua.off(AqualinkEvents.TrackStart, seekOnce)
|
|
477
|
+
player.removeListener('destroy', cleanup)
|
|
449
478
|
this._functions.unrefTimeout(() => player.seek?.(position), delay)
|
|
450
479
|
}
|
|
451
|
-
this.aqua.
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
)
|
|
480
|
+
const cleanup = () => this.aqua.off(AqualinkEvents.TrackStart, seekOnce)
|
|
481
|
+
this.aqua.on(AqualinkEvents.TrackStart, seekOnce)
|
|
482
|
+
player.once('destroy', cleanup)
|
|
455
483
|
}
|
|
456
484
|
|
|
457
485
|
async restorePlayerState(newPlayer, state) {
|
|
@@ -487,10 +515,12 @@ class AquaRecovery {
|
|
|
487
515
|
this.aqua._loading = true
|
|
488
516
|
const lockFile = `${filePath}.lock`
|
|
489
517
|
let stream = null,
|
|
490
|
-
rl = null
|
|
518
|
+
rl = null,
|
|
519
|
+
lockAcquired = false
|
|
491
520
|
try {
|
|
492
521
|
await fs.promises.access(filePath)
|
|
493
522
|
await fs.promises.writeFile(lockFile, String(process.pid), { flag: 'wx' })
|
|
523
|
+
lockAcquired = true
|
|
494
524
|
await this.waitForFirstNode()
|
|
495
525
|
|
|
496
526
|
stream = fs.createReadStream(filePath, { encoding: 'utf8' })
|
|
@@ -536,13 +566,14 @@ class AquaRecovery {
|
|
|
536
566
|
} catch (error) {
|
|
537
567
|
if (error.code !== 'ENOENT') {
|
|
538
568
|
console.error(`[Aqua/Autoresume]Error loading players:`, error)
|
|
539
|
-
this.aqua
|
|
569
|
+
emitOperationalError(this.aqua, null, error)
|
|
540
570
|
}
|
|
541
571
|
} finally {
|
|
542
572
|
this.aqua._loading = false
|
|
543
573
|
if (rl) this._functions.safeCall(() => rl.close())
|
|
544
574
|
if (stream) this._functions.safeCall(() => stream.destroy())
|
|
545
|
-
|
|
575
|
+
if (lockAcquired)
|
|
576
|
+
await fs.promises.unlink(lockFile).catch(this._functions.noop)
|
|
546
577
|
}
|
|
547
578
|
}
|
|
548
579
|
|
|
@@ -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
|
|
@@ -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,
|
|
@@ -242,7 +243,7 @@ class ConnectionRecovery {
|
|
|
242
243
|
return false
|
|
243
244
|
} finally {
|
|
244
245
|
conn._stateFlags &= ~this.STATE.ATTEMPTING_RESUME
|
|
245
|
-
sharedPool.release(payload)
|
|
246
|
+
this.sharedPool.release(payload)
|
|
246
247
|
}
|
|
247
248
|
}
|
|
248
249
|
|
|
@@ -386,40 +387,4 @@ class ConnectionRecovery {
|
|
|
386
387
|
}
|
|
387
388
|
}
|
|
388
389
|
|
|
389
|
-
class PayloadPool {
|
|
390
|
-
constructor() {
|
|
391
|
-
this._pool = []
|
|
392
|
-
this._size = 0
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
_create() {
|
|
396
|
-
return {
|
|
397
|
-
guildId: null,
|
|
398
|
-
data: {
|
|
399
|
-
voice: {
|
|
400
|
-
token: null,
|
|
401
|
-
endpoint: null,
|
|
402
|
-
sessionId: null
|
|
403
|
-
},
|
|
404
|
-
volume: null
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
acquire() {
|
|
410
|
-
return this._size > 0 ? this._pool[--this._size] : this._create()
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
release(payload) {
|
|
414
|
-
if (!payload || this._size >= 12) return
|
|
415
|
-
payload.guildId = null
|
|
416
|
-
const v = payload.data.voice
|
|
417
|
-
v.token = v.endpoint = v.sessionId = null
|
|
418
|
-
payload.data.volume = null
|
|
419
|
-
this._pool[this._size++] = payload
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const sharedPool = new PayloadPool()
|
|
424
|
-
|
|
425
390
|
module.exports = ConnectionRecovery
|