aqualink 2.17.3 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/structures/Aqua.js +23 -9
- package/build/structures/Connection.js +12 -9
- package/build/structures/Filters.js +35 -5
- package/build/structures/Node.js +105 -37
- package/build/structures/Player.js +45 -72
- package/build/structures/Queue.js +42 -21
- package/build/structures/Rest.js +117 -45
- package/build/structures/Track.js +5 -1
- package/package.json +4 -4
package/build/structures/Aqua.js
CHANGED
|
@@ -52,7 +52,9 @@ const DEFAULT_OPTIONS = Object.freeze({
|
|
|
52
52
|
resumePlayback: true,
|
|
53
53
|
cooldownTime: 5000,
|
|
54
54
|
maxFailoverAttempts: 5
|
|
55
|
-
})
|
|
55
|
+
}),
|
|
56
|
+
maxQueueSave: 10,
|
|
57
|
+
maxTracksRestore: 20
|
|
56
58
|
})
|
|
57
59
|
|
|
58
60
|
const _functions = {
|
|
@@ -118,6 +120,8 @@ class Aqua extends EventEmitter {
|
|
|
118
120
|
this.allowedDomains = merged.allowedDomains || []
|
|
119
121
|
this.loadBalancer = merged.loadBalancer
|
|
120
122
|
this.useHttp2 = merged.useHttp2
|
|
123
|
+
this.maxQueueSave = merged.maxQueueSave
|
|
124
|
+
this.maxTracksRestore = merged.maxTracksRestore
|
|
121
125
|
this.send = merged.send || this._createDefaultSend()
|
|
122
126
|
|
|
123
127
|
this._nodeStates = new Map()
|
|
@@ -489,13 +493,18 @@ class Aqua extends EventEmitter {
|
|
|
489
493
|
updateVoiceState({ d, t }) {
|
|
490
494
|
if (!d?.guild_id || (t !== 'VOICE_STATE_UPDATE' && t !== 'VOICE_SERVER_UPDATE')) return
|
|
491
495
|
const player = this.players.get(d.guild_id)
|
|
492
|
-
if (!player
|
|
496
|
+
if (!player) return
|
|
497
|
+
|
|
498
|
+
d.txId = player.txId
|
|
493
499
|
if (t === 'VOICE_STATE_UPDATE') {
|
|
494
500
|
if (d.user_id !== this.clientId) return
|
|
495
|
-
if (!d.channel_id) return void this.destroyPlayer(d.guild_id)
|
|
496
501
|
if (player.connection) {
|
|
497
|
-
player.connection.
|
|
498
|
-
|
|
502
|
+
if (!d.channel_id && player.connection.voiceChannel) {
|
|
503
|
+
player.connection.setStateUpdate(d)
|
|
504
|
+
} else {
|
|
505
|
+
player.connection.sessionId = d.session_id
|
|
506
|
+
player.connection.setStateUpdate(d)
|
|
507
|
+
}
|
|
499
508
|
}
|
|
500
509
|
} else {
|
|
501
510
|
player.connection?.setServerUpdate(d)
|
|
@@ -515,7 +524,7 @@ class Aqua extends EventEmitter {
|
|
|
515
524
|
createConnection(options) {
|
|
516
525
|
if (!this.initiated) throw new Error('Aqua not initialized')
|
|
517
526
|
const existing = this.players.get(options.guildId)
|
|
518
|
-
if (existing) {
|
|
527
|
+
if (existing && !existing.destroyed) {
|
|
519
528
|
if (options.voiceChannel && existing.voiceChannel !== options.voiceChannel) {
|
|
520
529
|
_functions.safeCall(() => existing.connect(options))
|
|
521
530
|
}
|
|
@@ -528,7 +537,12 @@ class Aqua extends EventEmitter {
|
|
|
528
537
|
|
|
529
538
|
createPlayer(node, options) {
|
|
530
539
|
const existing = this.players.get(options.guildId)
|
|
531
|
-
if (existing)
|
|
540
|
+
if (existing) {
|
|
541
|
+
_functions.safeCall(() => existing.destroy({
|
|
542
|
+
preserveMessage: options.preserveMessage || !!options.resuming || false,
|
|
543
|
+
preserveTracks: !!options.resuming || false
|
|
544
|
+
}))
|
|
545
|
+
}
|
|
532
546
|
const player = new Player(this, node, options)
|
|
533
547
|
this.players.set(options.guildId, player)
|
|
534
548
|
node?.players?.add?.(player)
|
|
@@ -654,7 +668,7 @@ class Aqua extends EventEmitter {
|
|
|
654
668
|
u: player.current?.uri || null,
|
|
655
669
|
p: player.position || 0,
|
|
656
670
|
ts: player.timestamp || 0,
|
|
657
|
-
q: player.queue.slice(0,
|
|
671
|
+
q: player.queue.slice(0, this.maxQueueSave).map(tr => tr.uri),
|
|
658
672
|
r: requester ? `${requester.id}:${requester.username}` : null,
|
|
659
673
|
vol: player.volume,
|
|
660
674
|
pa: player.paused,
|
|
@@ -730,7 +744,7 @@ class Aqua extends EventEmitter {
|
|
|
730
744
|
})
|
|
731
745
|
player._resuming = !!p.resuming
|
|
732
746
|
const requester = _functions.parseRequester(p.r)
|
|
733
|
-
const tracksToResolve = [p.u, ...(p.q || [])].filter(Boolean).slice(0,
|
|
747
|
+
const tracksToResolve = [p.u, ...(p.q || [])].filter(Boolean).slice(0, this.maxTracksRestore)
|
|
734
748
|
const resolved = await Promise.all(tracksToResolve.map(uri => this.resolve({ query: uri, requester }).catch(() => null)))
|
|
735
749
|
const validTracks = resolved.flatMap(r => r?.tracks || [])
|
|
736
750
|
if (validTracks.length && player.queue?.add) {
|
|
@@ -122,6 +122,7 @@ class Connection {
|
|
|
122
122
|
this.token = null
|
|
123
123
|
this.region = null
|
|
124
124
|
this.sequence = 0
|
|
125
|
+
this.txId = 0
|
|
125
126
|
|
|
126
127
|
this._lastEndpoint = null
|
|
127
128
|
this._stateFlags = 0
|
|
@@ -136,6 +137,7 @@ class Connection {
|
|
|
136
137
|
this._lastSentVoiceKey = ''
|
|
137
138
|
|
|
138
139
|
this._nullChannelTimer = null
|
|
140
|
+
this.isWaitingForDisconnect = false
|
|
139
141
|
|
|
140
142
|
this._lastStateReqAt = 0
|
|
141
143
|
this._stateGeneration = 0
|
|
@@ -178,6 +180,8 @@ class Connection {
|
|
|
178
180
|
|
|
179
181
|
if (this._lastEndpoint === endpoint && this.token === data.token) return
|
|
180
182
|
|
|
183
|
+
if (data.txId && data.txId < this.txId) return
|
|
184
|
+
|
|
181
185
|
this._stateGeneration++
|
|
182
186
|
|
|
183
187
|
if (this._lastEndpoint !== endpoint) {
|
|
@@ -212,12 +216,10 @@ class Connection {
|
|
|
212
216
|
|
|
213
217
|
if (channelId) this._clearNullChannelTimer()
|
|
214
218
|
|
|
215
|
-
|
|
216
|
-
const reqFresh = !!(reqCh && (Date.now() - (p._voiceRequestAt || 0)) < 5000)
|
|
219
|
+
if (data.txId && data.txId < this.txId) return
|
|
217
220
|
|
|
218
221
|
if (!channelId) {
|
|
219
|
-
|
|
220
|
-
|
|
222
|
+
this.isWaitingForDisconnect = true
|
|
221
223
|
if (!this._nullChannelTimer) {
|
|
222
224
|
this._nullChannelTimer = setTimeout(() => {
|
|
223
225
|
this._nullChannelTimer = null
|
|
@@ -228,16 +230,16 @@ class Connection {
|
|
|
228
230
|
return
|
|
229
231
|
}
|
|
230
232
|
|
|
231
|
-
|
|
233
|
+
this.isWaitingForDisconnect = false
|
|
232
234
|
|
|
233
|
-
if (
|
|
234
|
-
p._voiceRequestChannel = null
|
|
235
|
-
}
|
|
235
|
+
if (p && p.txId > this.txId) this.txId = p.txId
|
|
236
236
|
|
|
237
237
|
let needsUpdate = false
|
|
238
238
|
|
|
239
239
|
if (this.voiceChannel !== channelId) {
|
|
240
|
-
|
|
240
|
+
p._reconnecting = true
|
|
241
|
+
p._resuming = true
|
|
242
|
+
this._aqua.emit(AqualinkEvents.PlayerMove, p, this.voiceChannel, channelId)
|
|
241
243
|
this.voiceChannel = channelId
|
|
242
244
|
p.voiceChannel = channelId
|
|
243
245
|
needsUpdate = true
|
|
@@ -313,6 +315,7 @@ class Connection {
|
|
|
313
315
|
return false
|
|
314
316
|
}
|
|
315
317
|
|
|
318
|
+
this.txId = this._player.txId || this.txId
|
|
316
319
|
this._stateFlags |= STATE.ATTEMPTING_RESUME
|
|
317
320
|
this._reconnectAttempts++
|
|
318
321
|
this._aqua.emit(AqualinkEvents.Debug, `Attempt resume: guild=${this._guildId} endpoint=${this.endpoint} session=${this.sessionId}`)
|
|
@@ -60,6 +60,7 @@ class Filters {
|
|
|
60
60
|
if (!player) throw new Error('Player instance is required')
|
|
61
61
|
this.player = player
|
|
62
62
|
this._pendingUpdate = false
|
|
63
|
+
this._dirty = new Set()
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
this.filters = {
|
|
@@ -102,6 +103,7 @@ class Filters {
|
|
|
102
103
|
if (current && _utils.shallowEqual(current, defaults, options, keys)) return this
|
|
103
104
|
|
|
104
105
|
this.filters[filterName] = Object.assign({}, defaults, options)
|
|
106
|
+
this._dirty.add(filterName)
|
|
105
107
|
return this._scheduleUpdate()
|
|
106
108
|
}
|
|
107
109
|
|
|
@@ -122,6 +124,7 @@ class Filters {
|
|
|
122
124
|
const next = bands ?? EMPTY_ARRAY
|
|
123
125
|
if (_utils.equalizerEqual(this.filters.equalizer, next)) return this
|
|
124
126
|
this.filters.equalizer = next
|
|
127
|
+
this._dirty.add('equalizer')
|
|
125
128
|
return this._scheduleUpdate()
|
|
126
129
|
}
|
|
127
130
|
|
|
@@ -147,7 +150,17 @@ class Filters {
|
|
|
147
150
|
|
|
148
151
|
this.presets.bassboost = value
|
|
149
152
|
const gain = (value - 1) * (1.25 / 9) - 0.25
|
|
150
|
-
|
|
153
|
+
|
|
154
|
+
const current = Array.isArray(this.filters.equalizer) ? [...this.filters.equalizer] : []
|
|
155
|
+
const bands = _utils.makeEqArray(13, gain)
|
|
156
|
+
|
|
157
|
+
for (const b of bands) {
|
|
158
|
+
const idx = current.findIndex(e => e.band === b.band)
|
|
159
|
+
if (idx !== -1) current[idx] = b
|
|
160
|
+
else current.push(b)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return this.setEqualizer(current)
|
|
151
164
|
}
|
|
152
165
|
|
|
153
166
|
setSlowmode(enabled, options = {}) {
|
|
@@ -182,14 +195,23 @@ class Filters {
|
|
|
182
195
|
const f = this.filters
|
|
183
196
|
let changed = false
|
|
184
197
|
|
|
185
|
-
if (f.volume !== 1) {
|
|
186
|
-
|
|
198
|
+
if (f.volume !== 1) {
|
|
199
|
+
f.volume = 1
|
|
200
|
+
this._dirty.add('volume')
|
|
201
|
+
changed = true
|
|
202
|
+
}
|
|
203
|
+
if (!_utils.eqIsEmpty(f.equalizer)) {
|
|
204
|
+
f.equalizer = EMPTY_ARRAY
|
|
205
|
+
this._dirty.add('equalizer')
|
|
206
|
+
changed = true
|
|
207
|
+
}
|
|
187
208
|
|
|
188
209
|
const filterNames = Object.keys(FILTER_DEFAULTS)
|
|
189
210
|
for (let i = 0; i < filterNames.length; i++) {
|
|
190
211
|
const key = filterNames[i]
|
|
191
212
|
if (f[key] !== null) {
|
|
192
213
|
f[key] = null
|
|
214
|
+
this._dirty.add(key)
|
|
193
215
|
changed = true
|
|
194
216
|
}
|
|
195
217
|
}
|
|
@@ -202,10 +224,18 @@ class Filters {
|
|
|
202
224
|
}
|
|
203
225
|
|
|
204
226
|
async updateFilters() {
|
|
205
|
-
if (!this.player) return this
|
|
227
|
+
if (!this.player || !this._dirty.size) return this
|
|
228
|
+
|
|
229
|
+
const payload = {}
|
|
230
|
+
for (const key of this._dirty) {
|
|
231
|
+
payload[key] = this.filters[key]
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
this._dirty.clear()
|
|
235
|
+
|
|
206
236
|
await this.player.nodes.rest.updatePlayer({
|
|
207
237
|
guildId: this.player.guildId,
|
|
208
|
-
data: { filters:
|
|
238
|
+
data: { filters: payload }
|
|
209
239
|
})
|
|
210
240
|
return this
|
|
211
241
|
}
|
package/build/structures/Node.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const IS_BUN = !!(process?.isBun || process?.versions?.bun || globalThis.Bun)
|
|
4
|
+
if (process && typeof process.isBun !== 'boolean') process.isBun = IS_BUN
|
|
5
|
+
|
|
6
|
+
const WebSocketImpl = process.isBun ? globalThis.WebSocket : require('ws')
|
|
7
|
+
|
|
4
8
|
const Rest = require('./Rest')
|
|
5
9
|
const { AqualinkEvents } = require('./AqualinkEvents')
|
|
6
10
|
|
|
7
11
|
const privateData = new WeakMap()
|
|
8
12
|
|
|
13
|
+
const NODE_STATE = Object.freeze({ IDLE: 0, CONNECTING: 1, READY: 2, DISCONNECTING: 3, RECONNECTING: 4 })
|
|
9
14
|
const WS_STATES = Object.freeze({ CONNECTING: 0, OPEN: 1, CLOSING: 2, CLOSED: 3 })
|
|
10
15
|
const FATAL_CLOSE_CODES = Object.freeze([4003, 4004, 4010, 4011, 4012, 4015])
|
|
11
16
|
const WS_PATH = '/v4/websocket'
|
|
@@ -15,11 +20,12 @@ const OPS_READY = 'ready'
|
|
|
15
20
|
const OPS_PLAYER_UPDATE = 'playerUpdate'
|
|
16
21
|
const OPS_EVENT = 'event'
|
|
17
22
|
|
|
23
|
+
const unrefTimer = (t) => { try { t?.unref?.() } catch {} }
|
|
24
|
+
|
|
18
25
|
const _functions = {
|
|
19
26
|
buildWsUrl(host, port, ssl) {
|
|
20
27
|
const needsBrackets = host.includes(':') && !host.startsWith('[')
|
|
21
|
-
|
|
22
|
-
return `ws${ssl ? 's' : ''}://${h}:${port}${WS_PATH}`
|
|
28
|
+
return `ws${ssl ? 's' : ''}://${needsBrackets ? `[${host}]` : host}:${port}${WS_PATH}`
|
|
23
29
|
},
|
|
24
30
|
|
|
25
31
|
isLyricsOp(op) {
|
|
@@ -30,8 +36,13 @@ const _functions = {
|
|
|
30
36
|
if (!reason) return 'No reason provided'
|
|
31
37
|
if (typeof reason === 'string') return reason
|
|
32
38
|
if (Buffer.isBuffer(reason)) {
|
|
33
|
-
try { return reason.toString('utf8') }
|
|
34
|
-
|
|
39
|
+
try { return reason.toString('utf8') } catch { return String(reason) }
|
|
40
|
+
}
|
|
41
|
+
if (reason instanceof ArrayBuffer) {
|
|
42
|
+
try { return Buffer.from(reason).toString('utf8') } catch { return String(reason) }
|
|
43
|
+
}
|
|
44
|
+
if (ArrayBuffer.isView(reason)) {
|
|
45
|
+
try { return Buffer.from(reason.buffer, reason.byteOffset, reason.byteLength).toString('utf8') } catch { return String(reason) }
|
|
35
46
|
}
|
|
36
47
|
if (typeof reason === 'object') return reason.message || reason.code || JSON.stringify(reason)
|
|
37
48
|
return String(reason)
|
|
@@ -79,6 +90,7 @@ class Node {
|
|
|
79
90
|
this.skipUTF8Validation = options.skipUTF8Validation ?? true
|
|
80
91
|
|
|
81
92
|
this.connected = false
|
|
93
|
+
this.state = NODE_STATE.IDLE
|
|
82
94
|
this.info = null
|
|
83
95
|
this.ws = null
|
|
84
96
|
this.reconnectAttempted = 0
|
|
@@ -87,6 +99,9 @@ class Node {
|
|
|
87
99
|
this._isConnecting = false
|
|
88
100
|
this.isNodelink = false
|
|
89
101
|
|
|
102
|
+
this._wsIsBun = !!process.isBun
|
|
103
|
+
this._bunCleanup = null
|
|
104
|
+
|
|
90
105
|
this.stats = {
|
|
91
106
|
players: 0,
|
|
92
107
|
playingPlayers: 0,
|
|
@@ -113,13 +128,11 @@ class Node {
|
|
|
113
128
|
|
|
114
129
|
_buildHeaders() {
|
|
115
130
|
const headers = {
|
|
116
|
-
|
|
131
|
+
Authorization: this.auth,
|
|
117
132
|
'User-Id': this.aqua.clientId,
|
|
118
133
|
'Client-Name': this._clientName
|
|
119
134
|
}
|
|
120
|
-
if (this.sessionId)
|
|
121
|
-
headers['Session-Id'] = this.sessionId
|
|
122
|
-
}
|
|
135
|
+
if (this.sessionId) headers['Session-Id'] = this.sessionId
|
|
123
136
|
return headers
|
|
124
137
|
}
|
|
125
138
|
|
|
@@ -139,6 +152,7 @@ class Node {
|
|
|
139
152
|
|
|
140
153
|
async _handleOpen() {
|
|
141
154
|
this.connected = true
|
|
155
|
+
this.state = NODE_STATE.READY
|
|
142
156
|
this._isConnecting = false
|
|
143
157
|
this.reconnectAttempted = 0
|
|
144
158
|
this._emitDebug('WebSocket connection established')
|
|
@@ -147,11 +161,11 @@ class Node {
|
|
|
147
161
|
const timeoutId = setTimeout(() => {
|
|
148
162
|
if (!this.isDestroyed) this._emitError('Node info fetch timeout')
|
|
149
163
|
}, Node.INFO_FETCH_TIMEOUT)
|
|
150
|
-
timeoutId
|
|
164
|
+
unrefTimer(timeoutId)
|
|
151
165
|
|
|
152
166
|
try {
|
|
153
167
|
this.info = await this.rest.makeRequest('GET', '/v4/info')
|
|
154
|
-
this.isNodelink = !!this.info?.isNodelink
|
|
168
|
+
this.isNodelink = !!this.info?.isNodelink
|
|
155
169
|
} catch (err) {
|
|
156
170
|
this.info = null
|
|
157
171
|
this._emitError(`Failed to fetch node info: ${_functions.errMsg(err)}`)
|
|
@@ -171,12 +185,9 @@ class Node {
|
|
|
171
185
|
_handleMessage(data, isBinary) {
|
|
172
186
|
if (isBinary) return
|
|
173
187
|
|
|
174
|
-
const str = Buffer.isBuffer(data) ? data.toString('utf8') : data
|
|
175
|
-
if (!str || typeof str !== 'string') return
|
|
176
|
-
|
|
177
188
|
let payload
|
|
178
189
|
try {
|
|
179
|
-
payload = JSON.parse(
|
|
190
|
+
payload = JSON.parse(data)
|
|
180
191
|
} catch (err) {
|
|
181
192
|
this._emitDebug(() => `Invalid JSON from Lavalink: ${err.message}`)
|
|
182
193
|
return
|
|
@@ -195,7 +206,6 @@ class Node {
|
|
|
195
206
|
_emitToPlayer(eventName, payload) {
|
|
196
207
|
const player = this._getPlayer(payload?.guildId)
|
|
197
208
|
if (!player?.emit) return
|
|
198
|
-
|
|
199
209
|
try {
|
|
200
210
|
player.emit(eventName, payload)
|
|
201
211
|
} catch (err) {
|
|
@@ -214,9 +224,14 @@ class Node {
|
|
|
214
224
|
|
|
215
225
|
_handleClose(code, reason) {
|
|
216
226
|
this.connected = false
|
|
227
|
+
const wasReady = this.state === NODE_STATE.READY
|
|
228
|
+
this.state = this.isDestroyed ? NODE_STATE.IDLE : NODE_STATE.RECONNECTING
|
|
217
229
|
this._isConnecting = false
|
|
218
230
|
|
|
219
|
-
this.aqua.emit(AqualinkEvents.NodeDisconnect, this, {
|
|
231
|
+
this.aqua.emit(AqualinkEvents.NodeDisconnect, this, {
|
|
232
|
+
code,
|
|
233
|
+
reason: _functions.reasonToString(reason)
|
|
234
|
+
})
|
|
220
235
|
|
|
221
236
|
if (this.isDestroyed) return
|
|
222
237
|
|
|
@@ -244,9 +259,13 @@ class Node {
|
|
|
244
259
|
const attempt = ++this.reconnectAttempted
|
|
245
260
|
|
|
246
261
|
if (this.infiniteReconnects) {
|
|
247
|
-
this.aqua.emit(AqualinkEvents.NodeReconnect, this, {
|
|
262
|
+
this.aqua.emit(AqualinkEvents.NodeReconnect, this, {
|
|
263
|
+
infinite: true,
|
|
264
|
+
attempt,
|
|
265
|
+
backoffTime: Node.INFINITE_BACKOFF
|
|
266
|
+
})
|
|
248
267
|
this.reconnectTimeoutId = setTimeout(this._boundHandlers.connect, Node.INFINITE_BACKOFF)
|
|
249
|
-
this.reconnectTimeoutId
|
|
268
|
+
unrefTimer(this.reconnectTimeoutId)
|
|
250
269
|
return
|
|
251
270
|
}
|
|
252
271
|
|
|
@@ -259,7 +278,7 @@ class Node {
|
|
|
259
278
|
const backoffTime = this._calcBackoff(attempt)
|
|
260
279
|
this.aqua.emit(AqualinkEvents.NodeReconnect, this, { infinite: false, attempt, backoffTime })
|
|
261
280
|
this.reconnectTimeoutId = setTimeout(this._boundHandlers.connect, backoffTime)
|
|
262
|
-
this.reconnectTimeoutId
|
|
281
|
+
unrefTimer(this.reconnectTimeoutId)
|
|
263
282
|
}
|
|
264
283
|
|
|
265
284
|
_calcBackoff(attempt) {
|
|
@@ -269,30 +288,70 @@ class Node {
|
|
|
269
288
|
}
|
|
270
289
|
|
|
271
290
|
_clearReconnectTimeout() {
|
|
272
|
-
if (this.reconnectTimeoutId)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
291
|
+
if (!this.reconnectTimeoutId) return
|
|
292
|
+
clearTimeout(this.reconnectTimeoutId)
|
|
293
|
+
this.reconnectTimeoutId = null
|
|
276
294
|
}
|
|
277
295
|
|
|
278
296
|
connect() {
|
|
279
297
|
if (this.isDestroyed || this._isConnecting) return
|
|
280
298
|
|
|
281
|
-
const
|
|
282
|
-
if (
|
|
299
|
+
const state = this.ws?.readyState
|
|
300
|
+
if (state === WS_STATES.OPEN) {
|
|
283
301
|
this._emitDebug('WebSocket already connected')
|
|
284
302
|
return
|
|
285
303
|
}
|
|
286
|
-
if (
|
|
304
|
+
if (state === WS_STATES.CONNECTING || state === WS_STATES.CLOSING) {
|
|
287
305
|
this._emitDebug('WebSocket is connecting/closing; skipping new connect')
|
|
288
306
|
return
|
|
289
307
|
}
|
|
290
308
|
|
|
291
309
|
this._isConnecting = true
|
|
310
|
+
this.state = NODE_STATE.CONNECTING
|
|
292
311
|
this._cleanup()
|
|
293
312
|
|
|
294
313
|
try {
|
|
295
|
-
const
|
|
314
|
+
const h = this._boundHandlers
|
|
315
|
+
|
|
316
|
+
if (this._wsIsBun) {
|
|
317
|
+
const ws = new WebSocketImpl(this.wsUrl, { headers: this._headers })
|
|
318
|
+
ws.binaryType = 'arraybuffer'
|
|
319
|
+
|
|
320
|
+
const offs = []
|
|
321
|
+
const add = (type, fn, once = false) => {
|
|
322
|
+
const wrapped = once
|
|
323
|
+
? (ev) => { try { ws.removeEventListener(type, wrapped) } catch {} ; fn(ev) }
|
|
324
|
+
: fn
|
|
325
|
+
ws.addEventListener(type, wrapped)
|
|
326
|
+
offs.push(() => { try { ws.removeEventListener(type, wrapped) } catch {} })
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
add('open', () => h.open(), true)
|
|
330
|
+
|
|
331
|
+
add('error', (event) => {
|
|
332
|
+
const err = event?.error
|
|
333
|
+
h.error(err instanceof Error ? err : new Error('WebSocket error'))
|
|
334
|
+
}, true)
|
|
335
|
+
|
|
336
|
+
add('message', (event) => {
|
|
337
|
+
const data = event?.data
|
|
338
|
+
if (typeof data === 'string') h.message(data, false)
|
|
339
|
+
else h.message(data, true)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
add('close', (event) => {
|
|
343
|
+
h.close(
|
|
344
|
+
typeof event?.code === 'number' ? event.code : Node.WS_CLOSE_NORMAL,
|
|
345
|
+
typeof event?.reason === 'string' ? event.reason : ''
|
|
346
|
+
)
|
|
347
|
+
}, true)
|
|
348
|
+
|
|
349
|
+
this._bunCleanup = () => { for (let i = 0; i < offs.length; i++) offs[i]() }
|
|
350
|
+
this.ws = ws
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const ws = new WebSocketImpl(this.wsUrl, {
|
|
296
355
|
headers: this._headers,
|
|
297
356
|
perMessageDeflate: true,
|
|
298
357
|
handshakeTimeout: this.timeout,
|
|
@@ -302,7 +361,6 @@ class Node {
|
|
|
302
361
|
|
|
303
362
|
ws.binaryType = 'nodebuffer'
|
|
304
363
|
|
|
305
|
-
const h = this._boundHandlers
|
|
306
364
|
ws.once('open', h.open)
|
|
307
365
|
ws.once('error', h.error)
|
|
308
366
|
ws.on('message', h.message)
|
|
@@ -320,12 +378,20 @@ class Node {
|
|
|
320
378
|
const ws = this.ws
|
|
321
379
|
if (!ws) return
|
|
322
380
|
|
|
323
|
-
|
|
381
|
+
if (this._wsIsBun) {
|
|
382
|
+
try { this._bunCleanup?.() } catch {}
|
|
383
|
+
this._bunCleanup = null
|
|
384
|
+
} else {
|
|
385
|
+
ws.removeAllListeners?.()
|
|
386
|
+
}
|
|
324
387
|
|
|
325
388
|
try {
|
|
326
389
|
const state = ws.readyState
|
|
327
|
-
if (state === WS_STATES.OPEN
|
|
328
|
-
|
|
390
|
+
if (state === WS_STATES.OPEN || state === WS_STATES.CONNECTING) {
|
|
391
|
+
ws.close(Node.WS_CLOSE_NORMAL)
|
|
392
|
+
} else if (!this._wsIsBun && state !== WS_STATES.CLOSED) {
|
|
393
|
+
ws.terminate?.()
|
|
394
|
+
}
|
|
329
395
|
} catch (err) {
|
|
330
396
|
this._emitError(`WebSocket cleanup error: ${_functions.errMsg(err)}`)
|
|
331
397
|
}
|
|
@@ -337,6 +403,7 @@ class Node {
|
|
|
337
403
|
if (this.isDestroyed) return
|
|
338
404
|
|
|
339
405
|
this.isDestroyed = true
|
|
406
|
+
this.state = NODE_STATE.IDLE
|
|
340
407
|
this._isConnecting = false
|
|
341
408
|
this._clearReconnectTimeout()
|
|
342
409
|
this._cleanup()
|
|
@@ -415,7 +482,6 @@ class Node {
|
|
|
415
482
|
this._headers['Session-Id'] = sessionId
|
|
416
483
|
|
|
417
484
|
this.aqua.emit(AqualinkEvents.NodeReady, this, { resumed: !!payload.resumed })
|
|
418
|
-
this.aqua.emit(AqualinkEvents.NodeConnect, this)
|
|
419
485
|
|
|
420
486
|
if (this.autoResume) {
|
|
421
487
|
setImmediate(() => {
|
|
@@ -427,9 +493,7 @@ class Node {
|
|
|
427
493
|
}
|
|
428
494
|
|
|
429
495
|
async _resumePlayers() {
|
|
430
|
-
if (!this.sessionId)
|
|
431
|
-
return
|
|
432
|
-
}
|
|
496
|
+
if (!this.sessionId) return
|
|
433
497
|
|
|
434
498
|
try {
|
|
435
499
|
await this.rest.makeRequest('PATCH', `/v4/sessions/${this.sessionId}`, {
|
|
@@ -452,7 +516,11 @@ class Node {
|
|
|
452
516
|
|
|
453
517
|
_emitDebug(message) {
|
|
454
518
|
if (!this.aqua?.listenerCount?.(AqualinkEvents.Debug)) return
|
|
455
|
-
this.aqua.emit(
|
|
519
|
+
this.aqua.emit(
|
|
520
|
+
AqualinkEvents.Debug,
|
|
521
|
+
this.name,
|
|
522
|
+
typeof message === 'function' ? message() : message
|
|
523
|
+
)
|
|
456
524
|
}
|
|
457
525
|
}
|
|
458
526
|
|
|
@@ -7,6 +7,7 @@ const Filters = require('./Filters')
|
|
|
7
7
|
const { spAutoPlay, scAutoPlay } = require('../handlers/autoplay')
|
|
8
8
|
const Queue = require('./Queue')
|
|
9
9
|
|
|
10
|
+
const PLAYER_STATE = Object.freeze({ IDLE: 0, CONNECTING: 1, READY: 2, DISCONNECTING: 3, DESTROYED: 4 })
|
|
10
11
|
const LOOP_MODES = Object.freeze({ NONE: 0, TRACK: 1, QUEUE: 2 })
|
|
11
12
|
const LOOP_MODE_NAMES = Object.freeze(['none', 'track', 'queue'])
|
|
12
13
|
const EVENT_HANDLERS = Object.freeze({
|
|
@@ -166,6 +167,8 @@ class Player extends EventEmitter {
|
|
|
166
167
|
this.textChannel = options.textChannel
|
|
167
168
|
this.voiceChannel = options.voiceChannel
|
|
168
169
|
this.playing = this.paused = this.connected = this.destroyed = false
|
|
170
|
+
this.state = PLAYER_STATE.IDLE
|
|
171
|
+
this.txId = 0
|
|
169
172
|
this.isAutoplayEnabled = this.isAutoplay = false
|
|
170
173
|
this.autoplaySeed = this.current = this.nowPlayingMessage = null
|
|
171
174
|
this.position = this.timestamp = this.ping = 0
|
|
@@ -244,12 +247,12 @@ class Player extends EventEmitter {
|
|
|
244
247
|
this._voiceDownSince = Date.now()
|
|
245
248
|
this._createTimer(() => {
|
|
246
249
|
if (this.connected || this.destroyed || this.nodes?.info?.isNodelink) return
|
|
247
|
-
if (Date.now() < (this._suppressResumeUntil || 0)) return
|
|
248
250
|
this.connection.attemptResume()
|
|
249
251
|
}, 1000)
|
|
250
252
|
}
|
|
251
253
|
} else {
|
|
252
254
|
this._voiceDownSince = 0
|
|
255
|
+
this.state = PLAYER_STATE.READY
|
|
253
256
|
}
|
|
254
257
|
|
|
255
258
|
this.aqua.emit(AqualinkEvents.PlayerUpdate, this, packet)
|
|
@@ -291,37 +294,9 @@ class Player extends EventEmitter {
|
|
|
291
294
|
return this
|
|
292
295
|
}
|
|
293
296
|
|
|
294
|
-
async _waitForConnection(timeout = RESUME_TIMEOUT) {
|
|
295
|
-
if (this.destroyed) return
|
|
296
|
-
if (this.connected) return
|
|
297
|
-
return new Promise((resolve, reject) => {
|
|
298
|
-
let timer
|
|
299
|
-
const cleanup = () => {
|
|
300
|
-
if (timer) { this._pendingTimers?.delete(timer); clearTimeout(timer) }
|
|
301
|
-
this.off('playerUpdate', onUpdate)
|
|
302
|
-
}
|
|
303
|
-
const onUpdate = payload => {
|
|
304
|
-
if (this.destroyed) { cleanup(); return reject(new Error('Player destroyed')) }
|
|
305
|
-
if (payload?.state?.connected || _functions.isNum(payload?.state?.time)) {
|
|
306
|
-
cleanup()
|
|
307
|
-
return resolve()
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
this.on('playerUpdate', onUpdate)
|
|
311
|
-
timer = this._createTimer(() => { cleanup(); reject(new Error('No connection confirmation')) }, timeout)
|
|
312
|
-
})
|
|
313
|
-
}
|
|
314
297
|
|
|
315
298
|
async play() {
|
|
316
299
|
if (this.destroyed || !this.queue.size) return this
|
|
317
|
-
if (!this.connected) {
|
|
318
|
-
try {
|
|
319
|
-
await this._waitForConnection(RESUME_TIMEOUT)
|
|
320
|
-
if (!this.connected || this.destroyed) return this
|
|
321
|
-
} catch {
|
|
322
|
-
return this
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
300
|
|
|
326
301
|
const item = this.queue.dequeue()
|
|
327
302
|
if (!item) return this
|
|
@@ -348,8 +323,9 @@ class Player extends EventEmitter {
|
|
|
348
323
|
this.deaf = options.deaf !== undefined ? !!options.deaf : true
|
|
349
324
|
this.mute = !!options.mute
|
|
350
325
|
this.destroyed = false
|
|
326
|
+
this.state = PLAYER_STATE.CONNECTING
|
|
351
327
|
|
|
352
|
-
this.
|
|
328
|
+
this.txId++
|
|
353
329
|
this._voiceRequestChannel = voiceChannel
|
|
354
330
|
|
|
355
331
|
this.voiceChannel = voiceChannel
|
|
@@ -399,7 +375,11 @@ class Player extends EventEmitter {
|
|
|
399
375
|
}
|
|
400
376
|
|
|
401
377
|
destroy(options = {}) {
|
|
402
|
-
const {
|
|
378
|
+
const {
|
|
379
|
+
preserveClient = true, skipRemote = false,
|
|
380
|
+
preserveMessage = false, preserveReconnecting = false,
|
|
381
|
+
preserveTracks = false
|
|
382
|
+
} = options
|
|
403
383
|
if (this.destroyed && !this.queue) return this
|
|
404
384
|
|
|
405
385
|
if (!this.destroyed) {
|
|
@@ -416,12 +396,13 @@ class Player extends EventEmitter {
|
|
|
416
396
|
this._pendingTimers = null
|
|
417
397
|
|
|
418
398
|
this.connected = this.playing = this.paused = this.isAutoplay = false
|
|
399
|
+
this.state = PLAYER_STATE.DESTROYED
|
|
419
400
|
this.autoplayRetries = this.reconnectionRetries = 0
|
|
420
|
-
this._reconnecting = false
|
|
401
|
+
if (!preserveReconnecting) this._reconnecting = false
|
|
421
402
|
this._lastVoiceChannel = this.voiceChannel
|
|
422
403
|
this.voiceChannel = null
|
|
423
404
|
|
|
424
|
-
if (this.shouldDeleteMessage && this.nowPlayingMessage) {
|
|
405
|
+
if (this.shouldDeleteMessage && this.nowPlayingMessage && !preserveMessage) {
|
|
425
406
|
_functions.safeDel(this.nowPlayingMessage)
|
|
426
407
|
this.nowPlayingMessage = null
|
|
427
408
|
}
|
|
@@ -446,7 +427,7 @@ class Player extends EventEmitter {
|
|
|
446
427
|
this._dataStore?.clear()
|
|
447
428
|
this._dataStore = null
|
|
448
429
|
|
|
449
|
-
if (this.current?.dispose && !this.aqua?.options?.autoResume) this.current.dispose()
|
|
430
|
+
if (this.current?.dispose && !this.aqua?.options?.autoResume && !preserveTracks) this.current.dispose()
|
|
450
431
|
if (this.connection) {
|
|
451
432
|
try { this.connection.destroy() } catch { }
|
|
452
433
|
}
|
|
@@ -677,11 +658,12 @@ class Player extends EventEmitter {
|
|
|
677
658
|
return null
|
|
678
659
|
}
|
|
679
660
|
|
|
680
|
-
trackStart() {
|
|
661
|
+
trackStart(player, track, payload = {}) {
|
|
681
662
|
if (this.destroyed) return
|
|
682
663
|
this.playing = true
|
|
683
664
|
this.paused = false
|
|
684
|
-
this.aqua.emit(AqualinkEvents.TrackStart, this, this.current)
|
|
665
|
+
this.aqua.emit(AqualinkEvents.TrackStart, this, this.current, { ...payload, resumed: this._resuming })
|
|
666
|
+
this._resuming = false
|
|
685
667
|
}
|
|
686
668
|
|
|
687
669
|
async trackEnd(player, track, payload) {
|
|
@@ -692,12 +674,12 @@ class Player extends EventEmitter {
|
|
|
692
674
|
const isReplaced = reason === 'replaced'
|
|
693
675
|
|
|
694
676
|
if (track) this.previousTracks.push(track)
|
|
695
|
-
if (this.shouldDeleteMessage) _functions.safeDel(this.nowPlayingMessage)
|
|
677
|
+
if (this.shouldDeleteMessage && !this._reconnecting && !this._resuming) _functions.safeDel(this.nowPlayingMessage)
|
|
696
678
|
this.current = null
|
|
697
679
|
|
|
698
680
|
if (isFailure) {
|
|
699
681
|
if (!this.queue.size) {
|
|
700
|
-
this.clearData()
|
|
682
|
+
this.clearData({ preserveTracks: this._reconnecting || this._resuming })
|
|
701
683
|
this.aqua.emit(AqualinkEvents.QueueEnd, this)
|
|
702
684
|
} else {
|
|
703
685
|
this.aqua.emit(AqualinkEvents.TrackEnd, this, track, reason)
|
|
@@ -718,7 +700,7 @@ class Player extends EventEmitter {
|
|
|
718
700
|
} else {
|
|
719
701
|
this.playing = false
|
|
720
702
|
if (this.leaveOnEnd && !this.destroyed) {
|
|
721
|
-
this.clearData()
|
|
703
|
+
this.clearData({ preserveTracks: this._reconnecting || this._resuming })
|
|
722
704
|
this.destroy()
|
|
723
705
|
}
|
|
724
706
|
this.aqua.emit(AqualinkEvents.QueueEnd, this)
|
|
@@ -751,51 +733,33 @@ class Player extends EventEmitter {
|
|
|
751
733
|
mixStarted(p, t, payload) { _functions.emitIfActive(this, AqualinkEvents.MixStarted, t, payload) }
|
|
752
734
|
mixEnded(p, t, payload) { _functions.emitIfActive(this, AqualinkEvents.MixEnded, t, payload) }
|
|
753
735
|
|
|
736
|
+
|
|
754
737
|
async _attemptVoiceResume() {
|
|
755
738
|
if (!this.connection?.sessionId) throw new Error('No session')
|
|
756
739
|
if (!await this.connection.attemptResume()) throw new Error('Resume failed')
|
|
757
|
-
return new Promise((resolve, reject) => {
|
|
758
|
-
let timeout
|
|
759
|
-
const cleanup = () => {
|
|
760
|
-
if (timeout) { this._pendingTimers?.delete(timeout); clearTimeout(timeout) }
|
|
761
|
-
this.off('playerUpdate', onUpdate)
|
|
762
|
-
}
|
|
763
|
-
const onUpdate = payload => {
|
|
764
|
-
if (payload?.state?.connected || _functions.isNum(payload?.state?.time)) {
|
|
765
|
-
cleanup()
|
|
766
|
-
resolve()
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
timeout = this._createTimer(() => { cleanup(); reject(new Error('No confirmation')) }, RESUME_TIMEOUT)
|
|
770
|
-
this.on('playerUpdate', onUpdate)
|
|
771
|
-
})
|
|
772
740
|
}
|
|
773
741
|
|
|
774
742
|
async socketClosed(player, track, payload) {
|
|
775
743
|
if (this.destroyed) return
|
|
776
744
|
const code = payload?.code
|
|
777
|
-
|
|
778
|
-
if (code === 4014
|
|
779
|
-
this.aqua.emit(AqualinkEvents.SocketClosed, this, payload)
|
|
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
|
-
}
|
|
787
|
-
return
|
|
788
|
-
}
|
|
745
|
+
let isRecoverable = [4015, 4009, 4006, 4014, 4022].includes(code)
|
|
746
|
+
if (code === 4014 && this.connection?.isWaitingForDisconnect) isRecoverable = false
|
|
789
747
|
|
|
790
748
|
if (code === 4015 && !this.nodes?.info?.isNodelink) {
|
|
791
749
|
try { await this._attemptVoiceResume(); return } catch { /* ignore */ }
|
|
792
750
|
}
|
|
793
751
|
|
|
794
|
-
if (!
|
|
752
|
+
if (!isRecoverable) {
|
|
795
753
|
this.aqua.emit(AqualinkEvents.SocketClosed, this, payload)
|
|
796
754
|
return
|
|
797
755
|
}
|
|
798
756
|
|
|
757
|
+
if (code === 4014 || code === 4022) {
|
|
758
|
+
this.connected = false
|
|
759
|
+
if (!this._voiceDownSince) this._voiceDownSince = Date.now()
|
|
760
|
+
if (code === 4022) this._suppressResumeUntil = Date.now() + 3000
|
|
761
|
+
}
|
|
762
|
+
|
|
799
763
|
if (this._reconnecting) return
|
|
800
764
|
|
|
801
765
|
const aqua = this.aqua
|
|
@@ -817,18 +781,25 @@ class Player extends EventEmitter {
|
|
|
817
781
|
currentTrack: this.current,
|
|
818
782
|
queue: this.queue?.toArray() || [],
|
|
819
783
|
previousIdentifiers: Array.from(this.previousIdentifiers),
|
|
820
|
-
autoplaySeed: this.autoplaySeed
|
|
784
|
+
autoplaySeed: this.autoplaySeed,
|
|
785
|
+
nowPlayingMessage: this.nowPlayingMessage
|
|
821
786
|
}
|
|
822
787
|
|
|
823
788
|
this._reconnecting = true
|
|
824
|
-
this.destroy({
|
|
789
|
+
this.destroy({
|
|
790
|
+
preserveClient: true, skipRemote: true,
|
|
791
|
+
preserveMessage: true, preserveReconnecting: true,
|
|
792
|
+
preserveTracks: true
|
|
793
|
+
})
|
|
825
794
|
|
|
826
795
|
const reconnectTimers = new Set()
|
|
827
796
|
const tryReconnect = async attempt => {
|
|
828
797
|
if (aqua?.destroyed) { _functions.clearTimers(reconnectTimers); return }
|
|
829
798
|
try {
|
|
830
799
|
const np = await aqua.createConnection({
|
|
831
|
-
guildId, voiceChannel: vcId, textChannel: tcId, deaf, mute, defaultVolume: state.volume
|
|
800
|
+
guildId, voiceChannel: vcId, textChannel: tcId, deaf, mute, defaultVolume: state.volume,
|
|
801
|
+
preserveMessage: true,
|
|
802
|
+
resuming: true
|
|
832
803
|
})
|
|
833
804
|
if (!np) throw new Error('Failed to create player')
|
|
834
805
|
|
|
@@ -837,6 +808,7 @@ class Player extends EventEmitter {
|
|
|
837
808
|
np.isAutoplayEnabled = state.isAutoplayEnabled
|
|
838
809
|
np.autoplaySeed = state.autoplaySeed
|
|
839
810
|
np.previousIdentifiers = new Set(state.previousIdentifiers)
|
|
811
|
+
np.nowPlayingMessage = state.nowPlayingMessage
|
|
840
812
|
|
|
841
813
|
const ct = state.currentTrack
|
|
842
814
|
if (ct) np.queue.add(ct)
|
|
@@ -894,11 +866,12 @@ class Player extends EventEmitter {
|
|
|
894
866
|
return this._dataStore?.get(key)
|
|
895
867
|
}
|
|
896
868
|
|
|
897
|
-
clearData() {
|
|
869
|
+
clearData(options = {}) {
|
|
870
|
+
const { preserveTracks = false } = options
|
|
898
871
|
this.previousTracks?.clear()
|
|
899
872
|
this._dataStore?.clear()
|
|
900
873
|
this.previousIdentifiers?.clear()
|
|
901
|
-
if (this.current?.dispose) this.current.dispose()
|
|
874
|
+
if (this.current?.dispose && !preserveTracks) this.current.dispose()
|
|
902
875
|
this.current = null
|
|
903
876
|
this.position = this.timestamp = 0
|
|
904
877
|
this.queue?.clear()
|
|
@@ -1,65 +1,86 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
class Queue {
|
|
4
|
+
constructor() {
|
|
5
|
+
this._items = []
|
|
6
|
+
}
|
|
7
|
+
|
|
2
8
|
get size() {
|
|
3
|
-
return this.length
|
|
9
|
+
return this._items.length
|
|
4
10
|
}
|
|
5
11
|
|
|
6
12
|
get first() {
|
|
7
|
-
return this[0] || null
|
|
13
|
+
return this._items[0] || null
|
|
8
14
|
}
|
|
9
15
|
|
|
10
16
|
get last() {
|
|
11
|
-
return this[this.length - 1] || null
|
|
17
|
+
return this._items[this._items.length - 1] || null
|
|
12
18
|
}
|
|
13
19
|
|
|
14
|
-
add(
|
|
15
|
-
this.push(
|
|
20
|
+
add(...tracks) {
|
|
21
|
+
this._items.push(...tracks)
|
|
16
22
|
return this
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
remove(track) {
|
|
20
|
-
const idx = this.indexOf(track)
|
|
26
|
+
const idx = this._items.indexOf(track)
|
|
21
27
|
if (idx === -1) return false
|
|
22
|
-
const removed = this[idx]
|
|
23
|
-
this.splice(idx, 1)
|
|
28
|
+
const removed = this._items[idx]
|
|
29
|
+
this._items.splice(idx, 1)
|
|
24
30
|
if (removed?.dispose) removed.dispose()
|
|
25
31
|
return true
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
clear() {
|
|
29
|
-
for (let i = 0; i < this.length; i++) {
|
|
30
|
-
if (this[i]?.dispose) this[i].dispose()
|
|
35
|
+
for (let i = 0; i < this._items.length; i++) {
|
|
36
|
+
if (this._items[i]?.dispose) this._items[i].dispose()
|
|
31
37
|
}
|
|
32
|
-
this.length = 0
|
|
38
|
+
this._items.length = 0
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
shuffle() {
|
|
36
|
-
for (let i = this.length - 1; i > 0; i--) {
|
|
42
|
+
for (let i = this._items.length - 1; i > 0; i--) {
|
|
37
43
|
const j = Math.floor(Math.random() * (i + 1))
|
|
38
|
-
const temp = this[i]
|
|
39
|
-
this[i] = this[j]
|
|
40
|
-
this[j] = temp
|
|
44
|
+
const temp = this._items[i]
|
|
45
|
+
this._items[i] = this._items[j]
|
|
46
|
+
this._items[j] = temp
|
|
41
47
|
}
|
|
42
48
|
return this
|
|
43
49
|
}
|
|
44
50
|
|
|
51
|
+
move(from, to) {
|
|
52
|
+
if (from < 0 || from >= this._items.length || to < 0 || to >= this._items.length) return this
|
|
53
|
+
const [item] = this._items.splice(from, 1)
|
|
54
|
+
this._items.splice(to, 0, item)
|
|
55
|
+
return this
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
swap(index1, index2) {
|
|
59
|
+
if (index1 < 0 || index1 >= this._items.length || index2 < 0 || index2 >= this._items.length) return this
|
|
60
|
+
const temp = this._items[index1]
|
|
61
|
+
this._items[index1] = this._items[index2]
|
|
62
|
+
this._items[index2] = temp
|
|
63
|
+
return this
|
|
64
|
+
}
|
|
65
|
+
|
|
45
66
|
peek() {
|
|
46
67
|
return this.first
|
|
47
68
|
}
|
|
48
69
|
|
|
49
70
|
toArray() {
|
|
50
|
-
return this.
|
|
71
|
+
return [...this._items]
|
|
51
72
|
}
|
|
52
73
|
|
|
53
74
|
at(index) {
|
|
54
|
-
return this[index] || null
|
|
75
|
+
return this._items[index] || null
|
|
55
76
|
}
|
|
56
77
|
|
|
57
78
|
dequeue() {
|
|
58
|
-
return this.shift()
|
|
79
|
+
return this._items.shift()
|
|
59
80
|
}
|
|
60
81
|
|
|
61
82
|
isEmpty() {
|
|
62
|
-
return this.length === 0
|
|
83
|
+
return this._items.length === 0
|
|
63
84
|
}
|
|
64
85
|
|
|
65
86
|
enqueue(track) {
|
|
@@ -67,4 +88,4 @@ class Queue extends Array {
|
|
|
67
88
|
}
|
|
68
89
|
}
|
|
69
90
|
|
|
70
|
-
module.exports = Queue
|
|
91
|
+
module.exports = Queue
|
package/build/structures/Rest.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
const { Buffer } = require('buffer')
|
|
2
4
|
const { Agent: HttpsAgent, request: httpsRequest } = require('https')
|
|
3
5
|
const { Agent: HttpAgent, request: httpRequest } = require('http')
|
|
4
6
|
const http2 = require('http2')
|
|
5
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
createBrotliDecompress,
|
|
9
|
+
createUnzip,
|
|
10
|
+
brotliDecompressSync,
|
|
11
|
+
unzipSync,
|
|
12
|
+
createZstdDecompress,
|
|
13
|
+
zstdDecompressSync
|
|
14
|
+
} = require('zlib')
|
|
15
|
+
|
|
16
|
+
const unrefTimer = (t) => { try { t?.unref?.() } catch {} }
|
|
17
|
+
|
|
18
|
+
const HAS_ZSTD = typeof createZstdDecompress === 'function' && typeof zstdDecompressSync === 'function'
|
|
6
19
|
|
|
7
20
|
const BASE64_LOOKUP = new Uint8Array(256)
|
|
8
21
|
for (let i = 65; i <= 90; i++) BASE64_LOOKUP[i] = 1
|
|
@@ -10,9 +23,8 @@ for (let i = 97; i <= 122; i++) BASE64_LOOKUP[i] = 1
|
|
|
10
23
|
for (let i = 48; i <= 57; i++) BASE64_LOOKUP[i] = 1
|
|
11
24
|
BASE64_LOOKUP[43] = BASE64_LOOKUP[47] = BASE64_LOOKUP[61] = BASE64_LOOKUP[95] = BASE64_LOOKUP[45] = 1
|
|
12
25
|
|
|
13
|
-
const ENCODING_NONE = 0, ENCODING_BR = 1, ENCODING_GZIP = 2, ENCODING_DEFLATE = 3
|
|
26
|
+
const ENCODING_NONE = 0, ENCODING_BR = 1, ENCODING_GZIP = 2, ENCODING_DEFLATE = 3, ENCODING_ZSTD = 4
|
|
14
27
|
const MAX_RESPONSE_SIZE = 10485760
|
|
15
|
-
const SMALL_RESPONSE_THRESHOLD = 512
|
|
16
28
|
const COMPRESSION_MIN_SIZE = 1024
|
|
17
29
|
const API_VERSION = 'v4'
|
|
18
30
|
const UTF8 = 'utf8'
|
|
@@ -43,6 +55,10 @@ const _functions = {
|
|
|
43
55
|
getEncodingType(header) {
|
|
44
56
|
if (!header) return ENCODING_NONE
|
|
45
57
|
const c = header.charCodeAt(0)
|
|
58
|
+
|
|
59
|
+
if (c === 122 && header.startsWith('zstd')) return ENCODING_ZSTD
|
|
60
|
+
if (c === 120 && header.startsWith('x-zstd')) return ENCODING_ZSTD
|
|
61
|
+
|
|
46
62
|
if (c === 98 && header.startsWith('br')) return ENCODING_BR
|
|
47
63
|
if (c === 103 && header.startsWith('gzip')) return ENCODING_GZIP
|
|
48
64
|
if (c === 100 && header.startsWith('deflate')) return ENCODING_DEFLATE
|
|
@@ -53,8 +69,13 @@ const _functions = {
|
|
|
53
69
|
return ct && ct.charCodeAt(0) === 97 && ct.includes(JSON_CT)
|
|
54
70
|
},
|
|
55
71
|
|
|
56
|
-
parseBody(
|
|
57
|
-
|
|
72
|
+
parseBody(data, contentType, forceJson) {
|
|
73
|
+
const isJson = forceJson || this.isJsonContent(contentType)
|
|
74
|
+
if (isJson) {
|
|
75
|
+
if (typeof data === 'string') return JSON.parse(data)
|
|
76
|
+
return JSON.parse(data)
|
|
77
|
+
}
|
|
78
|
+
return typeof data === 'string' ? data : data.toString(UTF8)
|
|
58
79
|
},
|
|
59
80
|
|
|
60
81
|
createHttpError(status, method, url, headers, body, statusMessage) {
|
|
@@ -68,10 +89,18 @@ const _functions = {
|
|
|
68
89
|
},
|
|
69
90
|
|
|
70
91
|
createDecompressor(type) {
|
|
92
|
+
if (type === ENCODING_ZSTD) {
|
|
93
|
+
if (!HAS_ZSTD) throw new Error('Unsupported content-encoding: zstd (zlib zstd APIs not available in this Node runtime)')
|
|
94
|
+
return createZstdDecompress()
|
|
95
|
+
}
|
|
71
96
|
return type === ENCODING_BR ? createBrotliDecompress() : createUnzip()
|
|
72
97
|
},
|
|
73
98
|
|
|
74
99
|
decompressSync(buf, type) {
|
|
100
|
+
if (type === ENCODING_ZSTD) {
|
|
101
|
+
if (!HAS_ZSTD) throw new Error('Unsupported content-encoding: zstd (zlib zstd APIs not available in this Node runtime)')
|
|
102
|
+
return zstdDecompressSync(buf)
|
|
103
|
+
}
|
|
75
104
|
return type === ENCODING_BR ? brotliDecompressSync(buf) : unzipSync(buf)
|
|
76
105
|
}
|
|
77
106
|
}
|
|
@@ -103,14 +132,17 @@ class Rest {
|
|
|
103
132
|
lyrics: `${this._apiBase}/lyrics`
|
|
104
133
|
})
|
|
105
134
|
|
|
135
|
+
const acceptEncoding = HAS_ZSTD ? 'zstd, br, gzip, deflate' : 'br, gzip, deflate'
|
|
136
|
+
|
|
106
137
|
this.defaultHeaders = Object.freeze({
|
|
107
138
|
Authorization: String(node.auth || node.password || ''),
|
|
108
139
|
Accept: 'application/json, */*;q=0.5',
|
|
109
|
-
'Accept-Encoding':
|
|
140
|
+
'Accept-Encoding': acceptEncoding,
|
|
110
141
|
'User-Agent': `Aqualink/${aqua?.version || '1.0'} (Node.js ${process.version})`
|
|
111
142
|
})
|
|
112
143
|
|
|
113
144
|
this._headerPool = []
|
|
145
|
+
this._tlsOptions = null
|
|
114
146
|
this._setupAgent(node)
|
|
115
147
|
this.useHttp2 = !!(aqua?.options?.useHttp2)
|
|
116
148
|
this._h2 = null
|
|
@@ -130,11 +162,13 @@ class Rest {
|
|
|
130
162
|
|
|
131
163
|
if (node.ssl) {
|
|
132
164
|
opts.maxCachedSessions = node.maxCachedSessions || 200
|
|
133
|
-
|
|
134
|
-
if (node.
|
|
135
|
-
if (node.
|
|
136
|
-
if (node.
|
|
137
|
-
if (node.
|
|
165
|
+
this._tlsOptions = Object.create(null)
|
|
166
|
+
if (node.rejectUnauthorized !== undefined) this._tlsOptions.rejectUnauthorized = opts.rejectUnauthorized = node.rejectUnauthorized
|
|
167
|
+
if (node.ca) this._tlsOptions.ca = opts.ca = node.ca
|
|
168
|
+
if (node.cert) this._tlsOptions.cert = opts.cert = node.cert
|
|
169
|
+
if (node.key) this._tlsOptions.key = opts.key = node.key
|
|
170
|
+
if (node.passphrase) this._tlsOptions.passphrase = opts.passphrase = node.passphrase
|
|
171
|
+
if (node.servername) this._tlsOptions.servername = node.servername
|
|
138
172
|
}
|
|
139
173
|
|
|
140
174
|
this.agent = new (node.ssl ? HttpsAgent : HttpAgent)(opts)
|
|
@@ -160,7 +194,7 @@ class Rest {
|
|
|
160
194
|
|
|
161
195
|
_buildHeaders(hasPayload, payloadLength) {
|
|
162
196
|
if (!hasPayload) return this.defaultHeaders
|
|
163
|
-
|
|
197
|
+
const h = this._headerPool.pop() || Object.create(null)
|
|
164
198
|
h.Authorization = this.defaultHeaders.Authorization
|
|
165
199
|
h.Accept = this.defaultHeaders.Accept
|
|
166
200
|
h['Accept-Encoding'] = this.defaultHeaders['Accept-Encoding']
|
|
@@ -194,7 +228,8 @@ class Rest {
|
|
|
194
228
|
|
|
195
229
|
_h1Request(method, url, headers, payload) {
|
|
196
230
|
return new Promise((resolve, reject) => {
|
|
197
|
-
let req, timer, done = false
|
|
231
|
+
let req, timer, done = false
|
|
232
|
+
let chunks = null, size = 0, prealloc = null
|
|
198
233
|
|
|
199
234
|
const finish = (ok, val) => {
|
|
200
235
|
if (done) return
|
|
@@ -208,7 +243,7 @@ class Rest {
|
|
|
208
243
|
req = this.request(url, { method, headers, agent: this.agent, timeout: this.timeout }, (res) => {
|
|
209
244
|
if (timer) { clearTimeout(timer); timer = null }
|
|
210
245
|
|
|
211
|
-
const status = res.statusCode
|
|
246
|
+
const status = res.statusCode || 0
|
|
212
247
|
const cl = res.headers['content-length']
|
|
213
248
|
const contentType = res.headers['content-type'] || ''
|
|
214
249
|
|
|
@@ -226,9 +261,10 @@ class Rest {
|
|
|
226
261
|
const encoding = _functions.getEncodingType(res.headers['content-encoding'])
|
|
227
262
|
|
|
228
263
|
const handleResponse = (buffer) => {
|
|
229
|
-
|
|
264
|
+
if (buffer.length > MAX_RESPONSE_SIZE) return finish(false, ERRORS.RESPONSE_TOO_LARGE)
|
|
265
|
+
|
|
230
266
|
try {
|
|
231
|
-
const result = _functions.parseBody(
|
|
267
|
+
const result = _functions.parseBody(buffer, contentType, false)
|
|
232
268
|
if (status >= 400) {
|
|
233
269
|
finish(false, _functions.createHttpError(status, method, url, res.headers, result, res.statusMessage))
|
|
234
270
|
} else {
|
|
@@ -239,15 +275,15 @@ class Rest {
|
|
|
239
275
|
}
|
|
240
276
|
}
|
|
241
277
|
|
|
242
|
-
if (clInt > 0 && clInt < SMALL_RESPONSE_THRESHOLD && encoding === ENCODING_NONE) {
|
|
243
|
-
res.once('data', handleResponse)
|
|
244
|
-
res.once('error', (e) => finish(false, e))
|
|
245
|
-
return
|
|
246
|
-
}
|
|
247
|
-
|
|
248
278
|
if (encoding !== ENCODING_NONE && clInt > 0 && clInt < COMPRESSION_MIN_SIZE) {
|
|
249
279
|
const compressed = []
|
|
250
|
-
|
|
280
|
+
let csize = 0
|
|
281
|
+
|
|
282
|
+
res.on('data', (c) => {
|
|
283
|
+
csize += c.length
|
|
284
|
+
if (csize > MAX_RESPONSE_SIZE) return finish(false, ERRORS.RESPONSE_TOO_LARGE)
|
|
285
|
+
compressed.push(c)
|
|
286
|
+
})
|
|
251
287
|
res.once('end', () => {
|
|
252
288
|
try {
|
|
253
289
|
handleResponse(_functions.decompressSync(Buffer.concat(compressed), encoding))
|
|
@@ -255,12 +291,16 @@ class Rest {
|
|
|
255
291
|
finish(false, e)
|
|
256
292
|
}
|
|
257
293
|
})
|
|
294
|
+
res.once('aborted', () => finish(false, ERRORS.RESPONSE_ABORTED))
|
|
258
295
|
res.once('error', (e) => finish(false, e))
|
|
259
296
|
return
|
|
260
297
|
}
|
|
261
298
|
|
|
262
|
-
if (clInt > 0 && clInt <= MAX_RESPONSE_SIZE)
|
|
263
|
-
|
|
299
|
+
if (encoding === ENCODING_NONE && clInt > 0 && clInt <= MAX_RESPONSE_SIZE) {
|
|
300
|
+
prealloc = Buffer.allocUnsafe(clInt)
|
|
301
|
+
} else {
|
|
302
|
+
chunks = []
|
|
303
|
+
}
|
|
264
304
|
|
|
265
305
|
let stream = res
|
|
266
306
|
if (encoding !== ENCODING_NONE) {
|
|
@@ -286,12 +326,17 @@ class Rest {
|
|
|
286
326
|
|
|
287
327
|
stream.once('end', () => {
|
|
288
328
|
if (size === 0) return finish(true, null)
|
|
289
|
-
handleResponse(
|
|
329
|
+
handleResponse(
|
|
330
|
+
prealloc
|
|
331
|
+
? prealloc.slice(0, size)
|
|
332
|
+
: (chunks.length === 1 ? chunks[0] : Buffer.concat(chunks, size))
|
|
333
|
+
)
|
|
290
334
|
})
|
|
291
335
|
})
|
|
292
336
|
|
|
293
337
|
req.once('error', (e) => finish(false, e))
|
|
294
338
|
timer = setTimeout(() => finish(false, new Error(`Request timeout: ${this.timeout}ms`)), this.timeout)
|
|
339
|
+
unrefTimer(timer)
|
|
295
340
|
payload ? req.end(payload) : req.end()
|
|
296
341
|
})
|
|
297
342
|
}
|
|
@@ -299,9 +344,9 @@ class Rest {
|
|
|
299
344
|
_getH2Session() {
|
|
300
345
|
if (!this._h2 || this._h2.closed || this._h2.destroyed) {
|
|
301
346
|
this._clearH2()
|
|
302
|
-
this._h2 = http2.connect(this.baseUrl)
|
|
303
|
-
this.
|
|
304
|
-
|
|
347
|
+
this._h2 = http2.connect(this.baseUrl, this._tlsOptions || undefined)
|
|
348
|
+
this._resetH2Timer()
|
|
349
|
+
|
|
305
350
|
const onEnd = () => this._clearH2()
|
|
306
351
|
this._h2.once('error', onEnd)
|
|
307
352
|
this._h2.once('close', onEnd)
|
|
@@ -310,6 +355,14 @@ class Rest {
|
|
|
310
355
|
return this._h2
|
|
311
356
|
}
|
|
312
357
|
|
|
358
|
+
_resetH2Timer() {
|
|
359
|
+
if (this._h2Timer) { clearTimeout(this._h2Timer); this._h2Timer = null }
|
|
360
|
+
if (this._h2 && !this._h2.closed && !this._h2.destroyed) {
|
|
361
|
+
this._h2Timer = setTimeout(() => this._closeH2(), H2_TIMEOUT)
|
|
362
|
+
unrefTimer(this._h2Timer)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
313
366
|
_clearH2() {
|
|
314
367
|
if (this._h2Timer) { clearTimeout(this._h2Timer); this._h2Timer = null }
|
|
315
368
|
this._h2 = null
|
|
@@ -317,14 +370,18 @@ class Rest {
|
|
|
317
370
|
|
|
318
371
|
_closeH2() {
|
|
319
372
|
if (this._h2Timer) { clearTimeout(this._h2Timer); this._h2Timer = null }
|
|
320
|
-
if (this._h2) {
|
|
373
|
+
if (this._h2) {
|
|
374
|
+
try { this._h2.close() } catch {}
|
|
375
|
+
this._h2 = null
|
|
376
|
+
}
|
|
321
377
|
}
|
|
322
378
|
|
|
323
379
|
_h2Request(method, path, headers, payload) {
|
|
324
380
|
const session = this._getH2Session()
|
|
325
381
|
|
|
326
382
|
return new Promise((resolve, reject) => {
|
|
327
|
-
let req, timer, done = false
|
|
383
|
+
let req, timer, done = false
|
|
384
|
+
let chunks = null, size = 0, prealloc = null
|
|
328
385
|
|
|
329
386
|
const finish = (ok, val) => {
|
|
330
387
|
if (done) return
|
|
@@ -347,12 +404,14 @@ class Rest {
|
|
|
347
404
|
if (headers['Content-Length']) h2h['Content-Length'] = headers['Content-Length']
|
|
348
405
|
|
|
349
406
|
req = session.request(h2h)
|
|
407
|
+
this._resetH2Timer()
|
|
350
408
|
|
|
351
409
|
req.once('response', (rh) => {
|
|
352
410
|
if (timer) { clearTimeout(timer); timer = null }
|
|
353
411
|
|
|
354
412
|
const status = rh[':status'] || 0
|
|
355
413
|
const cl = rh['content-length']
|
|
414
|
+
const contentType = rh['content-type'] || ''
|
|
356
415
|
|
|
357
416
|
if (status === 204 || cl === '0') {
|
|
358
417
|
req.resume()
|
|
@@ -365,9 +424,14 @@ class Rest {
|
|
|
365
424
|
return finish(false, ERRORS.RESPONSE_TOO_LARGE)
|
|
366
425
|
}
|
|
367
426
|
|
|
368
|
-
if (clInt > 0 && clInt <= MAX_RESPONSE_SIZE) prealloc = Buffer.allocUnsafe(clInt)
|
|
369
|
-
|
|
370
427
|
const encoding = _functions.getEncodingType(rh['content-encoding'])
|
|
428
|
+
|
|
429
|
+
if (encoding === ENCODING_NONE && clInt > 0 && clInt <= MAX_RESPONSE_SIZE) {
|
|
430
|
+
prealloc = Buffer.allocUnsafe(clInt)
|
|
431
|
+
} else {
|
|
432
|
+
chunks = []
|
|
433
|
+
}
|
|
434
|
+
|
|
371
435
|
const decomp = encoding !== ENCODING_NONE ? _functions.createDecompressor(encoding) : null
|
|
372
436
|
const stream = decomp ? req.pipe(decomp) : req
|
|
373
437
|
|
|
@@ -387,9 +451,13 @@ class Rest {
|
|
|
387
451
|
|
|
388
452
|
stream.once('end', () => {
|
|
389
453
|
if (size === 0) return finish(true, null)
|
|
390
|
-
|
|
454
|
+
|
|
455
|
+
const buffer = prealloc
|
|
456
|
+
? prealloc.slice(0, size)
|
|
457
|
+
: (chunks.length === 1 ? chunks[0] : Buffer.concat(chunks, size))
|
|
458
|
+
|
|
391
459
|
try {
|
|
392
|
-
const result =
|
|
460
|
+
const result = _functions.parseBody(buffer, contentType, false)
|
|
393
461
|
if (status >= 400) {
|
|
394
462
|
finish(false, _functions.createHttpError(status, method, this.baseUrl + path, rh, result))
|
|
395
463
|
} else {
|
|
@@ -402,6 +470,7 @@ class Rest {
|
|
|
402
470
|
})
|
|
403
471
|
|
|
404
472
|
timer = setTimeout(() => finish(false, new Error(`Request timeout: ${this.timeout}ms`)), this.timeout)
|
|
473
|
+
unrefTimer(timer)
|
|
405
474
|
payload ? req.end(payload) : req.end()
|
|
406
475
|
})
|
|
407
476
|
}
|
|
@@ -480,22 +549,23 @@ class Rest {
|
|
|
480
549
|
try {
|
|
481
550
|
const lyrics = await this.makeRequest('GET', `${this._getSessionPath()}/players/${guildId}/track/lyrics?skipTrackSource=${skip}`)
|
|
482
551
|
if (this._validLyrics(lyrics)) return lyrics
|
|
483
|
-
} catch {
|
|
552
|
+
} catch {}
|
|
484
553
|
}
|
|
485
554
|
|
|
486
555
|
if (hasEncoded) {
|
|
487
556
|
try {
|
|
488
557
|
const lyrics = await this.makeRequest('GET', `${this._endpoints.lyrics}?track=${encodeURIComponent(encoded)}&skipTrackSource=${skip}`)
|
|
489
558
|
if (this._validLyrics(lyrics)) return lyrics
|
|
490
|
-
} catch {
|
|
559
|
+
} catch {}
|
|
491
560
|
}
|
|
492
561
|
|
|
493
562
|
if (title) {
|
|
494
|
-
const
|
|
563
|
+
const info = track.info || {}
|
|
564
|
+
const query = info.author ? `${title} ${info.author}` : title
|
|
495
565
|
try {
|
|
496
566
|
const lyrics = await this.makeRequest('GET', `${this._endpoints.lyrics}/search?query=${encodeURIComponent(query)}`)
|
|
497
567
|
if (this._validLyrics(lyrics)) return lyrics
|
|
498
|
-
} catch {
|
|
568
|
+
} catch {}
|
|
499
569
|
}
|
|
500
570
|
|
|
501
571
|
return null
|
|
@@ -510,7 +580,10 @@ class Rest {
|
|
|
510
580
|
|
|
511
581
|
async subscribeLiveLyrics(guildId, skipTrackSource = false) {
|
|
512
582
|
try {
|
|
513
|
-
return await this.makeRequest(
|
|
583
|
+
return await this.makeRequest(
|
|
584
|
+
'POST',
|
|
585
|
+
`${this._getSessionPath()}/players/${guildId}/lyrics/subscribe?skipTrackSource=${skipTrackSource ? 'true' : 'false'}`
|
|
586
|
+
) === null
|
|
514
587
|
} catch {
|
|
515
588
|
return false
|
|
516
589
|
}
|
|
@@ -538,12 +611,12 @@ class Rest {
|
|
|
538
611
|
volume: options.volume !== undefined ? options.volume : 0.8
|
|
539
612
|
}
|
|
540
613
|
|
|
541
|
-
return
|
|
614
|
+
return this.makeRequest('POST', `/v4/sessions/${this.sessionId}/players/${guildId}/mix`, payload)
|
|
542
615
|
}
|
|
543
616
|
|
|
544
617
|
async getActiveMixer(guildId) {
|
|
545
618
|
if (!this.node.isNodelink) throw new Error('Mixer endpoints are only available on Nodelink nodes')
|
|
546
|
-
const response = await this.makeRequest(
|
|
619
|
+
const response = await this.makeRequest('GET', `/v4/sessions/${this.sessionId}/players/${guildId}/mix`)
|
|
547
620
|
return response?.mixes || []
|
|
548
621
|
}
|
|
549
622
|
|
|
@@ -551,17 +624,16 @@ class Rest {
|
|
|
551
624
|
if (!this.node.isNodelink) throw new Error('Mixer endpoints are only available on Nodelink nodes')
|
|
552
625
|
if (!guildId || !mix || typeof volume !== 'number') throw new Error('You forget to set the guild_id, mix or volume options')
|
|
553
626
|
|
|
554
|
-
return
|
|
627
|
+
return this.makeRequest('PATCH', `/v4/sessions/${this.sessionId}/players/${guildId}/mix/${mix}`, { volume })
|
|
555
628
|
}
|
|
556
629
|
|
|
557
630
|
async removeMixer(guildId, mix) {
|
|
558
631
|
if (!this.node.isNodelink) throw new Error('Mixer endpoints are only available on Nodelink nodes')
|
|
559
632
|
if (!guildId || !mix) throw new Error('You forget to set the guild_id and/or mix options')
|
|
560
633
|
|
|
561
|
-
return
|
|
634
|
+
return this.makeRequest('DELETE', `/v4/sessions/${this.sessionId}/players/${guildId}/mix/${mix}`)
|
|
562
635
|
}
|
|
563
636
|
|
|
564
|
-
|
|
565
637
|
destroy() {
|
|
566
638
|
if (this.agent) { this.agent.destroy(); this.agent = null }
|
|
567
639
|
this._closeH2()
|
|
@@ -117,8 +117,12 @@ class Track {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
_computeArtwork() {
|
|
120
|
+
if (this.artworkUrl) return this.artworkUrl
|
|
120
121
|
const id = this.identifier || (this.uri && YT_ID_REGEX.exec(this.uri)?.[1])
|
|
121
|
-
|
|
122
|
+
if (id && this.sourceName?.includes('youtube')) {
|
|
123
|
+
return `https://i.ytimg.com/vi/${id}/hqdefault.jpg`
|
|
124
|
+
}
|
|
125
|
+
return null
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aqualink",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "An Lavalink client, focused in pure performance and features",
|
|
3
|
+
"version": "2.18.0",
|
|
4
|
+
"description": "An Lavalink/Nodelink client, focused in pure performance and features",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"discord-integration",
|
|
26
26
|
"high-performance",
|
|
27
27
|
"scalable",
|
|
28
|
-
"
|
|
28
|
+
"nodelink",
|
|
29
29
|
"api",
|
|
30
30
|
"discord",
|
|
31
31
|
"lavalink.js",
|
|
@@ -68,4 +68,4 @@
|
|
|
68
68
|
"url": "https://github.com/ToddyTheNoobDud"
|
|
69
69
|
}
|
|
70
70
|
]
|
|
71
|
-
}
|
|
71
|
+
}
|