Merge branch 'dev'

This commit is contained in:
Danielv123 committed 2017-06-20 20:10:51 +02:00
commit f3bb8c58e5
18 files changed
+2330 -82

No files matched your search

+7 -2
View File
@@ -31,6 +31,11 @@ Factorio*
/instances
/sharedMods
/database
/sharedPlugins
database/items.db
config.json
config.json
# weird pattern to ignore sharedPlugins except not ignore some core modules
!sharedPlugins/
sharedPlugins/*
!sharedPlugins/inventoryImports/
!sharedPlugins/UPSdisplay/
+9 -6
View File
@@ -66,9 +66,9 @@ function messageInterface(command, callback) {
client.exec(command, callback);
} catch (err) {
console.log(err);
if(typeof callback == "function"){
callback(err);
}
}
if(typeof callback == "function"){
callback();
}
}
}
@@ -111,6 +111,10 @@ if (!command || command == "help" || command == "--help") {
console.log("Downloaded "+name);
});
}
} else if (command == "start" && instance === undefined) {
console.log("ERROR: No instanceName provided!");
console.log("Usage: node client.js start [instanceName]");
process.exit(0);
} else if (command == "start" && typeof instance == "string" && instance != "/" && !fs.existsSync(instancedirectory)) {
// if instance does not exist, create it
console.log("Creating instance...");
@@ -305,7 +309,7 @@ function instanceManagement() {
// load plugins and execute onLoad event
let pluginDirectories = fileOps.getDirectoriesSync("./sharedPlugins/");
let plugins = [];
for(i=0; i<pluginDirectories.length; i++) {
for(let i=0; i<pluginDirectories.length; i++) {
let I = i
let log = function(t) {
console.log("Clusterio | "+ pluginDirectories[I] + " | " + t);
@@ -427,8 +431,7 @@ function instanceManagement() {
}
});
});
},1000);
});
},1000)});
}
});
+25 -21
View File
@@ -3,26 +3,29 @@ var fileOps = require('./fileOps');
var fs = require('fs');
// create folders for testing
if (!fs.existsSync("lib/test")){
fs.mkdirSync("lib/test");
function setupTestingEnv(){
if (!fs.existsSync("lib/test")){
fs.mkdirSync("lib/test");
}
if (!fs.existsSync("lib/test/folder")){
fs.mkdirSync("lib/test/folder");
}
if (!fs.existsSync("lib/test/another folder")){
fs.mkdirSync("lib/test/another folder");
}
if (!fs.existsSync("lib/test/file.txt")){
fs.writeFileSync("lib/test/file.txt", "contents");
}
if (!fs.existsSync("lib/test/another file.txt")){
fs.writeFileSync("lib/test/another file.txt", "more contents");
}
// we need this xfile to test a branch in fileOps.getNewestFile()s propagation
if (!fs.existsSync("lib/test/xfile.txt")){
fs.writeFileSync("lib/test/xfile.txt", "contents");
}
}
if (!fs.existsSync("lib/test/folder")){
fs.mkdirSync("lib/test/folder");
}
if (!fs.existsSync("lib/test/another folder")){
fs.mkdirSync("lib/test/another folder");
}
if (!fs.existsSync("lib/test/file.txt")){
fs.writeFileSync("lib/test/file.txt", "contents");
}
if (!fs.existsSync("lib/test/another file.txt")){
fs.writeFileSync("lib/test/another file.txt", "more contents");
}
// we need this xfile to test a branch in fileOps.getNewestFile()s propagation
if (!fs.existsSync("lib/test/xfile.txt")){
fs.writeFileSync("lib/test/xfile.txt", "contents");
}
setupTestingEnv();
setupTestingEnv(); // run twice to get branch coverage
describe("fileOps.js", function(){
describe("fileOps.getDirectoriesSync()", function(){
it("gets an array of directory names from a path", function(){
@@ -60,7 +63,7 @@ describe("fileOps.js", function(){
});
describe("fileOps.getNewestFile()", function(){
it("gets the newest file in a directory async", function(done){
fileOps.getNewestFile("lib/test", fileOps.getFileNamesSync("lib/test"), function(err, newest){
fileOps.getNewestFile("lib/test/", fileOps.getFileNamesSync("lib/test"), function(err, newest){
assert(newest && typeof newest.file == "string");
done();
});
@@ -97,7 +100,8 @@ describe("fileOps.js", function(){
fs.statSync("lib/test");
}, Error);
assert(x);
});
// give it some extra time because its sometimes very slow
}).timeout(5000);
it("returns false if you give it an invalid path", function(){
let x = fileOps.deleteFolderRecursiveSync("This isn't a real path");
assert(!x);
-41
View File
@@ -1,48 +1,7 @@
var copyChildren = function(d){
var copied = clone(d);
copy = [];
if(copied.children){
copied.children.forEach(function(h){
copy.push(h);
});
}
if(copied._children){
copied._children.forEach(function(h){
copy.push(h);
});
}
}
var clone = function(obj) {
if (null == obj || "object" != typeof obj) return obj;
var copy = obj.constructor();
for (var attr in obj) {
if (obj.hasOwnProperty(attr) && attr != "parent"){
if(attr == "children" && obj[attr] != null){
copy[attr] = obj[attr].map(function (d) {
var subCopy = clone(d);
return subCopy;
});
}
else if(attr == "_children" && obj[attr] != null){
copy[attr] = obj[attr].map(function(d) {
var subCopy = clone(d);
return subCopy;
});
}
else{
copy[attr] = obj[attr];
}
}
}
return copy;
}
var deepclone = function(obj){
return JSON.parse(JSON.stringify(obj));
}
module.exports = {
clone:clone,
deepclone:deepclone,
};
+3 -4
View File
@@ -2,7 +2,7 @@ var assert = require('assert');
var objectOps = require('./objectOps');
describe("objectOps.js", function(){
describe("objectOps.clone()", function(){
/*describe("objectOps.clone()", function(){
it("clones javascript objects", function(){
var object1 = {hello:"world", cat:{legs:4, name:"Kitty", colors:["brown", "yellow", "purple"]}};
var object2 = {};
@@ -20,7 +20,7 @@ describe("objectOps.js", function(){
assert.equal(obj1.cat.colors, "black");
assert.notEqual(obj2.cat.colors[1], "yellow");
});
});
});*/
describe("objectOps.deepclone()", function(){
it("deep clones javascript objects", function(){
var obj1 = {hello:"world", cat:{legs:4, name:"Kitty", colors:["brown", "yellow", "purple"]}};
@@ -32,9 +32,8 @@ describe("objectOps.js", function(){
assert.equal(obj2.cat.colors[1], "yellow");
});
it("throws on non JSON parameters", function(){
x = function(){};
assert.throws(function(){
y = objectOps.deepclone(x)
let y = objectOps.deepclone(objectOps.deepclone);
});
});
});
+1629
View File
File diff suppressed because it is too large. Load diff
+7 -2
View File
@@ -2,12 +2,13 @@
"name": "clusterio",
"version": "1.0.0",
"description": "Interserver communication for factorio",
"repository": "https://github.com/Danielv123/factorioClusterio",
"main": "master.js",
"scripts": {
"test": "npm run test-unit",
"test-unit": "mocha --recursive \"./{,!(node_modules)/**/}*.spec.js\" || echo .",
"start": "echo Run node client.js start [instance] to start slaves && node master.js",
"cover": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- lib/*.js -R spec"
"cover": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- \"./{,!(node_modules)/**/}*.spec.js\" -R spec || echo ."
},
"keywords": [
"factorio"
@@ -28,6 +29,7 @@
"fs-extra": "^1.0.0",
"getmac": "^1.2.1",
"hash-files": "^1.1.1",
"luamin": "^1.0.4",
"mkdirp": "^0.5.1",
"ncp": "^2.0.0",
"nedb": "^1.8.0",
@@ -43,6 +45,9 @@
"chai": "^3.5.0",
"istanbul": "^0.4.5",
"mocha": "^3.4.1",
"mock-fs": "^4.3.0"
"mocha-sinon": "^2.0.0",
"mock-fs": "^4.3.0",
"nock": "^9.0.13",
"sinon": "^2.3.4"
}
}
+9 -5
View File
@@ -1,9 +1,9 @@
const needle = require("needle");
console.log("/silent-command game.print('UPSdisplay enabled')\n");
console.log("/silent-command game.print('UPSdisplay enabled')");
setInterval(function(){
console.log("/silent-command game.write_file('UPSdisplay.txt', game.tick, true, 0)\n");
console.log("/silent-command game.write_file('UPSdisplay.txt', game.tick, true, 0)");
},1000);
var config = {};
process.stdin.setEncoding('utf8');
@@ -31,8 +31,12 @@ setInterval(function(){
let ticksInPeriod = historicalTicks[historicalTicks.length-1].tick - historicalTicks[0].tick;
let UPS = Math.round(ticksInPeriod / (timePeriod/1000));
// console.log("UPS: " + UPS);
needle.post(config.masterIP+':'+config.masterPort+'/api/editSlaveMeta', {slaveID: config.unique, password: config.clientPassword, meta: {UPS:UPS}}, function(err, resp) {
// success?
});
try{
needle.post(config.masterIP+':'+config.masterPort+'/api/editSlaveMeta', {slaveID: config.unique, password: config.clientPassword, meta: {UPS:UPS}}, function(err, resp) {
// success?
});
} catch (err){
console.log(err);
}
}
}, 10000);
+20
View File
@@ -0,0 +1,20 @@
/*
This clusterio plugin is tasked to read your inventory and logistic request slots
it will then attempt to fulfill those requests using items provided by the master
server. There *should* be an in game GUI to toggle this.
*/
module.exports = {
// Name of package. For display somewhere I guess.
name: "inventoryImports",
version: "1.0.0",
// Binary entrypoint for plugin. Don't let it crash. Stdout is sent to game as server chat (run /c commands from here for interface)
// Make sure its cross platform somehow.
binary: "node",
arguments: ["index.js"],
description: "Clusterio plugin to fill character logistic slots with master imports",
// We'll send everything in this file to your stdin. Beware.
scriptOutputFileSubscription: "inventoryImports.txt",
}
+31
View File
@@ -0,0 +1,31 @@
const needle = require("needle");
const isJson = require("./isJson.js");
const pluginConfig = require("./config.js");
const functions = require("./interfacingFunctions");
console.log("/silent-command game.print('InventoryImports enabled')");
var config = {};
process.stdin.setEncoding('utf8');
process.stdin.on('readable', () => {
var chunk = process.stdin.read();
// console.log(chunk)
if (chunk !== null && isNaN(chunk) && config && isJson(chunk) && !JSON.parse(chunk).factorioPort){
let inventory = JSON.parse(chunk)
// process inventory of players and ask master for more stuff
functions.handleInventory(inventory, config, console.log);
} else if(isJson(chunk) && JSON.parse(chunk).factorioPort){
config = JSON.parse(chunk);
}
});
process.stdin.on('end', () => {
process.stdout.write('end');
});
// poll inventories
setInterval(function(){
// console.log("Polling inventories\n")
console.log(functions.pollInventories(pluginConfig.scriptOutputFileSubscription));
}, 10000);
@@ -0,0 +1,155 @@
const objectOps = require("../../lib/objectOps.js");
const needle = require("needle");
const pluginConfig = require("./config")
const isJson = require("./isJson")
// returns JS object, throws if there are syntax errors in input string
function parseJsString(string){
// eval because my lua is writing a JS object instead of traditional JSON
// this was done because I hate escaping strings and there are more quotes
// in JSON than JS objects.
// since eval is unsafe, maybe use https://www.npmjs.com/package/eval-sanitizer
if(string.includes("require") || string.includes(";") || string.includes("eval")){
throw "parseJsString might have gotten something that could be a xss attempt";
}
let inventory = "";
eval("inventory = " + string);
/*console.log(string)
console.log(inventory)*/
return inventory;
}
function handleInventory(json, config, callback){
/*
json is one of these two samples
{"players":{"1":{"inventory":{"stone":12,"iron-ore":120,"raw-wood":10,"iron-plate":8,"steel-plate":480},"requestSlots":{}}}}
{"exports":{"1":[{"name":"iron-ore","count":0},{"name":"steel-plate","count":0}]}}
we want to seperate players, parse their json and compare to request slots
then ask master for the difference
we must then prepare it all and inject it into players inventories with lua before
the next json report arrives to avoid duplicating orders
*/
if(json && typeof json != "string" && typeof json == "object"){
let exports = {};
// this is an inventory report, check whats missing and request from master
// {"players":{"1":{"inventory":{"stone":12,"iron-ore":120,"raw-wood":10,"iron-plate":8,"steel-plate":480},"requestSlots":{}}}}
if(json.players){
let playerNames = Object.keys(json.players);
for(let i = 0; i < playerNames.length; i++) {
// haha, this turned into bit of a mess didn't it?
let playerInv = json.players[playerNames[i]];
// create a copy of player.requestSlots
let toRequest = objectOps.deepclone(playerInv.requestSlots);
let inventory = playerInv.inventory;
let itemsToConfirm = Object.keys(toRequest).length;
// create a place to store the object we have confirmed for importing
let confirmedItems = {};
let collectStacks = function(stack){
itemsToConfirm--;
if(stack){
// because of the way we collect data in a keyed JSON object, this first part of the IF cannot happen.
/* istanbul ignore next */
if(confirmedItems[stack.name] && confirmedItems[stack.name] > 0){
confirmedItems[stack.name] += stack.count;
} else {
confirmedItems[stack.name] = stack.count;
}
}
if(itemsToConfirm == 0 && Object.keys(confirmedItems).length > 0){
// return LUA
callback(insertItemsFromObject(confirmedItems, playerNames[i]));
}
}
// subtract items already in inventory
Object.keys(playerInv.requestSlots).forEach(function(key){
// if this item in inventory is also in a request slot, subtract invCount from requestCount
if(inventory[key]){
toRequest[key] -= inventory[key];
}
// if the difference is more than 0, request item from master
if(toRequest[key] > 0){
let request = {
name:key,
count:toRequest[key],
}
//console.log("Requesting: "+JSON.stringify(request));
// request the difference from master
needle.post(config.masterIP + ":" + config.masterPort + '/api/remove', request, {json:true}, function (err, response, body) {
if (!err && response && response.body && (typeof response.body == "object" || isJson(response.body))) {
// confirmed orders, already removed from master inventory
//response.body.name: response.body.count
if(typeof response.body == "string") response.body = JSON.parse(response.body);
let stack = {name:response.body.name,count:Number(response.body.count)}
collectStacks(stack);
} else {
collectStacks(false);
}
});
} else {
collectStacks(false);
}
});
}
}
// {"exports":{"1":[{"name":"iron-ore","count":0},{"name":"steel-plate","count":0}]}}
// this is a report of stuff we didn't have space for, send back to master
if(json.exports){
// loop over players (normally only one, but whatever)
Object.keys(json.exports).forEach(function(playerName){
// loop over simpleItemStacks to export
let stackArray = json.exports[playerName];
for(let i = 0; i < stackArray.length; i++){
if(stackArray[i].count > 0){
// console.log("Returning overflow: " + JSON.stringify(stackArray[i]));
needle.post(config.masterIP + ":" + config.masterPort + '/api/place', stackArray[i], function (err, resp, body) {
// console.log(body);
});
}
}
});
}
} else return false; // inventory is falsey or a string
}
function insertItemsFromObject(confirmedItems, playerName){
// expeced input
// {["iron-plate"]:100, coal:20}
// make lua table
// we gotta construct a string because I can't find any nice library for this
if(playerName && confirmedItems){
if(typeof playerName != "string") {
throw "playerName is: '"+typeof playerName+"' instead of string!";
}
let itemTable = "{";
Object.keys(confirmedItems).forEach(function(name){
let count = confirmedItems[name];
itemTable += '["'+name+'"]='+count+',';
});
itemTable += "}";
let outputFile = pluginConfig.scriptOutputFileSubscription;
// a="'+players[i]+'"
// b='+itemTable+'
return '/silent-command local a="'+playerName+'"local b='+itemTable+'local c={}if game.players[a]and game.players[a].connected then local d='+"'"+'{"exports":{"'+"'"+'..a..'+"'"+'":['+"'"+'local e=false;for f,g in pairs(b)do local h=game.players[a].insert{name=f,count=g}if e then d=d..'+"'"+','+"'"+'else e=true end;d=d..'+"'"+'{"name":"'+"'"+'..f..'+"'"+'","count":'+"'"+'..g-h..'+"'"+'}'+"'"+'end;d=d..'+"'"+']}}'+"'"+'game.write_file("'+outputFile+'",d)end';
}
}
function pollInventories(outputFile){
// console.log("/silent-command "+'local a=game.players["Danielv123"].get_inventory(defines.inventory.player_main).get_contents()local b=game.players["Danielv123"].get_quickbar().get_contents()local c={}local d="{inventory:{"for e,f in pairs(a)do d=d.."['+"'"+'"..e.."'+"'"+']:"..f..","end;for e,f in pairs(b)do d=d.."['+"'"+'"..e.."'+"'"+']:"..f..","end;d=d.."},requestSlots:{"for e,f in pairs(c)do d=d.."['+"'"+'"..e.."'+"'"+']:"..f..","end;d=d.."}}"game.write_file("'+pluginConfig.scriptOutputFileSubscription+'",d)');
// console.log("Running inventory LUA")
//console.log("/silent-command "+'local a=game.players["Danielv123"].get_inventory(defines.inventory.player_main).get_contents()local b=game.players["Danielv123"].get_quickbar().get_contents()local c={}for d=1,game.players["Danielv123"].force.character_logistic_slot_count do c[d]=game.players["Danielv123"].character.get_request_slot(d)end;local e="{inventory:{"for f,g in pairs(a)do e=e.."['+"'"+'"..f.."'+"'"+']:"..g..","end;for f,g in pairs(b)do e=e.."['+"'"+'"..f.."'+"'"+']:"..g..","end;e=e.."},requestSlots:{"for f,g in pairs(c)do e=e.."['+"'"+'"..g["name"].."'+"'"+']:"..g["count"]..", "end;e=e.."}}"game.write_file("'+pluginConfig.scriptOutputFileSubscription+'",e)')
// console.log("/silent-command "+'local a="{"for b,c in pairs(game.players)do local d=game.players[b].get_inventory(defines.inventory.player_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end;a=a.."}"game.write_file("'+pluginConfig.scriptOutputFileSubscription+'",a)')
//return "/silent-command "+'local a="{"for b,c in pairs(game.players)do if c.connected then local d=game.players[b].get_inventory(defines.inventory.player_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end end;a=a.."}"game.write_file("'+outputFile+'",a)'
return '/silent-command '+'local a='+"'"+'{"players":{'+"'"+'local b=false;for c,d in pairs(game.players)do if d.connected then if b then a=a..'+"'"+','+"'"+'else b=true end;local e=game.players[c].get_inventory(defines.inventory.player_main).get_contents()local f=game.players[c].get_quickbar().get_contents()local g={}for h=1,game.players[c].force.character_logistic_slot_count do g[h]=game.players[c].character.get_request_slot(h)end;a=a..'+"'"+'"'+"'"+'..c..'+"'"+'":{"inventory":{'+"'"+'local i=false;for j,k in pairs(e)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..j..'+"'"+'":'+"'"+'..k end;for j,k in pairs(f)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..j..'+"'"+'":'+"'"+'..k end;a=a..'+"'"+'},"requestSlots":{'+"'"+'i=false;for j,k in pairs(g)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..k['+"'"+'name'+"'"+']..'+"'"+'":'+"'"+'..k['+"'"+'count'+"'"+']end;a=a..'+"'"+'}}'+"'"+'end end;a=a.."}}"game.write_file("'+outputFile+'",a)'
/*
needle.post(config.masterIP+':'+config.masterPort+'/api/editSlaveMeta', {slaveID: config.unique, password: config.clientPassword, meta: {UPS:UPS}}, function(err, resp) {
// success?
});
*/
}
module.exports = {
handleInventory: handleInventory,
parseJsString: parseJsString,
pollInventories: pollInventories,
insertItemsFromObject: insertItemsFromObject,
}
@@ -0,0 +1,178 @@
const assert = require("assert");
const child_process = require("child_process");
const functions = require("./interfacingFunctions");
const luamin = require("luamin");
const isFactorioCommand = require("./isFactorioCommand");
const nock = require("nock");
// sinon does weird prototype manipulation and so doen't need to be saved to anything.
require("mocha-sinon");
const sinon = require("sinon");
describe("inventoryImports/interfacingFunctions.js", function(){
describe("handleInventory(inventory, config)", function(){
let config = {
masterIP:"localhost",
masterPort:8080,
}
it("returns false if given invalid input", function(){
assert(functions.handleInventory("this is a string") === false, "when given strings it should return false and do nothing");
});
it("Sends post requests and outputs factorio commands", function(done){
let inventory = {
"players":{
"Danielv123":{
"inventory":{
"stone":12,
"iron-ore":120,
"raw-wood":10,
"iron-plate":8,
"steel-plate":480
},
"requestSlots":{
"iron-plate":100,
"steel-plate":480,
"raw-wood":20,
"bullshit":0,
}
}
}
}
var scope = nock('http://localhost:8080')
.post('/api/remove', function(body){
// check that we are requesting the right things, () to convert to boolean
// 92 because 100 requested and 8 already in inventory
if(body.name == "iron-plate") assert(body.count == "92");
// nock wants us to return true if this is the correct body and we should reply
// if we return false we won't send a reply.
return true;
})
// nock destroys the endpoints after being hit once, this changes the number of hits required for destruction
.times(10)
// respond with JSON object instead of url query parameters
.reply(200,function(uri, req){
return req
},{
'Content-Type': 'application/json',
'Accept': 'application/json'
})
// nock logging statement .log((data) => console.log(data));
functions.handleInventory(inventory, config, callback);
function callback(x){
assert(x.includes(92) && x.includes("iron-plate"), "it should have included the items it was going to import");
assert(!x.includes("steel-plate"), "Inventory is already filled with steel so shouldn't be importing that");
assert(isFactorioCommand(x), "it should have returned a valid factorio command");
assert(x.includes('"Danielv123"'), "it should include the name of the player that will recieve the items");
done();
}
});
it("posts leftovers to master", function(done){
let inventory2 = {
"exports":{
"1":[
{"name":"iron-ore","count":1200},
{"name":"steel-plate","count":1337},
{"name":"copper-plate","count":0},
]
}
}
let timesRan = 0;
let callback = function(){
timesRan++
assert(timesRan <= 2, "placed more than 2 items, unexpeceted test result");
if(timesRan == 2){
done();
}
}
var scope = nock('http://localhost:8080')
.post('/api/place', function(body){
// check that we are requesting the right things
assert(body.name != "copper-plate", "copper plate should not be placed because it is 0");
assert(body.count > 0, "no need for placing 0 things");
assert(Number(body.count) != NaN, "count should be a number");
callback();
return true;
})
// nock destroys the endpoints after being hit once, this changes the number of hits required for destruction
.times(10)
.reply(200,"affirmative")
// if it tries using the nonexisting callback (argv 3) it will throw. It shoudln't.
assert.doesNotThrow(function(){
functions.handleInventory(inventory2, config);
}, "this here threw for some reason, remove assert.doesNotThrow for easier debugging of this issue");
});
});
describe("pollInventories(outputfile)", function(){
it("Returns a big factorio command as string", function(){
let x = functions.pollInventories("output.txt");
assert(typeof x == "string", "pollInventories() should always return a string");
assert(isFactorioCommand(x), "This is not a command factorio will accept, please prepend /c");
assert(x.length > 20, "LUA string seems to short, this ain't good");
});
it("Returns valid LUA", function(){
let x = functions.pollInventories("output.txt");
// remove string added to make factorio interpret as command
x = x.replace("/silent-command ","").replace("/c ","");
let y = false;
// luamin throws when it recieves something that is invalid LUA
assert.doesNotThrow(function(){
y = luamin.minify(x);
}, "Invalid LUA supplied");
assert(typeof y == "string", "luamin does not return string???");
assert(y.length > 20, "Minified LUA too short, something is up");
});
});
describe("insertItemsFromObject(object)", function(){
let testObject = {"copper-plate":100, "iron-ore":1337}
let playerName = "Danielv123"
it("Returns undefined if any of the parameters are missing", function(){
let x = functions.insertItemsFromObject();
assert(x === undefined);
let y = functions.insertItemsFromObject(testObject);
assert(y === undefined);
let z = functions.insertItemsFromObject(undefined, playerName);
assert(z === undefined);
});
it("Throws if player name is not a string", function(){
assert.throws(function(){
let x = functions.insertItemsFromObject(testObject, testObject);
}, "playerName is: 'object' instead of string!");
});
it("Returns a big factorio command as string", function(){
let x = functions.insertItemsFromObject(testObject, playerName);
assert(typeof x == "string", "LUA returned should be a string");
assert(x.length > 20, "LUA strings are quite long");
assert(isFactorioCommand(x), "This is not a command factorio will accept, please prepend /c");
});
it("The returned string is a valid factorio command", function(){
let x = functions.insertItemsFromObject(testObject, playerName);
assert(isFactorioCommand(x) == true);
});
});
describe("parseJsString(string) => object", function(){
it("converts a string with JS object notation to object", function(){
let x = functions.parseJsString("{hello:'thisAString!', yes:function(){return 'JS string!'}, not:'json'}");
assert(!!x == true, "Should under no circumstance return a falsey value, even !!{} is truthy");
assert(typeof x == "object", "parseJsString should return an object in this case");
assert(typeof x.yes == "function", "parseJsString is not JSON, so it should be able to contain functions");
});
it("*Tries* to avoid XSS attempts by throwing on certain symbols", function(){
assert.throws(function(){
let x = functions.parseJsString("{};throw 'fail'");
}, function(err) {
let error = err+""
/* istanbul ignore else */
if(error.includes('parseJsString might have gotten something that could be a xss attempt')){
return true;
}
}, "should throw when ; are passed because those are the simplest form of xss");
});
});
});
@@ -0,0 +1,23 @@
const luamin = require("luamin");
function isFactorioCommand(command){
if(typeof command != "string"){
return false;
} else if(!command.includes("/silent-command ") && !command.includes("/c ")){
return false;
} else if(command[0] != "/"){
return false;
} else {
// check that what follows /c is valid LUA
let x = command.replace("/silent-command ", "").replace("/c ","");
let y
try{
y = luamin.minify(x);
} catch (e) {
return false;
}
// by this point it is surely a LUA command
return true;
}
}
module.exports = isFactorioCommand;
@@ -0,0 +1,18 @@
const isFactorioCommand = require("./isFactorioCommand");
const assert = require("assert");
describe("inventoryImports/isFactorioCommand.js()", function(){
it("Returns false if input is not a factorio command", function(){
assert(isFactorioCommand({}) == false, "This is not even a string so should return false");
assert(isFactorioCommand("Howdy boys!") == false, "This is not a command so should return false");
assert(isFactorioCommand("/c this ain't a command because its invalid LUA") == false, "This is not valid LUA so it should return false");
assert(isFactorioCommand("Trying to trick the system /c game.print('Hello world!')") == false, "this is still not a command");
});
it("Returns true if it is a valid command", function(){
assert(isFactorioCommand("/c game.print('hello world')"), "This is a valid command, should output true not false")
});
it("Fails to understand that leading spaces are valid", function(){
assert(isFactorioCommand(" /c game.print('Hello world!')") == false, "This should be true but often returns false unintentionally. If you see this you probably fixed the bug so change this test, OK?")
});
});
+11
View File
@@ -0,0 +1,11 @@
module.exports = function(x){
if(x === false || x === true || x === null) return true; // === false is valid JSON, so is true and null
var isNotJson = false
try{
z = JSON.parse(x);
} catch (e) {
isNotJson = true;
}
return !isNotJson;
}
@@ -0,0 +1,20 @@
const isJson = require("./isJson");
const assert = require("assert");
describe("inventoryImports/isJson.js()", function(){
it("checks if a thing is valid JSON", function(){
assert(isJson("string") === false);
assert(isJson("{js_object_notation='very false'}") === false);
assert(isJson('{ "name":"John", "age":31, "city":"New York" }') === true, "This is valid JSON, lib disagrees");
});
it("recognizes that true and false are both valid JSON", function(){
assert(isJson(true) === true);
assert(isJson(false) === true);
});
it("Handles null as valid JSON (after spec)", function(){
assert(isJson(null) === true);
});
it("Recognizes that JSON doesn't allow trailing commas (,)", function(){
assert(isJson('["a", "b", ]') === false, "JSON should not allow trailing commas");
});
});
@@ -0,0 +1,180 @@
-- Lua cheatsheet
/c game.write_file("test.txt",serpent.block(game.players["Danielv123"].get_quickbar().get_contents()))
/c game.write_file("test.txt",serpent.block(game.players["Danielv123"].get_inventory(defines.inventory.player_main).get_contents()))
-- print JSON of entire inventory and request slots to file
-- TODO: Get requester slot data as well
-- requester slot data is game.players.character.get_request_slot(slot)
-- returns {name="copper-plate", count=47} or nil if nothing is set
-- maybe game.players.character.item_requests? http://lua-api.factorio.com/latest/LuaEntity.html#LuaEntity.item_requests
/c
local requests = {}
for i = 1, game.player.force.character_logistic_slot_count do
requests[i] = game.players["Danielv123"].character.get_request_slot(i)
end
local o = "Requests: "
for k,v in pairs(requests) do
o = o..v["name"]..": "..v["count"]..", "
end
game.print(o)
-- sum up our inventory and request slots and print it as valid JSON
-- turns out the valid part is important
/c
local o = '{"players":{'
local notFirst = false
for a,b in pairs(game.players) do
if b.connected then
if notFirst then
o = o .. ','
else
notFirst = true
end
local inventory = game.players[a].get_inventory(defines.inventory.player_main).get_contents()
local quickbar = game.players[a].get_quickbar().get_contents()
--[[ Get contents of requester slots into a table ]]
local requests = {}
for i = 1, game.players[a].force.character_logistic_slot_count do
requests[i] = game.players[a].character.get_request_slot(i)
end
o = o..'"'..a..'":{"inventory":{'
--[[ generate JSON table ]]
local addComma = false
for k,v in pairs(inventory) do
if addComma then
o = o..','
else
addComma = true
end
o = o..'"'..k..'":'..v
end
for k,v in pairs(quickbar) do
if addComma then
o = o..','
else
addComma = true
end
o = o..'"'..k..'":'..v
end
o = o..'},"requestSlots":{'
addComma = false
for k,v in pairs(requests) do
if addComma then
o = o..','
else
addComma = true
end
o = o..'"'..v['name']..'":'..v['count']
end
o = o..'}}' --[[ We need a comma after these two if there is more than 1 player, handled up top by notFirst]]
end
end
o = o.."}}"
game.write_file("t.txt", o)
game.print(o)
--[[
Example output
{"players":{"1":{"inventory":{"stone":12,"iron-ore":120,"raw-wood":10,"iron-plate":8,"steel-plate":480},"requestSlots":{}}}}
Minification procedure:
Run through https://mothereff.in/lua-minifier
Replace ' with '+"'"+'
Prepend /silent-command
remove game.print(o)
replace "t.txt" with "'+outputFile+'"
enclose in ''
]]
-- minified strings (newest on the top)
local a='+"'"+'{"players":{'+"'"+'local b=false;for c,d in pairs(game.players)do if d.connected then if b then a=a..'+"'"+','+"'"+'else b=true end;local e=game.players[c].get_inventory(defines.inventory.player_main).get_contents()local f=game.players[c].get_quickbar().get_contents()local g={}for h=1,game.players[c].force.character_logistic_slot_count do g[h]=game.players[c].character.get_request_slot(h)end;a=a..'+"'"+'"'+"'"+'..c..'+"'"+'":{"inventory":{'+"'"+'local i=false;for j,k in pairs(e)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..j..'+"'"+'":'+"'"+'..k end;for j,k in pairs(f)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..j..'+"'"+'":'+"'"+'..k end;a=a..'+"'"+'},"requestSlots":{'+"'"+'i=false;for j,k in pairs(g)do if i then a=a..'+"'"+','+"'"+'else i=true end;a=a..'+"'"+'"'+"'"+'..k['+"'"+'name'+"'"+']..'+"'"+'":'+"'"+'..k['+"'"+'count'+"'"+']end;a=a..'+"'"+'}}'+"'"+'end end;a=a.."}}"game.write_file("'+outputFile+'",a)
-- offline playes
local a="{"for b,c in pairs(game.players)do if c.connected then local d=game.players[b].get_inventory(defines.inventory.player_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end end;a=a.."}"game.print(a)
-- multiple players
local a="{"for b,c in pairs(game.players)do local d=game.players[b].get_inventory(defines.inventory.player_main).get_contents()local e=game.players[b].get_quickbar().get_contents()local f={}for g=1,game.players[b].force.character_logistic_slot_count do f[g]=game.players[b].character.get_request_slot(g)end;a=a..b..":{inventory:{"for h,i in pairs(d)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;for h,i in pairs(e)do a=a.."['+"'"+'"..h.."'+"'"+']:"..i..","end;a=a.."},requestSlots:{"for h,i in pairs(f)do a=a.."['+"'"+'"..i["name"].."'+"'"+']:"..i["count"]..", "end;a=a.."}},"end;a=a.."}"game.print(a)
local a=game.players["Danielv123"].get_inventory(defines.inventory.player_main).get_contents()local b=game.players["Danielv123"].get_quickbar().get_contents()local c={}for d=1,game.players["Danielv123"].force.character_logistic_slot_count do c[d]=game.players["Danielv123"].character.get_request_slot(d)end;local e="{inventory:{"for f,g in pairs(a)do e=e.."['+"'"+'"..f.."'+"'"+']:"..g..","end;for f,g in pairs(b)do e=e.."['+"'"+'"..f.."'+"'"+']:"..g..","end;e=e.."},requestSlots:{"for f,g in pairs(c)do e=e.."['+"'"+'"..g["name"].."'+"'"+']:"..g["count"]..", "end;e=e.."}}"game.print(e)
local inventory = game.players["Danielv123"].get_inventory(defines.inventory.player_main).get_contents()local quickbar = game.players["Danielv123"].get_quickbar().get_contents()local requests = {}for i = 1, game.player.force.character_logistic_slot_count do requests[i] = game.players["Danielv123"].character.get_request_slot(i) end local o = "{inventory:{" for k,v in pairs(inventory) do o = o.."['+"'"+'"..k.."'+"'"+']:"..v.."," end for k,v in pairs(quickbar) do o = o.."['+"'"+'"..k.."'+"'"+']:"..v.."," end o = o.."},requestSlots:{" for k,v in pairs(requests) do o = o.."['+"'"+'"..v["name"].."'+"'"+']:"..v["count"]..", " end o = o.."}}" game.write_file("test.txt", o)
-- insert items in players inventory and export leftovers
-- name should be players[i]
/c
local name = "1"
local items = {["iron-ore"]=30, ["steel-plate"]=120}
local items_failed = {}
if(game.players[name] and game.players[name].connected) then
for item,count in pairs(items) do
local i = game.players[name].insert{name=item,count=count}
if not items_failed[name] then
items_failed[name] = {}
end
if items_failed[name][item] then
items_failed[name][item] = items_failed[name][item] + count - i
else
items_failed[name][item] = count - i
end
end
game.write_file("t.txt", serpent.line({["exports"]=items_failed}, {["comment"]=false, ["compact"]=true}))
end
--[[
Writes something like
{exports={["1"]={["iron-ore"]=0,["steel-plate"]=0}}}
converting to JS would be
replace "[" and "]" with ""???
replace "=" with ":"
]]
--[[
JSON edition
{"exports":{"1":[{"name":"iron-ore","count":0},{"name":"steel-plate","count":0}]}}
Should always return valid JSON
]]
/c
local name = "1"
local items = {["iron-ore"]=30, ["steel-plate"]=120}
if(game.players[name] and game.players[name].connected) then
local o = '{"exports":{"'..name..'":['
local addComma = false
for item,count in pairs(items) do
local i = game.players[name].insert{name=item,count=count}
if addComma then
o = o..','
else
addComma = true
end
o = o .. '{"name":"'..item..'","count":'..count - i..'}'
end
o = o .. ']}}'
game.write_file("t.txt", o)
end
--[[
Minification procedure:
Run through https://mothereff.in/lua-minifier
Replace ' with '+"'"+'
Prepend /silent-command
replace "t.txt" with "'+outputFile+'"
enclose in ''
]]
-- JSON outputFile
local a="1"local b={["iron-ore"]=30,["steel-plate"]=120}local c={}if game.players[a]and game.players[a].connected then local d='+"'"+'{"exports":{"'+"'"+'..a..'+"'"+'":['+"'"+'local e=false;for f,g in pairs(b)do local h=game.players[a].insert{name=f,count=g}if e then d=d..'+"'"+','+"'"+'else e=true end;d=d..'+"'"+'{"name":"'+"'"+'..f..'+"'"+'","count":'+"'"+'..g-h..'+"'"+'}'+"'"+'end;d=d..'+"'"+']}}'+"'"+'game.write_file("'+outputFile+'",d)end
-- output to serpented LUA table
local a="Danielv123"local b={["iron-ore"]=30,["steel-plate"]=120}local c={}if game.players[a]and game.players[a].connected then for d,e in pairs(b)do local f=game.players[a].insert{name=d,count=e}if not c[a]then c[a]={}end;if c[a][d]then c[a][d]=c[a][d]+e-f else c[a][d]=e-f end end;game.write_file("'+outputFile+'",serpent.line(c,{["comment"]=false,["compact"]=true}))end
+5 -1
View File
@@ -42,6 +42,10 @@ setTimeout(function() {
xmlhttp.send();
}, 0)
let chartIgnoreList = [
"water",
"steam"
]
// ID of slave, ID of canvasjs div without #
function makeGraph(slaveID, selector) {
@@ -56,7 +60,7 @@ function makeGraph(slaveID, selector) {
}
let chartData = [];
for(let o = 0; o < itemNames.length; o++) {
if(itemNames[o] != "water"){
if(!chartIgnoreList.includes(itemNames[o])){
chartData[chartData.length] = generateLineChartArray(data, itemNames[o]);
}
}