aqualink 2.17.1 → 2.17.3
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 +7 -5
- package/build/index.d.ts +24 -4
- package/build/structures/Aqua.js +58 -45
- package/build/structures/AqualinkEvents.js +4 -2
- package/build/structures/Connection.js +127 -55
- package/build/structures/Node.js +1 -1
- package/build/structures/Player.js +80 -11
- package/build/structures/Rest.js +42 -4
- package/build/structures/Track.js +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -180,16 +180,18 @@ const aqua = new Aqua(client, nodes, {
|
|
|
180
180
|
|
|
181
181
|
client.aqua = aqua;
|
|
182
182
|
|
|
183
|
-
client.once(Events.
|
|
183
|
+
client.once(Events.ClientReady, () => {
|
|
184
184
|
client.aqua.init(client.user.id);
|
|
185
185
|
console.log(`Logged in as ${client.user.tag}`);
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
-
client.on(Events.Raw, (d) => {
|
|
189
|
-
if (
|
|
190
|
-
|
|
188
|
+
client.on(Events.Raw, (d, t) => {
|
|
189
|
+
if (d.t === "VOICE_SERVER_UPDATE" || d.t === "VOICE_STATE_UPDATE") {
|
|
190
|
+
return client.aqua.updateVoiceState(d, t);
|
|
191
|
+
}
|
|
191
192
|
});
|
|
192
193
|
|
|
194
|
+
|
|
193
195
|
client.on(Events.MessageCreate, async (message) => {
|
|
194
196
|
if (message.author.bot || !message.content.startsWith("!play")) return;
|
|
195
197
|
|
|
@@ -446,4 +448,4 @@ Join our thriving community of developers and bot creators!
|
|
|
446
448
|
|
|
447
449
|
<sub>Built with 💙 by the Aqualink Team</sub>
|
|
448
450
|
|
|
449
|
-
</div>
|
|
451
|
+
</div>
|
package/build/index.d.ts
CHANGED
|
@@ -38,8 +38,7 @@ declare module "aqualink" {
|
|
|
38
38
|
_rebuildLocks: Set<string>;
|
|
39
39
|
_leastUsedNodesCache: Node[] | null;
|
|
40
40
|
_leastUsedNodesCacheTime: number;
|
|
41
|
-
_nodeLoadCache: Map<string, number>;
|
|
42
|
-
_nodeLoadCacheTime: Map<string, number>;
|
|
41
|
+
_nodeLoadCache: Map<string, {load: number; time: number}>;
|
|
43
42
|
_cleanupTimer: NodeJS.Timer | null;
|
|
44
43
|
_onNodeConnect?: (node: Node) => void;
|
|
45
44
|
_onNodeDisconnect?: (node: Node) => void;
|
|
@@ -126,6 +125,7 @@ declare module "aqualink" {
|
|
|
126
125
|
infiniteReconnects: boolean;
|
|
127
126
|
connected: boolean;
|
|
128
127
|
info: NodeInfo | null;
|
|
128
|
+
isNodelink: boolean;
|
|
129
129
|
ws: any | null; // WebSocket
|
|
130
130
|
reconnectAttempted: number;
|
|
131
131
|
reconnectTimeoutId: NodeJS.Timeout | null;
|
|
@@ -198,6 +198,8 @@ declare module "aqualink" {
|
|
|
198
198
|
mute: boolean;
|
|
199
199
|
autoplayRetries: number;
|
|
200
200
|
reconnectionRetries: number;
|
|
201
|
+
_resuming: boolean;
|
|
202
|
+
_reconnecting: boolean;
|
|
201
203
|
previousIdentifiers: Set<string>;
|
|
202
204
|
self_deaf: boolean;
|
|
203
205
|
self_mute: boolean;
|
|
@@ -242,6 +244,11 @@ declare module "aqualink" {
|
|
|
242
244
|
setAutoplay(enabled: boolean): Player;
|
|
243
245
|
updatePlayer(data: any): Promise<any>;
|
|
244
246
|
cleanup(): Promise<void>;
|
|
247
|
+
getActiveMixer(guildId: string): Promise<any[]>;
|
|
248
|
+
updateMixerVolume(guildId: string, mix: string, volume: number): Promise<any>;
|
|
249
|
+
removeMixer(guildId: string, mix: string): Promise<any>;
|
|
250
|
+
addMixer(guildId: string, options: MixerOptions): Promise<any>;
|
|
251
|
+
getLoadLyrics(encodedTrack: string): Promise<LyricsResponse | null>;
|
|
245
252
|
|
|
246
253
|
// Data Methods
|
|
247
254
|
set(key: string, value: any): void;
|
|
@@ -276,7 +283,7 @@ declare module "aqualink" {
|
|
|
276
283
|
}
|
|
277
284
|
|
|
278
285
|
export class Track {
|
|
279
|
-
constructor(data?: TrackData, requester?: any,
|
|
286
|
+
constructor(data?: TrackData, requester?: any, node?: Node);
|
|
280
287
|
|
|
281
288
|
// Properties
|
|
282
289
|
identifier: string;
|
|
@@ -326,6 +333,7 @@ declare module "aqualink" {
|
|
|
326
333
|
baseUrl: string;
|
|
327
334
|
defaultHeaders: Record<string, string>;
|
|
328
335
|
agent: any; // HTTP/HTTPS Agent
|
|
336
|
+
useHttp2: boolean;
|
|
329
337
|
|
|
330
338
|
// Core Methods
|
|
331
339
|
setSessionId(sessionId: string): void;
|
|
@@ -347,6 +355,11 @@ declare module "aqualink" {
|
|
|
347
355
|
getRoutePlannerStatus(): Promise<any>;
|
|
348
356
|
freeRoutePlannerAddress(address: string): Promise<any>;
|
|
349
357
|
freeAllRoutePlannerAddresses(): Promise<any>;
|
|
358
|
+
addMixer(guildId: string, options: MixerOptions): Promise<any>;
|
|
359
|
+
getActiveMixer(guildId: string): Promise<any[]>;
|
|
360
|
+
updateMixerVolume(guildId: string, mix: string, volume: number): Promise<any>;
|
|
361
|
+
removeMixer(guildId: string, mix: string): Promise<any>;
|
|
362
|
+
getLoadLyrics(encodedTrack: string): Promise<LyricsResponse>;
|
|
350
363
|
destroy(): void;
|
|
351
364
|
}
|
|
352
365
|
|
|
@@ -452,7 +465,7 @@ declare module "aqualink" {
|
|
|
452
465
|
updateSequence(seq: number): void;
|
|
453
466
|
destroy(): void;
|
|
454
467
|
attemptResume(): Promise<boolean>;
|
|
455
|
-
resendVoiceUpdate(
|
|
468
|
+
resendVoiceUpdate(): boolean;
|
|
456
469
|
|
|
457
470
|
// Internal Methods
|
|
458
471
|
_extractRegion(endpoint: string): string | null;
|
|
@@ -736,6 +749,13 @@ declare module "aqualink" {
|
|
|
736
749
|
smoothing?: number;
|
|
737
750
|
}
|
|
738
751
|
|
|
752
|
+
export interface MixerOptions {
|
|
753
|
+
identifier?: string;
|
|
754
|
+
encoded?: string;
|
|
755
|
+
userData?: any;
|
|
756
|
+
volume?: number;
|
|
757
|
+
}
|
|
758
|
+
|
|
739
759
|
// Voice Update Interfaces
|
|
740
760
|
export interface VoiceStateUpdate {
|
|
741
761
|
d: {
|
package/build/structures/Aqua.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs')
|
|
4
4
|
const readline = require('node:readline')
|
|
5
|
-
const {EventEmitter} = require('tseep')
|
|
6
|
-
const {AqualinkEvents} = require('./AqualinkEvents')
|
|
5
|
+
const { EventEmitter } = require('tseep')
|
|
6
|
+
const { AqualinkEvents } = require('./AqualinkEvents')
|
|
7
7
|
const Node = require('./Node')
|
|
8
8
|
const Player = require('./Player')
|
|
9
9
|
const Track = require('./Track')
|
|
10
|
-
const {version: pkgVersion} = require('../../package.json')
|
|
10
|
+
const { version: pkgVersion } = require('../../package.json')
|
|
11
11
|
|
|
12
12
|
const SEARCH_PREFIX = ':'
|
|
13
13
|
const EMPTY_ARRAY = Object.freeze([])
|
|
@@ -33,7 +33,6 @@ const MAX_REBUILD_LOCKS = 100
|
|
|
33
33
|
const WRITE_BUFFER_SIZE = 100
|
|
34
34
|
const MAX_QUEUE_SAVE = 10
|
|
35
35
|
const MAX_TRACKS_RESTORE = 20
|
|
36
|
-
const URL_PATTERN = /^https?:\/\//i
|
|
37
36
|
|
|
38
37
|
const DEFAULT_OPTIONS = Object.freeze({
|
|
39
38
|
shouldDeleteMessage: false,
|
|
@@ -56,11 +55,17 @@ const DEFAULT_OPTIONS = Object.freeze({
|
|
|
56
55
|
})
|
|
57
56
|
})
|
|
58
57
|
|
|
59
|
-
// Shared helper functions
|
|
60
58
|
const _functions = {
|
|
61
|
-
delay: ms => new Promise(r =>
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
delay: ms => new Promise(r => {
|
|
60
|
+
const t = setTimeout(r, ms)
|
|
61
|
+
t.unref?.()
|
|
62
|
+
}),
|
|
63
|
+
noop: () => { },
|
|
64
|
+
isUrl: query => {
|
|
65
|
+
if (typeof query !== 'string' || query.length <= 8) return false
|
|
66
|
+
const q = query.trimStart()
|
|
67
|
+
return q.startsWith('http://') || q.startsWith('https://')
|
|
68
|
+
},
|
|
64
69
|
formatQuery(query, source) {
|
|
65
70
|
return this.isUrl(query) ? query : `${source}${SEARCH_PREFIX}${query}`
|
|
66
71
|
},
|
|
@@ -69,12 +74,17 @@ const _functions = {
|
|
|
69
74
|
try {
|
|
70
75
|
const result = fn()
|
|
71
76
|
return result?.then ? result.catch(this.noop) : result
|
|
72
|
-
} catch {}
|
|
77
|
+
} catch { }
|
|
73
78
|
},
|
|
74
79
|
parseRequester(str) {
|
|
75
80
|
if (!str || typeof str !== 'string') return null
|
|
76
81
|
const i = str.indexOf(':')
|
|
77
|
-
return i > 0 ? {id: str.substring(0, i), username: str.substring(i + 1)} : null
|
|
82
|
+
return i > 0 ? { id: str.substring(0, i), username: str.substring(i + 1) } : null
|
|
83
|
+
},
|
|
84
|
+
unrefTimeout: (fn, ms) => {
|
|
85
|
+
const t = setTimeout(fn, ms)
|
|
86
|
+
t.unref?.()
|
|
87
|
+
return t
|
|
78
88
|
}
|
|
79
89
|
}
|
|
80
90
|
|
|
@@ -92,9 +102,9 @@ class Aqua extends EventEmitter {
|
|
|
92
102
|
this.initiated = false
|
|
93
103
|
this.version = pkgVersion
|
|
94
104
|
|
|
95
|
-
const merged = {...DEFAULT_OPTIONS, ...options}
|
|
105
|
+
const merged = { ...DEFAULT_OPTIONS, ...options }
|
|
96
106
|
this.options = merged
|
|
97
|
-
this.failoverOptions = {...DEFAULT_OPTIONS.failoverOptions, ...options.failoverOptions}
|
|
107
|
+
this.failoverOptions = { ...DEFAULT_OPTIONS.failoverOptions, ...options.failoverOptions }
|
|
98
108
|
|
|
99
109
|
this.shouldDeleteMessage = merged.shouldDeleteMessage
|
|
100
110
|
this.defaultSearchPlatform = merged.defaultSearchPlatform
|
|
@@ -149,13 +159,13 @@ class Aqua extends EventEmitter {
|
|
|
149
159
|
this._performCleanup()
|
|
150
160
|
})
|
|
151
161
|
},
|
|
152
|
-
onNodeReady: (node, {resumed}) => {
|
|
162
|
+
onNodeReady: (node, { resumed }) => {
|
|
153
163
|
if (!resumed) return
|
|
154
164
|
const batch = []
|
|
155
165
|
for (const player of this.players.values()) {
|
|
156
166
|
if (player.nodes === node && player.connection) batch.push(player)
|
|
157
167
|
}
|
|
158
|
-
if (batch.length) queueMicrotask(() => batch.forEach(p => p.connection.resendVoiceUpdate(
|
|
168
|
+
if (batch.length) queueMicrotask(() => batch.forEach(p => p.connection.resendVoiceUpdate()))
|
|
159
169
|
}
|
|
160
170
|
}
|
|
161
171
|
this.on(AqualinkEvents.NodeConnect, this._eventHandlers.onNodeConnect)
|
|
@@ -171,8 +181,10 @@ class Aqua extends EventEmitter {
|
|
|
171
181
|
this._eventHandlers = null
|
|
172
182
|
}
|
|
173
183
|
this.removeAllListeners()
|
|
174
|
-
|
|
175
|
-
|
|
184
|
+
|
|
185
|
+
for (const id of Array.from(this.nodeMap.keys())) this._destroyNode(id)
|
|
186
|
+
for (const player of Array.from(this.players.values())) _functions.safeCall(() => player.destroy())
|
|
187
|
+
|
|
176
188
|
this.players.clear()
|
|
177
189
|
this._nodeStates.clear()
|
|
178
190
|
this._failoverQueue.clear()
|
|
@@ -220,7 +232,7 @@ class Aqua extends EventEmitter {
|
|
|
220
232
|
(stats.playingPlayers || 0) * 0.75 +
|
|
221
233
|
(stats.memory ? stats.memory.used / reservable : 0) * 40 +
|
|
222
234
|
(node.rest?.calls || 0) * 0.001
|
|
223
|
-
this._nodeLoadCache.set(id, {load, time: now})
|
|
235
|
+
this._nodeLoadCache.set(id, { load, time: now })
|
|
224
236
|
if (this._nodeLoadCache.size > MAX_CACHE_SIZE) {
|
|
225
237
|
const first = this._nodeLoadCache.keys().next().value
|
|
226
238
|
this._nodeLoadCache.delete(first)
|
|
@@ -233,7 +245,7 @@ class Aqua extends EventEmitter {
|
|
|
233
245
|
this.clientId = clientId
|
|
234
246
|
if (!this.clientId) return this
|
|
235
247
|
const results = await Promise.allSettled(
|
|
236
|
-
this.nodes.map(n => Promise.race([this._createNode(n), _functions.delay(NODE_TIMEOUT).then(() => {throw new Error('Timeout')})]))
|
|
248
|
+
this.nodes.map(n => Promise.race([this._createNode(n), _functions.delay(NODE_TIMEOUT).then(() => { throw new Error('Timeout') })]))
|
|
237
249
|
)
|
|
238
250
|
if (!results.some(r => r.status === 'fulfilled')) throw new Error('No nodes connected')
|
|
239
251
|
if (this.plugins?.length) {
|
|
@@ -249,10 +261,10 @@ class Aqua extends EventEmitter {
|
|
|
249
261
|
const node = new Node(this, options, this.options)
|
|
250
262
|
node.players = new Set()
|
|
251
263
|
this.nodeMap.set(id, node)
|
|
252
|
-
this._nodeStates.set(id, {connected: false, failoverInProgress: false})
|
|
264
|
+
this._nodeStates.set(id, { connected: false, failoverInProgress: false })
|
|
253
265
|
try {
|
|
254
266
|
await node.connect()
|
|
255
|
-
this._nodeStates.set(id, {connected: true, failoverInProgress: false})
|
|
267
|
+
this._nodeStates.set(id, { connected: true, failoverInProgress: false })
|
|
256
268
|
this._invalidateCache()
|
|
257
269
|
this.emit(AqualinkEvents.NodeCreate, node)
|
|
258
270
|
return node
|
|
@@ -304,7 +316,7 @@ class Aqua extends EventEmitter {
|
|
|
304
316
|
const now = Date.now()
|
|
305
317
|
for (const [guildId, state] of this._brokenPlayers) {
|
|
306
318
|
if (state.originalNodeId === id && (now - state.brokenAt) < BROKEN_PLAYER_TTL) {
|
|
307
|
-
rebuilds.push({guildId, state})
|
|
319
|
+
rebuilds.push({ guildId, state })
|
|
308
320
|
}
|
|
309
321
|
}
|
|
310
322
|
if (!rebuilds.length) return
|
|
@@ -312,7 +324,7 @@ class Aqua extends EventEmitter {
|
|
|
312
324
|
for (let i = 0; i < rebuilds.length; i += MAX_CONCURRENT_OPS) {
|
|
313
325
|
const batch = rebuilds.slice(i, i + MAX_CONCURRENT_OPS)
|
|
314
326
|
const results = await Promise.allSettled(
|
|
315
|
-
batch.map(({guildId, state}) => this._rebuildPlayer(state, node).then(() => guildId))
|
|
327
|
+
batch.map(({ guildId, state }) => this._rebuildPlayer(state, node).then(() => guildId))
|
|
316
328
|
)
|
|
317
329
|
for (const r of results) {
|
|
318
330
|
if (r.status === 'fulfilled') successes.push(r.value)
|
|
@@ -324,7 +336,7 @@ class Aqua extends EventEmitter {
|
|
|
324
336
|
}
|
|
325
337
|
|
|
326
338
|
async _rebuildPlayer(state, targetNode) {
|
|
327
|
-
const {guildId, textChannel, voiceChannel, current, volume = 65, deaf = true} = state
|
|
339
|
+
const { guildId, textChannel, voiceChannel, current, volume = 65, deaf = true } = state
|
|
328
340
|
const lockKey = `rebuild_${guildId}`
|
|
329
341
|
if (this._rebuildLocks.has(lockKey)) return
|
|
330
342
|
this._rebuildLocks.add(lockKey)
|
|
@@ -333,11 +345,11 @@ class Aqua extends EventEmitter {
|
|
|
333
345
|
await this.destroyPlayer(guildId)
|
|
334
346
|
await _functions.delay(RECONNECT_DELAY)
|
|
335
347
|
}
|
|
336
|
-
const player = this.createPlayer(targetNode, {guildId, textChannel, voiceChannel, defaultVolume: volume, deaf})
|
|
348
|
+
const player = this.createPlayer(targetNode, { guildId, textChannel, voiceChannel, defaultVolume: volume, deaf })
|
|
337
349
|
if (current && player?.queue?.add) {
|
|
338
350
|
player.queue.add(current)
|
|
339
351
|
await player.play()
|
|
340
|
-
if (state.position > 0)
|
|
352
|
+
if (state.position > 0) _functions.unrefTimeout(() => player.seek?.(state.position), SEEK_DELAY)
|
|
341
353
|
if (state.paused) player.pause(true)
|
|
342
354
|
}
|
|
343
355
|
return player
|
|
@@ -357,7 +369,7 @@ class Aqua extends EventEmitter {
|
|
|
357
369
|
const attempts = this._failoverQueue.get(id) || 0
|
|
358
370
|
if (attempts >= this.failoverOptions.maxFailoverAttempts) return
|
|
359
371
|
|
|
360
|
-
this._nodeStates.set(id, {connected: false, failoverInProgress: true})
|
|
372
|
+
this._nodeStates.set(id, { connected: false, failoverInProgress: true })
|
|
361
373
|
this._lastFailoverAttempt.set(id, now)
|
|
362
374
|
this._failoverQueue.set(id, attempts + 1)
|
|
363
375
|
|
|
@@ -379,7 +391,7 @@ class Aqua extends EventEmitter {
|
|
|
379
391
|
} catch (error) {
|
|
380
392
|
this.emit(AqualinkEvents.Error, null, error)
|
|
381
393
|
} finally {
|
|
382
|
-
this._nodeStates.set(id, {connected: false, failoverInProgress: false})
|
|
394
|
+
this._nodeStates.set(id, { connected: false, failoverInProgress: false })
|
|
383
395
|
}
|
|
384
396
|
}
|
|
385
397
|
|
|
@@ -403,7 +415,7 @@ class Aqua extends EventEmitter {
|
|
|
403
415
|
for (let i = 0; i < players.length; i += MAX_CONCURRENT_OPS) {
|
|
404
416
|
const batch = players.slice(i, i + MAX_CONCURRENT_OPS)
|
|
405
417
|
const batchResults = await Promise.allSettled(batch.map(p => this._migratePlayer(p, pickNode)))
|
|
406
|
-
for (const r of batchResults) results.push({success: r.status === 'fulfilled', error: r.reason})
|
|
418
|
+
for (const r of batchResults) results.push({ success: r.status === 'fulfilled', error: r.reason })
|
|
407
419
|
}
|
|
408
420
|
return results
|
|
409
421
|
}
|
|
@@ -411,7 +423,7 @@ class Aqua extends EventEmitter {
|
|
|
411
423
|
async _migratePlayer(player, pickNode) {
|
|
412
424
|
const state = this._capturePlayerState(player)
|
|
413
425
|
if (!state) throw new Error('Failed to capture state')
|
|
414
|
-
const {maxRetries, retryDelay} = this.failoverOptions
|
|
426
|
+
const { maxRetries, retryDelay } = this.failoverOptions
|
|
415
427
|
for (let retry = 0; retry < maxRetries; retry++) {
|
|
416
428
|
try {
|
|
417
429
|
const targetNode = pickNode()
|
|
@@ -462,10 +474,10 @@ class Aqua extends EventEmitter {
|
|
|
462
474
|
}
|
|
463
475
|
if (state.queue?.length && newPlayer.queue?.add) newPlayer.queue.add(...state.queue)
|
|
464
476
|
if (state.current && this.failoverOptions.preservePosition) {
|
|
465
|
-
newPlayer.queue?.add?.(state.current, {toFront: true})
|
|
477
|
+
newPlayer.queue?.add?.(state.current, { toFront: true })
|
|
466
478
|
if (this.failoverOptions.resumePlayback) {
|
|
467
479
|
ops.push(newPlayer.play())
|
|
468
|
-
if (state.position > 0)
|
|
480
|
+
if (state.position > 0) _functions.unrefTimeout(() => newPlayer.seek?.(state.position), SEEK_DELAY)
|
|
469
481
|
if (state.paused) ops.push(newPlayer.pause(true))
|
|
470
482
|
}
|
|
471
483
|
}
|
|
@@ -474,7 +486,7 @@ class Aqua extends EventEmitter {
|
|
|
474
486
|
await Promise.allSettled(ops)
|
|
475
487
|
}
|
|
476
488
|
|
|
477
|
-
updateVoiceState({d, t}) {
|
|
489
|
+
updateVoiceState({ d, t }) {
|
|
478
490
|
if (!d?.guild_id || (t !== 'VOICE_STATE_UPDATE' && t !== 'VOICE_SERVER_UPDATE')) return
|
|
479
491
|
const player = this.players.get(d.guild_id)
|
|
480
492
|
if (!player || !player.nodes?.connected) return
|
|
@@ -540,7 +552,7 @@ class Aqua extends EventEmitter {
|
|
|
540
552
|
await _functions.safeCall(() => player.destroy())
|
|
541
553
|
}
|
|
542
554
|
|
|
543
|
-
async resolve({query, source, requester, nodes}) {
|
|
555
|
+
async resolve({ query, source, requester, nodes }) {
|
|
544
556
|
if (!this.initiated) throw new Error('Aqua not initialized')
|
|
545
557
|
const node = this._getRequestNode(nodes)
|
|
546
558
|
if (!node) throw new Error('No nodes available')
|
|
@@ -581,8 +593,8 @@ class Aqua extends EventEmitter {
|
|
|
581
593
|
}
|
|
582
594
|
|
|
583
595
|
_constructResponse(response, requester, node) {
|
|
584
|
-
const {loadType, data, pluginInfo: rootPlugin} = response || {}
|
|
585
|
-
const base = {loadType, exception: null, playlistInfo: null, pluginInfo: rootPlugin || {}, tracks: []}
|
|
596
|
+
const { loadType, data, pluginInfo: rootPlugin } = response || {}
|
|
597
|
+
const base = { loadType, exception: null, playlistInfo: null, pluginInfo: rootPlugin || {}, tracks: [] }
|
|
586
598
|
if (loadType === 'error' || loadType === 'LOAD_FAILED') {
|
|
587
599
|
base.exception = data || response.exception || null
|
|
588
600
|
return base
|
|
@@ -599,7 +611,7 @@ class Aqua extends EventEmitter {
|
|
|
599
611
|
...info
|
|
600
612
|
}
|
|
601
613
|
}
|
|
602
|
-
base.pluginInfo = data.pluginInfo || base.pluginInfo
|
|
614
|
+
base.pluginInfo = data.pluginInfo || rootPlugin || base.pluginInfo
|
|
603
615
|
base.tracks = Array.isArray(data.tracks) ? data.tracks.map(t => _functions.makeTrack(t, requester, node)) : []
|
|
604
616
|
} else if (loadType === 'search') {
|
|
605
617
|
base.tracks = Array.isArray(data) ? data.map(t => _functions.makeTrack(t, requester, node)) : []
|
|
@@ -616,7 +628,7 @@ class Aqua extends EventEmitter {
|
|
|
616
628
|
async search(query, requester, source) {
|
|
617
629
|
if (!query || !requester) return null
|
|
618
630
|
try {
|
|
619
|
-
const {tracks} = await this.resolve({query, source: source || this.defaultSearchPlatform, requester})
|
|
631
|
+
const { tracks } = await this.resolve({ query, source: source || this.defaultSearchPlatform, requester })
|
|
620
632
|
return tracks || null
|
|
621
633
|
} catch {
|
|
622
634
|
return null
|
|
@@ -628,8 +640,8 @@ class Aqua extends EventEmitter {
|
|
|
628
640
|
const tempFile = `${filePath}.tmp`
|
|
629
641
|
let ws = null
|
|
630
642
|
try {
|
|
631
|
-
await fs.promises.writeFile(lockFile, String(process.pid), {flag: 'wx'})
|
|
632
|
-
ws = fs.createWriteStream(tempFile, {encoding: 'utf8', flags: 'w'})
|
|
643
|
+
await fs.promises.writeFile(lockFile, String(process.pid), { flag: 'wx' })
|
|
644
|
+
ws = fs.createWriteStream(tempFile, { encoding: 'utf8', flags: 'w' })
|
|
633
645
|
const buffer = []
|
|
634
646
|
let drainPromise = Promise.resolve()
|
|
635
647
|
|
|
@@ -680,11 +692,11 @@ class Aqua extends EventEmitter {
|
|
|
680
692
|
let stream = null, rl = null
|
|
681
693
|
try {
|
|
682
694
|
await fs.promises.access(filePath)
|
|
683
|
-
await fs.promises.writeFile(lockFile, String(process.pid), {flag: 'wx'})
|
|
695
|
+
await fs.promises.writeFile(lockFile, String(process.pid), { flag: 'wx' })
|
|
684
696
|
await this._waitForFirstNode()
|
|
685
697
|
|
|
686
|
-
stream = fs.createReadStream(filePath, {encoding: 'utf8'})
|
|
687
|
-
rl = readline.createInterface({input: stream, crlfDelay: Infinity})
|
|
698
|
+
stream = fs.createReadStream(filePath, { encoding: 'utf8' })
|
|
699
|
+
rl = readline.createInterface({ input: stream, crlfDelay: Infinity })
|
|
688
700
|
|
|
689
701
|
const batch = []
|
|
690
702
|
for await (const line of rl) {
|
|
@@ -719,7 +731,7 @@ class Aqua extends EventEmitter {
|
|
|
719
731
|
player._resuming = !!p.resuming
|
|
720
732
|
const requester = _functions.parseRequester(p.r)
|
|
721
733
|
const tracksToResolve = [p.u, ...(p.q || [])].filter(Boolean).slice(0, MAX_TRACKS_RESTORE)
|
|
722
|
-
const resolved = await Promise.all(tracksToResolve.map(uri => this.resolve({query: uri, requester}).catch(() => null)))
|
|
734
|
+
const resolved = await Promise.all(tracksToResolve.map(uri => this.resolve({ query: uri, requester }).catch(() => null)))
|
|
723
735
|
const validTracks = resolved.flatMap(r => r?.tracks || [])
|
|
724
736
|
if (validTracks.length && player.queue?.add) {
|
|
725
737
|
if (player.queue.length <= 2) player.queue.length = 0
|
|
@@ -731,14 +743,14 @@ class Aqua extends EventEmitter {
|
|
|
731
743
|
else player.volume = p.vol
|
|
732
744
|
}
|
|
733
745
|
await player.play()
|
|
734
|
-
if (p.p > 0)
|
|
746
|
+
if (p.p > 0) _functions.unrefTimeout(() => player.seek?.(p.p), SEEK_DELAY)
|
|
735
747
|
if (p.pa) await player.pause(true)
|
|
736
748
|
}
|
|
737
749
|
if (p.nw && p.t) {
|
|
738
750
|
const channel = this.client.channels?.cache?.get(p.t)
|
|
739
751
|
if (channel?.messages) player.nowPlayingMessage = await channel.messages.fetch(p.nw).catch(() => null)
|
|
740
752
|
}
|
|
741
|
-
} catch {}
|
|
753
|
+
} catch { }
|
|
742
754
|
}
|
|
743
755
|
|
|
744
756
|
async _waitForFirstNode(timeout = NODE_TIMEOUT) {
|
|
@@ -756,6 +768,7 @@ class Aqua extends EventEmitter {
|
|
|
756
768
|
if (this.leastUsedNodes.length) { cleanup(); resolve() }
|
|
757
769
|
}
|
|
758
770
|
const timer = setTimeout(() => { cleanup(); reject(new Error('Timeout waiting for first node')) }, timeout)
|
|
771
|
+
timer.unref?.()
|
|
759
772
|
this.on(AqualinkEvents.NodeConnect, onReady)
|
|
760
773
|
this.on(AqualinkEvents.NodeCreate, onReady)
|
|
761
774
|
onReady()
|
|
@@ -37,7 +37,9 @@ const AqualinkEvents = {
|
|
|
37
37
|
PlayerConnected: 'playerConnected',
|
|
38
38
|
PlayerDestroyed: 'playerDestroy',
|
|
39
39
|
PlayerMigrated: 'playerMigrated',
|
|
40
|
-
PauseEvent: 'pauseEvent'
|
|
40
|
+
PauseEvent: 'pauseEvent',
|
|
41
|
+
MixStarted: 'mixStarted',
|
|
42
|
+
MixEnded: 'mixEnded'
|
|
41
43
|
};
|
|
42
44
|
|
|
43
|
-
module.exports = { AqualinkEvents };
|
|
45
|
+
module.exports = { AqualinkEvents };
|
|
@@ -4,11 +4,17 @@ const { AqualinkEvents } = require('./AqualinkEvents')
|
|
|
4
4
|
|
|
5
5
|
const POOL_SIZE = 12
|
|
6
6
|
const UPDATE_TIMEOUT = 4000
|
|
7
|
+
|
|
7
8
|
const RECONNECT_DELAY = 1000
|
|
8
9
|
const MAX_RECONNECT_ATTEMPTS = 3
|
|
9
10
|
const RESUME_BACKOFF_MAX = 8000
|
|
11
|
+
|
|
10
12
|
const VOICE_DATA_TIMEOUT = 30000
|
|
11
13
|
|
|
14
|
+
const VOICE_FLUSH_DELAY = 50
|
|
15
|
+
|
|
16
|
+
const NULL_CHANNEL_GRACE_MS = 1500
|
|
17
|
+
|
|
12
18
|
const STATE = {
|
|
13
19
|
CONNECTED: 1,
|
|
14
20
|
UPDATE_SCHEDULED: 64,
|
|
@@ -53,6 +59,7 @@ const _functions = {
|
|
|
53
59
|
v.token = conn.token
|
|
54
60
|
v.endpoint = conn.endpoint
|
|
55
61
|
v.sessionId = conn.sessionId
|
|
62
|
+
v.channelId = player.voiceChannel
|
|
56
63
|
v.resume = resume ? true : undefined
|
|
57
64
|
v.sequence = resume ? conn.sequence : undefined
|
|
58
65
|
payload.data.volume = player?.volume ?? 100
|
|
@@ -110,19 +117,28 @@ class Connection {
|
|
|
110
117
|
|
|
111
118
|
this.voiceChannel = player.voiceChannel
|
|
112
119
|
this.sessionId = null
|
|
120
|
+
this.channelId = null
|
|
113
121
|
this.endpoint = null
|
|
114
122
|
this.token = null
|
|
115
123
|
this.region = null
|
|
116
124
|
this.sequence = 0
|
|
117
125
|
|
|
118
126
|
this._lastEndpoint = null
|
|
119
|
-
this._pendingUpdate = null
|
|
120
127
|
this._stateFlags = 0
|
|
121
128
|
this._reconnectAttempts = 0
|
|
122
129
|
this._destroyed = false
|
|
123
130
|
this._reconnectTimer = null
|
|
124
131
|
this._lastVoiceDataUpdate = 0
|
|
125
132
|
this._consecutiveFailures = 0
|
|
133
|
+
|
|
134
|
+
this._voiceFlushTimer = null
|
|
135
|
+
this._pendingUpdate = null
|
|
136
|
+
this._lastSentVoiceKey = ''
|
|
137
|
+
|
|
138
|
+
this._nullChannelTimer = null
|
|
139
|
+
|
|
140
|
+
this._lastStateReqAt = 0
|
|
141
|
+
this._stateGeneration = 0
|
|
126
142
|
}
|
|
127
143
|
|
|
128
144
|
_hasValidVoiceData() {
|
|
@@ -134,10 +150,17 @@ class Connection {
|
|
|
134
150
|
return true
|
|
135
151
|
}
|
|
136
152
|
|
|
137
|
-
|
|
138
|
-
if (this.
|
|
153
|
+
_clearNullChannelTimer() {
|
|
154
|
+
if (!this._nullChannelTimer) return
|
|
155
|
+
clearTimeout(this._nullChannelTimer)
|
|
156
|
+
this._nullChannelTimer = null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_canAttemptResumeCore() {
|
|
160
|
+
if (this._destroyed) return false
|
|
161
|
+
if (this._reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) return false
|
|
139
162
|
if (this._stateFlags & (STATE.ATTEMPTING_RESUME | STATE.DISCONNECTING)) return false
|
|
140
|
-
return
|
|
163
|
+
return true
|
|
141
164
|
}
|
|
142
165
|
|
|
143
166
|
_setReconnectTimer(delay) {
|
|
@@ -148,12 +171,15 @@ class Connection {
|
|
|
148
171
|
}
|
|
149
172
|
|
|
150
173
|
setServerUpdate(data) {
|
|
151
|
-
if (this._destroyed || !data?.
|
|
174
|
+
if (this._destroyed || !data?.token) return
|
|
152
175
|
|
|
153
176
|
const endpoint = typeof data.endpoint === 'string' ? data.endpoint.trim() : ''
|
|
154
|
-
if (!endpoint
|
|
177
|
+
if (!endpoint) return
|
|
178
|
+
|
|
155
179
|
if (this._lastEndpoint === endpoint && this.token === data.token) return
|
|
156
180
|
|
|
181
|
+
this._stateGeneration++
|
|
182
|
+
|
|
157
183
|
if (this._lastEndpoint !== endpoint) {
|
|
158
184
|
this.sequence = 0
|
|
159
185
|
this._lastEndpoint = endpoint
|
|
@@ -164,10 +190,11 @@ class Connection {
|
|
|
164
190
|
this.endpoint = endpoint
|
|
165
191
|
this.region = _functions.extractRegion(endpoint)
|
|
166
192
|
this.token = data.token
|
|
193
|
+
this.channelId = data.channel_id || this.channelId || this.voiceChannel
|
|
167
194
|
this._lastVoiceDataUpdate = Date.now()
|
|
168
195
|
this._stateFlags &= ~STATE.VOICE_DATA_STALE
|
|
169
196
|
|
|
170
|
-
if (this._player
|
|
197
|
+
if (this._player?.paused) this._player.pause(false)
|
|
171
198
|
this._scheduleVoiceUpdate()
|
|
172
199
|
}
|
|
173
200
|
|
|
@@ -181,15 +208,38 @@ class Connection {
|
|
|
181
208
|
if (this._destroyed || !data || data.user_id !== this._clientId) return
|
|
182
209
|
|
|
183
210
|
const { session_id: sessionId, channel_id: channelId, self_deaf: selfDeaf, self_mute: selfMute } = data
|
|
211
|
+
const p = this._player
|
|
184
212
|
|
|
185
|
-
if (
|
|
213
|
+
if (channelId) this._clearNullChannelTimer()
|
|
214
|
+
|
|
215
|
+
const reqCh = p?._voiceRequestChannel
|
|
216
|
+
const reqFresh = !!(reqCh && (Date.now() - (p._voiceRequestAt || 0)) < 5000)
|
|
217
|
+
|
|
218
|
+
if (!channelId) {
|
|
219
|
+
if (reqFresh) return
|
|
220
|
+
|
|
221
|
+
if (!this._nullChannelTimer) {
|
|
222
|
+
this._nullChannelTimer = setTimeout(() => {
|
|
223
|
+
this._nullChannelTimer = null
|
|
224
|
+
this._handleDisconnect()
|
|
225
|
+
}, NULL_CHANNEL_GRACE_MS)
|
|
226
|
+
_functions.safeUnref(this._nullChannelTimer)
|
|
227
|
+
}
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (reqFresh && channelId !== reqCh) return
|
|
232
|
+
|
|
233
|
+
if (reqCh && channelId === reqCh) {
|
|
234
|
+
p._voiceRequestChannel = null
|
|
235
|
+
}
|
|
186
236
|
|
|
187
237
|
let needsUpdate = false
|
|
188
238
|
|
|
189
239
|
if (this.voiceChannel !== channelId) {
|
|
190
240
|
this._aqua.emit(AqualinkEvents.PlayerMove, this.voiceChannel, channelId)
|
|
191
241
|
this.voiceChannel = channelId
|
|
192
|
-
|
|
242
|
+
p.voiceChannel = channelId
|
|
193
243
|
needsUpdate = true
|
|
194
244
|
}
|
|
195
245
|
|
|
@@ -202,30 +252,29 @@ class Connection {
|
|
|
202
252
|
needsUpdate = true
|
|
203
253
|
}
|
|
204
254
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
this._player.self_mute = this._player.selfMute = !!selfMute
|
|
208
|
-
this._player.connected = true
|
|
255
|
+
p.self_deaf = p.selfDeaf = !!selfDeaf
|
|
256
|
+
p.self_mute = p.selfMute = !!selfMute
|
|
209
257
|
this._stateFlags |= STATE.CONNECTED
|
|
210
258
|
|
|
211
259
|
if (needsUpdate) this._scheduleVoiceUpdate()
|
|
212
260
|
}
|
|
213
261
|
|
|
214
262
|
_handleDisconnect() {
|
|
215
|
-
if (this._destroyed
|
|
263
|
+
if (this._destroyed) return
|
|
216
264
|
|
|
217
265
|
this._stateFlags = (this._stateFlags | STATE.DISCONNECTING) & ~STATE.CONNECTED
|
|
218
|
-
this.
|
|
266
|
+
this._clearNullChannelTimer()
|
|
219
267
|
this._clearPendingUpdate()
|
|
220
268
|
this._clearReconnectTimer()
|
|
221
269
|
|
|
222
|
-
this.voiceChannel =
|
|
270
|
+
this.voiceChannel = null
|
|
271
|
+
this.sessionId = null
|
|
223
272
|
this.sequence = 0
|
|
224
273
|
this._lastVoiceDataUpdate = 0
|
|
225
274
|
this._stateFlags |= STATE.VOICE_DATA_STALE
|
|
226
275
|
|
|
227
276
|
try {
|
|
228
|
-
this._player
|
|
277
|
+
this._player?.destroy?.()
|
|
229
278
|
} catch (e) {
|
|
230
279
|
this._aqua.emit(AqualinkEvents.Debug, new Error(`Player destroy failed: ${e?.message || e}`))
|
|
231
280
|
} finally {
|
|
@@ -235,6 +284,10 @@ class Connection {
|
|
|
235
284
|
|
|
236
285
|
_requestVoiceState() {
|
|
237
286
|
try {
|
|
287
|
+
const now = Date.now()
|
|
288
|
+
if (now - (this._lastStateReqAt || 0) < 1500) return false
|
|
289
|
+
this._lastStateReqAt = now
|
|
290
|
+
|
|
238
291
|
if (typeof this._player?.send !== 'function' || !this._player.voiceChannel) return false
|
|
239
292
|
this._player.send({
|
|
240
293
|
guild_id: this._guildId,
|
|
@@ -242,7 +295,6 @@ class Connection {
|
|
|
242
295
|
self_deaf: this._player.deaf,
|
|
243
296
|
self_mute: this._player.mute
|
|
244
297
|
})
|
|
245
|
-
this._setReconnectTimer(1500)
|
|
246
298
|
return true
|
|
247
299
|
} catch {
|
|
248
300
|
return false
|
|
@@ -250,47 +302,38 @@ class Connection {
|
|
|
250
302
|
}
|
|
251
303
|
|
|
252
304
|
async attemptResume() {
|
|
253
|
-
if (!this.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
`Resume blocked: destroyed=${this._destroyed}, hasValidData=${this._hasValidVoiceData()}, attempts=${this._reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS}`
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
const isResuming = this._player?._resuming
|
|
260
|
-
const isStale = this._stateFlags & STATE.VOICE_DATA_STALE
|
|
261
|
-
const needsVoiceData = !this.sessionId || !this.endpoint || !this.token
|
|
262
|
-
|
|
263
|
-
if ((isStale || needsVoiceData) && isResuming) {
|
|
264
|
-
this._aqua.emit(AqualinkEvents.Debug, `Requesting fresh voice state for guild ${this._guildId}`)
|
|
265
|
-
this._requestVoiceState()
|
|
266
|
-
} else if (this._reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
267
|
-
this._handleDisconnect()
|
|
268
|
-
}
|
|
269
|
-
return false
|
|
270
|
-
}
|
|
305
|
+
if (!this._canAttemptResumeCore()) return false
|
|
306
|
+
|
|
307
|
+
const currentGen = this._stateGeneration
|
|
271
308
|
|
|
272
|
-
|
|
273
|
-
|
|
309
|
+
|
|
310
|
+
if (!this.sessionId || !this.endpoint || !this.token || (this._stateFlags & STATE.VOICE_DATA_STALE)) {
|
|
311
|
+
this._aqua.emit(AqualinkEvents.Debug, `Resume blocked: missing voice data for guild ${this._guildId}, requesting voice state`)
|
|
274
312
|
this._requestVoiceState()
|
|
275
313
|
return false
|
|
276
314
|
}
|
|
277
315
|
|
|
278
316
|
this._stateFlags |= STATE.ATTEMPTING_RESUME
|
|
279
317
|
this._reconnectAttempts++
|
|
280
|
-
this._aqua.emit(
|
|
281
|
-
AqualinkEvents.Debug,
|
|
282
|
-
`Attempt resume: guild=${this._guildId} endpoint=${this.endpoint} session=${this.sessionId}`
|
|
283
|
-
)
|
|
318
|
+
this._aqua.emit(AqualinkEvents.Debug, `Attempt resume: guild=${this._guildId} endpoint=${this.endpoint} session=${this.sessionId}`)
|
|
284
319
|
|
|
285
320
|
const payload = sharedPool.acquire()
|
|
286
321
|
try {
|
|
287
322
|
_functions.fillVoicePayload(payload, this._guildId, this, this._player, true)
|
|
323
|
+
|
|
324
|
+
if (this._stateGeneration !== currentGen) {
|
|
325
|
+
this._aqua.emit(AqualinkEvents.Debug, `Resume aborted: State changed during attempt for guild ${this._guildId}`)
|
|
326
|
+
return false
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
|
|
288
330
|
await this._sendUpdate(payload)
|
|
289
331
|
|
|
290
332
|
this._reconnectAttempts = 0
|
|
291
333
|
this._consecutiveFailures = 0
|
|
292
334
|
if (this._player) this._player._resuming = false
|
|
293
|
-
|
|
335
|
+
|
|
336
|
+
this._aqua.emit(AqualinkEvents.Debug, `Resume PATCH sent for guild ${this._guildId}`)
|
|
294
337
|
return true
|
|
295
338
|
} catch (e) {
|
|
296
339
|
this._consecutiveFailures++
|
|
@@ -300,7 +343,7 @@ class Connection {
|
|
|
300
343
|
const delay = Math.min(RECONNECT_DELAY * (1 << (this._reconnectAttempts - 1)), RESUME_BACKOFF_MAX)
|
|
301
344
|
this._setReconnectTimer(delay)
|
|
302
345
|
} else {
|
|
303
|
-
this._aqua.emit(AqualinkEvents.Debug, `Max reconnect attempts
|
|
346
|
+
this._aqua.emit(AqualinkEvents.Debug, `Max reconnect attempts/failures reached for guild ${this._guildId}`)
|
|
304
347
|
if (this._player) this._player._resuming = false
|
|
305
348
|
this._handleDisconnect()
|
|
306
349
|
}
|
|
@@ -330,36 +373,64 @@ class Connection {
|
|
|
330
373
|
this._stateFlags &= ~STATE.UPDATE_SCHEDULED
|
|
331
374
|
if (this._pendingUpdate?.payload) sharedPool.release(this._pendingUpdate.payload)
|
|
332
375
|
this._pendingUpdate = null
|
|
376
|
+
if (this._voiceFlushTimer) {
|
|
377
|
+
clearTimeout(this._voiceFlushTimer)
|
|
378
|
+
this._voiceFlushTimer = null
|
|
379
|
+
}
|
|
333
380
|
}
|
|
334
381
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
this.
|
|
382
|
+
_makeVoiceKey() {
|
|
383
|
+
const p = this._player
|
|
384
|
+
const vol = p?.volume ?? 100
|
|
385
|
+
return (this.sessionId || '') + '|' +
|
|
386
|
+
(this.token || '') + '|' +
|
|
387
|
+
(this.endpoint || '') + '|' +
|
|
388
|
+
(p?.voiceChannel || '') + '|' +
|
|
389
|
+
vol
|
|
390
|
+
}
|
|
339
391
|
|
|
340
|
-
|
|
341
|
-
|
|
392
|
+
_scheduleVoiceUpdate() {
|
|
393
|
+
if (this._destroyed) return
|
|
394
|
+
if (!this._hasValidVoiceData()) return
|
|
395
|
+
|
|
396
|
+
if (!this._pendingUpdate) {
|
|
397
|
+
const payload = sharedPool.acquire()
|
|
398
|
+
_functions.fillVoicePayload(payload, this._guildId, this, this._player, false)
|
|
399
|
+
this._pendingUpdate = { payload, timestamp: Date.now() }
|
|
400
|
+
} else {
|
|
401
|
+
this._pendingUpdate.timestamp = Date.now()
|
|
402
|
+
_functions.fillVoicePayload(this._pendingUpdate.payload, this._guildId, this, this._player, false)
|
|
403
|
+
}
|
|
342
404
|
|
|
343
|
-
this.
|
|
405
|
+
if (this._stateFlags & STATE.UPDATE_SCHEDULED) return
|
|
344
406
|
this._stateFlags |= STATE.UPDATE_SCHEDULED
|
|
345
|
-
|
|
407
|
+
|
|
408
|
+
this._voiceFlushTimer = setTimeout(() => this._executeVoiceUpdate(), VOICE_FLUSH_DELAY)
|
|
409
|
+
_functions.safeUnref(this._voiceFlushTimer)
|
|
346
410
|
}
|
|
347
411
|
|
|
348
412
|
_executeVoiceUpdate() {
|
|
349
413
|
if (this._destroyed) return
|
|
350
414
|
this._stateFlags &= ~STATE.UPDATE_SCHEDULED
|
|
415
|
+
this._voiceFlushTimer = null
|
|
351
416
|
|
|
352
417
|
const pending = this._pendingUpdate
|
|
353
|
-
if (!pending) return
|
|
354
418
|
this._pendingUpdate = null
|
|
355
419
|
|
|
420
|
+
if (!pending) return
|
|
356
421
|
if (Date.now() - pending.timestamp > UPDATE_TIMEOUT) {
|
|
357
422
|
sharedPool.release(pending.payload)
|
|
358
423
|
return
|
|
359
424
|
}
|
|
360
425
|
|
|
361
|
-
const
|
|
362
|
-
|
|
426
|
+
const key = this._makeVoiceKey()
|
|
427
|
+
if (key === this._lastSentVoiceKey) {
|
|
428
|
+
sharedPool.release(pending.payload)
|
|
429
|
+
return
|
|
430
|
+
}
|
|
431
|
+
this._lastSentVoiceKey = key
|
|
432
|
+
|
|
433
|
+
this._sendUpdate(pending.payload).finally(() => sharedPool.release(pending.payload))
|
|
363
434
|
}
|
|
364
435
|
|
|
365
436
|
async _sendUpdate(payload) {
|
|
@@ -380,6 +451,7 @@ class Connection {
|
|
|
380
451
|
if (this._destroyed) return
|
|
381
452
|
this._destroyed = true
|
|
382
453
|
|
|
454
|
+
this._clearNullChannelTimer()
|
|
383
455
|
this._clearPendingUpdate()
|
|
384
456
|
this._clearReconnectTimer()
|
|
385
457
|
|
package/build/structures/Node.js
CHANGED
|
@@ -22,10 +22,12 @@ const EVENT_HANDLERS = Object.freeze({
|
|
|
22
22
|
FiltersChangedEvent: 'filtersChanged',
|
|
23
23
|
SeekEvent: 'seekEvent',
|
|
24
24
|
PlayerCreatedEvent: 'playerCreated',
|
|
25
|
-
|
|
25
|
+
PauseEvent: 'pauseEvent',
|
|
26
26
|
PlayerConnectedEvent: 'playerConnected',
|
|
27
27
|
PlayerDestroyedEvent: 'playerDestroyed',
|
|
28
|
-
LyricsNotFoundEvent: 'lyricsNotFound'
|
|
28
|
+
LyricsNotFoundEvent: 'lyricsNotFound',
|
|
29
|
+
MixStartedEvent: 'mixStarted',
|
|
30
|
+
MixEndedEvent: 'mixEnded'
|
|
29
31
|
})
|
|
30
32
|
|
|
31
33
|
const WATCHDOG_INTERVAL = 15000
|
|
@@ -191,6 +193,9 @@ class Player extends EventEmitter {
|
|
|
191
193
|
this.previousTracks = new CircularBuffer(PREVIOUS_TRACKS_SIZE)
|
|
192
194
|
this._updateBatcher = batcherPool.acquire(this)
|
|
193
195
|
|
|
196
|
+
this._voiceRequestAt = 0
|
|
197
|
+
this._voiceRequestChannel = null
|
|
198
|
+
this._suppressResumeUntil = 0
|
|
194
199
|
this._bindEvents()
|
|
195
200
|
this._startWatchdog()
|
|
196
201
|
}
|
|
@@ -239,6 +244,7 @@ class Player extends EventEmitter {
|
|
|
239
244
|
this._voiceDownSince = Date.now()
|
|
240
245
|
this._createTimer(() => {
|
|
241
246
|
if (this.connected || this.destroyed || this.nodes?.info?.isNodelink) return
|
|
247
|
+
if (Date.now() < (this._suppressResumeUntil || 0)) return
|
|
242
248
|
this.connection.attemptResume()
|
|
243
249
|
}, 1000)
|
|
244
250
|
}
|
|
@@ -335,13 +341,24 @@ class Player extends EventEmitter {
|
|
|
335
341
|
|
|
336
342
|
connect(options = {}) {
|
|
337
343
|
if (this.destroyed) throw new Error('Cannot connect destroyed player')
|
|
344
|
+
|
|
338
345
|
const voiceChannel = _functions.toId(options.voiceChannel || this.voiceChannel)
|
|
339
346
|
if (!voiceChannel) throw new TypeError('Voice channel required')
|
|
347
|
+
|
|
340
348
|
this.deaf = options.deaf !== undefined ? !!options.deaf : true
|
|
341
349
|
this.mute = !!options.mute
|
|
342
350
|
this.destroyed = false
|
|
351
|
+
|
|
352
|
+
this._voiceRequestAt = Date.now()
|
|
353
|
+
this._voiceRequestChannel = voiceChannel
|
|
354
|
+
|
|
343
355
|
this.voiceChannel = voiceChannel
|
|
344
|
-
this.send({
|
|
356
|
+
this.send({
|
|
357
|
+
guild_id: this.guildId,
|
|
358
|
+
channel_id: voiceChannel,
|
|
359
|
+
self_deaf: this.deaf,
|
|
360
|
+
self_mute: this.mute
|
|
361
|
+
})
|
|
345
362
|
return this
|
|
346
363
|
}
|
|
347
364
|
|
|
@@ -430,6 +447,9 @@ class Player extends EventEmitter {
|
|
|
430
447
|
this._dataStore = null
|
|
431
448
|
|
|
432
449
|
if (this.current?.dispose && !this.aqua?.options?.autoResume) this.current.dispose()
|
|
450
|
+
if (this.connection) {
|
|
451
|
+
try { this.connection.destroy() } catch { }
|
|
452
|
+
}
|
|
433
453
|
this.connection = this.filters = this.current = this.autoplaySeed = null
|
|
434
454
|
|
|
435
455
|
if (!skipRemote) {
|
|
@@ -460,11 +480,54 @@ class Player extends EventEmitter {
|
|
|
460
480
|
return this
|
|
461
481
|
}
|
|
462
482
|
|
|
483
|
+
async getActiveMixer(guildId) {
|
|
484
|
+
if (this.destroyed) return null
|
|
485
|
+
return await this.nodes.rest.getActiveMixer(guildId)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async updateMixerVolume(guildId, mix, volume) {
|
|
489
|
+
if (this.destroyed) return null
|
|
490
|
+
return await this.nodes.rest.updateMixerVolume(guildId, mix, volume)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async removeMixer(guildId, mix) {
|
|
494
|
+
if (this.destroyed) return null
|
|
495
|
+
return await this.nodes.rest.removeMixer(guildId, mix)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async addMixer(guildId, options) {
|
|
499
|
+
if (this.destroyed) return null
|
|
500
|
+
|
|
501
|
+
if (options.identifier && !options.encoded) {
|
|
502
|
+
try {
|
|
503
|
+
const resolved = await this.aqua.resolve({
|
|
504
|
+
query: options.identifier,
|
|
505
|
+
requester: options.requester || this.current?.requester
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
if (resolved?.tracks?.[0]) {
|
|
509
|
+
const track = resolved.tracks[0]
|
|
510
|
+
options = {
|
|
511
|
+
...options,
|
|
512
|
+
encoded: track.track || track.encoded,
|
|
513
|
+
userData: options.userData
|
|
514
|
+
}
|
|
515
|
+
} else {
|
|
516
|
+
throw new Error('Failed to resolve track identifier')
|
|
517
|
+
}
|
|
518
|
+
} catch (error) {
|
|
519
|
+
throw new Error(`Failed to resolve track: ${error.message}`)
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return await this.nodes.rest.addMixer(guildId, options)
|
|
524
|
+
}
|
|
525
|
+
|
|
463
526
|
stop() {
|
|
464
527
|
if (this.destroyed || !this.playing) return this
|
|
465
528
|
this.playing = this.paused = false
|
|
466
529
|
this.position = 0
|
|
467
|
-
this.batchUpdatePlayer({ guildId: this.guildId, track: { encoded: null } }, true).catch(() => { })
|
|
530
|
+
this.batchUpdatePlayer({ guildId: this.guildId, track: { encoded: null, paused: this.paused } }, true).catch(() => { })
|
|
468
531
|
return this
|
|
469
532
|
}
|
|
470
533
|
|
|
@@ -684,7 +747,9 @@ class Player extends EventEmitter {
|
|
|
684
747
|
playerCreated(p, t, payload) { _functions.emitIfActive(this, AqualinkEvents.PlayerCreated, payload) }
|
|
685
748
|
playerConnected(p, t, payload) { _functions.emitIfActive(this, AqualinkEvents.PlayerConnected, payload) }
|
|
686
749
|
playerDestroyed(p, t, payload) { _functions.emitIfActive(this, AqualinkEvents.PlayerDestroyed, payload) }
|
|
687
|
-
|
|
750
|
+
pauseEvent(p, t, payload) { _functions.emitIfActive(this, AqualinkEvents.PauseEvent, payload) }
|
|
751
|
+
mixStarted(p, t, payload) { _functions.emitIfActive(this, AqualinkEvents.MixStarted, t, payload) }
|
|
752
|
+
mixEnded(p, t, payload) { _functions.emitIfActive(this, AqualinkEvents.MixEnded, t, payload) }
|
|
688
753
|
|
|
689
754
|
async _attemptVoiceResume() {
|
|
690
755
|
if (!this.connection?.sessionId) throw new Error('No session')
|
|
@@ -710,14 +775,20 @@ class Player extends EventEmitter {
|
|
|
710
775
|
if (this.destroyed) return
|
|
711
776
|
const code = payload?.code
|
|
712
777
|
|
|
713
|
-
if (code === 4022) {
|
|
778
|
+
if (code === 4014 || code === 4022) {
|
|
714
779
|
this.aqua.emit(AqualinkEvents.SocketClosed, this, payload)
|
|
715
|
-
|
|
780
|
+
|
|
781
|
+
this.connected = false
|
|
782
|
+
if (!this._voiceDownSince) this._voiceDownSince = Date.now()
|
|
783
|
+
|
|
784
|
+
if (code !== 4014) {
|
|
785
|
+
this._suppressResumeUntil = Date.now() + 3000
|
|
786
|
+
}
|
|
716
787
|
return
|
|
717
788
|
}
|
|
718
789
|
|
|
719
790
|
if (code === 4015 && !this.nodes?.info?.isNodelink) {
|
|
720
|
-
try { await this._attemptVoiceResume(); return } catch { }
|
|
791
|
+
try { await this._attemptVoiceResume(); return } catch { /* ignore */ }
|
|
721
792
|
}
|
|
722
793
|
|
|
723
794
|
if (![4015, 4009, 4006].includes(code)) {
|
|
@@ -804,8 +875,6 @@ class Player extends EventEmitter {
|
|
|
804
875
|
_handleAquaPlayerMove(oldChannel, newChannel) {
|
|
805
876
|
if (_functions.toId(oldChannel) !== _functions.toId(this.voiceChannel)) return
|
|
806
877
|
this.voiceChannel = _functions.toId(newChannel)
|
|
807
|
-
this.connected = !!newChannel
|
|
808
|
-
this.send({ guild_id: this.guildId, channel_id: this.voiceChannel, self_deaf: this.deaf, self_mute: this.mute })
|
|
809
878
|
}
|
|
810
879
|
|
|
811
880
|
send(data) {
|
|
@@ -845,4 +914,4 @@ class Player extends EventEmitter {
|
|
|
845
914
|
}
|
|
846
915
|
}
|
|
847
916
|
|
|
848
|
-
module.exports = Player
|
|
917
|
+
module.exports = Player
|
package/build/structures/Rest.js
CHANGED
|
@@ -317,7 +317,7 @@ class Rest {
|
|
|
317
317
|
|
|
318
318
|
_closeH2() {
|
|
319
319
|
if (this._h2Timer) { clearTimeout(this._h2Timer); this._h2Timer = null }
|
|
320
|
-
if (this._h2) { try { this._h2.close() } catch {} this._h2 = null }
|
|
320
|
+
if (this._h2) { try { this._h2.close() } catch { } this._h2 = null }
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
_h2Request(method, path, headers, payload) {
|
|
@@ -480,14 +480,14 @@ class Rest {
|
|
|
480
480
|
try {
|
|
481
481
|
const lyrics = await this.makeRequest('GET', `${this._getSessionPath()}/players/${guildId}/track/lyrics?skipTrackSource=${skip}`)
|
|
482
482
|
if (this._validLyrics(lyrics)) return lyrics
|
|
483
|
-
} catch {}
|
|
483
|
+
} catch { }
|
|
484
484
|
}
|
|
485
485
|
|
|
486
486
|
if (hasEncoded) {
|
|
487
487
|
try {
|
|
488
488
|
const lyrics = await this.makeRequest('GET', `${this._endpoints.lyrics}?track=${encodeURIComponent(encoded)}&skipTrackSource=${skip}`)
|
|
489
489
|
if (this._validLyrics(lyrics)) return lyrics
|
|
490
|
-
} catch {}
|
|
490
|
+
} catch { }
|
|
491
491
|
}
|
|
492
492
|
|
|
493
493
|
if (title) {
|
|
@@ -495,7 +495,7 @@ class Rest {
|
|
|
495
495
|
try {
|
|
496
496
|
const lyrics = await this.makeRequest('GET', `${this._endpoints.lyrics}/search?query=${encodeURIComponent(query)}`)
|
|
497
497
|
if (this._validLyrics(lyrics)) return lyrics
|
|
498
|
-
} catch {}
|
|
498
|
+
} catch { }
|
|
499
499
|
}
|
|
500
500
|
|
|
501
501
|
return null
|
|
@@ -524,6 +524,44 @@ class Rest {
|
|
|
524
524
|
}
|
|
525
525
|
}
|
|
526
526
|
|
|
527
|
+
async addMixer(guildId, options) {
|
|
528
|
+
if (!this.node.isNodelink) throw new Error('Mixer endpoints are only available on Nodelink nodes')
|
|
529
|
+
if (!options?.encoded && !options?.identifier) throw new Error('You must provide either encoded or identifier')
|
|
530
|
+
|
|
531
|
+
const track = {}
|
|
532
|
+
if (options.encoded) track.encoded = options.encoded
|
|
533
|
+
if (options.identifier) track.identifier = options.identifier
|
|
534
|
+
if (options.userData) track.userData = options.userData
|
|
535
|
+
|
|
536
|
+
const payload = {
|
|
537
|
+
track,
|
|
538
|
+
volume: options.volume !== undefined ? options.volume : 0.8
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return await this.makeRequest("POST", `/v4/sessions/${this.sessionId}/players/${guildId}/mix`, payload)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async getActiveMixer(guildId) {
|
|
545
|
+
if (!this.node.isNodelink) throw new Error('Mixer endpoints are only available on Nodelink nodes')
|
|
546
|
+
const response = await this.makeRequest("GET", `/v4/sessions/${this.sessionId}/players/${guildId}/mix`)
|
|
547
|
+
return response?.mixes || []
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async updateMixerVolume(guildId, mix, volume) {
|
|
551
|
+
if (!this.node.isNodelink) throw new Error('Mixer endpoints are only available on Nodelink nodes')
|
|
552
|
+
if (!guildId || !mix || typeof volume !== 'number') throw new Error('You forget to set the guild_id, mix or volume options')
|
|
553
|
+
|
|
554
|
+
return await this.makeRequest("PATCH", `/v4/sessions/${this.sessionId}/players/${guildId}/mix/${mix}`, { volume })
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
async removeMixer(guildId, mix) {
|
|
558
|
+
if (!this.node.isNodelink) throw new Error('Mixer endpoints are only available on Nodelink nodes')
|
|
559
|
+
if (!guildId || !mix) throw new Error('You forget to set the guild_id and/or mix options')
|
|
560
|
+
|
|
561
|
+
return await this.makeRequest("DELETE", `/v4/sessions/${this.sessionId}/players/${guildId}/mix/${mix}`)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
|
|
527
565
|
destroy() {
|
|
528
566
|
if (this.agent) { this.agent.destroy(); this.agent = null }
|
|
529
567
|
this._closeH2()
|
|
@@ -22,6 +22,7 @@ class Track {
|
|
|
22
22
|
this.uri = _h.str(info.uri)
|
|
23
23
|
this.sourceName = _h.str(info.sourceName)
|
|
24
24
|
this.artworkUrl = _h.str(info.artworkUrl)
|
|
25
|
+
this.pluginInfo = info.pluginInfo || data.pluginInfo || {}
|
|
25
26
|
|
|
26
27
|
this.playlist = data.playlist || null
|
|
27
28
|
this.node = node || data.node || null
|
|
@@ -92,6 +93,7 @@ class Track {
|
|
|
92
93
|
this.uri = fi.uri ?? this.uri
|
|
93
94
|
this.sourceName = fi.sourceName ?? this.sourceName
|
|
94
95
|
this.artworkUrl = fi.artworkUrl ?? this.artworkUrl
|
|
96
|
+
this.pluginInfo = fi.pluginInfo ?? found.pluginInfo ?? this.pluginInfo
|
|
95
97
|
this.isSeekable = fi.isSeekable ?? this.isSeekable
|
|
96
98
|
this.isStream = fi.isStream ?? this.isStream
|
|
97
99
|
this.position = _h.num(fi.position, this.position)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aqualink",
|
|
3
|
-
"version": "2.17.
|
|
3
|
+
"version": "2.17.3",
|
|
4
4
|
"description": "An Lavalink client, focused in pure performance and features",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"homepage": "https://aqualink-6006388d.mintlify.app/",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"ws": "^8.
|
|
48
|
+
"ws": "^8.19.0",
|
|
49
49
|
"tseep": "^1.3.1"
|
|
50
50
|
},
|
|
51
51
|
"contributors": [
|