aqualink 1.7.0-beta4 → 1.7.0-beta6
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 +80 -68
- package/build/structures/Connection.js +87 -92
- package/build/structures/Node.js +83 -94
- package/build/structures/Player.js +81 -75
- 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,29 +4,29 @@ 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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
constructor(client, nodes, options) {
|
|
10
|
+
* @param {Object} client - The client instance.
|
|
11
|
+
* @param {Array<Object>} nodes - An array of node configurations.
|
|
12
|
+
* @param {Object} options - Configuration options for Aqua.
|
|
13
|
+
* @param {Function} options.send - Function to send data.
|
|
14
|
+
* @param {string} [options.defaultSearchPlatform="ytsearch"] - Default search platform. Options include:
|
|
15
|
+
* - "youtube music": "ytmsearch"
|
|
16
|
+
* - "youtube": "ytsearch"
|
|
17
|
+
* - "spotify": "spsearch"
|
|
18
|
+
* - "jiosaavn": "jssearch"
|
|
19
|
+
* - "soundcloud": "scsearch"
|
|
20
|
+
* - "deezer": "dzsearch"
|
|
21
|
+
* - "tidal": "tdsearch"
|
|
22
|
+
* - "applemusic": "amsearch"
|
|
23
|
+
* - "bandcamp": "bcsearch"
|
|
24
|
+
* @param {string} [options.restVersion="v4"] - Version of the REST API.
|
|
25
|
+
* @param {Array<Object>} [options.plugins=[]] - Plugins to load.
|
|
26
|
+
* @param {boolean} [options.autoResume=false] - Automatically resume tracks on reconnect.
|
|
27
|
+
* @param {boolean} [options.infiniteReconnects=false] - Reconnect infinitely (default: false).
|
|
28
|
+
*/
|
|
29
|
+
constructor(client, nodes, options = {}) {
|
|
30
30
|
super();
|
|
31
31
|
this.validateInputs(client, nodes, options);
|
|
32
32
|
this.client = client;
|
|
@@ -35,28 +35,37 @@ class Aqua extends EventEmitter {
|
|
|
35
35
|
this.players = new Map();
|
|
36
36
|
this.clientId = null;
|
|
37
37
|
this.initiated = false;
|
|
38
|
-
this.shouldDeleteMessage = options.shouldDeleteMessage || false;
|
|
39
|
-
this.defaultSearchPlatform = options.defaultSearchPlatform || "ytsearch";
|
|
40
|
-
this.restVersion = options.restVersion || "v4";
|
|
41
|
-
this.plugins = options.plugins || [];
|
|
42
|
-
this.version = pkgVersion;
|
|
43
38
|
this.options = options;
|
|
44
|
-
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
39
|
+
|
|
40
|
+
this.shouldDeleteMessage = this.getOption(options, 'shouldDeleteMessage', false);
|
|
41
|
+
this.defaultSearchPlatform = this.getOption(options, 'defaultSearchPlatform', 'ytsearch');
|
|
42
|
+
this.leaveOnEnd = this.getOption(options, 'leaveOnEnd', true);
|
|
43
|
+
this.restVersion = this.getOption(options, 'restVersion', 'v4');
|
|
44
|
+
this.plugins = this.getOption(options, 'plugins', []);
|
|
45
|
+
this.version = pkgVersion;
|
|
46
|
+
this.send = options.send || this.defaultSendFunction;
|
|
47
|
+
this.autoResume = this.getOption(options, 'autoResume', false);
|
|
48
|
+
this.infiniteReconnects = this.getOption(options, 'infiniteReconnects', false);
|
|
49
|
+
|
|
47
50
|
this.setMaxListeners(0);
|
|
48
51
|
}
|
|
49
52
|
|
|
53
|
+
getOption(options, key, defaultValue) {
|
|
54
|
+
return options.hasOwnProperty(key) ? options[key] : defaultValue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
defaultSendFunction(payload) {
|
|
58
|
+
const guild = this.client.guilds.cache.get(payload.d.guild_id);
|
|
59
|
+
if (guild) guild.shard.send(payload);
|
|
60
|
+
}
|
|
50
61
|
|
|
51
62
|
validateInputs(client, nodes, options) {
|
|
52
63
|
if (!client) throw new Error("Client is required to initialize Aqua");
|
|
53
64
|
if (!Array.isArray(nodes) || !nodes.length) throw new Error(`Nodes must be a non-empty Array (Received ${typeof nodes})`);
|
|
54
|
-
if (typeof options?.send !== "function") throw new Error("Send function is required to initialize Aqua");
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
get leastUsedNodes() {
|
|
58
|
-
|
|
59
|
-
return activeNodes.length ? activeNodes.sort((a, b) => a.rest.calls - b.rest.calls) : [];
|
|
68
|
+
return [...this.nodeMap.values()].filter(node => node.connected).sort((a, b) => a.rest.calls - b.rest.calls);
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
init(clientId) {
|
|
@@ -78,41 +87,42 @@ class Aqua extends EventEmitter {
|
|
|
78
87
|
this.destroyNode(nodeId); // Ensure no duplicate nodes
|
|
79
88
|
const node = new Node(this, options, this.options);
|
|
80
89
|
this.nodeMap.set(nodeId, node);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
90
|
+
node.connect()
|
|
91
|
+
.then(() => this.emit("nodeCreate", node))
|
|
92
|
+
.catch(error => {
|
|
93
|
+
this.nodeMap.delete(nodeId);
|
|
94
|
+
throw error;
|
|
95
|
+
});
|
|
96
|
+
return node;
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
destroyNode(identifier) {
|
|
92
100
|
const node = this.nodeMap.get(identifier);
|
|
93
101
|
if (!node) return;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
|
|
103
|
+
node.disconnect()
|
|
104
|
+
.then(() => {
|
|
105
|
+
node.removeAllListeners();
|
|
106
|
+
this.nodeMap.delete(identifier);
|
|
107
|
+
this.emit("nodeDestroy", node);
|
|
108
|
+
})
|
|
109
|
+
.catch(error => console.error(`Error destroying node ${identifier}:`, error));
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
updateVoiceState({ d, t }) {
|
|
105
113
|
const player = this.players.get(d.guild_id);
|
|
106
|
-
if (player && (t === "VOICE_SERVER_UPDATE" || t === "VOICE_STATE_UPDATE" && d.user_id === this.clientId)) {
|
|
114
|
+
if (player && (t === "VOICE_SERVER_UPDATE" || (t === "VOICE_STATE_UPDATE" && d.user_id === this.clientId))) {
|
|
107
115
|
player.connection[t === "VOICE_SERVER_UPDATE" ? "setServerUpdate" : "setStateUpdate"](d);
|
|
108
116
|
if (d.status === "disconnected") this.cleanupPlayer(player);
|
|
109
117
|
}
|
|
110
118
|
}
|
|
119
|
+
|
|
111
120
|
fetchRegion(region) {
|
|
112
121
|
if (!region) return this.leastUsedNodes;
|
|
113
122
|
const lowerRegion = region.toLowerCase();
|
|
114
|
-
|
|
115
|
-
|
|
123
|
+
return [...this.nodeMap.values()]
|
|
124
|
+
.filter(node => node.connected && node.regions?.includes(lowerRegion))
|
|
125
|
+
.sort((a, b) => this.calculateLoad(a) - this.calculateLoad(b));
|
|
116
126
|
}
|
|
117
127
|
|
|
118
128
|
calculateLoad(node) {
|
|
@@ -125,6 +135,7 @@ class Aqua extends EventEmitter {
|
|
|
125
135
|
this.ensureInitialized();
|
|
126
136
|
const player = this.players.get(options.guildId);
|
|
127
137
|
if (player && player.voiceChannel) return player;
|
|
138
|
+
|
|
128
139
|
const node = options.region ? this.fetchRegion(options.region)[0] : this.leastUsedNodes[0];
|
|
129
140
|
if (!node) throw new Error("No nodes are available");
|
|
130
141
|
return this.createPlayer(node, options);
|
|
@@ -140,22 +151,21 @@ class Aqua extends EventEmitter {
|
|
|
140
151
|
return player;
|
|
141
152
|
}
|
|
142
153
|
|
|
143
|
-
async destroyPlayer(guildId) {
|
|
144
|
-
|
|
145
|
-
|
|
154
|
+
async destroyPlayer(guildId) {
|
|
155
|
+
const player = this.players.get(guildId);
|
|
156
|
+
if (!player) return;
|
|
146
157
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
158
|
+
try {
|
|
159
|
+
await player.clearData(); // Assuming clearData is an async function
|
|
160
|
+
player.removeAllListeners();
|
|
161
|
+
this.players.delete(guildId);
|
|
162
|
+
this.emit("playerDestroy", player);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error(`Error destroying player for guild ${guildId}:`, error);
|
|
165
|
+
}
|
|
155
166
|
}
|
|
156
|
-
}
|
|
157
167
|
|
|
158
|
-
async resolve({ query, source = this.defaultSearchPlatform
|
|
168
|
+
async resolve({ query, source = this.defaultSearchPlatform, requester, nodes }) {
|
|
159
169
|
this.ensureInitialized();
|
|
160
170
|
const requestNode = this.getRequestNode(nodes);
|
|
161
171
|
const formattedQuery = this.formatQuery(query, source);
|
|
@@ -188,11 +198,11 @@ async destroyPlayer(guildId) {
|
|
|
188
198
|
return URL_REGEX.test(query) ? query : `${source}:${query}`;
|
|
189
199
|
}
|
|
190
200
|
|
|
191
|
-
|
|
201
|
+
async handleNoMatches(rest, query) {
|
|
192
202
|
try {
|
|
193
|
-
const youtubeResponse =
|
|
203
|
+
const youtubeResponse = await rest.makeRequest("GET", `/v4/loadtracks?identifier=https://www.youtube.com/watch?v=${query}`);
|
|
194
204
|
if (["empty", "NO_MATCHES"].includes(youtubeResponse.loadType)) {
|
|
195
|
-
return
|
|
205
|
+
return await rest.makeRequest("GET", `/v4/loadtracks?identifier=https://open.spotify.com/track/${query}`);
|
|
196
206
|
}
|
|
197
207
|
return youtubeResponse;
|
|
198
208
|
} catch (error) {
|
|
@@ -208,10 +218,12 @@ async destroyPlayer(guildId) {
|
|
|
208
218
|
pluginInfo: response.pluginInfo ?? {},
|
|
209
219
|
tracks: [],
|
|
210
220
|
};
|
|
221
|
+
|
|
211
222
|
if (response.loadType === "error" || response.loadType === "LOAD_FAILED") {
|
|
212
223
|
baseResponse.exception = response.data ?? response.exception;
|
|
213
224
|
return baseResponse;
|
|
214
225
|
}
|
|
226
|
+
|
|
215
227
|
const trackFactory = (trackData) => new Track(trackData, requester, requestNode);
|
|
216
228
|
switch (response.loadType) {
|
|
217
229
|
case "track":
|
|
@@ -283,4 +295,4 @@ async destroyPlayer(guildId) {
|
|
|
283
295
|
}
|
|
284
296
|
}
|
|
285
297
|
|
|
286
|
-
module.exports = { Aqua };
|
|
298
|
+
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
|
@@ -3,9 +3,11 @@ const { Rest } = require("./Rest");
|
|
|
3
3
|
|
|
4
4
|
class Node {
|
|
5
5
|
#ws = null;
|
|
6
|
-
#statsCache =
|
|
6
|
+
#statsCache = new Map();
|
|
7
7
|
#lastStatsRequest = 0;
|
|
8
8
|
#reconnectAttempted = 0;
|
|
9
|
+
#reconnectTimeoutId = null;
|
|
10
|
+
|
|
9
11
|
constructor(aqua, nodes, options) {
|
|
10
12
|
const {
|
|
11
13
|
name,
|
|
@@ -35,7 +37,7 @@ class Node {
|
|
|
35
37
|
this.infiniteReconnects = options?.infiniteReconnects ?? false;
|
|
36
38
|
this.connected = false;
|
|
37
39
|
this.info = null;
|
|
38
|
-
this.stats =
|
|
40
|
+
this.stats = this.#createStats();
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
#createStats() {
|
|
@@ -43,75 +45,61 @@ 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: 0, nulled: 0, deficit: 0 },
|
|
65
51
|
ping: 0
|
|
66
52
|
};
|
|
67
53
|
}
|
|
68
54
|
|
|
69
55
|
async connect() {
|
|
70
56
|
this.#cleanup();
|
|
71
|
-
|
|
72
|
-
this.#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
57
|
+
this.#ws = new WebSocket(this.wsUrl.href, {
|
|
58
|
+
headers: this.#constructHeaders(),
|
|
59
|
+
perMessageDeflate: false,
|
|
60
|
+
handshakeTimeout: 5000
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
this.#ws.once('open', () => {
|
|
64
|
+
this.#onOpen();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
this.#setupWebSocketListeners();
|
|
68
|
+
this.aqua.emit('debug', this.name, 'Connecting...');
|
|
82
69
|
}
|
|
83
70
|
|
|
84
71
|
#cleanup() {
|
|
85
72
|
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
|
-
}
|
|
73
|
+
this.#ws.removeAllListeners();
|
|
74
|
+
this.#ws.terminate();
|
|
75
|
+
this.#ws = null;
|
|
94
76
|
}
|
|
77
|
+
this.info = null;
|
|
78
|
+
this.#statsCache.clear();
|
|
95
79
|
}
|
|
96
80
|
|
|
97
81
|
#constructHeaders() {
|
|
98
|
-
|
|
82
|
+
return {
|
|
99
83
|
Authorization: this.password,
|
|
100
84
|
"User-Id": this.aqua.clientId,
|
|
101
85
|
"Client-Name": `Aqua/${this.aqua.version}`,
|
|
86
|
+
...(this.sessionId && { "Session-Id": this.sessionId }),
|
|
87
|
+
...(this.resumeKey && { "Resume-Key": this.resumeKey })
|
|
102
88
|
};
|
|
103
|
-
if (this.sessionId) headers["Session-Id"] = this.sessionId;
|
|
104
|
-
if (this.resumeKey) headers["Resume-Key"] = this.resumeKey;
|
|
105
|
-
return Object.freeze(headers);
|
|
106
89
|
}
|
|
107
90
|
|
|
108
91
|
#setupWebSocketListeners() {
|
|
109
92
|
if (!this.#ws) return;
|
|
110
|
-
|
|
111
|
-
ws.
|
|
112
|
-
ws.
|
|
113
|
-
ws.
|
|
114
|
-
ws.
|
|
93
|
+
|
|
94
|
+
this.#ws.removeAllListeners('open');
|
|
95
|
+
this.#ws.removeAllListeners('error');
|
|
96
|
+
this.#ws.removeAllListeners('message');
|
|
97
|
+
this.#ws.removeAllListeners('close');
|
|
98
|
+
|
|
99
|
+
this.#ws.once("open", this.#onOpen.bind(this));
|
|
100
|
+
this.#ws.once("error", this.#onError.bind(this));
|
|
101
|
+
this.#ws.on("message", this.#onMessage.bind(this));
|
|
102
|
+
this.#ws.once("close", this.#onClose.bind(this));
|
|
115
103
|
}
|
|
116
104
|
|
|
117
105
|
async #onOpen() {
|
|
@@ -119,25 +107,28 @@ class Node {
|
|
|
119
107
|
this.aqua.emit('debug', this.name, `Connected to ${this.wsUrl.href}`);
|
|
120
108
|
try {
|
|
121
109
|
this.info = await this.rest.makeRequest("GET", "/v4/info");
|
|
122
|
-
this.autoResume
|
|
110
|
+
if (this.autoResume) await this.resumePlayers();
|
|
123
111
|
} catch (err) {
|
|
124
112
|
this.info = null;
|
|
125
|
-
!this.aqua.bypassChecks?.nodeFetchInfo
|
|
113
|
+
if (!this.aqua.bypassChecks?.nodeFetchInfo) {
|
|
126
114
|
this.aqua.emit('error', `Failed to fetch node info: ${err.message}`);
|
|
115
|
+
}
|
|
127
116
|
}
|
|
128
117
|
}
|
|
129
|
-
|
|
130
118
|
async getStats() {
|
|
119
|
+
if (!this.connected) return this.stats;
|
|
120
|
+
|
|
131
121
|
const now = Date.now();
|
|
132
122
|
const STATS_COOLDOWN = 60000;
|
|
123
|
+
|
|
133
124
|
if (now - this.#lastStatsRequest < STATS_COOLDOWN) {
|
|
134
|
-
return this
|
|
125
|
+
return this.stats;
|
|
135
126
|
}
|
|
127
|
+
|
|
136
128
|
try {
|
|
137
129
|
const stats = await this.rest.makeRequest("GET", "/v4/stats");
|
|
138
|
-
this.#
|
|
130
|
+
this.stats = { ...this.#createStats(), ...stats };
|
|
139
131
|
this.#lastStatsRequest = now;
|
|
140
|
-
this.#statsCache[this.name] = this.stats;
|
|
141
132
|
return this.stats;
|
|
142
133
|
} catch (err) {
|
|
143
134
|
this.aqua.emit('debug', `Stats fetch error: ${err.message}`);
|
|
@@ -147,66 +138,58 @@ class Node {
|
|
|
147
138
|
|
|
148
139
|
#updateStats(payload) {
|
|
149
140
|
if (!payload) return;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
uptime: payload.uptime ?? 0,
|
|
154
|
-
ping: payload.ping ?? 0,
|
|
141
|
+
this.stats = {
|
|
142
|
+
...this.stats,
|
|
143
|
+
...payload,
|
|
155
144
|
memory: this.#updateMemoryStats(payload.memory),
|
|
156
145
|
cpu: this.#updateCpuStats(payload.cpu),
|
|
157
146
|
frameStats: this.#updateFrameStats(payload.frameStats)
|
|
158
147
|
};
|
|
159
|
-
this.stats = Object.freeze(newStats);
|
|
160
148
|
}
|
|
161
149
|
|
|
162
150
|
#updateMemoryStats(memory = {}) {
|
|
163
151
|
const allocated = memory.allocated ?? 0;
|
|
164
152
|
const free = memory.free ?? 0;
|
|
165
153
|
const used = memory.used ?? 0;
|
|
166
|
-
return
|
|
154
|
+
return {
|
|
167
155
|
free,
|
|
168
156
|
used,
|
|
169
157
|
allocated,
|
|
170
158
|
reservable: memory.reservable ?? 0,
|
|
171
159
|
freePercentage: allocated ? (free / allocated) * 100 : 0,
|
|
172
160
|
usedPercentage: allocated ? (used / allocated) * 100 : 0
|
|
173
|
-
}
|
|
161
|
+
};
|
|
174
162
|
}
|
|
175
163
|
|
|
176
164
|
#updateCpuStats(cpu = {}) {
|
|
177
165
|
const cores = cpu.cores ?? 0;
|
|
178
|
-
return
|
|
166
|
+
return {
|
|
179
167
|
cores,
|
|
180
168
|
systemLoad: cpu.systemLoad ?? 0,
|
|
181
169
|
lavalinkLoad: cpu.lavalinkLoad ?? 0,
|
|
182
170
|
lavalinkLoadPercentage: cores ? (cpu.lavalinkLoad / cores) * 100 : 0
|
|
183
|
-
}
|
|
171
|
+
};
|
|
184
172
|
}
|
|
185
173
|
|
|
186
174
|
#updateFrameStats(frameStats = {}) {
|
|
187
|
-
if (!frameStats) {
|
|
188
|
-
|
|
189
|
-
sent: 0,
|
|
190
|
-
nulled: 0,
|
|
191
|
-
deficit: 0
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
return Object.freeze({
|
|
175
|
+
if (!frameStats) return {};
|
|
176
|
+
return {
|
|
195
177
|
sent: frameStats.sent ?? 0,
|
|
196
178
|
nulled: frameStats.nulled ?? 0,
|
|
197
179
|
deficit: frameStats.deficit ?? 0
|
|
198
|
-
}
|
|
180
|
+
};
|
|
199
181
|
}
|
|
200
182
|
|
|
201
183
|
#onMessage(msg) {
|
|
202
184
|
try {
|
|
203
185
|
const payload = JSON.parse(msg.toString());
|
|
204
186
|
if (!payload?.op) return;
|
|
187
|
+
|
|
205
188
|
switch (payload.op) {
|
|
206
|
-
case
|
|
189
|
+
case 'stats':
|
|
207
190
|
this.#updateStats(payload);
|
|
208
191
|
break;
|
|
209
|
-
case
|
|
192
|
+
case 'ready':
|
|
210
193
|
this.#handleReadyOp(payload);
|
|
211
194
|
break;
|
|
212
195
|
default:
|
|
@@ -242,26 +225,33 @@ class Node {
|
|
|
242
225
|
|
|
243
226
|
#reconnect() {
|
|
244
227
|
if (this.infiniteReconnects) {
|
|
245
|
-
this.aqua.emit("nodeReconnect", this,
|
|
246
|
-
setTimeout(() =>
|
|
247
|
-
this.connect();
|
|
248
|
-
}, 10000);
|
|
228
|
+
this.aqua.emit("nodeReconnect", this, "Experimental infinite reconnects enabled, will be trying non-stop...");
|
|
229
|
+
setTimeout(() => this.connect(), 10000);
|
|
249
230
|
return;
|
|
250
231
|
}
|
|
251
232
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
233
|
+
const jitter = Math.random() * 10000;
|
|
234
|
+
const backoffTime = Math.min(
|
|
235
|
+
this.reconnectTimeout * Math.pow(1.5, this.#reconnectAttempted) + jitter,
|
|
236
|
+
30000
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (this.#reconnectAttempted >= this.reconnectTries && !this.infiniteReconnects) {
|
|
256
240
|
this.aqua.emit("nodeError", this, new Error(`Max reconnection attempts reached (${this.reconnectTries})`));
|
|
257
|
-
|
|
258
|
-
return
|
|
241
|
+
this.destroy(true);
|
|
242
|
+
return;
|
|
259
243
|
}
|
|
260
|
-
|
|
261
|
-
this
|
|
262
|
-
|
|
244
|
+
|
|
245
|
+
clearTimeout(this.#reconnectTimeoutId);
|
|
246
|
+
this.#reconnectTimeoutId = setTimeout(() => {
|
|
247
|
+
this.#reconnectAttempted++;
|
|
248
|
+
this.aqua.emit("nodeReconnect", {
|
|
249
|
+
nodeName: this.name,
|
|
250
|
+
attempt: this.#reconnectAttempted,
|
|
251
|
+
backoffTime
|
|
252
|
+
});
|
|
263
253
|
this.connect();
|
|
264
|
-
},
|
|
254
|
+
}, backoffTime);
|
|
265
255
|
}
|
|
266
256
|
|
|
267
257
|
get penalties() {
|
|
@@ -271,8 +261,7 @@ class Node {
|
|
|
271
261
|
penalties += Math.round(Math.pow(1.05, 100 * this.stats.cpu.systemLoad) * 10 - 10);
|
|
272
262
|
}
|
|
273
263
|
if (this.stats.frameStats) {
|
|
274
|
-
penalties += this.stats.frameStats.deficit;
|
|
275
|
-
penalties += this.stats.frameStats.nulled * 2;
|
|
264
|
+
penalties += this.stats.frameStats.deficit + this.stats.frameStats.nulled * 2;
|
|
276
265
|
}
|
|
277
266
|
return penalties;
|
|
278
267
|
}
|
|
@@ -295,8 +284,8 @@ class Node {
|
|
|
295
284
|
this.aqua.nodeMap.delete(this.name);
|
|
296
285
|
this.aqua.emit("nodeDestroy", this);
|
|
297
286
|
this.info = null;
|
|
298
|
-
this.#statsCache
|
|
299
|
-
this.stats =
|
|
287
|
+
this.#statsCache.clear();
|
|
288
|
+
this.stats = this.#createStats();
|
|
300
289
|
}
|
|
301
290
|
}
|
|
302
291
|
|
|
@@ -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;
|
|
@@ -28,25 +33,26 @@ class Player extends EventEmitter {
|
|
|
28
33
|
this.ping = 0;
|
|
29
34
|
this.nowPlayingMessage = null;
|
|
30
35
|
this.previousTracks = [];
|
|
31
|
-
this.shouldDeleteMessage = options.shouldDeleteMessage
|
|
36
|
+
this.shouldDeleteMessage = options.shouldDeleteMessage;
|
|
37
|
+
this.leaveOnEnd = options.leaveOnEnd
|
|
32
38
|
|
|
33
|
-
this.
|
|
34
|
-
this.
|
|
39
|
+
this.boundHandleEvent = this.handleEvent.bind(this);
|
|
40
|
+
this.boundOnPlayerUpdate = this.onPlayerUpdate.bind(this);
|
|
41
|
+
this.on("playerUpdate", this.boundOnPlayerUpdate);
|
|
42
|
+
this.on("event", this.boundHandleEvent);
|
|
35
43
|
}
|
|
36
44
|
|
|
37
|
-
onPlayerUpdate(
|
|
38
|
-
if (!
|
|
39
|
-
|
|
45
|
+
onPlayerUpdate({ state } = {}) {
|
|
46
|
+
if (!state) return;
|
|
47
|
+
|
|
40
48
|
const { connected, position, ping, time } = state;
|
|
41
|
-
this
|
|
42
|
-
|
|
43
|
-
this.
|
|
44
|
-
this.timestamp = time;
|
|
45
|
-
this.aqua.emit("playerUpdate", this, packet);
|
|
49
|
+
Object.assign(this, { connected, position, ping, timestamp: time });
|
|
50
|
+
|
|
51
|
+
this.aqua.emit("playerUpdate", this, { state });
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
get previous() {
|
|
49
|
-
return this.previousTracks
|
|
55
|
+
return this.previousTracks[0] || null;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
get currenttrack() {
|
|
@@ -60,25 +66,28 @@ class Player extends EventEmitter {
|
|
|
60
66
|
this.previousTracks.unshift(track);
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
|
|
64
69
|
async play() {
|
|
65
|
-
if (!this.connected)
|
|
70
|
+
if (!this.connected) {
|
|
71
|
+
throw new Error("Player must be connected first.");
|
|
72
|
+
}
|
|
66
73
|
if (!this.queue.length) return;
|
|
67
|
-
|
|
74
|
+
|
|
68
75
|
const track = this.queue.shift();
|
|
69
|
-
|
|
70
76
|
this.current = track.track ? track : await track.resolve(this.aqua);
|
|
71
77
|
|
|
72
|
-
this
|
|
73
|
-
|
|
78
|
+
Object.assign(this, {
|
|
79
|
+
playing: true,
|
|
80
|
+
position: 0
|
|
81
|
+
});
|
|
82
|
+
|
|
74
83
|
this.aqua.emit("debug", this.guildId, `Playing track: ${this.current.track}`);
|
|
75
|
-
this.updatePlayer({ track: { encoded: this.current.track } });
|
|
76
|
-
return this;
|
|
84
|
+
return this.updatePlayer({ track: { encoded: this.current.track } });
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
connect(
|
|
80
|
-
if (this.connected)
|
|
81
|
-
|
|
87
|
+
connect({ guildId, voiceChannel, deaf = true, mute = false }) {
|
|
88
|
+
if (this.connected) {
|
|
89
|
+
throw new Error("Player is already connected.");
|
|
90
|
+
}
|
|
82
91
|
this.send({
|
|
83
92
|
guild_id: guildId,
|
|
84
93
|
channel_id: voiceChannel,
|
|
@@ -93,7 +102,7 @@ class Player extends EventEmitter {
|
|
|
93
102
|
destroy() {
|
|
94
103
|
if (!this.connected) return this;
|
|
95
104
|
this.disconnect();
|
|
96
|
-
this.nowPlayingMessage?.delete().catch(() => { });
|
|
105
|
+
this.nowPlayingMessage?.delete().catch(() => { });
|
|
97
106
|
this.aqua.destroyPlayer(this.guildId);
|
|
98
107
|
this.nodes.rest.destroyPlayer(this.guildId);
|
|
99
108
|
return this;
|
|
@@ -107,30 +116,29 @@ class Player extends EventEmitter {
|
|
|
107
116
|
|
|
108
117
|
async searchLyrics(query) {
|
|
109
118
|
if (!query) return null;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
track: {
|
|
119
|
+
const response = await this.nodes.rest.getLyrics({
|
|
120
|
+
track: {
|
|
113
121
|
encoded: { info: { title: query } },
|
|
114
122
|
guild_id: this.guildId,
|
|
115
123
|
search: true
|
|
116
|
-
}
|
|
124
|
+
}
|
|
117
125
|
});
|
|
118
|
-
|
|
119
126
|
return response || null;
|
|
120
127
|
}
|
|
128
|
+
|
|
121
129
|
async lyrics() {
|
|
122
130
|
if (!this.playing) return null;
|
|
123
131
|
const response = await this.nodes.rest.getLyrics({
|
|
124
132
|
track: {
|
|
125
|
-
|
|
126
|
-
|
|
133
|
+
encoded: this.current.track,
|
|
134
|
+
guild_id: this.guildId
|
|
127
135
|
},
|
|
128
|
-
|
|
136
|
+
});
|
|
129
137
|
return response || null;
|
|
130
138
|
}
|
|
131
139
|
|
|
132
140
|
seek(position) {
|
|
133
|
-
if (!this.playing) return this;
|
|
141
|
+
if (!this.playing) return this;
|
|
134
142
|
const newPosition = this.position + position;
|
|
135
143
|
if (newPosition < 0) {
|
|
136
144
|
throw new Error("Seek position cannot be negative.");
|
|
@@ -155,8 +163,7 @@ class Player extends EventEmitter {
|
|
|
155
163
|
return this;
|
|
156
164
|
}
|
|
157
165
|
|
|
158
|
-
static validModes = new Set(["none", "track", "queue"]);
|
|
159
|
-
|
|
166
|
+
static validModes = new Set(["none", "track", "queue"]);
|
|
160
167
|
setLoop(mode) {
|
|
161
168
|
if (!Player.validModes.has(mode)) throw new Error("Loop mode must be 'none', 'track', or 'queue'.");
|
|
162
169
|
this.loop = mode;
|
|
@@ -192,10 +199,10 @@ class Player extends EventEmitter {
|
|
|
192
199
|
}
|
|
193
200
|
|
|
194
201
|
shuffle() {
|
|
195
|
-
const
|
|
196
|
-
for (let i =
|
|
197
|
-
const j =
|
|
198
|
-
[
|
|
202
|
+
const arr = this.queue;
|
|
203
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
204
|
+
const j = (Math.random() * (i + 1)) | 0;
|
|
205
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
199
206
|
}
|
|
200
207
|
return this;
|
|
201
208
|
}
|
|
@@ -213,30 +220,29 @@ class Player extends EventEmitter {
|
|
|
213
220
|
return this.playing ? this.play() : undefined;
|
|
214
221
|
}
|
|
215
222
|
|
|
216
|
-
static EVENT_HANDLERS =
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
223
|
+
static EVENT_HANDLERS = Object.freeze({
|
|
224
|
+
TrackStartEvent: "trackStart",
|
|
225
|
+
TrackEndEvent: "trackEnd",
|
|
226
|
+
TrackExceptionEvent: "trackError",
|
|
227
|
+
TrackStuckEvent: "trackStuck",
|
|
228
|
+
TrackChangeEvent: "trackChange",
|
|
229
|
+
WebSocketClosedEvent: "socketClosed"
|
|
230
|
+
});
|
|
224
231
|
|
|
225
|
-
handleEvent
|
|
232
|
+
async handleEvent(payload) {
|
|
226
233
|
const player = this.aqua.players.get(payload.guildId);
|
|
227
234
|
if (!player) return;
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
if (
|
|
231
|
-
this[
|
|
235
|
+
|
|
236
|
+
const handler = Player.EVENT_HANDLERS[payload.type];
|
|
237
|
+
if (handler) {
|
|
238
|
+
await this[handler](player, this.current, payload);
|
|
232
239
|
} else {
|
|
233
|
-
this.handleUnknownEvent(
|
|
240
|
+
this.handleUnknownEvent(payload);
|
|
234
241
|
}
|
|
235
|
-
}
|
|
242
|
+
}
|
|
236
243
|
|
|
237
244
|
trackStart(player, track) {
|
|
238
|
-
this
|
|
239
|
-
this.paused = false;
|
|
245
|
+
Object.assign(this, { playing: true, paused: false });
|
|
240
246
|
this.aqua.emit("trackStart", player, track);
|
|
241
247
|
}
|
|
242
248
|
|
|
@@ -248,36 +254,37 @@ class Player extends EventEmitter {
|
|
|
248
254
|
|
|
249
255
|
async trackEnd(player, track, payload) {
|
|
250
256
|
if (this.shouldDeleteMessage && this.nowPlayingMessage) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
} catch (error) {
|
|
254
|
-
} finally {
|
|
255
|
-
this.nowPlayingMessage = null;
|
|
256
|
-
}
|
|
257
|
+
await this.nowPlayingMessage?.delete().catch(() => { });
|
|
258
|
+
this.nowPlayingMessage = null;
|
|
257
259
|
}
|
|
260
|
+
|
|
258
261
|
const reason = payload.reason.replace("_", "").toLowerCase();
|
|
259
262
|
if (reason === "loadfailed" || reason === "cleanup") {
|
|
260
|
-
|
|
261
|
-
this.aqua.emit("queueEnd", player)
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
return player.play();
|
|
263
|
+
return player.queue.isEmpty() ?
|
|
264
|
+
this.aqua.emit("queueEnd", player) :
|
|
265
|
+
player.play();
|
|
265
266
|
}
|
|
267
|
+
|
|
266
268
|
switch (this.loop) {
|
|
267
|
-
case
|
|
269
|
+
case Player.LOOP_MODES.TRACK:
|
|
268
270
|
this.aqua.emit("trackRepeat", player, track);
|
|
269
271
|
player.queue.unshift(track);
|
|
270
272
|
break;
|
|
271
|
-
case
|
|
273
|
+
case Player.LOOP_MODES.QUEUE:
|
|
272
274
|
this.aqua.emit("queueRepeat", player, track);
|
|
273
275
|
player.queue.push(track);
|
|
274
276
|
break;
|
|
275
277
|
}
|
|
278
|
+
|
|
276
279
|
if (player.queue.isEmpty()) {
|
|
277
280
|
this.playing = false;
|
|
278
|
-
this.
|
|
279
|
-
|
|
281
|
+
if (this.leaveOnEnd) {
|
|
282
|
+
await this.cleanup();
|
|
283
|
+
}
|
|
284
|
+
return this.aqua.emit("queueEnd", player);
|
|
280
285
|
}
|
|
286
|
+
|
|
287
|
+
|
|
281
288
|
return player.play();
|
|
282
289
|
}
|
|
283
290
|
|
|
@@ -309,9 +316,8 @@ class Player extends EventEmitter {
|
|
|
309
316
|
this.aqua.send({ op: 4, d: data });
|
|
310
317
|
}
|
|
311
318
|
|
|
312
|
-
#dataStore = new
|
|
313
|
-
|
|
314
|
-
set(key, value) {
|
|
319
|
+
#dataStore = new Map();
|
|
320
|
+
set(key, value) {
|
|
315
321
|
this.#dataStore.set(key, value);
|
|
316
322
|
}
|
|
317
323
|
|
|
@@ -320,7 +326,7 @@ class Player extends EventEmitter {
|
|
|
320
326
|
}
|
|
321
327
|
|
|
322
328
|
clearData() {
|
|
323
|
-
this.#dataStore.
|
|
329
|
+
this.#dataStore.clear();
|
|
324
330
|
return this;
|
|
325
331
|
}
|
|
326
332
|
|
|
@@ -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-beta6",
|
|
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": {
|