aqualink 1.7.0-beta5 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -55
- package/build/handlers/fetchImage.js +6 -9
- package/build/structures/Aqua.js +84 -96
- package/build/structures/Connection.js +58 -84
- package/build/structures/Node.js +110 -107
- package/build/structures/Player.js +91 -90
- package/build/structures/Track.js +13 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,14 +11,14 @@ This code is based in riffy, but its an 100% Rewrite made from scratch...
|
|
|
11
11
|
- Lowest CPU Usage
|
|
12
12
|
- Very fast (mine take less than 1 second to load an song!)
|
|
13
13
|
- 1 Player created = ~1 - 0,5 mb per player
|
|
14
|
-
- Auto clean Up memory when song finishes / bot leave the vc
|
|
14
|
+
- Auto clean Up memory when song finishes / bot leave the vc (Now Options supported!)
|
|
15
15
|
- Plugin system
|
|
16
16
|
- Lavalink v4 support (din't test v3)
|
|
17
|
-
- Youtube and Spotify support
|
|
17
|
+
- Youtube and Spotify support (Soundcloud, deezer, vimeo, etc also works...)
|
|
18
18
|
- Minimal Requests to the lavalink server (helps the lavalink recourses!)
|
|
19
|
-
- Easy player, node, aqua
|
|
19
|
+
- Easy player, node, aqua manager
|
|
20
20
|
- Fast responses from rest and node
|
|
21
|
-
- Playlist support (My mix playlists, youtube playlists, spotify playlists)
|
|
21
|
+
- Playlist support (My mix playlists, youtube playlists, spotify playlists, etc)
|
|
22
22
|
- Lyrics Support by Lavalink
|
|
23
23
|
- https://github.com/topi314/LavaLyrics (RECOMMENDED)
|
|
24
24
|
- https://github.com/DRSchlaubi/lyrics.kt (?)
|
|
@@ -29,50 +29,45 @@ This code is based in riffy, but its an 100% Rewrite made from scratch...
|
|
|
29
29
|
|
|
30
30
|
- Example bot: https://github.com/ToddyTheNoobDud/Thorium-Music
|
|
31
31
|
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- Document + fix destroyPlayer
|
|
72
|
-
|
|
73
|
-
- Remade some stuff in `FetchImage`
|
|
74
|
-
- Use promise.race since only first sucess is required. (will be tested, may revert to promise.any)
|
|
75
|
-
- Use map cuz its faster and more efficient than Objects.
|
|
32
|
+
# Brick by brick, 1.8.0 Update (yay)
|
|
33
|
+
|
|
34
|
+
- Misc changes on FetchImage (improves the overall checking and speed)
|
|
35
|
+
|
|
36
|
+
- Rewrite `AQUA` module
|
|
37
|
+
- Remade the resolve logic (improves the speed by a lot)
|
|
38
|
+
- Fixes many memory usages related to nodes
|
|
39
|
+
- send is no longer required to be Applied (Applied by default now.)
|
|
40
|
+
- Remade some stuff with discord VoiceGateway
|
|
41
|
+
|
|
42
|
+
- Remade `CONNECTION` module
|
|
43
|
+
- Way faster connections (Joining, reconnecting, connected, disconnect)
|
|
44
|
+
- Reduced memory overload by removing useless code
|
|
45
|
+
- Improved early Returns
|
|
46
|
+
|
|
47
|
+
- Remade `NODE` module
|
|
48
|
+
- MANY fixes for the connection logic (fixes reconnection, etc)
|
|
49
|
+
- Fixed memory leaks in heartbeat system (hopefully, reduced memory by a lot.)
|
|
50
|
+
- Faster connection speed and checkings
|
|
51
|
+
- Remade the Options system, improve JSON parsing
|
|
52
|
+
|
|
53
|
+
- Rewrite `PLAYER` module
|
|
54
|
+
- Many memory related fixes
|
|
55
|
+
- Improved the overall code speed by a lot
|
|
56
|
+
- Rewrote setLoop, play, shuffle, replay methods (fixes + performance)
|
|
57
|
+
- Added 2 new options:
|
|
58
|
+
|
|
59
|
+
leaveOnEnd: false, // Optional
|
|
60
|
+
|
|
61
|
+
shouldDeleteMessage: true // Optional
|
|
62
|
+
|
|
63
|
+
- Uses array for better performance and less memory allocation
|
|
64
|
+
- Rewrite the Events handling (speed and recourses fixes)
|
|
65
|
+
|
|
66
|
+
- Updated `TRACK` module
|
|
67
|
+
- Better object handling for internal code.
|
|
68
|
+
- Removed an useless method
|
|
69
|
+
|
|
70
|
+
Thats all for now, im lazy, help me fix code and improve this on github... i can't test properly 😭😭😭
|
|
76
71
|
|
|
77
72
|
# How to install
|
|
78
73
|
|
|
@@ -115,15 +110,12 @@ const nodes = [
|
|
|
115
110
|
];
|
|
116
111
|
|
|
117
112
|
const aqua = new Aqua(client, nodes, {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
defaultSearchPlatform: "ytsearch",
|
|
123
|
-
restVersion: "v4"
|
|
113
|
+
defaultSearchPlatform: "ytsearch",
|
|
114
|
+
restVersion: "v4",
|
|
115
|
+
autoResume: false,
|
|
116
|
+
infiniteReconnects: true,
|
|
124
117
|
});
|
|
125
118
|
|
|
126
|
-
|
|
127
119
|
client.aqua = aqua;
|
|
128
120
|
|
|
129
121
|
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
const { request } = require("undici");
|
|
2
|
-
|
|
3
2
|
const sourceHandlers = new Map([
|
|
4
3
|
['spotify', uri => fetchThumbnail(`https://open.spotify.com/oembed?url=${uri}`)],
|
|
5
4
|
['youtube', identifier => fetchYouTubeThumbnail(identifier)]
|
|
6
5
|
]);
|
|
7
|
-
|
|
8
6
|
const YOUTUBE_URL_TEMPLATE = quality => id => `https://img.youtube.com/vi/${id}/${quality}.jpg`;
|
|
9
7
|
const YOUTUBE_QUALITIES = ['maxresdefault', 'hqdefault', 'mqdefault', 'default'].map(YOUTUBE_URL_TEMPLATE);
|
|
10
8
|
|
|
11
9
|
async function getImageUrl(info) {
|
|
12
10
|
if (!info?.sourceName || !info?.uri) return null;
|
|
13
|
-
|
|
14
11
|
const handler = sourceHandlers.get(info.sourceName.toLowerCase());
|
|
15
12
|
if (!handler) return null;
|
|
16
|
-
|
|
17
13
|
try {
|
|
18
14
|
return await handler(info.uri);
|
|
19
15
|
} catch (error) {
|
|
@@ -31,18 +27,19 @@ async function fetchThumbnail(url) {
|
|
|
31
27
|
const json = await body.json();
|
|
32
28
|
return json.thumbnail_url || null;
|
|
33
29
|
} catch (error) {
|
|
34
|
-
console.error(
|
|
30
|
+
console.error(`Error fetching thumbnail from ${url}:`, error);
|
|
35
31
|
return null;
|
|
36
32
|
}
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
async function fetchYouTubeThumbnail(identifier) {
|
|
40
|
-
const fetchPromises = YOUTUBE_QUALITIES.map(urlFunc => fetchThumbnail(urlFunc(identifier)));
|
|
41
|
-
|
|
42
36
|
try {
|
|
43
|
-
|
|
37
|
+
const thumbnail = await Promise.race(
|
|
38
|
+
YOUTUBE_QUALITIES.map(urlFunc => fetchThumbnail(urlFunc(identifier)))
|
|
39
|
+
);
|
|
40
|
+
return thumbnail || null;
|
|
44
41
|
} catch (error) {
|
|
45
|
-
console.error('
|
|
42
|
+
console.error('No valid YouTube thumbnail found:', error);
|
|
46
43
|
return null;
|
|
47
44
|
}
|
|
48
45
|
}
|
package/build/structures/Aqua.js
CHANGED
|
@@ -6,28 +6,18 @@ const { version: pkgVersion } = require("../../package.json");
|
|
|
6
6
|
const URL_REGEX = /^https?:\/\//;
|
|
7
7
|
|
|
8
8
|
class Aqua extends EventEmitter {
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
10
|
* @param {Object} client - The client instance.
|
|
11
11
|
* @param {Array<Object>} nodes - An array of node configurations.
|
|
12
12
|
* @param {Object} options - Configuration options for Aqua.
|
|
13
13
|
* @param {Function} options.send - Function to send data.
|
|
14
|
-
* @param {string} [options.defaultSearchPlatform="ytsearch"] - Default search platform.
|
|
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"
|
|
14
|
+
* @param {string} [options.defaultSearchPlatform="ytsearch"] - Default search platform.
|
|
24
15
|
* @param {string} [options.restVersion="v4"] - Version of the REST API.
|
|
25
16
|
* @param {Array<Object>} [options.plugins=[]] - Plugins to load.
|
|
26
|
-
* @param {string} [options.shouldDeleteMessage='none'] - Should delete your message? (true, false)
|
|
27
17
|
* @param {boolean} [options.autoResume=false] - Automatically resume tracks on reconnect.
|
|
28
|
-
* @param {boolean} [options.infiniteReconnects=false] - Reconnect infinitely
|
|
18
|
+
* @param {boolean} [options.infiniteReconnects=false] - Reconnect infinitely.
|
|
29
19
|
*/
|
|
30
|
-
constructor(client, nodes, options) {
|
|
20
|
+
constructor(client, nodes, options = {}) {
|
|
31
21
|
super();
|
|
32
22
|
this.validateInputs(client, nodes, options);
|
|
33
23
|
this.client = client;
|
|
@@ -36,26 +26,46 @@ class Aqua extends EventEmitter {
|
|
|
36
26
|
this.players = new Map();
|
|
37
27
|
this.clientId = null;
|
|
38
28
|
this.initiated = false;
|
|
39
|
-
this.shouldDeleteMessage = options.shouldDeleteMessage || false;
|
|
40
|
-
this.defaultSearchPlatform = options.defaultSearchPlatform || "ytsearch";
|
|
41
|
-
this.restVersion = options.restVersion || "v4";
|
|
42
|
-
this.plugins = options.plugins || [];
|
|
43
|
-
this.version = pkgVersion;
|
|
44
29
|
this.options = options;
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
30
|
+
this.shouldDeleteMessage = this.getOption(options, 'shouldDeleteMessage', false);
|
|
31
|
+
this.defaultSearchPlatform = this.getOption(options, 'defaultSearchPlatform', 'ytsearch');
|
|
32
|
+
this.leaveOnEnd = this.getOption(options, 'leaveOnEnd', true);
|
|
33
|
+
this.restVersion = this.getOption(options, 'restVersion', 'v4');
|
|
34
|
+
this.plugins = this.getOption(options, 'plugins', []);
|
|
35
|
+
this.version = pkgVersion;
|
|
36
|
+
this.send = options.send || this.defaultSendFunction;
|
|
37
|
+
this.autoResume = this.getOption(options, 'autoResume', false);
|
|
38
|
+
this.infiniteReconnects = this.getOption(options, 'infiniteReconnects', false);
|
|
48
39
|
this.setMaxListeners(0);
|
|
40
|
+
this._leastUsedCache = { nodes: [], timestamp: 0 };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getOption(options, key, defaultValue) {
|
|
44
|
+
return Object.prototype.hasOwnProperty.call(options, key) ? options[key] : defaultValue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
defaultSendFunction(payload) {
|
|
48
|
+
const guild = this.client.guilds.cache.get(payload.d.guild_id);
|
|
49
|
+
if (guild) guild.shard.send(payload);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
validateInputs(client, nodes
|
|
52
|
+
validateInputs(client, nodes) {
|
|
52
53
|
if (!client) throw new Error("Client is required to initialize Aqua");
|
|
53
|
-
if (!Array.isArray(nodes) || !nodes.length)
|
|
54
|
-
|
|
54
|
+
if (!Array.isArray(nodes) || !nodes.length) {
|
|
55
|
+
throw new Error(`Nodes must be a non-empty Array (Received ${typeof nodes})`);
|
|
56
|
+
}
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
get leastUsedNodes() {
|
|
58
|
-
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
if (now - this._leastUsedCache.timestamp < 50) return this._leastUsedCache.nodes;
|
|
62
|
+
const nodes = [];
|
|
63
|
+
for (const node of this.nodeMap.values()) {
|
|
64
|
+
if (node.connected) nodes.push(node);
|
|
65
|
+
}
|
|
66
|
+
nodes.sort((a, b) => a.rest.calls - b.rest.calls);
|
|
67
|
+
this._leastUsedCache = { nodes, timestamp: now };
|
|
68
|
+
return nodes;
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
init(clientId) {
|
|
@@ -74,45 +84,55 @@ class Aqua extends EventEmitter {
|
|
|
74
84
|
|
|
75
85
|
createNode(options) {
|
|
76
86
|
const nodeId = options.name || options.host;
|
|
77
|
-
this.destroyNode(nodeId);
|
|
87
|
+
this.destroyNode(nodeId);
|
|
78
88
|
const node = new Node(this, options, this.options);
|
|
79
89
|
this.nodeMap.set(nodeId, node);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
this._leastUsedCache.timestamp = 0;
|
|
91
|
+
node.connect().then(() => {
|
|
92
|
+
this.emit("nodeCreate", node);
|
|
93
|
+
}).catch(error => {
|
|
94
|
+
this.nodeMap.delete(nodeId);
|
|
95
|
+
throw error;
|
|
96
|
+
});
|
|
86
97
|
return node;
|
|
87
98
|
}
|
|
88
99
|
|
|
89
100
|
destroyNode(identifier) {
|
|
90
101
|
const node = this.nodeMap.get(identifier);
|
|
91
102
|
if (!node) return;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
})
|
|
99
|
-
.catch(error => console.error(`Error destroying node ${identifier}:`, error));
|
|
103
|
+
node.disconnect().then(() => {
|
|
104
|
+
node.removeAllListeners();
|
|
105
|
+
this.nodeMap.delete(identifier);
|
|
106
|
+
this._leastUsedCache.timestamp = 0;
|
|
107
|
+
this.emit("nodeDestroy", node);
|
|
108
|
+
}).catch(error => console.error(`Error destroying node ${identifier}:`, error));
|
|
100
109
|
}
|
|
101
110
|
|
|
102
111
|
updateVoiceState({ d, t }) {
|
|
103
112
|
const player = this.players.get(d.guild_id);
|
|
104
|
-
if (player
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
if (!player) return;
|
|
114
|
+
if (t === "VOICE_SERVER_UPDATE" || (t === "VOICE_STATE_UPDATE" && d.user_id === this.clientId)) {
|
|
115
|
+
const updateMethod = t === "VOICE_SERVER_UPDATE" ? "setServerUpdate" : "setStateUpdate";
|
|
116
|
+
if (player.connection && typeof player.connection[updateMethod] === "function") {
|
|
117
|
+
player.connection[updateMethod](d);
|
|
118
|
+
}
|
|
119
|
+
if (d.status === "disconnected") {
|
|
120
|
+
this.cleanupPlayer(player);
|
|
121
|
+
}
|
|
107
122
|
}
|
|
108
123
|
}
|
|
109
124
|
|
|
110
125
|
fetchRegion(region) {
|
|
111
126
|
if (!region) return this.leastUsedNodes;
|
|
112
127
|
const lowerRegion = region.toLowerCase();
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
const regionNodes = [];
|
|
129
|
+
for (const node of this.nodeMap.values()) {
|
|
130
|
+
if (node.connected && node.regions?.includes(lowerRegion)) {
|
|
131
|
+
regionNodes.push(node);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
regionNodes.sort((a, b) => this.calculateLoad(a) - this.calculateLoad(b));
|
|
135
|
+
return regionNodes;
|
|
116
136
|
}
|
|
117
137
|
|
|
118
138
|
calculateLoad(node) {
|
|
@@ -123,10 +143,10 @@ class Aqua extends EventEmitter {
|
|
|
123
143
|
|
|
124
144
|
createConnection(options) {
|
|
125
145
|
this.ensureInitialized();
|
|
126
|
-
const
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
const node =
|
|
146
|
+
const existingPlayer = this.players.get(options.guildId);
|
|
147
|
+
if (existingPlayer && existingPlayer.voiceChannel) return existingPlayer;
|
|
148
|
+
const availableNodes = options.region ? this.fetchRegion(options.region) : this.leastUsedNodes;
|
|
149
|
+
const node = availableNodes[0];
|
|
130
150
|
if (!node) throw new Error("No nodes are available");
|
|
131
151
|
return this.createPlayer(node, options);
|
|
132
152
|
}
|
|
@@ -144,9 +164,8 @@ class Aqua extends EventEmitter {
|
|
|
144
164
|
async destroyPlayer(guildId) {
|
|
145
165
|
const player = this.players.get(guildId);
|
|
146
166
|
if (!player) return;
|
|
147
|
-
|
|
148
167
|
try {
|
|
149
|
-
await player.clearData();
|
|
168
|
+
await player.clearData();
|
|
150
169
|
player.removeAllListeners();
|
|
151
170
|
this.players.delete(guildId);
|
|
152
171
|
this.emit("playerDestroy", player);
|
|
@@ -159,6 +178,7 @@ class Aqua extends EventEmitter {
|
|
|
159
178
|
this.ensureInitialized();
|
|
160
179
|
const requestNode = this.getRequestNode(nodes);
|
|
161
180
|
const formattedQuery = this.formatQuery(query, source);
|
|
181
|
+
|
|
162
182
|
try {
|
|
163
183
|
const response = await requestNode.rest.makeRequest("GET", `/v4/loadtracks?identifier=${encodeURIComponent(formattedQuery)}`);
|
|
164
184
|
if (["empty", "NO_MATCHES"].includes(response.loadType)) {
|
|
@@ -166,7 +186,7 @@ class Aqua extends EventEmitter {
|
|
|
166
186
|
}
|
|
167
187
|
return this.constructorResponse(response, requester, requestNode);
|
|
168
188
|
} catch (error) {
|
|
169
|
-
if (error.name ===
|
|
189
|
+
if (error.name === "AbortError") {
|
|
170
190
|
throw new Error("Request timed out");
|
|
171
191
|
}
|
|
172
192
|
throw new Error(`Failed to resolve track: ${error.message}`);
|
|
@@ -177,7 +197,7 @@ class Aqua extends EventEmitter {
|
|
|
177
197
|
if (nodes && !(typeof nodes === "string" || nodes instanceof Node)) {
|
|
178
198
|
throw new TypeError(`'nodes' must be a string or Node instance, received: ${typeof nodes}`);
|
|
179
199
|
}
|
|
180
|
-
return (typeof nodes ===
|
|
200
|
+
return (typeof nodes === "string" ? this.nodeMap.get(nodes) : nodes) ?? this.leastUsedNodes[0];
|
|
181
201
|
}
|
|
182
202
|
|
|
183
203
|
ensureInitialized() {
|
|
@@ -190,9 +210,11 @@ class Aqua extends EventEmitter {
|
|
|
190
210
|
|
|
191
211
|
async handleNoMatches(rest, query) {
|
|
192
212
|
try {
|
|
193
|
-
const
|
|
213
|
+
const ytIdentifier = `/v4/loadtracks?identifier=https://www.youtube.com/watch?v=${query}`;
|
|
214
|
+
const youtubeResponse = await rest.makeRequest("GET", ytIdentifier);
|
|
194
215
|
if (["empty", "NO_MATCHES"].includes(youtubeResponse.loadType)) {
|
|
195
|
-
|
|
216
|
+
const spotifyIdentifier = `/v4/loadtracks?identifier=https://open.spotify.com/track/${query}`;
|
|
217
|
+
return await rest.makeRequest("GET", spotifyIdentifier);
|
|
196
218
|
}
|
|
197
219
|
return youtubeResponse;
|
|
198
220
|
} catch (error) {
|
|
@@ -206,7 +228,7 @@ class Aqua extends EventEmitter {
|
|
|
206
228
|
exception: null,
|
|
207
229
|
playlistInfo: null,
|
|
208
230
|
pluginInfo: response.pluginInfo ?? {},
|
|
209
|
-
tracks: []
|
|
231
|
+
tracks: []
|
|
210
232
|
};
|
|
211
233
|
|
|
212
234
|
if (response.loadType === "error" || response.loadType === "LOAD_FAILED") {
|
|
@@ -214,7 +236,8 @@ class Aqua extends EventEmitter {
|
|
|
214
236
|
return baseResponse;
|
|
215
237
|
}
|
|
216
238
|
|
|
217
|
-
const trackFactory =
|
|
239
|
+
const trackFactory = trackData => new Track(trackData, requester, requestNode);
|
|
240
|
+
|
|
218
241
|
switch (response.loadType) {
|
|
219
242
|
case "track":
|
|
220
243
|
if (response.data) {
|
|
@@ -225,7 +248,7 @@ class Aqua extends EventEmitter {
|
|
|
225
248
|
if (response.data?.info) {
|
|
226
249
|
baseResponse.playlistInfo = {
|
|
227
250
|
name: response.data.info.name ?? response.data.info.title,
|
|
228
|
-
...response.data.info
|
|
251
|
+
...response.data.info
|
|
229
252
|
};
|
|
230
253
|
}
|
|
231
254
|
baseResponse.tracks = (response.data?.tracks ?? []).map(trackFactory);
|
|
@@ -243,45 +266,10 @@ class Aqua extends EventEmitter {
|
|
|
243
266
|
return player;
|
|
244
267
|
}
|
|
245
268
|
|
|
246
|
-
cleanupIdle() {
|
|
247
|
-
const now = Date.now();
|
|
248
|
-
for (const [guildId, player] of this.players) {
|
|
249
|
-
if (!player.playing && !player.paused && player.queue.isEmpty() && (now - player.lastActivity) > this.options.idleTimeout) {
|
|
250
|
-
this.cleanupPlayer(player);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
269
|
cleanupPlayer(player) {
|
|
256
|
-
if (
|
|
257
|
-
try {
|
|
258
|
-
player.clearData();
|
|
259
|
-
player.removeAllListeners();
|
|
260
|
-
player.destroy();
|
|
270
|
+
if (player && this.players.has(player.guildId)) {
|
|
261
271
|
this.players.delete(player.guildId);
|
|
262
|
-
this.emit("playerDestroy", player);
|
|
263
|
-
} catch (error) {
|
|
264
|
-
console.error(`Error during player cleanup: ${error.message}`);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
cleanup() {
|
|
269
|
-
for (const player of this.players.values()) {
|
|
270
|
-
this.cleanupPlayer(player);
|
|
271
|
-
}
|
|
272
|
-
for (const node of this.nodeMap.values()) {
|
|
273
|
-
this.destroyNode(node.name || node.host);
|
|
274
272
|
}
|
|
275
|
-
this.nodeMap.clear();
|
|
276
|
-
this.players.clear();
|
|
277
|
-
this.client = null;
|
|
278
|
-
this.nodes = null;
|
|
279
|
-
this.plugins?.forEach(plugin => plugin.unload?.(this));
|
|
280
|
-
this.plugins = null;
|
|
281
|
-
this.options = null;
|
|
282
|
-
this.send = null;
|
|
283
|
-
this.version = null;
|
|
284
|
-
this.removeAllListeners();
|
|
285
273
|
}
|
|
286
274
|
}
|
|
287
275
|
|
|
@@ -1,100 +1,74 @@
|
|
|
1
1
|
class Connection {
|
|
2
2
|
constructor(player) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
this.playerRef = new WeakRef(player);
|
|
4
|
+
const { voiceChannel, guildId, aqua, nodes } = player;
|
|
5
|
+
this.voice = {
|
|
6
|
+
sessionId: null,
|
|
7
|
+
endpoint: null,
|
|
8
|
+
token: null
|
|
9
|
+
};
|
|
10
|
+
this.region = null;
|
|
11
|
+
this.selfDeaf = false;
|
|
12
|
+
this.selfMute = false;
|
|
13
|
+
this.voiceChannel = voiceChannel;
|
|
14
|
+
this.guildId = guildId;
|
|
15
|
+
this.aqua = aqua;
|
|
16
|
+
this.nodes = nodes;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
setServerUpdate(data) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const { endpoint, token } = data || {};
|
|
21
|
+
if (!endpoint) {
|
|
22
|
+
throw new Error("Missing 'endpoint' property");
|
|
23
|
+
}
|
|
24
|
+
const regionMatch = endpoint.match(/^([a-zA-Z]+)/);
|
|
25
|
+
if (!regionMatch) return;
|
|
26
|
+
const newRegion = regionMatch[1];
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
prevRegion
|
|
39
|
-
? `Changed Voice Region from ${prevRegion} to ${newRegion}`
|
|
40
|
-
: `Voice Server: ${newRegion}`
|
|
41
|
-
}`
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
this._updatePlayerVoiceData();
|
|
28
|
+
if (this.region !== newRegion) {
|
|
29
|
+
this.voice.endpoint = endpoint;
|
|
30
|
+
this.voice.token = token;
|
|
31
|
+
const prevRegion = this.region;
|
|
32
|
+
this.region = newRegion;
|
|
33
|
+
this.aqua.emit(
|
|
34
|
+
"debug",
|
|
35
|
+
`[Player ${this.guildId} - CONNECTION] ${
|
|
36
|
+
prevRegion
|
|
37
|
+
? `Changed Voice Region from ${prevRegion} to ${newRegion}`
|
|
38
|
+
: `Voice Server: ${newRegion}`
|
|
39
|
+
}`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
this._updatePlayerVoiceData();
|
|
46
43
|
}
|
|
47
44
|
|
|
48
45
|
setStateUpdate(data) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (!channelId || !sessionId) {
|
|
52
|
-
this._cleanup();
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
46
|
+
const { channel_id: channelId, session_id: sessionId, self_deaf: selfDeaf, self_mute: selfMute } = data || {};
|
|
47
|
+
if (!channelId || !sessionId) return;
|
|
55
48
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this.voice.sessionId = sessionId;
|
|
49
|
+
if (this.voiceChannel !== channelId) {
|
|
50
|
+
this.aqua.emit("playerMove", this.voiceChannel, channelId);
|
|
51
|
+
this.voiceChannel = channelId;
|
|
52
|
+
}
|
|
53
|
+
this.selfDeaf = selfDeaf;
|
|
54
|
+
this.selfMute = selfMute;
|
|
55
|
+
this.voice.sessionId = sessionId;
|
|
64
56
|
}
|
|
65
57
|
|
|
66
58
|
async _updatePlayerVoiceData() {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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;
|
|
59
|
+
const player = this.playerRef.deref();
|
|
60
|
+
if (!player) return;
|
|
61
|
+
try {
|
|
62
|
+
await this.nodes.rest.updatePlayer({
|
|
63
|
+
guildId: this.guildId,
|
|
64
|
+
data: {
|
|
65
|
+
voice: this.voice,
|
|
66
|
+
volume: player.volume
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
} catch (err) {
|
|
70
|
+
this.aqua.emit("apiError", "updatePlayer", err);
|
|
71
|
+
}
|
|
98
72
|
}
|
|
99
73
|
}
|
|
100
74
|
|