UNPKG

tennu

Version:

Tennu - Modular IRC Bot Framework for Node.js

225 lines (195 loc) 8.9 kB
const format = require("util").format; const Promise = require("bluebird"); const ResultCtors = require("r-result"); const Ok = ResultCtors.Ok; const Fail = ResultCtors.Fail; const chunk = require("chunk"); const memoizeSupport = typeof WeakMap === "function" && typeof Map === "function"; module.exports = function (client, rawf, emitter) { const memoizeMap = function () { if (memoizeSupport) { return new WeakMap(); } else { return null; } }(); // opts takes the following values: // * multiple: true || undefined - Used internally. Don't modify. // * memoizeOver: Object - An object to memoize over. If called in the future with the same object, // won't try the whois again, but reuse the same value. // For this function, you probably want to memoize over the privmsg that triggered // the want to /whois, so you don't whois more than once per message/command. // TODO(Havvy) Figure out what the `server` parameter does. // TODO(Havvy) Move the multiple case to `whoisAll` or return a Promise<[Result<WhoisInfo, WhoisFailureMessage>], Error>`? return function whois (nickname, server, opts) { if (memoizeSupport && opts && opts.memoizeOver) { const nicknameMap = memoizeMap.get(opts.memoizeOver); if (nicknameMap) { const memoizedPromise = nicknameMap.get(nickname); if (memoizedPromise) { return memoizedPromise; } } } // NOTE(Havvy): The logic in this block has two purposes. // 1) Check if input is of the correct types, throw an error if not. // 2) Determine whether multiple WHOISes. // If multiple, fire and forget. // If single, return a promise. if (Array.isArray(nickname)) { const nicknames = nickname; // rename for clarity if (nicknames.some(function (nickname) { return typeof nickname !== "string"; })) { throw new Error("Whois command takes either a string (a single nick) or an array (of string nicks)"); } if (nicknames.length > 15) { chunk(nicknames, 15) .map(function (nicknames) { return nicknames.join(','); }) .map(function (nicknames) {whois(nicknames, server, true); }); } } else if (typeof nickname === "string" && nickname.indexOf(",") !== -1) { multiple = true; } else if (typeof nickname === "string") { // NOOP } else { throw new Error("Whois command takes either a string (a single nick) or an array (of string nicks)"); } if (server || (opts && opts.multiple)) { if (server) { rawf("WHOIS %s %s", server, nickname); } else { rawf("WHOIS %s", nickname); } return; } const whoisPromise = new Promise(function (resolve, reject) { const whoisInfo = { nickname: nickname, username: undefined, hostname: undefined, hostmask: undefined, realname: undefined, identified: false, identifiedas: undefined, server: undefined, serverInfo: undefined, idleSeconds: undefined, loginTimestamp: undefined, secureConnection: false, isBot: false, isHelpop: false, isOper: false }; function formatn (formatstr) { return format(formatstr, nickname); } function forNick (handler) { return function (message) { if (message.nickname === nickname) { return handler(message); } }; } const whoisUserHandler = forNick(function (message) { whoisInfo.username = message.username; whoisInfo.hostname = message.hostname; whoisInfo.hostmask = message.hostmask; whoisInfo.realname = message.realname; }); const whoisRegisteredNickHandler = forNick(function (message) { client.debug("PluginAction", format("%s is identified as %s", nickname, nickname)); whoisInfo.identified = true; whoisInfo.identifiedas = nickname; }); const whoisLoggedInHandler = forNick(function (message) { client.debug("PluginAction", format("%s is identified as %s", nickname, message.identifiedas)); whoisInfo.identified = true; whoisInfo.identifiedas = message.identifiedas; }); const whoisServerHandler = forNick(function (message) { whoisInfo.server = message.server; whoisInfo.serverInfo = message.serverInfo; }); const whoisIdleHandler = forNick(function (message) { whoisInfo.idleSeconds = message.seconds; whoisInfo.loginTimestamp = message.since; }); const whoisChannelsHandler = forNick(function (message) { whoisInfo.channels = message.channels; }); const whoisHostHandler = forNick(function (message) { whoisInfo.ip = message.ip; }); const whoisSecureHandler = forNick(function (message) { whoisInfo.secureConnection = true; }); const whoisBotHandler = forNick(function (message) { whoisInfo.isBot = true; }); const whoisOperatorHandler = forNick(function (message) { whoisInfo.isOper = true; }); const whoisHelpopHandler = forNick(function (message) { whoisInfo.isHelpop = true; }); const endOfWhoisHandler = forNick(function (message) { unsubscribe(); resolve(Ok(whoisInfo)); }); const errUnknownCommandHandler = function (message) { if (message.unknownCommand === "whois") { unsubscribe(); resolve(Fail(message)); } }; const errDefaultHandler = forNick(function (message) { unsubscribe(); resolve(Fail(message)); }); // TODO(Havvy): Determine which whois messages are being ignored. var handlers = { "rpl_whoisuser": whoisUserHandler, "rpl_whoisregnick": whoisRegisteredNickHandler, "rpl_whoisloggedin": whoisLoggedInHandler, "rpl_whoisserver": whoisServerHandler, "rpl_whoisidle": whoisIdleHandler, "rpl_whoischannels": whoisChannelsHandler, "rpl_whoishost": whoisHostHandler, "rpl_whoissecure": whoisSecureHandler, "rpl_whoisbot": whoisBotHandler, "rpl_whoisoperator": whoisOperatorHandler, "rpl_whoishelpop": whoisHelpopHandler, "rpl_endofwhois": endOfWhoisHandler, "err_unknowncommand": errUnknownCommandHandler, "err_nosuchnick": errDefaultHandler }; client.debug("PluginAction", formatn("Registering handlers for WHOIS %s.")); client.on(handlers); // Assume failure in an hour. var timeout = setTimeout(timeout = function () { unsubscribe(); client.error(formatn("Attempt to whois %s failed.")); reject(new Error(formatn("Whois attempt timed out for nickname %s."))); }, 3600 * 1000); var unsubscribe = function () { client.debug("PluginAction", formatn("Unsubscribing events for WHOIS %s")); client.off(handlers); clearTimeout(timeout); }; client.debug("PluginAction", formatn("Attempting to whois %s.")); rawf("WHOIS %s", nickname); }) .tap(function (result) { emitter.emit("join", result); }); if (memoizeSupport && (opts && opts.memoizeOver)) { if (memoizeMap.has(opts.memoizeOver)) { memoizeMap.get(opts.memoizeOver).set(nickname, whoisPromise); } else { const newNicknameMap = new Map(); newNicknameMap.set(nickname, whoisPromise); memoizeMap.set(opts.memoizeOver, newNicknameMap); } } return whoisPromise; }; };