Merge release v1.1.0 to master
#10
Manually merged
warricksothr
merged 26 commits from develop
into master
5 years ago
23 changed files with 1209 additions and 120 deletions
-
8.dockerignore
-
11.gitignore
-
16Dockerfile
-
27README.md
-
140bot/bot.js
-
28bot/config.js
-
147bot/engine.js
-
30bot/logging.js
-
10bot/message.js
-
176bot/module/abstract.js
-
19bot/module/admin.js
-
46bot/module/giphy.js
-
50bot/module/help.js
-
15bot/module/index.js
-
85bot/utility.js
-
4data/giphy-config.json.example
-
9entrypoint.sh
-
12index.js
-
288package-lock.json
-
7package.json
-
178pipeline.yml
-
2scripts/get_tag.sh
-
21scripts/run_development_docker.sh
@ -1 +1,7 @@ |
|||
data/config.json |
|||
node_modules/ |
|||
log/ |
|||
*.swp |
|||
pipeline.yml |
|||
README.md |
|||
CONTRIBUTING.md |
|||
test/ |
@ -1,6 +1,16 @@ |
|||
FROM node:12.14-stretch |
|||
|
|||
COPY . /opt/baphomet |
|||
RUN npm install |
|||
|
|||
ENV NODE_ENV=production |
|||
ENV LOG_LEVEL=warn |
|||
|
|||
RUN mkdir /opt/baphomet/log |
|||
|
|||
RUN cd /opt/baphomet \ |
|||
&& npm install --only=prod \ |
|||
&& chmod +x entrypoint.sh |
|||
|
|||
WORKDIR /opt/baphomet |
|||
ENTRYPOINT entrypoint.sh |
|||
CMD run |
|||
ENTRYPOINT [ "./entrypoint.sh" ] |
|||
CMD [ "run" ] |
@ -1,62 +1,108 @@ |
|||
let sdk = require("matrix-js-sdk"); |
|||
let message = require("./message.js"); |
|||
let sdk = require('matrix-js-sdk'); |
|||
let message = require('./message'); |
|||
let utility = require('./utility'); |
|||
let { logger } = require('./logging'); |
|||
|
|||
class Bot { |
|||
constructor(config) { |
|||
this.config = config |
|||
constructor(config, buildInfo) { |
|||
this.config = config; |
|||
this.buildInfo = buildInfo; |
|||
this.connected = false; |
|||
} |
|||
} |
|||
|
|||
function create(configFile) { |
|||
let config = require(configFile); |
|||
console.log("Running with config:"); |
|||
console.log(config); |
|||
return new Bot(config); |
|||
} |
|||
/** |
|||
* Initialize the bot connection |
|||
*/ |
|||
init(messageCallback) { |
|||
logger.info("Creating Matrix Client") |
|||
this.client = sdk.createClient({ |
|||
baseUrl: this.config.baseUrl, |
|||
accessToken: this.config.accessToken, |
|||
userId: this.config.userId |
|||
}); |
|||
|
|||
function init(bot) { |
|||
console.log("Creating Matrix Client") |
|||
bot.client = sdk.createClient({ |
|||
baseUrl: bot.config.baseUrl, |
|||
accessToken: bot.config.accessToken, |
|||
userId: bot.config.userId |
|||
}); |
|||
|
|||
function sendClientStatusUpdate() { |
|||
bot.config.statusRooms.forEach(roomId => { |
|||
console.log("Notifying %s", roomId); |
|||
bot.client.sendMessage(roomId, message.createBasic("Started!")).done(function () { |
|||
console.log("Notified %s", roomId); |
|||
}) |
|||
this.client.on("sync", async (state, previousState, data) => { |
|||
switch (state) { |
|||
case "PREPARED": |
|||
this.connected = true; |
|||
await this.sendStatusStartup(); |
|||
this.client.getJoinedRooms() |
|||
.done((rooms) => { |
|||
logger.info("Connected to: %o", rooms) |
|||
}); |
|||
break; |
|||
case "SYNCING": |
|||
logger.debug("Syncing") |
|||
break; |
|||
case "RECONNECTING": |
|||
logger.debug("Reconnecting"); |
|||
break; |
|||
default: |
|||
logger.error("Unexpected sync state: %s", state); |
|||
process.exit(1); |
|||
} |
|||
}); |
|||
|
|||
this.client.on("RoomMember.membership", (event, member) => { |
|||
if (member.membership === "invite" |
|||
&& this.config.admin.indexOf(ember.userId) >= 0) { |
|||
this.client.joinRoom(member.roomId).done(() => { |
|||
logger.info("Auto-joined %s", member.roomId); |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
this.client.on("Room.timeline", messageCallback); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
// Prep the bot
|
|||
bot.client.on("sync", function (state, previousState, data) { |
|||
switch (state) { |
|||
case "PREPARED": |
|||
sendClientStatusUpdate(); |
|||
break; |
|||
} |
|||
}); |
|||
|
|||
// auto join rooms that an admin user has invited the bot to
|
|||
bot.client.on("RoomMember.membership", function (event, member) { |
|||
if (member.membership === "invite" |
|||
&& bot.config.admin.indexOf(ember.userId) >= 0) { |
|||
bot.client.joinRoom(member.roomId).done(function () { |
|||
console.log("Auto-joined %s", member.roomId); |
|||
async connect() { |
|||
// logger.info("Initializing Crypto");
|
|||
// await bot.client.initCrypto();
|
|||
logger.info("Starting Matrix SDK Client"); |
|||
await this.client.startClient(); |
|||
} |
|||
|
|||
sendStatusStartup() { |
|||
let promises = [Promise.resolve(true)] |
|||
if (this.connected) { |
|||
this.config.statusRooms.forEach(roomId => { |
|||
logger.debug("Notifying %s of startup", roomId); |
|||
promises.push(this.client.sendMessage( |
|||
roomId, message.createBasic("Started with version: " + this.buildInfo, message.types.NOTICE) |
|||
).then(() => { |
|||
logger.debug("Notified %s of startup", roomId); |
|||
})); |
|||
}); |
|||
} else { |
|||
logger.warn("Attempting to send startup message while disconnected"); |
|||
} |
|||
}); |
|||
return Promise.all(promises); |
|||
} |
|||
|
|||
return bot; |
|||
sendStatusShutdown() { |
|||
let promises = [Promise.resolve(true)] |
|||
if (this.connected) { |
|||
this.config.statusRooms.forEach(roomId => { |
|||
logger.debug("Notifying %s of shutdown", roomId); |
|||
promises.push(this.client.sendMessage( |
|||
roomId, message.createBasic("Shutting down", message.types.NOTICE) |
|||
).then(() => { |
|||
logger.debug("Notified %s of shutdown", roomId); |
|||
})); |
|||
}); |
|||
} else { |
|||
logger.warn("Attempting to send shutdown message while disconnected"); |
|||
} |
|||
return Promise.all(promises); |
|||
} |
|||
} |
|||
|
|||
function run(bot) { |
|||
bot.client.startClient() |
|||
function create(config) { |
|||
let buildInfo = utility.getBuildInfo("../build.info") |
|||
logger.info("Running version: %s", buildInfo); |
|||
return new Bot(config, buildInfo); |
|||
} |
|||
|
|||
exports.create = create; |
|||
exports.init = init; |
|||
exports.run = run; |
|||
exports.create = create; |
@ -0,0 +1,28 @@ |
|||
let fs = require('fs'); |
|||
let { logger } = require('./logging'); |
|||
|
|||
let loadedConfigs = new Map(); |
|||
|
|||
function sanitizeConfig(config, fields=[]) { |
|||
let clonedConfig = { ...config }; |
|||
fields.forEach((field) => { |
|||
clonedConfig[field] = '******' |
|||
}) |
|||
return clonedConfig; |
|||
} |
|||
|
|||
function getConfig(configFile, sanitizedFields=[]) { |
|||
if (!loadedConfigs.has(configFile)) { |
|||
logger.info("Reading config: %s", configFile); |
|||
let rawConfigData = fs.readFileSync(configFile); |
|||
let config = JSON.parse(rawConfigData); |
|||
logger.info("Loaded config: %s", configFile); |
|||
logger.debug("%o", sanitizeConfig(config, sanitizedFields)); |
|||
loadedConfigs.set(configFile, config); |
|||
return config; |
|||
} else { |
|||
return loadedConfigs.get(configFile); |
|||
} |
|||
} |
|||
|
|||
exports.getConfig = getConfig |
@ -0,0 +1,147 @@ |
|||
let { logger } = require('./logging'); |
|||
let { modules } = require('./module/index'); |
|||
let trie = require('trie-prefix-tree'); |
|||
let { getShortestPrefix } = require('./utility'); |
|||
let help = require('./module/help'); |
|||
let message = require('./message'); |
|||
let utility = require('./utility'); |
|||
let { initModule } = require('./module/abstract'); |
|||
|
|||
let sentinelValue = '!'; |
|||
|
|||
class Engine { |
|||
constructor(config, bot, modules) { |
|||
this.config = config; |
|||
this.bot = bot; |
|||
this.modules = modules; |
|||
this.moduleMap = new Map(); |
|||
this.commands = []; |
|||
this.commandMap = new Map(); |
|||
this.commandRadixTree = trie([]); |
|||
} |
|||
|
|||
initModules() { |
|||
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((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() |
|||
|
|||
logger.info("Bound modules to keywords: %o", this.moduleMap); |
|||
} |
|||
|
|||
init() { |
|||
logger.info("Initializing modules"); |
|||
this.initModules(); |
|||
|
|||
/* Bind Message Parsing */ |
|||
let engine = this; |
|||
let handleMessages = (event, room, toStartOfTimeline) => { |
|||
/* Don't process messages from self */ |
|||
if (event.sender.userId !== this.config.userId) { |
|||
/* don't process messages that aren't of type m.room.message */ |
|||
if (event.getType() !== "m.room.message") { |
|||
logger.debug("Recieved message of type: %s", event.getType()); |
|||
return; |
|||
} else { |
|||
let messageBody = event.event.content.body; |
|||
logger.debug("[%s] %s", room.name, messageBody); |
|||
if (messageBody.indexOf(sentinelValue) === 0) { |
|||
let command = messageBody.split(' ')[0].substring(1); |
|||
if (engine.commandMap.has(command)) { |
|||
engine.commandMap.get(command).handleMessage(event, room, sendResponseMessageCallback(engine.bot)); |
|||
} else { |
|||
let responseMessage = "The following commands are recognized" |
|||
responseMessage += "\n" + engine.commands.join(", ") |
|||
responseMessage += "\nAdditional information can be discovered with !help <command>" |
|||
sendResponseMessage(engine.bot, room, responseMessage); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
this.bot.init(handleMessages); |
|||
|
|||
/* Capture Exit Conditions */ |
|||
|
|||
["SIGINT", "SIGTERM"].forEach((signature) => { |
|||
process.on(signature, async () => { |
|||
await this.bot.sendStatusShutdown() |
|||
.then(() => { |
|||
logger.info("Gracefully stopping Matrix SDK Client") |
|||
this.bot.client.stopClient(); |
|||
}); |
|||
process.exit(0); |
|||
}); |
|||
}); |
|||
|
|||
process.on('exit', () => { |
|||
logger.info("Shutting Down"); |
|||
}); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
run() { |
|||
this.bot.connect(); |
|||
return this; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Handle the callback sending messages via the bot |
|||
* |
|||
* @param {*} bot |
|||
* @param {*} room |
|||
* @param {*} responseMessage |
|||
*/ |
|||
function sendResponseMessage(bot, room, responseMessage) { |
|||
logger.debug("Responding to room: %s with %o", room.roomId, responseMessage); |
|||
Promise.resolve(responseMessage).then((promisedMessage) => { |
|||
logger.debug("Sending message: %s", promisedMessage); |
|||
if (promisedMessage instanceof Object) { |
|||
bot.client.sendMessage(room.roomId, promisedMessage); |
|||
} else if (utility.isString(promisedMessage)) { |
|||
bot.client.sendMessage(room.roomId, message.createBasic(promisedMessage)); |
|||
} else { |
|||
logger.error("Unable to process response message: %s", promisedMessage); |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* Wrapper to produce a callback function that can be passed to the modules |
|||
* |
|||
* @param {*} bot |
|||
*/ |
|||
function sendResponseMessageCallback(bot) { |
|||
return (room, responseMessage) => { |
|||
sendResponseMessage(bot, room, responseMessage); |
|||
} |
|||
} |
|||
|
|||
function create(config, bot) { |
|||
return new Engine(config, bot, modules) |
|||
} |
|||
|
|||
exports.create = create; |
@ -0,0 +1,30 @@ |
|||
let winston = require('winston'); |
|||
|
|||
let logger = winston.createLogger({ |
|||
level: 'info', |
|||
format: winston.format.combine( |
|||
winston.format.timestamp(), |
|||
winston.format.splat(), |
|||
winston.format.json() |
|||
), |
|||
defaultMeta: { service: 'baphomet-js' }, |
|||
transports: [ |
|||
new winston.transports.File({ filename: 'log/error.log', level: 'error' }), |
|||
new winston.transports.File({ filename: 'log/combined.log' }) |
|||
] |
|||
}); |
|||
|
|||
if (process.env.NODE_ENV !== 'production') { |
|||
logger.add(new winston.transports.Console({ |
|||
format: winston.format.combine( |
|||
winston.format.simple() |
|||
) |
|||
})); |
|||
} |
|||
|
|||
if ('LOG_LEVEL' in process.env) { |
|||
logger.info('LOG_LEVEL:', process.env.LOG_LEVEL) |
|||
logger.level = process.env.LOG_LEVEL |
|||
} |
|||
|
|||
exports.logger = logger; |
@ -1,8 +1,14 @@ |
|||
function createBasicMessage(body) { |
|||
let messageTypes = { |
|||
TEXT: 'm.text', |
|||
NOTICE: 'm.notice' |
|||
} |
|||
|
|||
function createBasicMessage(body, msgtype=messageTypes.TEXT) { |
|||
return { |
|||
"body": body, |
|||
"msgtype": "m.text" |
|||
"msgtype": msgtype |
|||
} |
|||
} |
|||
|
|||
exports.types = messageTypes; |
|||
exports.createBasic = createBasicMessage; |
@ -0,0 +1,176 @@ |
|||
/** |
|||
* Base module that all modules extend |
|||
*/ |
|||
|
|||
let { logger } = require('../logging'); |
|||
let message = require('../message'); |
|||
let { isFunction, getObjectKeysToPrototype } = require('../utility'); |
|||
let { getConfig } = require('../config'); |
|||
|
|||
class AbstractModule { |
|||
/* |
|||
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"; |
|||
|
|||
/* |
|||
A helpful multiline string that defines the module usage |
|||
*/ |
|||
helpAndUsage = `Example: !abstract_module <command>
|
|||
!abstract_module <boo> : scares people`
|
|||
|
|||
/* |
|||
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; |
|||
|
|||
/* |
|||
Indicates if the module requires a readable config file. |
|||
*/ |
|||
needConfig = false; |
|||
|
|||
/* internal */ |
|||
|
|||
/* |
|||
The loaded config file, if it exists. |
|||
*/ |
|||
_config = null; |
|||
|
|||
constructor(name, description, command) { |
|||
this.name = name; |
|||
this.description = description; |
|||
this.command = command; |
|||
this._recognizedCommands = []; |
|||
this._recognizedCommandMap = new Map(); |
|||
} |
|||
|
|||
addRecognizedCommand(command, methodName) { |
|||
this._recognizedCommands.push(command); |
|||
this._recognizedCommandMap.set(command, methodName); |
|||
} |
|||
|
|||
getRecognizedCommands() { |
|||
return this._recognizedCommandMap.keys(); |
|||
} |
|||
|
|||
getConfigFilePath() { |
|||
return process.env.NODE_PATH + '/data/' + this.name.toLowerCase().replace(' ', '_') + '-config.json'; |
|||
} |
|||
|
|||
getConfigSensitiveFields() { |
|||
return []; |
|||
} |
|||
|
|||
/** |
|||
* 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, |
|||
responseMessage |
|||
); |
|||
} |
|||
|
|||
/* |
|||
Call the command method with the args |
|||
*/ |
|||
processMessage(command, ...args) { |
|||
if (this._recognizedCommands.includes(command)) { |
|||
logger.debug("Calling %s with %s", this._recognizedCommandMap.get(command), args); |
|||
return this[this._recognizedCommandMap.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._recognizedCommandMap.get(this.defaultCommand), newArgs); |
|||
return this[this._recognizedCommandMap.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.helpAndUsage); |
|||
} |
|||
} |
|||
|
|||
let abstractModulePrototype = Object.getPrototypeOf(new AbstractModule('', '', '')); |
|||
|
|||
/* |
|||
Initialization of a module. |
|||
*/ |
|||
function init(mod) { |
|||
logger.debug("Initializing module %s", mod.name); |
|||
if (mod.needConfig) { |
|||
logger.debug("Loading config file %s", mod.getConfigFilePath()); |
|||
try { |
|||
mod._config = getConfig(mod.getConfigFilePath(), mod.getConfigSensitiveFields()); |
|||
} catch (e) { |
|||
logger.error("Module %s needs a valid config file at %s", mod.name, mod.getConfigFilePath()); |
|||
process.exit(1); |
|||
} |
|||
} |
|||
logger.debug("Detecting command methods."); |
|||
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.initModule = init; |
@ -0,0 +1,19 @@ |
|||
/** |
|||
* Administration module |
|||
*/ |
|||
|
|||
let { AbstractModule } = require('./abstract'); |
|||
|
|||
class AdminModule extends AbstractModule { |
|||
constructor() { |
|||
super( |
|||
"Administration", |
|||
"Support administration tasks", |
|||
"admin" |
|||
); |
|||
this.helpAndUsage = `Usage: admin <command>
|
|||
...`;
|
|||
} |
|||
} |
|||
|
|||
exports.module = new AdminModule(); |
@ -0,0 +1,46 @@ |
|||
/** |
|||
* Giphy module |
|||
*/ |
|||
|
|||
let { AbstractModule } = require('./abstract'); |
|||
let axios = require('axios'); |
|||
let { logger } = require('../logging'); |
|||
|
|||
class GiphyModule extends AbstractModule { |
|||
constructor() { |
|||
super( |
|||
"Giphy", |
|||
"Insert Giphy Links/Media", |
|||
"giphy" |
|||
); |
|||
this.helpAndUsage = `Usage: !giphy itsworking
|
|||
...`;
|
|||
this.needConfig = true; |
|||
this.defaultCommand = 'search'; |
|||
} |
|||
|
|||
getConfigSensitiveFields() { |
|||
return ["apiKey"]; |
|||
} |
|||
|
|||
getGiphySearch(term) { |
|||
let url = this._config.endpoint + '/gifs/search?api_key=' + this._config.apiKey + '&q=' + term + '&limit=1'; |
|||
logger.debug("Requesting: %s", url.replace(this._config.apiKey, '******')); |
|||
return axios.get(url); |
|||
} |
|||
|
|||
/** |
|||
* Return the top item for the search terms. |
|||
* |
|||
* @param {...any} args |
|||
*/ |
|||
cmd_search(...args) { |
|||
return this.getGiphySearch(args[0]) |
|||
.then((response) => { |
|||
// logger.debug("Giphy response: %o", response.data.data[0].url);
|
|||
return response.data.data[0].embed_url; |
|||
}) |
|||
} |
|||
} |
|||
|
|||
exports.module = new GiphyModule(); |
@ -0,0 +1,50 @@ |
|||
/** |
|||
* Help module |
|||
*/ |
|||
|
|||
let { AbstractModule } = require('./abstract'); |
|||
let { logger } = require('../logging'); |
|||
|
|||
class HelpModule extends AbstractModule { |
|||
constructor(commandMap) { |
|||
super( |
|||
"Help", |
|||
"Provide helpful information about other modules.", |
|||
"help" |
|||
); |
|||
this._commandMap = commandMap; |
|||
this._commandList = Array.from(commandMap.keys()).sort(); |
|||
this.defaultCommand = 'help'; |
|||
} |
|||
|
|||
_default_help_message() { |
|||
let help = `!help <command>`; |
|||
for (let command of this._commandList) { |
|||
help += "\n!help " + command + " : " + this._commandMap.get(command).description; |
|||
} |
|||
return help; |
|||
} |
|||
|
|||
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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
function create(commandMap) { |
|||
return new HelpModule(commandMap); |
|||
} |
|||
|
|||
exports.create = create; |
@ -0,0 +1,15 @@ |
|||
/** |
|||
* Manage the registered modules |
|||
*/ |
|||
|
|||
let admin = require('./admin'); |
|||
let giphy = require('./giphy') |
|||
|
|||
function getModules() { |
|||
return [ |
|||
admin.module, |
|||
giphy.module |
|||
]; |
|||
} |
|||
|
|||
exports.modules = getModules(); |
@ -0,0 +1,85 @@ |
|||
let fs = require('fs'); |
|||
let { logger } = require('./logging'); |
|||
|
|||
function getShortestPrefix(radixTree, key, sliceSize) { |
|||
let shortKey = key.substring(0, sliceSize); |
|||
let keyCount = radixTree.countPrefix(shortKey); |
|||
if (keyCount < 1) { |
|||
return null; |
|||
} |
|||
if (key.length === sliceSize && radixTree.getPrefix(shortKey).includes(key)) { |
|||
return null; |
|||
} |
|||
if (keyCount === 1) { |
|||
return shortKey; |
|||
} |
|||
return getShortestPrefix(radixTree, key, sliceSize + 1); |
|||
} |
|||
|
|||
function toISODateString(d) { |
|||
function pad(n) { return n < 10 ? '0' + n : n } |
|||
return d.getUTCFullYear() + '-' |
|||
+ pad(d.getUTCMonth() + 1) + '-' |
|||
+ pad(d.getUTCDate()) + 'T' |
|||
+ pad(d.getUTCHours()) + ':' |
|||
+ pad(d.getUTCMinutes()) + ':' |
|||
+ pad(d.getUTCSeconds()) + 'Z' |
|||
} |
|||
|
|||
function getBuildInfo(buildInfoPath) { |
|||
try { |
|||
return fs.readFileSync(buildInfoPath, "utf8"); |
|||
} catch (err) { |
|||
if (err.code === 'ENOENT') { |
|||
return "UNKNOWN_" + toISODateString(new Date()); |
|||
} else { |
|||
logger.error("Unexpected Error!", err); |
|||
} |
|||
} |
|||
} |
|||
|
|||
function sleep(ms) { |
|||
return new Promise(resolve => { |
|||
setTimeout(resolve, ms) |
|||
}) |
|||
} |
|||
|
|||
function isString(s) { |
|||
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.isFunction = isFunction; |
|||
exports.getObjectKeysToPrototype = getObjectKeysToPrototype; |
@ -0,0 +1,4 @@ |
|||
{ |
|||
"endpoint": "api.giphy.com/v1", |
|||
"apiKey": "<Your API Key Here>" |
|||
} |
@ -1,2 +1,10 @@ |
|||
let bot = require('./bot/bot.js'); |
|||
bot.run(bot.init(bot.create("../data/config.json"))); |
|||
let bot = require('./bot/bot'); |
|||
let { getConfig } = require('./bot/config'); |
|||
let engine = require('./bot/engine'); |
|||
|
|||
let config = getConfig(process.env.NODE_PATH + "/data/config.json", ['accessToken']) |
|||
|
|||
engine.create( |
|||
config, |
|||
bot.create(config) |
|||
).init().run(); |
@ -0,0 +1,2 @@ |
|||
#!/usr/bin/env sh |
|||
echo "$(git describe --tags | awk '{split($0,a,"-"); print a[1]}')" |
@ -0,0 +1,21 @@ |
|||
#!/usr/bin/env bash |
|||
|
|||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" |
|||
pushd "${DIR}/.." |
|||
|
|||
CONTAINER_NAME=baphomet-dev |
|||
IMAGE_NAME=baphomet-js |
|||
IMAGE_TAG=dev |
|||
IMAGE_BUILD_DIR=. |
|||
|
|||
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_BUILD_DIR} |
|||
|
|||
docker rm ${CONTAINER_NAME} |
|||
|
|||
docker run -it \ |
|||
-e NODE_ENV=development \ |
|||
-e LOG_LEVEL=debug \ |
|||
--name ${CONTAINER_NAME} \ |
|||
${IMAGE_NAME}:${IMAGE_TAG} |
|||
|
|||
popd |
Write
Preview
Loading…
Cancel
Save
Reference in new issue