aqualink 2.17.0 → 2.17.1
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
CHANGED
|
@@ -529,7 +529,7 @@ class Aqua extends EventEmitter {
|
|
|
529
529
|
_handlePlayerDestroy(player) {
|
|
530
530
|
player.nodes?.players?.delete?.(player)
|
|
531
531
|
if (this.players.get(player.guildId) === player) this.players.delete(player.guildId)
|
|
532
|
-
this.emit(AqualinkEvents.
|
|
532
|
+
this.emit(AqualinkEvents.PlayerDestroyed, player)
|
|
533
533
|
}
|
|
534
534
|
|
|
535
535
|
async destroyPlayer(guildId) {
|
|
@@ -29,14 +29,13 @@ const AqualinkEvents = {
|
|
|
29
29
|
Debug: 'debug',
|
|
30
30
|
Error: 'error',
|
|
31
31
|
PlayerCreate: 'playerCreate',
|
|
32
|
-
PlayerDestroy: 'playerDestroy',
|
|
33
32
|
PlayersRebuilt: 'playersRebuilt',
|
|
34
33
|
VolumeChanged: 'volumeChanged',
|
|
35
34
|
FiltersChanged: 'filtersChanged',
|
|
36
35
|
Seek: 'seek',
|
|
37
36
|
PlayerCreated: 'playerCreated',
|
|
38
37
|
PlayerConnected: 'playerConnected',
|
|
39
|
-
PlayerDestroyed: '
|
|
38
|
+
PlayerDestroyed: 'playerDestroy',
|
|
40
39
|
PlayerMigrated: 'playerMigrated',
|
|
41
40
|
PauseEvent: 'pauseEvent'
|
|
42
41
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {AqualinkEvents} = require('./AqualinkEvents')
|
|
3
|
+
const { AqualinkEvents } = require('./AqualinkEvents')
|
|
4
4
|
|
|
5
5
|
const POOL_SIZE = 12
|
|
6
6
|
const UPDATE_TIMEOUT = 4000
|
|
@@ -17,15 +17,46 @@ const STATE = {
|
|
|
17
17
|
VOICE_DATA_STALE: 512
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const ENDPOINT_REGION_REGEX = /^([a-z-]+)\d*/i
|
|
21
|
-
|
|
22
20
|
const _functions = {
|
|
23
|
-
safeUnref: t => t?.unref
|
|
21
|
+
safeUnref: t => (typeof t?.unref === 'function' ? t.unref() : undefined),
|
|
24
22
|
isValidNumber: n => typeof n === 'number' && n >= 0 && Number.isFinite(n),
|
|
25
|
-
isNetworkError: e => e && (e.code === 'ECONNREFUSED' || e.code === 'ENOTFOUND' || e.code === 'ETIMEDOUT'),
|
|
23
|
+
isNetworkError: e => !!e && (e.code === 'ECONNREFUSED' || e.code === 'ENOTFOUND' || e.code === 'ETIMEDOUT'),
|
|
26
24
|
extractRegion: endpoint => {
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
if (typeof endpoint !== 'string') return 'unknown'
|
|
26
|
+
endpoint = endpoint.trim()
|
|
27
|
+
if (!endpoint) return 'unknown'
|
|
28
|
+
|
|
29
|
+
const proto = endpoint.indexOf('://')
|
|
30
|
+
if (proto !== -1) endpoint = endpoint.slice(proto + 3)
|
|
31
|
+
|
|
32
|
+
const slash = endpoint.indexOf('/')
|
|
33
|
+
if (slash !== -1) endpoint = endpoint.slice(0, slash)
|
|
34
|
+
|
|
35
|
+
const colon = endpoint.indexOf(':')
|
|
36
|
+
if (colon !== -1) endpoint = endpoint.slice(0, colon)
|
|
37
|
+
|
|
38
|
+
const dot = endpoint.indexOf('.')
|
|
39
|
+
const label = (dot === -1 ? endpoint : endpoint.slice(0, dot)).toLowerCase()
|
|
40
|
+
if (!label) return 'unknown'
|
|
41
|
+
|
|
42
|
+
let i = label.length - 1
|
|
43
|
+
while (i >= 0) {
|
|
44
|
+
const c = label.charCodeAt(i)
|
|
45
|
+
if (c >= 48 && c <= 57) i--
|
|
46
|
+
else break
|
|
47
|
+
}
|
|
48
|
+
return label.slice(0, i + 1) || 'unknown'
|
|
49
|
+
},
|
|
50
|
+
fillVoicePayload: (payload, guildId, conn, player, resume) => {
|
|
51
|
+
payload.guildId = guildId
|
|
52
|
+
const v = payload.data.voice
|
|
53
|
+
v.token = conn.token
|
|
54
|
+
v.endpoint = conn.endpoint
|
|
55
|
+
v.sessionId = conn.sessionId
|
|
56
|
+
v.resume = resume ? true : undefined
|
|
57
|
+
v.sequence = resume ? conn.sequence : undefined
|
|
58
|
+
payload.data.volume = player?.volume ?? 100
|
|
59
|
+
return payload
|
|
29
60
|
}
|
|
30
61
|
}
|
|
31
62
|
|
|
@@ -39,7 +70,7 @@ class PayloadPool {
|
|
|
39
70
|
return {
|
|
40
71
|
guildId: null,
|
|
41
72
|
data: {
|
|
42
|
-
voice: {token: null, endpoint: null, sessionId: null, resume: undefined, sequence: undefined},
|
|
73
|
+
voice: { token: null, endpoint: null, sessionId: null, resume: undefined, sequence: undefined },
|
|
43
74
|
volume: null
|
|
44
75
|
}
|
|
45
76
|
}
|
|
@@ -69,21 +100,21 @@ const sharedPool = new PayloadPool()
|
|
|
69
100
|
|
|
70
101
|
class Connection {
|
|
71
102
|
constructor(player) {
|
|
72
|
-
if (!player?.aqua?.clientId || !player.nodes?.rest)
|
|
73
|
-
throw new TypeError('Invalid player configuration')
|
|
74
|
-
}
|
|
103
|
+
if (!player?.aqua?.clientId || !player.nodes?.rest) throw new TypeError('Invalid player configuration')
|
|
75
104
|
|
|
76
105
|
this._player = player
|
|
77
106
|
this._aqua = player.aqua
|
|
78
107
|
this._rest = player.nodes.rest
|
|
79
108
|
this._guildId = player.guildId
|
|
80
109
|
this._clientId = player.aqua.clientId
|
|
110
|
+
|
|
81
111
|
this.voiceChannel = player.voiceChannel
|
|
82
112
|
this.sessionId = null
|
|
83
113
|
this.endpoint = null
|
|
84
114
|
this.token = null
|
|
85
115
|
this.region = null
|
|
86
116
|
this.sequence = 0
|
|
117
|
+
|
|
87
118
|
this._lastEndpoint = null
|
|
88
119
|
this._pendingUpdate = null
|
|
89
120
|
this._stateFlags = 0
|
|
@@ -109,21 +140,29 @@ class Connection {
|
|
|
109
140
|
return this._hasValidVoiceData() || !!this._player?._resuming
|
|
110
141
|
}
|
|
111
142
|
|
|
143
|
+
_setReconnectTimer(delay) {
|
|
144
|
+
if (this._destroyed) return
|
|
145
|
+
this._clearReconnectTimer()
|
|
146
|
+
this._reconnectTimer = setTimeout(() => this._handleReconnect(), delay)
|
|
147
|
+
_functions.safeUnref(this._reconnectTimer)
|
|
148
|
+
}
|
|
149
|
+
|
|
112
150
|
setServerUpdate(data) {
|
|
113
151
|
if (this._destroyed || !data?.endpoint || !data.token) return
|
|
114
|
-
|
|
152
|
+
|
|
153
|
+
const endpoint = typeof data.endpoint === 'string' ? data.endpoint.trim() : ''
|
|
115
154
|
if (!endpoint || typeof data.token !== 'string' || !data.token) return
|
|
116
155
|
if (this._lastEndpoint === endpoint && this.token === data.token) return
|
|
117
156
|
|
|
118
|
-
const newRegion = _functions.extractRegion(endpoint)
|
|
119
157
|
if (this._lastEndpoint !== endpoint) {
|
|
120
158
|
this.sequence = 0
|
|
121
159
|
this._lastEndpoint = endpoint
|
|
122
160
|
this._reconnectAttempts = 0
|
|
123
161
|
this._consecutiveFailures = 0
|
|
124
162
|
}
|
|
163
|
+
|
|
125
164
|
this.endpoint = endpoint
|
|
126
|
-
this.region =
|
|
165
|
+
this.region = _functions.extractRegion(endpoint)
|
|
127
166
|
this.token = data.token
|
|
128
167
|
this._lastVoiceDataUpdate = Date.now()
|
|
129
168
|
this._stateFlags &= ~STATE.VOICE_DATA_STALE
|
|
@@ -141,37 +180,35 @@ class Connection {
|
|
|
141
180
|
setStateUpdate(data) {
|
|
142
181
|
if (this._destroyed || !data || data.user_id !== this._clientId) return
|
|
143
182
|
|
|
144
|
-
const {session_id: sessionId, channel_id: channelId, self_deaf: selfDeaf, self_mute: selfMute} = data
|
|
183
|
+
const { session_id: sessionId, channel_id: channelId, self_deaf: selfDeaf, self_mute: selfMute } = data
|
|
145
184
|
|
|
146
|
-
if (channelId)
|
|
147
|
-
let needsUpdate = false
|
|
185
|
+
if (!channelId) return this._handleDisconnect()
|
|
148
186
|
|
|
149
|
-
|
|
150
|
-
this._aqua.emit(AqualinkEvents.PlayerMove, this.voiceChannel, channelId)
|
|
151
|
-
this.voiceChannel = channelId
|
|
152
|
-
this._player.voiceChannel = channelId
|
|
153
|
-
needsUpdate = true
|
|
154
|
-
}
|
|
187
|
+
let needsUpdate = false
|
|
155
188
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
needsUpdate = true
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
this._player.connection.sessionId = sessionId || this._player.connection.sessionId
|
|
166
|
-
this._player.self_deaf = this._player.selfDeaf = !!selfDeaf
|
|
167
|
-
this._player.self_mute = this._player.selfMute = !!selfMute
|
|
168
|
-
this._player.connected = true
|
|
169
|
-
this._stateFlags |= STATE.CONNECTED
|
|
189
|
+
if (this.voiceChannel !== channelId) {
|
|
190
|
+
this._aqua.emit(AqualinkEvents.PlayerMove, this.voiceChannel, channelId)
|
|
191
|
+
this.voiceChannel = channelId
|
|
192
|
+
this._player.voiceChannel = channelId
|
|
193
|
+
needsUpdate = true
|
|
194
|
+
}
|
|
170
195
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
this.
|
|
196
|
+
if (this.sessionId !== sessionId) {
|
|
197
|
+
this.sessionId = sessionId
|
|
198
|
+
this._lastVoiceDataUpdate = Date.now()
|
|
199
|
+
this._stateFlags &= ~STATE.VOICE_DATA_STALE
|
|
200
|
+
this._reconnectAttempts = 0
|
|
201
|
+
this._consecutiveFailures = 0
|
|
202
|
+
needsUpdate = true
|
|
174
203
|
}
|
|
204
|
+
|
|
205
|
+
this._player.connection.sessionId = sessionId || this._player.connection.sessionId
|
|
206
|
+
this._player.self_deaf = this._player.selfDeaf = !!selfDeaf
|
|
207
|
+
this._player.self_mute = this._player.selfMute = !!selfMute
|
|
208
|
+
this._player.connected = true
|
|
209
|
+
this._stateFlags |= STATE.CONNECTED
|
|
210
|
+
|
|
211
|
+
if (needsUpdate) this._scheduleVoiceUpdate()
|
|
175
212
|
}
|
|
176
213
|
|
|
177
214
|
_handleDisconnect() {
|
|
@@ -183,7 +220,8 @@ class Connection {
|
|
|
183
220
|
this._clearReconnectTimer()
|
|
184
221
|
|
|
185
222
|
this.voiceChannel = this.sessionId = null
|
|
186
|
-
this.sequence =
|
|
223
|
+
this.sequence = 0
|
|
224
|
+
this._lastVoiceDataUpdate = 0
|
|
187
225
|
this._stateFlags |= STATE.VOICE_DATA_STALE
|
|
188
226
|
|
|
189
227
|
try {
|
|
@@ -197,19 +235,18 @@ class Connection {
|
|
|
197
235
|
|
|
198
236
|
_requestVoiceState() {
|
|
199
237
|
try {
|
|
200
|
-
if (typeof this._player?.send
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
return false
|
|
238
|
+
if (typeof this._player?.send !== 'function' || !this._player.voiceChannel) return false
|
|
239
|
+
this._player.send({
|
|
240
|
+
guild_id: this._guildId,
|
|
241
|
+
channel_id: this._player.voiceChannel,
|
|
242
|
+
self_deaf: this._player.deaf,
|
|
243
|
+
self_mute: this._player.mute
|
|
244
|
+
})
|
|
245
|
+
this._setReconnectTimer(1500)
|
|
246
|
+
return true
|
|
247
|
+
} catch {
|
|
248
|
+
return false
|
|
249
|
+
}
|
|
213
250
|
}
|
|
214
251
|
|
|
215
252
|
async attemptResume() {
|
|
@@ -240,21 +277,18 @@ class Connection {
|
|
|
240
277
|
|
|
241
278
|
this._stateFlags |= STATE.ATTEMPTING_RESUME
|
|
242
279
|
this._reconnectAttempts++
|
|
243
|
-
this._aqua.emit(
|
|
280
|
+
this._aqua.emit(
|
|
281
|
+
AqualinkEvents.Debug,
|
|
282
|
+
`Attempt resume: guild=${this._guildId} endpoint=${this.endpoint} session=${this.sessionId}`
|
|
283
|
+
)
|
|
244
284
|
|
|
245
285
|
const payload = sharedPool.acquire()
|
|
246
286
|
try {
|
|
247
|
-
payload.
|
|
248
|
-
const v = payload.data.voice
|
|
249
|
-
v.token = this.token
|
|
250
|
-
v.endpoint = this.endpoint
|
|
251
|
-
v.sessionId = this.sessionId
|
|
252
|
-
v.resume = true
|
|
253
|
-
v.sequence = this.sequence
|
|
254
|
-
payload.data.volume = this._player?.volume ?? 100
|
|
255
|
-
|
|
287
|
+
_functions.fillVoicePayload(payload, this._guildId, this, this._player, true)
|
|
256
288
|
await this._sendUpdate(payload)
|
|
257
|
-
|
|
289
|
+
|
|
290
|
+
this._reconnectAttempts = 0
|
|
291
|
+
this._consecutiveFailures = 0
|
|
258
292
|
if (this._player) this._player._resuming = false
|
|
259
293
|
this._aqua.emit(AqualinkEvents.Debug, `Resume successful for guild ${this._guildId}`)
|
|
260
294
|
return true
|
|
@@ -264,8 +298,7 @@ class Connection {
|
|
|
264
298
|
|
|
265
299
|
if (this._reconnectAttempts < MAX_RECONNECT_ATTEMPTS && !this._destroyed && this._consecutiveFailures < 5) {
|
|
266
300
|
const delay = Math.min(RECONNECT_DELAY * (1 << (this._reconnectAttempts - 1)), RESUME_BACKOFF_MAX)
|
|
267
|
-
this.
|
|
268
|
-
_functions.safeUnref(this._reconnectTimer)
|
|
301
|
+
this._setReconnectTimer(delay)
|
|
269
302
|
} else {
|
|
270
303
|
this._aqua.emit(AqualinkEvents.Debug, `Max reconnect attempts or failures reached for guild ${this._guildId}`)
|
|
271
304
|
if (this._player) this._player._resuming = false
|
|
@@ -288,10 +321,9 @@ class Connection {
|
|
|
288
321
|
}
|
|
289
322
|
|
|
290
323
|
_clearReconnectTimer() {
|
|
291
|
-
if (this._reconnectTimer)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
324
|
+
if (!this._reconnectTimer) return
|
|
325
|
+
clearTimeout(this._reconnectTimer)
|
|
326
|
+
this._reconnectTimer = null
|
|
295
327
|
}
|
|
296
328
|
|
|
297
329
|
_clearPendingUpdate() {
|
|
@@ -304,16 +336,11 @@ class Connection {
|
|
|
304
336
|
if (this._destroyed || !this._hasValidVoiceData() || (this._stateFlags & STATE.UPDATE_SCHEDULED)) return
|
|
305
337
|
|
|
306
338
|
this._clearPendingUpdate()
|
|
339
|
+
|
|
307
340
|
const payload = sharedPool.acquire()
|
|
308
|
-
payload.
|
|
309
|
-
const v = payload.data.voice
|
|
310
|
-
v.token = this.token
|
|
311
|
-
v.endpoint = this.endpoint
|
|
312
|
-
v.sessionId = this.sessionId
|
|
313
|
-
v.resume = v.sequence = undefined
|
|
314
|
-
payload.data.volume = this._player.volume
|
|
341
|
+
_functions.fillVoicePayload(payload, this._guildId, this, this._player, false)
|
|
315
342
|
|
|
316
|
-
this._pendingUpdate = {payload, timestamp: Date.now()}
|
|
343
|
+
this._pendingUpdate = { payload, timestamp: Date.now() }
|
|
317
344
|
this._stateFlags |= STATE.UPDATE_SCHEDULED
|
|
318
345
|
queueMicrotask(() => this._executeVoiceUpdate())
|
|
319
346
|
}
|
|
@@ -358,7 +385,11 @@ class Connection {
|
|
|
358
385
|
|
|
359
386
|
this._player = this._aqua = this._rest = null
|
|
360
387
|
this.voiceChannel = this.sessionId = this.endpoint = this.token = this.region = this._lastEndpoint = null
|
|
361
|
-
this._stateFlags =
|
|
388
|
+
this._stateFlags = 0
|
|
389
|
+
this.sequence = 0
|
|
390
|
+
this._reconnectAttempts = 0
|
|
391
|
+
this._consecutiveFailures = 0
|
|
392
|
+
this._lastVoiceDataUpdate = 0
|
|
362
393
|
}
|
|
363
394
|
}
|
|
364
395
|
|
|
@@ -285,11 +285,37 @@ class Player extends EventEmitter {
|
|
|
285
285
|
return this
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
async _waitForConnection(timeout = RESUME_TIMEOUT) {
|
|
289
|
+
if (this.destroyed) return
|
|
290
|
+
if (this.connected) return
|
|
291
|
+
return new Promise((resolve, reject) => {
|
|
292
|
+
let timer
|
|
293
|
+
const cleanup = () => {
|
|
294
|
+
if (timer) { this._pendingTimers?.delete(timer); clearTimeout(timer) }
|
|
295
|
+
this.off('playerUpdate', onUpdate)
|
|
296
|
+
}
|
|
297
|
+
const onUpdate = payload => {
|
|
298
|
+
if (this.destroyed) { cleanup(); return reject(new Error('Player destroyed')) }
|
|
299
|
+
if (payload?.state?.connected || _functions.isNum(payload?.state?.time)) {
|
|
300
|
+
cleanup()
|
|
301
|
+
return resolve()
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
this.on('playerUpdate', onUpdate)
|
|
305
|
+
timer = this._createTimer(() => { cleanup(); reject(new Error('No connection confirmation')) }, timeout)
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
|
|
288
309
|
async play() {
|
|
289
310
|
if (this.destroyed || !this.queue.size) return this
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
311
|
+
if (!this.connected) {
|
|
312
|
+
try {
|
|
313
|
+
await this._waitForConnection(RESUME_TIMEOUT)
|
|
314
|
+
if (!this.connected || this.destroyed) return this
|
|
315
|
+
} catch {
|
|
316
|
+
return this
|
|
317
|
+
}
|
|
318
|
+
}
|
|
293
319
|
|
|
294
320
|
const item = this.queue.dequeue()
|
|
295
321
|
if (!item) return this
|
|
@@ -313,7 +339,6 @@ class Player extends EventEmitter {
|
|
|
313
339
|
if (!voiceChannel) throw new TypeError('Voice channel required')
|
|
314
340
|
this.deaf = options.deaf !== undefined ? !!options.deaf : true
|
|
315
341
|
this.mute = !!options.mute
|
|
316
|
-
this.connected = true
|
|
317
342
|
this.destroyed = false
|
|
318
343
|
this.voiceChannel = voiceChannel
|
|
319
344
|
this.send({ guild_id: this.guildId, channel_id: voiceChannel, self_deaf: this.deaf, self_mute: this.mute })
|