From b71c392b32fc3ba59204fc0f5c6469303ec929bb Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sat, 14 Dec 2019 17:51:54 +0100 Subject: [PATCH] Port globalChat to new plugin system Port the globalChat plugin to the new plugin system. Since there's no config system yet the config options for it is removed. --- .gitignore | 1 - CHANGELOG.md | 3 + config.json.dist | 2 - plugins/global_chat/info.js | 23 ++++++ plugins/global_chat/instance.js | 58 +++++++++++++++ plugins/global_chat/test/plugin.js | 91 ++++++++++++++++++++++++ sharedPlugins/globalChat/config.js | 9 --- sharedPlugins/globalChat/index.js | 52 -------------- sharedPlugins/globalChat/test/missing.js | 4 -- test/mock.js | 26 ++++++- 10 files changed, 199 insertions(+), 70 deletions(-) create mode 100644 plugins/global_chat/info.js create mode 100644 plugins/global_chat/instance.js create mode 100644 plugins/global_chat/test/plugin.js delete mode 100644 sharedPlugins/globalChat/config.js delete mode 100644 sharedPlugins/globalChat/index.js delete mode 100644 sharedPlugins/globalChat/test/missing.js diff --git a/.gitignore b/.gitignore index bc8664ff..8803d5f3 100644 --- a/.gitignore +++ b/.gitignore @@ -50,7 +50,6 @@ sharedPlugins/* !sharedPlugins/inventoryImports/ !sharedPlugins/UPSdisplay/ !sharedPlugins/researchSync/ -!sharedPlugins/globalChat/ !sharedPlugins/clusterioMod/ !sharedPlugins/statisticsExporter/ diff --git a/CHANGELOG.md b/CHANGELOG.md index f8eab618..7ff5b68b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,9 @@ Version 2.0.0 - Removed oddball limits to slaves.json size. - Moved slave specific configuration into its own configuration file. - Removed unused binary option from plugin config. +- Removed info and shout command from globalChat plugin +- Removed mirrorAllChat and enableCrossServerShout configuration options for + globalChat plugin. ### Breaking Changes diff --git a/config.json.dist b/config.json.dist index 5481789e..8b8bdb99 100644 --- a/config.json.dist +++ b/config.json.dist @@ -40,8 +40,6 @@ "useNeuralNetDoleDivider": true, "msBetweenCommands": 10, "allowRemoteCommandExecution": true, - "enableCrossServerShout": true, - "mirrorAllChat": false, "__comment7": "Autosave interval for items in the master storage in milliseconds. Defaults to one minute when set to 0 or not set.", "autosaveInterval": 600000, "__comment8": "This setting only affects the master. It makes every import for things that arent 'electricity' return a maximum of 0. Effectively disables teleport chests.", diff --git a/plugins/global_chat/info.js b/plugins/global_chat/info.js new file mode 100644 index 00000000..228bd554 --- /dev/null +++ b/plugins/global_chat/info.js @@ -0,0 +1,23 @@ +let link = require("lib/link"); +let plugin = require("lib/plugin"); + +module.exports = { + name: "global_chat", + title: "Global Chat", + description: "Forwards chat between instances.", + version: "2.0.0-alpha", + instanceEntrypoint: "instance", + + messages: { + chat: new link.Event({ + type: 'global_chat:chat', + links: ['instance-slave', 'slave-master', 'master-slave', 'slave-instance'], + forwardTo: 'master', + broadcastTo: 'instance', + eventProperties: { + "instance_name": { type: "string" }, + "content": { type: "string" }, + }, + }), + }, +} diff --git a/plugins/global_chat/instance.js b/plugins/global_chat/instance.js new file mode 100644 index 00000000..8d85d1c1 --- /dev/null +++ b/plugins/global_chat/instance.js @@ -0,0 +1,58 @@ +const plugin = require("lib/plugin"); + + +/** + * Removes gps and train tags from messags + */ +function removeTags(content) { + return content.replace(/(\[gps=-?\d+,-?\d+\]|\[train=\d+\])/g, ""); +} + +/** + * Escapes a string for inclusion into a lua string + */ +function escapeLuaString(content) { + return content + .replace(/\\/g, "\\\\") + .replace(/"/g, '\\"') + .replace(/'/g, "\\'") + .replace(/\0/g, "\\0") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + ; +} + +class InstancePlugin extends plugin.BaseInstancePlugin { + constructor(...args) { + super(...args); + } + + async init() { + // XXX Nothing? + } + + async chatEventHandler(message) { + // TODO check if cross server chat is enabled + let content = `[${message.data.instance_name}] ${removeTags(message.data.content)}`; + await this.instance.server.sendRcon(`/sc game.print('${escapeLuaString(content)}')`); + } + + async onOutput(output) { + if (output.type === "action" && output.action === "CHAT") { + this.info.messages.chat.send(this.instance, { + instance_name: this.instance.name, + content: output.message, + }); + } + } + + // TODO implement info command in lua? +} + +module.exports = { + InstancePlugin, + + // For testing only + _removeTags: removeTags, + _escapeLuaString: escapeLuaString, +} diff --git a/plugins/global_chat/test/plugin.js b/plugins/global_chat/test/plugin.js new file mode 100644 index 00000000..0e1d3de0 --- /dev/null +++ b/plugins/global_chat/test/plugin.js @@ -0,0 +1,91 @@ +const assert = require('assert').strict; + +const mock = require('../../../test/mock'); +const lines = require('../../../test/lib/factorio/lines'); +const instance = require('../instance'); +const info = require('../info'); + + +describe("global_chat plugin", function() { + describe("removeTags()", function() { + it("should pass through an ordinary string", function() { + assert.equal(instance._removeTags("string"), "string"); + }); + it("should strip out gps tag", function() { + assert.equal(instance._removeTags("Look at [gps=12,-4]"), "Look at "); + }); + it("should strip out train tag", function() { + assert.equal(instance._removeTags("Train [train=1235]"), "Train "); + }); + }); + + describe("escapeLuaSrting", function() { + it("should escape backslashes", function() { + assert.equal(instance._escapeLuaString("\\a"), "\\\\a"); + }); + it("should escape double quotes", function() { + assert.equal(instance._escapeLuaString('"a"'), '\\"a\\"'); + }); + it("should escape single quotes", function() { + assert.equal(instance._escapeLuaString("'a'"), "\\'a\\'"); + }); + it("should escape nul bytes", function() { + assert.equal(instance._escapeLuaString("a\0b"), "a\\0b"); + }); + it("should escape newlines", function() { + assert.equal(instance._escapeLuaString("a\nb"), "a\\nb"); + }); + it("should escape carriage return", function() { + assert.equal(instance._escapeLuaString("a\rb"), "a\\rb"); + }); + it("should escape all combined", function() { + assert.equal(instance._escapeLuaString("a\\b\"c'd\0e\nf\rg"), "a\\\\b\\\"c\\'d\\0e\\nf\\rg"); + }); + }); + + describe("class InstancePlugin", function() { + let instancePlugin; + + before(async function() { + instancePlugin = new instance.InstancePlugin(info, new mock.MockInstance()); + await instancePlugin.init(); + }); + + describe(".chatEventHandler()", function() { + it("should send received chat as command", async function() { + instancePlugin.instance.server.rconCommands = []; + await instancePlugin.chatEventHandler({ data: { instance_name: "test", content: "User: message" } }); + assert.deepEqual( + instancePlugin.instance.server.rconCommands, + ["/sc game.print('[test] User: message')"], + ); + }); + }); + describe(".onOutput()", function() { + it("should forward chat", async function() { + let count = 0; + for (let [_, output] of lines.testLines) { + if (output.type === "action" && output.action === "CHAT") { + instancePlugin.instance.connector.sentMessages = []; + await instancePlugin.onOutput(output); + assert(instancePlugin.instance.connector.sentMessages.length, "message was not sent"); + count++; + } + } + assert(count > 0, "no lines were tested"); + }); + it("should ignore regular output", async function() { + let count = 0; + for (let [_, output] of lines.testLines) { + if (output.type !== "action" || output.action !== "CHAT") { + instancePlugin.instance.connector.sentMessages = []; + await instancePlugin.onOutput(output); + assert(!instancePlugin.instance.connector.sentMessages.length, "message was sent"); + count++; + } + } + assert(count > 0, "no lines were tested"); + }); + }) + }); +}); diff --git a/sharedPlugins/globalChat/config.js b/sharedPlugins/globalChat/config.js deleted file mode 100644 index 277032f6..00000000 --- a/sharedPlugins/globalChat/config.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - Clusterio plugin to allow for chat between instances. -*/ -module.exports = { - // Name of package. For display somewhere I guess. - name: "globalChat", - version: "1.0.0", - description: "Clusterio plugin to allow for chat between instances.", -} \ No newline at end of file diff --git a/sharedPlugins/globalChat/index.js b/sharedPlugins/globalChat/index.js deleted file mode 100644 index 6eb03bf3..00000000 --- a/sharedPlugins/globalChat/index.js +++ /dev/null @@ -1,52 +0,0 @@ -module.exports = class remoteCommands { - constructor(mergedConfig, messageInterface, extras){ - this.messageInterface = messageInterface; - this.config = mergedConfig; - this.socket = extras.socket; - this.instances = {}; - - this.socket.emit("registerChatReciever"); - - this.socket.on("gameChat", async data => { - if(data.instanceID && data.instanceID != this.config.unique.toString()){ - if(data.data.includes("[CHAT]") - // if this isn't used as an info command or something (reduce spam) - && !data.data.includes("!info") - // check if we are allowed to cross chat at all, default to true - && (mergedConfig.enableCrossServerShout == undefined || mergedConfig.enableCrossServerShout) - // check if we are supposed to relay this message - && (mergedConfig.mirrorAllChat || data.data.toLowerCase().includes("/shout") || data.data.toLowerCase().includes("!shout"))){ - let words = data.data.trim().split(" "); - words.shift(); - words.shift(); - words.shift(); - let message = words.join(" ").replace(/[";]/g, " ").replace(/[']/g,"").replace("/shout ", "").replace("!shout ","").replace(/\[gps=.*?\]/g, "").replace(/\[train=.*?\]/g, ""); - - if(data.data.includes("[gps=")){ - //Nothing - }else{ - this.messageInterface("/silent-command game.print('[" + data.instanceName + "] " + message + "')"); - } - } - } - }); - } - async factorioOutput(data){ - try{ - this.socket.emit("gameChat", { - instanceID: this.config.unique.toString(), - instanceName: this.config.name, - data, - }); - if(data.includes("[CHAT]") && data.includes("!info")){ - let infoMessage = [ - "=== Server info ===", - "Name: "+await this.getInstanceName(this.config.unique)+" ("+this.config.unique+")", - ]; - infoMessage.forEach((line, i) => { - setTimeout(()=>this.messageInterface("/silent-command game.print('"+line+"')"),i*200); - }); - } - }catch(e){console.log(e)} - } -} diff --git a/sharedPlugins/globalChat/test/missing.js b/sharedPlugins/globalChat/test/missing.js deleted file mode 100644 index 923d82bf..00000000 --- a/sharedPlugins/globalChat/test/missing.js +++ /dev/null @@ -1,4 +0,0 @@ -// These are libraries that does not have any units test, they are -// required here in order to track coverage for them. - -require('../index'); diff --git a/test/mock.js b/test/mock.js index d771a48a..3a1f8f09 100644 --- a/test/mock.js +++ b/test/mock.js @@ -1,5 +1,7 @@ const events = require("events"); +const link = require("lib/link"); + class MockSocket { constructor() { @@ -23,7 +25,7 @@ class MockSocket { close() { this.closeCalled = true; } -}; +} class MockConnector extends events.EventEmitter { constructor() { @@ -50,9 +52,29 @@ class MockConnector extends events.EventEmitter { disconnect() { this.disconnectCalled = true; } -}; +} + +class MockServer { + constructor() { + this.rconCommands = []; + } + + async sendRcon(command) { + this.rconCommands.push(command); + } +} + +class MockInstance extends link.Link { + constructor() { + super('instance', 'slave', new MockConnector()); + this.server = new MockServer(); + this.name = "test"; + } +} + module.exports = { MockSocket, MockConnector, + MockInstance, };