fix super tokens and add minecraft server bridge

This commit is contained in:
hippoz 2022-02-11 15:20:59 +02:00
parent 5ad5d0ceba
commit 2ab4277696
No known key found for this signature in database
GPG key ID: 7C52899193467641
7 changed files with 184 additions and 4 deletions

View file

@ -1,4 +1,3 @@
import { use } from "express/lib/application";
import { WebSocketServer } from "ws"; import { WebSocketServer } from "ws";
import { guildMap } from "./common.js"; import { guildMap } from "./common.js";
import { decodeToken } from "./tokens.js"; import { decodeToken } from "./tokens.js";

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2021 hippoz Copyright (c) 2022 hippoz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View file

@ -1,5 +1,5 @@
export const mainHttpListenPort = 4050; export const mainHttpListenPort = 4050;
export const watchedGuildIds = ["822089558886842418"]; export const watchedGuildIds = ["822089558886842418", "736292509134749807"];
export const jwtSecret = process.env.JWT_SECRET; export const jwtSecret = process.env.JWT_SECRET;
export const discordToken = process.env.DISCORD_TOKEN; export const discordToken = process.env.DISCORD_TOKEN;
export const dangerousAdminMode = true; export const dangerousAdminMode = true;

View file

@ -9,5 +9,8 @@
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"node-fetch": "^3.2.0", "node-fetch": "^3.2.0",
"ws": "^8.4.2" "ws": "^8.4.2"
},
"optionalDependencies": {
"rcon": "^1.1.0"
} }
} }

View file

@ -31,7 +31,7 @@ router.get("/users/@self", checkAuth(async (req, res) => {
avatarURL: req.user.avatarURL, avatarURL: req.user.avatarURL,
discordID: req.user.discordID, discordID: req.user.discordID,
guildAccess: req.user.guildAccess, guildAccess: req.user.guildAccess,
isSuperToken: isSuperToken isSuperToken: req.user.isSuperToken
}}); }});
})); }));
@ -61,6 +61,10 @@ router.post("/guilds/:guildId/channels/:channelId/messages/create", checkAuth(as
return res.status(403).send({ error: true, message: "ERROR_NO_GUILD_ACCESS" }); return res.status(403).send({ error: true, message: "ERROR_NO_GUILD_ACCESS" });
const guild = guildMap.get(guildId); const guild = guildMap.get(guildId);
if (!guild)
return res.status(404).send({ error: true, message: "ERROR_GUILD_NOT_FOUND" });
try { try {
await guild.discordSendMessage(messageContent, channelId, username, avatarURL); await guild.discordSendMessage(messageContent, channelId, username, avatarURL);
res.status(201).send({ error: false }); res.status(201).send({ error: false });

169
scripts/minecraft.js Normal file
View file

@ -0,0 +1,169 @@
// 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();

View file

@ -388,6 +388,11 @@ raw-body@2.4.2:
iconv-lite "0.4.24" iconv-lite "0.4.24"
unpipe "1.0.0" unpipe "1.0.0"
rcon@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/rcon/-/rcon-1.1.0.tgz#82a27bbfadd4c13b3c5d828b55ce15bd606eb7c3"
integrity sha512-eotwcApOBjfadTjqQlrZVR4jzlwGCMNxmHhnFZx+g4kouwwRstRHkk1ON7DzkqrHNIjADSh0cU3gThSsDolUpg==
safe-buffer@5.2.1, safe-buffer@^5.0.1: safe-buffer@5.2.1, safe-buffer@^5.0.1:
version "5.2.1" version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"