aqualink 2.0.2 → 2.1.1
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 +38 -12
- package/build/index.d.ts +981 -9
- package/build/structures/Aqua.js +40 -20
- package/build/structures/Connection.js +63 -13
- package/build/structures/Node.js +213 -189
- package/build/structures/Player.js +79 -35
- package/build/structures/Rest.js +63 -19
- package/build/structures/Track.js +13 -6
- package/package.json +1 -1
|
@@ -30,14 +30,19 @@ class Player extends EventEmitter {
|
|
|
30
30
|
this.guildId = options.guildId;
|
|
31
31
|
this.textChannel = options.textChannel;
|
|
32
32
|
this.voiceChannel = options.voiceChannel;
|
|
33
|
+
|
|
33
34
|
this.connection = new Connection(this);
|
|
34
35
|
this.filters = new Filters(this);
|
|
35
|
-
|
|
36
|
+
|
|
37
|
+
const defaultVolume = options.defaultVolume ?? 100;
|
|
38
|
+
this.volume = defaultVolume > 200 ? 200 : (defaultVolume < 0 ? 0 : defaultVolume);
|
|
39
|
+
|
|
36
40
|
this.loop = Player.validModes.has(options.loop) ? options.loop : Player.LOOP_MODES.NONE;
|
|
41
|
+
|
|
37
42
|
this.queue = new Queue();
|
|
38
|
-
this.previousTracks = [];
|
|
39
|
-
this.shouldDeleteMessage = options.shouldDeleteMessage
|
|
40
|
-
this.leaveOnEnd = options.leaveOnEnd
|
|
43
|
+
this.previousTracks = [];
|
|
44
|
+
this.shouldDeleteMessage = !!options.shouldDeleteMessage;
|
|
45
|
+
this.leaveOnEnd = !!options.leaveOnEnd;
|
|
41
46
|
|
|
42
47
|
this.playing = false;
|
|
43
48
|
this.paused = false;
|
|
@@ -47,37 +52,54 @@ class Player extends EventEmitter {
|
|
|
47
52
|
this.ping = 0;
|
|
48
53
|
this.nowPlayingMessage = null;
|
|
49
54
|
|
|
50
|
-
this.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (handler && typeof this[handler] === "function") {
|
|
58
|
-
await this[handler](this, this.current, payload);
|
|
59
|
-
} else {
|
|
60
|
-
this.handleUnknownEvent(payload);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
55
|
+
this._handlePlayerUpdate = this._handlePlayerUpdate.bind(this);
|
|
56
|
+
this._handleEvent = this._handleEvent.bind(this);
|
|
57
|
+
|
|
58
|
+
this.on("playerUpdate", this._handlePlayerUpdate);
|
|
59
|
+
this.on("event", this._handleEvent);
|
|
60
|
+
|
|
61
|
+
this._dataStore = null;
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
get previous() {
|
|
66
65
|
return this.previousTracks[0] || null;
|
|
67
66
|
}
|
|
67
|
+
|
|
68
68
|
get currenttrack() {
|
|
69
69
|
return this.current;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
addToPreviousTrack(track) {
|
|
73
|
-
if (
|
|
73
|
+
if (!track) return;
|
|
74
|
+
|
|
75
|
+
if (this.previousTracks.length >= 50) {
|
|
76
|
+
this.previousTracks.pop();
|
|
77
|
+
}
|
|
74
78
|
this.previousTracks.unshift(track);
|
|
75
79
|
}
|
|
76
80
|
|
|
81
|
+
_handlePlayerUpdate({ state }) {
|
|
82
|
+
if (state) {
|
|
83
|
+
if (state.position !== undefined) this.position = state.position;
|
|
84
|
+
if (state.timestamp !== undefined) this.timestamp = state.timestamp;
|
|
85
|
+
if (state.ping !== undefined) this.ping = state.ping;
|
|
86
|
+
}
|
|
87
|
+
this.aqua.emit("playerUpdate", this, { state });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async _handleEvent(payload) {
|
|
91
|
+
const handlerName = Player.EVENT_HANDLERS[payload.type];
|
|
92
|
+
if (handlerName && typeof this[handlerName] === "function") {
|
|
93
|
+
await this[handlerName](this, this.current, payload);
|
|
94
|
+
} else {
|
|
95
|
+
this.handleUnknownEvent(payload);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
77
99
|
async play() {
|
|
78
100
|
if (!this.connected || !this.queue.length) return;
|
|
101
|
+
|
|
79
102
|
const item = this.queue.shift();
|
|
80
|
-
|
|
81
103
|
this.current = item.track ? item : await item.resolve(this.aqua);
|
|
82
104
|
this.playing = true;
|
|
83
105
|
this.position = 0;
|
|
@@ -88,12 +110,14 @@ class Player extends EventEmitter {
|
|
|
88
110
|
|
|
89
111
|
connect({ guildId, voiceChannel, deaf = true, mute = false }) {
|
|
90
112
|
if (this.connected) throw new Error("Player is already connected.");
|
|
113
|
+
|
|
91
114
|
this.send({
|
|
92
115
|
guild_id: guildId,
|
|
93
116
|
channel_id: voiceChannel,
|
|
94
117
|
self_deaf: deaf,
|
|
95
118
|
self_mute: mute
|
|
96
119
|
});
|
|
120
|
+
|
|
97
121
|
this.connected = true;
|
|
98
122
|
this.aqua.emit("debug", this.guildId, `Player connected to voice channel: ${voiceChannel}.`);
|
|
99
123
|
return this;
|
|
@@ -101,8 +125,11 @@ class Player extends EventEmitter {
|
|
|
101
125
|
|
|
102
126
|
destroy() {
|
|
103
127
|
if (!this.connected) return this;
|
|
128
|
+
|
|
104
129
|
this.disconnect();
|
|
105
|
-
|
|
130
|
+
|
|
131
|
+
this.nowPlayingMessage?.delete().catch(() => {});
|
|
132
|
+
|
|
106
133
|
this.aqua.destroyPlayer(this.guildId);
|
|
107
134
|
this.nodes.rest.destroyPlayer(this.guildId);
|
|
108
135
|
this.clearData();
|
|
@@ -112,6 +139,7 @@ class Player extends EventEmitter {
|
|
|
112
139
|
|
|
113
140
|
pause(paused) {
|
|
114
141
|
if (this.paused === paused) return this;
|
|
142
|
+
|
|
115
143
|
this.paused = paused;
|
|
116
144
|
this.updatePlayer({ paused });
|
|
117
145
|
return this;
|
|
@@ -119,22 +147,25 @@ class Player extends EventEmitter {
|
|
|
119
147
|
|
|
120
148
|
async searchLyrics(query) {
|
|
121
149
|
if (!query) return null;
|
|
122
|
-
return
|
|
150
|
+
return this.nodes.rest.getLyrics({ track: { info: { title: query }, search: true } }) || null;
|
|
123
151
|
}
|
|
124
152
|
|
|
125
153
|
async lyrics() {
|
|
126
154
|
if (!this.playing) return null;
|
|
127
|
-
return
|
|
155
|
+
return this.nodes.rest.getLyrics({ track: { encoded: this.current.track, guild_id: this.guildId } }) || null;
|
|
128
156
|
}
|
|
129
157
|
|
|
130
158
|
seek(position) {
|
|
131
159
|
if (!this.playing) return this;
|
|
132
|
-
|
|
160
|
+
|
|
161
|
+
this.position += position;
|
|
162
|
+
this.updatePlayer({ position: this.position });
|
|
133
163
|
return this;
|
|
134
164
|
}
|
|
135
165
|
|
|
136
166
|
stop() {
|
|
137
167
|
if (!this.playing) return this;
|
|
168
|
+
|
|
138
169
|
this.playing = false;
|
|
139
170
|
this.position = 0;
|
|
140
171
|
this.updatePlayer({ track: { encoded: null } });
|
|
@@ -143,13 +174,15 @@ class Player extends EventEmitter {
|
|
|
143
174
|
|
|
144
175
|
setVolume(volume) {
|
|
145
176
|
if (volume < 0 || volume > 200) throw new Error("Volume must be between 0 and 200.");
|
|
177
|
+
|
|
146
178
|
this.volume = volume;
|
|
147
179
|
this.updatePlayer({ volume });
|
|
148
180
|
return this;
|
|
149
181
|
}
|
|
150
182
|
|
|
151
183
|
setLoop(mode) {
|
|
152
|
-
if (!Player.validModes.
|
|
184
|
+
if (!Player.validModes.has(mode)) throw new Error("Loop mode must be 'none', 'track', or 'queue'.");
|
|
185
|
+
|
|
153
186
|
this.loop = mode;
|
|
154
187
|
this.updatePlayer({ loop: mode });
|
|
155
188
|
return this;
|
|
@@ -163,9 +196,11 @@ class Player extends EventEmitter {
|
|
|
163
196
|
|
|
164
197
|
setVoiceChannel(channel) {
|
|
165
198
|
if (!channel?.length) throw new TypeError("Channel must be a non-empty string.");
|
|
199
|
+
|
|
166
200
|
if (this.connected && channel === this.voiceChannel) {
|
|
167
201
|
throw new ReferenceError(`Player already connected to ${channel}.`);
|
|
168
202
|
}
|
|
203
|
+
|
|
169
204
|
this.voiceChannel = channel;
|
|
170
205
|
this.connect({
|
|
171
206
|
deaf: this.deaf,
|
|
@@ -173,6 +208,7 @@ class Player extends EventEmitter {
|
|
|
173
208
|
voiceChannel: channel,
|
|
174
209
|
mute: this.mute
|
|
175
210
|
});
|
|
211
|
+
|
|
176
212
|
return this;
|
|
177
213
|
}
|
|
178
214
|
|
|
@@ -183,10 +219,12 @@ class Player extends EventEmitter {
|
|
|
183
219
|
this.aqua.emit("debug", this.guildId, "Player disconnected.");
|
|
184
220
|
return this;
|
|
185
221
|
}
|
|
222
|
+
|
|
186
223
|
shuffle() {
|
|
187
|
-
|
|
224
|
+
const { queue } = this;
|
|
225
|
+
for (let i = queue.length - 1; i > 0; i--) {
|
|
188
226
|
const j = Math.floor(Math.random() * (i + 1));
|
|
189
|
-
[
|
|
227
|
+
[queue[i], queue[j]] = [queue[j], queue[i]];
|
|
190
228
|
}
|
|
191
229
|
return this;
|
|
192
230
|
}
|
|
@@ -197,6 +235,7 @@ class Player extends EventEmitter {
|
|
|
197
235
|
|
|
198
236
|
replay() {
|
|
199
237
|
this.seek(-this.position);
|
|
238
|
+
return this;
|
|
200
239
|
}
|
|
201
240
|
|
|
202
241
|
skip() {
|
|
@@ -222,8 +261,9 @@ class Player extends EventEmitter {
|
|
|
222
261
|
} catch {}
|
|
223
262
|
}
|
|
224
263
|
|
|
225
|
-
const reason = payload.reason?.replace("_", "")
|
|
226
|
-
|
|
264
|
+
const reason = payload.reason?.toLowerCase().replace("_", "");
|
|
265
|
+
|
|
266
|
+
if (reason === "loadfailed" || reason === "cleanup") {
|
|
227
267
|
if (!player.queue.length) {
|
|
228
268
|
this.clearData();
|
|
229
269
|
this.aqua.emit("queueEnd", player);
|
|
@@ -263,7 +303,9 @@ class Player extends EventEmitter {
|
|
|
263
303
|
}
|
|
264
304
|
|
|
265
305
|
async socketClosed(player, payload) {
|
|
266
|
-
|
|
306
|
+
const code = payload && payload.code;
|
|
307
|
+
|
|
308
|
+
if (code === 4015 || code === 4009) {
|
|
267
309
|
this.send({
|
|
268
310
|
guild_id: payload.guildId,
|
|
269
311
|
channel_id: this.voiceChannel,
|
|
@@ -271,6 +313,7 @@ class Player extends EventEmitter {
|
|
|
271
313
|
self_deaf: this.deaf,
|
|
272
314
|
});
|
|
273
315
|
}
|
|
316
|
+
|
|
274
317
|
this.aqua.emit("socketClosed", player, payload);
|
|
275
318
|
this.pause(true);
|
|
276
319
|
this.aqua.emit("debug", this.guildId, "Player paused due to socket closure.");
|
|
@@ -280,19 +323,20 @@ class Player extends EventEmitter {
|
|
|
280
323
|
this.aqua.send({ op: 4, d: data });
|
|
281
324
|
}
|
|
282
325
|
|
|
283
|
-
#dataStore = new WeakMap();
|
|
284
|
-
|
|
285
326
|
set(key, value) {
|
|
286
|
-
this
|
|
327
|
+
if (!this._dataStore) {
|
|
328
|
+
this._dataStore = new WeakMap();
|
|
329
|
+
}
|
|
330
|
+
this._dataStore.set(key, value);
|
|
287
331
|
}
|
|
288
332
|
|
|
289
333
|
get(key) {
|
|
290
|
-
return this
|
|
334
|
+
return this._dataStore ? this._dataStore.get(key) : undefined;
|
|
291
335
|
}
|
|
292
336
|
|
|
293
337
|
clearData() {
|
|
294
338
|
if (this.previousTracks) this.previousTracks.length = 0;
|
|
295
|
-
this
|
|
339
|
+
this._dataStore = null;
|
|
296
340
|
return this;
|
|
297
341
|
}
|
|
298
342
|
|
|
@@ -317,4 +361,4 @@ class Player extends EventEmitter {
|
|
|
317
361
|
}
|
|
318
362
|
}
|
|
319
363
|
|
|
320
|
-
module.exports = Player
|
|
364
|
+
module.exports = Player;
|
package/build/structures/Rest.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const https = require("https");
|
|
3
3
|
const http = require("http");
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
http2 = require("http2");
|
|
7
|
-
} catch (e) {
|
|
8
|
-
http2 = null;
|
|
9
|
-
}
|
|
4
|
+
|
|
5
|
+
let http2 = null;
|
|
10
6
|
|
|
11
7
|
class Rest {
|
|
12
8
|
constructor(aqua, { secure, host, port, sessionId, password }) {
|
|
@@ -16,9 +12,31 @@ class Rest {
|
|
|
16
12
|
this.baseUrl = `${secure ? "https" : "http"}://${host}:${port}`;
|
|
17
13
|
this.headers = {
|
|
18
14
|
"Content-Type": "application/json",
|
|
19
|
-
Authorization: password,
|
|
15
|
+
"Authorization": password,
|
|
20
16
|
};
|
|
21
|
-
|
|
17
|
+
|
|
18
|
+
this.secure = secure;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getClient() {
|
|
22
|
+
if (this.client) return this.client;
|
|
23
|
+
|
|
24
|
+
if (this.secure) {
|
|
25
|
+
if (!http2) {
|
|
26
|
+
try {
|
|
27
|
+
http2 = require("http2");
|
|
28
|
+
this.client = http2;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
this.client = https;
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
this.client = http2;
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
this.client = http;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return this.client;
|
|
22
40
|
}
|
|
23
41
|
|
|
24
42
|
setSessionId(sessionId) {
|
|
@@ -32,24 +50,37 @@ class Rest {
|
|
|
32
50
|
};
|
|
33
51
|
|
|
34
52
|
return new Promise((resolve, reject) => {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
const client = this.getClient();
|
|
54
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
55
|
+
|
|
56
|
+
const req = client.request(url, options, (res) => {
|
|
57
|
+
const chunks = [];
|
|
58
|
+
|
|
59
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
40
60
|
res.on("end", () => {
|
|
41
61
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
42
|
-
|
|
62
|
+
if (chunks.length === 0) {
|
|
63
|
+
resolve(null);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const data = Buffer.concat(chunks).toString('utf8');
|
|
69
|
+
resolve(data ? JSON.parse(data) : null);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
reject(new Error(`Failed to parse response: ${error.message}`));
|
|
72
|
+
}
|
|
43
73
|
} else {
|
|
44
74
|
reject(new Error(`Request failed with status ${res.statusCode}: ${res.statusMessage}`));
|
|
45
75
|
}
|
|
46
76
|
});
|
|
47
77
|
});
|
|
48
78
|
|
|
49
|
-
req.on("error", (error) => reject(new Error(`Request failed (${method} ${
|
|
79
|
+
req.on("error", (error) => reject(new Error(`Request failed (${method} ${url}): ${error.message}`)));
|
|
50
80
|
|
|
51
81
|
if (body) {
|
|
52
|
-
|
|
82
|
+
const jsonBody = JSON.stringify(body);
|
|
83
|
+
req.write(jsonBody);
|
|
53
84
|
}
|
|
54
85
|
req.end();
|
|
55
86
|
});
|
|
@@ -64,7 +95,11 @@ class Rest {
|
|
|
64
95
|
throw new Error("Cannot provide both 'encoded' and 'identifier' for track");
|
|
65
96
|
}
|
|
66
97
|
this.validateSessionId();
|
|
67
|
-
return this.makeRequest(
|
|
98
|
+
return this.makeRequest(
|
|
99
|
+
"PATCH",
|
|
100
|
+
`/${this.version}/sessions/${this.sessionId}/players/${guildId}?noReplace=false`,
|
|
101
|
+
data
|
|
102
|
+
);
|
|
68
103
|
}
|
|
69
104
|
|
|
70
105
|
getPlayers() {
|
|
@@ -106,16 +141,25 @@ class Rest {
|
|
|
106
141
|
}
|
|
107
142
|
|
|
108
143
|
async getLyrics({ track }) {
|
|
144
|
+
if (!track) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
109
148
|
if (track.search) {
|
|
110
149
|
try {
|
|
111
|
-
const
|
|
150
|
+
const query = encodeURIComponent(track.info.title);
|
|
151
|
+
const res = await this.makeRequest("GET", `/${this.version}/lyrics/search?query=${query}&source=genius`);
|
|
112
152
|
if (res) return res;
|
|
113
153
|
} catch (error) {
|
|
114
154
|
console.error("Failed to fetch lyrics:", error.message);
|
|
115
155
|
}
|
|
116
156
|
}
|
|
157
|
+
|
|
117
158
|
this.validateSessionId();
|
|
118
|
-
return this.makeRequest(
|
|
159
|
+
return this.makeRequest(
|
|
160
|
+
"GET",
|
|
161
|
+
`/${this.version}/sessions/${this.sessionId}/players/${track.guild_id}/track/lyrics?skipTrackSource=false`
|
|
162
|
+
);
|
|
119
163
|
}
|
|
120
164
|
}
|
|
121
165
|
|
|
@@ -4,17 +4,19 @@ const { getImageUrl } = require("../handlers/fetchImage");
|
|
|
4
4
|
class Track {
|
|
5
5
|
constructor(data = {}, requester, nodes) {
|
|
6
6
|
const { info = {}, encoded = null, playlist = null } = data;
|
|
7
|
+
|
|
7
8
|
this.info = {
|
|
8
9
|
identifier: info.identifier || '',
|
|
9
|
-
isSeekable:
|
|
10
|
+
isSeekable: Boolean(info.isSeekable),
|
|
10
11
|
author: info.author || '',
|
|
11
|
-
length: info.length | 0,
|
|
12
|
-
isStream:
|
|
12
|
+
length: info.length | 0,
|
|
13
|
+
isStream: Boolean(info.isStream),
|
|
13
14
|
title: info.title || '',
|
|
14
15
|
uri: info.uri || '',
|
|
15
16
|
sourceName: info.sourceName || '',
|
|
16
17
|
artworkUrl: info.artworkUrl || ''
|
|
17
18
|
};
|
|
19
|
+
|
|
18
20
|
this.track = encoded;
|
|
19
21
|
this.playlist = playlist;
|
|
20
22
|
this.requester = requester;
|
|
@@ -33,7 +35,9 @@ class Track {
|
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
try {
|
|
36
|
-
const
|
|
38
|
+
const { author, title } = this.info;
|
|
39
|
+
const query = `${author} - ${title}`;
|
|
40
|
+
|
|
37
41
|
const result = await aqua.resolve({
|
|
38
42
|
query,
|
|
39
43
|
source: searchPlatform,
|
|
@@ -42,13 +46,14 @@ class Track {
|
|
|
42
46
|
});
|
|
43
47
|
|
|
44
48
|
if (!result?.tracks?.length) return null;
|
|
49
|
+
|
|
45
50
|
const track = this._findMatchingTrack(result.tracks);
|
|
46
51
|
if (!track) return null;
|
|
47
52
|
|
|
48
|
-
// Update track info if a match is found
|
|
49
53
|
this.info.identifier = track.info.identifier;
|
|
50
54
|
this.track = track.track;
|
|
51
55
|
this.playlist = track.playlist || null;
|
|
56
|
+
|
|
52
57
|
return this;
|
|
53
58
|
} catch (error) {
|
|
54
59
|
console.error("Error resolving track:", error);
|
|
@@ -58,6 +63,7 @@ class Track {
|
|
|
58
63
|
|
|
59
64
|
_findMatchingTrack(tracks) {
|
|
60
65
|
const { author, title, length } = this.info;
|
|
66
|
+
|
|
61
67
|
for (const track of tracks) {
|
|
62
68
|
const tInfo = track.info;
|
|
63
69
|
if (author === tInfo.author && title === tInfo.title) {
|
|
@@ -66,8 +72,9 @@ class Track {
|
|
|
66
72
|
}
|
|
67
73
|
}
|
|
68
74
|
}
|
|
75
|
+
|
|
69
76
|
return null;
|
|
70
77
|
}
|
|
71
78
|
}
|
|
72
79
|
|
|
73
|
-
module.exports = Track;
|
|
80
|
+
module.exports = Track;
|