#50 nearing completion, needs taping together and testing

Includes great unit test coverage (npm run cover reports ~85%)
This commit is contained in:
Danielv123 committed 2017-06-07 20:40:03 +02:00
1 parent e5b6a94524
commit 0ada84e153
6 files changed
+264 -80

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/
+1
View File
@@ -28,6 +28,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",
+22 -63
View File
@@ -1,68 +1,36 @@
const needle = require("needle");
const isJson = require("./isJson.js");
const pluginConfig = require("./config.js");
const objectOps = require("../../lib/objectOps.js");
const functions = require("./interfacingFunctions");
console.log("/silent-command game.print('InventoryImports enabled')\n");
var config = {};
process.stdin.setEncoding('utf8');
let historicalTicks = [];
process.stdin.on('readable', () => {
var chunk = process.stdin.read();
if (chunk !== null && isNaN(chunk)){
// 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.
let inventory = "";
eval("inventory = " + chunk);
/* Example output:
{'1':{
inventory: {
'raw-wood': 4
},
requestSlots:{
'underground-belt': 50,
'fast-transport-belt': 50,
'express-underground-belt': 50
}}}
*/
if (chunk !== null && isNaN(chunk) && config && !isJson(chunk)&& !chunk.includes("[")){
// returns JS object, throws if there are syntax errors in input string
let inventory = functions.parseJsString(chunk);
console.log(inventory);
// we want to seperate players, parse their inventory 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 inventory report arrives to avoid duplicating orders
if(inventory && typeof inventory != "string"){
let players = Object.keys(inventory);
for(let i = 0; i < players.length; i++) {
let pinv = inventory[players[i]];
// create a copy of player.requestSlots
let toRequest = objectOps.deepclone(pinv.requestSlots);
// subtract items already in inventory
Object.keys(pinv.inventory).forEach(function(key){
let itemCount = pinv.inventory[key];
if(toRequest[key]){
toRequest[key] -= itemCount;
}
let request = {
name:key,
count:toRequest[key],
}
// request the difference from master
needle.post(config.masterIP + ":" + config.masterPort + '/api/remove', request, function (err, response, body) {
if (response && response.body && typeof response.body == "object") {
// confirmed orders, already removed from master inventory
//response.body.name: response.body.count
}
});
});
}
}
// process inventory of players and ask master for more stuff
functions.handleInventory(inventory, config);
} else if(isJson(chunk) && JSON.parse(chunk).factorioPort){
config = JSON.parse(chunk);
// if its a LUA table message returning with the exports send em off to master
} else if(!isJson(chunk) && !!chunk && config){
let thing = functions.parseJsString(chunk.replace(/=/g, ":"));
Object.keys(thing).forEach(function(playerName){
Object.keys(thing[playerName]).forEach(function(itemName){
needle.post(config.masterIP + ":" + config.masterPort + '/api/place', {
name: itemName,
count: thing[playerName][itemName]
}, function (err, resp, body) {
// console.log(body);
});
});
});
}
});
process.stdin.on('end', () => {
@@ -71,15 +39,6 @@ process.stdin.on('end', () => {
// poll inventories
setInterval(function(){
// TODO: Make it work for players other than myself, don't error when inventory does not exist, check logistic request slots
// 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)')
console.log("/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("'+pluginConfig.scriptOutputFileSubscription+'",a)')
/*
needle.post(config.masterIP+':'+config.masterPort+'/api/editSlaveMeta', {slaveID: config.unique, password: config.clientPassword, meta: {UPS:UPS}}, function(err, resp) {
// success?
});
*/
console.log("Polling inventories")
console.log(functions.pollInventories(pluginConfig.scriptOutputFileSubscription));
}, 10000);
@@ -0,0 +1,134 @@
const objectOps = require("../../lib/objectOps.js");
const needle = require("needle");
// 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;
/* Example output:
{'1':{
inventory: {
'raw-wood': 4
},
requestSlots:{
'underground-belt': 50,
'fast-transport-belt': 50,
'express-underground-belt': 50
}}}
*/
}
function handleInventory(inventory, config){
// we want to seperate players, parse their inventory 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 inventory report arrives to avoid duplicating orders
if(inventory && typeof inventory != "string" && typeof inventory == "object"){
let exports = {};
let players = Object.keys(inventory);
for(let i = 0; i < players.length; i++) {
let pinv = inventory[players[i]];
// create a copy of player.requestSlots
let toRequest = objectOps.deepclone(pinv.requestSlots);
// create a place to store the object we have confirmed for importing
let confirmedItems = {};
let itemsToConfirm = Object.keys(pinv.inventory).length;
// subtract items already in inventory
Object.keys(pinv.inventory).forEach(function(key){
let itemCount = pinv.inventory[key];
if(toRequest[key]){
toRequest[key] -= itemCount;
}
if(toRequest[key] > 0){
let request = {
name:key,
count:toRequest[key],
}
// request the difference from master
needle.post(config.masterIP + ":" + config.masterPort + '/api/remove', request, function (err, response, body) {
if (response && response.body && typeof response.body == "object") {
// confirmed orders, already removed from master inventory
//response.body.name: response.body.count
let stack = {name:response.body.name,count:response.body.count}
collectStacks(stack);
} else {
collectStacks(false);
}
});
}
});
function collectStacks(stack){
itemsToConfirm--;
if(stack){
if(confirmedItems[stack.name]){
confirmedItems[stack.name] += stack.count;
} else {
confirmedItems[stack.name] = stack.count;
}
}
if(itemsToConfirm <= 0){
// make lua table
// we gotta construct a string because I can't find any nice library for this
let itemTable = "{";
Object.keys(confirmedItems).forEach(function(name){
let count = confirmedItems[name];
itemTable += '["'+name+'"]='+count+',';
});
itemTable += "}";
console.log('/c local a="'+players[i]+'"local b='+itemTable+'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');
}
}
// handle exports (why would they be there? I don't know!)
if(pinv.exports){
Object.keys(pinv.exports).forEach(function(name){
if(exports[name]){
exports[name] += pinv.exports[name];
} else {
exports[name] = pinv.exports[name];
}
});
}
}
// keep handling exports here, now we send em off
Object.keys(exports).forEach(function(name){
needle.post(config.masterIP + ":" + config.masterPort + '/api/place', {
name: name,
count: exports[name]
}, function (err, resp, body) {
// console.log(body);
});
});
} else return false; // inventory is falsey or a string
}
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)'
/*
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,
}
@@ -0,0 +1,61 @@
const assert = require("assert");
const child_process = require("child_process");
const functions = require("./interfacingFunctions");
const luamin = require("luamin");
describe("inventoryImports/interfacingFunctions.js", function(){
describe("handleInventory(inventory)", function(){
it("does things to an object", function(){
let inventory = {'1':{inventory: {"iron-plate":63,'raw-wood': 4},requestSlots:{'raw-wood': 50,'fast-transport-belt': 50,'express-underground-belt': 50}}};
assert.throws(function(){
functions.handleInventory(inventory);
}, function(err){
// convert error to string
let error = ""+err
// expected error is here
/* istanbul ignore else */
if(error.includes("TypeError: Cannot read property 'masterIP' of undefined")) return true
}, "threw unexpected error");
});
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");
});
});
describe("pollInventories()", 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(x.includes("/silent-command "), "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");
x = x.replace("/silent-command ","").replace("/c ","");
let y = false;
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("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");
});
})
});
+39 -15
View File
@@ -55,19 +55,6 @@ o = o.."}"
game.print(o)
game.write_file("test.txt", o)
-- insert items in players inventory and export leftovers
/c
local name = "Danielv123"
local itemName = "iron-ore"
local itemCount = 100
if game.players[name] do
local items = {name=itemName, count=itemCount}
if game.players[name].can_insert(items) do
local insertedItems = game.players[name].insert(items)
local restItems = itemCount - insertedItems
end
end
--[[
Minification procedure:
@@ -75,14 +62,51 @@ Minification procedure:
Run through https://mothereff.in/lua-minifier
Replace ' with '+"'"+'
Prepend /silent-command
remove game.print(o)
replace "test.txt" with "'+outputFile+'"
enclose in ''
]]
-- minified strings
-- minified strings (newest on the top)
-- 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)
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 = "Danielv123"
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(items_failed, {["comment"]=false, ["compact"]=true}))
end
--[[
Minification procedure:
Run through https://mothereff.in/lua-minifier
Prepend /silent-command
replace "t.txt" with "'+outputFile+'"
enclose in ''
]]
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