Browse Source

fix responses, allow selectable (non-playername) skins

master
poikilos 6 years ago
committed by Jacob Gustafson
parent
commit
aacb40fd2c
  1. 2
      webapp/.gitignore
  2. 9
      webapp/CHANGELOG.md
  3. 13
      webapp/README.md
  4. 12
      webapp/masterserver.js
  5. 89
      webapp/minetestinfo.js
  6. 7
      webapp/no-javascript.html
  7. 5
      webapp/node_modules/cookie-parser/HISTORY.md
  8. 28
      webapp/node_modules/cookie-parser/README.md
  9. 113
      webapp/node_modules/cookie-parser/index.js
  10. 41
      webapp/node_modules/cookie-parser/package.json
  11. 222
      webapp/package-lock.json
  12. 6
      webapp/package.json
  13. 612
      webapp/server.js
  14. 0
      webapp/views.deprecated/home.handlebars
  15. 0
      webapp/views.deprecated/layouts/main.handlebars
  16. 1
      webapp/views/notes.txt
  17. 42
      webapp/views/pages/index.ejs
  18. 32
      webapp/views/pages/result.ejs
  19. 69
      webapp/views/pages/skin-selection-form.ejs
  20. 45
      webapp/views/pages/skin-upload-form.ejs
  21. 1
      webapp/views/partials/footer.ejs
  22. 8
      webapp/views/partials/head.ejs
  23. 18
      webapp/views/partials/header.ejs

2
webapp/.gitignore

@ -1,3 +1,5 @@
/public/
# Logs
logs
*.log

9
webapp/CHANGELOG.md

@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [git] - 2019-03-19
### Added
- skin uploading via webapp (chooses Bucket_Game, or ENLIVEN if present)
- `npm install formidable mv` (switched from fs.rename to mv due to
- choosing existing (non-player `skin_*.png`) files
- `npm install multer`
- mv is no longer needed--had switched from fs.rename to mv due to
rename not working across filesystems (tmp is commonly on a different
volume).
volume), but then switched from formidable to multer since multer has
built-in functionality to cancel upload if too large, but formidable
crashes (see <https://stackoverflow.com/a/27067596/4541104>), so mv is
no longer needed)

13
webapp/README.md

@ -12,11 +12,16 @@ EnlivenMinetest Node.js webapp for web management of minetest
npm install
```
## Features
* upload skin
## Usage
* start like:
`node server.js`
* then it will listen on port 3000
* change skin at localhost:3000/skin-form
* public/skins will be created automatically. To force updating skins
during startup, delete the public/skins directory if already exists
and is outdated.
* In browser, go to http://localhost:64638
* for security, no overwrite is allowed
@ -35,6 +40,8 @@ npm install
not `RUN_IN_PLACE`
* choose minetest worlds directory separately from bin in case
not `RUN_IN_PLACE`
* try https://github.com/timbuchwaldt/node-async-fileupload
* try nodemon (automatically reloads changed js)
## Developer Notes
@ -80,7 +87,7 @@ fi
cd "$target_dir"
npm init
#except changed jade to pug
npm install express static-favicon morgan cookie-parser body-parser debug pug passport passport-local mongoose formidable mv
npm install express static-favicon morgan cookie-parser body-parser debug pug passport passport-local mongoose multer mv
#NOTE: multiparty has streaming like busboy, but is non-trivial to implement
```

12
webapp/masterserver.js

@ -1,22 +1,22 @@
var express = require('express');
var app = express();
var mt = require('./minetestinfo.js');
app.get('/get-players', function (req, res) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(players));
res.send(JSON.stringify(mt.players(true)));
});
var last_announce_string = "none";
var previousAnnounceStr = "none";
app.get('/last-announce', function (req, res) {
res.setHeader('Content-Type', 'text/plain');
res.send(last_announce_string);
res.send(previousAnnounceStr);
});
app.get('/announce', function (req, res) {
last_announce_string = JSON.stringify(req.body);
console.log("announce got:"+last_announce_string);
previousAnnounceStr = JSON.stringify(req.body);
console.log("announce got:" + previousAnnounceStr);
res.setHeader('Content-Type', 'text/plain');
res.send();
});

89
webapp/minetestinfo.js

@ -9,17 +9,94 @@ exports.minetestPath = function() {
}
const myName = "minetestinfo.js";
var skinDir = "";
exports.skinDir = function () {
return skinDir;
var skinsPath = "";
exports.skinsPath = function () {
return skinsPath;
}
var selectableSkinFileNames = [];
exports.selectableSkinFileNames = function() {
return selectableSkinFileNames;
}
exports.players = function(isLoggedIn) {
return []; // TODO: implement this
}
exports.setSkin = function(userName, skinFileName) {
var indirectName = "player_" + userName + ".skin";
var indirectPath = exports.skinsPath() + "/" + indirectName;
//if (skinName.endsWith('.png')) {
//console.log("WARNING: skinName should not specify extension--removing .png");
//skinName = skinName.substring(0, skinName.length-4);
//}
//var skinFileName = skinName + ".png";
fs.writeFile(indirectPath, skinFileName, function(err, data) {
if (err) {
msg = err.message;
console.log(err);
// res.write(msg + "<br/>")
}
else {
// res.write("Before the skin is applied, The minetestserver instance must be restarted.<br/>")
msg = "Successfully wrote " + skinFileName;
console.log(msg + " to " + indirectPath + ".");
// res.write(msg + "<br/>")
}
// res.end(ending);
});
}
exports.regeneratePaths = function () {
skinDir = minetestPath + "/games/Bucket_Game/mods/codercore/coderskins/textures";
skinsPath = minetestPath + "/games/Bucket_Game/mods/codercore/coderskins/textures";
if (fs.existsSync( minetestPath + "/games/ENLIVEN")) {
skinDir = minetestPath + "/games/ENLIVEN/mods/codercore/coderskins/textures";
skinsPath = minetestPath + "/games/ENLIVEN/mods/codercore/coderskins/textures";
}
console.log("[" + myName + "] skinDir: \"" + skinDir + "\"");
console.log("[" + myName + "] skinsPath: \"" + skinsPath + "\"");
var publicPath = __dirname + "/public";
var publicSkinsPath = publicPath + "/skins";
if (!fs.existsSync(publicPath)) {
fs.mkdirSync(publicPath, 0744);
}
if (!fs.existsSync(publicSkinsPath)) {
fs.mkdirSync(publicSkinsPath, 0744);
fs.readdir(skinsPath, (err, files) => {
selectableSkinFileNames = [];
files.forEach(file => {
if (file.startsWith("skin_") && file.endsWith(".png")) {
var srcPath = skinsPath + '/' + file;
var dstPath = publicSkinsPath + '/' + file;
console.log("copying '" + srcPath + "' to '" + dstPath + "'");
fs.copyFile(srcPath, dstPath, fs.constants.COPYFILE_EXCL, (err) => {
if (err) throw err;
selectableSkinFileNames.push(file);
// console.log('source.txt was copied to destination.txt');
});
}
else {
console.log("not a skin: " + file)
}
// console.log(file);
});
});
}
else {
fs.readdir(publicSkinsPath, (err, files) => {
selectableSkinFileNames = [];
files.forEach(file => {
if (file.startsWith("skin_") && file.endsWith(".png")) {
selectableSkinFileNames.push(file);
// console.log("detected existing " + file);
}
else {
console.log("bad skin: " + file)
}
// console.log(file);
});
});
}
}
var thisMinetest = "/tank/local/owner/minetest";
if (fs.existsSync(thisMinetest)) {

7
webapp/no-javascript.html

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<body>
This feature requires javascript.<br/>
<a href="/">Back to Home</a>
</body>
</html>

5
webapp/node_modules/cookie-parser/HISTORY.md

@ -1,3 +1,8 @@
1.4.4 / 2019-02-12
==================
* perf: normalize `secret` argument only once
1.4.3 / 2016-05-26
==================

28
webapp/node_modules/cookie-parser/README.md

@ -1,8 +1,7 @@
# cookie-parser
[![NPM Version][npm-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url]
[![Node.js Version][node-version-image]][node-version-url]
[![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][npm-downloads-image]][npm-url]
[![Build Status][travis-image]][travis-url]
[![Test Coverage][coveralls-image]][coveralls-url]
@ -38,11 +37,11 @@ Parse a cookie value as a JSON cookie. This will return the parsed JSON value if
### cookieParser.JSONCookies(cookies)
Given an object, this will iterate over the keys and call `JSONCookie` on each value. This will return the same object passed in.
Given an object, this will iterate over the keys and call `JSONCookie` on each value, replacing the original value with the parsed value. This returns the same object that was passed in.
### cookieParser.signedCookie(str, secret)
Parse a cookie value as a signed cookie. This will return the parsed unsigned value if it was a signed cookie and the signature was valid, otherwise it will return the passed value.
Parse a cookie value as a signed cookie. This will return the parsed unsigned value if it was a signed cookie and the signature was valid. If the value was not signed, the original value is returned. If the value was signed but the signature could not be validated, `false` is returned.
The `secret` argument can be an array or string. If a string is provided, this is used as the secret. If an array is provided, an attempt will be made to unsign the cookie with each secret in order.
@ -61,8 +60,12 @@ var cookieParser = require('cookie-parser')
var app = express()
app.use(cookieParser())
app.get('/', function(req, res) {
app.get('/', function (req, res) {
// Cookies that have not been signed
console.log('Cookies: ', req.cookies)
// Cookies that have been signed
console.log('Signed Cookies: ', req.signedCookies)
})
app.listen(8080)
@ -73,13 +76,10 @@ app.listen(8080)
### [MIT Licensed](LICENSE)
[npm-image]: https://img.shields.io/npm/v/cookie-parser.svg
[coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/cookie-parser/master
[coveralls-url]: https://coveralls.io/r/expressjs/cookie-parser?branch=master
[npm-downloads-image]: https://badgen.net/npm/dm/cookie-parser
[npm-url]: https://npmjs.org/package/cookie-parser
[node-version-image]: https://img.shields.io/node/v/cookie-parser.svg
[node-version-url]: https://nodejs.org/en/download
[travis-image]: https://img.shields.io/travis/expressjs/cookie-parser/master.svg
[npm-version-image]: https://badgen.net/npm/v/cookie-parser
[travis-image]: https://badgen.net/travis/expressjs/cookie-parser/master
[travis-url]: https://travis-ci.org/expressjs/cookie-parser
[coveralls-image]: https://img.shields.io/coveralls/expressjs/cookie-parser/master.svg
[coveralls-url]: https://coveralls.io/r/expressjs/cookie-parser?branch=master
[downloads-image]: https://img.shields.io/npm/dm/cookie-parser.svg
[downloads-url]: https://npmjs.org/package/cookie-parser

113
webapp/node_modules/cookie-parser/index.js

@ -5,26 +5,26 @@
* MIT Licensed
*/
'use strict';
'use strict'
/**
* Module dependencies.
* @private
*/
var cookie = require('cookie');
var signature = require('cookie-signature');
var cookie = require('cookie')
var signature = require('cookie-signature')
/**
* Module exports.
* @public
*/
module.exports = cookieParser;
module.exports.JSONCookie = JSONCookie;
module.exports.JSONCookies = JSONCookies;
module.exports.signedCookie = signedCookie;
module.exports.signedCookies = signedCookies;
module.exports = cookieParser
module.exports.JSONCookie = JSONCookie
module.exports.JSONCookies = JSONCookies
module.exports.signedCookie = signedCookie
module.exports.signedCookies = signedCookies
/**
* Parse Cookie header and populate `req.cookies`
@ -36,39 +36,40 @@ module.exports.signedCookies = signedCookies;
* @public
*/
function cookieParser(secret, options) {
return function cookieParser(req, res, next) {
function cookieParser (secret, options) {
var secrets = !secret || Array.isArray(secret)
? (secret || [])
: [secret]
return function cookieParser (req, res, next) {
if (req.cookies) {
return next();
return next()
}
var cookies = req.headers.cookie;
var secrets = !secret || Array.isArray(secret)
? (secret || [])
: [secret];
var cookies = req.headers.cookie
req.secret = secrets[0];
req.cookies = Object.create(null);
req.signedCookies = Object.create(null);
req.secret = secrets[0]
req.cookies = Object.create(null)
req.signedCookies = Object.create(null)
// no cookies
if (!cookies) {
return next();
return next()
}
req.cookies = cookie.parse(cookies, options);
req.cookies = cookie.parse(cookies, options)
// parse signed cookies
if (secrets.length !== 0) {
req.signedCookies = signedCookies(req.cookies, secrets);
req.signedCookies = JSONCookies(req.signedCookies);
req.signedCookies = signedCookies(req.cookies, secrets)
req.signedCookies = JSONCookies(req.signedCookies)
}
// parse JSON cookies
req.cookies = JSONCookies(req.cookies);
req.cookies = JSONCookies(req.cookies)
next();
};
next()
}
}
/**
@ -79,15 +80,15 @@ function cookieParser(secret, options) {
* @public
*/
function JSONCookie(str) {
function JSONCookie (str) {
if (typeof str !== 'string' || str.substr(0, 2) !== 'j:') {
return undefined;
return undefined
}
try {
return JSON.parse(str.slice(2));
return JSON.parse(str.slice(2))
} catch (err) {
return undefined;
return undefined
}
}
@ -99,21 +100,21 @@ function JSONCookie(str) {
* @public
*/
function JSONCookies(obj) {
var cookies = Object.keys(obj);
var key;
var val;
function JSONCookies (obj) {
var cookies = Object.keys(obj)
var key
var val
for (var i = 0; i < cookies.length; i++) {
key = cookies[i];
val = JSONCookie(obj[key]);
key = cookies[i]
val = JSONCookie(obj[key])
if (val) {
obj[key] = val;
obj[key] = val
}
}
return obj;
return obj
}
/**
@ -125,28 +126,28 @@ function JSONCookies(obj) {
* @public
*/
function signedCookie(str, secret) {
function signedCookie (str, secret) {
if (typeof str !== 'string') {
return undefined;
return undefined
}
if (str.substr(0, 2) !== 's:') {
return str;
return str
}
var secrets = !secret || Array.isArray(secret)
? (secret || [])
: [secret];
: [secret]
for (var i = 0; i < secrets.length; i++) {
var val = signature.unsign(str.slice(2), secrets[i]);
var val = signature.unsign(str.slice(2), secrets[i])
if (val !== false) {
return val;
return val
}
}
return false;
return false
}
/**
@ -159,23 +160,23 @@ function signedCookie(str, secret) {
* @public
*/
function signedCookies(obj, secret) {
var cookies = Object.keys(obj);
var dec;
var key;
var ret = Object.create(null);
var val;
function signedCookies (obj, secret) {
var cookies = Object.keys(obj)
var dec
var key
var ret = Object.create(null)
var val
for (var i = 0; i < cookies.length; i++) {
key = cookies[i];
val = obj[key];
dec = signedCookie(val, secret);
key = cookies[i]
val = obj[key]
dec = signedCookie(val, secret)
if (val !== dec) {
ret[key] = dec;
delete obj[key];
ret[key] = dec
delete obj[key]
}
}
return ret;
return ret
}

41
webapp/node_modules/cookie-parser/package.json

@ -1,28 +1,28 @@
{
"_from": "cookie-parser",
"_id": "cookie-parser@1.4.3",
"_from": "cookie-parser@^1.4.3",
"_id": "cookie-parser@1.4.4",
"_inBundle": false,
"_integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=",
"_integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==",
"_location": "/cookie-parser",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"type": "range",
"registry": true,
"raw": "cookie-parser",
"raw": "cookie-parser@^1.4.3",
"name": "cookie-parser",
"escapedName": "cookie-parser",
"rawSpec": "",
"rawSpec": "^1.4.3",
"saveSpec": null,
"fetchSpec": "latest"
"fetchSpec": "^1.4.3"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz",
"_shasum": "0fe31fa19d000b95f4aadf1f53fdc2b8a203baa5",
"_spec": "cookie-parser",
"_where": "/home/owner/GitHub/EnlivenMinetest/webapp",
"_resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz",
"_shasum": "e6363de4ea98c3def9697b93421c09f30cf5d188",
"_spec": "cookie-parser@^1.4.3",
"_where": "/home/owner/git/EnlivenMinetest/webapp",
"author": {
"name": "TJ Holowaychuk",
"email": "tj@vision-media.ca",
@ -43,11 +43,19 @@
"cookie-signature": "1.0.6"
},
"deprecated": false,
"description": "cookie parsing with signatures",
"description": "Parse HTTP request cookies",
"devDependencies": {
"istanbul": "0.4.3",
"mocha": "2.5.3",
"supertest": "1.1.0"
"deep-equal": "1.0.1",
"eslint": "5.13.0",
"eslint-config-standard": "12.0.0",
"eslint-plugin-import": "2.16.0",
"eslint-plugin-markdown": "1.0.0",
"eslint-plugin-node": "7.0.1",
"eslint-plugin-promise": "4.0.1",
"eslint-plugin-standard": "4.0.0",
"istanbul": "0.4.5",
"mocha": "5.2.0",
"supertest": "3.4.2"
},
"engines": {
"node": ">= 0.8.0"
@ -69,9 +77,10 @@
"url": "git+https://github.com/expressjs/cookie-parser.git"
},
"scripts": {
"lint": "eslint --plugin markdown --ext js,md .",
"test": "mocha --reporter spec --bail --check-leaks test/",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/",
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/"
},
"version": "1.4.3"
"version": "1.4.4"
}

222
webapp/package-lock.json

@ -13,16 +13,16 @@
"negotiator": "0.6.1"
}
},
"append-field": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
"integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY="
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"body-parser": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
@ -40,13 +40,18 @@
"type-is": "~1.6.15"
}
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"busboy": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
"integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
"dicer": "0.2.5",
"readable-stream": "1.1.x"
}
},
"bytes": {
@ -54,10 +59,45 @@
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
"concat-stream": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"requires": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^2.2.2",
"typedarray": "^0.0.6"
},
"dependencies": {
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
}
}
},
"content-disposition": {
"version": "0.5.2",
@ -75,9 +115,9 @@
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
},
"cookie-parser": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz",
"integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=",
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz",
"integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==",
"requires": {
"cookie": "0.3.1",
"cookie-signature": "1.0.6"
@ -88,6 +128,11 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -106,11 +151,25 @@
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"dicer": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
"integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=",
"requires": {
"readable-stream": "1.1.x",
"streamsearch": "0.1.2"
}
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"ejs": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz",
"integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ=="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@ -177,11 +236,6 @@
"unpipe": "~1.0.0"
}
},
"formidable": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz",
"integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg=="
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@ -192,18 +246,6 @@
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"glob": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
"requires": {
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "2 || 3",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"http-errors": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
@ -220,15 +262,6 @@
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
@ -239,6 +272,11 @@
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz",
"integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs="
},
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -272,14 +310,6 @@
"mime-db": "~1.33.0"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
@ -298,14 +328,19 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"mv": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
"integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=",
"multer": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.1.tgz",
"integrity": "sha512-zzOLNRxzszwd+61JFuAo0fxdQfvku12aNJgnla0AQ+hHxFmfc/B7jBVuPr5Rmvu46Jze/iJrFpSOsD7afO8SDw==",
"requires": {
"mkdirp": "~0.5.1",
"ncp": "~2.0.0",
"rimraf": "~2.4.0"
"append-field": "^1.0.0",
"busboy": "^0.2.11",
"concat-stream": "^1.5.2",
"mkdirp": "^0.5.1",
"object-assign": "^4.1.1",
"on-finished": "^2.3.0",
"type-is": "^1.6.4",
"xtend": "^4.0.0"
}
},
"n-readlines": {
@ -313,16 +348,16 @@
"resolved": "https://registry.npmjs.org/n-readlines/-/n-readlines-0.2.8.tgz",
"integrity": "sha512-FRr6GU0vooiPAuHMBt/Pspm4htItJKCehs8Q2urdUuigXsdQzP8V03UaQedeCmiygqJwtuGhnidWHRHhUOAg9w=="
},
"ncp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
"integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M="
},
"negotiator": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@ -331,29 +366,21 @@
"ee-first": "1.1.1"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"parseurl": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
"integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"process-nextick-args": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
},
"proxy-addr": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz",
@ -407,12 +434,15 @@
}
}
},
"rimraf": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
"integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"glob": "^6.0.1"
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"safe-buffer": {
@ -461,6 +491,16 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
},
"streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
},
"type-is": {
"version": "1.6.16",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
@ -470,11 +510,21 @@
"mime-types": "~2.1.18"
}
},
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@ -485,10 +535,10 @@
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
"xtend": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
}
}
}

6
webapp/package.json

@ -12,10 +12,10 @@
"license": "BSD",
"dependencies": {
"body-parser": "^1.18.2",
"cookie-parser": "^1.4.3",
"cookie-parser": "^1.4.4",
"ejs": "^2.6.1",
"express": "^4.7.2",
"formidable": "^1.2.1",
"mv": "^2.1.1",
"multer": "^1.4.1",
"n-readlines": "^0.2.8"
}
}

612
webapp/server.js

@ -5,12 +5,13 @@
//return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'];
//}
var tz_offset = 240; //subtract this from server time to get local time; 4hrs is 240; 5hrs is 300
// TODO: handle tz_offset not divisible by 60
// var selected_date_s = null;
// selected_date_s = "2018-05-08";
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";
var express = require('express'),
const express = require('express'),
multer = require('multer'),
// var exphbs = require("express-handlebars");
// exphbs = require('../../'); // "express-handlebars"
cookieParser = require('cookie-parser'),
@ -19,368 +20,432 @@ var express = require('express'),
fs = require('fs'),
readlines = require('n-readlines');
const os = require('os');
var formidable = require('formidable')
var querystring = require("querystring"); // built-in
var mv = require('mv');
// TODO: var config = require(storage_path + '/config.js') // config file contains all tokens and other private info
//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 player_indices = {};
var play_dates = [];
var playerIndices = {};
var activityDates = [];
//see https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction
var prev_line = null;
var previousLine = null;
//#region derived from mtsenliven.py
var msgprefix_flags = ["WARNING[Server]: ", "ACTION[Server]: "]
var msgprefix_lists = {} // where flag is key
var mf_len = msgprefix_flags.length;
for (var mf_i=0; mf_i<mf_len; mf_i++) {
msgprefix_lists[msgprefix_flags[mf_i]] = [];
var msgPrefixFlags = ["WARNING[Server]: ", "ACTION[Server]: "]
var msgPrefixLists = {} // where flag is key
var mfLen = msgPrefixFlags.length;
for (var mfIndex=0; mfIndex<mfLen; mfIndex++) {
msgPrefixLists[msgPrefixFlags[mfIndex]] = [];
}
var non_unique_wraps = [];
non_unique_wraps.push({
var nonUniqueWraps = [];
nonUniqueWraps.push({
"opener":"active block modifiers took ",
"closer":"ms (longer than 200ms)"
});
var unique_flags = [
var uniqueFlags = [
"leaves game",
"joins game"
];
//#endregion derived from mtsenliven.py
const skinStorage = multer.diskStorage({
// see https://medium.com/@TheJesseLewis/how-to-make-a-basic-html-form-file-upload-using-multer-in-an-express-node-js-app-16dac2476610
destination: function(req, file, next) {
next(null, mt.skinsPath()); // or something like './public/photo-storage'
},
limits: {
fileSize: 1*1024*1024 // in bytes
},
// Change filename
filename: function(req, file, next) {
const ext = "png";
// var errMsg = null;
console.log("* Checking name...");
if (!req.body.userName) {
return next(new Error("userName is missing"));
}
else if (req.body.userName.length < 1) {
return next(new Error("userName is blank."));
}
if (file.size < 1) {
return next(new Error("image not selected"));
}
//if (errMsg === null) {
var directName = "player_" + req.body.userName + '.' + ext;
console.log("* Renaming '" + file + "' to " + directName);
next(null, directName);
//}
//else {
//console.log(errMsg);
//next(new Error(errMsg));
//}
// const ext = file.mimetype.split('/')[1];
// next(null, file.fieldname + '-' + Date.now() + '.'+ext);
}
});
// see https://medium.com/@bmshamsnahid/nodejs-file-upload-using-multer-3a904516f6d2
const skinUpload = multer({
storage: skinStorage,
fileFilter: function(req, file, next) {
const ext = "png";
console.log("filtering...");
var errMsg = null;
// NOTE: return with error aborts the upload.
if (!file) {
errMsg = "You did not select a file.";
req.fileValidationError = errMsg;
return next(new Error(errMsg)); // next(null, false, new Error(errMsg))
}
else if (file.size < 1) {
errMsg = "Empty file.";
req.fileValidationError = errMsg;
return next(new Error(errMsg));
}
if (!file.mimetype.startsWith('image/')) {
errMsg = "* ERROR: file type " + file.mimetype
+ " is not supported";
req.fileValidationError = errMsg;
return next(new Error(errMsg));
}
if (errMsg === null) {
var directName = "player_" + req.body.userName + '.' + ext;
console.log("* " + file.mimetype + " '" + file + "' uploaded...");
// set player skin name to new file name:
mt.setSkin(req.body.userName, directName);
// TODO: allow setSkin output to res
next(null, true);
} else {
console.log(errMsg);
return next(new Error(errMsg));
}
}
});
function process_logline(line, line_number) {
//selected_date_s
//TODO: use store_unique_log_data instead of this function
var player_name = null;
function processLogLine(line, lineNumber) {
//selectedDateStr
//TODO: use storeUniqueLogData instead of this function
var playerName = null;
var verb = "";
var time_s = "";
var date_s = "";
var player_ip = null;
const time_start_i = 11;
var uf_len = unique_flags.length;
mf_len = msgprefix_flags.length;
var verb_i = -1;
var verb_number = -1;
var msgprefix_i = -1;
var msgprefix_number = -1;
var timeStr = "";
var dateStr = "";
var playerIP = null;
const timeStartInt = 11;
var ufLen = uniqueFlags.length;
mfLen = msgPrefixFlags.length;
var verbIndex = -1;
var verbNumber = -1;
var msgPrefixIndex = -1;
var msgPrefixNumber = -1;
var msgprefix = null;
var index_msg = "";
for (var mf_i=0; mf_i<mf_len; mf_i++) {
msgprefix_i = line.indexOf(msgprefix_flags[mf_i]);
if (msgprefix_i > -1) {
msgprefix_number = mf_i;
msgprefix = msgprefix_flags[mf_i];
var indexMsg = "";
for (var mfIndex=0; mfIndex<mfLen; mfIndex++) {
msgPrefixIndex = line.indexOf(msgPrefixFlags[mfIndex]);
if (msgPrefixIndex > -1) {
msgPrefixNumber = mfIndex;
msgprefix = msgPrefixFlags[mfIndex];
break;
}
}
var skip_date_enable = false;
for (var uf_i=0; uf_i<uf_len; uf_i++) {
verb_i = line.indexOf(unique_flags[uf_i]);
if (verb_i > -1) {
verb_number = uf_i;
verb = unique_flags[uf_i];
date_s = line.substring(0,10).trim();
//if (selected_date_s==null || selected_date_s==date_s) {
//console.log("(verbose message in process_logline) using '" + date_s + "' since selected '"+selected_date_s+"'");
time_s = line.substring(time_start_i, time_start_i+8);
//console.log("using time "+time_s);
var skipDateEnable = false;
for (var ufIndex=0; ufIndex<ufLen; ufIndex++) {
verbIndex = line.indexOf(uniqueFlags[ufIndex]);
if (verbIndex > -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) {
player_name = line.substring(msgprefix_i+msgprefix.length, verb_i).trim();
var ip_flag = " [";
var ip_i = player_name.indexOf(ip_flag);
if (ip_i > -1) {
player_ip = player_name.substring(ip_i+ip_flag.length, player_name.length-1);
player_name = player_name.substring(0,ip_i);
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 {
player_name = "&lt;missing msgprefix&rt;";
playerName = "&lt;missing msgprefix&rt;";
}
//}
//else {
// skip_date_enable = true;
//console.log("WARNING in process_logline: skipping '" + date_s + "' since not '"+selected_date_s+"'");
// skipDateEnable = true;
//console.log("WARNING in processLogLine: skipping '" + dateStr + "' since not '"+selectedDateStr+"'");
//}
break;
}
}
var index = -1; // player index
if (player_name != null) {
if (player_name.length > 0) {
if (player_indices.hasOwnProperty(player_name)) {
index = player_indices[player_name];
index_msg = "cached ";
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].display_name = player_name;
player_indices[player_name] = index;
players[index].displayName = playerName;
playerIndices[playerName] = index;
//console.log("created new index "+index);
}
}
else {
console.log("WARNING in process_logline: zero-length player name");
console.log("WARNING in processLogLine: zero-length player name");
}
}
if (index<0 && (verb=="leaves game"||verb=="joins game")) {
console.log("(ERROR in process_logline) " + index_msg +
console.log("(ERROR in processLogLine) " + indexMsg +
"index was '"+index+"' but date was present '" +
date_s + "' for '"+line+"' (no player found, but" +
dateStr + "' for '"+line+"' (no player found, but" +
"verb is a player verb).");
}
var play_date_enable = false;
var playDateEnable = false;
if (verb == "leaves game") {
if (index > -1) {
var play_i = -1;
var playIndex = -1;
if (!players[index].hasOwnProperty("plays")) {
players[index].plays = {};
}
if (!players[index].plays.hasOwnProperty(date_s)) {
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[date_s] = [];
players[index].plays[date_s].push({});
play_i = 0;
players[index].plays[dateStr] = [];
players[index].plays[dateStr].push({});
playIndex = 0;
}
else {
if (players[index].plays[date_s].length==0) players[index].plays[date_s].push({});
play_i = players[index].plays[date_s].length - 1;
if (players[index].plays[date_s][play_i].hasOwnProperty("logout_time")) {
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[date_s].push({});
play_i++;
players[index].plays[dateStr].push({});
playIndex++;
}
}
players[index].plays[date_s][play_i].logout_time = time_s;
play_date_enable = true;
players[index].plays[dateStr][playIndex].logoutTime = timeStr;
playDateEnable = true;
}
}
else if (verb == "joins game") {
if (index > -1) {
if (player_ip!=null) {
players[index].player_ip = player_ip;
var play_i = -1;
if (playerIP!=null) {
players[index].playerIP = playerIP;
var playIndex = -1;
if (!players[index].hasOwnProperty("plays")) {
players[index].plays = {};
}
if (!players[index].plays.hasOwnProperty(date_s)) {
players[index].plays[date_s] = [];
play_i = 0;
if (!players[index].plays.hasOwnProperty(dateStr)) {
players[index].plays[dateStr] = [];
playIndex = 0;
}
else play_i = players[index].plays[date_s].length;
players[index].plays[date_s].push({});
//console.log(verb+" on "+date_s+" (length "+players[index].plays[date_s].length+") play "+play_i+"+1 for player ["+index+"] "+player_name+"...");
players[index].plays[date_s][play_i].login_time = time_s;
play_date_enable = true;
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 (play_date_enable) {
if (date_s.length>0) {
if (play_dates.indexOf(date_s) < 0) {
play_dates.push(date_s);
if (playDateEnable) {
if (dateStr.length>0) {
if (activityDates.indexOf(dateStr) < 0) {
activityDates.push(dateStr);
}
}
}
}
function store_unique_log_data(output, line_number, err_flag=false) {
function storeUniqueLogData(output, lineNumber, errFlag=false) {
var ret = "";
var output_strip = output.trim();
var u_prefix = "active block modifiers took ";
var u_suffix = "ms (longer than 200ms)";
// (out_bytes is bytes)
var show_enable = true;
var found_flag = null;
var f_i = null;
var always_show_enable = false;
var msg_msg = "previous message";
var uf_len = unique_flags.length;
for (var uf_i=0; uf_i<uf_len; uf_i++) {
if (output.includes(unique_flags[uf_i])) {
always_show_enable = true;
}
}
if (!always_show_enable) {
var mf_len = msgprefix_flags.length;
for (var mf_i=0; mf_i<mf_len; mf_i++) {
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<ufLen; ufIndex++) {
if (output.includes(uniqueFlags[ufIndex])) {
alwaysShowEnable = true;
}
}
if (!alwaysShowEnable) {
var mfLen = msgPrefixFlags.length;
for (var mfIndex=0; mfIndex<mfLen; mfIndex++) {
// such as '2018-02-06 21:08:06: WARNING[Server]: Deprecated call to get_look_yaw, use get_look_horizontal instead'
// or 2018-02-06 21:08:05: ACTION[Server]: [playereffects] Wrote playereffects data into /home/owner/.minetest/worlds/FCAGameAWorld/playereffects.mt.
f_i = output.find(msgprefix_flags[mf_i]);
if (f_i >= 0) {
found_flag = msgprefix_flags[mf_i];
fIndex = output.find(msgPrefixFlags[mfIndex]);
if (fIndex >= 0) {
foundFlag = msgPrefixFlags[mfIndex];
break;
}
}
if (found_flag!=null) {
var sub_msg = output.substring(f_i+flag.length).trim();
var nuw_len = non_unique_wraps.length;
for (var nuw_i=0; nuw_i<nuw_len; nuw_i++) {
//for (wrap in non_unique_wraps) {
var wrap = non_unique_wraps[nuw_i];
if (sub_msg.includes(wrap["opener"]) && sub_msg.includes(wrap["closer"])) {
sub_msg = wrap["opener"] + "..." + wrap["closer"];
msg_msg = "similar messages";
if (foundFlag!=null) {
var subMsg = output.substring(fIndex+flag.length).trim();
var nUWLen = nonUniqueWraps.length;
for (var nUWIndex=0; nUWIndex<nUWLen; nUWIndex++) {
//for (wrap in nonUniqueWraps) {
var wrap = nonUniqueWraps[nUWIndex];
if (subMsg.includes(wrap["opener"]) && subMsg.includes(wrap["closer"])) {
subMsg = wrap["opener"] + "..." + wrap["closer"];
msgMsg = "similar messages";
break;
}
}
if (msgprefix_lists[found_flag].indexOf(sub_msg) > -1) {
show_enable = false;
if (msgPrefixLists[foundFlag].indexOf(subMsg) > -1) {
showEnable = false;
}
else {
msgprefix_lists[found_flag].push(sub_msg);
msgPrefixLists[foundFlag].push(subMsg);
}
}
}
if (show_enable) {
ret = output_strip;
if (found_flag != null) {
ret += "\n [ EnlivenMinetest ] " + msg_msg + " will be suppressed";
if (showEnable) {
ret = outputTrim;
if (foundFlag != null) {
ret += "\n [ EnlivenMinetest ] " + msgMsg + " will be suppressed";
}
}
return ret;
}
function read_log() {
function readLog() {
if (players==null) players = [];
if (player_indices==null) player_indices = {};
if (playerIndices==null) playerIndices = {};
// os.homedir() + "/.minetest/debug_archived/2018/05/08.txt",
// var log_paths = [os.homedir() + "/.minetest/debug.txt"];
var log_paths = [os.homedir() + "/minetest/bin/debug.txt"];
var lp_len = log_paths.length;
for (var lp_i=0; lp_i<lp_len; lp_i++) {
var this_log_path = log_paths[lp_i];
console.log("EnlivenMinetest webapp reading '" + this_log_path + "'...");
var line_number = 1;
if (fs.existsSync(this_log_path)) {
// var logPaths = [os.homedir() + "/.minetest/debug.txt"];
var logPaths = [os.homedir() + "/minetest/bin/debug.txt"];
var lpLen = logPaths.length;
for (var lpIndex=0; lpIndex<lpLen; lpIndex++) {
var thisLogPath = logPaths[lpIndex];
console.log("EnlivenMinetest webapp reading '" + thisLogPath + "'...");
var lineNumber = 1;
if (fs.existsSync(thisLogPath)) {
//uses n-readlines package: see https://stackoverflow.com/questions/34223065/read-lines-synchronously-from-file-in-node-js
var liner = new readlines(this_log_path);
var next = true;
while (next) {
next = liner.next();
if (next!=false) {
process_logline(next.toString('ascii'), line_number);
line_number++;
var readLines = new readlines(thisLogPath);
var nextLine = true;
while (nextLine) {
nextLine = readLines.next();
if (nextLine!=false) {
processLogLine(nextLine.toString('ascii'), lineNumber);
lineNumber++;
}
}
}
else {
console.log("WARNING: file not found: '" + this_log_path + "' (listing actual archived log folders is not yet implemented, so this is a hard-coded demo folder only on poikilos' server)");
console.log("WARNING: file not found: '" + thisLogPath + "' (listing actual archived log folders is not yet implemented, so this is a hard-coded demo folder only on poikilos' server)");
}
}
}
app.get('/skin-form', function (req, res) {
var ret = "";
ret += '<html><body style="font-family:calibri,sans">'+"\n";
ret += '<form action="/set-skin" method="post" enctype="multipart/form-data">'+"\n";
ret += 'User Name (case-sensitive): <input type="text" name="userName" id="userName">'+"\n";
ret += 'Select a png image to upload:'+"\n";
ret += '<input type="file" name="userFile" id="userFile">'+"\n";
ret += '<input type="submit" value="Upload Image" name="submit">'+"\n";
ret += '</form>'+"\n";
ret += '</body></html>';
res.send(ret);
//res.render('home');
app.get('/skin-upload-form', function(req, res, next) {
//var ending = "";
//ending += '<a href="/">Back to Main Site</a><br/>' + "\n";
////ending += '<a href="/skin-upload-form">Back to Upload</a><br/>' + "\n";
//ending += '</body></html>';
//res.write('<html><body style="font-family:calibri,sans">'+"\n");
//res.write('<form action="/upload-skin" method="post" enctype="multipart/form-data">'+"\n");
//res.write('User Name (case-sensitive): <input type="text" name="userName" id="userName">'+"\n");
//res.write('Select a png image to upload:'+"\n");
//res.write('<input type="file" name="userFile" id="userFile">'+"\n");
//res.write('<input type="submit" value="Upload Image" name="submit">'+"\n");
//res.write('</form>'+"\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()
});
});
//using express & formidable:
app.post('/set-skin', function (req, res){
var form = new formidable.IncomingForm();
// from coderskins/readme.txt:
//To install a specific skin for a specific player, name the PNG file
//to be used as follows:
//player_NAME.png
//where NAME is the player's in-game nick. Then copy the PNG file into
//the mod's "textures" directory.
//The PNG file should be a standard Minetest 64x32 or Minecraft 64x64
//"skin" file.
//Or, if you prefer, create a text file, in the mod's "textures" direc-
//tory with a similar filename:
//player_NAME.skin
//(OldCoder, 2019)
var directPath = "";
var indirectPath = "";
var destNameNoExt = "";
var msg = "Uploading...";
form.parse(req, function(err, fields, files) {
if (err) next(err);
destNameNoExt = destNameNoExt = "player_" + fields.userName;
directPath = mt.skinDir() + "/" + destNameNoExt + ".png";
indirectPath = mt.skinDir() + "/" + destNameNoExt + ".skin";
// TODO: make sure my_file and userName values are present
if (files.hasOwnProperty('userFile')) {
if (fields.hasOwnProperty('userName')) {
var originalPath = files.userFile.path;
console.log("trying to rename " + files.userFile.path
+ " to " + directPath);
// NOTE: rename does not work if tmp is on different device (common)
mv(files.userFile.path, directPath, function(err) {
// fs.rename(files.userFile.path, directPath, function(err) {
if (err) {
msg = "Failed to rename " + originalPath
+ " to " + directPath;
console.log(msg);
console.log(JSON.stringify(err));
msg += "<br/>\n";
//next(err);
// TODO: why does next above show:
//ReferenceError: next is not defined
//at /home/owner/git/EnlivenMinetest/webapp/server.js:355:6
//at FSReqWrap.oncomplete (fs.js:135:15)
}
else {
var thisData = destNameNoExt + ".png";
fs.writeFile(indirectPath, thisData, function(err, data) {
if (err) console.log(err);
console.log("Successfully wrote " + thisData
+ " to "+indirectPath+".");
});
}
res.end();
});
// 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 {
console.log("userName is undefined.");
}
// var ending = "";
var msg = "";
// ending += '<a href="/">Back to Main Site</a><br/>' + "\n";
// ending += '<a href="/skin-upload-form">Back to Upload</a><br/>' + "\n";
// ending += '</body></html>';
// res.write('<html>');
// res.write('<body style="font-family:calibri,sans">');
if (!req.fileValidationError) {
// res.write('<p>Complete!</p>');
msg = "Complete!";
}
else {
console.log("userFile is undefined.");
msg = req.fileValidationError;
// res.write('<p>' + req.fileValidationError + '</p>');
}
//res.end(ending);
res.render('pages/result', {
msg: msg
});
//form.on('fileBegin', function (name, file){
////file.path = __dirname + '/uploads/' + file.name;
//// file.path = skinDir + "/" + file.name;
//// manual_path = "player_" +
//});
form.on('file', function (name, file){
msg = 'Uploaded ' + file.name + "<br/>\n";
console.log(msg);
}
})
});
app.get('/select-skin', function(req, res, next) {
mt.setSkin(req.query.userName, req.query.skinFileName);
res.render('pages/result', {
msg: "Complete."
});
//res.sendFile(__dirname + '/index.html');
res.redirect("/?msg=" + querystring.stringify(msg));
});
app.get('/', function (req, res) {
var ret = "";
ret += '<html><body style="font-family:calibri,sans">';
app.get('/', function(req, res, next) {
//var ret = "";
//res.write('<html>');
//res.write('<body style="font-family:calibri,sans">');
// Whenever server starts the following is logged
// (see also etc/example-input.txt):
//
@ -388,11 +453,12 @@ app.get('/', function (req, res) {
// Separator
//-------------
//
var selected_date_s = null;
if (req.query.date) selected_date_s = req.query.date
var selectedDateStr = null;
var msg = "";
if (req.query.date) selectedDateStr = req.query.date
if (req.query.msg != undefined) {
ret += "<br/>\n";
//ret += "<b>" + querystring.parse(req.query.msg) + "</b><br>\n";
//res.write("<br/>");
//res.write("<b>" + querystring.parse(req.query.msg) + "</b><br>\n");
// line above causes:
//TypeError: Cannot convert object to primitive value
//at /home/owner/git/EnlivenMinetest/webapp/server.js:390:16
@ -406,53 +472,59 @@ app.get('/', function (req, res) {
//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)
ret += "<br/>\n";
}
ret += "Server must restart for uploaded skins to take effect.<br/>\n";
ret += "assuming minetestserver ran as: " + os.homedir() + "<br/>\n";
ret += "timezone (tz_offset/60*-1): " + (Math.floor(tz_offset/60)*-1) + '<span name="tzArea" id="tzArea"></span><br/>\n';
ret += 'date: <span id="dateArea" name="dateArea">' + selected_date_s + '</span><br/>'+"\n";
//ret += 'var play_dates = [];';
//res.write("<br/>");
}
//res.write('<p><a href="/skin-upload-form">Upload Skin</a></p>');
//res.write("<h3>Server info</h3>");
//res.write("<ul>");
//res.write("<li>assuming minetestserver ran as: " + os.homedir() + "</li>");
//res.write("<li>timezone (tzOffsetMinutes/60*-1): " + (Math.floor(tzOffsetMinutes/60)*-1) + '<span name="tzArea" id="tzArea"></span>' + "</li>");
//res.write('<li>date: <span id="dateArea" name="dateArea">' + ((selectedDateStr!=null)?selectedDateStr:"not selected") + '</span>' + "</li>");
//res.write('var activityDates = [];');
var pdLength = 0;
if (play_dates != null) pdLength = play_dates.length;
for (var pd_i = 0; pd_i < pdLength; pd_i++) {
//ret += 'play_dates.push("' + play_dates[pd_i] + '");';
if (selected_date_s!=play_dates[pd_i]) {
ret += '<a href="?date='+play_dates[pd_i]+'">'+play_dates[pd_i]+'</a> ';
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('<a href="?date='+activityDates[pdIndex]+'">'+activityDates[pdIndex]+'</a> ');
logDates.push( { date:activityDates[pdIndex], active:true});
}
else {
ret += play_dates[pd_i]+' ';
logDates.push( { date:activityDates[pdIndex], active:false});
//res.write(activityDates[pdIndex]+' ');
}
}
//if (selected_date_s==null) {
//ret += '<a href="?date=2018-05-08">2018-05-08</a>';
//if (selectedDateStr==null) {
//res.write('<a href="?date=2018-05-08">2018-05-08</a>');
//}
//see ~/.minetest/debug.txt
//and logs archived by EnlivenMinetest:
//~/.minetest/debug_archived/2018/05/08.txt
ret += `
<div style="color:gray" id="statusArea"></div>
<canvas id="myCanvas" width="100" height="1540"> <!--style="border:1px solid #c3c3c3;">-->
Your browser does not support the canvas element.
</canvas>
<div style="color:gray" id="outputArea">loading...</div>
<script src="js/main.js"></script>`;
ret += '</body></html>';
res.send(ret);
//res.write('</body></html>');
//res.end('</body></html>');
//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(64638, function () {
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: 3000');
//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...");
read_log();
readLog();
console.log("EnlivenMinetest webapp is listening at http://%s:%s", host, port);
});

0
webapp/views/home.handlebars → webapp/views.deprecated/home.handlebars

0
webapp/views/layouts/main.handlebars → webapp/views.deprecated/layouts/main.handlebars

1
webapp/views/notes.txt

@ -0,0 +1 @@
https://scotch.io/tutorials/use-ejs-to-template-your-node-application

42
webapp/views/pages/index.ejs

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<% include ../partials/head %>
</head>
<body class="container">
<header>
<% include ../partials/header %>
</header>
<main>
<div class="jumbotron">
<h1>EnlivenMinetest</h1>
<p>Welcome.</p>
<h2>&nbsp;<!--result--></h2>
<p><%= msg %></p>
</div>
<p>assuming minetestserver ran as: <%= serverUser %></p>
<p>timezone (tz_offset/60*-1): <%= tzOffset %></p>
<p>selected date: <%= selectedDate %></p>
<h2>Activity Dates:</h2>
<ul>
<% logDates.forEach(function(logDate) { %>
<li><a href="?date=<%= logDate.date %>"><%= logDate.date %></a> - <%= logDate.active %></li>
<% }); %>
</ul>
<div style="color:gray" id="statusArea"></div>
<canvas id="myCanvas" width="100" height="1540"> <!--style="border:1px solid #c3c3c3;">-->
Your browser does not support the canvas element.
</canvas>
<div style="color:gray" id="outputArea">loading...</div>
<script src="js/main.js"></script>
</main>
<footer>
<% include ../partials/footer %>
</footer>
</body>
</html>

32
webapp/views/pages/result.ejs

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<% include ../partials/head %>
</head>
<body class="container">
<header>
<% include ../partials/header %>
</header>
<main>
<div class="jumbotron">
<h1>EnlivenMinetest</h1>
<p></p>
<h2>&nbsp;<!--result--></h2>
<p><%= msg %></p>
</div>
<div style="color:gray" id="statusArea"></div>
<canvas id="myCanvas" width="100" height="1540"> <!--style="border:1px solid #c3c3c3;">-->
Your browser does not support the canvas element.
</canvas>
<div style="color:gray" id="outputArea">loading...</div>
<script src="js/main.js"></script>
</main>
<footer>
<% include ../partials/footer %>
</footer>
</body>
</html>

69
webapp/views/pages/skin-selection-form.ejs

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<% include ../partials/head %>
</head>
<body class="container">
<header>
<% include ../partials/header %>
</header>
<main>
<div class="row">
<div class="col-sm-8">
<div class="jumbotron">
<h1>EnlivenMinetest</h1>
<p>Choose Skin.</p>
<h2>&nbsp;<!--result--></h2>
<p><%= msg %></p>
</div>
<div>
<form action="/select-skin" method="post" enctype="multipart/form-data">
<div>User Name (case-sensitive): <input type="text" name="userName" id="userName"></div>
<!--<div><input type="hidden" name="skinFileName" id="skinFileName"></div>-->
<!--<div><input type="submit" value="Select" name="submit"></div>-->
</form>
</div>
<div id="setSkinResult"></div>
<script type="text/javascript">
function setSkin(skinFileName) {
if ((skinFileName != undefined) && (skinFileName.length>0)) {
var userName = document.getElementById("userName").value;
if (userName.length>0) {
document.getElementById("setSkinResult").innerHTML="You chose " + skinFileName + "...";
location.href = "/select-skin?userName="+userName+"&skinFileName="+skinFileName;
}
else {
alert("You must enter your username in the box first.");
}
}
else {
alert("No skin was specified.");
}
};
</script>
<div>
<% skinFileNames.forEach(function(skinFileName) { %>
<a id="<%= skinFileName %>" href="javascript:setSkin('<%= skinFileName %>')"><img alt="<%= skinFileName %>" src="public/skins/<%= skinFileName %>"/></a>
<% }); %>
</script>
</div>
</div>
<div class="col-sm-4">
<div class="well">
<h3>Links:</h3>
<p><a href="https://poikilos.dyndns.org">Server Page</a></p>
</div>
</div>
</div>
</main>
<footer>
<% include ../partials/footer %>
</footer>
</body>
</html>

45
webapp/views/pages/skin-upload-form.ejs

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<% include ../partials/head %>
</head>
<body class="container">
<header>
<% include ../partials/header %>
</header>
<main>
<div class="row">
<div class="col-sm-8">
<div class="jumbotron">
<h1>EnlivenMinetest</h1>
<p>Upload Skin.</p>
<h2>&nbsp;<!--result--></h2>
<p><%= msg %></p>
</div>
<div>
<form action="/upload-skin" method="post" enctype="multipart/form-data">
<div>User Name (case-sensitive): <input type="text" name="userName" id="userName"></div>
<div>Select a png image to upload: <input type="file" name="userFile" id="userFile"></div>
<div><input type="submit" value="Upload Image" name="submit"></div>
</form>
</div>
</div>
<div class="col-sm-4">
<div class="well">
<h3>Links:</h3>
<p><a href="https://poikilos.dyndns.org">Server Page</a></p>
</div>
</div>
</div>
</main>
<footer>
<% include ../partials/footer %>
</footer>
</body>
</html>

1
webapp/views/partials/footer.ejs

@ -0,0 +1 @@
<p class="text-center text-muted">Copyright &copy; 2019 Jake Gustafson</p>

8
webapp/views/partials/head.ejs

@ -0,0 +1,8 @@
<meta charset="UTF-8">
<title>EnlivenMinetest</title>
<!-- CSS (load bootstrap from a CDN) -->
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<style>
body { padding-top:50px; }
</style>

18
webapp/views/partials/header.ejs

@ -0,0 +1,18 @@
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">
<span class="glyphicon glyphicon glyphicon-tree-deciduous"></span>
EnlivenMinetest
</a>
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
<li><a href="/skin-upload-form">Upload Skin</a></li>
<li><a href="/skin-selection-form">Choose Skin</a></li>
</ul>
</div>
</div>
</nav>
Loading…
Cancel
Save