muthera 1.0.11 → 1.0.13
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/package.json
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
async function getImageUrl(info) {
|
|
2
|
-
if (info.sourceName === "spotify") {
|
|
3
|
-
try {
|
|
4
|
-
const match = info.uri.match(/track\/([a-zA-Z0-9]+)/);
|
|
5
|
-
if (match) {
|
|
6
|
-
const res = await fetch(
|
|
7
|
-
`https://open.spotify.com/oembed?url=${info.uri}`
|
|
8
|
-
);
|
|
9
|
-
const json = await res.json();
|
|
10
|
-
|
|
11
|
-
return json.thumbnail_url;
|
|
12
|
-
}
|
|
13
|
-
} catch (error) {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (info.sourceName === "applemusic") {
|
|
19
|
-
try {
|
|
20
|
-
const res = await fetch(
|
|
21
|
-
`https://itunes.apple.com/lookup?id=${info.identifier}`
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
const response = await res.json();
|
|
25
|
-
|
|
26
|
-
return response.results[0].artworkUrl100;
|
|
27
|
-
} catch (error) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (info.sourceName === "soundcloud") {
|
|
33
|
-
try {
|
|
34
|
-
const res = await fetch(
|
|
35
|
-
`https://soundcloud.com/oembed?format=json&url=${info.uri}`
|
|
36
|
-
);
|
|
37
|
-
const json = await res.json();
|
|
38
|
-
const thumbnailUrl = json.thumbnail_url;
|
|
39
|
-
|
|
40
|
-
return thumbnailUrl;
|
|
41
|
-
} catch (error) {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (info.sourceName === "youtube") {
|
|
47
|
-
try {
|
|
48
|
-
const thumbnailUrl = `https://img.youtube.com/vi/${info.identifier}/maxresdefault.jpg`;
|
|
49
|
-
|
|
50
|
-
return thumbnailUrl;
|
|
51
|
-
} catch {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
module.exports = { getImageUrl };
|
|
1
|
+
async function getImageUrl(info) {
|
|
2
|
+
if (info.sourceName === "spotify") {
|
|
3
|
+
try {
|
|
4
|
+
const match = info.uri.match(/track\/([a-zA-Z0-9]+)/);
|
|
5
|
+
if (match) {
|
|
6
|
+
const res = await fetch(
|
|
7
|
+
`https://open.spotify.com/oembed?url=${info.uri}`
|
|
8
|
+
);
|
|
9
|
+
const json = await res.json();
|
|
10
|
+
|
|
11
|
+
return json.thumbnail_url;
|
|
12
|
+
}
|
|
13
|
+
} catch (error) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (info.sourceName === "applemusic") {
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetch(
|
|
21
|
+
`https://itunes.apple.com/lookup?id=${info.identifier}`
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const response = await res.json();
|
|
25
|
+
|
|
26
|
+
return response.results[0].artworkUrl100;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (info.sourceName === "soundcloud") {
|
|
33
|
+
try {
|
|
34
|
+
const res = await fetch(
|
|
35
|
+
`https://soundcloud.com/oembed?format=json&url=${info.uri}`
|
|
36
|
+
);
|
|
37
|
+
const json = await res.json();
|
|
38
|
+
const thumbnailUrl = json.thumbnail_url;
|
|
39
|
+
|
|
40
|
+
return thumbnailUrl;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (info.sourceName === "youtube") {
|
|
47
|
+
try {
|
|
48
|
+
const thumbnailUrl = `https://img.youtube.com/vi/${info.identifier}/maxresdefault.jpg`;
|
|
49
|
+
|
|
50
|
+
return thumbnailUrl;
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { getImageUrl };
|
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
class Connection {
|
|
2
|
-
/**
|
|
3
|
-
* @param {import ("../index").Player} player
|
|
4
|
-
*/
|
|
5
|
-
constructor(player) {
|
|
6
|
-
this.player = player;
|
|
7
|
-
this.sessionId = null;
|
|
8
|
-
this.voice = {
|
|
9
|
-
event: null,
|
|
10
|
-
endpoint: null,
|
|
11
|
-
sessionId: null,
|
|
12
|
-
channelId: null,
|
|
13
|
-
};
|
|
14
|
-
this.self_deaf = false;
|
|
15
|
-
this.self_mute = false;
|
|
16
|
-
this.region = null;
|
|
17
|
-
this.voiceChannel = player.voiceChannel;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
setServerUpdate(data) {
|
|
21
|
-
const { endpoint, token } = data;
|
|
22
|
-
|
|
23
|
-
if (!endpoint) throw new Error("Invalid session. Please try again.");
|
|
24
|
-
|
|
25
|
-
this.voice.endpoint = endpoint;
|
|
26
|
-
this.voice.token = token;
|
|
27
|
-
this.region = endpoint.split(".").shift()?.replace(/[0-9]/g, "") || null;
|
|
28
|
-
|
|
29
|
-
setTimeout(() => {
|
|
30
|
-
this.player.pause(false)
|
|
31
|
-
}, 5000);
|
|
32
|
-
|
|
33
|
-
this.updatePlayerVoiceData();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
setStateUpdate(data) {
|
|
37
|
-
const { session_id, channel_id, self_deaf, self_mute } = data;
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
this.player.voiceChannel &&
|
|
41
|
-
channel_id &&
|
|
42
|
-
this.player.voiceChannel !== channel_id
|
|
43
|
-
) {
|
|
44
|
-
this.player.muthera.emit("playerMove", this.player.voiceChannel, channel_id);
|
|
45
|
-
this.player.voiceChannel = channel_id;
|
|
46
|
-
this.voiceChannel = channel_id;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
this.self_deaf = self_deaf;
|
|
50
|
-
this.self_mute = self_mute;
|
|
51
|
-
this.voice.sessionId = session_id || null;
|
|
52
|
-
this.voice.channelId = channel_id || null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
updatePlayerVoiceData() {
|
|
56
|
-
this.player.muthera.emit(
|
|
57
|
-
"debug",
|
|
58
|
-
this.player.node.name,
|
|
59
|
-
`Update player data: ${JSON.stringify({ voice: this.voice })}`
|
|
60
|
-
);
|
|
61
|
-
this.player.node.rest.updatePlayer({
|
|
62
|
-
guildId: this.player.guildId,
|
|
63
|
-
data: { voice: this.voice },
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
module.exports = { Connection };
|
|
1
|
+
class Connection {
|
|
2
|
+
/**
|
|
3
|
+
* @param {import ("../index").Player} player
|
|
4
|
+
*/
|
|
5
|
+
constructor(player) {
|
|
6
|
+
this.player = player;
|
|
7
|
+
this.sessionId = null;
|
|
8
|
+
this.voice = {
|
|
9
|
+
event: null,
|
|
10
|
+
endpoint: null,
|
|
11
|
+
sessionId: null,
|
|
12
|
+
channelId: null,
|
|
13
|
+
};
|
|
14
|
+
this.self_deaf = false;
|
|
15
|
+
this.self_mute = false;
|
|
16
|
+
this.region = null;
|
|
17
|
+
this.voiceChannel = player.voiceChannel;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setServerUpdate(data) {
|
|
21
|
+
const { endpoint, token } = data;
|
|
22
|
+
|
|
23
|
+
if (!endpoint) throw new Error("Invalid session. Please try again.");
|
|
24
|
+
|
|
25
|
+
this.voice.endpoint = endpoint;
|
|
26
|
+
this.voice.token = token;
|
|
27
|
+
this.region = endpoint.split(".").shift()?.replace(/[0-9]/g, "") || null;
|
|
28
|
+
|
|
29
|
+
setTimeout(() => {
|
|
30
|
+
this.player.pause(false)
|
|
31
|
+
}, 5000);
|
|
32
|
+
|
|
33
|
+
this.updatePlayerVoiceData();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setStateUpdate(data) {
|
|
37
|
+
const { session_id, channel_id, self_deaf, self_mute } = data;
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
this.player.voiceChannel &&
|
|
41
|
+
channel_id &&
|
|
42
|
+
this.player.voiceChannel !== channel_id
|
|
43
|
+
) {
|
|
44
|
+
this.player.muthera.emit("playerMove", this.player.voiceChannel, channel_id);
|
|
45
|
+
this.player.voiceChannel = channel_id;
|
|
46
|
+
this.voiceChannel = channel_id;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.self_deaf = self_deaf;
|
|
50
|
+
this.self_mute = self_mute;
|
|
51
|
+
this.voice.sessionId = session_id || null;
|
|
52
|
+
this.voice.channelId = channel_id || null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
updatePlayerVoiceData() {
|
|
56
|
+
this.player.muthera.emit(
|
|
57
|
+
"debug",
|
|
58
|
+
this.player.node.name,
|
|
59
|
+
`Update player data: ${JSON.stringify({ voice: this.voice })}`
|
|
60
|
+
);
|
|
61
|
+
this.player.node.rest.updatePlayer({
|
|
62
|
+
guildId: this.player.guildId,
|
|
63
|
+
data: { voice: this.voice },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { Connection };
|
|
@@ -1,228 +1,228 @@
|
|
|
1
|
-
const Websocket = require("ws");
|
|
2
|
-
const { Rest } = require("./mutheraRest");
|
|
3
|
-
|
|
4
|
-
class Node {
|
|
5
|
-
constructor(muthera, node, options) {
|
|
6
|
-
this.muthera = muthera;
|
|
7
|
-
this.name = node.name || node.host;
|
|
8
|
-
this.host = node.host || "localhost";
|
|
9
|
-
this.port = node.port || 2333;
|
|
10
|
-
this.password = node.password || "youshallnotpass";
|
|
11
|
-
this.secure = node.secure || false;
|
|
12
|
-
this.sessionId = node.sessionId || null;
|
|
13
|
-
this.rest = new Rest(muthera, this);
|
|
14
|
-
this.wsUrl = `ws${this.secure ? "s" : ""}://${this.host}:${
|
|
15
|
-
this.port
|
|
16
|
-
}/v4/websocket`;
|
|
17
|
-
this.restUrl = `http${this.secure ? "s" : ""}://${this.host}:${this.port}`;
|
|
18
|
-
this.ws = null;
|
|
19
|
-
this.send = options.send;
|
|
20
|
-
this.regions = node.regions;
|
|
21
|
-
this.stats = {
|
|
22
|
-
players: 0,
|
|
23
|
-
playingPlayers: 0,
|
|
24
|
-
uptime: 0,
|
|
25
|
-
memory: {
|
|
26
|
-
free: 0,
|
|
27
|
-
used: 0,
|
|
28
|
-
allocated: 0,
|
|
29
|
-
reservable: 0,
|
|
30
|
-
},
|
|
31
|
-
cpu: {
|
|
32
|
-
cores: 0,
|
|
33
|
-
systemLoad: 0,
|
|
34
|
-
lavalinkLoad: 0,
|
|
35
|
-
},
|
|
36
|
-
frameStats: {
|
|
37
|
-
sent: 0,
|
|
38
|
-
nulled: 0,
|
|
39
|
-
deficit: 0,
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
this.connected = false;
|
|
43
|
-
this.resumeKey = options.resumeKey || null;
|
|
44
|
-
this.resumeTimeout = options.resumeTimeout || 60;
|
|
45
|
-
this.reconnectTimeout = options.reconnectTimeout || 5000;
|
|
46
|
-
this.reconnectTries = options.reconnectTries || 3;
|
|
47
|
-
this.reconnectAttempt = null;
|
|
48
|
-
this.reconnectAttempted = 1;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
connect() {
|
|
52
|
-
if (this.ws) this.ws.close();
|
|
53
|
-
const headers = {
|
|
54
|
-
Authorization: this.password,
|
|
55
|
-
"User-Id": this.muthera.clientId,
|
|
56
|
-
"Client-Name": `Muthera@${require("../../package.json").version}`,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
if (this.sessionId) headers["Session-Id"] = this.sessionId;
|
|
60
|
-
|
|
61
|
-
this.ws = new Websocket(this.wsUrl, { headers });
|
|
62
|
-
this.ws.on("open", this.open.bind(this));
|
|
63
|
-
this.ws.on("error", this.error.bind(this));
|
|
64
|
-
this.ws.on("message", this.message.bind(this));
|
|
65
|
-
this.ws.on("close", this.close.bind(this));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
open() {
|
|
69
|
-
if (this.reconnectAttempt) clearTimeout(this.reconnectAttempt);
|
|
70
|
-
|
|
71
|
-
this.muthera.emit("nodeConnect", this);
|
|
72
|
-
this.connected = true;
|
|
73
|
-
this.reconnectAttempted = 1; // Reset reconnection attempts on successful connection
|
|
74
|
-
this.muthera.emit(
|
|
75
|
-
"debug",
|
|
76
|
-
this.name,
|
|
77
|
-
`Lavalink connected on ${this.wsUrl}`
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
error(event) {
|
|
82
|
-
if (!event) return;
|
|
83
|
-
this.muthera.emit("nodeError", this, event);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
message(msg) {
|
|
87
|
-
if (Array.isArray(msg)) msg = Buffer.concat(msg);
|
|
88
|
-
else if (msg instanceof ArrayBuffer) msg = Buffer.from(msg);
|
|
89
|
-
|
|
90
|
-
const payload = JSON.parse(msg.toString());
|
|
91
|
-
if (!payload.op) return;
|
|
92
|
-
|
|
93
|
-
this.muthera.emit("mutheraRaw", payload);
|
|
94
|
-
this.muthera.emit(
|
|
95
|
-
"debug",
|
|
96
|
-
this.name,
|
|
97
|
-
`Node Update : ${JSON.stringify(payload)}`
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
if (payload.op === "stats") {
|
|
101
|
-
this.stats = { ...payload };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (payload.op === "ready") {
|
|
105
|
-
if (this.sessionId !== payload.sessionId) {
|
|
106
|
-
this.rest.setSessionId(payload.sessionId);
|
|
107
|
-
this.sessionId = payload.sessionId;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
this.muthera.emit(
|
|
111
|
-
"debug",
|
|
112
|
-
this.name,
|
|
113
|
-
`Payload received ${JSON.stringify(payload)}`
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
if (this.sessionId) {
|
|
117
|
-
this.rest.makeRequest(
|
|
118
|
-
`PATCH`,
|
|
119
|
-
`/${this.rest.version}/sessions/${this.sessionId}`,
|
|
120
|
-
{ resuming: true, timeout: this.resumeTimeout }
|
|
121
|
-
);
|
|
122
|
-
this.muthera.emit("debug", this.name, `Node resuming.`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const player = this.muthera.players.get(payload.guildId);
|
|
127
|
-
if (payload.guildId && player) player.emit(payload.op, payload);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
close(event, reason) {
|
|
131
|
-
this.muthera.emit("nodeDisconnect", this, { event, reason });
|
|
132
|
-
this.muthera.emit(
|
|
133
|
-
"debug",
|
|
134
|
-
`Lavalink connection closed with Error code : ${event || "Unknown code"}`
|
|
135
|
-
);
|
|
136
|
-
this.connected = false;
|
|
137
|
-
this.reconnect();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
reconnect() {
|
|
141
|
-
this.reconnectAttempt = setTimeout(() => {
|
|
142
|
-
if (this.reconnectAttempted > this.reconnectTries) {
|
|
143
|
-
const error = new Error(
|
|
144
|
-
`Unable to connect node: ${this.name} after ${this.reconnectTries} attempts.`
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
this.muthera.emit("nodeError", this, error);
|
|
148
|
-
return this.destroy();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
this.ws?.removeAllListeners();
|
|
152
|
-
this.ws = null;
|
|
153
|
-
this.muthera.emit("nodeReconnect", this);
|
|
154
|
-
this.muthera.emit("debug", this.name, `Reconnection attempt ${this.reconnectAttempted}/${this.reconnectTries}`);
|
|
155
|
-
this.connect();
|
|
156
|
-
this.reconnectAttempted++;
|
|
157
|
-
}, this.reconnectTimeout);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
destroy() {
|
|
161
|
-
if (!this.connected) return;
|
|
162
|
-
|
|
163
|
-
const player = this.muthera.players.filter(
|
|
164
|
-
(player) => player.node === this
|
|
165
|
-
);
|
|
166
|
-
if (player.size) player.forEach((player) => player.destroy());
|
|
167
|
-
|
|
168
|
-
if (this.ws) this.ws.close(1000, "destroy");
|
|
169
|
-
this.ws.removeAllListeners();
|
|
170
|
-
this.ws = null;
|
|
171
|
-
|
|
172
|
-
this.reconnectAttempted = 1;
|
|
173
|
-
clearTimeout(this.reconnectAttempt);
|
|
174
|
-
|
|
175
|
-
this.muthera.emit("nodeDestroy", this);
|
|
176
|
-
this.muthera.destroyPlayer(player.guildId);
|
|
177
|
-
|
|
178
|
-
this.muthera.nodeMap.delete(this.name);
|
|
179
|
-
this.connected = false;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
send(payload) {
|
|
183
|
-
const data = JSON.stringify(payload);
|
|
184
|
-
this.ws.send(data, (error) => {
|
|
185
|
-
if (error) return error;
|
|
186
|
-
return null;
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
disconnect() {
|
|
191
|
-
if (!this.connected) return;
|
|
192
|
-
this.muthera.players.forEach((player) => {
|
|
193
|
-
if (player.node == this) {
|
|
194
|
-
player.move();
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
this.ws.close(1000, "destroy");
|
|
198
|
-
this.ws?.removeAllListeners();
|
|
199
|
-
this.ws = null;
|
|
200
|
-
this.muthera.nodes.delete(this.name);
|
|
201
|
-
this.muthera.emit("nodeDisconnect", this);
|
|
202
|
-
this.connected = false;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
get penalties() {
|
|
206
|
-
let penalties = 0;
|
|
207
|
-
if (!this.connected) return penalties;
|
|
208
|
-
if (this.stats.players) {
|
|
209
|
-
penalties += this.stats.players;
|
|
210
|
-
}
|
|
211
|
-
if (this.stats.cpu && this.stats.cpu.systemLoad) {
|
|
212
|
-
penalties += Math.round(
|
|
213
|
-
Math.pow(1.05, 100 * this.stats.cpu.systemLoad) * 10 - 10
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
if (this.stats.frameStats) {
|
|
217
|
-
if (this.stats.frameStats.deficit) {
|
|
218
|
-
penalties += this.stats.frameStats.deficit;
|
|
219
|
-
}
|
|
220
|
-
if (this.stats.frameStats.nulled) {
|
|
221
|
-
penalties += this.stats.frameStats.nulled * 2;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return penalties;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
module.exports = { Node };
|
|
1
|
+
const Websocket = require("ws");
|
|
2
|
+
const { Rest } = require("./mutheraRest");
|
|
3
|
+
|
|
4
|
+
class Node {
|
|
5
|
+
constructor(muthera, node, options) {
|
|
6
|
+
this.muthera = muthera;
|
|
7
|
+
this.name = node.name || node.host;
|
|
8
|
+
this.host = node.host || "localhost";
|
|
9
|
+
this.port = node.port || 2333;
|
|
10
|
+
this.password = node.password || "youshallnotpass";
|
|
11
|
+
this.secure = node.secure || false;
|
|
12
|
+
this.sessionId = node.sessionId || null;
|
|
13
|
+
this.rest = new Rest(muthera, this);
|
|
14
|
+
this.wsUrl = `ws${this.secure ? "s" : ""}://${this.host}:${
|
|
15
|
+
this.port
|
|
16
|
+
}/v4/websocket`;
|
|
17
|
+
this.restUrl = `http${this.secure ? "s" : ""}://${this.host}:${this.port}`;
|
|
18
|
+
this.ws = null;
|
|
19
|
+
this.send = options.send;
|
|
20
|
+
this.regions = node.regions;
|
|
21
|
+
this.stats = {
|
|
22
|
+
players: 0,
|
|
23
|
+
playingPlayers: 0,
|
|
24
|
+
uptime: 0,
|
|
25
|
+
memory: {
|
|
26
|
+
free: 0,
|
|
27
|
+
used: 0,
|
|
28
|
+
allocated: 0,
|
|
29
|
+
reservable: 0,
|
|
30
|
+
},
|
|
31
|
+
cpu: {
|
|
32
|
+
cores: 0,
|
|
33
|
+
systemLoad: 0,
|
|
34
|
+
lavalinkLoad: 0,
|
|
35
|
+
},
|
|
36
|
+
frameStats: {
|
|
37
|
+
sent: 0,
|
|
38
|
+
nulled: 0,
|
|
39
|
+
deficit: 0,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
this.connected = false;
|
|
43
|
+
this.resumeKey = options.resumeKey || null;
|
|
44
|
+
this.resumeTimeout = options.resumeTimeout || 60;
|
|
45
|
+
this.reconnectTimeout = options.reconnectTimeout || 5000;
|
|
46
|
+
this.reconnectTries = options.reconnectTries || 3;
|
|
47
|
+
this.reconnectAttempt = null;
|
|
48
|
+
this.reconnectAttempted = 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
connect() {
|
|
52
|
+
if (this.ws) this.ws.close();
|
|
53
|
+
const headers = {
|
|
54
|
+
Authorization: this.password,
|
|
55
|
+
"User-Id": this.muthera.clientId,
|
|
56
|
+
"Client-Name": `Muthera@${require("../../package.json").version}`,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (this.sessionId) headers["Session-Id"] = this.sessionId;
|
|
60
|
+
|
|
61
|
+
this.ws = new Websocket(this.wsUrl, { headers });
|
|
62
|
+
this.ws.on("open", this.open.bind(this));
|
|
63
|
+
this.ws.on("error", this.error.bind(this));
|
|
64
|
+
this.ws.on("message", this.message.bind(this));
|
|
65
|
+
this.ws.on("close", this.close.bind(this));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
open() {
|
|
69
|
+
if (this.reconnectAttempt) clearTimeout(this.reconnectAttempt);
|
|
70
|
+
|
|
71
|
+
this.muthera.emit("nodeConnect", this);
|
|
72
|
+
this.connected = true;
|
|
73
|
+
this.reconnectAttempted = 1; // Reset reconnection attempts on successful connection
|
|
74
|
+
this.muthera.emit(
|
|
75
|
+
"debug",
|
|
76
|
+
this.name,
|
|
77
|
+
`Lavalink connected on ${this.wsUrl}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
error(event) {
|
|
82
|
+
if (!event) return;
|
|
83
|
+
this.muthera.emit("nodeError", this, event);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
message(msg) {
|
|
87
|
+
if (Array.isArray(msg)) msg = Buffer.concat(msg);
|
|
88
|
+
else if (msg instanceof ArrayBuffer) msg = Buffer.from(msg);
|
|
89
|
+
|
|
90
|
+
const payload = JSON.parse(msg.toString());
|
|
91
|
+
if (!payload.op) return;
|
|
92
|
+
|
|
93
|
+
this.muthera.emit("mutheraRaw", payload);
|
|
94
|
+
this.muthera.emit(
|
|
95
|
+
"debug",
|
|
96
|
+
this.name,
|
|
97
|
+
`Node Update : ${JSON.stringify(payload)}`
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (payload.op === "stats") {
|
|
101
|
+
this.stats = { ...payload };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (payload.op === "ready") {
|
|
105
|
+
if (this.sessionId !== payload.sessionId) {
|
|
106
|
+
this.rest.setSessionId(payload.sessionId);
|
|
107
|
+
this.sessionId = payload.sessionId;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.muthera.emit(
|
|
111
|
+
"debug",
|
|
112
|
+
this.name,
|
|
113
|
+
`Payload received ${JSON.stringify(payload)}`
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (this.sessionId) {
|
|
117
|
+
this.rest.makeRequest(
|
|
118
|
+
`PATCH`,
|
|
119
|
+
`/${this.rest.version}/sessions/${this.sessionId}`,
|
|
120
|
+
{ resuming: true, timeout: this.resumeTimeout }
|
|
121
|
+
);
|
|
122
|
+
this.muthera.emit("debug", this.name, `Node resuming.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const player = this.muthera.players.get(payload.guildId);
|
|
127
|
+
if (payload.guildId && player) player.emit(payload.op, payload);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
close(event, reason) {
|
|
131
|
+
this.muthera.emit("nodeDisconnect", this, { event, reason });
|
|
132
|
+
this.muthera.emit(
|
|
133
|
+
"debug",
|
|
134
|
+
`Lavalink connection closed with Error code : ${event || "Unknown code"}`
|
|
135
|
+
);
|
|
136
|
+
this.connected = false;
|
|
137
|
+
this.reconnect();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
reconnect() {
|
|
141
|
+
this.reconnectAttempt = setTimeout(() => {
|
|
142
|
+
if (this.reconnectAttempted > this.reconnectTries) {
|
|
143
|
+
const error = new Error(
|
|
144
|
+
`Unable to connect node: ${this.name} after ${this.reconnectTries} attempts.`
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
this.muthera.emit("nodeError", this, error);
|
|
148
|
+
return this.destroy();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this.ws?.removeAllListeners();
|
|
152
|
+
this.ws = null;
|
|
153
|
+
this.muthera.emit("nodeReconnect", this);
|
|
154
|
+
this.muthera.emit("debug", this.name, `Reconnection attempt ${this.reconnectAttempted}/${this.reconnectTries}`);
|
|
155
|
+
this.connect();
|
|
156
|
+
this.reconnectAttempted++;
|
|
157
|
+
}, this.reconnectTimeout);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
destroy() {
|
|
161
|
+
if (!this.connected) return;
|
|
162
|
+
|
|
163
|
+
const player = this.muthera.players.filter(
|
|
164
|
+
(player) => player.node === this
|
|
165
|
+
);
|
|
166
|
+
if (player.size) player.forEach((player) => player.destroy());
|
|
167
|
+
|
|
168
|
+
if (this.ws) this.ws.close(1000, "destroy");
|
|
169
|
+
this.ws.removeAllListeners();
|
|
170
|
+
this.ws = null;
|
|
171
|
+
|
|
172
|
+
this.reconnectAttempted = 1;
|
|
173
|
+
clearTimeout(this.reconnectAttempt);
|
|
174
|
+
|
|
175
|
+
this.muthera.emit("nodeDestroy", this);
|
|
176
|
+
this.muthera.destroyPlayer(player.guildId);
|
|
177
|
+
|
|
178
|
+
this.muthera.nodeMap.delete(this.name);
|
|
179
|
+
this.connected = false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
send(payload) {
|
|
183
|
+
const data = JSON.stringify(payload);
|
|
184
|
+
this.ws.send(data, (error) => {
|
|
185
|
+
if (error) return error;
|
|
186
|
+
return null;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
disconnect() {
|
|
191
|
+
if (!this.connected) return;
|
|
192
|
+
this.muthera.players.forEach((player) => {
|
|
193
|
+
if (player.node == this) {
|
|
194
|
+
player.move();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
this.ws.close(1000, "destroy");
|
|
198
|
+
this.ws?.removeAllListeners();
|
|
199
|
+
this.ws = null;
|
|
200
|
+
this.muthera.nodes.delete(this.name);
|
|
201
|
+
this.muthera.emit("nodeDisconnect", this);
|
|
202
|
+
this.connected = false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
get penalties() {
|
|
206
|
+
let penalties = 0;
|
|
207
|
+
if (!this.connected) return penalties;
|
|
208
|
+
if (this.stats.players) {
|
|
209
|
+
penalties += this.stats.players;
|
|
210
|
+
}
|
|
211
|
+
if (this.stats.cpu && this.stats.cpu.systemLoad) {
|
|
212
|
+
penalties += Math.round(
|
|
213
|
+
Math.pow(1.05, 100 * this.stats.cpu.systemLoad) * 10 - 10
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (this.stats.frameStats) {
|
|
217
|
+
if (this.stats.frameStats.deficit) {
|
|
218
|
+
penalties += this.stats.frameStats.deficit;
|
|
219
|
+
}
|
|
220
|
+
if (this.stats.frameStats.nulled) {
|
|
221
|
+
penalties += this.stats.frameStats.nulled * 2;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return penalties;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = { Node };
|
|
@@ -516,9 +516,7 @@ class Player extends EventEmitter {
|
|
|
516
516
|
}
|
|
517
517
|
|
|
518
518
|
destroy() {
|
|
519
|
-
if (!this.
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
519
|
+
if (!this.muthera.players.has(this.guildId)) return;
|
|
522
520
|
this.connected = false;
|
|
523
521
|
this.send({
|
|
524
522
|
guild_id: this.guildId,
|
|
@@ -527,13 +525,13 @@ class Player extends EventEmitter {
|
|
|
527
525
|
self_deaf: false,
|
|
528
526
|
});
|
|
529
527
|
this.node.rest.destroyPlayer(this.guildId);
|
|
528
|
+
this.muthera.players.delete(this.guildId);
|
|
530
529
|
this.muthera.emit("playerDisconnect", this);
|
|
531
530
|
this.muthera.emit(
|
|
532
531
|
"debug",
|
|
533
532
|
this.guildId,
|
|
534
533
|
"Destroyed the player and clear the queue."
|
|
535
534
|
);
|
|
536
|
-
this.muthera.players.delete(this.guildId);
|
|
537
535
|
}
|
|
538
536
|
|
|
539
537
|
async handleEvent(payload) {
|
|
@@ -574,9 +572,9 @@ class Player extends EventEmitter {
|
|
|
574
572
|
}
|
|
575
573
|
|
|
576
574
|
trackEnd(player, track, payload) {
|
|
577
|
-
// If the track ended due to an error (loadFailed), trackError() already
|
|
575
|
+
// If the track ended due to an error (loadFailed, LOAD_FAILED), trackError() already
|
|
578
576
|
// handled advancing the queue / emitting queueEnd. Skip to avoid double handling.
|
|
579
|
-
if (payload.reason === "loadFailed") return;
|
|
577
|
+
if (payload.reason === "loadFailed" || payload.reason === "LOAD_FAILED" || track.isErrored || payload.reason === "cleanup") return;
|
|
580
578
|
|
|
581
579
|
this.previous = track;
|
|
582
580
|
if (this.loop === "track") {
|
|
@@ -601,6 +599,7 @@ class Player extends EventEmitter {
|
|
|
601
599
|
}
|
|
602
600
|
|
|
603
601
|
async trackError(player, track, payload) {
|
|
602
|
+
track.isErrored = true;
|
|
604
603
|
this.muthera.emit("trackError", player, track, payload);
|
|
605
604
|
this.playing = false;
|
|
606
605
|
this.previous = track;
|
|
@@ -612,6 +611,7 @@ class Player extends EventEmitter {
|
|
|
612
611
|
}
|
|
613
612
|
|
|
614
613
|
async trackStuck(player, track, payload) {
|
|
614
|
+
track.isErrored = true;
|
|
615
615
|
this.muthera.emit("trackStuck", player, track, payload);
|
|
616
616
|
this.playing = false;
|
|
617
617
|
this.previous = track;
|
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
class Rest {
|
|
2
|
-
constructor(muthera, options) {
|
|
3
|
-
this.muthera = muthera;
|
|
4
|
-
this.url = `http${options.secure ? "s" : ""}://${options.host}:${options.port}`;
|
|
5
|
-
this.sessionId = options.sessionId;
|
|
6
|
-
this.password = options.password;
|
|
7
|
-
this.version = options.restVersion;
|
|
8
|
-
this.calls = 0;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
setSessionId(sessionId) {
|
|
12
|
-
this.sessionId = sessionId;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async makeRequest(method, endpoint, body = null) {
|
|
16
|
-
const headers = {
|
|
17
|
-
"Content-Type": "application/json",
|
|
18
|
-
Authorization: this.password,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const requestOptions = {
|
|
22
|
-
method,
|
|
23
|
-
headers,
|
|
24
|
-
body: body ? JSON.stringify(body) : null,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const response = await fetch(this.url + endpoint, requestOptions);
|
|
28
|
-
|
|
29
|
-
this.calls++
|
|
30
|
-
|
|
31
|
-
if (response.statusCode === 204) {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
const data = await response.json();
|
|
37
|
-
return data;
|
|
38
|
-
} catch (e) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async getPlayers() {
|
|
44
|
-
return this.makeRequest("GET", `/v4/sessions/${this.sessionId}/players`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async updatePlayer(options) {
|
|
48
|
-
return this.makeRequest("PATCH", `/v4/sessions/${this.sessionId}/players/${options.guildId}?noReplace=false`, options.data).then((res) => {
|
|
49
|
-
this.muthera.emit("res", options.guildId, res);
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async destroyPlayer(guildId) {
|
|
54
|
-
return this.makeRequest("DELETE", `/v4/sessions/${this.sessionId}/players/${guildId}`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async getTracks(identifier) {
|
|
58
|
-
return this.makeRequest("GET", `/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`).then((res) => {
|
|
59
|
-
this.muthera.emit("res", identifier, res);
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async decodeTrack(track, node) {
|
|
64
|
-
if (!node) node = this.leastUsedNodes[0];
|
|
65
|
-
return this.makeRequest(`GET`, `/v4/decodetrack?encodedTrack=${encodeURIComponent(track)}`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async decodeTracks(tracks) {
|
|
69
|
-
return await this.makeRequest(`POST`, `/v4/decodetracks`, tracks);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async getStats() {
|
|
73
|
-
return this.makeRequest("GET", `/v4/stats`);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async getInfo() {
|
|
77
|
-
return this.makeRequest("GET", `/v4/info`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async getRoutePlannerStatus() {
|
|
81
|
-
return await this.makeRequest(`GET`, `/v4/routeplanner/status`);
|
|
82
|
-
}
|
|
83
|
-
async getRoutePlannerAddress(address) {
|
|
84
|
-
return this.makeRequest(`POST`, `/v4/routeplanner/free/address`, { address });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async parseResponse(req) {
|
|
88
|
-
try {
|
|
89
|
-
this.muthera.emit("mutheraRaw", "Rest", await req.json());
|
|
90
|
-
return await req.json();
|
|
91
|
-
}
|
|
92
|
-
catch (e) {
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
1
|
+
class Rest {
|
|
2
|
+
constructor(muthera, options) {
|
|
3
|
+
this.muthera = muthera;
|
|
4
|
+
this.url = `http${options.secure ? "s" : ""}://${options.host}:${options.port}`;
|
|
5
|
+
this.sessionId = options.sessionId;
|
|
6
|
+
this.password = options.password;
|
|
7
|
+
this.version = options.restVersion;
|
|
8
|
+
this.calls = 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
setSessionId(sessionId) {
|
|
12
|
+
this.sessionId = sessionId;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async makeRequest(method, endpoint, body = null) {
|
|
16
|
+
const headers = {
|
|
17
|
+
"Content-Type": "application/json",
|
|
18
|
+
Authorization: this.password,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const requestOptions = {
|
|
22
|
+
method,
|
|
23
|
+
headers,
|
|
24
|
+
body: body ? JSON.stringify(body) : null,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const response = await fetch(this.url + endpoint, requestOptions);
|
|
28
|
+
|
|
29
|
+
this.calls++
|
|
30
|
+
|
|
31
|
+
if (response.statusCode === 204) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const data = await response.json();
|
|
37
|
+
return data;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async getPlayers() {
|
|
44
|
+
return this.makeRequest("GET", `/v4/sessions/${this.sessionId}/players`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async updatePlayer(options) {
|
|
48
|
+
return this.makeRequest("PATCH", `/v4/sessions/${this.sessionId}/players/${options.guildId}?noReplace=false`, options.data).then((res) => {
|
|
49
|
+
this.muthera.emit("res", options.guildId, res);
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async destroyPlayer(guildId) {
|
|
54
|
+
return this.makeRequest("DELETE", `/v4/sessions/${this.sessionId}/players/${guildId}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getTracks(identifier) {
|
|
58
|
+
return this.makeRequest("GET", `/v4/loadtracks?identifier=${encodeURIComponent(identifier)}`).then((res) => {
|
|
59
|
+
this.muthera.emit("res", identifier, res);
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async decodeTrack(track, node) {
|
|
64
|
+
if (!node) node = this.leastUsedNodes[0];
|
|
65
|
+
return this.makeRequest(`GET`, `/v4/decodetrack?encodedTrack=${encodeURIComponent(track)}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async decodeTracks(tracks) {
|
|
69
|
+
return await this.makeRequest(`POST`, `/v4/decodetracks`, tracks);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async getStats() {
|
|
73
|
+
return this.makeRequest("GET", `/v4/stats`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async getInfo() {
|
|
77
|
+
return this.makeRequest("GET", `/v4/info`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async getRoutePlannerStatus() {
|
|
81
|
+
return await this.makeRequest(`GET`, `/v4/routeplanner/status`);
|
|
82
|
+
}
|
|
83
|
+
async getRoutePlannerAddress(address) {
|
|
84
|
+
return this.makeRequest(`POST`, `/v4/routeplanner/free/address`, { address });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async parseResponse(req) {
|
|
88
|
+
try {
|
|
89
|
+
this.muthera.emit("mutheraRaw", "Rest", await req.json());
|
|
90
|
+
return await req.json();
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
98
|
module.exports = { Rest };
|