Compare commits

..

No commits in common. "742a46c708bee585e55e315764c337c98620db4e" and "52df7bb4afcfb6dc930e59bb8d91d994f62f6d5d" have entirely different histories.

8 changed files with 9 additions and 201 deletions

View file

@ -161,8 +161,7 @@ class GatewayServer {
username: user.username, username: user.username,
guildAccess: user.guildAccess, guildAccess: user.guildAccess,
discordID: user.discordID, discordID: user.discordID,
avatarURL: user.avatarURL, avatarURL: user.avatarURL
isSuperToken: user.isSuperToken
} }
} }
})); }));

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2022 hippoz Copyright (c) 2021 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", "736292509134749807"]; export const watchedGuildIds = ["822089558886842418"];
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,8 +9,5 @@
"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

@ -13,12 +13,12 @@ router.post("/tokens/create", async (req, res) => {
if (!dangerousAdminMode) if (!dangerousAdminMode)
return res.status(403).send({ error: true, message: "ERROR_FEATURE_DISABLED" }); return res.status(403).send({ error: true, message: "ERROR_FEATURE_DISABLED" });
const { username, avatarURL, discordID, guildAccess, isSuperToken=false } = req.body; const { username, avatarURL, discordID, guildAccess } = req.body;
if (!username || !discordID || !guildAccess) if (!username || !discordID || !guildAccess)
return res.status(400).send({ error: true, message: "ERROR_BAD_REQUEST" }); return res.status(400).send({ error: true, message: "ERROR_BAD_REQUEST" });
try { try {
const token = await createToken({ username, avatarURL, discordID, guildAccess, isSuperToken }); const token = await createToken({ username, avatarURL, discordID, guildAccess });
res.status(200).send({ error: false, message: "SUCCESS_TOKEN_CREATED", token }); res.status(200).send({ error: false, message: "SUCCESS_TOKEN_CREATED", token });
} catch(e) { } catch(e) {
res.status(500).send({ error: true, message: "ERROR_TOKEN_CREATE_FAILURE" }); res.status(500).send({ error: true, message: "ERROR_TOKEN_CREATE_FAILURE" });
@ -30,8 +30,7 @@ router.get("/users/@self", checkAuth(async (req, res) => {
username: req.user.username, username: req.user.username,
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: req.user.isSuperToken
}}); }});
})); }));
@ -49,22 +48,12 @@ router.post("/guilds/:guildId/channels/:channelId/messages/create", checkAuth(as
return res.status(400).send({ error: true, message: "ERROR_NO_CHANNEL_ID" }); return res.status(400).send({ error: true, message: "ERROR_NO_CHANNEL_ID" });
let { username, avatarURL, guildAccess, isSuperToken } = req.user; const { username, avatarURL, guildAccess } = req.user;
if (isSuperToken) {
if (req.body.username)
username = req.body.username;
if (req.body.avatarURL)
avatarURL = req.body.avatarURL;
}
if (guildAccess.indexOf(guildId) === -1) if (guildAccess.indexOf(guildId) === -1)
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 });

View file

@ -1,172 +0,0 @@
// 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.
// Running example - working directory: project root:
// (cd ~/minecraft_server/ ; exec ~/minecraft_server/start.sh) | RCON_PASSWORD="your rcon password" node scripts/minecraft.js
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

@ -1,9 +1,9 @@
import jsonwebtoken from "jsonwebtoken"; import jsonwebtoken from "jsonwebtoken";
import { jwtSecret } from "./config.js"; import { jwtSecret } from "./config.js";
export function createToken({ username, avatarURL, discordID, guildAccess, isSuperToken=false }) { export function createToken({ username, avatarURL, discordID, guildAccess }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
jsonwebtoken.sign({ username, avatarURL, discordID, guildAccess, isSuperToken }, jwtSecret, (err, token) => { jsonwebtoken.sign({ username, avatarURL, discordID, guildAccess }, jwtSecret, (err, token) => {
if (err) if (err)
return reject(err); return reject(err);

View file

@ -388,11 +388,6 @@ 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"