170 lines
5.5 KiB
JavaScript
170 lines
5.5 KiB
JavaScript
|
// This script aims to bridge Minecraft to a guild of your choice by piping
|
||
|
// the Minecraft output into this script. RCON isrequired for messages to
|
||
|
// be sent to Minecraft.
|
||
|
|
||
|
import fetch from "node-fetch";
|
||
|
import { WebSocket } from "ws";
|
||
|
import Rcon from "rcon";
|
||
|
|
||
|
const TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InN0YWdpbmctbWluZWNyYWZ0LWJyaWRnZSIsImF2YXRhclVSTCI6bnVsbCwiZGlzY29yZElEIjoiMCIsImd1aWxkQWNjZXNzIjpbIjczNjI5MjUwOTEzNDc0OTgwNyJdLCJpc1N1cGVyVG9rZW4iOnRydWUsImlhdCI6MTY0NDQ1MzM4MX0.-XIBl6VLnXVwve9iqhWs51ABZkm1i_v1tS6X01SPk3U"; // A supertoken is required to send messages from Minecraft.
|
||
|
const TARGET_GUILD_ID = "_";
|
||
|
const TARGET_CHANNEL_ID = "_";
|
||
|
const ORIGIN = "http://localhost:4050";
|
||
|
const GATEWAY_ORIGIN = "ws://localhost:4050/gateway";
|
||
|
const messageSchema = { t: "number", d: "object" };
|
||
|
const messageTypes = {
|
||
|
HELLO: 0,
|
||
|
YOO: 1,
|
||
|
READY: 2,
|
||
|
EVENT: 3
|
||
|
};
|
||
|
|
||
|
const chatMessageRegex = /^\[(?:.*?)\]: \<(?<username>.*)\> (?<message>.*)/;
|
||
|
const rconConnection = new Rcon("localhost", "25575", process.env.RCON_PASSWORD);
|
||
|
|
||
|
export default class GatewayClient {
|
||
|
constructor(gatewayPath) {
|
||
|
this.gatewayPath = gatewayPath;
|
||
|
this.ws = null;
|
||
|
this.token = null;
|
||
|
this.user = null;
|
||
|
this.onEvent = (e) => {};
|
||
|
}
|
||
|
|
||
|
connect(token) {
|
||
|
if (!token)
|
||
|
token = this.token;
|
||
|
|
||
|
console.log("gateway: connecting");
|
||
|
|
||
|
this.ws = new WebSocket(this.gatewayPath);
|
||
|
|
||
|
this.ws.on("message", (data, isBinary) => {
|
||
|
if (isBinary) {
|
||
|
console.warn("gateway: got binary data from server, ignoring...");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
let message = data.toString();
|
||
|
try {
|
||
|
message = JSON.parse(data);
|
||
|
} catch(e) {
|
||
|
console.warn("gateway: got invalid JSON from server (failed to parse), ignoring...");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!this._checkMessageSchema(message)) {
|
||
|
console.warn("gateway: got invalid JSON from server (does not match schema), ignoring...");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch (message.t) {
|
||
|
case messageTypes.HELLO: {
|
||
|
console.log("gateway: HELLO");
|
||
|
this.ws.send(JSON.stringify({
|
||
|
t: messageTypes.YOO,
|
||
|
d: {
|
||
|
token
|
||
|
}
|
||
|
}));
|
||
|
break;
|
||
|
}
|
||
|
case messageTypes.READY: {
|
||
|
console.log("gateway: READY");
|
||
|
this.user = message.d.user;
|
||
|
break;
|
||
|
}
|
||
|
case messageTypes.EVENT: {
|
||
|
this.onEvent(message.d);
|
||
|
break;
|
||
|
}
|
||
|
default: {
|
||
|
console.warn("gateway: got invalid JSON from server (invalid type), ignoring...");
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
this.ws.on("open", () => {
|
||
|
console.log("gateway: open");
|
||
|
});
|
||
|
this.ws.on("close", () => {
|
||
|
console.log("gateway: closed");
|
||
|
setTimeout(() => {
|
||
|
console.log("gateway: reconnecting");
|
||
|
this.connect(token);
|
||
|
}, 4000);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
_checkMessageSchema(message) {
|
||
|
for (const [key, value] of Object.entries(message)) {
|
||
|
if (!messageSchema[key])
|
||
|
return false;
|
||
|
|
||
|
if (typeof value !== messageSchema[key])
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
async function sendBridgeMessageAs(guildId, channelId, content, username=undefined, avatarURL=undefined) {
|
||
|
return await fetch(`${ORIGIN}/api/v1/guilds/${guildId}/channels/${channelId}/messages/create`, {
|
||
|
method: "POST",
|
||
|
body: JSON.stringify({
|
||
|
content,
|
||
|
username,
|
||
|
avatarURL
|
||
|
}),
|
||
|
headers: {
|
||
|
"content-type": "application/json",
|
||
|
"authorization": TOKEN
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
async function sendMinecraftMessageAs(rcon, username, content) {
|
||
|
rcon.send(`tellraw @a ${JSON.stringify([
|
||
|
{ text: "[" },
|
||
|
{ text: `${username}`, color: "gray" },
|
||
|
{ text: "]" },
|
||
|
{ text: " " },
|
||
|
{ text: content },
|
||
|
])}`);
|
||
|
}
|
||
|
|
||
|
async function main() {
|
||
|
rconConnection.on("error", (e) => {
|
||
|
console.error("rcon: got error", e);
|
||
|
if (!rconConnection.hasAuthed) {
|
||
|
console.log("rcon: reconnecting in 1200ms due to error before hasAuthed");
|
||
|
setTimeout(() => {
|
||
|
rconConnection.connect();
|
||
|
}, 1200);
|
||
|
}
|
||
|
});
|
||
|
const gateway = new GatewayClient(GATEWAY_ORIGIN);
|
||
|
gateway.onEvent = (e) => {
|
||
|
if (e.eventType === "MESSAGE_CREATE" && e.message.channel_id === TARGET_CHANNEL_ID && e.message.guild_id === TARGET_GUILD_ID && !e.message.webhook_id) {
|
||
|
sendMinecraftMessageAs(rconConnection, e.message.author.username, e.message.content);
|
||
|
}
|
||
|
};
|
||
|
rconConnection.connect();
|
||
|
gateway.connect(TOKEN);
|
||
|
|
||
|
process.stdin.resume();
|
||
|
process.stdin.on("data", async (rawDataBuffer) => {
|
||
|
const stringData = rawDataBuffer.toString().trim();
|
||
|
console.log(stringData);
|
||
|
const result = chatMessageRegex.exec(stringData);
|
||
|
if (!result)
|
||
|
return;
|
||
|
const { username, message } = result.groups;
|
||
|
|
||
|
await sendBridgeMessageAs(TARGET_GUILD_ID, TARGET_CHANNEL_ID, message, username, null);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
await main();
|