forked from MrGeorgen/2smart2wait
Initial commit
This commit is contained in:
411
src/main.js
Normal file
411
src/main.js
Normal file
@ -0,0 +1,411 @@
|
||||
// imports
|
||||
const jsonminify = require("./json.minify.js"); // to remove comments from the config.json, because normally comments in json are not allowed
|
||||
const fs = require('fs');
|
||||
const mc = require('minecraft-protocol'); // to handle minecraft login session
|
||||
const webserver = require('./webserver.js'); // to serve the webserver
|
||||
const opn = require('opn'); //to open a browser window
|
||||
const secrets = require('../secrets.json'); // read the creds
|
||||
const config = JSON.parse(jsonminify(fs.readFileSync("../config.json", "utf8"))); // read the config
|
||||
const discord = require('discord.js');
|
||||
const {DateTime} = require("luxon");
|
||||
const https = require("https");
|
||||
const prompt = require("prompt");
|
||||
const ping = require('minecraft-server-util');
|
||||
const sleep = require("sleep-ms")
|
||||
var timedStart;
|
||||
var lastQueuePlace;
|
||||
var queueData;
|
||||
var secRun;
|
||||
var chunkData = [];
|
||||
var c = 0;
|
||||
webserver.restartQueue = config.reconnect.notConnectedQueueEnd;
|
||||
prioQueueLength();
|
||||
if (config.webserver) {
|
||||
webserver.createServer(config.ports.web); // create the webserver
|
||||
webserver.password = config.password
|
||||
}
|
||||
webserver.onstart(() => { // set up actions for the webserver
|
||||
startQueuing();
|
||||
});
|
||||
webserver.onstop(() => {
|
||||
stop();
|
||||
});
|
||||
|
||||
if (config.openBrowserOnStart && config.webserver) {
|
||||
opn('http://localhost:' + config.ports.web); //open a browser window
|
||||
}
|
||||
// lets
|
||||
var proxyClient; // a reference to the client that is the actual minecraft game
|
||||
let client; // the client to connect to 2b2t
|
||||
let server; // the minecraft server to pass packets
|
||||
|
||||
//comand prompt
|
||||
prompt.start();
|
||||
|
||||
function cmdInput() {
|
||||
prompt.get("cmd", function (err, result) {
|
||||
userInput(result.cmd, false);
|
||||
cmdInput()
|
||||
});
|
||||
}
|
||||
|
||||
// function to disconnect from the server
|
||||
function stop() {
|
||||
webserver.isInQueue = false;
|
||||
webserver.queuePlace = "None";
|
||||
webserver.ETA = "None";
|
||||
client.end(); // disconnect
|
||||
if (proxyClient) {
|
||||
proxyClient.end("Stopped the proxy."); // boot the player from the server
|
||||
}
|
||||
server.close(); // close the server
|
||||
dc.user.setActivity("Queue is stopped.");
|
||||
}
|
||||
|
||||
// function to start the whole thing
|
||||
function startQueuing() {
|
||||
var playerid;
|
||||
let startTime = DateTime.local();
|
||||
webserver.isInQueue = true;
|
||||
dc.user.setActivity("Starting the queue...");
|
||||
client = mc.createClient({ // connect to 2b2t
|
||||
host: config.minecraftserver.hostname,
|
||||
port: config.minecraftserver.port,
|
||||
username: secrets.username,
|
||||
password: secrets.password,
|
||||
version: config.minecraftserver.version
|
||||
});
|
||||
let finishedQueue = false;
|
||||
client.on("packet", (data, meta) => { // each time 2b2t sends a packet
|
||||
if (meta.name === "map_chunk") {
|
||||
chunkData.push(data);
|
||||
}
|
||||
if (!finishedQueue && meta.name === "playerlist_header" && config.minecraftserver.hostname === "2b2t.org") { // if the packet contains the player list, we can use it to see our place in the queue
|
||||
let headermessage = JSON.parse(data.header);
|
||||
let positioninqueue = headermessage.text.split("\n")[5].substring(25);
|
||||
let ETA = headermessage.text.split("\n")[6].substring(27);
|
||||
webserver.queuePlace = positioninqueue; // update info on the web page
|
||||
webserver.ETA = ETA;
|
||||
server.motd = `Place in queue: ${positioninqueue}`; // set the MOTD because why not
|
||||
if (webserver.queuePlace != "None") dc.user.setActivity("Pos: " + webserver.queuePlace + " ETA: " + webserver.ETA); //set the Discord Activity
|
||||
if (lastQueuePlace != webserver.queuePlace) {
|
||||
log("Position in Queue: " + webserver.queuePlace)
|
||||
}
|
||||
lastQueuePlace = webserver.queuePlace;
|
||||
}
|
||||
if (meta.name === "login") {
|
||||
playerid = data.entityId;
|
||||
}
|
||||
if (finishedQueue === false && meta.name === "chat") { // we can know if we're about to finish the queue by reading the chat message
|
||||
// we need to know if we finished the queue otherwise we crash when we're done, because the queue info is no longer in packets the server sends us.
|
||||
let chatMessage = JSON.parse(data.message);
|
||||
if (chatMessage.text && chatMessage.text === "Connecting to the server...") {
|
||||
if (webserver.restartQueue && proxyClient == null) { //if we have no client connected and we should restart
|
||||
stop();
|
||||
setTimeout(reconnect, 4000);
|
||||
} else {
|
||||
finishedQueue = true;
|
||||
webserver.queuePlace = "FINISHED";
|
||||
webserver.ETA = "NOW";
|
||||
}
|
||||
}
|
||||
}
|
||||
/*if (c<50) {
|
||||
console.log("meta: " + JSON.stringify(meta) + "\ndata: " + JSON.stringify(data));
|
||||
c++;
|
||||
}*/
|
||||
if (proxyClient) { // if we are connected to the proxy, forward the packet we recieved to our game.
|
||||
filterPacketAndSend(data, meta, proxyClient);
|
||||
}
|
||||
});
|
||||
|
||||
// set up actions in case we get disconnected.
|
||||
client.on('end', () => {
|
||||
if (proxyClient) {
|
||||
proxyClient.end("Connection reset by 2b2t server.\nReconnecting...");
|
||||
proxyClient = null
|
||||
}
|
||||
stop();
|
||||
log("Connection reset by 2b2t server. Reconnecting...");
|
||||
if (config.reconnect.onError) setTimeout(reconnect, 4000);
|
||||
});
|
||||
|
||||
client.on('error', (err) => {
|
||||
if (proxyClient) {
|
||||
proxyClient.end(`Connection error by 2b2t server.\n Error message: ${err}\nReconnecting...`);
|
||||
proxyClient = null
|
||||
}
|
||||
stop();
|
||||
log(`Connection error by 2b2t server. Error message: ${err} Reconnecting...`);
|
||||
if (config.reconnect.onError) setTimeout(reconnect, 4000);
|
||||
});
|
||||
|
||||
server = mc.createServer({ // create a server for us to connect to
|
||||
'online-mode': false,
|
||||
encryption: true,
|
||||
host: '0.0.0.0',
|
||||
port: config.ports.minecraft,
|
||||
version: config.MCversion,
|
||||
'max-players': maxPlayers = 1
|
||||
});
|
||||
|
||||
server.on('login', (newProxyClient) => { // handle login
|
||||
setTimeout(send, 50)
|
||||
newProxyClient.write('login', {
|
||||
entityId: playerid,
|
||||
levelType: 'default',
|
||||
gameMode: 0,
|
||||
dimension: 0,
|
||||
difficulty: 2,
|
||||
maxPlayers: server.maxPlayers,
|
||||
reducedDebugInfo: false
|
||||
});
|
||||
newProxyClient.write('position', {
|
||||
x: 0,
|
||||
y: 1.62,
|
||||
z: 0,
|
||||
yaw: 0,
|
||||
pitch: 0,
|
||||
flags: 0x00
|
||||
});
|
||||
|
||||
newProxyClient.on('packet', (data, meta) => { // redirect everything we do to 2b2t
|
||||
let chunkPos = {};
|
||||
if (meta.name === "position") {
|
||||
chunkPos.x = roundToZero(data.x / 16);
|
||||
chunkPos.z = roundToZero(data.z / 16);
|
||||
if (chunkPos.z !== chunkPos.lx || chunkPos.x !== chunkPos.lx) {
|
||||
|
||||
for (let i = 0; i < chunkData.length; i++) {
|
||||
if (chunkData[i].x < chunkPos.x - 10 || chunkData[i].x > chunkPos + 10 || chunkData[i].z < chunkPos.z - 10 || chunkData[i] > chunkPos.z + 10) { //if a cached chunk is outside of the render distance
|
||||
chunkData.splice(i, 1); // we delete it.
|
||||
}
|
||||
}
|
||||
}
|
||||
chunkPos.lx = chunkPos.x;
|
||||
chunkPos.lz = chunkPos.z;
|
||||
}
|
||||
filterPacketAndSend(data, meta, client);
|
||||
//console.log("meta: " + JSON.stringify(meta) + "\ndata: " + JSON.stringify(data))
|
||||
});
|
||||
|
||||
proxyClient = newProxyClient;
|
||||
});
|
||||
}
|
||||
|
||||
function send() {
|
||||
for (let i = 0; i < chunkData.length; i++) {
|
||||
proxyClient.write("map_chunk", chunkData[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function log(logmsg) {
|
||||
if (config.logging) fs.appendFile('../2smart2wait.log', DateTime.local().toLocaleString({
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
}) + " " + logmsg + "\n", err => {
|
||||
if (err) console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
function reconnect() {
|
||||
ping(config.minecraftserver.hostname, config.minecraftserver.port)
|
||||
.then((response) => {
|
||||
startQueuing();
|
||||
})
|
||||
.catch((error) => {
|
||||
reconnect();
|
||||
});
|
||||
}
|
||||
|
||||
function prioQueueLength() {
|
||||
https.get("https://api.2b2t.dev/prioq", (resp) => {
|
||||
let qdata = '';
|
||||
resp.on('data', (chunk) => {
|
||||
qdata += chunk;
|
||||
});
|
||||
resp.on("end", () => {
|
||||
queueData = JSON.parse(qdata);
|
||||
secRun = true;
|
||||
})
|
||||
}).on("error", (err) => {
|
||||
log("prioQueue api error: " + err)
|
||||
});
|
||||
if (secRun) return queueData[1];
|
||||
}
|
||||
|
||||
//function to filter out some packets that would make us disconnect otherwise.
|
||||
//this is where you could filter out packets with sign data to prevent chunk bans.
|
||||
function filterPacketAndSend(data, meta, dest) {
|
||||
if (meta.name !== "keep_alive" && meta.name !== "update_time") { //keep alive packets are handled by the client we created, so if we were to forward them, the minecraft client would respond too and the server would kick us for responding twice.
|
||||
dest.write(meta.name, data);
|
||||
}
|
||||
}
|
||||
|
||||
function roundToZero(number) {
|
||||
if (number < 0) return Math.ceil(number);
|
||||
else return Math.floor(number);
|
||||
}
|
||||
|
||||
//the discordBot part starts here.
|
||||
if (config.discordBot) {
|
||||
var dc = new discord.Client()
|
||||
dc.on('ready', () => {
|
||||
dc.user.setActivity("Queue is stopped.");
|
||||
cmdInput();
|
||||
});
|
||||
|
||||
dc.on('message', msg => {
|
||||
if (msg.author.username !== dc.user.username) {
|
||||
userInput(msg.content, true, msg);
|
||||
}
|
||||
});
|
||||
dc.login(secrets.BotToken);
|
||||
}
|
||||
|
||||
function userInput(cmd, DiscordOrigin, discordMsg) {
|
||||
switch (cmd) {
|
||||
case "start":
|
||||
startQueuing();
|
||||
if (DiscordOrigin) discordMsg.channel.send({
|
||||
embed: {
|
||||
color: 3447003,
|
||||
author: {
|
||||
name: dc.user.username,
|
||||
icon_url: dc.user.avatarURL
|
||||
},
|
||||
fields: [{
|
||||
name: "Queue",
|
||||
value: `Queue is starting up.`
|
||||
}
|
||||
],
|
||||
timestamp: new Date(),
|
||||
footer: {
|
||||
icon_url: dc.user.avatarURL,
|
||||
text: "Author: Surprisejedi"
|
||||
}
|
||||
}
|
||||
});
|
||||
else console.log("Queue is starting up.")
|
||||
break;
|
||||
case "update":
|
||||
if (DiscordOrigin) discordMsg.channel.send({
|
||||
embed: {
|
||||
color: 3447003,
|
||||
author: {
|
||||
name: dc.user.username,
|
||||
icon_url: dc.user.avatarURL
|
||||
},
|
||||
title: "2bored2wait discord bridge",
|
||||
description: "Start and stop the queue from discord!",
|
||||
fields: [{
|
||||
name: "Position",
|
||||
value: `You are in position **${webserver.queuePlace}**.`
|
||||
},
|
||||
{
|
||||
name: "ETA",
|
||||
value: `Estimated time until login: **${webserver.ETA}**`
|
||||
}
|
||||
],
|
||||
timestamp: new Date(),
|
||||
footer: {
|
||||
icon_url: dc.user.avatarURL,
|
||||
text: "Author: Surprisejedi"
|
||||
}
|
||||
}
|
||||
});
|
||||
else console.log("Position: " + webserver.queuePlace + " Estimated time until login: " + webserver.ETA)
|
||||
break;
|
||||
case "stop":
|
||||
if (webserver.isInQueue) {
|
||||
stop();
|
||||
if (DiscordOrigin) discordMsg.channel.send({
|
||||
embed: {
|
||||
color: 3447003,
|
||||
author: {
|
||||
name: dc.user.username,
|
||||
icon_url: dc.user.avatarURL
|
||||
},
|
||||
fields: [{
|
||||
name: "Queue",
|
||||
value: `Queue is **stopped**.`
|
||||
}
|
||||
],
|
||||
timestamp: new Date(),
|
||||
footer: {
|
||||
icon_url: dc.user.avatarURL,
|
||||
text: "Author: Surprisejedi"
|
||||
}
|
||||
}
|
||||
});
|
||||
else console.log("Queue is stopped")
|
||||
} else {
|
||||
clearTimeout(timedStart);
|
||||
if (DiscordOrigin) discordMsg.channel.send({
|
||||
embed: {
|
||||
color: 3447003,
|
||||
author: {
|
||||
name: dc.user.username,
|
||||
icon_url: dc.user.avatarURL
|
||||
},
|
||||
fields: [{
|
||||
name: "Queue",
|
||||
value: `Queue timer is **stopped**.`
|
||||
}
|
||||
],
|
||||
timestamp: new Date(),
|
||||
footer: {
|
||||
icon_url: dc.user.avatarURL,
|
||||
text: "Author: MrGeorgen"
|
||||
}
|
||||
}
|
||||
});
|
||||
else console.log("Queue timer is stopped")
|
||||
}
|
||||
break;
|
||||
default:
|
||||
let startregex = /^start (\d|[0-1]\d|2[0-3]):[0-5]\d$/
|
||||
if (startregex.test(cmd)) {
|
||||
let starttimestring = msg.content.split(" ");
|
||||
let starttime = starttimestring[1].split(":");
|
||||
let currentTime = DateTime.local();
|
||||
let startdt = currentTime.set({hour: starttime[0], minute: starttime[1]});
|
||||
if (startdt.toMillis() < currentTime.toMillis()) sartdt = startdt.plus({days: 1});
|
||||
timedStart = setTimeout(startQueuing, startdt.toMillis() - currentTime.toMillis());
|
||||
dc.user.setActivity("Starting at " + starttimestring[1]);
|
||||
discordMsg.channel.send({
|
||||
embed: {
|
||||
color: 3447003,
|
||||
author: {
|
||||
name: dc.user.username,
|
||||
icon_url: dc.user.avatarURL
|
||||
},
|
||||
fields: [{
|
||||
name: "Queue",
|
||||
value: `Queue is starting at ` + starttimestring[1]
|
||||
}
|
||||
],
|
||||
timestamp: new Date(),
|
||||
footer: {
|
||||
icon_url: dc.user.avatarURL,
|
||||
text: "Author: MrGeorgen"
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (DiscordOrigin) discordMsg.channel.send("Error: Unknown command");
|
||||
else console.error("Unknown command")
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
startQueue: function () {
|
||||
startQueuing();
|
||||
},
|
||||
filterPacketAndSend: function () {
|
||||
filterPacketAndSend();
|
||||
},
|
||||
stop: function () {
|
||||
stop();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user