aqualink 1.0.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.
@@ -0,0 +1,39 @@
1
+ const undici = require("undici");
2
+
3
+ async function getImageUrl(info) {
4
+ if (!info || !info.sourceName || !info.uri) return null;
5
+
6
+ switch (info.sourceName.toLowerCase()) {
7
+ case "spotify":
8
+ return (await fetchFromUrl(`https://open.spotify.com/oembed?url=${info.uri}`))?.json().then(json => json.thumbnail_url || null);
9
+
10
+ case "soundcloud":
11
+ return (await fetchFromUrl(`https://soundcloud.com/oembed?format=json&url=${info.uri}`))?.json().then(json => json.thumbnail_url || null);
12
+
13
+ case "youtube":
14
+ const urls = [
15
+ `https://img.youtube.com/vi/${info.identifier}/maxresdefault.jpg`,
16
+ `https://img.youtube.com/vi/${info.identifier}/hqdefault.jpg`,
17
+ `https://img.youtube.com/vi/${info.identifier}/mqdefault.jpg`,
18
+ `https://img.youtube.com/vi/${info.identifier}/default.jpg`,
19
+ ];
20
+
21
+ const firstValidUrl = await Promise.any(urls.map(url => fetchFromUrl(url)));
22
+ return firstValidUrl ? firstValidUrl.url : null;
23
+
24
+ default:
25
+ return null;
26
+ }
27
+ }
28
+
29
+ async function fetchFromUrl(url) {
30
+ try {
31
+ const response = await undici.fetch(url, { cache: "force-cache" });
32
+ return response.ok ? response : null;
33
+ } catch (error) {
34
+ console.error(`Error fetching ${url}:`, error);
35
+ return null;
36
+ }
37
+ }
38
+
39
+ module.exports = { getImageUrl };
package/build/index.js ADDED
@@ -0,0 +1,11 @@
1
+ const { Connection } = require("./structures/Connection");
2
+ const { Filters } = require("./structures/Filters");
3
+ const { Node } = require("./structures/Node");
4
+ const { Aqua } = require("./structures/Aqua");
5
+ const { Player } = require("./structures/Player");
6
+ const { Plugin } = require("./structures/Plugins");
7
+ const { Queue } = require("./structures/Queue");
8
+ const { Rest } = require("./structures/Rest");
9
+ const { Track } = require("./structures/Track");
10
+
11
+ module.exports = { Connection, Filters, Node, Aqua, Player, Plugin, Queue, Rest, Track };
@@ -0,0 +1,196 @@
1
+ const { EventEmitter } = require("events");
2
+ const { Node } = require("./Node");
3
+ const { Player } = require("./Player");
4
+ const { Track } = require("./Track");
5
+ const { version: pkgVersion } = require("../../package.json");
6
+
7
+ class Aqua extends EventEmitter {
8
+ constructor(client, nodes, options) {
9
+ super()
10
+ // Input validation
11
+ if (!client) throw new Error("Client is required to initialize Aqua");
12
+ if (!Array.isArray(nodes) || nodes.length === 0) throw new Error(`Nodes must be a non-empty Array (Received ${typeof nodes})`);
13
+ if (typeof options.send !== "function") throw new Error("Send function is required to initialize Aqua");
14
+
15
+ this.client = client;
16
+ this.nodes = nodes;
17
+ this.nodeMap = new Map();
18
+ this.players = new Map();
19
+ this.clientId = null;
20
+ this.initiated = false;
21
+ this.sessionId = null;
22
+ this.defaultSearchPlatform = options.defaultSearchPlatform || "ytmsearch";
23
+ this.restVersion = options.restVersion || "v3";
24
+ this.plugins = options.plugins || [];
25
+ this.version = pkgVersion;
26
+ this.loadType = null;
27
+ this.tracks = [];
28
+ this.playlistInfo = null;
29
+ this.pluginInfo = null;
30
+ this.options = options;
31
+ this.send = options.send || null;
32
+ }
33
+ get leastUsedNodes() {
34
+ return [...this.nodeMap.values()]
35
+ .filter(node => node.connected)
36
+ .sort((a, b) => a.rest.calls - b.rest.calls);
37
+ }
38
+
39
+ init(clientId) {
40
+ if (this.initiated) return this;
41
+
42
+ this.clientId = clientId;
43
+ this.nodes.forEach(nodeConfig => this.createNode(nodeConfig));
44
+ this.initiated = true;
45
+
46
+ this.plugins.forEach(plugin => plugin.load(this));
47
+ }
48
+
49
+ createNode(options) {
50
+ const node = new Node(this, options, this.options);
51
+ this.nodeMap.set(options.name || options.host, node);
52
+ node.connect();
53
+ this.emit("nodeCreate", node);
54
+ return node;
55
+ }
56
+
57
+ destroyNode(identifier) {
58
+ const node = this.nodeMap.get(identifier);
59
+ if (!node) return;
60
+
61
+ node.disconnect();
62
+ this.nodeMap.delete(identifier);
63
+ this.emit("nodeDestroy", node);
64
+ }
65
+
66
+ updateVoiceState(packet) {
67
+ if (!["VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE"].includes(packet.t)) return;
68
+
69
+ const player = this.players.get(packet.d.guild_id);
70
+ if (!player) return;
71
+
72
+ if (packet.t === "VOICE_SERVER_UPDATE") {
73
+ player.connection.setServerUpdate(packet.d);
74
+ } else if (packet.t === "VOICE_STATE_UPDATE") {
75
+ if (packet.d.user_id !== this.clientId) return;
76
+ player.connection.setStateUpdate(packet.d);
77
+ }
78
+ }
79
+
80
+ fetchRegion(region) {
81
+ const nodesByRegion = [...this.nodeMap.values()]
82
+ .filter(node => node.connected && node.regions?.includes(region?.toLowerCase()))
83
+ .sort((a, b) => {
84
+ const aLoad = a.stats.cpu ? (a.stats.cpu.systemLoad / a.stats.cpu.cores) * 100 : 0;
85
+ const bLoad = b.stats.cpu ? (b.stats.cpu.systemLoad / b.stats.cpu.cores) * 100 : 0;
86
+ return aLoad - bLoad;
87
+ });
88
+
89
+ return nodesByRegion;
90
+ }
91
+
92
+ createConnection(options) {
93
+ if (!this.initiated) throw new Error("BRO! Get aqua on before !!!");
94
+ if (this.leastUsedNodes.length === 0) throw new Error("No nodes are available");
95
+ const node = options.region
96
+ ? this.fetchRegion(options.region)[0] || this.leastUsedNodes[0]
97
+ : this.leastUsedNodes[0];
98
+ if (!node) throw new Error("No nodes are available");
99
+ return this.createPlayer(node, options);
100
+ }
101
+
102
+ createPlayer(node, options) {
103
+ const player = new Player(this, node, options);
104
+ this.players.set(options.guildId, player);
105
+ player.connect(options);
106
+
107
+ this.emit("playerCreate", player);
108
+ return player;
109
+ }
110
+
111
+ destroyPlayer(guildId) {
112
+ const player = this.players.get(guildId);
113
+ if (!player) return;
114
+
115
+ player.destroy();
116
+ this.players.delete(guildId);
117
+ this.emit("playerDestroy", player);
118
+ }
119
+
120
+ removeConnection(guildId) {
121
+ const player = this.players.get(guildId);
122
+ if (player) {
123
+ player.destroy();
124
+ this.players.delete(guildId);
125
+ }
126
+ }
127
+
128
+ async resolve({ query, source, requester, nodes }) {
129
+ if (!this.initiated) throw new Error("Aqua must be initialized before resolving");
130
+
131
+ if (nodes && (typeof nodes !== "string" && !(nodes instanceof Node))) {
132
+ throw new Error(`'nodes' must be a string or Node instance, but received: ${typeof nodes}`);
133
+ }
134
+
135
+ const searchSources = source || this.defaultSearchPlatform;
136
+ const requestNode = (typeof nodes === 'string' ? this.nodeMap.get(nodes) : nodes) || this.leastUsedNodes[0];
137
+
138
+ if (!requestNode) throw new Error("No nodes are available.");
139
+
140
+ const formattedQuery = /^https?:\/\//.test(query) ? query : `${searchSources}:${query}`;
141
+ let response = await requestNode.rest.makeRequest("GET", `/v4/loadtracks?identifier=${encodeURIComponent(formattedQuery)}`);
142
+
143
+ // Fallback attempts if response loadType indicates no matches
144
+ if (["empty", "NO_MATCHES"].includes(response.loadType)) {
145
+ response = await this.handleNoMatches(requestNode.rest, query);
146
+ }
147
+
148
+ this.loadTracks(response, requester, requestNode);
149
+ return this.constructResponse();
150
+ }
151
+
152
+ async handleNoMatches(rest, query) {
153
+ let response = await rest.makeRequest("GET", `/v4/loadtracks?identifier=https://open.spotify.com/track/${query}`);
154
+ if (["empty", "NO_MATCHES"].includes(response.loadType)) {
155
+ response = await rest.makeRequest("GET", `/v4/loadtracks?identifier=https://www.youtube.com/watch?v=${query}`);
156
+ }
157
+ return response;
158
+ }
159
+
160
+ loadTracks(response, requester, requestNode) {
161
+ this.tracks = [];
162
+ if (response.loadType === "track") {
163
+ if (response.data) {
164
+ this.tracks.push(new Track(response.data, requester, requestNode));
165
+ }
166
+ } else if (response.loadType === "playlist") {
167
+ this.tracks = response.data?.tracks.map(track => new Track(track, requester, requestNode)) || [];
168
+ this.playlistInfo = response.data?.info || null;
169
+ } else if (response.loadType === "search") {
170
+ this.tracks = response.data.map(track => new Track(track, requester, requestNode));
171
+ }
172
+
173
+ this.loadType = response.loadType;
174
+ this.pluginInfo = response.pluginInfo || {};
175
+ }
176
+
177
+ constructResponse() {
178
+ return {
179
+ loadType: this.loadType,
180
+ exception: this.loadType === "error" ? this.loadType.data : (this.loadType === "LOAD_FAILED" ? this.loadType.exception : null),
181
+ playlistInfo: this.playlistInfo,
182
+ pluginInfo: this.pluginInfo,
183
+ tracks: this.tracks.length ? [this.tracks.shift()] : [],
184
+ };
185
+ }
186
+
187
+ get(guildId) {
188
+ const player = this.players.get(guildId);
189
+ if (!player) throw new Error(`Player not found for guild ID: ${guildId}`);
190
+ return player;
191
+ }
192
+ }
193
+
194
+ module.exports = { Aqua };
195
+
196
+
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Class representing a player connection.
3
+ * @param {Player} player The player instance of this connection.
4
+ */
5
+ class Connection {
6
+ constructor(player) {
7
+ this.player = player;
8
+ this.voice = { sessionId: null, endpoint: null, token: null };
9
+ this.region = null;
10
+ this.selfDeaf = false;
11
+ this.selfMute = false;
12
+ this.voiceChannel = player.voiceChannel;
13
+ }
14
+
15
+ /**
16
+ * Sets the server update data (endpoint and token) for the player.
17
+ * @param {object} data The server update data from the VOICE_SERVER_UPDATE packet.
18
+ * @param {string} data.endpoint The endpoint URL of the voice server.
19
+ * @param {string} data.token The token for the voice server.
20
+ */
21
+ setServerUpdate({ endpoint, token }) {
22
+ if (!endpoint) {
23
+ throw new Error("Missing 'endpoint' property in VOICE_SERVER_UPDATE packet/payload. Please wait or disconnect the bot from the voice channel and try again.");
24
+ }
25
+
26
+ const previousVoiceRegion = this.region;
27
+ this.voice.endpoint = endpoint;
28
+ this.voice.token = token;
29
+ this.region = endpoint.split(".")[0].replace(/[0-9]/g, "");
30
+ this.player.aqua.emit("debug", `[Player ${this.player.guildId} - CONNECTION] ${previousVoiceRegion ? `Changed Voice Region from ${previousVoiceRegion} to ${this.region}` : `Voice Server: ${this.region}`}`);
31
+
32
+ if (this.player.paused) {
33
+ this.player.pause(false);
34
+ }
35
+
36
+ this.updatePlayerVoiceData();
37
+ }
38
+
39
+ /**
40
+ * Sets the state update data (session_id, channel_id, self_deaf, and self_mute) for the player.
41
+ * @param {object} data The state update data from the VOICE_STATE_UPDATE packet.
42
+ * @param {string} data.session_id The session id of the voice server.
43
+ * @param {string} data.channel_id The voice channel id of the player.
44
+ * @param {boolean} data.self_deaf The self-deafened status of the player.
45
+ * @param {boolean} data.self_mute The self-muted status of the player.
46
+ */
47
+ setStateUpdate({ session_id, channel_id, self_deaf, self_mute }) {
48
+ if (channel_id == null) {
49
+ this.player.destroy();
50
+ this.player.aqua.emit("playerDestroy", this.player);
51
+ return;
52
+ }
53
+
54
+ if (this.player.voiceChannel !== channel_id) {
55
+ this.player.aqua.emit("playerMove", this.player.voiceChannel, channel_id);
56
+ this.player.voiceChannel = channel_id;
57
+ this.voiceChannel = channel_id;
58
+ }
59
+
60
+ this.selfDeaf = self_deaf;
61
+ this.selfMute = self_mute;
62
+ this.voice.sessionId = session_id || null;
63
+
64
+ this.updatePlayerVoiceData();
65
+ }
66
+
67
+ /**
68
+ * Updates the player voice data.
69
+ */
70
+ updatePlayerVoiceData() {
71
+ this.player.nodes.rest.updatePlayer({
72
+ guildId: this.player.guildId,
73
+ data: {
74
+ voice: this.voice,
75
+ volume: this.player.volume
76
+ }
77
+ });
78
+ }
79
+ }
80
+
81
+ module.exports = { Connection };
82
+
@@ -0,0 +1,169 @@
1
+ class Filters {
2
+ constructor(player, options = {}) {
3
+ this.player = player;
4
+ this.volume = options.volume ?? 1;
5
+ this.equalizer = options.equalizer ?? [];
6
+ this.karaoke = options.karaoke ?? null;
7
+ this.timescale = options.timescale ?? null;
8
+ this.tremolo = options.tremolo ?? null;
9
+ this.vibrato = options.vibrato ?? null;
10
+ this.rotation = options.rotation ?? null;
11
+ this.distortion = options.distortion ?? null;
12
+ this.channelMix = options.channelMix ?? null;
13
+ this.lowPass = options.lowPass ?? null;
14
+ this.bassboost = options.bassboost ?? null;
15
+ this.slowmode = options.slowmode ?? null;
16
+ this.nightcore = options.nightcore ?? null;
17
+ this.vaporwave = options.vaporwave ?? null;
18
+ this._8d = options._8d ?? null;
19
+ }
20
+
21
+ setEqualizer(bands) {
22
+ this.equalizer = bands;
23
+ return this.updateFilters();
24
+ }
25
+
26
+ setKaraoke(enabled, options = {}) {
27
+ this.karaoke = enabled ? {
28
+ level: options.level ?? 1.0,
29
+ monoLevel: options.monoLevel ?? 1.0,
30
+ filterBand: options.filterBand ?? 220.0,
31
+ filterWidth: options.filterWidth ?? 100.0
32
+ } : null;
33
+ return this.updateFilters();
34
+ }
35
+
36
+ setTimescale(enabled, options = {}) {
37
+ this.timescale = enabled ? {
38
+ speed: options.speed ?? 1.0,
39
+ pitch: options.pitch ?? 1.0,
40
+ rate: options.rate ?? 1.0
41
+ } : null;
42
+ return this.updateFilters();
43
+ }
44
+
45
+ setTremolo(enabled, options = {}) {
46
+ this.tremolo = enabled ? {
47
+ frequency: options.frequency ?? 2.0,
48
+ depth: options.depth ?? 0.5
49
+ } : null;
50
+ return this.updateFilters();
51
+ }
52
+
53
+ setVibrato(enabled, options = {}) {
54
+ this.vibrato = enabled ? {
55
+ frequency: options.frequency ?? 2.0,
56
+ depth: options.depth ?? 0.5
57
+ } : null;
58
+ return this.updateFilters();
59
+ }
60
+
61
+ setRotation(enabled, options = {}) {
62
+ this.rotation = enabled ? {
63
+ rotationHz: options.rotationHz ?? 0.0
64
+ } : null;
65
+ return this.updateFilters();
66
+ }
67
+
68
+ setDistortion(enabled, options = {}) {
69
+ this.distortion = enabled ? {
70
+ sinOffset: options.sinOffset ?? 0.0,
71
+ sinScale: options.sinScale ?? 1.0,
72
+ cosOffset: options.cosOffset ?? 0.0,
73
+ cosScale: options.cosScale ?? 1.0,
74
+ tanOffset: options.tanOffset ?? 0.0,
75
+ tanScale: options.tanScale ?? 1.0,
76
+ offset: options.offset ?? 0.0,
77
+ scale: options.scale ?? 1.0
78
+ } : null;
79
+ return this.updateFilters();
80
+ }
81
+
82
+ setChannelMix(enabled, options = {}) {
83
+ this.channelMix = enabled ? {
84
+ leftToLeft: options.leftToLeft ?? 1.0,
85
+ leftToRight: options.leftToRight ?? 0.0,
86
+ rightToLeft: options.rightToLeft ?? 0.0,
87
+ rightToRight: options.rightToRight ?? 1.0
88
+ } : null;
89
+ return this.updateFilters();
90
+ }
91
+
92
+ setLowPass(enabled, options = {}) {
93
+ this.lowPass = enabled ? {
94
+ smoothing: options.smoothing ?? 20.0
95
+ } : null;
96
+ return this.updateFilters();
97
+ }
98
+
99
+ setBassboost(enabled, options = {}) {
100
+ if (enabled) {
101
+ const value = options.value ?? 5;
102
+ if (value < 0 || value > 5) throw new Error("Bassboost value must be between 0 and 5");
103
+ this.bassboost = value;
104
+ const num = (value - 1) * (1.25 / 9) - 0.25;
105
+ return this.setEqualizer(Array(13).fill(0).map((_, i) => ({
106
+ band: i,
107
+ gain: num
108
+ })));
109
+ }
110
+ this.bassboost = null;
111
+ return this.setEqualizer([]);
112
+ }
113
+
114
+ setSlowmode(enabled, options = {}) {
115
+ this.slowmode = enabled;
116
+ return this.setTimescale(enabled, { rate: enabled ? options.rate ?? 0.8 : 1.0 });
117
+ }
118
+
119
+ setNightcore(enabled, options = {}) {
120
+ this.nightcore = enabled;
121
+ if (enabled) {
122
+ return this.setTimescale(true, { rate: options.rate ?? 1.5 });
123
+ }
124
+ return this.setTimescale(false);
125
+ }
126
+
127
+ setVaporwave(enabled, options = {}) {
128
+ this.vaporwave = enabled;
129
+ if (enabled) {
130
+ return this.setTimescale(true, { pitch: options.pitch ?? 0.5 });
131
+ }
132
+ return this.setTimescale(false);
133
+ }
134
+
135
+ set8D(enabled, options = {}) {
136
+ this._8d = enabled;
137
+ return this.setRotation(enabled, { rotationHz: enabled ? options.rotationHz ?? 0.2 : 0.0 });
138
+ }
139
+
140
+ async clearFilters() {
141
+ Object.assign(this, new Filters(this.player));
142
+ await this.updateFilters();
143
+ return this;
144
+ }
145
+
146
+ async updateFilters() {
147
+ const filterData = {
148
+ volume: this.volume,
149
+ equalizer: this.equalizer,
150
+ karaoke: this.karaoke,
151
+ timescale: this.timescale,
152
+ tremolo: this.tremolo,
153
+ vibrato: this.vibrato,
154
+ rotation: this.rotation,
155
+ distortion: this.distortion,
156
+ channelMix: this.channelMix,
157
+ lowPass: this.lowPass
158
+ };
159
+
160
+ await this.player.nodes.rest.updatePlayer({
161
+ guildId: this.player.guildId,
162
+ data: { filters: filterData }
163
+ });
164
+
165
+ return this;
166
+ }
167
+ }
168
+
169
+ module.exports = {Filters}