aqualink 1.7.0-beta6 → 1.8.1-beta1
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/index.d.ts +80 -0
- package/build/structures/Aqua.js +82 -101
- package/build/structures/Connection.js +66 -85
- package/build/structures/Filters.js +3 -1
- package/build/structures/Node.js +82 -97
- package/build/structures/Player.js +96 -97
- package/build/structures/Plugins.js +2 -1
- package/build/structures/Queue.js +2 -2
- package/build/structures/Rest.js +70 -61
- package/build/structures/Track.js +17 -40
- package/package.json +2 -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/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
declare module "AquaLink" {
|
|
2
|
+
export class Aqua {
|
|
3
|
+
/**
|
|
4
|
+
* @param {Object} client - The client instance.
|
|
5
|
+
* @param {Array<Object>} nodes - An array of node configurations.
|
|
6
|
+
* @param {Object} options - Configuration options for Aqua.
|
|
7
|
+
* @param {Function} options.send - Function to send data.
|
|
8
|
+
* @param {string} [options.defaultSearchPlatform="ytsearch"] - Default search platform.
|
|
9
|
+
* @param {string} [options.restVersion="v4"] - Version of the REST API.
|
|
10
|
+
* @param {Array<Object>} [options.plugins=[]] - Plugins to load.
|
|
11
|
+
* @param {boolean} [options.autoResume=false] - Automatically resume tracks on reconnect.
|
|
12
|
+
* @param {boolean} [options.infiniteReconnects=false] - Reconnect infinitely.
|
|
13
|
+
*/
|
|
14
|
+
constructor(client: any, nodes: Array<any>, options?: { [key: string]: any });
|
|
15
|
+
init(clientId: string): this;
|
|
16
|
+
createNode(options: { [key: string]: any }): Node;
|
|
17
|
+
createPlayer(node: Node, options: { [key: string]: any }): Player;
|
|
18
|
+
destroyPlayer(guildId: string): Promise<void>;
|
|
19
|
+
resolve(options: { query: string, source?: string, requester?: any, nodes?: any }): Promise<any>;
|
|
20
|
+
updateVoiceState(data: { d: any, t: string }): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class Connection {
|
|
24
|
+
constructor(player: Player);
|
|
25
|
+
setServerUpdate(data: { endpoint: string, token: string }): void;
|
|
26
|
+
setStateUpdate(data: { channel_id: string, session_id: string, self_deaf: boolean, self_mute: boolean }): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class Filters {
|
|
30
|
+
constructor(player: Player, options?: { [key: string]: any });
|
|
31
|
+
setEqualizer(bands: Array<any>): Promise<void>;
|
|
32
|
+
setKaraoke(enabled: boolean, options?: { [key: string]: any }): Promise<void>;
|
|
33
|
+
clearFilters(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class Node {
|
|
37
|
+
constructor(aqua: Aqua, connOptions: { [key: string]: any }, options?: { [key: string]: any });
|
|
38
|
+
connect(): Promise<void>;
|
|
39
|
+
getStats(): Promise<any>;
|
|
40
|
+
destroy(clean?: boolean): void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class Player {
|
|
44
|
+
constructor(aqua: Aqua, nodes: any, options?: { [key: string]: any });
|
|
45
|
+
play(): Promise<void>;
|
|
46
|
+
pause(paused: boolean): this;
|
|
47
|
+
skip(): Promise<void>;
|
|
48
|
+
destroy(): void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class Plugin {
|
|
52
|
+
constructor(name: string);
|
|
53
|
+
load(aqua: Aqua): void;
|
|
54
|
+
unload(aqua: Aqua): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class Queue extends Array {
|
|
58
|
+
add(track: any): this;
|
|
59
|
+
remove(track: any): void;
|
|
60
|
+
clear(): void;
|
|
61
|
+
shuffle(): void;
|
|
62
|
+
peek(): any;
|
|
63
|
+
toArray(): Array<any>;
|
|
64
|
+
at(index: number): any;
|
|
65
|
+
dequeue(): any;
|
|
66
|
+
isEmpty(): boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export class Rest {
|
|
70
|
+
constructor(aqua: Aqua, options: { [key: string]: any });
|
|
71
|
+
makeRequest(method: string, endpoint: string, body?: any): Promise<any>;
|
|
72
|
+
getPlayers(): Promise<any>;
|
|
73
|
+
destroyPlayer(guildId: string): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class Track {
|
|
77
|
+
constructor(data: { [key: string]: any }, requester: Player, nodes: Node);
|
|
78
|
+
resolve(aqua: Aqua): Promise<Track | null>;
|
|
79
|
+
}
|
|
80
|
+
}
|
package/build/structures/Aqua.js
CHANGED
|
@@ -1,31 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
const
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require("node:events");
|
|
4
|
+
const Node = require("./Node");
|
|
5
|
+
const Player = require("./Player");
|
|
6
|
+
const Track = require("./Track");
|
|
5
7
|
const { version: pkgVersion } = require("../../package.json");
|
|
6
8
|
const URL_REGEX = /^https?:\/\//;
|
|
7
9
|
|
|
8
10
|
class Aqua extends EventEmitter {
|
|
9
11
|
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
*/
|
|
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
|
+
*/
|
|
29
22
|
constructor(client, nodes, options = {}) {
|
|
30
23
|
super();
|
|
31
24
|
this.validateInputs(client, nodes, options);
|
|
@@ -36,22 +29,21 @@ class Aqua extends EventEmitter {
|
|
|
36
29
|
this.clientId = null;
|
|
37
30
|
this.initiated = false;
|
|
38
31
|
this.options = options;
|
|
39
|
-
|
|
40
32
|
this.shouldDeleteMessage = this.getOption(options, 'shouldDeleteMessage', false);
|
|
41
33
|
this.defaultSearchPlatform = this.getOption(options, 'defaultSearchPlatform', 'ytsearch');
|
|
42
|
-
this.leaveOnEnd =
|
|
34
|
+
this.leaveOnEnd = this.getOption(options, 'leaveOnEnd', true);
|
|
43
35
|
this.restVersion = this.getOption(options, 'restVersion', 'v4');
|
|
44
36
|
this.plugins = this.getOption(options, 'plugins', []);
|
|
45
37
|
this.version = pkgVersion;
|
|
46
38
|
this.send = options.send || this.defaultSendFunction;
|
|
47
39
|
this.autoResume = this.getOption(options, 'autoResume', false);
|
|
48
40
|
this.infiniteReconnects = this.getOption(options, 'infiniteReconnects', false);
|
|
49
|
-
|
|
50
41
|
this.setMaxListeners(0);
|
|
42
|
+
this._leastUsedCache = { nodes: [], timestamp: 0 };
|
|
51
43
|
}
|
|
52
44
|
|
|
53
45
|
getOption(options, key, defaultValue) {
|
|
54
|
-
return
|
|
46
|
+
return Object.prototype.hasOwnProperty.call(options, key) ? options[key] : defaultValue;
|
|
55
47
|
}
|
|
56
48
|
|
|
57
49
|
defaultSendFunction(payload) {
|
|
@@ -59,13 +51,23 @@ class Aqua extends EventEmitter {
|
|
|
59
51
|
if (guild) guild.shard.send(payload);
|
|
60
52
|
}
|
|
61
53
|
|
|
62
|
-
validateInputs(client, nodes
|
|
54
|
+
validateInputs(client, nodes) {
|
|
63
55
|
if (!client) throw new Error("Client is required to initialize Aqua");
|
|
64
|
-
if (!Array.isArray(nodes) || !nodes.length)
|
|
56
|
+
if (!Array.isArray(nodes) || !nodes.length) {
|
|
57
|
+
throw new Error(`Nodes must be a non-empty Array (Received ${typeof nodes})`);
|
|
58
|
+
}
|
|
65
59
|
}
|
|
66
60
|
|
|
67
61
|
get leastUsedNodes() {
|
|
68
|
-
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
if (now - this._leastUsedCache.timestamp < 50) return this._leastUsedCache.nodes;
|
|
64
|
+
const nodes = [];
|
|
65
|
+
for (const node of this.nodeMap.values()) {
|
|
66
|
+
if (node.connected) nodes.push(node);
|
|
67
|
+
}
|
|
68
|
+
nodes.sort((a, b) => a.rest.calls - b.rest.calls);
|
|
69
|
+
this._leastUsedCache = { nodes, timestamp: now };
|
|
70
|
+
return nodes;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
init(clientId) {
|
|
@@ -84,45 +86,56 @@ class Aqua extends EventEmitter {
|
|
|
84
86
|
|
|
85
87
|
createNode(options) {
|
|
86
88
|
const nodeId = options.name || options.host;
|
|
87
|
-
this.destroyNode(nodeId);
|
|
89
|
+
this.destroyNode(nodeId);
|
|
88
90
|
const node = new Node(this, options, this.options);
|
|
89
91
|
this.nodeMap.set(nodeId, node);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
this._leastUsedCache.timestamp = 0;
|
|
93
|
+
node.connect().then(() => {
|
|
94
|
+
this.emit("nodeCreate", node);
|
|
95
|
+
}).catch(error => {
|
|
96
|
+
this.nodeMap.delete(nodeId);
|
|
97
|
+
throw error;
|
|
98
|
+
});
|
|
96
99
|
return node;
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
destroyNode(identifier) {
|
|
100
103
|
const node = this.nodeMap.get(identifier);
|
|
101
104
|
if (!node) return;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
})
|
|
109
|
-
.catch(error => console.error(`Error destroying node ${identifier}:`, error));
|
|
105
|
+
node.disconnect().then(() => {
|
|
106
|
+
node.removeAllListeners();
|
|
107
|
+
this.nodeMap.delete(identifier);
|
|
108
|
+
this._leastUsedCache.timestamp = 0;
|
|
109
|
+
this.emit("nodeDestroy", node);
|
|
110
|
+
}).catch(error => console.error(`Error destroying node ${identifier}:`, error));
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
updateVoiceState({ d, t }) {
|
|
113
114
|
const player = this.players.get(d.guild_id);
|
|
114
|
-
if (player
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
if (!player) return;
|
|
116
|
+
|
|
117
|
+
if (t === "VOICE_SERVER_UPDATE" || (t === "VOICE_STATE_UPDATE" && d.user_id === this.clientId)) {
|
|
118
|
+
const updateMethod = t === "VOICE_SERVER_UPDATE" ? "setServerUpdate" : "setStateUpdate";
|
|
119
|
+
if (player.connection && typeof player.connection[updateMethod] === "function") {
|
|
120
|
+
player.connection[updateMethod](d);
|
|
121
|
+
}
|
|
122
|
+
if (d.channel_id === null) {
|
|
123
|
+
this.cleanupPlayer(player);
|
|
124
|
+
}
|
|
117
125
|
}
|
|
118
126
|
}
|
|
119
127
|
|
|
120
128
|
fetchRegion(region) {
|
|
121
129
|
if (!region) return this.leastUsedNodes;
|
|
122
130
|
const lowerRegion = region.toLowerCase();
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
const regionNodes = [];
|
|
132
|
+
for (const node of this.nodeMap.values()) {
|
|
133
|
+
if (node.connected && node.regions?.includes(lowerRegion)) {
|
|
134
|
+
regionNodes.push(node);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
regionNodes.sort((a, b) => this.calculateLoad(a) - this.calculateLoad(b));
|
|
138
|
+
return regionNodes;
|
|
126
139
|
}
|
|
127
140
|
|
|
128
141
|
calculateLoad(node) {
|
|
@@ -133,10 +146,10 @@ class Aqua extends EventEmitter {
|
|
|
133
146
|
|
|
134
147
|
createConnection(options) {
|
|
135
148
|
this.ensureInitialized();
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
const node =
|
|
149
|
+
const existingPlayer = this.players.get(options.guildId);
|
|
150
|
+
if (existingPlayer && existingPlayer.voiceChannel) return existingPlayer;
|
|
151
|
+
const availableNodes = options.region ? this.fetchRegion(options.region) : this.leastUsedNodes;
|
|
152
|
+
const node = availableNodes[0];
|
|
140
153
|
if (!node) throw new Error("No nodes are available");
|
|
141
154
|
return this.createPlayer(node, options);
|
|
142
155
|
}
|
|
@@ -154,9 +167,8 @@ class Aqua extends EventEmitter {
|
|
|
154
167
|
async destroyPlayer(guildId) {
|
|
155
168
|
const player = this.players.get(guildId);
|
|
156
169
|
if (!player) return;
|
|
157
|
-
|
|
158
170
|
try {
|
|
159
|
-
await player.clearData();
|
|
171
|
+
await player.clearData();
|
|
160
172
|
player.removeAllListeners();
|
|
161
173
|
this.players.delete(guildId);
|
|
162
174
|
this.emit("playerDestroy", player);
|
|
@@ -169,6 +181,7 @@ class Aqua extends EventEmitter {
|
|
|
169
181
|
this.ensureInitialized();
|
|
170
182
|
const requestNode = this.getRequestNode(nodes);
|
|
171
183
|
const formattedQuery = this.formatQuery(query, source);
|
|
184
|
+
|
|
172
185
|
try {
|
|
173
186
|
const response = await requestNode.rest.makeRequest("GET", `/v4/loadtracks?identifier=${encodeURIComponent(formattedQuery)}`);
|
|
174
187
|
if (["empty", "NO_MATCHES"].includes(response.loadType)) {
|
|
@@ -176,7 +189,7 @@ class Aqua extends EventEmitter {
|
|
|
176
189
|
}
|
|
177
190
|
return this.constructorResponse(response, requester, requestNode);
|
|
178
191
|
} catch (error) {
|
|
179
|
-
if (error.name ===
|
|
192
|
+
if (error.name === "AbortError") {
|
|
180
193
|
throw new Error("Request timed out");
|
|
181
194
|
}
|
|
182
195
|
throw new Error(`Failed to resolve track: ${error.message}`);
|
|
@@ -187,7 +200,7 @@ class Aqua extends EventEmitter {
|
|
|
187
200
|
if (nodes && !(typeof nodes === "string" || nodes instanceof Node)) {
|
|
188
201
|
throw new TypeError(`'nodes' must be a string or Node instance, received: ${typeof nodes}`);
|
|
189
202
|
}
|
|
190
|
-
return (typeof nodes ===
|
|
203
|
+
return (typeof nodes === "string" ? this.nodeMap.get(nodes) : nodes) ?? this.leastUsedNodes[0];
|
|
191
204
|
}
|
|
192
205
|
|
|
193
206
|
ensureInitialized() {
|
|
@@ -200,9 +213,11 @@ class Aqua extends EventEmitter {
|
|
|
200
213
|
|
|
201
214
|
async handleNoMatches(rest, query) {
|
|
202
215
|
try {
|
|
203
|
-
const
|
|
216
|
+
const ytIdentifier = `/v4/loadtracks?identifier=https://www.youtube.com/watch?v=${query}`;
|
|
217
|
+
const youtubeResponse = await rest.makeRequest("GET", ytIdentifier);
|
|
204
218
|
if (["empty", "NO_MATCHES"].includes(youtubeResponse.loadType)) {
|
|
205
|
-
|
|
219
|
+
const spotifyIdentifier = `/v4/loadtracks?identifier=https://open.spotify.com/track/${query}`;
|
|
220
|
+
return await rest.makeRequest("GET", spotifyIdentifier);
|
|
206
221
|
}
|
|
207
222
|
return youtubeResponse;
|
|
208
223
|
} catch (error) {
|
|
@@ -216,7 +231,7 @@ class Aqua extends EventEmitter {
|
|
|
216
231
|
exception: null,
|
|
217
232
|
playlistInfo: null,
|
|
218
233
|
pluginInfo: response.pluginInfo ?? {},
|
|
219
|
-
tracks: []
|
|
234
|
+
tracks: []
|
|
220
235
|
};
|
|
221
236
|
|
|
222
237
|
if (response.loadType === "error" || response.loadType === "LOAD_FAILED") {
|
|
@@ -224,7 +239,8 @@ class Aqua extends EventEmitter {
|
|
|
224
239
|
return baseResponse;
|
|
225
240
|
}
|
|
226
241
|
|
|
227
|
-
const trackFactory =
|
|
242
|
+
const trackFactory = trackData => new Track(trackData, requester, requestNode);
|
|
243
|
+
|
|
228
244
|
switch (response.loadType) {
|
|
229
245
|
case "track":
|
|
230
246
|
if (response.data) {
|
|
@@ -235,7 +251,7 @@ class Aqua extends EventEmitter {
|
|
|
235
251
|
if (response.data?.info) {
|
|
236
252
|
baseResponse.playlistInfo = {
|
|
237
253
|
name: response.data.info.name ?? response.data.info.title,
|
|
238
|
-
...response.data.info
|
|
254
|
+
...response.data.info
|
|
239
255
|
};
|
|
240
256
|
}
|
|
241
257
|
baseResponse.tracks = (response.data?.tracks ?? []).map(trackFactory);
|
|
@@ -253,46 +269,11 @@ class Aqua extends EventEmitter {
|
|
|
253
269
|
return player;
|
|
254
270
|
}
|
|
255
271
|
|
|
256
|
-
cleanupIdle() {
|
|
257
|
-
const now = Date.now();
|
|
258
|
-
for (const [guildId, player] of this.players) {
|
|
259
|
-
if (!player.playing && !player.paused && player.queue.isEmpty() && (now - player.lastActivity) > this.options.idleTimeout) {
|
|
260
|
-
this.cleanupPlayer(player);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
272
|
cleanupPlayer(player) {
|
|
266
|
-
if (
|
|
267
|
-
try {
|
|
268
|
-
player.clearData();
|
|
269
|
-
player.removeAllListeners();
|
|
270
|
-
player.destroy();
|
|
273
|
+
if (player && this.players.has(player.guildId)) {
|
|
271
274
|
this.players.delete(player.guildId);
|
|
272
|
-
this.emit("playerDestroy", player);
|
|
273
|
-
} catch (error) {
|
|
274
|
-
console.error(`Error during player cleanup: ${error.message}`);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
cleanup() {
|
|
279
|
-
for (const player of this.players.values()) {
|
|
280
|
-
this.cleanupPlayer(player);
|
|
281
|
-
}
|
|
282
|
-
for (const node of this.nodeMap.values()) {
|
|
283
|
-
this.destroyNode(node.name || node.host);
|
|
284
275
|
}
|
|
285
|
-
this.nodeMap.clear();
|
|
286
|
-
this.players.clear();
|
|
287
|
-
this.client = null;
|
|
288
|
-
this.nodes = null;
|
|
289
|
-
this.plugins?.forEach(plugin => plugin.unload?.(this));
|
|
290
|
-
this.plugins = null;
|
|
291
|
-
this.options = null;
|
|
292
|
-
this.send = null;
|
|
293
|
-
this.version = null;
|
|
294
|
-
this.removeAllListeners();
|
|
295
276
|
}
|
|
296
277
|
}
|
|
297
278
|
|
|
298
|
-
module.exports =
|
|
279
|
+
module.exports = Aqua
|