aqualink 1.8.1-beta5 → 1.9.0-beta2
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 +73 -49
- package/build/handlers/fetchImage.js +11 -10
- package/build/structures/Aqua.js +30 -32
- package/build/structures/Connection.js +21 -45
- package/build/structures/Node.js +54 -23
- package/build/structures/Player.js +50 -81
- package/build/structures/Rest.js +25 -51
- package/build/structures/Track.js +15 -22
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ An Stable, performant, Recourse friendly and fast lavalink wrapper
|
|
|
4
4
|
This code is based in riffy, but its an 100% Rewrite made from scratch...
|
|
5
5
|
|
|
6
6
|
# Why use AquaLink
|
|
7
|
-
-
|
|
7
|
+
- Uses my modified fork of @performanc/pwsl-mini, for an way faster WebSocket
|
|
8
8
|
- Very Low memory comsuption
|
|
9
9
|
- Built in Queue manager
|
|
10
10
|
- Lots of features to use
|
|
@@ -16,66 +16,90 @@ This code is based in riffy, but its an 100% Rewrite made from scratch...
|
|
|
16
16
|
- Lavalink v4 support (din't test v3)
|
|
17
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 manager
|
|
19
|
+
- Easy player, node, aqua manager
|
|
20
20
|
- Fast responses from rest and node
|
|
21
21
|
- Playlist support (My mix playlists, youtube playlists, spotify playlists, etc)
|
|
22
|
-
- Uses @performanc/pwsl-mini as default WebSocket system
|
|
23
22
|
- Lyrics Support by Lavalink
|
|
24
23
|
- https://github.com/topi314/LavaLyrics (RECOMMENDED)
|
|
25
24
|
- https://github.com/DRSchlaubi/lyrics.kt (?)
|
|
26
25
|
- https://github.com/DuncteBot/java-timed-lyrics (RECOMMENDED)
|
|
26
|
+
|
|
27
|
+
# Tralalero Tralala 1.9.0 Released
|
|
28
|
+
**Whoa, lots of stuff to write here 😭**
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
### **Small changes on the `fetchImage` Handler**
|
|
33
|
+
- Improves the overall speed, less memory overhead.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### **Remade some stuff on `AQUA` module**
|
|
38
|
+
- This fixes some bugs related to destroying players.
|
|
39
|
+
- Faster node connection speeds.
|
|
40
|
+
- Uses an Array for getting the region instead (testing).
|
|
41
|
+
- Small change on the Voice Handler.
|
|
42
|
+
- Improved Error handling.
|
|
43
|
+
- Use `node.destroy()` method directly.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### **Remade `Connection` module**
|
|
48
|
+
- Removed lots of useless code.
|
|
49
|
+
- Improved joining voice channel speed.
|
|
50
|
+
- Improved configuration set/get speed.
|
|
51
|
+
- Improved overall checking.
|
|
52
|
+
- Improved debug messages.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
### **Remade `Node` module (this one is good)**
|
|
57
|
+
- 1.9.1-beta1: Fixed the auto reconnect system
|
|
58
|
+
- Fixed the `autoResume` system (now will actually work, for 60 seconds).
|
|
59
|
+
- New WebSocket System.
|
|
60
|
+
- Improved the events handling speed.
|
|
61
|
+
- Now does recalculation of the backoff time (for more efficiency on reconnect).
|
|
62
|
+
- Now avoids reconnecting if the WebSocket is already open (sorry, I forgot to add this before).
|
|
63
|
+
- Better cleaning system (improved, now removes listeners instead of setting to `null`).
|
|
64
|
+
- Avoids re-binding the functions every time `connect` is called (yay).
|
|
65
|
+
- This update also improves long-process running.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
### **Remade the `Player` module (also a good one)**
|
|
70
|
+
- Remade every method.
|
|
71
|
+
- Fixed destroy system.
|
|
72
|
+
- Better event handling, I think.
|
|
73
|
+
- Made the events async.
|
|
74
|
+
- Removed `trackChange` (does not exist in Lavalink API, use `trackStart` instead).
|
|
75
|
+
- Uses a new listener system (way more efficient for creating/destroying players).
|
|
76
|
+
- Faster shuffle in V8 Engine (Math stuff).
|
|
77
|
+
- Improved overall configs (more precise).
|
|
78
|
+
- Use `pop()` instead of disabling the track 50 on the length.
|
|
79
|
+
- Improved overall speed on the check-ins and some stuff I forgot.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
### **Remade the `Rest` module**
|
|
84
|
+
- Better speed (removed useless `buildEndpoint`).
|
|
85
|
+
- More compact code.
|
|
86
|
+
- Removed `stats/all` in the stats (correct by using the Lavalink API).
|
|
87
|
+
- Better `makeRequest`.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### **Small changes in `Track` module**
|
|
92
|
+
- More efficient final result (`author` + `track`).
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
That’s all for **1.9.0** atm. I’m a lazy dev. 😴
|
|
27
97
|
|
|
28
98
|
# Docs (Wiki)
|
|
29
99
|
- https://github.com/ToddyTheNoobDud/AquaLink/wiki
|
|
30
100
|
|
|
31
101
|
- Example bot: https://github.com/ToddyTheNoobDud/Thorium-Music
|
|
32
102
|
|
|
33
|
-
# Brick by brick, 1.8.0 Update (yay)
|
|
34
|
-
|
|
35
|
-
### 1.8.1-beta5 Update:
|
|
36
|
-
- Use @performanc/pwsl-mini as main WebSocket
|
|
37
|
-
- Use pool for connections (Experimental, help me improve it. Undici pool)
|
|
38
|
-
- Default will not leave the VC anymore (leaveOnEnd: false default)
|
|
39
|
-
- Misc optimizations on node and player
|
|
40
|
-
|
|
41
|
-
### 1.8.0
|
|
42
|
-
- Misc changes on FetchImage (improves the overall checking and speed)
|
|
43
|
-
- Rewrite `AQUA` module
|
|
44
|
-
- Remade the resolve logic (improves the speed by a lot)
|
|
45
|
-
- Fixes many memory usages related to nodes
|
|
46
|
-
- send is no longer required to be Applied (Applied by default now.)
|
|
47
|
-
- Remade some stuff with discord VoiceGateway
|
|
48
|
-
|
|
49
|
-
- Remade `CONNECTION` module
|
|
50
|
-
- Way faster connections (Joining, reconnecting, connected, disconnect)
|
|
51
|
-
- Reduced memory overload by removing useless code
|
|
52
|
-
- Improved early Returns
|
|
53
|
-
|
|
54
|
-
- Remade `NODE` module
|
|
55
|
-
- MANY fixes for the connection logic (fixes reconnection, etc)
|
|
56
|
-
- Fixed memory leaks in heartbeat system (hopefully, reduced memory by a lot.)
|
|
57
|
-
- Faster connection speed and checkings
|
|
58
|
-
- Remade the Options system, improve JSON parsing
|
|
59
|
-
|
|
60
|
-
- Rewrite `PLAYER` module
|
|
61
|
-
- Many memory related fixes
|
|
62
|
-
- Improved the overall code speed by a lot
|
|
63
|
-
- Rewrote setLoop, play, shuffle, replay methods (fixes + performance)
|
|
64
|
-
- Added 2 new options:
|
|
65
|
-
|
|
66
|
-
leaveOnEnd: false, // Optional
|
|
67
|
-
|
|
68
|
-
shouldDeleteMessage: true // Optional
|
|
69
|
-
|
|
70
|
-
- Uses array for better performance and less memory allocation
|
|
71
|
-
- Rewrite the Events handling (speed and recourses fixes)
|
|
72
|
-
|
|
73
|
-
- Updated `TRACK` module
|
|
74
|
-
- Better object handling for internal code.
|
|
75
|
-
- Removed an useless method
|
|
76
|
-
|
|
77
|
-
Thats all for now, im lazy, help me fix code and improve this on github... i can't test properly 😭😭😭
|
|
78
|
-
|
|
79
103
|
# How to install
|
|
80
104
|
|
|
81
105
|
`npm install aqualink`
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
const { request } = require("undici");
|
|
2
|
+
|
|
2
3
|
const sourceHandlers = new Map([
|
|
3
4
|
['spotify', uri => fetchThumbnail(`https://open.spotify.com/oembed?url=${uri}`)],
|
|
4
5
|
['youtube', identifier => fetchYouTubeThumbnail(identifier)]
|
|
5
6
|
]);
|
|
6
|
-
|
|
7
|
+
|
|
8
|
+
const YOUTUBE_URL_TEMPLATE = (quality) => (id) => `https://img.youtube.com/vi/${id}/${quality}.jpg`;
|
|
7
9
|
const YOUTUBE_QUALITIES = ['maxresdefault', 'hqdefault', 'mqdefault', 'default'].map(YOUTUBE_URL_TEMPLATE);
|
|
8
10
|
|
|
9
11
|
async function getImageUrl(info) {
|
|
10
12
|
if (!info?.sourceName || !info?.uri) return null;
|
|
13
|
+
|
|
11
14
|
const handler = sourceHandlers.get(info.sourceName.toLowerCase());
|
|
12
15
|
if (!handler) return null;
|
|
16
|
+
|
|
13
17
|
try {
|
|
14
18
|
return await handler(info.uri);
|
|
15
19
|
} catch (error) {
|
|
@@ -33,15 +37,12 @@ async function fetchThumbnail(url) {
|
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
async function fetchYouTubeThumbnail(identifier) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
);
|
|
40
|
-
return thumbnail || null;
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.error('No valid YouTube thumbnail found:', error);
|
|
40
|
+
return Promise.race(
|
|
41
|
+
YOUTUBE_QUALITIES.map(urlFunc => fetchThumbnail(urlFunc(identifier)))
|
|
42
|
+
).catch(() => {
|
|
43
|
+
console.error('No valid YouTube thumbnail found.');
|
|
43
44
|
return null;
|
|
44
|
-
}
|
|
45
|
+
});
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
module.exports = { getImageUrl };
|
|
48
|
+
module.exports = { getImageUrl };
|
package/build/structures/Aqua.js
CHANGED
|
@@ -8,17 +8,6 @@ const { version: pkgVersion } = require("../../package.json");
|
|
|
8
8
|
const URL_REGEX = /^https?:\/\//;
|
|
9
9
|
|
|
10
10
|
class Aqua extends EventEmitter {
|
|
11
|
-
/**
|
|
12
|
-
* @param {Object} client - The client instance.
|
|
13
|
-
* @param {Array<Object>} nodes - An array of node configurations.
|
|
14
|
-
* @param {Object} options - Configuration options for Aqua.
|
|
15
|
-
* @param {Function} options.send - Function to send data.
|
|
16
|
-
* @param {string} [options.defaultSearchPlatform="ytsearch"] - Default search platform.
|
|
17
|
-
* @param {string} [options.restVersion="v4"] - Version of the REST API.
|
|
18
|
-
* @param {Array<Object>} [options.plugins=[]] - Plugins to load.
|
|
19
|
-
* @param {boolean} [options.autoResume=false] - Automatically resume tracks on reconnect.
|
|
20
|
-
* @param {boolean} [options.infiniteReconnects=false] - Reconnect infinitely.
|
|
21
|
-
*/
|
|
22
11
|
constructor(client, nodes, options = {}) {
|
|
23
12
|
super();
|
|
24
13
|
this.validateInputs(client, nodes, options);
|
|
@@ -61,61 +50,68 @@ class Aqua extends EventEmitter {
|
|
|
61
50
|
get leastUsedNodes() {
|
|
62
51
|
const now = Date.now();
|
|
63
52
|
if (now - this._leastUsedCache.timestamp < 50) return this._leastUsedCache.nodes;
|
|
53
|
+
|
|
64
54
|
const nodes = [];
|
|
65
55
|
for (const node of this.nodeMap.values()) {
|
|
66
56
|
if (node.connected) nodes.push(node);
|
|
67
57
|
}
|
|
68
58
|
nodes.sort((a, b) => a.rest.calls - b.rest.calls);
|
|
59
|
+
|
|
69
60
|
this._leastUsedCache = { nodes, timestamp: now };
|
|
70
61
|
return nodes;
|
|
71
62
|
}
|
|
72
63
|
|
|
73
64
|
init(clientId) {
|
|
74
65
|
if (this.initiated) return this;
|
|
66
|
+
|
|
75
67
|
this.clientId = clientId;
|
|
76
68
|
try {
|
|
77
69
|
this.nodes.forEach(nodeConfig => this.createNode(nodeConfig));
|
|
78
|
-
this.initiated = true;
|
|
79
70
|
this.plugins.forEach(plugin => plugin.load(this));
|
|
71
|
+
this.initiated = true;
|
|
80
72
|
} catch (error) {
|
|
81
73
|
this.initiated = false;
|
|
82
74
|
throw error;
|
|
83
75
|
}
|
|
76
|
+
|
|
84
77
|
return this;
|
|
85
78
|
}
|
|
86
79
|
|
|
87
80
|
createNode(options) {
|
|
88
81
|
const nodeId = options.name || options.host;
|
|
89
82
|
this.destroyNode(nodeId);
|
|
83
|
+
|
|
90
84
|
const node = new Node(this, options, this.options);
|
|
91
85
|
this.nodeMap.set(nodeId, node);
|
|
92
86
|
this._leastUsedCache.timestamp = 0;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
|
|
88
|
+
node.connect()
|
|
89
|
+
.then(() => this.emit("nodeCreate", node))
|
|
90
|
+
.catch(error => {
|
|
91
|
+
this.nodeMap.delete(nodeId);
|
|
92
|
+
console.error("Failed to connect node:", error);
|
|
93
|
+
throw error;
|
|
94
|
+
});
|
|
95
|
+
|
|
99
96
|
return node;
|
|
100
97
|
}
|
|
101
98
|
|
|
102
99
|
destroyNode(identifier) {
|
|
103
100
|
const node = this.nodeMap.get(identifier);
|
|
104
101
|
if (!node) return;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
this.emit("nodeDestroy", node);
|
|
110
|
-
}).catch(error => console.error(`Error destroying node ${identifier}:`, error));
|
|
102
|
+
|
|
103
|
+
node.destroy();
|
|
104
|
+
this.nodeMap.delete(identifier);
|
|
105
|
+
this.emit("nodeDestroy", node);
|
|
111
106
|
}
|
|
112
107
|
|
|
108
|
+
|
|
113
109
|
updateVoiceState({ d, t }) {
|
|
114
110
|
const player = this.players.get(d.guild_id);
|
|
115
111
|
if (!player) return;
|
|
116
112
|
|
|
113
|
+
const updateMethod = t === "VOICE_SERVER_UPDATE" ? "setServerUpdate" : "setStateUpdate";
|
|
117
114
|
if (t === "VOICE_SERVER_UPDATE" || (t === "VOICE_STATE_UPDATE" && d.user_id === this.clientId)) {
|
|
118
|
-
const updateMethod = t === "VOICE_SERVER_UPDATE" ? "setServerUpdate" : "setStateUpdate";
|
|
119
115
|
if (player.connection && typeof player.connection[updateMethod] === "function") {
|
|
120
116
|
player.connection[updateMethod](d);
|
|
121
117
|
}
|
|
@@ -127,14 +123,13 @@ class Aqua extends EventEmitter {
|
|
|
127
123
|
|
|
128
124
|
fetchRegion(region) {
|
|
129
125
|
if (!region) return this.leastUsedNodes;
|
|
126
|
+
|
|
130
127
|
const lowerRegion = region.toLowerCase();
|
|
131
|
-
const regionNodes =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
regionNodes.push(node);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
128
|
+
const regionNodes = Array.from(this.nodeMap.values()).filter(node =>
|
|
129
|
+
node.connected && node.regions?.includes(lowerRegion)
|
|
130
|
+
);
|
|
137
131
|
regionNodes.sort((a, b) => this.calculateLoad(a) - this.calculateLoad(b));
|
|
132
|
+
|
|
138
133
|
return regionNodes;
|
|
139
134
|
}
|
|
140
135
|
|
|
@@ -148,9 +143,11 @@ class Aqua extends EventEmitter {
|
|
|
148
143
|
this.ensureInitialized();
|
|
149
144
|
const existingPlayer = this.players.get(options.guildId);
|
|
150
145
|
if (existingPlayer && existingPlayer.voiceChannel) return existingPlayer;
|
|
146
|
+
|
|
151
147
|
const availableNodes = options.region ? this.fetchRegion(options.region) : this.leastUsedNodes;
|
|
152
148
|
const node = availableNodes[0];
|
|
153
149
|
if (!node) throw new Error("No nodes are available");
|
|
150
|
+
|
|
154
151
|
return this.createPlayer(node, options);
|
|
155
152
|
}
|
|
156
153
|
|
|
@@ -167,6 +164,7 @@ class Aqua extends EventEmitter {
|
|
|
167
164
|
async destroyPlayer(guildId) {
|
|
168
165
|
const player = this.players.get(guildId);
|
|
169
166
|
if (!player) return;
|
|
167
|
+
|
|
170
168
|
try {
|
|
171
169
|
await player.clearData();
|
|
172
170
|
player.removeAllListeners();
|
|
@@ -276,4 +274,4 @@ class Aqua extends EventEmitter {
|
|
|
276
274
|
}
|
|
277
275
|
}
|
|
278
276
|
|
|
279
|
-
module.exports = Aqua
|
|
277
|
+
module.exports = Aqua;
|
|
@@ -3,58 +3,41 @@
|
|
|
3
3
|
class Connection {
|
|
4
4
|
constructor(player) {
|
|
5
5
|
this.playerRef = new WeakRef(player);
|
|
6
|
-
|
|
7
|
-
this.voice = {
|
|
8
|
-
sessionId: null,
|
|
9
|
-
endpoint: null,
|
|
10
|
-
token: null
|
|
11
|
-
};
|
|
6
|
+
this.voice = { sessionId: null, endpoint: null, token: null };
|
|
12
7
|
this.region = null;
|
|
13
8
|
this.selfDeaf = false;
|
|
14
9
|
this.selfMute = false;
|
|
15
|
-
this.voiceChannel = voiceChannel;
|
|
16
|
-
this.guildId = guildId;
|
|
17
|
-
this.aqua = aqua;
|
|
18
|
-
this.nodes = nodes;
|
|
10
|
+
this.voiceChannel = player.voiceChannel;
|
|
11
|
+
this.guildId = player.guildId;
|
|
12
|
+
this.aqua = player.aqua;
|
|
13
|
+
this.nodes = player.nodes;
|
|
19
14
|
}
|
|
20
15
|
|
|
21
|
-
setServerUpdate(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (!endpoint) {
|
|
25
|
-
throw new Error("Missing 'endpoint' property");
|
|
26
|
-
}
|
|
27
|
-
|
|
16
|
+
setServerUpdate({ endpoint, token } = {}) {
|
|
17
|
+
if (!endpoint) throw new Error("Missing 'endpoint' property");
|
|
28
18
|
const newRegion = endpoint.split('.')[0];
|
|
29
19
|
if (this.region !== newRegion) {
|
|
30
|
-
this.voice.endpoint
|
|
31
|
-
this.voice.token = token;
|
|
20
|
+
this.voice = { ...this.voice, endpoint, token };
|
|
32
21
|
const prevRegion = this.region;
|
|
33
22
|
this.region = newRegion;
|
|
34
|
-
|
|
35
|
-
const message = prevRegion
|
|
36
|
-
? `Changed Voice Region from ${prevRegion} to ${newRegion}`
|
|
37
|
-
: `Voice Server: ${newRegion}`;
|
|
38
|
-
|
|
39
|
-
this.aqua.emit("debug", `[Player ${this.guildId} - CONNECTION] ${message}`);
|
|
23
|
+
this.aqua.emit("debug", `[Player ${this.guildId} - CONNECTION] Voice Server: ${prevRegion ? `Changed from ${prevRegion} to ${newRegion}` : newRegion}`);
|
|
40
24
|
this._updatePlayerVoiceData();
|
|
41
25
|
}
|
|
42
26
|
}
|
|
43
27
|
|
|
44
|
-
setStateUpdate({ channel_id
|
|
45
|
-
if (!
|
|
28
|
+
setStateUpdate({ channel_id, session_id, self_deaf, self_mute } = {}) {
|
|
29
|
+
if (!channel_id || !session_id) {
|
|
46
30
|
this.playerRef.deref()?.destroy();
|
|
47
31
|
return;
|
|
48
32
|
}
|
|
49
|
-
|
|
50
|
-
if (this.voiceChannel !==
|
|
51
|
-
this.aqua.emit("playerMove", this.voiceChannel,
|
|
52
|
-
this.voiceChannel =
|
|
33
|
+
|
|
34
|
+
if (this.voiceChannel !== channel_id) {
|
|
35
|
+
this.aqua.emit("playerMove", this.voiceChannel, channel_id);
|
|
36
|
+
this.voiceChannel = channel_id;
|
|
53
37
|
}
|
|
54
|
-
|
|
55
|
-
this
|
|
56
|
-
this.
|
|
57
|
-
this.voice.sessionId = sessionId;
|
|
38
|
+
|
|
39
|
+
Object.assign(this, { selfDeaf: self_deaf, selfMute: self_mute });
|
|
40
|
+
this.voice.sessionId = session_id;
|
|
58
41
|
}
|
|
59
42
|
|
|
60
43
|
async _updatePlayerVoiceData() {
|
|
@@ -64,19 +47,12 @@ class Connection {
|
|
|
64
47
|
try {
|
|
65
48
|
await this.nodes.rest.updatePlayer({
|
|
66
49
|
guildId: this.guildId,
|
|
67
|
-
data: {
|
|
68
|
-
voice: this.voice,
|
|
69
|
-
volume: player.volume
|
|
70
|
-
}
|
|
50
|
+
data: { voice: this.voice, volume: player.volume }
|
|
71
51
|
});
|
|
72
52
|
} catch (err) {
|
|
73
|
-
this.aqua.emit("apiError", "updatePlayer", {
|
|
74
|
-
error: err,
|
|
75
|
-
guildId: this.guildId,
|
|
76
|
-
voiceData: this.voice
|
|
77
|
-
});
|
|
53
|
+
this.aqua.emit("apiError", "updatePlayer", { error: err, guildId: this.guildId, voiceData: this.voice });
|
|
78
54
|
}
|
|
79
55
|
}
|
|
80
56
|
}
|
|
81
57
|
|
|
82
|
-
module.exports = Connection;
|
|
58
|
+
module.exports = Connection;
|
package/build/structures/Node.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const WebSocket = require(
|
|
2
|
+
const WebSocket = require('ws');
|
|
3
3
|
const Rest = require("./Rest");
|
|
4
4
|
|
|
5
5
|
class Node {
|
|
@@ -30,7 +30,6 @@ class Node {
|
|
|
30
30
|
this.regions = regions;
|
|
31
31
|
this.wsUrl = new URL(`ws${secure ? "s" : ""}://${host}:${port}/v4/websocket`);
|
|
32
32
|
this.rest = new Rest(aqua, this);
|
|
33
|
-
this.resumeKey = options.resumeKey || null;
|
|
34
33
|
this.resumeTimeout = options.resumeTimeout || 60;
|
|
35
34
|
this.autoResume = options.autoResume || false;
|
|
36
35
|
this.reconnectTimeout = options.reconnectTimeout || 2000;
|
|
@@ -70,29 +69,29 @@ class Node {
|
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
#constructHeaders() {
|
|
73
|
-
|
|
72
|
+
const headers = {
|
|
74
73
|
Authorization: this.password,
|
|
75
74
|
"User-Id": this.aqua.clientId,
|
|
76
75
|
"Client-Name": `Aqua/${this.aqua.version}`,
|
|
77
|
-
...(this.sessionId && { "Session-Id": this.sessionId }),
|
|
78
|
-
...(this.resumeKey && { "Resume-Key": this.resumeKey })
|
|
79
76
|
};
|
|
77
|
+
if (this.sessionId) headers["Session-Id"] = this.sessionId;
|
|
78
|
+
return headers;
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
async #onOpen() {
|
|
83
82
|
this.connected = true;
|
|
84
83
|
this.#reconnectAttempted = 0;
|
|
85
84
|
this.aqua.emit("debug", this.name, `Connected to ${this.wsUrl.href}`);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
this.info = await this.rest.makeRequest("GET", "/v4/info");
|
|
88
|
+
if (this.autoResume && this.sessionId) {
|
|
90
89
|
await this.resumePlayers();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
90
|
+
}
|
|
91
|
+
} catch (err) {
|
|
92
|
+
this.info = null;
|
|
93
|
+
if (!this.aqua.bypassChecks?.nodeFetchInfo) {
|
|
94
|
+
this.aqua.emit("error", this, `Failed to fetch node info: ${err.message}`);
|
|
96
95
|
}
|
|
97
96
|
}
|
|
98
97
|
}
|
|
@@ -108,8 +107,9 @@ class Node {
|
|
|
108
107
|
try {
|
|
109
108
|
payload = JSON.parse(msg);
|
|
110
109
|
} catch {
|
|
111
|
-
return;
|
|
110
|
+
return; // Invalid JSON, ignore the message
|
|
112
111
|
}
|
|
112
|
+
|
|
113
113
|
const op = payload?.op;
|
|
114
114
|
if (!op) return;
|
|
115
115
|
|
|
@@ -125,6 +125,18 @@ class Node {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
async resumePlayers() {
|
|
129
|
+
try {
|
|
130
|
+
await this.rest.makeRequest("PATCH", `/v4/sessions/${this.sessionId}`, {
|
|
131
|
+
resuming: true,
|
|
132
|
+
timeout: this.resumeTimeout
|
|
133
|
+
});
|
|
134
|
+
this.aqua.emit("debug", this.name, `Successfully resumed session ${this.sessionId}`);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
this.aqua.emit("error", this, `Failed to resume session: ${err.message}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
128
140
|
#updateStats(payload) {
|
|
129
141
|
if (!payload) return;
|
|
130
142
|
this.stats = {
|
|
@@ -170,14 +182,19 @@ class Node {
|
|
|
170
182
|
}
|
|
171
183
|
|
|
172
184
|
#handleReadyOp(payload) {
|
|
173
|
-
if (
|
|
174
|
-
this.
|
|
175
|
-
|
|
185
|
+
if (!payload.sessionId) {
|
|
186
|
+
this.aqua.emit("error", this, "Ready payload missing sessionId");
|
|
187
|
+
return;
|
|
176
188
|
}
|
|
189
|
+
|
|
190
|
+
this.sessionId = payload.sessionId;
|
|
191
|
+
this.rest.setSessionId(payload.sessionId);
|
|
192
|
+
|
|
177
193
|
this.aqua.emit("nodeConnect", this);
|
|
178
194
|
}
|
|
179
195
|
|
|
180
196
|
#handlePlayerOp(payload) {
|
|
197
|
+
if (!payload.guildId) return;
|
|
181
198
|
const player = this.aqua.players.get(payload.guildId);
|
|
182
199
|
if (player) player.emit(payload.op, payload);
|
|
183
200
|
}
|
|
@@ -188,7 +205,7 @@ class Node {
|
|
|
188
205
|
|
|
189
206
|
#onClose(code, reason) {
|
|
190
207
|
this.connected = false;
|
|
191
|
-
this.aqua.emit("nodeDisconnect", this, { code, reason });
|
|
208
|
+
this.aqua.emit("nodeDisconnect", this, { code, reason: reason?.toString() || "No reason provided" });
|
|
192
209
|
this.#reconnect();
|
|
193
210
|
}
|
|
194
211
|
|
|
@@ -198,22 +215,24 @@ class Node {
|
|
|
198
215
|
setTimeout(() => this.connect(), 10000);
|
|
199
216
|
return;
|
|
200
217
|
}
|
|
218
|
+
|
|
201
219
|
if (this.#reconnectAttempted >= this.reconnectTries) {
|
|
202
220
|
this.aqua.emit("nodeError", this,
|
|
203
221
|
new Error(`Max reconnection attempts reached (${this.reconnectTries})`));
|
|
204
222
|
this.destroy(true);
|
|
205
223
|
return;
|
|
206
224
|
}
|
|
225
|
+
|
|
207
226
|
clearTimeout(this.#reconnectTimeoutId);
|
|
208
227
|
const jitter = Math.random() * 10000;
|
|
209
228
|
const backoffTime = Math.min(
|
|
210
229
|
this.reconnectTimeout * Math.pow(Node.BACKOFF_MULTIPLIER, this.#reconnectAttempted) + jitter,
|
|
211
230
|
Node.MAX_BACKOFF
|
|
212
231
|
);
|
|
232
|
+
|
|
213
233
|
this.#reconnectTimeoutId = setTimeout(() => {
|
|
214
234
|
this.#reconnectAttempted++;
|
|
215
|
-
this.aqua.emit("nodeReconnect", {
|
|
216
|
-
nodeName: this.name,
|
|
235
|
+
this.aqua.emit("nodeReconnect", this, {
|
|
217
236
|
attempt: this.#reconnectAttempted,
|
|
218
237
|
backoffTime
|
|
219
238
|
});
|
|
@@ -222,11 +241,22 @@ class Node {
|
|
|
222
241
|
}
|
|
223
242
|
|
|
224
243
|
destroy(clean = false) {
|
|
244
|
+
clearTimeout(this.#reconnectTimeoutId);
|
|
245
|
+
|
|
246
|
+
if (this.#ws) {
|
|
247
|
+
this.#ws.removeAllListeners();
|
|
248
|
+
if (this.#ws.readyState === WebSocket.OPEN) {
|
|
249
|
+
this.#ws.close();
|
|
250
|
+
}
|
|
251
|
+
this.#ws = null;
|
|
252
|
+
}
|
|
253
|
+
|
|
225
254
|
if (clean) {
|
|
226
255
|
this.aqua.emit("nodeDestroy", this);
|
|
227
256
|
this.aqua.nodes.delete(this.name);
|
|
228
257
|
return;
|
|
229
258
|
}
|
|
259
|
+
|
|
230
260
|
if (this.connected) {
|
|
231
261
|
for (const player of this.aqua.players.values()) {
|
|
232
262
|
if (player.node === this) {
|
|
@@ -234,11 +264,12 @@ class Node {
|
|
|
234
264
|
}
|
|
235
265
|
}
|
|
236
266
|
}
|
|
267
|
+
|
|
237
268
|
this.connected = false;
|
|
238
|
-
this.aqua.
|
|
269
|
+
this.aqua.nodes.delete(this.name);
|
|
239
270
|
this.aqua.emit("nodeDestroy", this);
|
|
240
271
|
this.info = null;
|
|
241
272
|
}
|
|
242
273
|
}
|
|
243
274
|
|
|
244
|
-
module.exports = Node;
|
|
275
|
+
module.exports = Node;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const { EventEmitter } = require("events");
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
4
|
+
const Connection = require("./Connection");
|
|
5
|
+
const Queue = require("./Queue");
|
|
6
|
+
const Filters = require("./Filters");
|
|
7
7
|
|
|
8
8
|
class Player extends EventEmitter {
|
|
9
9
|
static LOOP_MODES = Object.freeze({
|
|
@@ -30,32 +30,22 @@ class Player extends EventEmitter {
|
|
|
30
30
|
this.voiceChannel = options.voiceChannel;
|
|
31
31
|
this.connection = new Connection(this);
|
|
32
32
|
this.filters = new Filters(this);
|
|
33
|
-
this.
|
|
34
|
-
this.
|
|
35
|
-
this.volume = options.defaultVolume ?? 100;
|
|
36
|
-
this.loop = options.loop ?? Player.LOOP_MODES.NONE;
|
|
33
|
+
this.volume = Math.min(Math.max(options.defaultVolume ?? 100, 0), 200);
|
|
34
|
+
this.loop = Player.LOOP_MODES[options.loop?.toUpperCase()] || Player.LOOP_MODES.NONE;
|
|
37
35
|
this.queue = new Queue();
|
|
38
|
-
this.
|
|
39
|
-
this.
|
|
36
|
+
this.previousTracks = [];
|
|
37
|
+
this.shouldDeleteMessage = options.shouldDeleteMessage ?? false;
|
|
38
|
+
this.leaveOnEnd = options.leaveOnEnd ?? false;
|
|
39
|
+
|
|
40
40
|
this.playing = false;
|
|
41
41
|
this.paused = false;
|
|
42
42
|
this.connected = false;
|
|
43
|
+
this.current = null;
|
|
43
44
|
this.timestamp = 0;
|
|
44
45
|
this.ping = 0;
|
|
45
46
|
this.nowPlayingMessage = null;
|
|
46
|
-
|
|
47
|
-
this.
|
|
48
|
-
this.leaveOnEnd = options.leaveOnEnd ?? false;
|
|
49
|
-
|
|
50
|
-
this.onPlayerUpdate = ({ state } = {}) => {
|
|
51
|
-
if (!state) return;
|
|
52
|
-
for (const key in state) {
|
|
53
|
-
if (state.hasOwnProperty(key)) {
|
|
54
|
-
this[key] = state[key];
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
this.aqua.emit("playerUpdate", this, { state });
|
|
58
|
-
};
|
|
47
|
+
|
|
48
|
+
this.onPlayerUpdate = ({ state }) => state && Object.assign(this, state) && this.aqua.emit("playerUpdate", this, { state });
|
|
59
49
|
this.handleEvent = async (payload) => {
|
|
60
50
|
const player = this.aqua.players.get(payload.guildId);
|
|
61
51
|
if (!player) return;
|
|
@@ -66,8 +56,12 @@ class Player extends EventEmitter {
|
|
|
66
56
|
this.handleUnknownEvent(payload);
|
|
67
57
|
}
|
|
68
58
|
};
|
|
69
|
-
this.
|
|
70
|
-
|
|
59
|
+
if (!this.listenerCount("playerUpdate")) {
|
|
60
|
+
this.on("playerUpdate", this.onPlayerUpdate);
|
|
61
|
+
}
|
|
62
|
+
if (!this.listenerCount("event")) {
|
|
63
|
+
this.on("event", this.handleEvent);
|
|
64
|
+
}
|
|
71
65
|
}
|
|
72
66
|
|
|
73
67
|
get previous() {
|
|
@@ -78,21 +72,18 @@ class Player extends EventEmitter {
|
|
|
78
72
|
}
|
|
79
73
|
|
|
80
74
|
addToPreviousTrack(track) {
|
|
81
|
-
if (this.previousTracks.length >= 50)
|
|
82
|
-
this.previousTracks.length = 49;
|
|
83
|
-
}
|
|
75
|
+
if (this.previousTracks.length >= 50) this.previousTracks.pop();
|
|
84
76
|
this.previousTracks.unshift(track);
|
|
85
77
|
}
|
|
86
78
|
|
|
87
79
|
async play() {
|
|
88
|
-
if (!this.connected
|
|
89
|
-
if (!this.queue.length) return;
|
|
90
|
-
|
|
80
|
+
if (!this.connected || !this.queue.length) return;
|
|
91
81
|
const item = this.queue.shift();
|
|
82
|
+
|
|
92
83
|
this.current = item.track ? item : await item.resolve(this.aqua);
|
|
93
84
|
this.playing = true;
|
|
94
85
|
this.position = 0;
|
|
95
|
-
|
|
86
|
+
|
|
96
87
|
this.aqua.emit("debug", this.guildId, `Playing track: ${this.current.track}`);
|
|
97
88
|
return this.updatePlayer({ track: { encoded: this.current.track } });
|
|
98
89
|
}
|
|
@@ -128,39 +119,25 @@ class Player extends EventEmitter {
|
|
|
128
119
|
|
|
129
120
|
async searchLyrics(query) {
|
|
130
121
|
if (!query) return null;
|
|
131
|
-
|
|
132
|
-
track: {
|
|
133
|
-
encoded: { info: { title: query } },
|
|
134
|
-
guild_id: this.guildId,
|
|
135
|
-
search: true
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
return response || null;
|
|
122
|
+
return await this.nodes.rest.getLyrics({ track: { info: { title: query } }, search: true }) || null;
|
|
139
123
|
}
|
|
140
124
|
|
|
141
125
|
async lyrics() {
|
|
142
126
|
if (!this.playing) return null;
|
|
143
|
-
|
|
144
|
-
track: {
|
|
145
|
-
encoded: this.current.track,
|
|
146
|
-
guild_id: this.guildId
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
return response || null;
|
|
127
|
+
return await this.nodes.rest.getLyrics({ track: { encoded: this.current.track } }) || null;
|
|
150
128
|
}
|
|
151
129
|
|
|
152
130
|
seek(position) {
|
|
153
131
|
if (!this.playing) return this;
|
|
154
|
-
this.position += position;
|
|
155
|
-
this.updatePlayer({ position: this.position });
|
|
132
|
+
this.updatePlayer({ position: (this.position += position) });
|
|
156
133
|
return this;
|
|
157
134
|
}
|
|
158
135
|
|
|
159
136
|
stop() {
|
|
160
137
|
if (!this.playing) return this;
|
|
161
|
-
this.updatePlayer({ track: { encoded: null } });
|
|
162
138
|
this.playing = false;
|
|
163
139
|
this.position = 0;
|
|
140
|
+
this.updatePlayer({ track: { encoded: null } });
|
|
164
141
|
return this;
|
|
165
142
|
}
|
|
166
143
|
|
|
@@ -179,6 +156,7 @@ class Player extends EventEmitter {
|
|
|
179
156
|
}
|
|
180
157
|
|
|
181
158
|
setTextChannel(channel) {
|
|
159
|
+
this.textChannel = channel;
|
|
182
160
|
this.updatePlayer({ text_channel: channel });
|
|
183
161
|
return this;
|
|
184
162
|
}
|
|
@@ -199,23 +177,14 @@ class Player extends EventEmitter {
|
|
|
199
177
|
}
|
|
200
178
|
|
|
201
179
|
disconnect() {
|
|
202
|
-
this.updatePlayer({ track: { encoded: null } });
|
|
203
180
|
this.connected = false;
|
|
204
181
|
this.send({ guild_id: this.guildId, channel_id: null });
|
|
205
|
-
this.
|
|
182
|
+
this.voiceChannel = null;
|
|
183
|
+
this.aqua.emit("debug", this.guildId, "Player disconnected.");
|
|
206
184
|
return this;
|
|
207
185
|
}
|
|
208
|
-
|
|
209
186
|
shuffle() {
|
|
210
|
-
|
|
211
|
-
let currentIndex = array.length;
|
|
212
|
-
|
|
213
|
-
while (currentIndex > 0) {
|
|
214
|
-
const randomIndex = Math.floor(Math.random() * currentIndex);
|
|
215
|
-
currentIndex--;
|
|
216
|
-
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
|
|
217
|
-
}
|
|
218
|
-
|
|
187
|
+
this.queue = this.queue.sort(() => Math.random() - 0.5);
|
|
219
188
|
return this;
|
|
220
189
|
}
|
|
221
190
|
|
|
@@ -225,7 +194,6 @@ class Player extends EventEmitter {
|
|
|
225
194
|
|
|
226
195
|
replay() {
|
|
227
196
|
this.seek(-this.position);
|
|
228
|
-
return this;
|
|
229
197
|
}
|
|
230
198
|
|
|
231
199
|
skip() {
|
|
@@ -245,12 +213,12 @@ class Player extends EventEmitter {
|
|
|
245
213
|
|
|
246
214
|
async trackEnd(player, track, payload) {
|
|
247
215
|
if (this.shouldDeleteMessage && this.nowPlayingMessage) {
|
|
248
|
-
await this.nowPlayingMessage.delete().catch(() => {});
|
|
216
|
+
await this.nowPlayingMessage.delete().catch(() => { });
|
|
249
217
|
this.nowPlayingMessage = null;
|
|
250
218
|
}
|
|
251
219
|
|
|
252
220
|
const reason = payload.reason?.replace("_", "").toLowerCase();
|
|
253
|
-
|
|
221
|
+
|
|
254
222
|
if (reason === "loadfailed" || reason === "cleanup") {
|
|
255
223
|
if (player.queue.isEmpty()) {
|
|
256
224
|
this.aqua.emit("queueEnd", player);
|
|
@@ -283,17 +251,17 @@ class Player extends EventEmitter {
|
|
|
283
251
|
await player.play();
|
|
284
252
|
}
|
|
285
253
|
|
|
286
|
-
trackError(player, track, payload) {
|
|
254
|
+
async trackError(player, track, payload) {
|
|
287
255
|
this.aqua.emit("trackError", player, track, payload);
|
|
288
256
|
return this.stop();
|
|
289
257
|
}
|
|
290
258
|
|
|
291
|
-
trackStuck(player, track, payload) {
|
|
259
|
+
async trackStuck(player, track, payload) {
|
|
292
260
|
this.aqua.emit("trackStuck", player, track, payload);
|
|
293
261
|
return this.stop();
|
|
294
262
|
}
|
|
295
263
|
|
|
296
|
-
socketClosed(player, payload) {
|
|
264
|
+
async socketClosed(player, payload) {
|
|
297
265
|
if (payload?.code === 4015 || payload?.code === 4009) {
|
|
298
266
|
this.send({
|
|
299
267
|
guild_id: payload.guildId,
|
|
@@ -311,22 +279,23 @@ class Player extends EventEmitter {
|
|
|
311
279
|
this.aqua.send({ op: 4, d: data });
|
|
312
280
|
}
|
|
313
281
|
|
|
314
|
-
|
|
282
|
+
#dataStore = new WeakMap();
|
|
283
|
+
|
|
284
|
+
set(key, value) {
|
|
285
|
+
this.#dataStore.set(key, value);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
get(key) {
|
|
289
|
+
return this.#dataStore.get(key);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
clearData() {
|
|
293
|
+
this.#dataStore = new WeakMap();
|
|
294
|
+
return this;
|
|
295
|
+
}
|
|
315
296
|
|
|
316
|
-
set(key, value) {
|
|
317
|
-
this.#dataStore.set(key, value);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
get(key) {
|
|
321
|
-
return this.#dataStore.get(key);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
clearData() {
|
|
325
|
-
this.#dataStore.clear();
|
|
326
|
-
return this;
|
|
327
|
-
}
|
|
328
297
|
|
|
329
|
-
|
|
298
|
+
updatePlayer(data) {
|
|
330
299
|
return this.nodes.rest.updatePlayer({ guildId: this.guildId, data });
|
|
331
300
|
}
|
|
332
301
|
|
package/build/structures/Rest.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const { Pool } = require("undici");
|
|
3
3
|
|
|
4
4
|
class Rest {
|
|
5
|
-
constructor(aqua, { secure, host, port, sessionId, password
|
|
5
|
+
constructor(aqua, { secure, host, port, sessionId, password }) {
|
|
6
6
|
this.aqua = aqua;
|
|
7
7
|
this.sessionId = sessionId;
|
|
8
8
|
this.version = "v4";
|
|
@@ -11,9 +11,7 @@ class Rest {
|
|
|
11
11
|
"Content-Type": "application/json",
|
|
12
12
|
Authorization: password,
|
|
13
13
|
};
|
|
14
|
-
this.client = new Pool(this.baseUrl, {
|
|
15
|
-
pipelining: 1,
|
|
16
|
-
});
|
|
14
|
+
this.client = new Pool(this.baseUrl, { pipelining: 1 });
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
setSessionId(sessionId) {
|
|
@@ -21,101 +19,77 @@ class Rest {
|
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
async makeRequest(method, endpoint, body = null) {
|
|
24
|
-
const options = {
|
|
25
|
-
path: endpoint,
|
|
26
|
-
method,
|
|
27
|
-
headers: this.headers,
|
|
28
|
-
...(body && { body: JSON.stringify(body) }),
|
|
29
|
-
};
|
|
30
22
|
try {
|
|
31
|
-
const response = await this.client.request(
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
const response = await this.client.request({
|
|
24
|
+
path: endpoint,
|
|
25
|
+
method,
|
|
26
|
+
headers: this.headers,
|
|
27
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return response.statusCode === 204 ? null : await response.body.json();
|
|
34
31
|
} catch (error) {
|
|
35
|
-
throw new Error(`Request
|
|
32
|
+
throw new Error(`Request failed (${method} ${endpoint}): ${error.message}`);
|
|
36
33
|
}
|
|
37
34
|
}
|
|
38
35
|
|
|
39
|
-
buildEndpoint(...segments) {
|
|
40
|
-
const validSegments = segments.filter(segment => segment && segment.trim());
|
|
41
|
-
return '/' + validSegments.join('/');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
36
|
validateSessionId() {
|
|
45
|
-
if (!this.sessionId)
|
|
46
|
-
throw new Error("Session ID is not set.");
|
|
47
|
-
}
|
|
37
|
+
if (!this.sessionId) throw new Error("Session ID is not set.");
|
|
48
38
|
}
|
|
49
39
|
|
|
50
40
|
async updatePlayer({ guildId, data }) {
|
|
51
|
-
|
|
52
|
-
const hasEncodedTrackAlt = data.encodedTrack && data.identifier;
|
|
53
|
-
|
|
54
|
-
if (hasEncodedTrack || hasEncodedTrackAlt) {
|
|
41
|
+
if ((data.track?.encoded && data.track?.identifier) || (data.encodedTrack && data.identifier)) {
|
|
55
42
|
throw new Error("Cannot provide both 'encoded' and 'identifier' for track");
|
|
56
43
|
}
|
|
57
|
-
|
|
58
44
|
this.validateSessionId();
|
|
59
|
-
|
|
60
|
-
return this.makeRequest("PATCH", endpoint, data);
|
|
45
|
+
return this.makeRequest("PATCH", `/${this.version}/sessions/${this.sessionId}/players/${guildId}?noReplace=false`, data);
|
|
61
46
|
}
|
|
62
47
|
|
|
63
48
|
async getPlayers() {
|
|
64
49
|
this.validateSessionId();
|
|
65
|
-
|
|
66
|
-
return this.makeRequest("GET", endpoint);
|
|
50
|
+
return this.makeRequest("GET", `/${this.version}/sessions/${this.sessionId}/players`);
|
|
67
51
|
}
|
|
68
52
|
|
|
69
53
|
async destroyPlayer(guildId) {
|
|
70
54
|
this.validateSessionId();
|
|
71
|
-
|
|
72
|
-
return this.makeRequest("DELETE", endpoint);
|
|
55
|
+
return this.makeRequest("DELETE", `/${this.version}/sessions/${this.sessionId}/players/${guildId}`);
|
|
73
56
|
}
|
|
74
57
|
|
|
75
58
|
async getTracks(identifier) {
|
|
76
|
-
|
|
77
|
-
return this.makeRequest("GET", endpoint);
|
|
59
|
+
return this.makeRequest("GET", `/${this.version}/loadtracks?identifier=${encodeURIComponent(identifier)}`);
|
|
78
60
|
}
|
|
79
61
|
|
|
80
62
|
async decodeTrack(track) {
|
|
81
|
-
|
|
82
|
-
return this.makeRequest("GET", endpoint);
|
|
63
|
+
return this.makeRequest("GET", `/${this.version}/decodetrack?encodedTrack=${encodeURIComponent(track)}`);
|
|
83
64
|
}
|
|
84
65
|
|
|
85
66
|
async decodeTracks(tracks) {
|
|
86
|
-
|
|
87
|
-
return this.makeRequest("POST", endpoint, tracks);
|
|
67
|
+
return this.makeRequest("POST", `/${this.version}/decodetracks`, tracks);
|
|
88
68
|
}
|
|
89
69
|
|
|
90
70
|
async getStats() {
|
|
91
|
-
|
|
92
|
-
return this.makeRequest("GET", endpoint);
|
|
71
|
+
return this.makeRequest("GET", `/${this.version}/stats`);
|
|
93
72
|
}
|
|
94
73
|
|
|
95
74
|
async getInfo() {
|
|
96
|
-
|
|
97
|
-
return this.makeRequest("GET", endpoint);
|
|
75
|
+
return this.makeRequest("GET", `/${this.version}/info`);
|
|
98
76
|
}
|
|
99
77
|
|
|
100
78
|
async getRoutePlannerStatus() {
|
|
101
|
-
|
|
102
|
-
return this.makeRequest("GET", endpoint);
|
|
79
|
+
return this.makeRequest("GET", `/${this.version}/routeplanner/status`);
|
|
103
80
|
}
|
|
104
81
|
|
|
105
82
|
async getRoutePlannerAddress(address) {
|
|
106
|
-
|
|
107
|
-
return this.makeRequest("POST", endpoint, { address });
|
|
83
|
+
return this.makeRequest("POST", `/${this.version}/routeplanner/free/address`, { address });
|
|
108
84
|
}
|
|
109
85
|
|
|
110
86
|
async getLyrics({ track }) {
|
|
111
87
|
if (track.search) {
|
|
112
|
-
const
|
|
113
|
-
const res = await this.makeRequest("GET", endpoint);
|
|
88
|
+
const res = await this.makeRequest("GET", `/${this.version}/lyrics/search?query=${encodeURIComponent(track.encoded.info.title)}&source=genius`);
|
|
114
89
|
if (res) return res;
|
|
115
90
|
}
|
|
116
91
|
this.validateSessionId();
|
|
117
|
-
|
|
118
|
-
return this.makeRequest("GET", endpoint);
|
|
92
|
+
return this.makeRequest("GET", `/${this.version}/sessions/${this.sessionId}/players/${track.guild_id}/track/lyrics?skipTrackSource=false`);
|
|
119
93
|
}
|
|
120
94
|
}
|
|
121
95
|
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const { getImageUrl } = require("../handlers/fetchImage");
|
|
3
|
-
|
|
4
|
-
* @typedef {import("../Aqua")} Aqua
|
|
5
|
-
* @typedef {import("../structures/Player")} Player
|
|
6
|
-
* @typedef {import("../structures/Node")} Node
|
|
7
|
-
*/
|
|
3
|
+
|
|
8
4
|
class Track {
|
|
9
|
-
/**
|
|
10
|
-
* @param {Object} data
|
|
11
|
-
* @param {Player} requester
|
|
12
|
-
* @param {Node} nodes
|
|
13
|
-
*/
|
|
14
5
|
constructor(data = {}, requester, nodes) {
|
|
15
6
|
const { info = {}, encoded = null, playlist = null } = data;
|
|
16
7
|
this.info = {
|
|
@@ -29,21 +20,18 @@ class Track {
|
|
|
29
20
|
this.requester = requester;
|
|
30
21
|
this.nodes = nodes;
|
|
31
22
|
}
|
|
32
|
-
|
|
33
|
-
* @param {string} thumbnail
|
|
34
|
-
* @returns {string|null}
|
|
35
|
-
*/
|
|
23
|
+
|
|
36
24
|
resolveThumbnail(thumbnail) {
|
|
37
|
-
|
|
38
|
-
return getImageUrl(thumbnail);
|
|
25
|
+
return thumbnail ? getImageUrl(thumbnail) : null;
|
|
39
26
|
}
|
|
40
|
-
|
|
41
|
-
* @param {Aqua} aqua
|
|
42
|
-
* @returns {Promise<Track|null>}
|
|
43
|
-
*/
|
|
27
|
+
|
|
44
28
|
async resolve(aqua) {
|
|
45
29
|
const searchPlatform = aqua?.options?.defaultSearchPlatform;
|
|
46
|
-
if (!searchPlatform)
|
|
30
|
+
if (!searchPlatform) {
|
|
31
|
+
console.warn("No search platform configured.");
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
47
35
|
try {
|
|
48
36
|
const query = `${this.info.author} - ${this.info.title}`;
|
|
49
37
|
const result = await aqua.resolve({
|
|
@@ -52,9 +40,12 @@ class Track {
|
|
|
52
40
|
requester: this.requester,
|
|
53
41
|
node: this.nodes
|
|
54
42
|
});
|
|
43
|
+
|
|
55
44
|
if (!result?.tracks?.length) return null;
|
|
56
45
|
const track = this._findMatchingTrack(result.tracks);
|
|
57
46
|
if (!track) return null;
|
|
47
|
+
|
|
48
|
+
// Update track info if a match is found
|
|
58
49
|
this.info.identifier = track.info.identifier;
|
|
59
50
|
this.track = track.track;
|
|
60
51
|
this.playlist = track.playlist || null;
|
|
@@ -64,6 +55,7 @@ class Track {
|
|
|
64
55
|
return null;
|
|
65
56
|
}
|
|
66
57
|
}
|
|
58
|
+
|
|
67
59
|
_findMatchingTrack(tracks) {
|
|
68
60
|
const { author, title, length } = this.info;
|
|
69
61
|
for (const track of tracks) {
|
|
@@ -77,4 +69,5 @@ class Track {
|
|
|
77
69
|
return null;
|
|
78
70
|
}
|
|
79
71
|
}
|
|
80
|
-
|
|
72
|
+
|
|
73
|
+
module.exports = Track;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aqualink",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0-beta2",
|
|
4
4
|
"description": "An Lavalink wrapper, focused in speed, performance, and features, Based in Riffy!",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"license": "ISC",
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"undici": "^7.4.0",
|
|
41
|
-
|
|
41
|
+
"ws": "^8.18.1"
|
|
42
42
|
},
|
|
43
43
|
"repository": {
|
|
44
44
|
"type": "git",
|