aqualink 2.11.8 → 2.11.9
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/structures/Aqua.js +130 -306
- package/build/structures/Player.js +251 -294
- package/build/structures/Rest.js +0 -23
- package/package.json +1 -1
package/build/structures/Aqua.js
CHANGED
|
@@ -19,6 +19,17 @@ const EMPTY_TRACKS_RESPONSE = Object.freeze({
|
|
|
19
19
|
tracks: EMPTY_ARRAY
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
+
const CLEANUP_INTERVAL = 180000
|
|
23
|
+
const MAX_CONCURRENT_OPS = 10
|
|
24
|
+
const BROKEN_PLAYER_TTL = 300000
|
|
25
|
+
const FAILOVER_CLEANUP_TTL = 600000
|
|
26
|
+
const PLAYER_BATCH_SIZE = 20
|
|
27
|
+
const SEEK_DELAY = 120
|
|
28
|
+
const RECONNECT_DELAY = 400
|
|
29
|
+
const CACHE_VALID_TIME = 12000
|
|
30
|
+
const NODE_TIMEOUT = 30000
|
|
31
|
+
|
|
32
|
+
const URL_PATTERN = /^https?:\/\//i
|
|
22
33
|
const DEFAULT_OPTIONS = Object.freeze({
|
|
23
34
|
shouldDeleteMessage: false,
|
|
24
35
|
defaultSearchPlatform: 'ytsearch',
|
|
@@ -27,10 +38,7 @@ const DEFAULT_OPTIONS = Object.freeze({
|
|
|
27
38
|
plugins: [],
|
|
28
39
|
autoResume: true,
|
|
29
40
|
infiniteReconnects: true,
|
|
30
|
-
|
|
31
|
-
restrictedDomains: [],
|
|
32
|
-
allowedDomains: [],
|
|
33
|
-
loadBancer: 'leastLoad', // cpu, memory, rest: leastLoad, only rest: leastRest, no check: random
|
|
41
|
+
loadBalancer: 'leastLoad',
|
|
34
42
|
failoverOptions: Object.freeze({
|
|
35
43
|
enabled: true,
|
|
36
44
|
maxRetries: 3,
|
|
@@ -42,34 +50,20 @@ const DEFAULT_OPTIONS = Object.freeze({
|
|
|
42
50
|
})
|
|
43
51
|
})
|
|
44
52
|
|
|
45
|
-
const CLEANUP_INTERVAL = 180000
|
|
46
|
-
const MAX_CONCURRENT_OPS = 10
|
|
47
|
-
const BROKEN_PLAYER_TTL = 300000
|
|
48
|
-
const FAILOVER_CLEANUP_TTL = 600000
|
|
49
|
-
const PLAYER_BATCH_SIZE = 20
|
|
50
|
-
const SEEK_DELAY = 120
|
|
51
|
-
const RECONNECT_DELAY = 400
|
|
52
|
-
const CACHE_VALID_TIME = 12000
|
|
53
|
-
const NODE_TIMEOUT = 30000
|
|
54
|
-
const URL_PATTERN = /^https?:\/\//i
|
|
55
53
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
const _isProbablyUrl = (s) => typeof s === 'string' && s.length > 8 && URL_PATTERN.test(s)
|
|
55
|
+
const _delay = (ms) => new Promise(r => setTimeout(r, ms))
|
|
56
|
+
const _range = (start, end, step = 1) => {
|
|
57
|
+
const result = []
|
|
58
|
+
for (let i = start; i < end; i += step) result.push(i)
|
|
59
|
+
return result
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return host === suffix || host.endsWith('.' + suffix)
|
|
62
|
+
const _unref = (timer) => {
|
|
63
|
+
if (timer && typeof timer.unref === 'function') timer.unref()
|
|
64
|
+
return timer
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
const _isProbablyUrl = (s) => typeof s === 'string' && s.length > 8 && URL_PATTERN.test(s)
|
|
70
|
-
|
|
71
|
-
const _delay = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
|
72
|
-
|
|
73
67
|
class Aqua extends EventEmitter {
|
|
74
68
|
constructor(client, nodes, options = {}) {
|
|
75
69
|
super()
|
|
@@ -84,20 +78,22 @@ class Aqua extends EventEmitter {
|
|
|
84
78
|
this.initiated = false
|
|
85
79
|
this.version = pkgVersion
|
|
86
80
|
|
|
87
|
-
this.options =
|
|
88
|
-
this.failoverOptions =
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.
|
|
92
|
-
this.
|
|
93
|
-
this.
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
96
|
-
this.
|
|
97
|
-
this.
|
|
98
|
-
this.
|
|
99
|
-
this.
|
|
100
|
-
this.
|
|
81
|
+
this.options = { ...DEFAULT_OPTIONS, ...options }
|
|
82
|
+
this.failoverOptions = { ...DEFAULT_OPTIONS.failoverOptions, ...options.failoverOptions }
|
|
83
|
+
|
|
84
|
+
const opts = this.options
|
|
85
|
+
this.shouldDeleteMessage = opts.shouldDeleteMessage
|
|
86
|
+
this.defaultSearchPlatform = opts.defaultSearchPlatform
|
|
87
|
+
this.leaveOnEnd = opts.leaveOnEnd
|
|
88
|
+
this.restVersion = opts.restVersion || 'v4'
|
|
89
|
+
this.plugins = opts.plugins
|
|
90
|
+
this.autoResume = opts.autoResume
|
|
91
|
+
this.infiniteReconnects = opts.infiniteReconnects
|
|
92
|
+
this.urlFilteringEnabled = opts.urlFilteringEnabled
|
|
93
|
+
this.restrictedDomains = opts.restrictedDomains || []
|
|
94
|
+
this.allowedDomains = opts.allowedDomains || []
|
|
95
|
+
this.loadBalancer = opts.loadBalancer
|
|
96
|
+
this.send = opts.send || this._createDefaultSend()
|
|
101
97
|
|
|
102
98
|
this._nodeStates = new Map()
|
|
103
99
|
this._failoverQueue = new Map()
|
|
@@ -109,97 +105,70 @@ class Aqua extends EventEmitter {
|
|
|
109
105
|
this._nodeLoadCache = new Map()
|
|
110
106
|
this._nodeLoadCacheTime = new Map()
|
|
111
107
|
|
|
112
|
-
this.on('nodeReady', (
|
|
113
|
-
for (const player of this.players.values()) {
|
|
114
|
-
if (player.nodes === node && player.connection) {
|
|
115
|
-
player.connection.resendVoiceUpdate({ resume: !!resumed })
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
})
|
|
119
|
-
|
|
108
|
+
this.on('nodeReady', this._onNodeReady.bind(this))
|
|
120
109
|
this._bindEventHandlers()
|
|
121
110
|
this._startCleanupTimer()
|
|
122
111
|
}
|
|
123
112
|
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
for (const d of this.allowedDomains) {
|
|
130
|
-
if (!d) continue
|
|
131
|
-
if (d instanceof RegExp) allowedRegexes.push(d)
|
|
132
|
-
else allowedHostSuffixes.push(_normalizeHost(String(d)))
|
|
113
|
+
_onNodeReady(node, { resumed }) {
|
|
114
|
+
if (!resumed) return
|
|
115
|
+
const playersToUpdate = []
|
|
116
|
+
for (const player of this.players.values()) {
|
|
117
|
+
if (player.nodes === node && player.connection) playersToUpdate.push(player)
|
|
133
118
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
else restrictedHostSuffixes.push(_normalizeHost(String(d)))
|
|
119
|
+
if (playersToUpdate.length) {
|
|
120
|
+
queueMicrotask(() => {
|
|
121
|
+
for (const player of playersToUpdate) {
|
|
122
|
+
player.connection.resendVoiceUpdate({ resume: true })
|
|
123
|
+
}
|
|
124
|
+
})
|
|
141
125
|
}
|
|
142
|
-
|
|
143
|
-
this._domainLists = { allowedHostSuffixes, allowedRegexes, restrictedHostSuffixes, restrictedRegexes }
|
|
144
|
-
return this._domainLists
|
|
145
126
|
}
|
|
146
127
|
|
|
147
128
|
_createDefaultSend() {
|
|
148
129
|
return packet => {
|
|
149
130
|
const guildId = packet?.d?.guild_id
|
|
150
131
|
if (!guildId) return
|
|
151
|
-
const guild = this.client.cache?.
|
|
132
|
+
const guild = this.client.guilds?.cache?.get?.(guildId) || this.client.cache?.guilds?.get?.(guildId)
|
|
152
133
|
if (!guild) return
|
|
153
134
|
const gateway = this.client.gateway
|
|
154
|
-
if (gateway?.send)
|
|
155
|
-
|
|
156
|
-
} else if (guild.shard?.send) {
|
|
157
|
-
guild.shard.send(packet)
|
|
158
|
-
}
|
|
135
|
+
if (gateway?.send) gateway.send(gateway.calculateShardId(guildId), packet)
|
|
136
|
+
else if (guild.shard?.send) guild.shard.send(packet)
|
|
159
137
|
}
|
|
160
138
|
}
|
|
161
139
|
|
|
162
140
|
_bindEventHandlers() {
|
|
163
141
|
if (!this.autoResume) return
|
|
164
|
-
this._onNodeConnect = node =>
|
|
142
|
+
this._onNodeConnect = node => {
|
|
165
143
|
this._invalidateCache()
|
|
166
|
-
this._rebuildBrokenPlayers(node)
|
|
167
|
-
}
|
|
168
|
-
this._onNodeDisconnect = node =>
|
|
144
|
+
queueMicrotask(() => this._rebuildBrokenPlayers(node))
|
|
145
|
+
}
|
|
146
|
+
this._onNodeDisconnect = node => {
|
|
169
147
|
this._invalidateCache()
|
|
170
|
-
this._storeBrokenPlayers(node)
|
|
171
|
-
}
|
|
148
|
+
queueMicrotask(() => this._storeBrokenPlayers(node))
|
|
149
|
+
}
|
|
172
150
|
this.on('nodeConnect', this._onNodeConnect)
|
|
173
151
|
this.on('nodeDisconnect', this._onNodeDisconnect)
|
|
174
152
|
}
|
|
175
153
|
|
|
176
154
|
_startCleanupTimer() {
|
|
177
|
-
this._cleanupTimer = setInterval(() => this._performCleanup(), CLEANUP_INTERVAL)
|
|
178
|
-
if (this._cleanupTimer.unref) this._cleanupTimer.unref()
|
|
155
|
+
this._cleanupTimer = _unref(setInterval(() => this._performCleanup(), CLEANUP_INTERVAL))
|
|
179
156
|
}
|
|
180
157
|
|
|
181
158
|
get leastUsedNodes() {
|
|
182
159
|
const now = Date.now()
|
|
183
|
-
if (this._leastUsedNodesCache && (now - this._leastUsedNodesCacheTime) < CACHE_VALID_TIME) {
|
|
160
|
+
if (this._leastUsedNodesCache && (now - this._leastUsedNodesCacheTime) < CACHE_VALID_TIME) {
|
|
161
|
+
return this._leastUsedNodesCache
|
|
162
|
+
}
|
|
184
163
|
const connected = []
|
|
185
|
-
for (const node of this.nodeMap.values()) {
|
|
186
|
-
|
|
187
|
-
switch (this.options.loadBancer) {
|
|
188
|
-
case 'leastRest':
|
|
189
|
-
sorted = connected.slice().sort((a, b) => {
|
|
190
|
-
const restA = a?.rest?.calls || 0
|
|
191
|
-
const restB = b?.rest?.calls || 0
|
|
192
|
-
return restA - restB
|
|
193
|
-
})
|
|
194
|
-
break
|
|
195
|
-
case 'random':
|
|
196
|
-
sorted = Array.from(connected)
|
|
197
|
-
break
|
|
198
|
-
case 'leastLoad':
|
|
199
|
-
default:
|
|
200
|
-
sorted = connected.slice().sort((a, b) => this._getCachedNodeLoad(a) - this._getCachedNodeLoad(b))
|
|
201
|
-
break
|
|
164
|
+
for (const node of this.nodeMap.values()) {
|
|
165
|
+
if (node.connected) connected.push(node)
|
|
202
166
|
}
|
|
167
|
+
const sorted = this.loadBalancer === 'leastRest'
|
|
168
|
+
? connected.sort((a, b) => (a.rest?.calls || 0) - (b.rest?.calls || 0))
|
|
169
|
+
: this.loadBalancer === 'random'
|
|
170
|
+
? connected.slice()
|
|
171
|
+
: connected.sort((a, b) => this._getCachedNodeLoad(a) - this._getCachedNodeLoad(b))
|
|
203
172
|
this._leastUsedNodesCache = Object.freeze(sorted)
|
|
204
173
|
this._leastUsedNodesCacheTime = now
|
|
205
174
|
return this._leastUsedNodesCache
|
|
@@ -214,9 +183,7 @@ class Aqua extends EventEmitter {
|
|
|
214
183
|
const nodeId = node.name || node.host
|
|
215
184
|
const now = Date.now()
|
|
216
185
|
const cacheTime = this._nodeLoadCacheTime.get(nodeId)
|
|
217
|
-
if (cacheTime && (now - cacheTime) < 5000)
|
|
218
|
-
return this._nodeLoadCache.get(nodeId) || 0
|
|
219
|
-
}
|
|
186
|
+
if (cacheTime && (now - cacheTime) < 5000) return this._nodeLoadCache.get(nodeId) || 0
|
|
220
187
|
const load = this._calculateNodeLoad(node)
|
|
221
188
|
this._nodeLoadCache.set(nodeId, load)
|
|
222
189
|
this._nodeLoadCacheTime.set(nodeId, now)
|
|
@@ -227,27 +194,23 @@ class Aqua extends EventEmitter {
|
|
|
227
194
|
const stats = node?.stats
|
|
228
195
|
if (!stats) return 0
|
|
229
196
|
const cpu = stats.cpu
|
|
230
|
-
const
|
|
231
|
-
const cpuLoad = cpu ? (cpu.systemLoad / cores) : 0
|
|
197
|
+
const cpuLoad = cpu ? (cpu.systemLoad / Math.max(1, cpu.cores || 1)) : 0
|
|
232
198
|
const playing = stats.playingPlayers || 0
|
|
233
199
|
const memory = stats.memory
|
|
234
|
-
const
|
|
235
|
-
const restCalls = node
|
|
236
|
-
return (cpuLoad * 100) + (playing * 0.75) + (
|
|
200
|
+
const memUsage = memory ? (memory.used / Math.max(1, memory.reservable)) : 0
|
|
201
|
+
const restCalls = node.rest?.calls || 0
|
|
202
|
+
return (cpuLoad * 100) + (playing * 0.75) + (memUsage * 40) + (restCalls * 0.001)
|
|
237
203
|
}
|
|
238
204
|
|
|
239
205
|
async init(clientId) {
|
|
240
206
|
if (this.initiated) return this
|
|
241
207
|
this.clientId = clientId
|
|
242
208
|
if (!this.clientId) return
|
|
243
|
-
const
|
|
244
|
-
this.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
])
|
|
249
|
-
)
|
|
250
|
-
)
|
|
209
|
+
const nodePromises = this.nodes.map(n => Promise.race([
|
|
210
|
+
this._createNode(n),
|
|
211
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Node timeout')), NODE_TIMEOUT))
|
|
212
|
+
]))
|
|
213
|
+
const results = await Promise.allSettled(nodePromises)
|
|
251
214
|
const successCount = results.filter(r => r.status === 'fulfilled').length
|
|
252
215
|
if (!successCount) throw new Error('No nodes connected')
|
|
253
216
|
await this._loadPlugins()
|
|
@@ -257,15 +220,13 @@ class Aqua extends EventEmitter {
|
|
|
257
220
|
|
|
258
221
|
async _loadPlugins() {
|
|
259
222
|
if (!this.plugins?.length) return
|
|
260
|
-
await Promise.allSettled(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
})
|
|
268
|
-
)
|
|
223
|
+
await Promise.allSettled(this.plugins.map(async plugin => {
|
|
224
|
+
try {
|
|
225
|
+
await plugin.load(this)
|
|
226
|
+
} catch (err) {
|
|
227
|
+
this.emit('error', null, new Error(`Plugin error: ${err?.message || String(err)}`))
|
|
228
|
+
}
|
|
229
|
+
}))
|
|
269
230
|
}
|
|
270
231
|
|
|
271
232
|
async _createNode(options) {
|
|
@@ -290,7 +251,7 @@ class Aqua extends EventEmitter {
|
|
|
290
251
|
_destroyNode(identifier) {
|
|
291
252
|
const node = this.nodeMap.get(identifier)
|
|
292
253
|
if (node) {
|
|
293
|
-
try { node.destroy?.() } catch {
|
|
254
|
+
try { node.destroy?.() } catch {}
|
|
294
255
|
this._cleanupNode(identifier)
|
|
295
256
|
this.emit('nodeDestroy', node)
|
|
296
257
|
}
|
|
@@ -340,18 +301,14 @@ class Aqua extends EventEmitter {
|
|
|
340
301
|
const successes = []
|
|
341
302
|
for (const i of _range(0, rebuilds.length, batchSize)) {
|
|
342
303
|
const batch = rebuilds.slice(i, i + batchSize)
|
|
343
|
-
const results = await Promise.allSettled(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
)
|
|
347
|
-
)
|
|
304
|
+
const results = await Promise.allSettled(batch.map(({ guildId, brokenState }) =>
|
|
305
|
+
this._rebuildPlayer(brokenState, node).then(() => guildId)
|
|
306
|
+
))
|
|
348
307
|
for (const r of results) {
|
|
349
308
|
if (r.status === 'fulfilled') successes.push(r.value)
|
|
350
309
|
}
|
|
351
310
|
}
|
|
352
|
-
for (const guildId of successes)
|
|
353
|
-
this._brokenPlayers.delete(guildId)
|
|
354
|
-
}
|
|
311
|
+
for (const guildId of successes) this._brokenPlayers.delete(guildId)
|
|
355
312
|
if (successes.length) this.emit('playersRebuilt', node, successes.length)
|
|
356
313
|
}
|
|
357
314
|
|
|
@@ -370,12 +327,8 @@ class Aqua extends EventEmitter {
|
|
|
370
327
|
if (current && player?.queue?.add) {
|
|
371
328
|
player.queue.add(current)
|
|
372
329
|
await player.play()
|
|
373
|
-
if (brokenState.position > 0)
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
if (brokenState.paused) {
|
|
377
|
-
await player.pause(true)
|
|
378
|
-
}
|
|
330
|
+
if (brokenState.position > 0) setTimeout(() => player.seek?.(brokenState.position), SEEK_DELAY)
|
|
331
|
+
if (brokenState.paused) await player.pause(true)
|
|
379
332
|
}
|
|
380
333
|
return player
|
|
381
334
|
} finally {
|
|
@@ -407,9 +360,7 @@ class Aqua extends EventEmitter {
|
|
|
407
360
|
if (!availableNodes.length) throw new Error('No failover nodes available')
|
|
408
361
|
const results = await this._migratePlayersOptimized(affectedPlayers, availableNodes)
|
|
409
362
|
const successful = results.filter(r => r.success).length
|
|
410
|
-
if (successful)
|
|
411
|
-
this.emit('nodeFailoverComplete', failedNode, successful, results.length - successful)
|
|
412
|
-
}
|
|
363
|
+
if (successful) this.emit('nodeFailoverComplete', failedNode, successful, results.length - successful)
|
|
413
364
|
} catch (error) {
|
|
414
365
|
this.emit('error', null, new Error(`Failover failed: ${error?.message || String(error)}`))
|
|
415
366
|
} finally {
|
|
@@ -489,27 +440,16 @@ class Aqua extends EventEmitter {
|
|
|
489
440
|
async _restorePlayerState(newPlayer, playerState) {
|
|
490
441
|
const operations = []
|
|
491
442
|
if (typeof playerState.volume === 'number') {
|
|
492
|
-
if (typeof newPlayer.setVolume === 'function')
|
|
493
|
-
|
|
494
|
-
} else {
|
|
495
|
-
newPlayer.volume = playerState.volume
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
if (playerState.queue?.length && newPlayer.queue?.add) {
|
|
499
|
-
newPlayer.queue.add(...playerState.queue)
|
|
443
|
+
if (typeof newPlayer.setVolume === 'function') operations.push(newPlayer.setVolume(playerState.volume))
|
|
444
|
+
else newPlayer.volume = playerState.volume
|
|
500
445
|
}
|
|
446
|
+
if (playerState.queue?.length && newPlayer.queue?.add) newPlayer.queue.add(...playerState.queue)
|
|
501
447
|
if (playerState.current && this.failoverOptions.preservePosition) {
|
|
502
|
-
if (newPlayer.queue?.add) {
|
|
503
|
-
newPlayer.queue.add(playerState.current, { toFront: true })
|
|
504
|
-
}
|
|
448
|
+
if (newPlayer.queue?.add) newPlayer.queue.add(playerState.current, { toFront: true })
|
|
505
449
|
if (this.failoverOptions.resumePlayback) {
|
|
506
450
|
operations.push(newPlayer.play())
|
|
507
|
-
if (playerState.position > 0)
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
if (playerState.paused) {
|
|
511
|
-
operations.push(newPlayer.pause(true))
|
|
512
|
-
}
|
|
451
|
+
if (playerState.position > 0) setTimeout(() => newPlayer.seek?.(playerState.position), SEEK_DELAY)
|
|
452
|
+
if (playerState.paused) operations.push(newPlayer.pause(true))
|
|
513
453
|
}
|
|
514
454
|
}
|
|
515
455
|
Object.assign(newPlayer, { repeat: playerState.repeat, shuffle: playerState.shuffle })
|
|
@@ -550,7 +490,7 @@ class Aqua extends EventEmitter {
|
|
|
550
490
|
const existing = this.players.get(options.guildId)
|
|
551
491
|
if (existing) {
|
|
552
492
|
if (options.voiceChannel && existing.voiceChannel !== options.voiceChannel) {
|
|
553
|
-
try { existing.connect(options) } catch {
|
|
493
|
+
try { existing.connect(options) } catch {}
|
|
554
494
|
}
|
|
555
495
|
return existing
|
|
556
496
|
}
|
|
@@ -563,9 +503,7 @@ class Aqua extends EventEmitter {
|
|
|
563
503
|
|
|
564
504
|
createPlayer(node, options) {
|
|
565
505
|
const existing = this.players.get(options.guildId)
|
|
566
|
-
if (existing) {
|
|
567
|
-
try { existing.destroy?.() } catch { }
|
|
568
|
-
}
|
|
506
|
+
if (existing) try { existing.destroy?.() } catch {}
|
|
569
507
|
const player = new Player(this, node, options)
|
|
570
508
|
this.players.set(options.guildId, player)
|
|
571
509
|
node?.players?.add?.(player)
|
|
@@ -578,9 +516,7 @@ class Aqua extends EventEmitter {
|
|
|
578
516
|
_handlePlayerDestroy(player) {
|
|
579
517
|
const node = player.nodes
|
|
580
518
|
node?.players?.delete?.(player)
|
|
581
|
-
if (this.players.get(player.guildId) === player)
|
|
582
|
-
this.players.delete(player.guildId)
|
|
583
|
-
}
|
|
519
|
+
if (this.players.get(player.guildId) === player) this.players.delete(player.guildId)
|
|
584
520
|
this.emit('playerDestroy', player)
|
|
585
521
|
}
|
|
586
522
|
|
|
@@ -591,89 +527,18 @@ class Aqua extends EventEmitter {
|
|
|
591
527
|
this.players.delete(guildId)
|
|
592
528
|
player.removeAllListeners?.()
|
|
593
529
|
await player.destroy?.()
|
|
594
|
-
} finally {
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
_isUrlAllowed(url) {
|
|
598
|
-
if (!this.urlFilteringEnabled || !_isProbablyUrl(url)) return true
|
|
599
|
-
const hostname = _safeGetHostname(url)
|
|
600
|
-
if (!hostname) return false
|
|
601
|
-
const { allowedHostSuffixes, allowedRegexes, restrictedHostSuffixes, restrictedRegexes } = this._getDomainLists()
|
|
602
|
-
if (allowedHostSuffixes.length || allowedRegexes.length) {
|
|
603
|
-
for (const rx of allowedRegexes) {
|
|
604
|
-
if (rx.test(hostname)) return true
|
|
605
|
-
}
|
|
606
|
-
for (const suffix of allowedHostSuffixes) {
|
|
607
|
-
if (_hostMatchesSuffix(hostname, suffix)) return true
|
|
608
|
-
}
|
|
609
|
-
return false
|
|
610
|
-
}
|
|
611
|
-
if (restrictedHostSuffixes.length || restrictedRegexes.length) {
|
|
612
|
-
for (const rx of restrictedRegexes) {
|
|
613
|
-
if (rx.test(hostname)) return false
|
|
614
|
-
}
|
|
615
|
-
for (const suffix of restrictedHostSuffixes) {
|
|
616
|
-
if (_hostMatchesSuffix(hostname, suffix)) return false
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
return true
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
_resolveSearchPlatform(source) {
|
|
623
|
-
if (!source) return this.defaultSearchPlatform
|
|
624
|
-
const normalized = source.toLowerCase().trim()
|
|
625
|
-
return SearchPlatforms.SEARCH_PLATFORMS[normalized] || source
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
_validateSourceRegex(urlStr) {
|
|
629
|
-
if (!_isProbablyUrl(urlStr)) return null
|
|
630
|
-
const parsed = _safeParseUrl(urlStr)
|
|
631
|
-
if (!parsed) return null
|
|
632
|
-
const { host, path } = parsed
|
|
633
|
-
if (/\.(mp3|m3u8?|mp4|m4a|wav|aacp)(?:$|\?)/i.test(path)) return 'http'
|
|
634
|
-
const hostMapping = {
|
|
635
|
-
'music.youtube.com': 'ytmsearch',
|
|
636
|
-
'youtu.be': 'ytsearch',
|
|
637
|
-
'youtube.com': 'ytsearch',
|
|
638
|
-
'soundcloud.com': 'scsearch',
|
|
639
|
-
'soundcloud.app.goo.gl': 'scsearch',
|
|
640
|
-
'open.spotify.com': 'spsearch',
|
|
641
|
-
'deezer.com': 'dzsearch',
|
|
642
|
-
'deezer.page.link': 'dzsearch',
|
|
643
|
-
'music.apple.com': 'amsearch',
|
|
644
|
-
'tidal.com': 'tdsearch',
|
|
645
|
-
'listen.tidal.com': 'tdsearch',
|
|
646
|
-
'jiosaavn.com': 'jssearch',
|
|
647
|
-
'music.yandex.ru': 'ymsearch',
|
|
648
|
-
'bandcamp.com': 'bcsearch'
|
|
649
|
-
}
|
|
650
|
-
for (const [hostSuffix, platform] of Object.entries(hostMapping)) {
|
|
651
|
-
if (host === hostSuffix || _hostMatchesSuffix(host, hostSuffix)) return platform
|
|
652
|
-
}
|
|
653
|
-
const linkHosts = ['twitch.tv', 'vimeo.com', 'tiktok.com', 'mixcloud.com', 'radiohost.de']
|
|
654
|
-
for (const linkHost of linkHosts) {
|
|
655
|
-
if (_hostMatchesSuffix(host, linkHost)) return 'link'
|
|
656
|
-
}
|
|
657
|
-
return null
|
|
530
|
+
} finally {}
|
|
658
531
|
}
|
|
659
532
|
|
|
660
533
|
async resolve({ query, source = this.defaultSearchPlatform, requester, nodes }) {
|
|
661
534
|
if (!this.initiated) throw new Error('Aqua not initialized')
|
|
662
535
|
const requestNode = this._getRequestNode(nodes)
|
|
663
536
|
if (!requestNode) throw new Error('No nodes available')
|
|
664
|
-
if (this.restrictedDomains.length > 0) {
|
|
665
|
-
if (_isProbablyUrl(query) && !this._isUrlAllowed(query)) {
|
|
666
|
-
this.emit('debug', `Blocked URL by domain restrictions: ${query}`)
|
|
667
|
-
return EMPTY_TRACKS_RESPONSE
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
537
|
const formattedQuery = _isProbablyUrl(query) ? query : `${source}${SEARCH_PREFIX}${query}`
|
|
671
538
|
try {
|
|
672
539
|
const endpoint = `/${this.restVersion}/loadtracks?identifier=${encodeURIComponent(formattedQuery)}`
|
|
673
540
|
const response = await requestNode.rest.makeRequest('GET', endpoint)
|
|
674
|
-
if (!response || response.loadType === 'empty' || response.loadType === 'NO_MATCHES')
|
|
675
|
-
return EMPTY_TRACKS_RESPONSE
|
|
676
|
-
}
|
|
541
|
+
if (!response || response.loadType === 'empty' || response.loadType === 'NO_MATCHES') return EMPTY_TRACKS_RESPONSE
|
|
677
542
|
return this._constructResponse(response, requester, requestNode)
|
|
678
543
|
} catch (error) {
|
|
679
544
|
throw new Error(error?.name === 'AbortError' ? 'Request timeout' : `Resolve failed: ${error?.message || String(error)}`)
|
|
@@ -714,42 +579,31 @@ class Aqua extends EventEmitter {
|
|
|
714
579
|
|
|
715
580
|
_constructResponse(response, requester, requestNode) {
|
|
716
581
|
const { loadType, data, pluginInfo: rootPluginInfo } = response || {}
|
|
717
|
-
const baseResponse = {
|
|
718
|
-
loadType,
|
|
719
|
-
exception: null,
|
|
720
|
-
playlistInfo: null,
|
|
721
|
-
pluginInfo: rootPluginInfo ?? {},
|
|
722
|
-
tracks: []
|
|
723
|
-
}
|
|
582
|
+
const baseResponse = { loadType, exception: null, playlistInfo: null, pluginInfo: rootPluginInfo || {}, tracks: [] }
|
|
724
583
|
if (loadType === 'error' || loadType === 'LOAD_FAILED') {
|
|
725
|
-
baseResponse.exception = data
|
|
584
|
+
baseResponse.exception = data || response.exception || null
|
|
726
585
|
return baseResponse
|
|
727
586
|
}
|
|
728
587
|
const makeTrack = t => new Track(t, requester, requestNode)
|
|
729
588
|
switch (loadType) {
|
|
730
|
-
case 'track':
|
|
589
|
+
case 'track':
|
|
731
590
|
if (data) {
|
|
732
|
-
baseResponse.pluginInfo = data.info?.pluginInfo
|
|
591
|
+
baseResponse.pluginInfo = data.info?.pluginInfo || data.pluginInfo || baseResponse.pluginInfo
|
|
733
592
|
baseResponse.tracks.push(makeTrack(data))
|
|
734
593
|
}
|
|
735
594
|
break
|
|
736
|
-
|
|
737
|
-
case 'playlist': {
|
|
595
|
+
case 'playlist':
|
|
738
596
|
if (data) {
|
|
739
|
-
const info = data.info
|
|
597
|
+
const info = data.info || null
|
|
740
598
|
const thumbnail = data.pluginInfo?.artworkUrl || data.tracks?.[0]?.info?.artworkUrl || null
|
|
741
|
-
if (info) {
|
|
742
|
-
|
|
743
|
-
}
|
|
744
|
-
baseResponse.pluginInfo = data.pluginInfo ?? baseResponse.pluginInfo
|
|
599
|
+
if (info) baseResponse.playlistInfo = { name: info.name || info.title, thumbnail, ...info }
|
|
600
|
+
baseResponse.pluginInfo = data.pluginInfo || baseResponse.pluginInfo
|
|
745
601
|
baseResponse.tracks = Array.isArray(data.tracks) ? data.tracks.map(makeTrack) : []
|
|
746
602
|
}
|
|
747
603
|
break
|
|
748
|
-
|
|
749
|
-
case 'search': {
|
|
604
|
+
case 'search':
|
|
750
605
|
baseResponse.tracks = Array.isArray(data) ? data.map(makeTrack) : []
|
|
751
606
|
break
|
|
752
|
-
}
|
|
753
607
|
}
|
|
754
608
|
return baseResponse
|
|
755
609
|
}
|
|
@@ -788,14 +642,12 @@ class Aqua extends EventEmitter {
|
|
|
788
642
|
batch.length = 0
|
|
789
643
|
}
|
|
790
644
|
}
|
|
791
|
-
if (batch.length)
|
|
792
|
-
await Promise.allSettled(batch.map(p => this._restorePlayer(p)))
|
|
793
|
-
}
|
|
645
|
+
if (batch.length) await Promise.allSettled(batch.map(p => this._restorePlayer(p)))
|
|
794
646
|
await fs.promises.writeFile(filePath, '')
|
|
795
647
|
} catch (error) {
|
|
796
648
|
this.emit('debug', 'Aqua', `Load players error: ${error?.message || String(error)}`)
|
|
797
649
|
} finally {
|
|
798
|
-
await fs.promises.unlink(lockFile).catch(() => {
|
|
650
|
+
await fs.promises.unlink(lockFile).catch(() => {})
|
|
799
651
|
}
|
|
800
652
|
}
|
|
801
653
|
|
|
@@ -834,7 +686,7 @@ class Aqua extends EventEmitter {
|
|
|
834
686
|
} catch (error) {
|
|
835
687
|
this.emit('error', null, new Error(`Save players failed: ${error?.message || String(error)}`))
|
|
836
688
|
} finally {
|
|
837
|
-
await fs.promises.unlink(lockFile).catch(() => {
|
|
689
|
+
await fs.promises.unlink(lockFile).catch(() => {})
|
|
838
690
|
}
|
|
839
691
|
}
|
|
840
692
|
|
|
@@ -857,11 +709,8 @@ class Aqua extends EventEmitter {
|
|
|
857
709
|
}
|
|
858
710
|
if (p.u && validTracks[0]) {
|
|
859
711
|
if (p.vol != null) {
|
|
860
|
-
if (typeof player.setVolume === 'function')
|
|
861
|
-
|
|
862
|
-
} else {
|
|
863
|
-
player.volume = p.vol
|
|
864
|
-
}
|
|
712
|
+
if (typeof player.setVolume === 'function') await player.setVolume(p.vol)
|
|
713
|
+
else player.volume = p.vol
|
|
865
714
|
}
|
|
866
715
|
await player.play()
|
|
867
716
|
if (p.p > 0) setTimeout(() => player.seek?.(p.p), SEEK_DELAY)
|
|
@@ -870,9 +719,7 @@ class Aqua extends EventEmitter {
|
|
|
870
719
|
if (p.nw && p.t) {
|
|
871
720
|
const channel = this.client.channels?.cache?.get(p.t)
|
|
872
721
|
if (channel?.messages) {
|
|
873
|
-
try {
|
|
874
|
-
player.nowPlayingMessage = await channel.messages.fetch(p.nw).catch(() => null)
|
|
875
|
-
} catch { }
|
|
722
|
+
try { player.nowPlayingMessage = await channel.messages.fetch(p.nw).catch(() => null) } catch {}
|
|
876
723
|
}
|
|
877
724
|
}
|
|
878
725
|
} catch (error) {
|
|
@@ -915,18 +762,18 @@ class Aqua extends EventEmitter {
|
|
|
915
762
|
|
|
916
763
|
_performCleanup() {
|
|
917
764
|
const now = Date.now()
|
|
918
|
-
const
|
|
765
|
+
const expiredPlayers = []
|
|
919
766
|
for (const [guildId, state] of this._brokenPlayers) {
|
|
920
|
-
if (now - state.brokenAt > BROKEN_PLAYER_TTL)
|
|
767
|
+
if (now - state.brokenAt > BROKEN_PLAYER_TTL) expiredPlayers.push(guildId)
|
|
921
768
|
}
|
|
922
|
-
for (const
|
|
769
|
+
for (const guildId of expiredPlayers) this._brokenPlayers.delete(guildId)
|
|
923
770
|
const expiredNodes = []
|
|
924
771
|
for (const [nodeId, ts] of this._lastFailoverAttempt) {
|
|
925
772
|
if (now - ts > FAILOVER_CLEANUP_TTL) expiredNodes.push(nodeId)
|
|
926
773
|
}
|
|
927
|
-
for (const
|
|
928
|
-
this._lastFailoverAttempt.delete(
|
|
929
|
-
this._failoverQueue.delete(
|
|
774
|
+
for (const nodeId of expiredNodes) {
|
|
775
|
+
this._lastFailoverAttempt.delete(nodeId)
|
|
776
|
+
this._failoverQueue.delete(nodeId)
|
|
930
777
|
}
|
|
931
778
|
if (this._nodeLoadCache.size > 50) {
|
|
932
779
|
this._nodeLoadCache.clear()
|
|
@@ -947,13 +794,13 @@ class Aqua extends EventEmitter {
|
|
|
947
794
|
this.off('nodeConnect', this._onNodeConnect)
|
|
948
795
|
this.off('nodeDisconnect', this._onNodeDisconnect)
|
|
949
796
|
}
|
|
950
|
-
const
|
|
797
|
+
const destroyTasks = []
|
|
951
798
|
for (const player of this.players.values()) {
|
|
952
799
|
player.removeAllListeners?.()
|
|
953
|
-
|
|
800
|
+
if (player.destroy) destroyTasks.push(Promise.resolve(player.destroy()).catch(() => {}))
|
|
954
801
|
}
|
|
955
802
|
for (const node of this.nodeMap.values()) {
|
|
956
|
-
|
|
803
|
+
if (node.destroy) destroyTasks.push(Promise.resolve(node.destroy()).catch(() => {}))
|
|
957
804
|
}
|
|
958
805
|
this.players.clear()
|
|
959
806
|
this.nodeMap.clear()
|
|
@@ -965,30 +812,7 @@ class Aqua extends EventEmitter {
|
|
|
965
812
|
this._nodeLoadCacheTime.clear()
|
|
966
813
|
this._leastUsedNodesCache = null
|
|
967
814
|
this.removeAllListeners()
|
|
968
|
-
return Promise.all(
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
const _range = function* (start, end, step = 1) {
|
|
973
|
-
for (let i = start; i < end; i += step) {
|
|
974
|
-
yield i
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
const _safeGetHostname = (url) => {
|
|
979
|
-
try {
|
|
980
|
-
return _normalizeHost(new URL(url).hostname)
|
|
981
|
-
} catch {
|
|
982
|
-
return null
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
const _safeParseUrl = (url) => {
|
|
987
|
-
try {
|
|
988
|
-
const u = new URL(url)
|
|
989
|
-
return { host: _normalizeHost(u.hostname), path: u.pathname.toLowerCase() }
|
|
990
|
-
} catch {
|
|
991
|
-
return null
|
|
815
|
+
return Promise.all(destroyTasks)
|
|
992
816
|
}
|
|
993
817
|
}
|
|
994
818
|
|