aqualink 1.7.0-beta4 → 1.7.0-beta5
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/handlers/fetchImage.js +12 -21
- package/build/structures/Aqua.js +42 -40
- package/build/structures/Connection.js +87 -92
- package/build/structures/Node.js +23 -54
- package/build/structures/Player.js +74 -72
- package/build/structures/Track.js +29 -39
- package/package.json +2 -2
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
const { request } = require("undici");
|
|
2
2
|
|
|
3
3
|
const sourceHandlers = new Map([
|
|
4
|
-
['spotify',
|
|
5
|
-
['youtube',
|
|
4
|
+
['spotify', uri => fetchThumbnail(`https://open.spotify.com/oembed?url=${uri}`)],
|
|
5
|
+
['youtube', identifier => fetchYouTubeThumbnail(identifier)]
|
|
6
6
|
]);
|
|
7
7
|
|
|
8
|
-
const YOUTUBE_URL_TEMPLATE =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const YOUTUBE_QUALITIES = [
|
|
12
|
-
'maxresdefault',
|
|
13
|
-
'hqdefault',
|
|
14
|
-
'mqdefault',
|
|
15
|
-
'default'
|
|
16
|
-
].map(YOUTUBE_URL_TEMPLATE);
|
|
8
|
+
const YOUTUBE_URL_TEMPLATE = quality => id => `https://img.youtube.com/vi/${id}/${quality}.jpg`;
|
|
9
|
+
const YOUTUBE_QUALITIES = ['maxresdefault', 'hqdefault', 'mqdefault', 'default'].map(YOUTUBE_URL_TEMPLATE);
|
|
17
10
|
|
|
18
11
|
async function getImageUrl(info) {
|
|
19
12
|
if (!info?.sourceName || !info?.uri) return null;
|
|
@@ -23,7 +16,8 @@ async function getImageUrl(info) {
|
|
|
23
16
|
|
|
24
17
|
try {
|
|
25
18
|
return await handler(info.uri);
|
|
26
|
-
} catch {
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error('Error fetching image URL:', error);
|
|
27
21
|
return null;
|
|
28
22
|
}
|
|
29
23
|
}
|
|
@@ -32,26 +26,23 @@ async function fetchThumbnail(url) {
|
|
|
32
26
|
try {
|
|
33
27
|
const { body } = await request(url, {
|
|
34
28
|
method: "GET",
|
|
35
|
-
headers: {
|
|
36
|
-
'Accept': 'application/json'
|
|
37
|
-
}
|
|
29
|
+
headers: { 'Accept': 'application/json' }
|
|
38
30
|
});
|
|
39
|
-
|
|
40
31
|
const json = await body.json();
|
|
41
32
|
return json.thumbnail_url || null;
|
|
42
|
-
} catch {
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Error fetching thumbnail:', error);
|
|
43
35
|
return null;
|
|
44
36
|
}
|
|
45
37
|
}
|
|
46
38
|
|
|
47
39
|
async function fetchYouTubeThumbnail(identifier) {
|
|
48
|
-
const fetchPromises = YOUTUBE_QUALITIES.map(urlFunc =>
|
|
49
|
-
fetchThumbnail(urlFunc(identifier))
|
|
50
|
-
);
|
|
40
|
+
const fetchPromises = YOUTUBE_QUALITIES.map(urlFunc => fetchThumbnail(urlFunc(identifier)));
|
|
51
41
|
|
|
52
42
|
try {
|
|
53
43
|
return await Promise.race(fetchPromises);
|
|
54
|
-
} catch {
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Error fetching YouTube thumbnail:', error);
|
|
55
46
|
return null;
|
|
56
47
|
}
|
|
57
48
|
}
|
package/build/structures/Aqua.js
CHANGED
|
@@ -4,8 +4,9 @@ const { Player } = require("./Player");
|
|
|
4
4
|
const { Track } = require("./Track");
|
|
5
5
|
const { version: pkgVersion } = require("../../package.json");
|
|
6
6
|
const URL_REGEX = /^https?:\/\//;
|
|
7
|
+
|
|
7
8
|
class Aqua extends EventEmitter {
|
|
8
|
-
|
|
9
|
+
/**
|
|
9
10
|
* @param {Object} client - The client instance.
|
|
10
11
|
* @param {Array<Object>} nodes - An array of node configurations.
|
|
11
12
|
* @param {Object} options - Configuration options for Aqua.
|
|
@@ -47,7 +48,6 @@ class Aqua extends EventEmitter {
|
|
|
47
48
|
this.setMaxListeners(0);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
51
|
validateInputs(client, nodes, options) {
|
|
52
52
|
if (!client) throw new Error("Client is required to initialize Aqua");
|
|
53
53
|
if (!Array.isArray(nodes) || !nodes.length) throw new Error(`Nodes must be a non-empty Array (Received ${typeof nodes})`);
|
|
@@ -55,8 +55,7 @@ class Aqua extends EventEmitter {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
get leastUsedNodes() {
|
|
58
|
-
|
|
59
|
-
return activeNodes.length ? activeNodes.sort((a, b) => a.rest.calls - b.rest.calls) : [];
|
|
58
|
+
return [...this.nodeMap.values()].filter(node => node.connected).sort((a, b) => a.rest.calls - b.rest.calls);
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
init(clientId) {
|
|
@@ -78,41 +77,42 @@ class Aqua extends EventEmitter {
|
|
|
78
77
|
this.destroyNode(nodeId); // Ensure no duplicate nodes
|
|
79
78
|
const node = new Node(this, options, this.options);
|
|
80
79
|
this.nodeMap.set(nodeId, node);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
80
|
+
node.connect()
|
|
81
|
+
.then(() => this.emit("nodeCreate", node))
|
|
82
|
+
.catch(error => {
|
|
83
|
+
this.nodeMap.delete(nodeId);
|
|
84
|
+
throw error;
|
|
85
|
+
});
|
|
86
|
+
return node;
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
destroyNode(identifier) {
|
|
92
90
|
const node = this.nodeMap.get(identifier);
|
|
93
91
|
if (!node) return;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
92
|
+
|
|
93
|
+
node.disconnect()
|
|
94
|
+
.then(() => {
|
|
95
|
+
node.removeAllListeners();
|
|
96
|
+
this.nodeMap.delete(identifier);
|
|
97
|
+
this.emit("nodeDestroy", node);
|
|
98
|
+
})
|
|
99
|
+
.catch(error => console.error(`Error destroying node ${identifier}:`, error));
|
|
102
100
|
}
|
|
103
101
|
|
|
104
102
|
updateVoiceState({ d, t }) {
|
|
105
103
|
const player = this.players.get(d.guild_id);
|
|
106
|
-
if (player && (t === "VOICE_SERVER_UPDATE" || t === "VOICE_STATE_UPDATE" && d.user_id === this.clientId)) {
|
|
104
|
+
if (player && (t === "VOICE_SERVER_UPDATE" || (t === "VOICE_STATE_UPDATE" && d.user_id === this.clientId))) {
|
|
107
105
|
player.connection[t === "VOICE_SERVER_UPDATE" ? "setServerUpdate" : "setStateUpdate"](d);
|
|
108
106
|
if (d.status === "disconnected") this.cleanupPlayer(player);
|
|
109
107
|
}
|
|
110
108
|
}
|
|
109
|
+
|
|
111
110
|
fetchRegion(region) {
|
|
112
111
|
if (!region) return this.leastUsedNodes;
|
|
113
112
|
const lowerRegion = region.toLowerCase();
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
return [...this.nodeMap.values()]
|
|
114
|
+
.filter(node => node.connected && node.regions?.includes(lowerRegion))
|
|
115
|
+
.sort((a, b) => this.calculateLoad(a) - this.calculateLoad(b));
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
calculateLoad(node) {
|
|
@@ -125,6 +125,7 @@ class Aqua extends EventEmitter {
|
|
|
125
125
|
this.ensureInitialized();
|
|
126
126
|
const player = this.players.get(options.guildId);
|
|
127
127
|
if (player && player.voiceChannel) return player;
|
|
128
|
+
|
|
128
129
|
const node = options.region ? this.fetchRegion(options.region)[0] : this.leastUsedNodes[0];
|
|
129
130
|
if (!node) throw new Error("No nodes are available");
|
|
130
131
|
return this.createPlayer(node, options);
|
|
@@ -140,22 +141,21 @@ class Aqua extends EventEmitter {
|
|
|
140
141
|
return player;
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
async destroyPlayer(guildId) {
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
async destroyPlayer(guildId) {
|
|
145
|
+
const player = this.players.get(guildId);
|
|
146
|
+
if (!player) return;
|
|
146
147
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
try {
|
|
149
|
+
await player.clearData(); // Assuming clearData is an async function
|
|
150
|
+
player.removeAllListeners();
|
|
151
|
+
this.players.delete(guildId);
|
|
152
|
+
this.emit("playerDestroy", player);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error(`Error destroying player for guild ${guildId}:`, error);
|
|
155
|
+
}
|
|
155
156
|
}
|
|
156
|
-
}
|
|
157
157
|
|
|
158
|
-
async resolve({ query, source = this.defaultSearchPlatform
|
|
158
|
+
async resolve({ query, source = this.defaultSearchPlatform, requester, nodes }) {
|
|
159
159
|
this.ensureInitialized();
|
|
160
160
|
const requestNode = this.getRequestNode(nodes);
|
|
161
161
|
const formattedQuery = this.formatQuery(query, source);
|
|
@@ -188,11 +188,11 @@ async destroyPlayer(guildId) {
|
|
|
188
188
|
return URL_REGEX.test(query) ? query : `${source}:${query}`;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
async handleNoMatches(rest, query) {
|
|
192
192
|
try {
|
|
193
|
-
const youtubeResponse =
|
|
193
|
+
const youtubeResponse = await rest.makeRequest("GET", `/v4/loadtracks?identifier=https://www.youtube.com/watch?v=${query}`);
|
|
194
194
|
if (["empty", "NO_MATCHES"].includes(youtubeResponse.loadType)) {
|
|
195
|
-
return
|
|
195
|
+
return await rest.makeRequest("GET", `/v4/loadtracks?identifier=https://open.spotify.com/track/${query}`);
|
|
196
196
|
}
|
|
197
197
|
return youtubeResponse;
|
|
198
198
|
} catch (error) {
|
|
@@ -208,10 +208,12 @@ async destroyPlayer(guildId) {
|
|
|
208
208
|
pluginInfo: response.pluginInfo ?? {},
|
|
209
209
|
tracks: [],
|
|
210
210
|
};
|
|
211
|
+
|
|
211
212
|
if (response.loadType === "error" || response.loadType === "LOAD_FAILED") {
|
|
212
213
|
baseResponse.exception = response.data ?? response.exception;
|
|
213
214
|
return baseResponse;
|
|
214
215
|
}
|
|
216
|
+
|
|
215
217
|
const trackFactory = (trackData) => new Track(trackData, requester, requestNode);
|
|
216
218
|
switch (response.loadType) {
|
|
217
219
|
case "track":
|
|
@@ -283,4 +285,4 @@ async destroyPlayer(guildId) {
|
|
|
283
285
|
}
|
|
284
286
|
}
|
|
285
287
|
|
|
286
|
-
module.exports = { Aqua };
|
|
288
|
+
module.exports = { Aqua };
|
|
@@ -1,106 +1,101 @@
|
|
|
1
1
|
class Connection {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
constructor(player) {
|
|
3
|
+
this.player = player;
|
|
4
|
+
this.voice = {
|
|
5
|
+
sessionId: null,
|
|
6
|
+
endpoint: null,
|
|
7
|
+
token: null
|
|
8
|
+
};
|
|
9
|
+
this.region = null;
|
|
10
|
+
this.selfDeaf = false;
|
|
11
|
+
this.selfMute = false;
|
|
12
|
+
this.voiceChannel = player.voiceChannel;
|
|
13
|
+
this.guildId = player.guildId;
|
|
14
|
+
this.aqua = player.aqua;
|
|
15
|
+
this.nodes = player.nodes;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setServerUpdate(data) {
|
|
19
|
+
if (!data?.endpoint) {
|
|
20
|
+
throw new Error("Missing 'endpoint' property");
|
|
14
21
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
|
|
23
|
+
const endpoint = data.endpoint;
|
|
24
|
+
const regionMatch = endpoint.match(/^([a-zA-Z]+)/);
|
|
25
|
+
if (!regionMatch) return;
|
|
26
|
+
|
|
27
|
+
const newRegion = regionMatch[1];
|
|
28
|
+
if (this.region !== newRegion) {
|
|
29
|
+
this.voice.endpoint = endpoint;
|
|
30
|
+
this.voice.token = data.token;
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
`[Player ${this._guildId} - CONNECTION] ${
|
|
37
|
-
prevRegion
|
|
38
|
-
? `Changed Voice Region from ${prevRegion} to ${newRegion}`
|
|
39
|
-
: `Voice Server: ${newRegion}`
|
|
40
|
-
}`
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
this._updatePlayerVoiceData();
|
|
32
|
+
const prevRegion = this.region;
|
|
33
|
+
this.region = newRegion;
|
|
34
|
+
|
|
35
|
+
this.aqua.emit(
|
|
36
|
+
"debug",
|
|
37
|
+
`[Player ${this.guildId} - CONNECTION] ${
|
|
38
|
+
prevRegion
|
|
39
|
+
? `Changed Voice Region from ${prevRegion} to ${newRegion}`
|
|
40
|
+
: `Voice Server: ${newRegion}`
|
|
41
|
+
}`
|
|
42
|
+
);
|
|
45
43
|
}
|
|
46
|
-
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const sessionId = data.session_id;
|
|
51
|
-
|
|
52
|
-
if (!channelId || !sessionId) {
|
|
53
|
-
this._cleanup();
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
45
|
+
this._updatePlayerVoiceData();
|
|
46
|
+
}
|
|
56
47
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this.
|
|
48
|
+
setStateUpdate(data) {
|
|
49
|
+
const { channel_id: channelId, session_id: sessionId, self_deaf: selfDeaf, self_mute: selfMute } = data;
|
|
50
|
+
|
|
51
|
+
if (!channelId || !sessionId) {
|
|
52
|
+
this._cleanup();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (this.voiceChannel !== channelId) {
|
|
57
|
+
this.aqua.emit("playerMove", this.voiceChannel, channelId);
|
|
58
|
+
this.voiceChannel = channelId;
|
|
67
59
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
60
|
+
|
|
61
|
+
this.selfDeaf = selfDeaf;
|
|
62
|
+
this.selfMute = selfMute;
|
|
63
|
+
this.voice.sessionId = sessionId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async _updatePlayerVoiceData() {
|
|
67
|
+
try {
|
|
68
|
+
await this.nodes.rest.updatePlayer({
|
|
69
|
+
guildId: this.guildId,
|
|
72
70
|
data: {
|
|
73
71
|
voice: this.voice,
|
|
74
72
|
volume: this.player.volume
|
|
75
73
|
}
|
|
76
|
-
}).catch(err => {
|
|
77
|
-
this._aqua.emit("apiError", "updatePlayer", err);
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
_cleanup() {
|
|
82
|
-
const aqua = this._aqua;
|
|
83
|
-
const channel = this.player.voiceChannel;
|
|
84
|
-
|
|
85
|
-
aqua.emit("playerLeave", channel);
|
|
86
|
-
|
|
87
|
-
this.player.voiceChannel = null;
|
|
88
|
-
this.voiceChannel = null;
|
|
89
|
-
this.player.destroy();
|
|
90
|
-
|
|
91
|
-
aqua.emit("playerDestroy", this.player);
|
|
92
|
-
|
|
93
|
-
Object.assign(this, {
|
|
94
|
-
player: null,
|
|
95
|
-
voice: null,
|
|
96
|
-
region: null,
|
|
97
|
-
selfDeaf: null,
|
|
98
|
-
selfMute: null,
|
|
99
|
-
_guildId: null,
|
|
100
|
-
_aqua: null,
|
|
101
|
-
_nodes: null
|
|
102
74
|
});
|
|
75
|
+
} catch (err) {
|
|
76
|
+
this.aqua.emit("apiError", "updatePlayer", err);
|
|
103
77
|
}
|
|
104
78
|
}
|
|
105
|
-
|
|
106
|
-
|
|
79
|
+
|
|
80
|
+
_cleanup() {
|
|
81
|
+
const { aqua, player, voiceChannel } = this;
|
|
82
|
+
|
|
83
|
+
aqua.emit("playerLeave", voiceChannel);
|
|
84
|
+
|
|
85
|
+
player.voiceChannel = null;
|
|
86
|
+
this.voiceChannel = null;
|
|
87
|
+
player.destroy();
|
|
88
|
+
|
|
89
|
+
aqua.emit("playerDestroy", player);
|
|
90
|
+
this.player = null;
|
|
91
|
+
this.voice = null;
|
|
92
|
+
this.region = null;
|
|
93
|
+
this.selfDeaf = null;
|
|
94
|
+
this.selfMute = null;
|
|
95
|
+
this.guildId = null;
|
|
96
|
+
this.aqua = null;
|
|
97
|
+
this.nodes = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = { Connection };
|
package/build/structures/Node.js
CHANGED
|
@@ -6,6 +6,8 @@ class Node {
|
|
|
6
6
|
#statsCache = {};
|
|
7
7
|
#lastStatsRequest = 0;
|
|
8
8
|
#reconnectAttempted = 0;
|
|
9
|
+
#reconnectTimeoutId = null;
|
|
10
|
+
|
|
9
11
|
constructor(aqua, nodes, options) {
|
|
10
12
|
const {
|
|
11
13
|
name,
|
|
@@ -43,25 +45,9 @@ class Node {
|
|
|
43
45
|
players: 0,
|
|
44
46
|
playingPlayers: 0,
|
|
45
47
|
uptime: 0,
|
|
46
|
-
memory:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
allocated: 0,
|
|
50
|
-
reservable: 0,
|
|
51
|
-
freePercentage: 0,
|
|
52
|
-
usedPercentage: 0
|
|
53
|
-
}),
|
|
54
|
-
cpu: Object.freeze({
|
|
55
|
-
cores: 0,
|
|
56
|
-
systemLoad: 0,
|
|
57
|
-
lavalinkLoad: 0,
|
|
58
|
-
lavalinkLoadPercentage: 0
|
|
59
|
-
}),
|
|
60
|
-
frameStats: Object.freeze({
|
|
61
|
-
sent: 0,
|
|
62
|
-
nulled: 0,
|
|
63
|
-
deficit: 0
|
|
64
|
-
}),
|
|
48
|
+
memory: { free: 0, used: 0, allocated: 0, reservable: 0, freePercentage: 0, usedPercentage: 0 },
|
|
49
|
+
cpu: { cores: 0, systemLoad: 0, lavalinkLoad: 0, lavalinkLoadPercentage: 0 },
|
|
50
|
+
frameStats: { sent: null, nulled: null, deficit: null },
|
|
65
51
|
ping: 0
|
|
66
52
|
};
|
|
67
53
|
}
|
|
@@ -83,14 +69,9 @@ class Node {
|
|
|
83
69
|
|
|
84
70
|
#cleanup() {
|
|
85
71
|
if (this.#ws) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
} catch (err) {
|
|
90
|
-
this.aqua.emit('debug', `Cleanup error: ${err.message}`);
|
|
91
|
-
} finally {
|
|
92
|
-
this.#ws = null;
|
|
93
|
-
}
|
|
72
|
+
this.#ws.removeAllListeners();
|
|
73
|
+
this.#ws.terminate();
|
|
74
|
+
this.#ws = null;
|
|
94
75
|
}
|
|
95
76
|
}
|
|
96
77
|
|
|
@@ -107,11 +88,10 @@ class Node {
|
|
|
107
88
|
|
|
108
89
|
#setupWebSocketListeners() {
|
|
109
90
|
if (!this.#ws) return;
|
|
110
|
-
|
|
111
|
-
ws.once("
|
|
112
|
-
ws.
|
|
113
|
-
ws.
|
|
114
|
-
ws.once("close", this.#onClose.bind(this));
|
|
91
|
+
this.#ws.once("open", this.#onOpen.bind(this));
|
|
92
|
+
this.#ws.once("error", this.#onError.bind(this));
|
|
93
|
+
this.#ws.on("message", this.#onMessage.bind(this));
|
|
94
|
+
this.#ws.once("close", this.#onClose.bind(this));
|
|
115
95
|
}
|
|
116
96
|
|
|
117
97
|
async #onOpen() {
|
|
@@ -119,11 +99,12 @@ class Node {
|
|
|
119
99
|
this.aqua.emit('debug', this.name, `Connected to ${this.wsUrl.href}`);
|
|
120
100
|
try {
|
|
121
101
|
this.info = await this.rest.makeRequest("GET", "/v4/info");
|
|
122
|
-
this.autoResume
|
|
102
|
+
if (this.autoResume) await this.resumePlayers();
|
|
123
103
|
} catch (err) {
|
|
124
104
|
this.info = null;
|
|
125
|
-
!this.aqua.bypassChecks?.nodeFetchInfo
|
|
105
|
+
if (!this.aqua.bypassChecks?.nodeFetchInfo) {
|
|
126
106
|
this.aqua.emit('error', `Failed to fetch node info: ${err.message}`);
|
|
107
|
+
}
|
|
127
108
|
}
|
|
128
109
|
}
|
|
129
110
|
|
|
@@ -147,7 +128,7 @@ class Node {
|
|
|
147
128
|
|
|
148
129
|
#updateStats(payload) {
|
|
149
130
|
if (!payload) return;
|
|
150
|
-
|
|
131
|
+
this.stats = Object.freeze({
|
|
151
132
|
players: payload.players ?? 0,
|
|
152
133
|
playingPlayers: payload.playingPlayers ?? 0,
|
|
153
134
|
uptime: payload.uptime ?? 0,
|
|
@@ -155,8 +136,7 @@ class Node {
|
|
|
155
136
|
memory: this.#updateMemoryStats(payload.memory),
|
|
156
137
|
cpu: this.#updateCpuStats(payload.cpu),
|
|
157
138
|
frameStats: this.#updateFrameStats(payload.frameStats)
|
|
158
|
-
};
|
|
159
|
-
this.stats = Object.freeze(newStats);
|
|
139
|
+
});
|
|
160
140
|
}
|
|
161
141
|
|
|
162
142
|
#updateMemoryStats(memory = {}) {
|
|
@@ -191,11 +171,6 @@ class Node {
|
|
|
191
171
|
deficit: 0
|
|
192
172
|
});
|
|
193
173
|
}
|
|
194
|
-
return Object.freeze({
|
|
195
|
-
sent: frameStats.sent ?? 0,
|
|
196
|
-
nulled: frameStats.nulled ?? 0,
|
|
197
|
-
deficit: frameStats.deficit ?? 0
|
|
198
|
-
});
|
|
199
174
|
}
|
|
200
175
|
|
|
201
176
|
#onMessage(msg) {
|
|
@@ -242,23 +217,18 @@ class Node {
|
|
|
242
217
|
|
|
243
218
|
#reconnect() {
|
|
244
219
|
if (this.infiniteReconnects) {
|
|
245
|
-
this.aqua.emit("nodeReconnect", this,
|
|
246
|
-
setTimeout(() =>
|
|
247
|
-
this.connect();
|
|
248
|
-
}, 10000);
|
|
220
|
+
this.aqua.emit("nodeReconnect", this, "Experimental infinite reconnects enabled, will be trying non-stop...");
|
|
221
|
+
setTimeout(() => this.connect(), 10000);
|
|
249
222
|
return;
|
|
250
223
|
}
|
|
251
224
|
|
|
252
|
-
|
|
253
|
-
clearTimeout(this.reconnectTimeoutId);
|
|
254
|
-
}
|
|
225
|
+
clearTimeout(this.#reconnectTimeoutId);
|
|
255
226
|
if (++this.#reconnectAttempted >= this.reconnectTries) {
|
|
256
227
|
this.aqua.emit("nodeError", this, new Error(`Max reconnection attempts reached (${this.reconnectTries})`));
|
|
257
|
-
clearTimeout(this.reconnectTimeoutId);
|
|
258
228
|
return this.destroy();
|
|
259
229
|
}
|
|
260
|
-
|
|
261
|
-
this
|
|
230
|
+
|
|
231
|
+
this.#reconnectTimeoutId = setTimeout(() => {
|
|
262
232
|
this.aqua.emit("nodeReconnect", this, this.#reconnectAttempted);
|
|
263
233
|
this.connect();
|
|
264
234
|
}, this.reconnectTimeout * Math.pow(2, this.#reconnectAttempted)); // Exponential backoff
|
|
@@ -271,8 +241,7 @@ class Node {
|
|
|
271
241
|
penalties += Math.round(Math.pow(1.05, 100 * this.stats.cpu.systemLoad) * 10 - 10);
|
|
272
242
|
}
|
|
273
243
|
if (this.stats.frameStats) {
|
|
274
|
-
penalties += this.stats.frameStats.deficit;
|
|
275
|
-
penalties += this.stats.frameStats.nulled * 2;
|
|
244
|
+
penalties += this.stats.frameStats.deficit + this.stats.frameStats.nulled * 2;
|
|
276
245
|
}
|
|
277
246
|
return penalties;
|
|
278
247
|
}
|
|
@@ -4,6 +4,11 @@ const { Queue } = require("./Queue");
|
|
|
4
4
|
const { Filters } = require("./Filters");
|
|
5
5
|
|
|
6
6
|
class Player extends EventEmitter {
|
|
7
|
+
static LOOP_MODES = Object.freeze({
|
|
8
|
+
NONE: "none",
|
|
9
|
+
TRACK: "track",
|
|
10
|
+
QUEUE: "queue"
|
|
11
|
+
});
|
|
7
12
|
constructor(aqua, nodes, options = {}) {
|
|
8
13
|
super();
|
|
9
14
|
this.aqua = aqua;
|
|
@@ -30,23 +35,23 @@ class Player extends EventEmitter {
|
|
|
30
35
|
this.previousTracks = [];
|
|
31
36
|
this.shouldDeleteMessage = options.shouldDeleteMessage ?? true;
|
|
32
37
|
|
|
33
|
-
this.
|
|
34
|
-
this.
|
|
38
|
+
this.boundHandleEvent = this.handleEvent.bind(this);
|
|
39
|
+
this.boundOnPlayerUpdate = this.onPlayerUpdate.bind(this);
|
|
40
|
+
this.on("playerUpdate", this.boundOnPlayerUpdate);
|
|
41
|
+
this.on("event", this.boundHandleEvent);
|
|
35
42
|
}
|
|
36
43
|
|
|
37
|
-
onPlayerUpdate(
|
|
38
|
-
if (!
|
|
39
|
-
|
|
44
|
+
onPlayerUpdate({ state } = {}) {
|
|
45
|
+
if (!state) return;
|
|
46
|
+
|
|
40
47
|
const { connected, position, ping, time } = state;
|
|
41
|
-
this
|
|
42
|
-
|
|
43
|
-
this.
|
|
44
|
-
this.timestamp = time;
|
|
45
|
-
this.aqua.emit("playerUpdate", this, packet);
|
|
48
|
+
Object.assign(this, { connected, position, ping, timestamp: time });
|
|
49
|
+
|
|
50
|
+
this.aqua.emit("playerUpdate", this, { state });
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
get previous() {
|
|
49
|
-
return this.previousTracks
|
|
54
|
+
return this.previousTracks[0] || null;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
get currenttrack() {
|
|
@@ -60,25 +65,28 @@ class Player extends EventEmitter {
|
|
|
60
65
|
this.previousTracks.unshift(track);
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
|
|
64
68
|
async play() {
|
|
65
|
-
if (!this.connected)
|
|
69
|
+
if (!this.connected) {
|
|
70
|
+
throw new Error("Player must be connected first.");
|
|
71
|
+
}
|
|
66
72
|
if (!this.queue.length) return;
|
|
67
|
-
|
|
73
|
+
|
|
68
74
|
const track = this.queue.shift();
|
|
69
|
-
|
|
70
75
|
this.current = track.track ? track : await track.resolve(this.aqua);
|
|
71
76
|
|
|
72
|
-
this
|
|
73
|
-
|
|
77
|
+
Object.assign(this, {
|
|
78
|
+
playing: true,
|
|
79
|
+
position: 0
|
|
80
|
+
});
|
|
81
|
+
|
|
74
82
|
this.aqua.emit("debug", this.guildId, `Playing track: ${this.current.track}`);
|
|
75
|
-
this.updatePlayer({ track: { encoded: this.current.track } });
|
|
76
|
-
return this;
|
|
83
|
+
return this.updatePlayer({ track: { encoded: this.current.track } });
|
|
77
84
|
}
|
|
78
85
|
|
|
79
|
-
connect(
|
|
80
|
-
if (this.connected)
|
|
81
|
-
|
|
86
|
+
connect({ guildId, voiceChannel, deaf = true, mute = false }) {
|
|
87
|
+
if (this.connected) {
|
|
88
|
+
throw new Error("Player is already connected.");
|
|
89
|
+
}
|
|
82
90
|
this.send({
|
|
83
91
|
guild_id: guildId,
|
|
84
92
|
channel_id: voiceChannel,
|
|
@@ -93,7 +101,7 @@ class Player extends EventEmitter {
|
|
|
93
101
|
destroy() {
|
|
94
102
|
if (!this.connected) return this;
|
|
95
103
|
this.disconnect();
|
|
96
|
-
this.nowPlayingMessage?.delete().catch(() => { });
|
|
104
|
+
this.nowPlayingMessage?.delete().catch(() => { });
|
|
97
105
|
this.aqua.destroyPlayer(this.guildId);
|
|
98
106
|
this.nodes.rest.destroyPlayer(this.guildId);
|
|
99
107
|
return this;
|
|
@@ -107,30 +115,29 @@ class Player extends EventEmitter {
|
|
|
107
115
|
|
|
108
116
|
async searchLyrics(query) {
|
|
109
117
|
if (!query) return null;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
track: {
|
|
118
|
+
const response = await this.nodes.rest.getLyrics({
|
|
119
|
+
track: {
|
|
113
120
|
encoded: { info: { title: query } },
|
|
114
121
|
guild_id: this.guildId,
|
|
115
122
|
search: true
|
|
116
|
-
}
|
|
123
|
+
}
|
|
117
124
|
});
|
|
118
|
-
|
|
119
125
|
return response || null;
|
|
120
126
|
}
|
|
127
|
+
|
|
121
128
|
async lyrics() {
|
|
122
129
|
if (!this.playing) return null;
|
|
123
130
|
const response = await this.nodes.rest.getLyrics({
|
|
124
131
|
track: {
|
|
125
|
-
|
|
126
|
-
|
|
132
|
+
encoded: this.current.track,
|
|
133
|
+
guild_id: this.guildId
|
|
127
134
|
},
|
|
128
|
-
|
|
135
|
+
});
|
|
129
136
|
return response || null;
|
|
130
137
|
}
|
|
131
138
|
|
|
132
139
|
seek(position) {
|
|
133
|
-
if (!this.playing) return this;
|
|
140
|
+
if (!this.playing) return this;
|
|
134
141
|
const newPosition = this.position + position;
|
|
135
142
|
if (newPosition < 0) {
|
|
136
143
|
throw new Error("Seek position cannot be negative.");
|
|
@@ -155,8 +162,7 @@ class Player extends EventEmitter {
|
|
|
155
162
|
return this;
|
|
156
163
|
}
|
|
157
164
|
|
|
158
|
-
static validModes = new Set(["none", "track", "queue"]);
|
|
159
|
-
|
|
165
|
+
static validModes = new Set(["none", "track", "queue"]);
|
|
160
166
|
setLoop(mode) {
|
|
161
167
|
if (!Player.validModes.has(mode)) throw new Error("Loop mode must be 'none', 'track', or 'queue'.");
|
|
162
168
|
this.loop = mode;
|
|
@@ -192,10 +198,10 @@ class Player extends EventEmitter {
|
|
|
192
198
|
}
|
|
193
199
|
|
|
194
200
|
shuffle() {
|
|
195
|
-
const
|
|
196
|
-
for (let i =
|
|
197
|
-
const j =
|
|
198
|
-
[
|
|
201
|
+
const arr = this.queue;
|
|
202
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
203
|
+
const j = (Math.random() * (i + 1)) | 0;
|
|
204
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
199
205
|
}
|
|
200
206
|
return this;
|
|
201
207
|
}
|
|
@@ -213,30 +219,29 @@ class Player extends EventEmitter {
|
|
|
213
219
|
return this.playing ? this.play() : undefined;
|
|
214
220
|
}
|
|
215
221
|
|
|
216
|
-
static EVENT_HANDLERS =
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
222
|
+
static EVENT_HANDLERS = Object.freeze({
|
|
223
|
+
TrackStartEvent: "trackStart",
|
|
224
|
+
TrackEndEvent: "trackEnd",
|
|
225
|
+
TrackExceptionEvent: "trackError",
|
|
226
|
+
TrackStuckEvent: "trackStuck",
|
|
227
|
+
TrackChangeEvent: "trackChange",
|
|
228
|
+
WebSocketClosedEvent: "socketClosed"
|
|
229
|
+
});
|
|
224
230
|
|
|
225
|
-
handleEvent
|
|
231
|
+
async handleEvent(payload) {
|
|
226
232
|
const player = this.aqua.players.get(payload.guildId);
|
|
227
233
|
if (!player) return;
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
if (
|
|
231
|
-
this[
|
|
234
|
+
|
|
235
|
+
const handler = Player.EVENT_HANDLERS[payload.type];
|
|
236
|
+
if (handler) {
|
|
237
|
+
await this[handler](player, this.current, payload);
|
|
232
238
|
} else {
|
|
233
|
-
this.handleUnknownEvent(
|
|
239
|
+
this.handleUnknownEvent(payload);
|
|
234
240
|
}
|
|
235
|
-
}
|
|
241
|
+
}
|
|
236
242
|
|
|
237
243
|
trackStart(player, track) {
|
|
238
|
-
this
|
|
239
|
-
this.paused = false;
|
|
244
|
+
Object.assign(this, { playing: true, paused: false });
|
|
240
245
|
this.aqua.emit("trackStart", player, track);
|
|
241
246
|
}
|
|
242
247
|
|
|
@@ -248,36 +253,34 @@ class Player extends EventEmitter {
|
|
|
248
253
|
|
|
249
254
|
async trackEnd(player, track, payload) {
|
|
250
255
|
if (this.shouldDeleteMessage && this.nowPlayingMessage) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
} catch (error) {
|
|
254
|
-
} finally {
|
|
255
|
-
this.nowPlayingMessage = null;
|
|
256
|
-
}
|
|
256
|
+
await this.nowPlayingMessage?.delete().catch(() => { });
|
|
257
|
+
this.nowPlayingMessage = null;
|
|
257
258
|
}
|
|
259
|
+
|
|
258
260
|
const reason = payload.reason.replace("_", "").toLowerCase();
|
|
259
261
|
if (reason === "loadfailed" || reason === "cleanup") {
|
|
260
|
-
|
|
261
|
-
this.aqua.emit("queueEnd", player)
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
return player.play();
|
|
262
|
+
return player.queue.isEmpty() ?
|
|
263
|
+
this.aqua.emit("queueEnd", player) :
|
|
264
|
+
player.play();
|
|
265
265
|
}
|
|
266
|
+
|
|
266
267
|
switch (this.loop) {
|
|
267
|
-
case
|
|
268
|
+
case Player.LOOP_MODES.TRACK:
|
|
268
269
|
this.aqua.emit("trackRepeat", player, track);
|
|
269
270
|
player.queue.unshift(track);
|
|
270
271
|
break;
|
|
271
|
-
case
|
|
272
|
+
case Player.LOOP_MODES.QUEUE:
|
|
272
273
|
this.aqua.emit("queueRepeat", player, track);
|
|
273
274
|
player.queue.push(track);
|
|
274
275
|
break;
|
|
275
276
|
}
|
|
277
|
+
|
|
276
278
|
if (player.queue.isEmpty()) {
|
|
277
279
|
this.playing = false;
|
|
278
280
|
this.aqua.emit("queueEnd", player);
|
|
279
281
|
return this.cleanup();
|
|
280
282
|
}
|
|
283
|
+
|
|
281
284
|
return player.play();
|
|
282
285
|
}
|
|
283
286
|
|
|
@@ -309,9 +312,8 @@ class Player extends EventEmitter {
|
|
|
309
312
|
this.aqua.send({ op: 4, d: data });
|
|
310
313
|
}
|
|
311
314
|
|
|
312
|
-
#dataStore = new
|
|
313
|
-
|
|
314
|
-
set(key, value) {
|
|
315
|
+
#dataStore = new Map();
|
|
316
|
+
set(key, value) {
|
|
315
317
|
this.#dataStore.set(key, value);
|
|
316
318
|
}
|
|
317
319
|
|
|
@@ -320,7 +322,7 @@ class Player extends EventEmitter {
|
|
|
320
322
|
}
|
|
321
323
|
|
|
322
324
|
clearData() {
|
|
323
|
-
this.#dataStore.
|
|
325
|
+
this.#dataStore.clear();
|
|
324
326
|
return this;
|
|
325
327
|
}
|
|
326
328
|
|
|
@@ -13,22 +13,22 @@ class Track {
|
|
|
13
13
|
* @param {Node} nodes
|
|
14
14
|
*/
|
|
15
15
|
constructor(data, requester, nodes) {
|
|
16
|
-
const info = data
|
|
16
|
+
const { info = {}, encoded = null, playlist = null } = data || {};
|
|
17
17
|
|
|
18
|
-
this.info = {
|
|
18
|
+
this.info = Object.freeze({
|
|
19
19
|
identifier: info.identifier || '',
|
|
20
|
-
isSeekable:
|
|
20
|
+
isSeekable: Boolean(info.isSeekable),
|
|
21
21
|
author: info.author || '',
|
|
22
|
-
length:
|
|
23
|
-
isStream:
|
|
22
|
+
length: info.length | 0,
|
|
23
|
+
isStream: Boolean(info.isStream),
|
|
24
24
|
title: info.title || '',
|
|
25
25
|
uri: info.uri || '',
|
|
26
26
|
sourceName: info.sourceName || '',
|
|
27
27
|
artworkUrl: info.artworkUrl || ''
|
|
28
|
-
};
|
|
28
|
+
});
|
|
29
29
|
|
|
30
|
-
this.track =
|
|
31
|
-
this.playlist =
|
|
30
|
+
this.track = encoded;
|
|
31
|
+
this.playlist = playlist;
|
|
32
32
|
this.requester = requester;
|
|
33
33
|
this.nodes = nodes;
|
|
34
34
|
}
|
|
@@ -38,8 +38,9 @@ class Track {
|
|
|
38
38
|
* @returns {string|null}
|
|
39
39
|
*/
|
|
40
40
|
resolveThumbnail(thumbnail) {
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
return thumbnail && thumbnail.startsWith("http") ?
|
|
42
|
+
thumbnail :
|
|
43
|
+
thumbnail ? getImageUrl(thumbnail, this.nodes) : null;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/**
|
|
@@ -47,12 +48,14 @@ class Track {
|
|
|
47
48
|
* @returns {Promise<Track|null>}
|
|
48
49
|
*/
|
|
49
50
|
async resolve(aqua) {
|
|
50
|
-
|
|
51
|
+
const searchPlatform = aqua?.options?.defaultSearchPlatform;
|
|
52
|
+
if (!searchPlatform) return null;
|
|
51
53
|
|
|
52
54
|
try {
|
|
55
|
+
const query = `${this.info.author} - ${this.info.title}`;
|
|
53
56
|
const result = await aqua.resolve({
|
|
54
|
-
query
|
|
55
|
-
source:
|
|
57
|
+
query,
|
|
58
|
+
source: searchPlatform,
|
|
56
59
|
requester: this.requester,
|
|
57
60
|
node: this.nodes
|
|
58
61
|
});
|
|
@@ -62,7 +65,10 @@ class Track {
|
|
|
62
65
|
const track = this._findMatchingTrack(result.tracks);
|
|
63
66
|
if (!track) return null;
|
|
64
67
|
|
|
65
|
-
this.
|
|
68
|
+
this.info.identifier = track.info.identifier;
|
|
69
|
+
this.track = track.track;
|
|
70
|
+
this.playlist = track.playlist || null;
|
|
71
|
+
|
|
66
72
|
return this;
|
|
67
73
|
} catch {
|
|
68
74
|
return null;
|
|
@@ -74,40 +80,24 @@ class Track {
|
|
|
74
80
|
*/
|
|
75
81
|
_findMatchingTrack(tracks) {
|
|
76
82
|
const { author, title, length } = this.info;
|
|
77
|
-
|
|
78
|
-
for (
|
|
79
|
-
const track = tracks[i];
|
|
83
|
+
|
|
84
|
+
for (const track of tracks) {
|
|
80
85
|
const tInfo = track.info;
|
|
81
86
|
|
|
82
|
-
if (tInfo.author
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
if (!author || !title || author !== tInfo.author || title !== tInfo.title) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!length || Math.abs(tInfo.length - length) <= 2000) {
|
|
85
92
|
return track;
|
|
86
93
|
}
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
return tracks[0];
|
|
90
97
|
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* @private
|
|
94
|
-
*/
|
|
95
|
-
_updateTrack(track) {
|
|
96
|
-
this.info.identifier = track.info.identifier;
|
|
97
|
-
this.track = track.track;
|
|
98
|
-
this.playlist = track.playlist || null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Fast cleanup
|
|
103
|
-
*/
|
|
104
98
|
destroy() {
|
|
105
|
-
this.
|
|
106
|
-
this.nodes = null;
|
|
107
|
-
this.track = null;
|
|
108
|
-
this.playlist = null;
|
|
109
|
-
this.info = null;
|
|
99
|
+
Object.keys(this).forEach(key => this[key] = null);
|
|
110
100
|
}
|
|
111
101
|
}
|
|
112
102
|
|
|
113
|
-
module.exports = { Track };
|
|
103
|
+
module.exports = { Track };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aqualink",
|
|
3
|
-
"version": "1.7.0-
|
|
3
|
+
"version": "1.7.0-beta5",
|
|
4
4
|
"description": "An Lavalink wrapper, focused in speed, performance, and features, Based in Riffy!",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"author": "mushroom0162 (https://github.com/ToddyTheNoobDud)",
|
|
37
37
|
"license": "ISC",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"undici": "^7.
|
|
39
|
+
"undici": "^7.3.0",
|
|
40
40
|
"ws": "^8.18.0"
|
|
41
41
|
},
|
|
42
42
|
"repository": {
|