UNPKG

hermes

Version:
250 lines (213 loc) 6.24 kB
var assert = require('assert'); var echo = require('./echo'); var format = require('util').format; var help = require('./help'); var memory = require('./memory'); var noop = function(){}; var type = require('component-type'); var Emitter = require('events').EventEmitter; var inherit = require('util').inherits; /** * Expose `Robot`. */ module.exports = Robot; /** * Initialize a new `Robot` instance with a `name`. * * @param {Object} name */ function Robot(name){ if (!(this instanceof Robot)) return new Robot(name); this.setMaxListeners(Infinity); this.on('error', noop); this.name(name || 'Hermes'); this.template('@%s '); this.use(memory()); this.use(help()); this.use(echo()); } /** * Inherit from `Emitter`. */ inherit(Robot, Emitter); var on = Robot.prototype.on; var once = Robot.prototype.once; var off = Robot.prototype.off = Robot.prototype.removeListener; /** * Use a `plugin` function. * * @param {Function} plugin * @return {Robot} */ Robot.prototype.use = function(plugin){ plugin(this); return this; }; /** * Get or set the robot's `name`. * * @param {Object} name * @return {Robot} */ Robot.prototype.name = function(name){ if (!arguments.length) return this._name; assert('string' == type(name), 'The robot\'s name must be a string.'); this._name = name; this.nickname(nickify(name)); this.emit('name', name); return this; }; /** * Get or set the robot's `nickname`. * * @param {Object} nickname * @return {Robot} */ Robot.prototype.nickname = function(nickname){ if (!arguments.length) return this._nickname; assert('string' == type(nickname), 'The robot\'s nickname must be a string.'); this._nickname = nickname; this.emit('nickname', nickname); return this; }; /** * Get or set the way the robot is mentioned with a substitution `template`. * * @param {String} template * @return {Robot} */ Robot.prototype.template = function(template){ if (!arguments.length) return this._template; assert('string' == type(template), 'The mention template must be a string.'); this._template = template; this.emit('template', template); return this; }; /** * Format a `nickname` into a mention. * * @param {String} nickname * @return {String} */ Robot.prototype.mention = function(nickname){ if (!nickname) nickname = this.nickname(); var template = this.template(); return format(template, nickname); }; /** * Hear a `string` with optional `context` and process it. * * @param {String} message * @param {Object} context (optional) * @property {String} user * @property {String} room * @return {Robot} */ Robot.prototype.hear = function(message, context){ context = context || {}; assert('string' == type(message), 'You must pass a message string.'); assert('object' == type(context), 'The message\'s context must be an object.'); var mention = this.mention(); var regex = new RegExp('^' + mention + '(.*)', 'i'); var match = message.match(regex); this.emit('hear', message, context); if (match) this.emit('mention', match[1], context); return this; }; /** * Wrap `#on` to optionally filter messages by a `regex` and/or `context`. * * @param {String} event * @param {RegExp or String} regex (optional) * @param {Object} context (optional) * @property {String} user * @property {String} room * @param {Function} fn * @return {Robot} */ Robot.prototype.on = function(event, regex, context, fn){ if ('hear' != event && 'mention' != event) return on.call(this, event, regex); if ('function' == type(regex)) fn = regex, context = null, regex = null; if ('function' == type(context)) fn = context, context = null; if ('object' == type(regex)) context = regex, regex = null; if ('string' == type(regex)) regex = new RegExp(regex); regex = regex || /.*/; context = context || {}; var robot = this; on.call(this, event, function(message, ctx){ if (!has(ctx, context)) return; var res = message.match(regex); if (!res) return; res.message = message; res.context = ctx; if (ctx.user) res.user = robot.user(ctx.user); if (ctx.room) res.room = robot.room(ctx.room); bind(res, robot); fn(res, robot); }); return this; }; /** * Wrap `#once` to optionally filter messages by a `regex` and/or `context`. * * @param {String} event * @param {RegExp or String} regex (optional) * @param {Object} context (optional) * @property {String} user * @property {String} room * @param {Function} fn * @return {Robot} */ Robot.prototype.once = function(event, regex, context, fn){ if ('hear' != event && 'mention' != event) return on.call(this, event, regex); var robot = this; return this.on(event, regex, context, function callback(){ robot.off(event, callback); fn.apply(this, arguments); }); }; /** * Convert a `name` into a nickname string. * * @param {String} name * @return {String} */ function nickify(name){ return name.split(' ')[0].toLowerCase(); } /** * Check whether an `object` has all the values of `another` object. * * @param {Object} object * @param {Object} another * @return {Boolean} */ function has(object, another) { for (var key in another) { if (another[key] !== object[key]) return false; } return true; } /** * Bind a `res` object with all of the speaking methods of a `robot`. * * @param {Object} res * @param {Robot} robot */ function bind(res, robot){ var user = res.user; var room = res.room; var ctx = res.context; res.say = function(msg){ robot.say(msg, ctx); }; res.emote = function(msg){ robot.emote(msg, ctx); }; res.reply = function(msg){ robot.reply(user, msg, ctx); }; res.error = function(msg){ robot.error(msg, ctx); }; res.warn = function(msg){ robot.warn(msg, ctx); }; res.info = function(msg){ robot.info(msg, ctx); }; res.success = function(msg){ robot.success(msg, ctx); }; res.topic = function(topic){ robot.topic(room, topic); }; res.on = function(event, regexp, fn){ robot.on(event, regexp, ctx, fn); }; res.once = function(event, regexp, fn){ robot.once(event, regexp, ctx, fn); }; res.off = function(event, fn){ robot.off(event, fn); }; res.hear = function(msg){ robot.hear(msg, ctx); }; }