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.
@@ -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
- urlFilteringEnabled: false,
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 _normalizeHost = (h) => {
57
- if (!h) return ''
58
- h = h.toLowerCase()
59
- if (h.startsWith('www.')) h = h.slice(4)
60
- return h.endsWith('.') ? h.slice(0, -1) : h
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 _hostMatchesSuffix = (host, suffix) => {
64
- host = _normalizeHost(host)
65
- suffix = _normalizeHost(suffix)
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 = Object.assign({}, DEFAULT_OPTIONS, options)
88
- this.failoverOptions = Object.assign({}, DEFAULT_OPTIONS.failoverOptions, options.failoverOptions)
89
- this.shouldDeleteMessage = this.options.shouldDeleteMessage
90
- this.defaultSearchPlatform = this.options.defaultSearchPlatform
91
- this.leaveOnEnd = this.options.leaveOnEnd
92
- this.restVersion = this.options.restVersion || 'v4'
93
- this.plugins = this.options.plugins
94
- this.autoResume = this.options.autoResume
95
- this.infiniteReconnects = this.options.infiniteReconnects
96
- this.urlFilteringEnabled = this.options.urlFilteringEnabled
97
- this.restrictedDomains = this.options.restrictedDomains || []
98
- this.allowedDomains = this.options.allowedDomains || []
99
- this.loadBalancer = this.options.loadBalancer
100
- this.send = this.options.send || this._createDefaultSend()
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', (node, { resumed }) => {
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
- _getDomainLists() {
125
- if (this._domainLists) return this._domainLists
126
-
127
- const allowedHostSuffixes = []
128
- const allowedRegexes = []
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
- const restrictedHostSuffixes = []
136
- const restrictedRegexes = []
137
- for (const d of this.restrictedDomains) {
138
- if (!d) continue
139
- if (d instanceof RegExp) restrictedRegexes.push(d)
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?.guilds?.get?.(guildId) ?? this.client.guilds?.cache?.get?.(guildId)
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
- gateway.send(gateway.calculateShardId(guildId), packet)
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 => queueMicrotask(() => {
142
+ this._onNodeConnect = node => {
165
143
  this._invalidateCache()
166
- this._rebuildBrokenPlayers(node)
167
- })
168
- this._onNodeDisconnect = node => queueMicrotask(() => {
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) { return this._leastUsedNodesCache }
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()) { if (node.connected) connected.push(node) }
186
- let sorted
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 cores = Math.max(1, cpu?.cores || 1)
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 memoryUsage = memory ? (memory.used / Math.max(1, memory.reservable)) : 0
235
- const restCalls = node?.rest?.calls || 0
236
- return (cpuLoad * 100) + (playing * 0.75) + (memoryUsage * 40) + (restCalls * 0.001)
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 results = await Promise.allSettled(
244
- this.nodes.map(n =>
245
- Promise.race([
246
- this._createNode(n),
247
- new Promise((_, reject) => setTimeout(() => reject(new Error('Node timeout')), NODE_TIMEOUT))
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
- this.plugins.map(async plugin => {
262
- try {
263
- await plugin.load(this)
264
- } catch (err) {
265
- this.emit('error', null, new Error(`Plugin error: ${err?.message || String(err)}`))
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
- batch.map(({ guildId, brokenState }) =>
345
- this._rebuildPlayer(brokenState, node).then(() => guildId)
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
- setTimeout(() => player.seek?.(brokenState.position), SEEK_DELAY)
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
- operations.push(newPlayer.setVolume(playerState.volume))
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
- setTimeout(() => newPlayer.seek?.(playerState.position), SEEK_DELAY)
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 ?? response.exception ?? null
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 ?? data.pluginInfo ?? baseResponse.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 ?? null
597
+ const info = data.info || null
740
598
  const thumbnail = data.pluginInfo?.artworkUrl || data.tracks?.[0]?.info?.artworkUrl || null
741
- if (info) {
742
- baseResponse.playlistInfo = { name: info.name || info.title, thumbnail, ...info }
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
- await player.setVolume(p.vol)
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 expiredGuilds = []
765
+ const expiredPlayers = []
919
766
  for (const [guildId, state] of this._brokenPlayers) {
920
- if (now - state.brokenAt > BROKEN_PLAYER_TTL) expiredGuilds.push(guildId)
767
+ if (now - state.brokenAt > BROKEN_PLAYER_TTL) expiredPlayers.push(guildId)
921
768
  }
922
- for (const g of expiredGuilds) this._brokenPlayers.delete(g)
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 n of expiredNodes) {
928
- this._lastFailoverAttempt.delete(n)
929
- this._failoverQueue.delete(n)
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 tasks = []
797
+ const destroyTasks = []
951
798
  for (const player of this.players.values()) {
952
799
  player.removeAllListeners?.()
953
- tasks.push(Promise.resolve(player.destroy?.()).catch(() => { }))
800
+ if (player.destroy) destroyTasks.push(Promise.resolve(player.destroy()).catch(() => {}))
954
801
  }
955
802
  for (const node of this.nodeMap.values()) {
956
- tasks.push(Promise.resolve(node.destroy?.()).catch(() => { }))
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(tasks)
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