aqualink 2.17.0 → 2.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/build/index.d.ts +24 -4
- package/build/structures/Aqua.js +1 -1
- package/build/structures/AqualinkEvents.js +5 -4
- package/build/structures/Connection.js +111 -80
- package/build/structures/Player.js +81 -9
- package/build/structures/Rest.js +43 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -180,16 +180,18 @@ const aqua = new Aqua(client, nodes, {
|
|
|
180
180
|
|
|
181
181
|
client.aqua = aqua;
|
|
182
182
|
|
|
183
|
-
client.once(Events.
|
|
183
|
+
client.once(Events.ClientReady, () => {
|
|
184
184
|
client.aqua.init(client.user.id);
|
|
185
185
|
console.log(`Logged in as ${client.user.tag}`);
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
-
client.on(Events.Raw, (d) => {
|
|
189
|
-
if (
|
|
190
|
-
|
|
188
|
+
client.on(Events.Raw, (d, t) => {
|
|
189
|
+
if (d.t === "VOICE_SERVER_UPDATE" || d.t === "VOICE_STATE_UPDATE") {
|
|
190
|
+
return client.aqua.updateVoiceState(d, t);
|
|
191
|
+
}
|
|
191
192
|
});
|
|
192
193
|
|
|
194
|
+
|
|
193
195
|
client.on(Events.MessageCreate, async (message) => {
|
|
194
196
|
if (message.author.bot || !message.content.startsWith("!play")) return;
|
|
195
197
|
|
|
@@ -446,4 +448,4 @@ Join our thriving community of developers and bot creators!
|
|
|
446
448
|
|
|
447
449
|
<sub>Built with 💙 by the Aqualink Team</sub>
|
|
448
450
|
|
|
449
|
-
</div>
|
|
451
|
+
</div>
|
package/build/index.d.ts
CHANGED
|
@@ -38,8 +38,7 @@ declare module "aqualink" {
|
|
|
38
38
|
_rebuildLocks: Set<string>;
|
|
39
39
|
_leastUsedNodesCache: Node[] | null;
|
|
40
40
|
_leastUsedNodesCacheTime: number;
|
|
41
|
-
_nodeLoadCache: Map<string, number>;
|
|
42
|
-
_nodeLoadCacheTime: Map<string, number>;
|
|
41
|
+
_nodeLoadCache: Map<string, {load: number; time: number}>;
|
|
43
42
|
_cleanupTimer: NodeJS.Timer | null;
|
|
44
43
|
_onNodeConnect?: (node: Node) => void;
|
|
45
44
|
_onNodeDisconnect?: (node: Node) => void;
|
|
@@ -126,6 +125,7 @@ declare module "aqualink" {
|
|
|
126
125
|
infiniteReconnects: boolean;
|
|
127
126
|
connected: boolean;
|
|
128
127
|
info: NodeInfo | null;
|
|
128
|
+
isNodelink: boolean;
|
|
129
129
|
ws: any | null; // WebSocket
|
|
130
130
|
reconnectAttempted: number;
|
|
131
131
|
reconnectTimeoutId: NodeJS.Timeout | null;
|
|
@@ -198,6 +198,8 @@ declare module "aqualink" {
|
|
|
198
198
|
mute: boolean;
|
|
199
199
|
autoplayRetries: number;
|
|
200
200
|
reconnectionRetries: number;
|
|
201
|
+
_resuming: boolean;
|
|
202
|
+
_reconnecting: boolean;
|
|
201
203
|
previousIdentifiers: Set<string>;
|
|
202
204
|
self_deaf: boolean;
|
|
203
205
|
self_mute: boolean;
|
|
@@ -242,6 +244,11 @@ declare module "aqualink" {
|
|
|
242
244
|
setAutoplay(enabled: boolean): Player;
|
|
243
245
|
updatePlayer(data: any): Promise<any>;
|
|
244
246
|
cleanup(): Promise<void>;
|
|
247
|
+
getActiveMixer(guildId: string): Promise<any[]>;
|
|
248
|
+
updateMixerVolume(guildId: string, mix: string, volume: number): Promise<any>;
|
|
249
|
+
removeMixer(guildId: string, mix: string): Promise<any>;
|
|
250
|
+
addMixer(guildId: string, options: MixerOptions): Promise<any>;
|
|
251
|
+
getLoadLyrics(encodedTrack: string): Promise<LyricsResponse | null>;
|
|
245
252
|
|
|
246
253
|
// Data Methods
|
|
247
254
|
set(key: string, value: any): void;
|
|
@@ -276,7 +283,7 @@ declare module "aqualink" {
|
|
|
276
283
|
}
|
|
277
284
|
|
|
278
285
|
export class Track {
|
|
279
|
-
constructor(data?: TrackData, requester?: any,
|
|
286
|
+
constructor(data?: TrackData, requester?: any, node?: Node);
|
|
280
287
|
|
|
281
288
|
// Properties
|
|
282
289
|
identifier: string;
|
|
@@ -326,6 +333,7 @@ declare module "aqualink" {
|
|
|
326
333
|
baseUrl: string;
|
|
327
334
|
defaultHeaders: Record<string, string>;
|
|
328
335
|
agent: any; // HTTP/HTTPS Agent
|
|
336
|
+
useHttp2: boolean;
|
|
329
337
|
|
|
330
338
|
// Core Methods
|
|
331
339
|
setSessionId(sessionId: string): void;
|
|
@@ -347,6 +355,11 @@ declare module "aqualink" {
|
|
|
347
355
|
getRoutePlannerStatus(): Promise<any>;
|
|
348
356
|
freeRoutePlannerAddress(address: string): Promise<any>;
|
|
349
357
|
freeAllRoutePlannerAddresses(): Promise<any>;
|
|
358
|
+
addMixer(guildId: string, options: MixerOptions): Promise<any>;
|
|
359
|
+
getActiveMixer(guildId: string): Promise<any[]>;
|
|
360
|
+
updateMixerVolume(guildId: string, mix: string, volume: number): Promise<any>;
|
|
361
|
+
removeMixer(guildId: string, mix: string): Promise<any>;
|
|
362
|
+
getLoadLyrics(encodedTrack: string): Promise<LyricsResponse>;
|
|
350
363
|
destroy(): void;
|
|
351
364
|
}
|
|
352
365
|
|
|
@@ -452,7 +465,7 @@ declare module "aqualink" {
|
|
|
452
465
|
updateSequence(seq: number): void;
|
|
453
466
|
destroy(): void;
|
|
454
467
|
attemptResume(): Promise<boolean>;
|
|
455
|
-
resendVoiceUpdate(
|
|
468
|
+
resendVoiceUpdate(): boolean;
|
|
456
469
|
|
|
457
470
|
// Internal Methods
|
|
458
471
|
_extractRegion(endpoint: string): string | null;
|
|
@@ -736,6 +749,13 @@ declare module "aqualink" {
|
|
|
736
749
|
smoothing?: number;
|
|
737
750
|
}
|
|
738
751
|
|
|
752
|
+
export interface MixerOptions {
|
|
753
|
+
identifier?: string;
|
|
754
|
+
encoded?: string;
|
|
755
|
+
userData?: any;
|
|
756
|
+
volume?: number;
|
|
757
|
+
}
|
|
758
|
+
|
|
739
759
|
// Voice Update Interfaces
|
|
740
760
|
export interface VoiceStateUpdate {
|
|
741
761
|
d: {
|
package/build/structures/Aqua.js
CHANGED
|
@@ -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,16 +29,17 @@ 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
|
-
PauseEvent: 'pauseEvent'
|
|
40
|
+
PauseEvent: 'pauseEvent',
|
|
41
|
+
MixStarted: 'mixStarted',
|
|
42
|
+
MixEnded: 'mixEnded'
|
|
42
43
|
};
|
|
43
44
|
|
|
44
|
-
module.exports = { AqualinkEvents };
|
|
45
|
+
module.exports = { AqualinkEvents };
|
|
@@ -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
|
|
|
@@ -22,10 +22,12 @@ const EVENT_HANDLERS = Object.freeze({
|
|
|
22
22
|
FiltersChangedEvent: 'filtersChanged',
|
|
23
23
|
SeekEvent: 'seekEvent',
|
|
24
24
|
PlayerCreatedEvent: 'playerCreated',
|
|
25
|
-
|
|
25
|
+
PauseEvent: 'pauseEvent',
|
|
26
26
|
PlayerConnectedEvent: 'playerConnected',
|
|
27
27
|
PlayerDestroyedEvent: 'playerDestroyed',
|
|
28
|
-
LyricsNotFoundEvent: 'lyricsNotFound'
|
|
28
|
+
LyricsNotFoundEvent: 'lyricsNotFound',
|
|
29
|
+
MixStartedEvent: 'mixStarted',
|
|
30
|
+
MixEndedEvent: 'mixEnded'
|
|
29
31
|
})
|
|
30
32
|
|
|
31
33
|
const WATCHDOG_INTERVAL = 15000
|
|
@@ -285,11 +287,37 @@ class Player extends EventEmitter {
|
|
|
285
287
|
return this
|
|
286
288
|
}
|
|
287
289
|
|
|
290
|
+
async _waitForConnection(timeout = RESUME_TIMEOUT) {
|
|
291
|
+
if (this.destroyed) return
|
|
292
|
+
if (this.connected) return
|
|
293
|
+
return new Promise((resolve, reject) => {
|
|
294
|
+
let timer
|
|
295
|
+
const cleanup = () => {
|
|
296
|
+
if (timer) { this._pendingTimers?.delete(timer); clearTimeout(timer) }
|
|
297
|
+
this.off('playerUpdate', onUpdate)
|
|
298
|
+
}
|
|
299
|
+
const onUpdate = payload => {
|
|
300
|
+
if (this.destroyed) { cleanup(); return reject(new Error('Player destroyed')) }
|
|
301
|
+
if (payload?.state?.connected || _functions.isNum(payload?.state?.time)) {
|
|
302
|
+
cleanup()
|
|
303
|
+
return resolve()
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
this.on('playerUpdate', onUpdate)
|
|
307
|
+
timer = this._createTimer(() => { cleanup(); reject(new Error('No connection confirmation')) }, timeout)
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
288
311
|
async play() {
|
|
289
312
|
if (this.destroyed || !this.queue.size) return this
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
313
|
+
if (!this.connected) {
|
|
314
|
+
try {
|
|
315
|
+
await this._waitForConnection(RESUME_TIMEOUT)
|
|
316
|
+
if (!this.connected || this.destroyed) return this
|
|
317
|
+
} catch {
|
|
318
|
+
return this
|
|
319
|
+
}
|
|
320
|
+
}
|
|
293
321
|
|
|
294
322
|
const item = this.queue.dequeue()
|
|
295
323
|
if (!item) return this
|
|
@@ -313,7 +341,6 @@ class Player extends EventEmitter {
|
|
|
313
341
|
if (!voiceChannel) throw new TypeError('Voice channel required')
|
|
314
342
|
this.deaf = options.deaf !== undefined ? !!options.deaf : true
|
|
315
343
|
this.mute = !!options.mute
|
|
316
|
-
this.connected = true
|
|
317
344
|
this.destroyed = false
|
|
318
345
|
this.voiceChannel = voiceChannel
|
|
319
346
|
this.send({ guild_id: this.guildId, channel_id: voiceChannel, self_deaf: this.deaf, self_mute: this.mute })
|
|
@@ -435,11 +462,54 @@ class Player extends EventEmitter {
|
|
|
435
462
|
return this
|
|
436
463
|
}
|
|
437
464
|
|
|
465
|
+
async getActiveMixer(guildId) {
|
|
466
|
+
if (this.destroyed) return null
|
|
467
|
+
return await this.nodes.rest.getActiveMixer(guildId)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async updateMixerVolume(guildId, mix, volume) {
|
|
471
|
+
if (this.destroyed) return null
|
|
472
|
+
return await this.nodes.rest.updateMixerVolume(guildId, mix, volume)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async removeMixer(guildId, mix) {
|
|
476
|
+
if (this.destroyed) return null
|
|
477
|
+
return await this.nodes.rest.removeMixer(guildId, mix)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async addMixer(guildId, options) {
|
|
481
|
+
if (this.destroyed) return null
|
|
482
|
+
|
|
483
|
+
if (options.identifier && !options.encoded) {
|
|
484
|
+
try {
|
|
485
|
+
const resolved = await this.aqua.resolve({
|
|
486
|
+
query: options.identifier,
|
|
487
|
+
requester: options.requester || this.current?.requester
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
if (resolved?.tracks?.[0]) {
|
|
491
|
+
const track = resolved.tracks[0]
|
|
492
|
+
options = {
|
|
493
|
+
...options,
|
|
494
|
+
encoded: track.track || track.encoded,
|
|
495
|
+
userData: options.userData
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
throw new Error('Failed to resolve track identifier')
|
|
499
|
+
}
|
|
500
|
+
} catch (error) {
|
|
501
|
+
throw new Error(`Failed to resolve track: ${error.message}`)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return await this.nodes.rest.addMixer(guildId, options)
|
|
506
|
+
}
|
|
507
|
+
|
|
438
508
|
stop() {
|
|
439
509
|
if (this.destroyed || !this.playing) return this
|
|
440
510
|
this.playing = this.paused = false
|
|
441
511
|
this.position = 0
|
|
442
|
-
this.batchUpdatePlayer({ guildId: this.guildId, track: { encoded: null } }, true).catch(() => { })
|
|
512
|
+
this.batchUpdatePlayer({ guildId: this.guildId, track: { encoded: null, paused: this.paused } }, true).catch(() => { })
|
|
443
513
|
return this
|
|
444
514
|
}
|
|
445
515
|
|
|
@@ -659,7 +729,9 @@ class Player extends EventEmitter {
|
|
|
659
729
|
playerCreated(p, t, payload) { _functions.emitIfActive(this, AqualinkEvents.PlayerCreated, payload) }
|
|
660
730
|
playerConnected(p, t, payload) { _functions.emitIfActive(this, AqualinkEvents.PlayerConnected, payload) }
|
|
661
731
|
playerDestroyed(p, t, payload) { _functions.emitIfActive(this, AqualinkEvents.PlayerDestroyed, payload) }
|
|
662
|
-
|
|
732
|
+
pauseEvent(p, t, payload) { _functions.emitIfActive(this, AqualinkEvents.PauseEvent, payload) }
|
|
733
|
+
mixStarted(p, t, payload) { _functions.emitIfActive(this, AqualinkEvents.MixStarted, t, payload) }
|
|
734
|
+
mixEnded(p, t, payload) { _functions.emitIfActive(this, AqualinkEvents.MixEnded, t, payload) }
|
|
663
735
|
|
|
664
736
|
async _attemptVoiceResume() {
|
|
665
737
|
if (!this.connection?.sessionId) throw new Error('No session')
|
|
@@ -820,4 +892,4 @@ class Player extends EventEmitter {
|
|
|
820
892
|
}
|
|
821
893
|
}
|
|
822
894
|
|
|
823
|
-
module.exports = Player
|
|
895
|
+
module.exports = Player
|
package/build/structures/Rest.js
CHANGED
|
@@ -317,7 +317,7 @@ class Rest {
|
|
|
317
317
|
|
|
318
318
|
_closeH2() {
|
|
319
319
|
if (this._h2Timer) { clearTimeout(this._h2Timer); this._h2Timer = null }
|
|
320
|
-
if (this._h2) { try { this._h2.close() } catch {} this._h2 = null }
|
|
320
|
+
if (this._h2) { try { this._h2.close() } catch { } this._h2 = null }
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
_h2Request(method, path, headers, payload) {
|
|
@@ -480,14 +480,14 @@ class Rest {
|
|
|
480
480
|
try {
|
|
481
481
|
const lyrics = await this.makeRequest('GET', `${this._getSessionPath()}/players/${guildId}/track/lyrics?skipTrackSource=${skip}`)
|
|
482
482
|
if (this._validLyrics(lyrics)) return lyrics
|
|
483
|
-
} catch {}
|
|
483
|
+
} catch { }
|
|
484
484
|
}
|
|
485
485
|
|
|
486
486
|
if (hasEncoded) {
|
|
487
487
|
try {
|
|
488
488
|
const lyrics = await this.makeRequest('GET', `${this._endpoints.lyrics}?track=${encodeURIComponent(encoded)}&skipTrackSource=${skip}`)
|
|
489
489
|
if (this._validLyrics(lyrics)) return lyrics
|
|
490
|
-
} catch {}
|
|
490
|
+
} catch { }
|
|
491
491
|
}
|
|
492
492
|
|
|
493
493
|
if (title) {
|
|
@@ -495,7 +495,7 @@ class Rest {
|
|
|
495
495
|
try {
|
|
496
496
|
const lyrics = await this.makeRequest('GET', `${this._endpoints.lyrics}/search?query=${encodeURIComponent(query)}`)
|
|
497
497
|
if (this._validLyrics(lyrics)) return lyrics
|
|
498
|
-
} catch {}
|
|
498
|
+
} catch { }
|
|
499
499
|
}
|
|
500
500
|
|
|
501
501
|
return null
|
|
@@ -524,6 +524,44 @@ class Rest {
|
|
|
524
524
|
}
|
|
525
525
|
}
|
|
526
526
|
|
|
527
|
+
async addMixer(guildId, options) {
|
|
528
|
+
if (!this.node.isNodelink) throw new Error('Mixer endpoints are only available on Nodelink nodes')
|
|
529
|
+
if (!options?.encoded && !options?.identifier) throw new Error('You must provide either encoded or identifier')
|
|
530
|
+
|
|
531
|
+
const track = {}
|
|
532
|
+
if (options.encoded) track.encoded = options.encoded
|
|
533
|
+
if (options.identifier) track.identifier = options.identifier
|
|
534
|
+
if (options.userData) track.userData = options.userData
|
|
535
|
+
|
|
536
|
+
const payload = {
|
|
537
|
+
track,
|
|
538
|
+
volume: options.volume !== undefined ? options.volume : 0.8
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return await this.makeRequest("POST", `/v4/sessions/${this.sessionId}/players/${guildId}/mix`, payload)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async getActiveMixer(guildId) {
|
|
545
|
+
if (!this.node.isNodelink) throw new Error('Mixer endpoints are only available on Nodelink nodes')
|
|
546
|
+
const response = await this.makeRequest("GET", `/v4/sessions/${this.sessionId}/players/${guildId}/mix`)
|
|
547
|
+
return response?.mixes || []
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async updateMixerVolume(guildId, mix, volume) {
|
|
551
|
+
if (!this.node.isNodelink) throw new Error('Mixer endpoints are only available on Nodelink nodes')
|
|
552
|
+
if (!guildId || !mix || typeof volume !== 'number') throw new Error('You forget to set the guild_id, mix or volume options')
|
|
553
|
+
|
|
554
|
+
return await this.makeRequest("PATCH", `/v4/sessions/${this.sessionId}/players/${guildId}/mix/${mix}`, { volume })
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
async removeMixer(guildId, mix) {
|
|
558
|
+
if (!this.node.isNodelink) throw new Error('Mixer endpoints are only available on Nodelink nodes')
|
|
559
|
+
if (!guildId || !mix) throw new Error('You forget to set the guild_id and/or mix options')
|
|
560
|
+
|
|
561
|
+
return await this.makeRequest("DELETE", `/v4/sessions/${this.sessionId}/players/${guildId}/mix/${mix}`)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
|
|
527
565
|
destroy() {
|
|
528
566
|
if (this.agent) { this.agent.destroy(); this.agent = null }
|
|
529
567
|
this._closeH2()
|
|
@@ -532,4 +570,4 @@ class Rest {
|
|
|
532
570
|
}
|
|
533
571
|
}
|
|
534
572
|
|
|
535
|
-
module.exports = Rest
|
|
573
|
+
module.exports = Rest
|