diff --git a/client/js/constants.js b/client/js/constants.js index 3432151d..21647306 100644 --- a/client/js/constants.js +++ b/client/js/constants.js @@ -19,62 +19,6 @@ const colorCodeMap = [ ["15", "Light Grey"], ]; -const commands = [ - "/as", - "/away", - "/back", - "/ban", - "/banlist", - "/bs", - "/close", - "/collapse", - "/connect", - "/cs", - "/ctcp", - "/cycle", - "/dehop", - "/deop", - "/devoice", - "/disconnect", - "/expand", - "/ho", - "/hop", - "/hs", - "/ignore", - "/ignorelist", - "/invite", - "/invitelist", - "/join", - "/kick", - "/leave", - "/list", - "/me", - "/mode", - "/ms", - "/msg", - "/nick", - "/notice", - "/ns", - "/op", - "/os", - "/part", - "/query", - "/quote", - "/quit", - "/raw", - "/rejoin", - "/rs", - "/say", - "/send", - "/server", - "/slap", - "/topic", - "/unban", - "/unignore", - "/voice", - "/whois", -]; - const condensedTypes = [ "away", "back", @@ -95,7 +39,7 @@ const timeFormats = { module.exports = { colorCodeMap, - commands, + commands: [], condensedTypes, condensedTypesQuery, timeFormats, diff --git a/client/js/socket-events/commands.js b/client/js/socket-events/commands.js new file mode 100644 index 00000000..f7ca9073 --- /dev/null +++ b/client/js/socket-events/commands.js @@ -0,0 +1,8 @@ +const constants = require("../constants"); +const socket = require("../socket"); + +socket.on("commands", function(commands) { + if (commands) { + constants.commands = commands; + } +}); diff --git a/client/js/socket-events/index.js b/client/js/socket-events/index.js index 8bc1d5a8..5453e74f 100644 --- a/client/js/socket-events/index.js +++ b/client/js/socket-events/index.js @@ -2,6 +2,7 @@ require("./auth"); require("./change_password"); +require("./commands"); require("./init"); require("./join"); require("./more"); diff --git a/src/client.js b/src/client.js index 6dde4079..86383ed7 100644 --- a/src/client.js +++ b/src/client.js @@ -11,6 +11,7 @@ const Helper = require("./helper"); const UAParser = require("ua-parser-js"); const uuidv4 = require("uuid/v4"); const escapeRegExp = require("lodash/escapeRegExp"); +const inputs = require("./plugins/inputs"); const MessageStorage = require("./plugins/messageStorage/sqlite"); const TextFileMessageStorage = require("./plugins/messageStorage/text"); @@ -40,33 +41,6 @@ const events = [ "list", "whois", ]; -const inputs = [ - "ban", - "ctcp", - "msg", - "part", - "rejoin", - "action", - "away", - "connect", - "disconnect", - "ignore", - "invite", - "kick", - "kill", - "mode", - "nick", - "notice", - "quit", - "raw", - "topic", - "list", - "whois", -].reduce(function(plugins, name) { - const plugin = require(`./plugins/inputs/${name}`); - plugin.commands.forEach((command) => plugins[command] = plugin); - return plugins; -}, {}); function Client(manager, name, config = {}) { _.merge(this, { @@ -362,12 +336,20 @@ Client.prototype.inputLine = function(data) { const irc = target.network.irc; let connected = irc && irc.connection && irc.connection.connected; - if (Object.prototype.hasOwnProperty.call(inputs, cmd) && typeof inputs[cmd].input === "function") { - const plugin = inputs[cmd]; + if (Object.prototype.hasOwnProperty.call(inputs.userInputs, cmd) && typeof inputs.userInputs[cmd].input === "function") { + const plugin = inputs.userInputs[cmd]; if (connected || plugin.allowDisconnected) { connected = true; - plugin.input.apply(client, [target.network, target.chan, cmd, args]); + plugin.input.apply(client, + [ + target.network, + target.chan, + cmd, + args, + (command) => this.inputLine({target: data.target, text: command}), + ] + ); } } else if (connected) { irc.raw(text); diff --git a/src/plugins/inputs/index.js b/src/plugins/inputs/index.js new file mode 100644 index 00000000..12be924c --- /dev/null +++ b/src/plugins/inputs/index.js @@ -0,0 +1,54 @@ +const clientSideCommands = ["/collapse", "/expand"]; + +const passThroughCommands = [ + "/as", + "/bs", + "/cs", + "/ho", + "/hs", + "/join", + "/ms", + "/ns", + "/os", + "/rs", +]; + +const userInputs = [ + "action", + "away", + "ban", + "connect", + "ctcp", + "disconnect", + "ignore", + "invite", + "kick", + "kill", + "list", + "mode", + "msg", + "nick", + "notice", + "part", + "quit", + "raw", + "rejoin", + "topic", + "whois", +].reduce(function(plugins, name) { + const plugin = require(`./${name}`); + plugin.commands.forEach((command) => (plugins[command] = plugin)); + return plugins; +}, {}); + +const getCommands = () => + Object.keys(userInputs) + .map((command) => `/${command}`) + .concat(clientSideCommands) + .concat(passThroughCommands) + .sort(); + +module.exports = { + getCommands, + userInputs, +}; diff --git a/src/plugins/packages/index.js b/src/plugins/packages/index.js index d9d0b8f9..de456888 100644 --- a/src/plugins/packages/index.js +++ b/src/plugins/packages/index.js @@ -6,6 +6,7 @@ const path = require("path"); const Helper = require("../../helper"); const themes = require("./themes"); const packageMap = new Map(); +const inputs = require("../inputs"); const stylesheets = []; @@ -15,11 +16,15 @@ module.exports = { loadPackages, }; -const packageApis = function(packageName) { +const packageApis = function(clientManager, packageName) { return { Stylesheets: { addFile: addStylesheet.bind(this, packageName), }, + Commands: { + add: (command, func) => inputs.userInputs[command] = func, + runAsUser: (line, userName, target) => clientManager.findClient(userName).inputLine({target, text: line}), + }, Config: { getConfig: () => Helper.config, }, @@ -38,7 +43,7 @@ function getPackage(name) { return packageMap.get(name); } -function loadPackages() { +function loadPackages(clientManager) { const packageJson = path.join(Helper.getPackagesPath(), "package.json"); let packages; @@ -75,7 +80,7 @@ function loadPackages() { } if (packageFile.onServerStart) { - packageFile.onServerStart(packageApis(packageName)); + packageFile.onServerStart(packageApis(clientManager, packageName)); } log.info(`Package ${colors.bold(packageName)} loaded`); diff --git a/src/server.js b/src/server.js index c43d3093..214bd35e 100644 --- a/src/server.js +++ b/src/server.js @@ -16,12 +16,12 @@ const colors = require("chalk"); const net = require("net"); const Identification = require("./identification"); const changelog = require("./plugins/changelog"); +const inputs = require("./plugins/inputs"); const themes = require("./plugins/packages/themes"); themes.loadLocalThemes(); const packages = require("./plugins/packages/index"); -packages.loadPackages(); // The order defined the priority: the first available plugin is used // ALways keep local auth in the end, which should always be enabled. @@ -173,6 +173,7 @@ module.exports = function() { }); manager = new ClientManager(); + packages.loadPackages(manager); new Identification((identHandler) => { manager.init(identHandler, sockets); @@ -582,6 +583,7 @@ function initializeClient(socket, client, token, lastMessage) { networks: client.networks.map((network) => network.getFilteredClone(client.lastActiveChannel, lastMessage)), token: tokenToSend, }); + socket.emit("commands", inputs.getCommands()); }; if (!Helper.config.public && token === null) { diff --git a/test/client/js/constantsTest.js b/test/client/js/constantsTest.js index f3140a89..2b941cc5 100644 --- a/test/client/js/constantsTest.js +++ b/test/client/js/constantsTest.js @@ -17,19 +17,6 @@ describe("client-side constants", function() { }); }); - describe(".commands", function() { - it("should be a non-empty array", function() { - expect(constants.commands).to.be.an("array").that.is.not.empty; - }); - - it("should only contain strings with no whitespaces and starting with /", function() { - constants.commands.forEach((command) => { - expect(command).to.be.a("string").that.does.not.match(/\s/); - expect(command[0]).to.equal("/"); - }); - }); - }); - describe(".condensedTypes", function() { it("should be a non-empty array", function() { expect(constants.condensedTypes).to.be.an("array").that.is.not.empty; diff --git a/test/plugins/inputs/indexTest.js b/test/plugins/inputs/indexTest.js new file mode 100644 index 00000000..56c2eef1 --- /dev/null +++ b/test/plugins/inputs/indexTest.js @@ -0,0 +1,21 @@ +"use strict"; + +const expect = require("chai").expect; +const inputs = require("../../../src/plugins/inputs"); + +describe("inputs", function() { + describe(".getCommands", function() { + it("should return a non-empty array", function() { + expect(inputs.getCommands()).to.be.an("array").that.is.not.empty; + }); + + it("should only return strings with no whitespaces and starting with /", function() { + inputs.getCommands().forEach((command) => { + expect(command) + .to.be.a("string") + .that.does.not.match(/\s/); + expect(command[0]).to.equal("/"); + }); + }); + }); +});