'use strict'; // Howto: see README.md //function getUserHome() { //return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME']; //} var tzOffsetMinutes = 240; //subtract this from server time to get local time; 4hrs is 240; 5hrs is 300 // TODO: handle tzOffsetMinutes not divisible by 60 // var selectedDateStr = null; // selectedDateStr = "2018-05-08"; const express = require('express'), multer = require('multer'), // var exphbs = require("express-handlebars"); // exphbs = require('../../'); // "express-handlebars" cookieParser = require('cookie-parser'), bodyParser = require('body-parser'), //session = require('express-session'), fs = require('fs'), readlines = require('n-readlines'); const os = require('os'); //var formidable = require('formidable'); const querystring = require("querystring"); // built-in // TODO: var config = require(storagePath + '/config.js') // config file contains all tokens and other private info // var fun = require('./functions.js'); // functions file contains our non-app-specific functions including those for our Passport and database work var mt = require('./minetestinfo.js'); // functions file contains our non-app-specific functions including those for our Passport and database work // var util = require('util') var app = express(); app.set('view engine', 'ejs'); // see https://medium.com/@TheJesseLewis/how-to-make-a-basic-html-form-file-upload-using-multer-in-an-express-node-js-app-16dac2476610 const port = process.env.PORT || 64638; app.use(bodyParser.urlencoded({extended:false})); // handle body requests app.use(bodyParser.json()); // make JSON work app.use('/public', express.static(__dirname + '/public')); //app.engine('handlebars', exphbs({defaultLayout: 'main'})); //app.set('view engine', 'handlebars'); var players = []; var playerIndices = {}; var activityDates = []; //see https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction var previousLine = null; //#region derived from mtsenliven.py var msgPrefixFlags = ["WARNING[Server]: ", "ACTION[Server]: "] var msgPrefixLists = {} // where flag is key var mfLen = msgPrefixFlags.length; for (var mfIndex=0; mfIndex -1) { msgPrefixNumber = mfIndex; msgprefix = msgPrefixFlags[mfIndex]; break; } } var skipDateEnable = false; for (var ufIndex=0; ufIndex -1) { verbNumber = ufIndex; verb = uniqueFlags[ufIndex]; dateStr = line.substring(0,10).trim(); //if (selectedDateStr==null || selectedDateStr==dateStr) { //console.log("(verbose message in processLogLine) using '" + dateStr + "' since selected '"+selectedDateStr+"'"); timeStr = line.substring(timeStartInt, timeStartInt+8); //console.log("using time "+timeStr); if (msgprefix!=null) { playerName = line.substring(msgPrefixIndex+msgprefix.length, verbIndex).trim(); var ipFlag = " ["; var ipIndex = playerName.indexOf(ipFlag); if (ipIndex > -1) { playerIP = playerName.substring(ipIndex+ipFlag.length, playerName.length-1); playerName = playerName.substring(0, ipIndex); } } else { playerName = "<missing msgprefix&rt;"; } //} //else { // skipDateEnable = true; //console.log("WARNING in processLogLine: skipping '" + dateStr + "' since not '"+selectedDateStr+"'"); //} break; } } var index = -1; // player index if (playerName != null) { if (playerName.length > 0) { if (playerIndices.hasOwnProperty(playerName)) { index = playerIndices[playerName]; indexMsg = "cached "; } else { index = players.length; //players.push({}); players[index] = {}; players[index].displayName = playerName; playerIndices[playerName] = index; //console.log("created new index "+index); } } else { console.log("WARNING in processLogLine: zero-length player name"); } } if (index<0 && (verb=="leaves game"||verb=="joins game")) { console.log("(ERROR in processLogLine) " + indexMsg + "index was '"+index+"' but date was present '" + dateStr + "' for '"+line+"' (no player found, but" + "verb is a player verb)."); } var playDateEnable = false; if (verb == "leaves game") { if (index > -1) { var playIndex = -1; if (!players[index].hasOwnProperty("plays")) { players[index].plays = {}; } if (!players[index].plays.hasOwnProperty(dateStr)) { //leave login time blank--player must have logged in before the available part of the log began players[index].plays[dateStr] = []; players[index].plays[dateStr].push({}); playIndex = 0; } else { if (players[index].plays[dateStr].length==0) players[index].plays[dateStr].push({}); playIndex = players[index].plays[dateStr].length - 1; if (players[index].plays[dateStr][playIndex].hasOwnProperty("logoutTime")) { //If last entry is incomplete, start a new one: players[index].plays[dateStr].push({}); playIndex++; } } players[index].plays[dateStr][playIndex].logoutTime = timeStr; playDateEnable = true; } } else if (verb == "joins game") { if (index > -1) { if (playerIP!=null) { players[index].playerIP = playerIP; var playIndex = -1; if (!players[index].hasOwnProperty("plays")) { players[index].plays = {}; } if (!players[index].plays.hasOwnProperty(dateStr)) { players[index].plays[dateStr] = []; playIndex = 0; } else playIndex = players[index].plays[dateStr].length; players[index].plays[dateStr].push({}); //console.log(verb+" on "+dateStr+" (length "+players[index].plays[dateStr].length+") play "+playIndex+"+1 for player ["+index+"] "+playerName+"..."); players[index].plays[dateStr][playIndex].loginTime = timeStr; playDateEnable = true; } // else redundant (server writes " joins game " again // and shows list of players instead of ip). //TODO: else analyze list of players to confirm in case player logged in all day } } if (playDateEnable) { if (dateStr.length>0) { if (activityDates.indexOf(dateStr) < 0) { activityDates.push(dateStr); } } } } function storeUniqueLogData(output, lineNumber, errFlag=false) { var ret = ""; var outputTrim = output.trim(); var uPrefix = "active block modifiers took "; var uSuffix = "ms (longer than 200ms)"; // (outBytes is bytes) var showEnable = true; var foundFlag = null; var fIndex = null; var alwaysShowEnable = false; var msgMsg = "previous message"; var ufLen = uniqueFlags.length; for (var ufIndex=0; ufIndex= 0) { foundFlag = msgPrefixFlags[mfIndex]; break; } } if (foundFlag!=null) { var subMsg = output.substring(fIndex+flag.length).trim(); var nUWLen = nonUniqueWraps.length; for (var nUWIndex=0; nUWIndex -1) { showEnable = false; } else { msgPrefixLists[foundFlag].push(subMsg); } } } if (showEnable) { ret = outputTrim; if (foundFlag != null) { ret += "\n [ EnlivenMinetest ] " + msgMsg + " will be suppressed"; } } return ret; } function readLog() { if (players==null) players = []; if (playerIndices==null) playerIndices = {}; // os.homedir() + "/.minetest/debug_archived/2018/05/08.txt", // var logPaths = [os.homedir() + "/.minetest/debug.txt"]; var logPaths = [os.homedir() + "/minetest/bin/debug.txt"]; var lpLen = logPaths.length; for (var lpIndex=0; lpIndex'+"\n"); //res.write('
'+"\n"); //res.write('User Name (case-sensitive): '+"\n"); //res.write('Select a png image to upload:'+"\n"); //res.write(''+"\n"); //res.write(''+"\n"); //res.write('
'+"\n"); //res.end(ending); var msg = ""; res.render('pages/skin-upload-form', { msg: msg }); }); app.get('/skin-selection-form', function(req, res, next) { var msg = ""; res.render('pages/skin-selection-form', { msg: msg, skinFileNames: mt.selectableSkinFileNames() }); }); // see "new way" of handling multer errors: https://github.com/expressjs/multer#error-handling var singleSkinUpload = skinUpload.single('userFile'); app.post('/upload-skin', function(req, res) { singleSkinUpload(req, res, function(err) { if (err instanceof multer.MulterError) { // A Multer error occurred when uploading. res.render('multer error', { error: err }); //res.render('pages/result', { //msg: "An error occurred in processing the form: " + err, //}); } else if (err) { // An unknown error occurred when uploading. res.render('unknown error', { error: err }); //res.render('pages/result', { //msg: "An error occurred in processing: " + err, //}); } else { // var ending = ""; var msg = ""; // ending += 'Back to Main Site
' + "\n"; // ending += 'Back to Upload
' + "\n"; // ending += ''; // res.write(''); // res.write(''); if (!req.fileValidationError) { // res.write('

Complete!

'); msg = "Complete!"; } else { msg = req.fileValidationError; // res.write('

' + req.fileValidationError + '

'); } //res.end(ending); res.render('pages/result', { msg: msg }); } }) }); app.get('/select-skin', function(req, res, next) { mt.setSkin(req.query.userName, req.query.skinFileName); res.render('pages/result', { msg: "Complete." }); }); app.get('/', function(req, res, next) { //var ret = ""; //res.write(''); //res.write(''); // Whenever server starts the following is logged // (see also etc/example-input.txt): // //------------- // Separator //------------- // var selectedDateStr = null; var msg = ""; if (req.query.date) selectedDateStr = req.query.date if (req.query.msg != undefined) { //res.write("
"); //res.write("" + querystring.parse(req.query.msg) + "
\n"); // line above causes: //TypeError: Cannot convert object to primitive value //at /home/owner/git/EnlivenMinetest/webapp/server.js:390:16 //at Layer.handle [as handle_request] (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/layer.js:95:5) //at next (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/route.js:137:13) //at Route.dispatch (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/route.js:112:3) //at Layer.handle [as handle_request] (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/layer.js:95:5) //at /home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/index.js:281:22 //at Function.process_params (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/index.js:335:12) //at next (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/index.js:275:10) //at expressInit (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/middleware/init.js:40:5) //at Layer.handle [as handle_request] (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/layer.js:95:5) //res.write("
"); } //res.write('

Upload Skin

'); //res.write("

Server info

"); //res.write("
    "); //res.write("
  • assuming minetestserver ran as: " + os.homedir() + "
  • "); //res.write("
  • timezone (tzOffsetMinutes/60*-1): " + (Math.floor(tzOffsetMinutes/60)*-1) + '' + "
  • "); //res.write('
  • date: ' + ((selectedDateStr!=null)?selectedDateStr:"not selected") + '' + "
  • "); //res.write('var activityDates = [];'); var pdLength = 0; if (activityDates != null) pdLength = activityDates.length; var logDates = []; for (var pdIndex = 0; pdIndex < pdLength; pdIndex++) { //res.write('activityDates.push("' + activityDates[pdIndex] + '");'); if (selectedDateStr!=activityDates[pdIndex]) { //res.write(''+activityDates[pdIndex]+' '); logDates.push( { date:activityDates[pdIndex], active:true}); } else { logDates.push( { date:activityDates[pdIndex], active:false}); //res.write(activityDates[pdIndex]+' '); } } //if (selectedDateStr==null) { //res.write('2018-05-08'); //} //see ~/.minetest/debug.txt //and logs archived by EnlivenMinetest: //~/.minetest/debug_archived/2018/05/08.txt //res.write(''); //res.end(''); //res.render('home'); res.render('pages/index', { msg: msg, serverUser: os.homedir(), tzOffset: (Math.floor(tzOffsetMinutes/60)*-1), selectedDate: ((selectedDateStr!=null)?selectedDateStr:"not selected"), logDates: logDates }); }); var server = app.listen(port, function () { // 8123 is default for Minecraft DynMap // 64638 spells 'minet' on a telephone keypad, but 6463, 6472 is already Discord RPC server //console.log('express-handlebars example server listening on: ' + port); var host = server.address().address; var port = server.address().port; console.log("server address:"); console.log(JSON.stringify(server.address())); console.log("reading log..."); readLog(); console.log("EnlivenMinetest webapp is listening at http://%s:%s", host, port); });