Browse Source

Working delegation to defaultCommand

* Completed basic help module
* Added meta binding for module commands
* Fixed LOG_LEVEL env reader
* Added isFunnction utility
* Aded variable depth getObjectKeysToPrototype
* Added basic message handlers in AbstractModule
pull/10/head
Drew Short 4 years ago
parent
commit
06cf7aa19f
  1. 27
      bot/engine.js
  2. 2
      bot/logging.js
  3. 114
      bot/module/abstract.js
  4. 35
      bot/module/help.js
  5. 34
      bot/utility.js

27
bot/engine.js

@ -5,6 +5,7 @@ let { getShortestPrefix } = require('./utility');
let help = require('./module/help');
let message = require('./message');
let utility = require('./utility');
let { initModule } = require('./module/abstract');
let sentinelValue = '!';
@ -20,20 +21,28 @@ class Engine {
}
initModules() {
this.modules.forEach((module) => {
logger.info("Loading module: %s", module.name);
this.moduleMap.set(module.command, module);
this.commandMap.set(module.command, module);
this.commandRadixTree.addWord(module.command);
this.modules.forEach((mod) => {
logger.info("Loading module: %s", mod.name);
initModule(mod);
console.log("Recognized commands: %s", mod.getRecognizedCommands())
this.moduleMap.set(mod.command, mod);
this.commandMap.set(mod.command, mod);
this.commandRadixTree.addWord(mod.command);
});
this.modules.forEach((module) => {
let shortCommand = getShortestPrefix(this.commandRadixTree, module.command, 3);
logger.info("Adding short command %s for module: %s", shortCommand, module.name);
this.commandMap.set(shortCommand, module);
this.modules.forEach((mod) => {
let shortCharCommand = getShortestPrefix(this.commandRadixTree, mod.command, 1);
let short3CharCommand = getShortestPrefix(this.commandRadixTree, mod.command, 3);
let shortCommandAliases = [shortCharCommand, short3CharCommand];
logger.info("Adding short command %s for module: %s", shortCommandAliases, mod.name);
shortCommandAliases.forEach( (commandAlias) => {
this.commandMap.set(commandAlias, mod);
})
});
this.helpModule = help.create(this.moduleMap)
initModule(this.helpModule);
this.moduleMap.set(this.helpModule.command, this.helpModule);
this.commandMap.set('help', this.helpModule);
this.commands = Array.from(this.commandMap.keys()).sort()

2
bot/logging.js

@ -22,7 +22,7 @@ if (process.env.NODE_ENV !== 'production') {
}));
}
if ('LOG_LEVE' in process.env) {
if ('LOG_LEVEL' in process.env) {
logger.info('LOG_LEVEL:', process.env.LOG_LEVEL)
logger.level = process.env.LOG_LEVEL
}

114
bot/module/abstract.js

@ -4,30 +4,132 @@
let { logger } = require('../logging');
let message = require('../message');
let { isFunction, getObjectKeysToPrototype } = require('../utility');
class AbstractModule {
name = "AbstractModule"
description = "Base Module That All Other Modules Extend"
command = "abstract_module"
/*
Name of the module used in help documentation and logging.
*/
name = "AbstractModule";
/*
Short description of the module functionality.
*/
description = "Base Module That All Other Modules Extend";
/*
The exported command used to invoke the module directly.
*/
command = "abstract_module";
/*
The default method to call when a command word is not recognized.
*/
defaultCommand = null;
/*
The module should be hidden from help and command dialogs.
*/
hidden = false;
/*
This module should receive all messages, regardless of whether
the module was directly referenced with a command.
*/
canHandleIndirectMessages = false;
constructor(name, description, command) {
this.name = name;
this.description = description;
this.command = command;
this._recognizedCommands = new Map();
}
addRecognizedCommand(command, methodName) {
this._recognizedCommands.set(command, methodName)
}
getRecognizedCommands() {
return this._recognizedCommands.keys();
}
/**
* Default functionality for receiving and processing a message.
*
* Override this if the module needs to do more complicated message processing.
*/
handleMessage(event, room, callback) {
logger.debug("[%s] [%s] [%s]", this.name, room.name, event.event.content.body);
let messageBody = event.event.content.body;
let bodyParts = messageBody.split(' ');
let trigger = bodyParts[0];
let command = bodyParts[1];
var args = [];
if (bodyParts.length > 2) {
args = bodyParts.slice(2);
}
logger.debug("Attempting to call %s with %s", command, args);
let responseMessage = this.processMessage(command, args);
callback(
room,
message.createBasic(this.name + " processed the message")
responseMessage
);
}
help(event, room) {
/*
Call the command method with the args
*/
processMessage(command, ...args) {
if (command in this._recognizedCommands) {
logger.debug("Calling %s with %s", this._recognizedCommands.get(command), args);
return this[this._recognizedCommands.get(command)](...args);
} else {
if (this.defaultCommand != null) {
logger.debug("Attempting to use default command %s", this.defaultCommand);
try {
let newArgs = [command].concat(...args);
logger.debug("Calling %s with %s", this._recognizedCommands.get(this.defaultCommand), newArgs);
return this[this._recognizedCommands.get(this.defaultCommand)](...newArgs);
} catch (e) {
logger.error("Error while calling default command %s %s", this.defaultCommand, e);
return this.cmd_help();
}
} else {
logger.debug("Unrecognized command %s", command);
return this.cmd_help();
}
}
}
/* Basic cmd methods */
/*
return basic help information,.
*/
cmd_help(...args) {
return message.createBasic(this.name + " HELP!");
}
}
let abstractModulePrototype = Object.getPrototypeOf(new AbstractModule('', '', ''));
/*
Initialization of a module.
*/
function init(mod) {
logger.debug("Initializing module %s", mod.name)
let commandMethods = getObjectKeysToPrototype(mod, abstractModulePrototype, (key) => {
return key.startsWith('cmd_') && isFunction(mod[key]);
})
// let commandMethods = objectKeys.filter();
logger.debug("Identified command methods: %s", commandMethods);
commandMethods.forEach((commandMethodName) => {
let command = commandMethodName.substring(4);
mod.addRecognizedCommand(command, commandMethodName);
})
logger.debug("Bound command methods for %s as %s", mod.name, mod.getRecognizedCommands());
}
exports.AbstractModule = AbstractModule
exports.AbstractModule = AbstractModule
exports.initModule = init;

35
bot/module/help.js

@ -12,20 +12,34 @@ class HelpModule extends AbstractModule {
"Provide helpful information about other modules.",
"help"
);
this.commandMap = commandMap;
this._commandMap = commandMap;
this._commandList = Array.from(commandMap.keys()).sort();
this.defaultCommand = 'help';
}
addCommand(command, module) {
logger.debug("Adding help for command %s against module %s", command, module.name);
this.commands.push(command);
_default_help_message() {
let help = `!help <command>`;
for (let command of this._commandList) {
help += "\n!help " + command + " : " + this._commandMap.get(command).description;
}
return help;
}
help() {
let help = `!help <command>`;
for (let command of Array.from(this.commandMap.keys()).sort()) {
help += "\n!help " + command + " : " + this.commandMap.get(command).description;
cmd_help(...args) {
logger.debug("%o", args)
if (args.length < 1) {
return this._default_help_message();
} else {
let command = args[0];
logger.debug("Looking up help for %s from %o", command, this._commandMap);
if (this._commandList.includes(command)) {
return this._commandMap.get(command).cmd_help();
} else {
let help = command + " is an unrecognized module\n";
help += this._default_help_message();
return help;
}
}
return help;
}
}
@ -33,5 +47,4 @@ function create(commandMap) {
return new HelpModule(commandMap);
}
exports.create = create;
exports.module = new HelpModule();
exports.create = create;

34
bot/utility.js

@ -45,11 +45,41 @@ function sleep(ms) {
}
function isString(s) {
return typeof(s) === 'string' || s instanceof String;
return typeof (s) === 'string' || s instanceof String;
}
function isFunction(f) {
return f && {}.toString.call(f) === '[object Function]';
}
/**
* Parse the prototype tree to return all accessible properties till
* reaching a sentinelPrototype.
*
* Optionally provide a filtering function to return only the names that match.
*
* @param {*} initialObj The starting object to derive the from
* @param {*} sentinelPrototype The prototype that represents the end of the line
* @param {*} filterFunc A fioltering function for the return names
*/
function getObjectKeysToPrototype(initialObj, sentinelPrototype, filterFunc = (e) => true) {
let prototypeChain = []
var targetPrototype = initialObj;
while (Object.getPrototypeOf(targetPrototype) && targetPrototype !== sentinelPrototype) {
targetPrototype = Object.getPrototypeOf(targetPrototype);
prototypeChain.push(targetPrototype);
}
// console.log("Prototype chain: %s", prototypeChain);
let completePropertyNames = prototypeChain.map((obj) => {
return Object.getOwnPropertyNames(obj);
})
return [Object.getOwnPropertyNames(initialObj)].concat.apply([], completePropertyNames).filter(filterFunc);
}
exports.getShortestPrefix = getShortestPrefix;
exports.toISODateString = toISODateString;
exports.getBuildInfo = getBuildInfo;
exports.sleep = sleep;
exports.isString = isString;
exports.isString = isString;
exports.isFunction = isFunction;
exports.getObjectKeysToPrototype = getObjectKeysToPrototype;
Loading…
Cancel
Save