UNPKG

tennu

Version:

Tennu - Modular IRC Bot Framework for Node.js

370 lines (305 loc) 10.4 kB
const TlsSocket = require("tls").TLSSocket; const format = require("util").format; const inspect = require("util").inspect; const defaults = require("lodash.defaults"); const mapValues = require("lodash.mapvalues"); const deepClone = function (obj) { if (obj === null) { return null; } else if (Array.isArray(obj)) { return obj.map(deepClone); } else if (typeof obj === "object") { const ret = Object.create(Object.getPrototypeOf(obj)); Object.keys(obj).forEach(function (key) { ret[key] = deepClone(obj[key]); }); return ret; } else { return obj; } }; // Because lodash.defaults doesn't clone. const clonedDefaults = function () { const source = arguments[0]; const templates = Array.prototype.slice.call(arguments, 1); templates.forEach(function (template) { Object.keys(template).forEach(function (key) { if (!source[key]) { source[key] = deepClone(template[key]); } }); }); return source; }; const defaultFactoryConfiguration = { "NetSocket" : require("net").Socket, "IrcSocket" : require("irc-socket"), "Plugins" : require("tennu-plugins"), "Logger": require("./null-logger.js"), }; const defaultClientConfiguration = Object.freeze({ // IrcSocket Config "server": undefined, "port": 6667, "password": undefined, "capabilities": {}, "nicknames": Object.freeze(["tennubot"]), "username": "tennu", "realname": "tennu " + require("../package.json")["version"], "connectOptions": undefined, // Tennu Config "tls": false, "channels": Object.freeze([]), "nickserv": "nickserv", "auth-password": undefined, "plugins": Object.freeze([]), "command-trigger": "!", "disable-help": false }); const loggerMethods = ["debug", "info", "notice", "warn", "error", "crit", "alert", "emerg"]; /** Fields * _config * _socket * _logger * _action * _self * _subscriber * events (_subscriber) * plugins (_plugins) */ const Client = function (config, dependencies) { const client = Object.create(Client.prototype); // Parse the configuration object. client._configObject = config = clonedDefaults({}, config, defaultClientConfiguration); // Allow dependencies to either be constructors or objects. dependencies = mapValues(dependencies, function (dep) { if (typeof dep === "object") { return function () { return dep; }; } else { return dep; } }); di = defaults({}, dependencies || {}, defaultFactoryConfiguration); // Create a logger. // Default logger is a bunch of NOOPs. client._logger = new di.Logger(); var missingLoggerMethods = loggerMethods.filter(function (method) { return typeof client._logger[method] !== "function"; }); if (missingLoggerMethods.length !== 0) { throw new Error(format("Logger passed to tennu.Client is missing the following methods: %s", inspect(missingLoggerMethods))); } var netSocket = new di.NetSocket(); if (config.tls) { netSocket = new TlsSocket(netSocket, { rejectUnauthorized: false, isServer: false }); } // NOTE(Havvy): The config plugin has to be loaded before the IRC socket is started. // It also has to be loaded so that we have a value for client._config // so that client.config() works for other plugins. client._plugins = new di.Plugins("tennu", client); client._plugins.use(["config"], __dirname); client._config = client.getPlugin("config"); // The socket reads and sends messages from/to the IRC server. // TODO(Havvy): Put the Socket into a plugin. client._socket = new di.IrcSocket(config, netSocket); // Configure the plugin system. client.note("Tennu", "Loading base plugins"); var basePlugins = [ "subscriber", "messages", "commands", "server", "action", "ctcp", "help", "user", "channel", "startup", "self" ]; if (config.daemon === "twitch" || config.daemon === "irc2") { // The channel plugin requires certain capabilities that these networks cannot provide. basePlugins = basePlugins.filter(function (plugin) { return plugin !== "channel"; }); } client._plugins.use(basePlugins, __dirname); client.note("Tennu", "Loading your plugins"); client._plugins.use(config.plugins || [], process.cwd()); // Grab a reference to various plugin exports // so that the client can delegate the actions to it. // TODO(Havvy): Have a static hook for doing this. // Note how "config" has to be loaded specially above. client._action = client.getPlugin("action"); client._self = client.getPlugin("self"); client._subscriber = client.getPlugin("subscriber"); client.plugins = client._plugins; client.connected = false; client.note("Tennu", "Client created."); return client; }; // implements Runnable ;) const connect = function () { if (this.connected) { this.warn("Tennu", "Attempted to connect already connected client."); return; } this._socket.connect(); this.connected = true; this.note("Tennu", "Connected"); return this; } Client.prototype.connect = connect; Client.prototype.start = connect; const disconnect = function () { if (!this.connected) { this.warn('Tennu', 'Attempted to disconnect already disconnected client.'); return this; } this._socket.end(); this.connected = false; this.note('Tennu', 'Disconnected'); return this; }; Client.prototype.disconnect = disconnect; Client.prototype.end = disconnect; // implements Config Plugin Client.prototype.config = function () { return this._config.get.apply(this._config, arguments); }; // implements IRC Output Socket Client.prototype.act = function () { return this._action.act.apply(this._action, arguments); }; // Deprecated(4.2.x) Client.prototype.ctcp = function () { return this._action.ctcp.apply(this._action, arguments); }; Client.prototype.ctcpRequest = function () { return this._action.ctcpRequest.apply(this._action, arguments); }; Client.prototype.ctcpRespond = function () { return this._action.ctcpRespond.apply(this._action, arguments); }; Client.prototype.join = function () { return this._action.join.apply(this._action, arguments); }; Client.prototype.kick = function () { return this._action.kick.apply(this._action, arguments); }; Client.prototype.mode = function () { return this._action.mode.apply(this._action, arguments); }; Client.prototype.nick = function () { return this._action.nick.apply(this._action, arguments); }; Client.prototype.notice = function () { return this._action.notice.apply(this._action, arguments); }; Client.prototype.part = function () { return this._action.part.apply(this._action, arguments); }; Client.prototype.quit = function () { return this._action.quit.apply(this._action, arguments); }; Client.prototype.say = function () { return this._action.say.apply(this._action, arguments); }; Client.prototype.respond = function () { return this._action.respond.apply(this._action, arguments); }; Client.prototype.userhost = function () { return this._action.userhost.apply(this._action, arguments); }; Client.prototype.who = function () { return this._action.who.apply(this._action, arguments); }; Client.prototype.whois = function () { return this._action.whois.apply(this._action, arguments); }; Client.prototype.raw = function () { return this._action.raw.apply(this._action, arguments); }; Client.prototype.rawf = function () { return this._action.rawf.apply(this._action, arguments); }; // implements Self Plugin Exports Client.prototype.nickname = function () { return this._self.nickname.apply(this._self, arguments); }; // implements Subscriber Client.prototype.on = function () { this._subscriber.on.apply(this._subscriber, arguments); return this; }; Client.prototype.once = function () { this._subscriber.once.apply(this._subscriber, arguments); return this; }; Client.prototype.off = function () { this._subscriber.off.apply(this._subscriber, arguments); return this; }; // implements PluginSystem Client.prototype.use = function () { this._plugins.use.apply(this._plugins, arguments); return this; }; Client.prototype.getPlugin = function () { return this._plugins.getPlugin.apply(this._plugins, arguments); }; Client.prototype.getRole = function () { return this._plugins.getRole.apply(this._plugins, arguments); }; Client.prototype.initializePlugin = function () { this._plugins.initialize.apply(this._plugins, arguments); return this; }; Client.prototype.isPluginInitializable = function () { return this._plugins.isInitializable.apply(this._plugins, arguments); }; Client.prototype.addHook = function () { this._plugins.addHook.apply(this._plugins, arguments); return this; }; // implements Logger Client.prototype.debug = function () { this._logger.debug.apply(this._logger, arguments); return this; }; Client.prototype.info = function () { this._logger.info.apply(this._logger, arguments); return this; }; Client.prototype.note = function () { this._logger.notice.apply(this._logger, arguments); return this; }; Client.prototype.warn = function () { this._logger.warn.apply(this._logger, arguments); return this; }; Client.prototype.error = function () { this._logger.error.apply(this._logger, arguments); return this; }; Client.prototype.crit = function () { this._logger.crit.apply(this._logger, arguments); return this; }; Client.prototype.alert = function () { this._logger.alert.apply(this._logger, arguments); return this; }; Client.prototype.emerg = function () { this._logger.emerg.apply(this._logger, arguments); return this; }; Client.prototype.log = function (level) { const args = Array.prototype.slice.call(arguments, 1); this._logger[level].apply(this._logger, args); return this; }; // Export the factory. module.exports = Client;