From 101e97454c211b6d21d0a8f071cf12c7aae50ab1 Mon Sep 17 00:00:00 2001 From: saltire sable Date: Mon, 11 Sep 2017 16:06:05 -0400 Subject: [PATCH 01/10] Add ESLint config, start updating core bot to ES6 syntax --- .eslintrc.json | 16 + borgil.js | 6 +- bot/bot.js | 70 +- bot/buffers.js | 12 +- bot/log.js | 41 +- bot/transports.js | 32 +- package-lock.json | 2896 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 + 8 files changed, 3002 insertions(+), 74 deletions(-) create mode 100644 .eslintrc.json create mode 100644 package-lock.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..99bccc3 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,16 @@ +{ + "extends": "airbnb-base", + "parserOptions": { + "sourceType": "script" + }, + "rules": { + "brace-style": [2, "stroustrup"], + "consistent-return": 0, + "function-paren-newline": 0, + "indent": [2, 4], + "max-len": [2, 100], + "new-cap": [2, {"capIsNewExceptions": ["Router"]}], + "no-shadow": [2, {"allow": ["err"]}], + "object-curly-spacing": [2, "never"] + } +} diff --git a/borgil.js b/borgil.js index ceacd20..2127d8e 100644 --- a/borgil.js +++ b/borgil.js @@ -1,4 +1,6 @@ -var Bot = require('./bot/bot'); +'use strict'; +const Bot = require('./bot/bot'); -var borgil = new Bot(); + +module.exports = new Bot(); diff --git a/bot/bot.js b/bot/bot.js index 9f38984..a92423d 100644 --- a/bot/bot.js +++ b/bot/bot.js @@ -1,37 +1,37 @@ -var EventEmitter = require('eventemitter2').EventEmitter2; -var util = require('util'); - -var Config = require('./config'); -var Plugin = require('./plugin'); - - -// The bot object. -var Bot = module.exports = function (configfile) { - // Run the event emitter constructor. - EventEmitter.call(this); - - this.config = new Config(configfile); - - this.plugins = {}; - this.memory = {}; - - // Include extra functionality. - require('./log')(this); - require('./transports')(this); - require('./buffers')(this); - - // Activate all plugins mentioned in the plugins section of the config. - for (pluginName in this.config.get('plugins', {})) { - this.log.info('Activating plugin:', pluginName); - try { - var plugin = new Plugin(this, pluginName); - } - catch (err) { - this.log.warn('Error activating plugin %s:', pluginName, err.message); - continue; - } - this.plugins[pluginName] = plugin; +'use strict'; + +const EventEmitter = require('eventemitter2').EventEmitter2; + +const Config = require('./config'); +const Plugin = require('./plugin'); + + +module.exports = class Bot extends EventEmitter { + constructor(configfile) { + super(); + + this.config = new Config(configfile); + + this.plugins = {}; + this.memory = {}; + + // Include extra functionality. + require('./log')(this); + require('./transports')(this); + require('./buffers')(this); + + // Activate all plugins mentioned in the plugins section of the config. + Object.keys(this.config.get('plugins', {})).forEach((pluginName) => { + this.log.info('Activating plugin:', pluginName); + let plugin; + try { + plugin = new Plugin(this, pluginName); + } + catch (err) { + this.log.warn('Error activating plugin %s:', pluginName, err.message); + return; + } + this.plugins[pluginName] = plugin; + }); } }; -// Extend the event emitter class. -util.inherits(Bot, EventEmitter); diff --git a/bot/buffers.js b/bot/buffers.js index 231a7b3..7098369 100644 --- a/bot/buffers.js +++ b/bot/buffers.js @@ -1,11 +1,13 @@ -var default_buffer = 100; +'use strict'; -module.exports = function (bot) { +const defaultBuffer = 100; + +module.exports = function initBuffers(bot) { // Create buffer objects for each client. bot.buffers = {}; // Log each message to a buffer. - bot.on('message', function (transport, msg) { + bot.on('message', (transport, msg) => { // Initialize buffer for this transport and source if necessary. if (!(transport.name in bot.buffers)) { bot.buffers[transport.name] = {}; @@ -13,10 +15,10 @@ module.exports = function (bot) { if (!(msg.replyto in bot.buffers[transport.name])) { bot.buffers[transport.name][msg.replyto] = []; } - var buffer = bot.buffers[transport.name][msg.replyto]; + const buffer = bot.buffers[transport.name][msg.replyto]; // Trim buffer to maximum length, then add this message. - if (buffer.length >= bot.config.get('buffer', default_buffer)) { + if (buffer.length >= bot.config.get('buffer', defaultBuffer)) { buffer.pop(); } buffer.unshift(msg); diff --git a/bot/log.js b/bot/log.js index 89c7ec0..53cad2d 100644 --- a/bot/log.js +++ b/bot/log.js @@ -1,11 +1,13 @@ -var fs = require('fs'); -var handlebars = require('handlebars'); -var moment = require('moment-timezone'); -var path = require('path'); -var winston = require('winston'); +'use strict'; +const fs = require('fs'); +const handlebars = require('handlebars'); +const moment = require('moment-timezone'); +const path = require('path'); +const winston = require('winston'); -var log_defaults = { + +const logDefaults = { dir: 'logs', filename_template: 'bot--{{date}}.log', date_format: 'YYYY-MM-DD--HH-mm-ss', @@ -13,42 +15,43 @@ var log_defaults = { debug: false, }; -module.exports = function (bot) { - var level = bot.config.get('log.debug') ? 'debug' : 'info'; - var render_filename = handlebars.compile(bot.config.get('log.filename_template', log_defaults.filename_template)); +module.exports = function initLog(bot) { + const level = bot.config.get('log.debug') ? 'debug' : 'info'; + const renderFilename = handlebars.compile( + bot.config.get('log.filename_template', logDefaults.filename_template)); - var logdir = bot.config.get('log.dir', log_defaults.dir); + const logdir = bot.config.get('log.dir', logDefaults.dir); try { fs.mkdirSync(logdir); } catch (err) { - if (err.code != 'EEXIST') throw err; + if (err.code !== 'EEXIST') throw err; } - var date_format = bot.config.get('log.date_format', log_defaults.date_format); - var timezone = bot.config.get('log.timezone'); - var logfile = path.join(logdir, render_filename({ - date: (timezone ? moment.tz(timezone) : moment()).format(date_format) + const dateFormat = bot.config.get('log.date_format', logDefaults.dateFormat); + const timezone = bot.config.get('log.timezone'); + const logfile = path.join(logdir, renderFilename({ + date: (timezone ? moment.tz(timezone) : moment()).format(dateFormat), })); - var transports = []; + const transports = []; if (logfile) { transports.push(new winston.transports.File({ filename: logfile, json: false, - level: level, + level, timestamp: true, })); } if (bot.config.get('log.console')) { transports.push(new winston.transports.Console({ colorize: true, - level: level, + level, timestamp: true, })); } bot.log = new winston.Logger({ - transports: transports + transports, }); }; diff --git a/bot/transports.js b/bot/transports.js index 8b98040..b954fff 100644 --- a/bot/transports.js +++ b/bot/transports.js @@ -1,25 +1,31 @@ -module.exports = function (bot) { - var tptypes = { - IRC: require('./transports/irc'), - Telegram: require('./transports/telegram'), +'use strict'; + +const IRC = require('./transports/irc'); +const Telegram = require('./transports/telegram'); + + +module.exports = function initTransports(bot) { + const tptypes = { + IRC, + Telegram, }; - var tpconfigs = bot.config.get('transports', {}); + const tpconfigs = bot.config.get('transports', {}); - var numtransports = Object.keys(tpconfigs).length; + const numtransports = Object.keys(tpconfigs).length; if (numtransports) { bot.log.info('Found %d transport(s) in configuration.', numtransports); } else { - bot.log.warn('No transports configured.') + bot.log.warn('No transports configured.'); } bot.transports = {}; - for (tpname in tpconfigs) { - var tpconfig = tpconfigs[tpname]; - for (tptype in tptypes) { - if (tpconfig.type.toLowerCase() == tptype.toLowerCase()) { + Object.keys(tpconfigs).forEach((tpname) => { + const tpconfig = tpconfigs[tpname]; + Object.keys(tptypes).forEach((tptype) => { + if (tpconfig.type.toLowerCase() === tptype.toLowerCase()) { bot.transports[tpname] = new tptypes[tptype](bot, tpname, tpconfig); } - } - } + }); + }); }; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9ece739 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2896 @@ +{ + "name": "borgil", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "acorn": { + "version": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz", + "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ=" + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "addressparser": { + "version": "https://registry.npmjs.org/addressparser/-/addressparser-0.1.3.tgz", + "integrity": "sha1-npq0PSV+GueE4d9fWAyfUkD1iHQ=" + }, + "ajv": { + "version": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "requires": { + "co": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "json-stable-stringify": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "amdefine": { + "version": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, + "ansi-color": { + "version": "https://registry.npmjs.org/ansi-color/-/ansi-color-0.2.1.tgz", + "integrity": "sha1-PnXAN0dSF1RO12Oo21cJ+prlv5o=" + }, + "ansi-escapes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz", + "integrity": "sha1-W65SvkJIeN2Xg+iRDj/Cki6DyBs=", + "dev": true + }, + "ansi-regex": { + "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "argparse": { + "version": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "requires": { + "sprintf-js": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + } + }, + "array-indexofobject": { + "version": "https://registry.npmjs.org/array-indexofobject/-/array-indexofobject-0.0.1.tgz", + "integrity": "sha1-qqEo5iybPDWAlFaMIZ/2T+SJ1Co=" + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1": { + "version": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + }, + "assertion-error": { + "version": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", + "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", + "dev": true + }, + "ast-types": { + "version": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.15.tgz", + "integrity": "sha1-ju8IJ/BN/w7IhXupJavj/qYZTlI=" + }, + "async": { + "version": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "asynckit": { + "version": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "aws4": { + "version": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "balanced-match": { + "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base62": { + "version": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz", + "integrity": "sha1-e0F0wvlESXU7EcJlHAg9qEGnsIQ=" + }, + "base64url": { + "version": "https://registry.npmjs.org/base64url/-/base64url-1.0.6.tgz", + "integrity": "sha1-1k03XWinxkDZEuI1jRcNylu1RoE=", + "requires": { + "concat-stream": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.10.tgz", + "meow": "https://registry.npmjs.org/meow/-/meow-2.0.0.tgz" + } + }, + "bcrypt-pbkdf": { + "version": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "http://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" + } + }, + "binary-search-tree": { + "version": "https://registry.npmjs.org/binary-search-tree/-/binary-search-tree-0.2.5.tgz", + "integrity": "sha1-fbs7IQ/coIJFDa0jNMMErzm9x4Q=", + "requires": { + "underscore": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz" + } + }, + "bl": { + "version": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", + "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=", + "requires": { + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" + }, + "dependencies": { + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + } + } + } + }, + "boolbase": { + "version": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "boom": { + "version": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "requires": { + "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + } + }, + "brace-expansion": { + "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "concat-map": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + } + }, + "buffer-equal-constant-time": { + "version": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camelcase": { + "version": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "camelcase-keys": { + "version": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-1.0.0.tgz", + "integrity": "sha1-vRoRv5sxoc5JNJOpMN4aC69K1+w=", + "requires": { + "camelcase": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "map-obj": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz" + } + }, + "caseless": { + "version": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "dev": true, + "requires": { + "assertion-error": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", + "deep-eql": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "type-detect": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz" + } + }, + "chalk": { + "version": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "has-ansi": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" + } + }, + "cheerio": { + "version": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz", + "integrity": "sha1-dy5wFfLuKZZQltcepBdbdas1SSU=", + "requires": { + "css-select": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz", + "dom-serializer": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "entities": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "htmlparser2": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "co": { + "version": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "color-convert": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", + "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colors": { + "version": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + }, + "combined-stream": { + "version": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "requires": { + "delayed-stream": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + } + }, + "commander": { + "version": "http://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha1-FXFS/R56bI2YpbcVzzdt+SgARWM=" + }, + "concat-map": { + "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.10.tgz", + "integrity": "sha1-rMO79WAsuMyYDGrIQPp9hgPj7zY=", + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "typedarray": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + } + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "core-util-is": { + "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "cryptiles": { + "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "requires": { + "boom": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" + } + }, + "css-select": { + "version": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz", + "integrity": "sha1-sRIcpRhI3SZOIkTQWM7iVN7rRLA=", + "requires": { + "boolbase": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "css-what": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz", + "domutils": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz", + "nth-check": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz" + } + }, + "css-what": { + "version": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz", + "integrity": "sha1-18wt9FGAZm+Z0rFEYmOUaeAPc2w=" + }, + "ctype": { + "version": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", + "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=" + }, + "cycle": { + "version": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" + }, + "dashdash": { + "version": "http://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + }, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "debug": { + "version": "http://registry.npmjs.org/debug/-/debug-1.0.5.tgz", + "integrity": "sha1-9yQSF0MPmd7EwrRz6rkiKOh0wqw=", + "dev": true, + "requires": { + "ms": "http://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + }, + "deep-eql": { + "version": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz" + }, + "dependencies": { + "type-detect": { + "version": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + } + } + }, + "deep-equal": { + "version": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.0", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "rimraf": "2.6.2" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, + "delayed-stream": { + "version": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "doctrine": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", + "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "dom-serializer": { + "version": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "requires": { + "domelementtype": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "entities": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz" + }, + "dependencies": { + "domelementtype": { + "version": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" + } + } + }, + "domelementtype": { + "version": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" + }, + "domhandler": { + "version": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "requires": { + "domelementtype": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz" + } + }, + "domutils": { + "version": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz", + "integrity": "sha1-CGVRN5bGswYDGFDhdVFrr4C3Km8=", + "requires": { + "domelementtype": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz" + } + }, + "ecc-jsbn": { + "version": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" + } + }, + "ecdsa-sig-formatter": { + "version": "http://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz", + "integrity": "sha1-S8kmJ07Dtau1AW5+HWCSGsJisqE=", + "requires": { + "base64url": "http://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + }, + "dependencies": { + "base64url": { + "version": "http://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", + "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" + } + } + }, + "entities": { + "version": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "es3ify": { + "version": "https://registry.npmjs.org/es3ify/-/es3ify-0.1.4.tgz", + "integrity": "sha1-rZ+l3xrjTz8x4SEbWBiy1RB439E=", + "requires": { + "esprima-fb": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz", + "jstransform": "https://registry.npmjs.org/jstransform/-/jstransform-3.0.0.tgz", + "through": "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + }, + "dependencies": { + "esprima-fb": { + "version": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz", + "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE=" + } + } + }, + "escape-string-regexp": { + "version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "eslint": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.6.1.tgz", + "integrity": "sha1-3cf8f9cL+TIFsLNEm7FqHp59SVA=", + "dev": true, + "requires": { + "ajv": "5.2.2", + "babel-code-frame": "6.26.0", + "chalk": "2.1.0", + "concat-stream": "1.6.0", + "cross-spawn": "5.1.0", + "debug": "2.6.8", + "doctrine": "2.0.0", + "eslint-scope": "3.7.1", + "espree": "3.5.0", + "esquery": "1.0.0", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "globals": "9.18.0", + "ignore": "3.3.5", + "imurmurhash": "0.1.4", + "inquirer": "3.2.3", + "is-resolvable": "1.0.0", + "js-yaml": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.1.tgz", + "json-stable-stringify": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "levn": "0.3.0", + "lodash": "4.17.4", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "4.0.0", + "progress": "2.0.0", + "require-uncached": "1.0.3", + "semver": "5.4.1", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.1", + "text-table": "0.2.0" + }, + "dependencies": { + "ajv": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.2.tgz", + "integrity": "sha1-R8aNaehvXZUxA7AHSpQw3GPaXjk=", + "dev": true, + "requires": { + "co": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "fast-deep-equal": "1.0.0", + "json-schema-traverse": "0.3.1", + "json-stable-stringify": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", + "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "supports-color": "4.4.0" + } + }, + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "readable-stream": "2.3.3", + "typedarray": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + } + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "http://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "1.0.0", + "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "string_decoder": "1.0.3", + "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "eslint-config-airbnb-base": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.0.0.tgz", + "integrity": "sha512-/XlFQGn3Mkwm642/GYBtOH3pgFX4Z7saBsqqyp96v0bEUPq24nIrZ6N72qAoD0lR2wAne4EC4YsHYkbPfaRfiA==", + "dev": true, + "requires": { + "eslint-restricted-globals": "0.1.1" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz", + "integrity": "sha512-yUtXS15gIcij68NmXmP9Ni77AQuCN0itXbCc/jWd8C6/yKZaSNXicpC8cgvjnxVdmfsosIXrjpzFq7GcDryb6A==", + "dev": true, + "requires": { + "debug": "2.6.8", + "resolve": "1.4.0" + }, + "dependencies": { + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "http://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + } + } + }, + "eslint-module-utils": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz", + "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==", + "dev": true, + "requires": { + "debug": "2.6.8", + "pkg-dir": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "http://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.7.0.tgz", + "integrity": "sha512-HGYmpU9f/zJaQiKNQOVfHUh2oLWW3STBrCgH0sHTX1xtsxYlH1zjLh8FlQGEIdZSdTbUMaV36WaZ6ImXkenGxQ==", + "dev": true, + "requires": { + "builtin-modules": "1.1.1", + "contains-path": "0.1.0", + "debug": "2.6.8", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "0.3.1", + "eslint-module-utils": "2.1.1", + "has": "1.0.1", + "lodash.cond": "4.5.2", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "read-pkg-up": "2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "http://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "eslint-restricted-globals": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", + "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", + "dev": true + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "4.2.0", + "estraverse": "4.2.0" + } + }, + "esmangle-evaluator": { + "version": "https://registry.npmjs.org/esmangle-evaluator/-/esmangle-evaluator-1.0.1.tgz", + "integrity": "sha1-Yg2GbvSGGzMR91dm1SqFcrs8YzY=" + }, + "espree": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.0.tgz", + "integrity": "sha1-mDWGJb3QVYYeon4oZ+pyn69GPY0=", + "dev": true, + "requires": { + "acorn": "5.1.2", + "acorn-jsx": "3.0.1" + }, + "dependencies": { + "acorn": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz", + "integrity": "sha512-o96FZLJBPY1lvTuJylGA9Bk3t/GKPPJG8H0ydQQl01crzwJgspa4AEIq/pVTXigmK0PHVQhiAtn8WMBLL9D2WA==", + "dev": true + } + } + }, + "esprima": { + "version": "http://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ=" + }, + "esquery": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "esrecurse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "dev": true, + "requires": { + "estraverse": "4.2.0", + "object-assign": "4.1.1" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "eventemitter2": { + "version": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=" + }, + "exit": { + "version": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "extend": { + "version": "https://registry.npmjs.org/extend/-/extend-2.0.1.tgz", + "integrity": "sha1-HugBBonnOV/5RIJByYZSvHWagmA=" + }, + "external-editor": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.4.tgz", + "integrity": "sha1-HtkZnanL/i7y96MbL96LDRI2iXI=", + "dev": true, + "requires": { + "iconv-lite": "0.4.19", + "jschardet": "1.5.1", + "tmp": "0.0.31" + } + }, + "extsprintf": { + "version": "http://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "eyes": { + "version": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" + }, + "falafel": { + "version": "https://registry.npmjs.org/falafel/-/falafel-1.2.0.tgz", + "integrity": "sha1-wY0k71CRF0pJfzGM0ksCaiXN2rQ=", + "requires": { + "acorn": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz", + "foreach": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "object-keys": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz" + } + }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "feedparser": { + "version": "https://registry.npmjs.org/feedparser/-/feedparser-1.1.6.tgz", + "integrity": "sha1-VrcboPlpHXCqp/3YhNWulv3+U2Y=", + "requires": { + "addressparser": "https://registry.npmjs.org/addressparser/-/addressparser-0.1.3.tgz", + "array-indexofobject": "https://registry.npmjs.org/array-indexofobject/-/array-indexofobject-0.0.1.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "sax": "https://registry.npmjs.org/sax/-/sax-0.6.1.tgz" + }, + "dependencies": { + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + } + } + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.2.2", + "object-assign": "4.1.1" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + } + }, + "flat-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", + "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, + "foreach": { + "version": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + }, + "forever-agent": { + "version": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "requires": { + "asynckit": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "mime-types": "http://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz" + } + }, + "fs.realpath": { + "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gapitoken": { + "version": "https://registry.npmjs.org/gapitoken/-/gapitoken-0.1.5.tgz", + "integrity": "sha1-NXf8+1Qmvjp7jrrakmcSKdjMgc4=", + "requires": { + "jws": "https://registry.npmjs.org/jws/-/jws-3.0.0.tgz", + "request": "http://registry.npmjs.org/request/-/request-2.81.0.tgz" + } + }, + "generate-function": { + "version": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=" + }, + "generate-object-property": { + "version": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "requires": { + "is-property": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" + } + }, + "get-stdin": { + "version": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" + }, + "getpass": { + "version": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + }, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "dev": true, + "requires": { + "fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, + "google-auth-library": { + "version": "http://registry.npmjs.org/google-auth-library/-/google-auth-library-0.9.10.tgz", + "integrity": "sha1-SZPcB7tINLjKA1AhOmhzoyxgUbk=", + "requires": { + "async": "https://registry.npmjs.org/async/-/async-1.4.2.tgz", + "gtoken": "http://registry.npmjs.org/gtoken/-/gtoken-1.2.2.tgz", + "jws": "https://registry.npmjs.org/jws/-/jws-3.0.0.tgz", + "lodash.noop": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-3.0.1.tgz", + "request": "https://registry.npmjs.org/request/-/request-2.74.0.tgz", + "string-template": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz" + }, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-1.4.2.tgz", + "integrity": "sha1-bJ7csRztTw3S8tQNsNSaEJwIiqs=" + }, + "caseless": { + "version": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "extend": { + "version": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "form-data": { + "version": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", + "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", + "requires": { + "async": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", + "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "mime-types": "http://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz" + }, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", + "integrity": "sha1-hDGQ/WtzV6C54clW7d3V7IRitU0=", + "requires": { + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + } + } + } + }, + "har-validator": { + "version": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "commander": "http://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "is-my-json-valid": "http://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz", + "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + } + }, + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "node-uuid": { + "version": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, + "qs": { + "version": "http://registry.npmjs.org/qs/-/qs-6.2.3.tgz", + "integrity": "sha1-HPyyXBCpsrSDBT/zn138kjOQjP4=" + }, + "request": { + "version": "https://registry.npmjs.org/request/-/request-2.74.0.tgz", + "integrity": "sha1-dpPKdou7DqXIzgjAhKRe+gW4kqs=", + "requires": { + "aws-sign2": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "aws4": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "bl": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", + "caseless": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "extend": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "forever-agent": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "form-data": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", + "har-validator": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "hawk": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "http-signature": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "is-typedarray": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "isstream": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "json-stringify-safe": "http://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "mime-types": "http://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "node-uuid": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "oauth-sign": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "qs": "http://registry.npmjs.org/qs/-/qs-6.2.3.tgz", + "stringstream": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "tough-cookie": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "tunnel-agent": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz" + } + }, + "tunnel-agent": { + "version": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" + } + } + }, + "google-p12-pem": { + "version": "http://registry.npmjs.org/google-p12-pem/-/google-p12-pem-0.1.2.tgz", + "integrity": "sha1-M8RqsCGqc0+gMys5YKmj/8svMXc=", + "requires": { + "node-forge": "http://registry.npmjs.org/node-forge/-/node-forge-0.7.1.tgz" + } + }, + "googleapis": { + "version": "https://registry.npmjs.org/googleapis/-/googleapis-2.1.7.tgz", + "integrity": "sha1-2Cg+FuaOVS0osTTbJdpSoESD7U0=", + "requires": { + "async": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "gapitoken": "https://registry.npmjs.org/gapitoken/-/gapitoken-0.1.5.tgz", + "google-auth-library": "http://registry.npmjs.org/google-auth-library/-/google-auth-library-0.9.10.tgz", + "request": "https://registry.npmjs.org/request/-/request-2.65.0.tgz", + "string-template": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz" + }, + "dependencies": { + "asn1": { + "version": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=" + }, + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=" + }, + "bl": { + "version": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz", + "integrity": "sha1-/FQhoo/UImA2w7OJGmaiW8ZNIm4=", + "requires": { + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" + } + }, + "caseless": { + "version": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "extend": { + "version": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "form-data": { + "version": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", + "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", + "requires": { + "async": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", + "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "mime-types": "http://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz" + }, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", + "integrity": "sha1-hDGQ/WtzV6C54clW7d3V7IRitU0=", + "requires": { + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + } + } + } + }, + "har-validator": { + "version": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "commander": "http://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "is-my-json-valid": "http://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz", + "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + } + }, + "http-signature": { + "version": "https://registry.npmjs.org/http-signature/-/http-signature-0.11.0.tgz", + "integrity": "sha1-F5bPZ6ABrVzWhJ3KCZFIXwkIn+Y=", + "requires": { + "asn1": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "ctype": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz" + } + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "node-uuid": { + "version": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-5.2.1.tgz", + "integrity": "sha1-gB/uAw4LlFDWOFrcSKTMVbRK7fw=" + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + } + }, + "request": { + "version": "https://registry.npmjs.org/request/-/request-2.65.0.tgz", + "integrity": "sha1-zBo7xyuWJUc0/DQpbaMi+Uht3ro=", + "requires": { + "aws-sign2": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "bl": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz", + "caseless": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "extend": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "forever-agent": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "form-data": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", + "har-validator": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "hawk": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "http-signature": "https://registry.npmjs.org/http-signature/-/http-signature-0.11.0.tgz", + "isstream": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "json-stringify-safe": "http://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "mime-types": "http://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "node-uuid": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "oauth-sign": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "qs": "https://registry.npmjs.org/qs/-/qs-5.2.1.tgz", + "stringstream": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "tough-cookie": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz", + "tunnel-agent": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz" + } + }, + "tough-cookie": { + "version": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz", + "integrity": "sha1-yDoYMPTl7wuT7yo0iOck+N4Basc=" + }, + "tunnel-agent": { + "version": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" + } + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "gtoken": { + "version": "http://registry.npmjs.org/gtoken/-/gtoken-1.2.2.tgz", + "integrity": "sha1-Fyd2oanZasCfwioA9b6DzubeiCA=", + "requires": { + "google-p12-pem": "http://registry.npmjs.org/google-p12-pem/-/google-p12-pem-0.1.2.tgz", + "jws": "https://registry.npmjs.org/jws/-/jws-3.0.0.tgz", + "mime": "https://registry.npmjs.org/mime/-/mime-1.4.0.tgz", + "request": "http://registry.npmjs.org/request/-/request-2.81.0.tgz" + } + }, + "handlebars": { + "version": "https://registry.npmjs.org/handlebars/-/handlebars-3.0.3.tgz", + "integrity": "sha1-DgllGi8Ps8lJFgWDcQ1VH5Lm0q0=", + "requires": { + "optimist": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "uglify-js": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.3.6.tgz" + } + }, + "har-schema": { + "version": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" + }, + "har-validator": { + "version": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "requires": { + "ajv": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "har-schema": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz" + } + }, + "has": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "dev": true, + "requires": { + "function-bind": "1.1.1" + } + }, + "has-ansi": { + "version": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "hawk": { + "version": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "requires": { + "boom": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "cryptiles": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "sntp": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" + } + }, + "hoek": { + "version": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "hosted-git-info": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "dev": true + }, + "html-entities": { + "version": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" + }, + "htmlparser2": { + "version": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "requires": { + "domelementtype": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "domhandler": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "domutils": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "entities": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" + }, + "dependencies": { + "domutils": { + "version": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "domelementtype": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz" + } + }, + "entities": { + "version": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=" + } + } + }, + "http-signature": { + "version": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "jsprim": "http://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "sshpk": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + }, + "ignore": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.5.tgz", + "integrity": "sha512-JLH93mL8amZQhh/p6mfQgVBH3M6epNq3DfsXsTSuSrInVjwyYlFE1nv2AgfRCC8PoOhM0jwQ5v8s9LgbK7yGDw==", + "dev": true + }, + "immediate": { + "version": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "https://registry.npmjs.org/indent-string/-/indent-string-1.2.2.tgz", + "integrity": "sha1-25m8xYPrarux5I3LsZmamGBBy2s=", + "requires": { + "get-stdin": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "minimist": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "repeating": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz" + } + }, + "inflight": { + "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + } + }, + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "inline-process-browser": { + "version": "https://registry.npmjs.org/inline-process-browser/-/inline-process-browser-1.0.0.tgz", + "integrity": "sha1-RqYbFT3TybFiSxoAYm7bT39BTyI=", + "requires": { + "falafel": "https://registry.npmjs.org/falafel/-/falafel-1.2.0.tgz", + "through2": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz" + } + }, + "inquirer": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.2.3.tgz", + "integrity": "sha512-Bc3KbimpDTOeQdDj18Ir/rlsGuhBSSNqdOnxaAuKhpkdnMMuKsEGbZD2v5KFF9oso2OU+BPh7+/u5obmFDRmWw==", + "dev": true, + "requires": { + "ansi-escapes": "2.0.0", + "chalk": "2.1.0", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.0.4", + "figures": "2.0.0", + "lodash": "4.17.4", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx-lite": "4.0.8", + "rx-lite-aggregates": "4.0.8", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", + "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "supports-color": "4.4.0" + } + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "irc": { + "version": "https://registry.npmjs.org/irc/-/irc-0.3.12.tgz", + "integrity": "sha1-YXR6iOt4n6tIi9+obHUFwr+QI9Y=", + "requires": { + "ansi-color": "https://registry.npmjs.org/ansi-color/-/ansi-color-0.2.1.tgz", + "irc-colors": "https://registry.npmjs.org/irc-colors/-/irc-colors-1.3.3.tgz" + } + }, + "irc-colors": { + "version": "https://registry.npmjs.org/irc-colors/-/irc-colors-1.3.3.tgz", + "integrity": "sha1-Fvj6YTCjiC/fT6uAHFtPWDKKM5E=" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-finite": { + "version": "http://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "requires": { + "number-is-nan": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-my-json-valid": { + "version": "http://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz", + "integrity": "sha1-WoRnd+LCYg0eaRBOXToDsfYIjxE=", + "requires": { + "generate-function": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "generate-object-property": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "jsonpointer": "http://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true, + "requires": { + "is-path-inside": "1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", + "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-property": { + "version": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + }, + "is-resolvable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", + "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", + "dev": true, + "requires": { + "tryit": "1.0.3" + } + }, + "is-typedarray": { + "version": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jasmine": { + "version": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "dev": true, + "requires": { + "exit": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "jasmine-core": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz" + } + }, + "jasmine-core": { + "version": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.1.tgz", + "integrity": "sha1-CHdc69/dNZIJ8NKs04PI+GppBKA=", + "requires": { + "argparse": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "esprima": "http://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz" + } + }, + "jsbn": { + "version": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "jschardet": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.5.1.tgz", + "integrity": "sha512-vE2hT1D0HLZCLLclfBSfkfTTedhVj0fubHpJBHKwwUWX0nSbhPAfk+SG9rTX95BYNmau8rGFfCeaT6T5OW1C2A==", + "dev": true + }, + "json-schema": { + "version": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stable-stringify": { + "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz" + } + }, + "json-stringify-safe": { + "version": "http://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonify": { + "version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsonpointer": { + "version": "http://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" + }, + "jsprim": { + "version": "http://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "extsprintf": "http://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "json-schema": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "verror": "http://registry.npmjs.org/verror/-/verror-1.10.0.tgz" + }, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "jstransform": { + "version": "https://registry.npmjs.org/jstransform/-/jstransform-3.0.0.tgz", + "integrity": "sha1-olkats7o2XvzvoMNv6IxO4fNZAs=", + "requires": { + "base62": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz", + "esprima-fb": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz" + }, + "dependencies": { + "esprima-fb": { + "version": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz", + "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE=" + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz", + "integrity": "sha1-n3BNDWnZ4TioG63267T94z0VHGE=", + "requires": { + "amdefine": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz" + } + } + } + }, + "jwa": { + "version": "https://registry.npmjs.org/jwa/-/jwa-1.0.2.tgz", + "integrity": "sha1-/Xlgnx53Limdzo3bdtAGWd2DUR8=", + "requires": { + "base64url": "https://registry.npmjs.org/base64url/-/base64url-0.0.6.tgz", + "buffer-equal-constant-time": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "ecdsa-sig-formatter": "http://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz" + }, + "dependencies": { + "base64url": { + "version": "https://registry.npmjs.org/base64url/-/base64url-0.0.6.tgz", + "integrity": "sha1-lZezazMNscQkdzIuqH6oAnSZuCs=" + } + } + }, + "jws": { + "version": "https://registry.npmjs.org/jws/-/jws-3.0.0.tgz", + "integrity": "sha1-2l8meJfdTpz4E3l52zP8VKPAVBg=", + "requires": { + "base64url": "https://registry.npmjs.org/base64url/-/base64url-1.0.6.tgz", + "jwa": "https://registry.npmjs.org/jwa/-/jwa-1.0.2.tgz" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "lie": { + "version": "https://registry.npmjs.org/lie/-/lie-3.0.2.tgz", + "integrity": "sha1-/9oh17uibzd8rYZdNkmy/Izjn+o=", + "requires": { + "es3ify": "https://registry.npmjs.org/es3ify/-/es3ify-0.1.4.tgz", + "immediate": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "inline-process-browser": "https://registry.npmjs.org/inline-process-browser/-/inline-process-browser-1.0.0.tgz", + "unreachable-branch-transform": "https://registry.npmjs.org/unreachable-branch-transform/-/unreachable-branch-transform-0.3.0.tgz" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" + } + }, + "localforage": { + "version": "https://registry.npmjs.org/localforage/-/localforage-1.5.0.tgz", + "integrity": "sha1-a5lOGbVmEfqF3zmS3zl6xKtm6BU=", + "requires": { + "lie": "https://registry.npmjs.org/lie/-/lie-3.0.2.tgz" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + }, + "lodash.cond": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", + "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", + "dev": true + }, + "lodash.noop": { + "version": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-3.0.1.tgz", + "integrity": "sha1-OBiPTWUKOkdCWEObluxFsyYXEzw=" + }, + "lru-cache": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "map-obj": { + "version": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + }, + "meow": { + "version": "https://registry.npmjs.org/meow/-/meow-2.0.0.tgz", + "integrity": "sha1-j1MKjs9dQNP0tN+Tw0cpAPuiqPE=", + "requires": { + "camelcase-keys": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-1.0.0.tgz", + "indent-string": "https://registry.npmjs.org/indent-string/-/indent-string-1.2.2.tgz", + "minimist": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-1.0.0.tgz" + } + }, + "mime": { + "version": "https://registry.npmjs.org/mime/-/mime-1.4.0.tgz", + "integrity": "sha1-aeng21HUTyo7VuSLeBfX0Tfxo0M=" + }, + "mime-db": { + "version": "http://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "http://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "http://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz" + } + }, + "mimic-fn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", + "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "dev": true + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "dev": true, + "requires": { + "brace-expansion": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz" + } + }, + "minimist": { + "version": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + }, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "moment": { + "version": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz", + "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8=" + }, + "moment-timezone": { + "version": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.13.tgz", + "integrity": "sha1-mc5cfYJyYusPH3AgRBd/YHRde5A=", + "requires": { + "moment": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz" + } + }, + "ms": { + "version": "http://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nedb": { + "version": "https://registry.npmjs.org/nedb/-/nedb-1.8.0.tgz", + "integrity": "sha1-DjUCzYLABNU1WkPJ5VV3vXvZHYg=", + "requires": { + "async": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "binary-search-tree": "https://registry.npmjs.org/binary-search-tree/-/binary-search-tree-0.2.5.tgz", + "localforage": "https://registry.npmjs.org/localforage/-/localforage-1.5.0.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "underscore": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz" + }, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=" + } + } + }, + "nock": { + "version": "https://registry.npmjs.org/nock/-/nock-2.18.2.tgz", + "integrity": "sha1-oTiuy/5dKoN75Dl5GKvSOUg85Aw=", + "dev": true, + "requires": { + "chai": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "debug": "http://registry.npmjs.org/debug/-/debug-1.0.5.tgz", + "deep-equal": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "propagate": "https://registry.npmjs.org/propagate/-/propagate-0.3.1.tgz" + }, + "dependencies": { + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz", + "integrity": "sha1-W3cjA03aTSYuWkb7LFjXzCL3FCA=", + "dev": true + } + } + }, + "node-forge": { + "version": "http://registry.npmjs.org/node-forge/-/node-forge-0.7.1.tgz", + "integrity": "sha1-naYR6giYL0uUIGs760zJZl8gwwA=" + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "2.5.0", + "is-builtin-module": "1.0.0", + "semver": "5.4.1", + "validate-npm-package-license": "3.0.1" + } + }, + "nth-check": { + "version": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "requires": { + "boolbase": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" + } + }, + "number-is-nan": { + "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "https://registry.npmjs.org/object-assign/-/object-assign-1.0.0.tgz", + "integrity": "sha1-5l3Idm07R7S4MHRlyDEdoDCwcKY=" + }, + "object-keys": { + "version": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", + "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=" + }, + "once": { + "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "1.1.0" + } + }, + "optimist": { + "version": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "wordwrap": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz" + }, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", + "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=", + "dev": true + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "1.1.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + } + }, + "path-is-absolute": { + "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "2.3.0" + } + }, + "performance-now": { + "version": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "1.1.2" + } + }, + "pkginfo": { + "version": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", + "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=" + }, + "pluralize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-4.0.0.tgz", + "integrity": "sha1-WbcIwcAZCi9pLxx2GMRGsFL9F2I=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "private": { + "version": "https://registry.npmjs.org/private/-/private-0.1.7.tgz", + "integrity": "sha1-aM5eih7woju1cMwoU3tTMqumPvE=" + }, + "process-nextick-args": { + "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "propagate": { + "version": "https://registry.npmjs.org/propagate/-/propagate-0.3.1.tgz", + "integrity": "sha1-46hEBKfs6CDda76p9tkk4xNa4Jw=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "punycode": { + "version": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "2.0.0", + "normalize-package-data": "2.4.0", + "path-type": "2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "2.1.0", + "read-pkg": "2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + } + } + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + } + }, + "recast": { + "version": "https://registry.npmjs.org/recast/-/recast-0.10.43.tgz", + "integrity": "sha1-uV1Q9tYHYaX2JS4V2AZ4FoSRzn8=", + "requires": { + "ast-types": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.15.tgz", + "esprima-fb": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz", + "private": "https://registry.npmjs.org/private/-/private-0.1.7.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" + }, + "dependencies": { + "esprima-fb": { + "version": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz", + "integrity": "sha1-Q761fsJujPI3092LM+QlM1d/Jlk=" + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "repeating": { + "version": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", + "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", + "requires": { + "is-finite": "http://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz" + } + }, + "request": { + "version": "http://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "requires": { + "aws-sign2": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "aws4": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "caseless": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "combined-stream": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "extend": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "forever-agent": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "form-data": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "har-validator": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "hawk": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "http-signature": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "is-typedarray": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "isstream": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "json-stringify-safe": "http://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "mime-types": "http://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "oauth-sign": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "performance-now": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "qs": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "stringstream": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "tough-cookie": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "tunnel-agent": "http://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "uuid": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz" + }, + "dependencies": { + "extend": { + "version": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + } + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "resolve": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz", + "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "2.0.1", + "signal-exit": "3.0.2" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "2.1.0" + } + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "4.0.8" + } + }, + "safe-buffer": { + "version": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" + }, + "sax": { + "version": "https://registry.npmjs.org/sax/-/sax-0.6.1.tgz", + "integrity": "sha1-VjsZx8HeiS4Jv8Ty/DDjwn8JUrk=" + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "sntp": { + "version": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "requires": { + "hoek": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + } + }, + "source-map": { + "version": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "requires": { + "amdefine": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz" + } + }, + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "dev": true + }, + "spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "dev": true + }, + "sprintf-js": { + "version": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "requires": { + "asn1": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "bcrypt-pbkdf": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "dashdash": "http://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "ecc-jsbn": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "getpass": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "jsbn": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "tweetnacl": "http://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" + }, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "stack-trace": { + "version": "http://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "string-template": { + "version": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "stringstream": { + "version": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "table": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.1.tgz", + "integrity": "sha1-qBFsEz+sLGH0pCCrbN9cTWHw5DU=", + "dev": true, + "requires": { + "ajv": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "ajv-keywords": "1.5.1", + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "lodash": "4.17.4", + "slice-ansi": "0.0.4", + "string-width": "2.1.1" + }, + "dependencies": { + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "requires": { + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + }, + "dependencies": { + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + } + } + } + }, + "tmp": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", + "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "tough-cookie": { + "version": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "requires": { + "punycode": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" + } + }, + "tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "dev": true + }, + "tunnel-agent": { + "version": "http://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + } + }, + "tweetnacl": { + "version": "http://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "type-detect": { + "version": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", + "dev": true + }, + "typedarray": { + "version": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "uglify-js": { + "version": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.3.6.tgz", + "integrity": "sha1-+gmEdwtCi3qbKoBY9GNV0U/vIRo=", + "optional": true, + "requires": { + "async": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "optimist": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "source-map": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz" + }, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "optional": true + }, + "optimist": { + "version": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", + "optional": true, + "requires": { + "wordwrap": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz" + } + } + } + }, + "underscore": { + "version": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", + "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=" + }, + "unreachable-branch-transform": { + "version": "https://registry.npmjs.org/unreachable-branch-transform/-/unreachable-branch-transform-0.3.0.tgz", + "integrity": "sha1-2ZzExudG0mSSiEW2EdtUsPNHTKo=", + "requires": { + "esmangle-evaluator": "https://registry.npmjs.org/esmangle-evaluator/-/esmangle-evaluator-1.0.1.tgz", + "recast": "https://registry.npmjs.org/recast/-/recast-0.10.43.tgz", + "through2": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz" + } + }, + "util-deprecate": { + "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha1-PdPT55Crwk17DToDT/q6vijrvAQ=" + }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } + }, + "verror": { + "version": "http://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "extsprintf": "http://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" + }, + "dependencies": { + "assert-plus": { + "version": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "winston": { + "version": "https://registry.npmjs.org/winston/-/winston-1.1.2.tgz", + "integrity": "sha1-aO3Xaf951PlSjPDl2AAhqt5nSAw=", + "requires": { + "async": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "colors": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "cycle": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "eyes": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "isstream": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "pkginfo": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", + "stack-trace": "http://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz" + }, + "dependencies": { + "async": { + "version": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" + } + } + }, + "wordwrap": { + "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + }, + "wrappy": { + "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + } + }, + "xtend": { + "version": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } +} diff --git a/package.json b/package.json index e01a524..363c8c0 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,9 @@ "winston": "^1.0.0" }, "devDependencies": { + "eslint": "^4.6.1", + "eslint-config-airbnb-base": "^12.0.0", + "eslint-plugin-import": "^2.7.0", "jasmine": "^2.3.1", "nock": "^2.10.0" }, From f4376e4fa9fcfda1c9af256c2cfacaae4ced2d62 Mon Sep 17 00:00:00 2001 From: saltire sable Date: Mon, 11 Sep 2017 17:42:40 -0400 Subject: [PATCH 02/10] Move some functionality into bot core; update transports to ES6 --- .eslintrc.json | 2 + bot/bot.js | 117 +++++++++++++++++- bot/buffers.js | 26 ---- bot/log.js | 57 --------- bot/transport.js | 31 +++++ bot/transports.js | 31 ----- bot/transports/irc.js | 144 +++++++++++----------- bot/transports/telegram.js | 240 ++++++++++++++++++------------------ bot/transports/transport.js | 32 ----- 9 files changed, 337 insertions(+), 343 deletions(-) delete mode 100644 bot/buffers.js delete mode 100644 bot/log.js create mode 100644 bot/transport.js delete mode 100644 bot/transports.js delete mode 100644 bot/transports/transport.js diff --git a/.eslintrc.json b/.eslintrc.json index 99bccc3..07c3ed4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,10 +7,12 @@ "brace-style": [2, "stroustrup"], "consistent-return": 0, "function-paren-newline": 0, + "import/no-dynamic-require": 0, "indent": [2, 4], "max-len": [2, 100], "new-cap": [2, {"capIsNewExceptions": ["Router"]}], "no-shadow": [2, {"allow": ["err"]}], + "no-underscore-dangle": [2, {"allowAfterThis": true}], "object-curly-spacing": [2, "never"] } } diff --git a/bot/bot.js b/bot/bot.js index a92423d..589e079 100644 --- a/bot/bot.js +++ b/bot/bot.js @@ -1,26 +1,131 @@ 'use strict'; const EventEmitter = require('eventemitter2').EventEmitter2; +const fs = require('fs'); +const handlebars = require('handlebars'); +const moment = require('moment-timezone'); +const path = require('path'); +const winston = require('winston'); const Config = require('./config'); const Plugin = require('./plugin'); +const logDefaults = { + dir: 'logs', + filename_template: 'bot--{{date}}.log', + date_format: 'YYYY-MM-DD--HH-mm-ss', + console: false, + debug: false, +}; +const defaultBuffer = 100; + module.exports = class Bot extends EventEmitter { constructor(configfile) { super(); this.config = new Config(configfile); - this.plugins = {}; this.memory = {}; // Include extra functionality. - require('./log')(this); - require('./transports')(this); - require('./buffers')(this); + this.initLog(); + this.initBuffers(); + this.initTransports(); + this.initPlugins(); + } + + initLog() { + const level = this.config.get('log.debug') ? 'debug' : 'info'; + const renderFilename = handlebars.compile( + this.config.get('log.filename_template', logDefaults.filename_template)); + + const logdir = this.config.get('log.dir', logDefaults.dir); + try { + fs.mkdirSync(logdir); + } + catch (err) { + if (err.code !== 'EEXIST') throw err; + } + + const dateFormat = this.config.get('log.date_format', logDefaults.dateFormat); + const timezone = this.config.get('log.timezone'); + const logfile = path.join(logdir, renderFilename({ + date: (timezone ? moment.tz(timezone) : moment()).format(dateFormat), + })); + + const transports = []; + if (logfile) { + transports.push(new winston.transports.File({ + filename: logfile, + json: false, + level, + timestamp: true, + })); + } + if (this.config.get('log.console')) { + transports.push(new winston.transports.Console({ + colorize: true, + level, + timestamp: true, + })); + } - // Activate all plugins mentioned in the plugins section of the config. + this.log = new winston.Logger({ + transports, + }); + } + + initBuffers() { + // Create buffer objects for each client. + this.buffers = {}; + + // Log each message to a buffer. + this.on('message', (transport, msg) => { + // Initialize buffer for this transport and source if necessary. + if (!(transport.name in this.buffers)) { + this.buffers[transport.name] = {}; + } + if (!(msg.replyto in this.buffers[transport.name])) { + this.buffers[transport.name][msg.replyto] = []; + } + const buffer = this.buffers[transport.name][msg.replyto]; + + // Trim buffer to maximum length, then add this message. + if (buffer.length >= this.config.get('buffer', defaultBuffer)) { + buffer.pop(); + } + buffer.unshift(msg); + }); + } + + // Activate all transports mentioned in the transports section of the config. + initTransports() { + const tpConfigs = this.config.get('transports', {}); + + this.transports = {}; + Object.keys(tpConfigs).forEach((tpName) => { + this.log.info('Activating transport:', tpName); + const tpConfig = tpConfigs[tpName]; + let transport; + try { + // eslint-disable-next-line global-require + const TpType = require(`./transports/${tpConfig.type}`); + transport = new TpType(this, tpName, tpConfig); + } + catch (err) { + this.log.warn('Error activating transport %s:', tpName, err.message); + return; + } + this.transports[tpName] = transport; + }); + + this.log.info(Object.keys(this.transports).length, 'transports(s) activated.'); + } + + // Activate all plugins mentioned in the plugins section of the config. + initPlugins() { + this.plugins = {}; Object.keys(this.config.get('plugins', {})).forEach((pluginName) => { this.log.info('Activating plugin:', pluginName); let plugin; @@ -33,5 +138,7 @@ module.exports = class Bot extends EventEmitter { } this.plugins[pluginName] = plugin; }); + + this.log.info(Object.keys(this.plugins).length, 'plugin(s) activated.'); } }; diff --git a/bot/buffers.js b/bot/buffers.js deleted file mode 100644 index 7098369..0000000 --- a/bot/buffers.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -const defaultBuffer = 100; - -module.exports = function initBuffers(bot) { - // Create buffer objects for each client. - bot.buffers = {}; - - // Log each message to a buffer. - bot.on('message', (transport, msg) => { - // Initialize buffer for this transport and source if necessary. - if (!(transport.name in bot.buffers)) { - bot.buffers[transport.name] = {}; - } - if (!(msg.replyto in bot.buffers[transport.name])) { - bot.buffers[transport.name][msg.replyto] = []; - } - const buffer = bot.buffers[transport.name][msg.replyto]; - - // Trim buffer to maximum length, then add this message. - if (buffer.length >= bot.config.get('buffer', defaultBuffer)) { - buffer.pop(); - } - buffer.unshift(msg); - }); -}; diff --git a/bot/log.js b/bot/log.js deleted file mode 100644 index 53cad2d..0000000 --- a/bot/log.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const handlebars = require('handlebars'); -const moment = require('moment-timezone'); -const path = require('path'); -const winston = require('winston'); - - -const logDefaults = { - dir: 'logs', - filename_template: 'bot--{{date}}.log', - date_format: 'YYYY-MM-DD--HH-mm-ss', - console: false, - debug: false, -}; - -module.exports = function initLog(bot) { - const level = bot.config.get('log.debug') ? 'debug' : 'info'; - const renderFilename = handlebars.compile( - bot.config.get('log.filename_template', logDefaults.filename_template)); - - const logdir = bot.config.get('log.dir', logDefaults.dir); - try { - fs.mkdirSync(logdir); - } - catch (err) { - if (err.code !== 'EEXIST') throw err; - } - - const dateFormat = bot.config.get('log.date_format', logDefaults.dateFormat); - const timezone = bot.config.get('log.timezone'); - const logfile = path.join(logdir, renderFilename({ - date: (timezone ? moment.tz(timezone) : moment()).format(dateFormat), - })); - - const transports = []; - if (logfile) { - transports.push(new winston.transports.File({ - filename: logfile, - json: false, - level, - timestamp: true, - })); - } - if (bot.config.get('log.console')) { - transports.push(new winston.transports.Console({ - colorize: true, - level, - timestamp: true, - })); - } - - bot.log = new winston.Logger({ - transports, - }); -}; diff --git a/bot/transport.js b/bot/transport.js new file mode 100644 index 0000000..3e0d037 --- /dev/null +++ b/bot/transport.js @@ -0,0 +1,31 @@ +'use strict'; + +const EventEmitter = require('eventemitter2').EventEmitter2; + + +module.exports = class Transport extends EventEmitter { + constructor(bot, name) { + super(); + + this.bot = bot; + this.name = name; + + this.onAny((...args) => { + if (this.event === 'error') { + return bot.log.error('Error on %s (%s):', this.name, typeof this, args[0]); + } + + // Emit events at the bot level, + // passing the transport as the first argument and original arguments after it. + bot.emit(this.event, this, ...args); + }); + } + + // Interface methods that should be implemented by every transport. + /* eslint-disable class-methods-use-this */ + say() {} + connect() {} + disconnect() {} + join() {} + leave() {} +}; diff --git a/bot/transports.js b/bot/transports.js deleted file mode 100644 index b954fff..0000000 --- a/bot/transports.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -const IRC = require('./transports/irc'); -const Telegram = require('./transports/telegram'); - - -module.exports = function initTransports(bot) { - const tptypes = { - IRC, - Telegram, - }; - const tpconfigs = bot.config.get('transports', {}); - - const numtransports = Object.keys(tpconfigs).length; - if (numtransports) { - bot.log.info('Found %d transport(s) in configuration.', numtransports); - } - else { - bot.log.warn('No transports configured.'); - } - - bot.transports = {}; - Object.keys(tpconfigs).forEach((tpname) => { - const tpconfig = tpconfigs[tpname]; - Object.keys(tptypes).forEach((tptype) => { - if (tpconfig.type.toLowerCase() === tptype.toLowerCase()) { - bot.transports[tpname] = new tptypes[tptype](bot, tpname, tpconfig); - } - }); - }); -}; diff --git a/bot/transports/irc.js b/bot/transports/irc.js index c169f6d..ad0ba40 100644 --- a/bot/transports/irc.js +++ b/bot/transports/irc.js @@ -1,88 +1,90 @@ -var irc = require('irc'); -var util = require('util'); +'use strict'; -var Transport = require('./transport'); +const irc = require('irc'); +const util = require('util'); +const Transport = require('../transport'); -var default_commandchar = '.'; -var IRC = module.exports = function (bot, name, config) { - Transport.call(this, bot, name); - var transport = this; +const defaultCommandchar = '.'; - var client = this.irc = new irc.Client(config.host, config.nick, config.opts); +module.exports = class IRC extends Transport { + constructor(bot, name, config) { + super(bot, name); - // Log a message and connect to channels manually after receiving MOTD. - this.irc.on('motd', function () { - bot.log.info('Got MOTD from', transport.name); + this.irc = new irc.Client(config.host, config.nick, config.opts); - (config.channels || []).forEach(function (channel) { - var keyword = config.channel_keywords && config.channel_keywords[channel]; - client.join(channel + (keyword ? ' ' + keyword : '')); + // Log a message and connect to channels manually after receiving MOTD. + this.irc.on('motd', () => { + bot.log.info('Got MOTD from', this.name); + + (config.channels || []).forEach((channel) => { + const keyword = config.channel_keywords && config.channel_keywords[channel]; + this.irc.join(channel + (keyword ? ` ${keyword}` : '')); + }); + }); + + // Set up some log events. + this.irc.conn.on('connect', () => { + bot.log.info('Connected to', this.name); }); - }); - - // Set up some log events. - this.irc.conn.on('connect', function () { - bot.log.info('Connected to', transport.name); - }); - this.irc.conn.on('end', function () { - bot.log.info('Got END from', transport.name); - }); - this.irc.conn.on('close', function () { - bot.log.info('Disconnected from', transport.name); - }); - - // Log raw IRC messages. - if (bot.config.get('log.debug')) { - this.irc.on('raw', function (msg) { - bot.log.debug('%s: <-', transport.name, msg.rawCommand, msg.command.toUpperCase(), msg.nick || '', msg.args); + this.irc.conn.on('end', () => { + bot.log.info('Got END from', this.name); }); - this.irc.on('selfMessage', function (target, text) { - bot.log.debug('%s: -> %s:', transport.name, target, text); + this.irc.conn.on('close', () => { + bot.log.info('Disconnected from', this.name); }); - } - // Log IRC error events. - this.irc.on('error', function (msg) { - transport.emit('error', msg.command.toUpperCase() + ' ' + msg.args.join(', ')); - }); - - // Listen for IRC message events, and emit message/command events from the transport. - this.irc.on('message', function (nick, to, text, msg) { - var data = { - from: nick, - from_name: nick, - to: to, - replyto: to == this.nick ? nick : to, - replyto_name: to == this.nick ? nick : to, - text: text, - time: new Date(), - }; - - // If this message is a command, add command properties and emit a command event. - var commandchar = bot.config.get('commandchar', default_commandchar) - .replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); - var commandRegex = new RegExp('^' + commandchar + '(\\S+)(?:\\s+(.*?))?\\s*$'); - var m = text.match(commandRegex); - if (m) { - data.command = m[1]; - data.args = (m[2] || '').trim(); - transport.emit('command', data); + // Log raw IRC messages. + if (bot.config.get('log.debug')) { + this.irc.on('raw', (msg) => { + bot.log.debug('%s: <-', this.name, msg.rawCommand, msg.command.toUpperCase(), + msg.nick || '', msg.args); + }); + this.irc.on('selfMessage', (target, text) => { + bot.log.debug('%s: -> %s:', this.name, target, text); + }); } - transport.emit('message', data); - }); -}; -util.inherits(IRC, Transport); + // Log IRC error events. + this.irc.on('error', (msg) => { + this.emit('error', `${msg.command.toUpperCase()} ${msg.args.join(', ')}`); + }); -IRC.prototype.say = function (target) { - var text = util.format.apply(null, Array.prototype.slice.call(arguments, 1)); - this.irc.say(target, text); -}; + // Listen for IRC message events, and emit message/command events from the transport. + this.irc.on('message', (nick, to, text) => { + const data = { + from: nick, + from_name: nick, + to, + replyto: to === this.nick ? nick : to, + replyto_name: to === this.nick ? nick : to, + text, + time: new Date(), + }; -Object.defineProperty(IRC.prototype, 'channels', { - get: function () { + // If this message is a command, add command properties and emit a command event. + const commandchar = bot.config.get('commandchar', defaultCommandchar) + .replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); + const commandRegex = new RegExp(`^${commandchar}(\\S+)(?:\\s+(.*?))?\\s*$`); + const m = text.match(commandRegex); + if (m) { + Object.assign(data, { + command: m[1], + args: (m[2] || '').trim(), + }); + this.emit('command', data); + } + + this.emit('message', data); + }); + } + + say(target, ...args) { + this.irc.say(target, util.format(...args)); + } + + get channels() { return Object.keys(this.irc.chans); } -}); +}; diff --git a/bot/transports/telegram.js b/bot/transports/telegram.js index 485f35d..0071568 100644 --- a/bot/transports/telegram.js +++ b/bot/transports/telegram.js @@ -1,144 +1,142 @@ -var request = require('request'); -var util = require('util'); +'use strict'; -var Transport = require('./transport'); +const request = require('request'); +const util = require('util'); +const Transport = require('../transport'); -var Telegram = module.exports = function (bot, name, config) { - Transport.call(this, bot, name); - this.token = config.token; - this.offset = 0; +module.exports = class Telegram extends Transport { + constructor(bot, name, config) { + super(bot, name); - this._getUpdates(); -}; -util.inherits(Telegram, Transport); - -Telegram.prototype._send = function (method, params, callback) { - request({ - uri: 'https://api.telegram.org/bot' + this.token + '/' + method, - method: 'post', - json: true, - qs: params - }, function (err, res, body) { - if (err) return callback ? callback(err) : null; - if (!body.ok || !body.result) { - var err = new Error(body.description || 'Unknown API error'); - err.status = body.error_code || 400; - return callback ? callback(err) : null; - } - - if (callback) callback(null, body.result); - }); -} - -Telegram.prototype._getUpdates = function () { - var transport = this; + this.token = config.token; + this.offset = 0; - var debug = this.bot.config.get('log.debug'); - - if (debug) { - this.bot.log.debug('%s: Polling for Telegram updates...', this.name); + this._getUpdates(); } - // Make a long polling call to the API. - this._send('getUpdates', { - offset: this.offset, - timeout: 20 - }, function (err, result) { - if (err) { - transport.emit('error', err.message); - } - else { - // Emit events for any messages received. - (result || []).forEach(function (update) { - if (debug) { - transport.bot.log.debug('%s: Got update:', transport.name, JSON.stringify(update)); + _send(method, params, callback) { + request( + { + uri: `https://api.telegram.org/bot${this.token}/${method}`, + method: 'post', + json: true, + qs: params, + }, + (err, res, body) => { + if (err) { + return callback ? callback(err) : null; } + if (!body.ok || !body.result) { + const err = new Error(body.description || 'Unknown API error'); + err.status = body.error_code || 400; + return callback ? callback(err) : null; + } + + if (callback) { + callback(null, body.result); + } + }); + } - transport.offset = Math.max(transport.offset, update.update_id + 1); + _getUpdates() { + this.bot.log.debug('%s: Polling for Telegram updates...', this.name); - if (update.message) { - transport._parseMessage(update.message); + // Make a long polling call to the API. + this._send('getUpdates', + { + offset: this.offset, + timeout: 20, + }, + (err, result) => { + if (err) { + this.emit('error', err.message); } - if (update.channel_post) { - transport._parseChannelPost(update.channel_post); + else { + // Emit events for any messages received. + (result || []).forEach((update) => { + this.bot.log.debug('%s: Got update:', this.name, JSON.stringify(update)); + + this.offset = Math.max(this.offset, update.update_id + 1); + + if (update.message) { + this._parseMessage(update.message); + } + if (update.channel_post) { + this._parseChannelPost(update.channel_post); + } + }); } + + // Continue polling. + this._getUpdates(); }); - } + } - // Continue polling. - transport._getUpdates(); - }); -}; + _parseMessage(message) { + const fromName = message.from.first_name + + (message.from.last_name ? ` ${message.from.last_name}` : ''); + + const data = { + from: message.from.username || message.from.id, + from_name: fromName, + to: message.chat.id, + replyto: message.chat.id, + replyto_name: message.chat.title || fromName, + text: message.text || '', + time: new Date(message.date * 1000), + }; + + // If this message is a command, add command properties and emit a command event. + // Currently this uses a slash instead of the configured command character. + const cmd = (message.entities || []) + .find(entity => (entity.type === 'bot_command' && entity.offset === 0)); + if (cmd) { + data.command = message.text.slice(cmd.offset + 1, cmd.length); + data.args = message.text.slice(cmd.offset + cmd.length).trim(); + this.emit('command', data); + } -Telegram.prototype._parseMessage = function (message) { - var from_name = message.from.first_name + - (message.from.last_name ? ' ' + message.from.last_name : ''); - - var data = { - from: message.from.username || message.from.id, - from_name: from_name, - to: message.chat.id, - replyto: message.chat.id, - replyto_name: message.chat.title || from_name, - text: message.text || '', - time: new Date(message.date * 1000), - }; - - // If this message is a command, add command properties and emit a command event. - // Currently this uses a slash instead of the configured command character. - var cmd = (message.entities || []).find(function (entity) { - return entity.type === 'bot_command' && entity.offset === 0; - }); - if (cmd) { - data.command = message.text.slice(cmd.offset + 1, cmd.length); - data.args = message.text.slice(cmd.offset + cmd.length).trim(); - this.emit('command', data); + this.emit('message', data); } - this.emit('message', data); -}; + _parseChannelPost(post) { + // TODO: Telegram channel posts don't expose sending user, but come from the channel itself. + // Most plugins assume a sending user, so don't emit message/command events for now. + // Instead let's emit 'broadcast' events, but maybe remove these in the future + // if plugins are updated to handle them as regular message events. + + const fromName = post.chat.title || post.chat.username; + + const data = { + from: post.chat.username || post.chat.id, + from_name: fromName, + text: post.text || '', + time: new Date(post.date * 1000), + }; + + // If this message is a command, add command properties and emit a command event. + // Currently this uses a slash instead of the configured command character. + const cmd = (post.entities || []) + .find(entity => (entity.type === 'bot_command' && entity.offset === 0)); + if (cmd) { + data.command = post.text.slice(cmd.offset + 1, cmd.length); + data.args = post.text.slice(cmd.offset + cmd.length).trim(); + this.emit('broadcastcommand', data); + } -Telegram.prototype._parseChannelPost = function (post) { - // TODO: Telegram channel posts don't expose a sending user, but come from the channel itself. - // Most plugins assume a sending user, so don't emit message/command events for now. - // Instead let's emit 'broadcast' events, but maybe remove these in the future - // if plugins are updated to handle them as regular message events. - - var from_name = post.chat.title || post.chat.username; - - var data = { - from: post.chat.username || post.chat.id, - from_name: from_name, - text: post.text || '', - time: new Date(post.date * 1000), - }; - - // If this message is a command, add command properties and emit a command event. - // Currently this uses a slash instead of the configured command character. - var cmd = (post.entities || []).find(function (entity) { - return entity.type === 'bot_command' && entity.offset === 0; - }); - if (cmd) { - data.command = post.text.slice(cmd.offset + 1, cmd.length); - data.args = post.text.slice(cmd.offset + cmd.length).trim(); - this.emit('broadcastcommand', data); + this.emit('broadcast', data); } - this.emit('broadcast', data); -}; - -Telegram.prototype.say = function (target) { - var text = util.format.apply(null, Array.prototype.slice.call(arguments, 1)); - this._send('sendMessage', { - chat_id: target, - text: text, - }); -}; + say(target, ...args) { + this._send('sendMessage', { + chat_id: target, + text: util.format(...args), + }); + } -Object.defineProperty(Telegram.prototype, 'channels', { - get: function () { - return this.bot.config.get('transports.' + this.name + '.chat_ids', []); + get channels() { + return this.bot.config.get(`transports.${this.name}.chat_ids`, []); } -}); +}; diff --git a/bot/transports/transport.js b/bot/transports/transport.js deleted file mode 100644 index d597e30..0000000 --- a/bot/transports/transport.js +++ /dev/null @@ -1,32 +0,0 @@ -var EventEmitter = require('eventemitter2').EventEmitter2; -var util = require('util'); - - -var Transport = module.exports = function (bot, name) { - EventEmitter.call(this); - - this.bot = bot; - this.name = name; - - this.channels = []; - - this.onAny(function () { - var args = Array.prototype.slice.call(arguments); - - if (this.event === 'error') { - return bot.log.error('Error on %s (%s):', this.name, typeof this, args[0]); - } - - // Emit events at the bot level, - // passing the transport as the first argument and original arguments after it. - bot.emit.apply(bot, [this.event, this].concat(args)); - }); -}; -util.inherits(Transport, EventEmitter); - -// Interface methods that should be implemented -Transport.prototype.say = function () {}; -Transport.prototype.connect = function () {}; -Transport.prototype.disconnect = function () {}; -Transport.prototype.join = function () {}; -Transport.prototype.leave = function () {}; From 81d151fd36a9ab5532e49d7013b598ca405bf526 Mon Sep 17 00:00:00 2001 From: saltire sable Date: Mon, 11 Sep 2017 18:01:46 -0400 Subject: [PATCH 03/10] Update config/plugin modules to ES6 --- bot/config.js | 119 ++++++++++++++++++++++++++------------------------ bot/plugin.js | 118 +++++++++++++++++++++++++------------------------ 2 files changed, 122 insertions(+), 115 deletions(-) diff --git a/bot/config.js b/bot/config.js index 276991f..d5f1839 100644 --- a/bot/config.js +++ b/bot/config.js @@ -1,15 +1,17 @@ -var extend = require('extend'); -var fs = require('fs'); -var yaml = require('js-yaml'); +'use strict'; + +const extend = require('extend'); +const fs = require('fs'); +const yaml = require('js-yaml'); function getValue(obj, elems) { - if (elems.length == 1) { + if (elems.length === 1) { // Leaf node. return obj[elems[0]]; } - if (typeof obj[elems[0]] != 'object') { + if (typeof obj[elems[0]] !== 'object') { // Branch doesn't exist. return undefined; } @@ -18,70 +20,71 @@ function getValue(obj, elems) { return getValue(obj[elems[0]], elems.slice(1)); } -function splitPath(key, value) { - value = splitValue(value); +module.exports = class Config { + constructor(configInit) { + this.config = {}; - // If the key is a dotted path, split off the first element and parse the rest. - var tree = {}; - var kelem = Array.isArray(key) ? key : key.split('.'); - tree[kelem[0]] = kelem.length == 1 ? value : splitPath(kelem.slice(1), value); - return tree; -} + if (typeof configInit === 'object') { + // If an object is passed, use it as the config. + this.config = Config.splitValue(configInit); + } + else { + // Otherwise treat it as a filename and read that file as YAML or JSON. + // Fall back to default filenames if no existing filename was passed. + const configPath = [configInit, 'config.json', 'config.yml'] + .find(fs.existsSync.bind(fs)); + if (!configPath) { + return console.error('Config file not found.'); + } -function splitValue(value) { - if (Array.isArray(value)) { - // Parse each value individually. - return value.map(splitValue); - } - if (typeof value === 'object') { - // Parse each key and value individually. - var tree = {}; - for (key in value) { - extend(true, tree, splitPath(key, value[key])); + const configFile = fs.readFileSync(configPath, 'utf-8'); + + try { + if (configPath.match(/\.ya?ml$/i)) { + this.config = Config.splitValue(yaml.safeLoad(configFile)); + } + else { + this.config = Config.splitValue(JSON.parse(configFile)); + } + } + catch (err) { + console.error(`Error reading config file at ${configPath}:`, err.message); + } } - return tree; } - return value; -} -var Config = module.exports = function (config_init) { - this.config = {}; + static splitPath(key, value) { + const val = Config.splitValue(value); - if (typeof config_init == 'object') { - // If an object is passed, use it as the config. - this.config = splitValue(config_init); + // If the key is a dotted path, split off the first element and parse the rest. + const tree = {}; + const kelem = Array.isArray(key) ? key : key.split('.'); + tree[kelem[0]] = kelem.length === 1 ? val : Config.splitPath(kelem.slice(1), val); + return tree; } - else { - // Otherwise treat it as a filename and read that file as YAML or JSON. - // Fall back to default filenames if no existing filename was passed. - var configpath = [config_init, 'config.json', 'config.yml'].find(fs.existsSync.bind(fs)); - if (!configpath) { - return console.error('Config file not found.'); - } - - var configfile = fs.readFileSync(configpath, 'utf-8'); - try { - if (configpath.match(/\.ya?ml$/i)) this.config = splitValue(yaml.safeLoad(configfile)); - else this.config = splitValue(JSON.parse(configfile)); - } catch (e) { - console.error('Error reading config file at ' + configpath + ':', e.message); + static splitValue(value) { + if (Array.isArray(value)) { + // Parse each value individually. + return value.map(Config.splitValue); } + if (typeof value === 'object') { + // Parse each key and value individually. + const tree = {}; + Object.keys(value).forEach((key) => { + extend(true, tree, Config.splitPath(key, value[key])); + }); + return tree; + } + return value; } -}; -Config.prototype.get = function (path, defval) { - var value = getValue(this.config, Array.isArray(path) ? path : path.split('.')); - return value !== undefined ? value : defval; -}; - -Config.prototype.set = function (path, value) { - //setValue(this.config, path, value); - extend(true, this.config, splitPath(path, value)); -}; + get(path, defval) { + const value = getValue(this.config, Array.isArray(path) ? path : path.split('.')); + return value !== undefined ? value : defval; + } -Config.prototype.save = function () { - if (typeof config_init == 'string') { - fs.writeFile(config_init, JSON.stringify(this.config, null, 4), {encoding: 'utf-8'}); + set(path, value) { + extend(true, this.config, Config.splitPath(path, value)); } }; diff --git a/bot/plugin.js b/bot/plugin.js index d7217c5..942f403 100644 --- a/bot/plugin.js +++ b/bot/plugin.js @@ -1,72 +1,76 @@ -var extend = require('extend'); -var util = require('util'); +'use strict'; + +const extend = require('extend'); +const util = require('util'); // A set of commands and properties for a single plugin to access. // Instantiating one of these objects per client helps with logging and error handling. -var Plugin = module.exports = function (bot, name) { - this.bot = bot; - this.name = name; - - require('../plugins/' + name)(this); -}; +module.exports = class Plugin { + constructor(bot, name) { + this.bot = bot; + this.name = name; -Object.defineProperties(Plugin.prototype, { - config: {get: function () { return this.bot.config; }}, - buffers: {get: function () { return this.bot.buffers; }}, - memory: {get: function () { return this.bot.memory; }}, - transports: {get: function () { return this.bot.transports; }} -}); + // eslint-disable-next-line global-require + require(`../plugins/${name}`)(this); + } -// Start listening for a message matching a pattern, and call back with data about the message. -Plugin.prototype.listen = function (pattern, callback) { - var plugin = this; + get config() { + return this.bot.config; + } + get buffers() { + return this.bot.buffers; + } + get memory() { + return this.bot.memory; + } + get transports() { + return this.bot.transports; + } - this.bot.on('message', function (transport, msg) { - var match = msg.text.match(pattern); - if (match) { - try { - callback(extend({ - transport: transport, - match: match, - }, msg)); + // Start listening for a message matching a pattern, and call back with data about the message. + listen(pattern, callback) { + this.bot.on('message', (transport, msg) => { + const match = msg.text.match(pattern); + if (match) { + try { + callback(extend({transport, match}, msg)); + } + catch (err) { + this.error(err.message); + } } - catch (e) { - plugin.error(e.message); - } - } - }); -}; - -// Start listening for a command message matching a particular set of commands. -Plugin.prototype.addCommand = function (commands, callback) { - var plugin = this; - - if (typeof commands === 'string') { - commands = commands.split(','); + }); } - this.bot.on('command', function (transport, msg) { - if (commands.indexOf(msg.command) > -1) { - try { - callback(extend({ - transport: transport, - }, msg)); - } - catch (e) { - plugin.error(e.message); + // Start listening for a command message matching a particular set of commands. + addCommand(commands, callback) { + const cmds = (typeof commands === 'string') ? commands.split(',') : commands; + + this.bot.on('command', (transport, msg) => { + if (cmds.indexOf(msg.command) > -1) { + try { + callback(extend({transport}, msg)); + } + catch (err) { + this.error(err.message); + } } - } - }); -}; + }); + } + // Add a line to the log. + log(...args) { + this.bot.log.info('%s:', this.name, util.format(...args)); + } -// Add a line to the log. -Plugin.prototype.log = function () { - this.bot.log.info('%s:', this.name, util.format.apply(null, arguments)); -}; + // Add a warning to the log. + warn(...args) { + this.bot.log.warn('%s:', this.name, util.format(...args)); + } -// Add an error to the log. -Plugin.prototype.error = function () { - this.bot.log.error('%s:', this.name, util.format.apply(null, arguments)); + // Add an error to the log. + error(...args) { + this.bot.log.error('%s:', this.name, util.format(...args)); + } }; From 3f7c4700ca9ea8c365e27006baad713976d41354 Mon Sep 17 00:00:00 2001 From: saltire sable Date: Mon, 11 Sep 2017 18:25:29 -0400 Subject: [PATCH 04/10] Fix a config issue; fix unit tests using buffers --- .eslintrc.json | 3 ++ bot/config.js | 12 +++++--- spec/bot/config-spec.js | 34 +++++++++++---------- spec/bot/{transports => }/transport-spec.js | 2 +- spec/plugins/quote-spec.js | 5 ++- spec/plugins/sed-spec.js | 5 ++- 6 files changed, 34 insertions(+), 27 deletions(-) rename spec/bot/{transports => }/transport-spec.js (89%) diff --git a/.eslintrc.json b/.eslintrc.json index 07c3ed4..b4fe0b1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,4 +1,7 @@ { + "env": { + "jasmine": true + }, "extends": "airbnb-base", "parserOptions": { "sourceType": "script" diff --git a/bot/config.js b/bot/config.js index d5f1839..a46652f 100644 --- a/bot/config.js +++ b/bot/config.js @@ -6,18 +6,20 @@ const yaml = require('js-yaml'); function getValue(obj, elems) { + const value = obj[elems[0]]; + if (elems.length === 1) { // Leaf node. - return obj[elems[0]]; + return value; } - if (typeof obj[elems[0]] !== 'object') { + if (!value || typeof value !== 'object') { // Branch doesn't exist. return undefined; } // Move down the tree. - return getValue(obj[elems[0]], elems.slice(1)); + return getValue(value, elems.slice(1)); } module.exports = class Config { @@ -68,7 +70,8 @@ module.exports = class Config { // Parse each value individually. return value.map(Config.splitValue); } - if (typeof value === 'object') { + + if (value && typeof value === 'object') { // Parse each key and value individually. const tree = {}; Object.keys(value).forEach((key) => { @@ -76,6 +79,7 @@ module.exports = class Config { }); return tree; } + return value; } diff --git a/spec/bot/config-spec.js b/spec/bot/config-spec.js index 1daec7a..1859952 100644 --- a/spec/bot/config-spec.js +++ b/spec/bot/config-spec.js @@ -1,9 +1,11 @@ -var Config = require('../../bot/config'); +'use strict'; +const Config = require('../../bot/config'); -describe('Configuration object', function () { - it('should set and get using dot notation', function () { - var config = new Config(); + +describe('Configuration object', () => { + it('should set and get using dot notation', () => { + const config = new Config(); config.set('one.two.three', 'four'); expect(config.get('one')).toEqual({ two: { @@ -16,8 +18,8 @@ describe('Configuration object', function () { expect(config.get('one.two.three')).toEqual('four'); }); - it('should set using mixed dot notation', function () { - var config = new Config(); + it('should set using mixed dot notation', () => { + const config = new Config(); config.set('one.two', { 'three.four': { 'five.six': 'seven', @@ -40,8 +42,8 @@ describe('Configuration object', function () { }); }); - it('should initialize using mixed dot notation', function () { - var config = new Config({ + it('should initialize using mixed dot notation', () => { + const config = new Config({ one: { two: { three: 'four', @@ -59,8 +61,8 @@ describe('Configuration object', function () { expect(config.get('nine.ten.eleven')).toEqual('twelve'); }); - it('should set multiple mixed-dot values with some of the same roots', function () { - var config = new Config({ + it('should set multiple mixed-dot values with some of the same roots', () => { + const config = new Config({ 'one.two': 'three', 'one.four': 'five', }); @@ -70,8 +72,8 @@ describe('Configuration object', function () { }); }); - it('should get a default value for undefined paths', function () { - var config = new Config({ + it('should get a default value for undefined paths', () => { + const config = new Config({ one: 'two', 'three.four': 'five', }); @@ -80,8 +82,8 @@ describe('Configuration object', function () { expect(config.get('three.five', 'default')).toEqual('default'); }); - it('should parse objects inside of arrays', function () { - var config = new Config({ + it('should parse objects inside of arrays', () => { + const config = new Config({ one: ['two', 'three'], 'four.five': [{ 'six.seven': 'eight', @@ -95,8 +97,8 @@ describe('Configuration object', function () { expect(config.get('nine.ten.eleven')).toEqual(['twelve']); }); - it('should return undefined when getting a child of a scalar or null value', function () { - var config = new Config({ + it('should return undefined when getting a child of a scalar or null value', () => { + const config = new Config({ one: null, two: 'two', three: 3, diff --git a/spec/bot/transports/transport-spec.js b/spec/bot/transport-spec.js similarity index 89% rename from spec/bot/transports/transport-spec.js rename to spec/bot/transport-spec.js index 0d2f92a..440dd37 100644 --- a/spec/bot/transports/transport-spec.js +++ b/spec/bot/transport-spec.js @@ -1,6 +1,6 @@ var EventEmitter = require('eventemitter2').EventEmitter2; -var Transport = require('../../../bot/transports/transport'); +var Transport = require('../../bot/transport'); describe('Transport base class', function () { diff --git a/spec/plugins/quote-spec.js b/spec/plugins/quote-spec.js index d8502b6..537c582 100644 --- a/spec/plugins/quote-spec.js +++ b/spec/plugins/quote-spec.js @@ -2,8 +2,7 @@ var fs = require('fs'); var mkdirp = require('mkdirp'); var path = require('path'); -var buffers = require('../../bot/buffers'); - +var Bot = require('../../bot/bot'); var MockBot = require('../helpers/mock-bot'); var MockTransport = require('../helpers/mock-transport'); @@ -20,7 +19,7 @@ describe('Quote plugin', function () { dbdir: path.join(__dirname, '../temp'), plugins: {quote: {}} }); - buffers(mockBot); // Add normal buffer functionality. + Bot.prototype.initBuffers.call(mockBot); // Add normal buffer functionality. mockTransport = new MockTransport(); // Clear the database and set up spies. diff --git a/spec/plugins/sed-spec.js b/spec/plugins/sed-spec.js index 628b6a9..5d812ab 100644 --- a/spec/plugins/sed-spec.js +++ b/spec/plugins/sed-spec.js @@ -1,5 +1,4 @@ -var buffers = require('../../bot/buffers'); - +var Bot = require('../../bot/bot'); var MockBot = require('../helpers/mock-bot'); var MockTransport = require('../helpers/mock-transport'); @@ -10,7 +9,7 @@ describe('Search and replace plugin', function () { beforeEach(function () { mockBot = new MockBot({plugins: {sed: {}}}); - buffers(mockBot); // Add normal buffer functionality. + Bot.prototype.initBuffers.call(mockBot); // Add normal buffer functionality. mockTransport = new MockTransport(); }); From 83a2b8aefc031fdf8b3bba34cb5914d3ffa64199 Mon Sep 17 00:00:00 2001 From: saltire sable Date: Mon, 11 Sep 2017 19:14:30 -0400 Subject: [PATCH 05/10] Update spec files to ES6 --- .eslintrc.json | 2 +- spec/bot/transport-spec.js | 18 +++--- spec/bot/transports/irc-spec.js | 28 ++++----- spec/helpers/mock-bot.js | 41 ++++++------ spec/helpers/mock-transport.js | 26 ++++---- spec/plugins/admin-spec.js | 33 +++++----- spec/plugins/broadcast-spec.js | 42 +++++++------ spec/plugins/ddg-scraper-spec.js | 29 +++++---- spec/plugins/echo-spec.js | 18 +++--- spec/plugins/eightball-spec.js | 16 ++--- spec/plugins/modes-spec.js | 19 +++--- spec/plugins/nickserv-spec.js | 32 +++++----- spec/plugins/quote-spec.js | 105 +++++++++++++++++-------------- spec/plugins/rss-spec.js | 74 +++++++++++----------- spec/plugins/sed-spec.js | 23 ++++--- spec/plugins/url-spec.js | 23 ++++--- spec/plugins/youtube-spec.js | 49 ++++++++------- 17 files changed, 315 insertions(+), 263 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index b4fe0b1..47bc5b6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,7 +13,7 @@ "import/no-dynamic-require": 0, "indent": [2, 4], "max-len": [2, 100], - "new-cap": [2, {"capIsNewExceptions": ["Router"]}], + "no-multi-assign": 0, "no-shadow": [2, {"allow": ["err"]}], "no-underscore-dangle": [2, {"allowAfterThis": true}], "object-curly-spacing": [2, "never"] diff --git a/spec/bot/transport-spec.js b/spec/bot/transport-spec.js index 440dd37..501c22a 100644 --- a/spec/bot/transport-spec.js +++ b/spec/bot/transport-spec.js @@ -1,19 +1,21 @@ -var EventEmitter = require('eventemitter2').EventEmitter2; +'use strict'; -var Transport = require('../../bot/transport'); +const EventEmitter = require('eventemitter2').EventEmitter2; +const Transport = require('../../bot/transport'); -describe('Transport base class', function () { - var bot; - var transport; - beforeEach(function () { +describe('Transport base class', () => { + let bot; + let transport; + + beforeEach(() => { bot = new EventEmitter(); transport = new Transport(bot, 'test'); }); - it('should pass events to the bot object', function () { - var handler = jasmine.createSpy(); + it('should pass events to the bot object', () => { + const handler = jasmine.createSpy(); bot.on('event', handler); transport.emit('event', 'arg1', 'arg2'); diff --git a/spec/bot/transports/irc-spec.js b/spec/bot/transports/irc-spec.js index 503abe0..81524e5 100644 --- a/spec/bot/transports/irc-spec.js +++ b/spec/bot/transports/irc-spec.js @@ -1,25 +1,25 @@ -var irc = require('irc'); +'use strict'; -var IRC = require('../../../bot/transports/irc'); -var MockBot = require('../../helpers/mock-bot'); +const IRC = require('../../../bot/transports/irc'); +const MockBot = require('../../helpers/mock-bot'); -describe('IRC transport', function () { - var bot; - var transport; - var messageHandler; - var commandHandler; +describe('IRC transport', () => { + let bot; + let transport; + let messageHandler; + let commandHandler; - beforeEach(function () { + beforeEach(() => { bot = new MockBot({ 'debug.log': false, - 'commandchar': '%', + commandchar: '%', }); transport = new IRC(bot, 'irc', { host: 'irc.server.com', nick: 'borgil', - opts: {} + opts: {}, }); messageHandler = jasmine.createSpy(); @@ -28,7 +28,7 @@ describe('IRC transport', function () { transport.on('command', commandHandler); }); - it('should emit message events', function () { + it('should emit message events', () => { transport.irc.emit('message', 'nick', '#channel', 'blah blah', {}); expect(messageHandler).toHaveBeenCalledWith(jasmine.objectContaining({ @@ -40,10 +40,10 @@ describe('IRC transport', function () { expect(commandHandler).not.toHaveBeenCalled(); }); - it('should emit commands as message and command events', function () { + it('should emit commands as message and command events', () => { transport.irc.emit('message', 'nick', '#channel', '%command arg1 arg2'); - var expectedData = jasmine.objectContaining({ + const expectedData = jasmine.objectContaining({ from: 'nick', to: '#channel', replyto: '#channel', diff --git a/spec/helpers/mock-bot.js b/spec/helpers/mock-bot.js index d578c00..178067b 100644 --- a/spec/helpers/mock-bot.js +++ b/spec/helpers/mock-bot.js @@ -1,32 +1,31 @@ -var EventEmitter = require('eventemitter2').EventEmitter2; -var util = require('util'); -var winston = require('winston'); +'use strict'; -var Bot = require('../../bot/bot'); -var Config = require('../../bot/config'); -var Plugin = require('../../bot/plugin'); +const EventEmitter = require('eventemitter2').EventEmitter2; +const winston = require('winston'); + +const Config = require('../../bot/config'); +const Plugin = require('../../bot/plugin'); // The bot object. -var MockBot = module.exports = function (config, transports) { - // Run the event emitter constructor. - EventEmitter.call(this); +module.exports = class MockBot extends EventEmitter { + constructor(config, transports) { + super(); - this.config = new Config(config || {}); + this.config = new Config(config || {}); - this.plugins = {}; - this.memory = {}; + this.plugins = {}; + this.memory = {}; - // Mock out extra bot functionality. - this.log = winston; - this.transports = transports || {}; - this.buffers = {}; + // Mock out extra bot functionality. + this.log = winston; + this.transports = transports || {}; + this.buffers = {}; - winston.level = 'error'; + winston.level = 'error'; - for (pluginName in this.config.get('plugins', {})) { - this.plugins[pluginName] = new Plugin(this, pluginName); + Object.keys(this.config.get('plugins', {})).forEach((pluginName) => { + this.plugins[pluginName] = new Plugin(this, pluginName); + }); } }; -// Extend the event emitter class. -util.inherits(MockBot, Bot); diff --git a/spec/helpers/mock-transport.js b/spec/helpers/mock-transport.js index e8293d3..3a80979 100644 --- a/spec/helpers/mock-transport.js +++ b/spec/helpers/mock-transport.js @@ -1,17 +1,19 @@ -var EventEmitter = require('eventemitter2').EventEmitter2; -var util = require('util'); +'use strict'; +const EventEmitter = require('eventemitter2').EventEmitter2; -var MockTransport = module.exports = function (name) { - EventEmitter.call(this); - this.name = name || 'mockTransport'; - this.channels = []; +module.exports = class MockTransport extends EventEmitter { + constructor(name) { + super(); - this.say = jasmine.createSpy(); - this.join = jasmine.createSpy(); - this.leave = jasmine.createSpy(); - this.connect = jasmine.createSpy(); - this.disconnect = jasmine.createSpy(); + this.name = name || 'mockTransport'; + this.channels = []; + + this.say = jasmine.createSpy(); + this.join = jasmine.createSpy(); + this.leave = jasmine.createSpy(); + this.connect = jasmine.createSpy(); + this.disconnect = jasmine.createSpy(); + } }; -util.inherits(MockTransport, EventEmitter); diff --git a/spec/plugins/admin-spec.js b/spec/plugins/admin-spec.js index 917bc0a..74bfb23 100644 --- a/spec/plugins/admin-spec.js +++ b/spec/plugins/admin-spec.js @@ -1,15 +1,17 @@ -var MockBot = require('../helpers/mock-bot'); -var MockTransport = require('../helpers/mock-transport'); +'use strict'; +const MockBot = require('../helpers/mock-bot'); +const MockTransport = require('../helpers/mock-transport'); -describe('Admin plugin', function () { - var mockBot; - var mockTransport; - beforeEach(function () { +describe('Admin plugin', () => { + let mockBot; + let mockTransport; + + beforeEach(() => { mockBot = new MockBot({ admins: ['admin'], - plugins: {admin: {}} + plugins: {admin: {}}, }); mockTransport = new MockTransport(); mockTransport.channels = [ @@ -17,7 +19,7 @@ describe('Admin plugin', function () { ]; }); - it('should join a new channel', function () { + it('should join a new channel', () => { mockBot.emit('command', mockTransport, { from: 'admin', command: 'join', @@ -26,7 +28,7 @@ describe('Admin plugin', function () { expect(mockTransport.join).toHaveBeenCalledWith('#newchannel', jasmine.any(Function)); }); - it('should refuse to join channels the bot is already in', function () { + it('should refuse to join channels the bot is already in', () => { mockBot.emit('command', mockTransport, { from: 'admin', replyto: '#channel1', @@ -37,7 +39,7 @@ describe('Admin plugin', function () { expect(mockTransport.say).toHaveBeenCalledWith('#channel1', "I'm already in that channel."); }); - it('should refuse to join channels if the caller is not an admin', function () { + it('should refuse to join channels if the caller is not an admin', () => { mockBot.emit('command', mockTransport, { from: 'notadmin', command: 'join', @@ -46,7 +48,7 @@ describe('Admin plugin', function () { expect(mockTransport.join).not.toHaveBeenCalled(); }); - it('should part the channel', function () { + it('should part the channel', () => { mockBot.emit('command', mockTransport, { from: 'admin', command: 'part', @@ -55,16 +57,17 @@ describe('Admin plugin', function () { expect(mockTransport.leave).toHaveBeenCalledWith('#channel1', null, jasmine.any(Function)); }); - it('should part the channel with a message', function () { + it('should part the channel with a message', () => { mockBot.emit('command', mockTransport, { from: 'admin', command: 'part', args: '#channel1 Later, dudes!', }); - expect(mockTransport.leave).toHaveBeenCalledWith('#channel1', 'Later, dudes!', jasmine.any(Function)); + expect(mockTransport.leave).toHaveBeenCalledWith('#channel1', 'Later, dudes!', + jasmine.any(Function)); }); - it('should refuse to part channels the bot is not in', function () { + it('should refuse to part channels the bot is not in', () => { mockBot.emit('command', mockTransport, { from: 'admin', replyto: '#channel1', @@ -75,7 +78,7 @@ describe('Admin plugin', function () { expect(mockTransport.say).toHaveBeenCalledWith('#channel1', "I'm not in that channel."); }); - it('should refuse to part channels if the caller is not an admin', function () { + it('should refuse to part channels if the caller is not an admin', () => { mockBot.emit('command', mockTransport, { from: 'notadmin', command: 'part', diff --git a/spec/plugins/broadcast-spec.js b/spec/plugins/broadcast-spec.js index c79e085..204a9be 100644 --- a/spec/plugins/broadcast-spec.js +++ b/spec/plugins/broadcast-spec.js @@ -1,15 +1,17 @@ -var MockBot = require('../helpers/mock-bot'); -var MockTransport = require('../helpers/mock-transport'); +'use strict'; +const MockBot = require('../helpers/mock-bot'); +const MockTransport = require('../helpers/mock-transport'); -describe('Broadcast plugin', function () { - var mockBot; - var ircA; - var ircB; - beforeEach(function () { +describe('Broadcast plugin', () => { + let mockBot; + let ircA; + let ircB; + + beforeEach(() => { mockBot = new MockBot({ - 'plugins.broadcast.template': '[{{{source}}}] <{{{from}}}> {{{text}}}' + 'plugins.broadcast.template': '[{{{source}}}] <{{{from}}}> {{{text}}}', }); ircA = mockBot.transports.ircA = new MockTransport('ircA'); @@ -24,12 +26,12 @@ describe('Broadcast plugin', function () { ]; }); - describe('broadcasting to all', function () { - beforeEach(function () { + describe('broadcasting to all', () => { + beforeEach(() => { mockBot.config.set('plugins.broadcast.broadcast_all', true); }); - it('should emit messages to all other channels on all transports', function () { + it('should emit messages to all other channels on all transports', () => { mockBot.emit('message', ircA, { from: 'somebody', replyto: '#channelA', @@ -37,14 +39,17 @@ describe('Broadcast plugin', function () { }); expect(ircA.say.calls.count()).toEqual(2); expect(ircB.say.calls.count()).toEqual(1); - expect(ircA.say).toHaveBeenCalledWith('#channelAAA', '[#channelA] Message text'); - expect(ircA.say).toHaveBeenCalledWith('#channelZZZ', '[#channelA] Message text'); - expect(ircB.say).toHaveBeenCalledWith('#channelB', '[ircA:#channelA] Message text'); + expect(ircA.say).toHaveBeenCalledWith('#channelAAA', + '[#channelA] Message text'); + expect(ircA.say).toHaveBeenCalledWith('#channelZZZ', + '[#channelA] Message text'); + expect(ircB.say).toHaveBeenCalledWith('#channelB', + '[ircA:#channelA] Message text'); }); }); - describe('broadcasting to sets', function () { - beforeEach(function () { + describe('broadcasting to sets', () => { + beforeEach(() => { mockBot.config.set('plugins.broadcast.target_sets', [ [ { @@ -69,7 +74,7 @@ describe('Broadcast plugin', function () { ]); }); - it('should emit messages to other channels in the same set', function () { + it('should emit messages to other channels in the same set', () => { mockBot.emit('message', ircA, { from: 'somebody', replyto: '#channelA', @@ -77,7 +82,8 @@ describe('Broadcast plugin', function () { }); expect(ircA.say).not.toHaveBeenCalled(); expect(ircB.say.calls.count()).toEqual(1); - expect(ircB.say).toHaveBeenCalledWith('#channelB', '[ircA:#channelA] Message text'); + expect(ircB.say).toHaveBeenCalledWith('#channelB', + '[ircA:#channelA] Message text'); }); }); }); diff --git a/spec/plugins/ddg-scraper-spec.js b/spec/plugins/ddg-scraper-spec.js index 84b4d54..e526441 100644 --- a/spec/plugins/ddg-scraper-spec.js +++ b/spec/plugins/ddg-scraper-spec.js @@ -1,23 +1,25 @@ -var fs = require('fs'); -var nock = require('nock'); -var path = require('path'); +'use strict'; -var MockBot = require('../helpers/mock-bot'); -var MockTransport = require('../helpers/mock-transport'); +const fs = require('fs'); +const nock = require('nock'); +const path = require('path'); +const MockBot = require('../helpers/mock-bot'); +const MockTransport = require('../helpers/mock-transport'); -describe('DuckDuckGo scraper plugin', function () { - var mockBot; - var mockTransport; - var searchPage = fs.readFileSync(path.join(__dirname, '../data/ddg.html')); +describe('DuckDuckGo scraper plugin', () => { + let mockBot; + let mockTransport; - beforeEach(function () { + const searchPage = fs.readFileSync(path.join(__dirname, '../data/ddg.html')); + + beforeEach(() => { mockBot = new MockBot({plugins: {'ddg-scraper': {}}}); mockTransport = new MockTransport(); }); - it('should fetch and parse the first item from a DuckDuckGo results page', function (done) { + it('should fetch and parse the first item from a DuckDuckGo results page', (done) => { nock('https://duckduckgo.com') .get('/html?q=Betelgeuse') .reply(200, searchPage); @@ -28,9 +30,10 @@ describe('DuckDuckGo scraper plugin', function () { args: 'Betelgeuse', }); - setTimeout(function () { + setTimeout(() => { expect(mockTransport.say).toHaveBeenCalledWith('#channel1', - '[DDG] Betelgeuse - Wikipedia, the free encyclopedia | https://en.wikipedia.org/wiki/Betelgeuse'); + '[DDG] Betelgeuse - Wikipedia, the free encyclopedia | ' + + 'https://en.wikipedia.org/wiki/Betelgeuse'); done(); }, 50); }); diff --git a/spec/plugins/echo-spec.js b/spec/plugins/echo-spec.js index dad2c1c..bd04549 100644 --- a/spec/plugins/echo-spec.js +++ b/spec/plugins/echo-spec.js @@ -1,17 +1,19 @@ -var MockBot = require('../helpers/mock-bot'); -var MockTransport = require('../helpers/mock-transport'); +'use strict'; +const MockBot = require('../helpers/mock-bot'); +const MockTransport = require('../helpers/mock-transport'); -describe('Echo plugin', function () { - var mockBot; - var mockTransport; - beforeEach(function () { +describe('Echo plugin', () => { + let mockBot; + let mockTransport; + + beforeEach(() => { mockBot = new MockBot({plugins: {echo: {}}}); mockTransport = new MockTransport(); }); - it('should repeat any message on the same channel', function () { + it('should repeat any message on the same channel', () => { mockBot.emit('message', mockTransport, { from: 'somebody', to: '#channel1', @@ -21,7 +23,7 @@ describe('Echo plugin', function () { expect(mockTransport.say).toHaveBeenCalledWith('#channel1', 'message text'); }); - it('should repeat any private message', function () { + it('should repeat any private message', () => { mockBot.emit('message', mockTransport, { from: 'somebody', to: 'borgil', diff --git a/spec/plugins/eightball-spec.js b/spec/plugins/eightball-spec.js index 7b94270..95ba0ac 100644 --- a/spec/plugins/eightball-spec.js +++ b/spec/plugins/eightball-spec.js @@ -1,12 +1,14 @@ -var MockBot = require('../helpers/mock-bot'); -var MockTransport = require('../helpers/mock-transport'); +'use strict'; +const MockBot = require('../helpers/mock-bot'); +const MockTransport = require('../helpers/mock-transport'); -describe('Magic 8-ball plugin', function () { - var mockBot; - var mockTransport; - beforeEach(function (done) { +describe('Magic 8-ball plugin', () => { + let mockBot; + let mockTransport; + + beforeEach((done) => { mockBot = new MockBot({plugins: {eightball: {}}}); mockTransport = new MockTransport(); @@ -14,7 +16,7 @@ describe('Magic 8-ball plugin', function () { setTimeout(done, 50); }); - it('should send a message to the channel when called', function () { + it('should send a message to the channel when called', () => { mockBot.emit('command', mockTransport, { replyto: '#channel1', command: '8ball', diff --git a/spec/plugins/modes-spec.js b/spec/plugins/modes-spec.js index 9f83bc9..04f07f4 100644 --- a/spec/plugins/modes-spec.js +++ b/spec/plugins/modes-spec.js @@ -1,24 +1,25 @@ -var MockBot = require('../helpers/mock-bot'); -var MockTransport = require('../helpers/mock-transport'); +'use strict'; +const MockBot = require('../helpers/mock-bot'); +const MockTransport = require('../helpers/mock-transport'); -describe('IRC modes plugin', function () { - var mockBot; - var mockIRC; - beforeEach(function () { +describe('IRC modes plugin', () => { + let mockIRC; + + beforeEach(() => { mockIRC = new MockTransport(); mockIRC.irc = { send: jasmine.createSpy(), }; - mockBot = new MockBot( + const mockBot = new MockBot( {'plugins.modes.irc': '+B'}, - {irc: mockIRC} + {irc: mockIRC}, ); }); - it('should send a MODE command to an IRC transport once registered', function () { + it('should send a MODE command to an IRC transport once registered', () => { mockIRC.emit('registered'); expect(mockIRC.irc.send).toHaveBeenCalledWith('MODE', '+B'); }); diff --git a/spec/plugins/nickserv-spec.js b/spec/plugins/nickserv-spec.js index c81c5f0..dfb3f44 100644 --- a/spec/plugins/nickserv-spec.js +++ b/spec/plugins/nickserv-spec.js @@ -1,14 +1,16 @@ -var EventEmitter = require('eventemitter2').EventEmitter2; -var MockBot = require('../helpers/mock-bot'); -var MockTransport = require('../helpers/mock-transport'); +'use strict'; +const EventEmitter = require('eventemitter2').EventEmitter2; +const MockBot = require('../helpers/mock-bot'); +const MockTransport = require('../helpers/mock-transport'); -describe('IRC NickServ plugin', function () { - var mockBot; - var mockIRC; - beforeEach(function () { - var config = { +describe('IRC NickServ plugin', () => { + let mockBot; + let mockIRC; + + beforeEach(() => { + const config = { 'transports.irc.nick': 'borgil', 'plugins.nickserv.networks.irc': { password: 'borgilpass', @@ -31,27 +33,29 @@ describe('IRC NickServ plugin', function () { mockBot = new MockBot(config, {irc: mockIRC}); }); - it('should send an identify message to NickServ once registered', function () { + it('should send an identify message to NickServ once registered', () => { mockIRC.irc.emit('registered'); expect(mockIRC.say).toHaveBeenCalledWith('NickServ', 'IDENTIFY', 'borgilpass', 'borgil'); }); - it('should send nick before password if so configured', function () { + it('should send nick before password if so configured', () => { mockBot.config.set('plugins.nickserv.networks.irc.nick_first', true); mockIRC.irc.emit('registered'); expect(mockIRC.say).toHaveBeenCalledWith('NickServ', 'IDENTIFY', 'borgil', 'borgilpass'); }); - it('should join channels once identified on the network', function () { + it('should join channels once identified on the network', () => { mockIRC.irc.emit('registered'); - mockIRC.irc.emit('notice', 'NickServ', 'borgil', 'You are successfully identified as borgil.'); + mockIRC.irc.emit('notice', 'NickServ', 'borgil', + 'You are successfully identified as borgil.'); expect(mockIRC.irc.join).toHaveBeenCalledWith('#torontocrypto channelkey'); expect(mockIRC.irc.join).toHaveBeenCalledWith('#cryptoparty'); }); - it('should send a NICK command if the current nick is not the desired one', function () { + it('should send a NICK command if the current nick is not the desired one', () => { mockIRC.irc.emit('registered'); - mockIRC.irc.emit('notice', 'NickServ', 'borgil2', 'You are successfully identified as borgil2.'); + mockIRC.irc.emit('notice', 'NickServ', 'borgil2', + 'You are successfully identified as borgil2.'); expect(mockIRC.irc.send).toHaveBeenCalledWith('NICK', 'borgil'); }); }); diff --git a/spec/plugins/quote-spec.js b/spec/plugins/quote-spec.js index 537c582..b38c550 100644 --- a/spec/plugins/quote-spec.js +++ b/spec/plugins/quote-spec.js @@ -1,38 +1,39 @@ -var fs = require('fs'); -var mkdirp = require('mkdirp'); -var path = require('path'); +'use strict'; -var Bot = require('../../bot/bot'); -var MockBot = require('../helpers/mock-bot'); -var MockTransport = require('../helpers/mock-transport'); +const mkdirp = require('mkdirp'); +const path = require('path'); +const Bot = require('../../bot/bot'); +const MockBot = require('../helpers/mock-bot'); +const MockTransport = require('../helpers/mock-transport'); -describe('Quote plugin', function () { - var mockBot; - var mockTransport; - var db; - beforeEach(function (done) { +describe('Quote plugin', () => { + let mockBot; + let mockTransport; + let db; + + beforeEach((done) => { mkdirp.sync(path.join(__dirname, '../temp')); mockBot = new MockBot({ dbdir: path.join(__dirname, '../temp'), - plugins: {quote: {}} + plugins: {quote: {}}, }); - Bot.prototype.initBuffers.call(mockBot); // Add normal buffer functionality. + Bot.prototype.initBuffers.call(mockBot); // Add normal buffer functionality. mockTransport = new MockTransport(); // Clear the database and set up spies. db = mockBot.plugins.quote.db; - db.remove({}, {multi: true}, function () { + db.remove({}, {multi: true}, () => { spyOn(db, 'insert').and.callThrough(); spyOn(db, 'find').and.callThrough(); done(); }); }); - describe('commands', function () { - beforeEach(function () { + describe('commands', () => { + beforeEach(() => { mockBot.emit('message', mockTransport, { from: 'somebody', replyto: '#channel1', @@ -50,11 +51,11 @@ describe('Quote plugin', function () { }); }); - it('should remember the most recent message if no user is specified', function (done) { + it('should remember the most recent message if no user is specified', (done) => { mockBot.emit('command', mockTransport, { replyto: '#channel1', command: 'remember', - args: '' + args: '', }); expect(db.insert).toHaveBeenCalledWith(jasmine.objectContaining({ transport: 'mockTransport', @@ -62,13 +63,14 @@ describe('Quote plugin', function () { replyto: '#channel1', text: 'third message', }), jasmine.any(Function)); - setTimeout(function () { - expect(mockTransport.say).toHaveBeenCalledWith('#channel1', 'Remembered somebodyelse saying "third message".'); + setTimeout(() => { + expect(mockTransport.say).toHaveBeenCalledWith('#channel1', + 'Remembered somebodyelse saying "third message".'); done(); }, 50); }); - it('should filter messages by user if specified', function (done) { + it('should filter messages by user if specified', (done) => { mockBot.emit('command', mockTransport, { replyto: '#channel1', command: 'remember', @@ -80,13 +82,14 @@ describe('Quote plugin', function () { replyto: '#channel1', text: 'second message', }), jasmine.any(Function)); - setTimeout(function () { - expect(mockTransport.say).toHaveBeenCalledWith('#channel1', 'Remembered somebody saying "second message".'); + setTimeout(() => { + expect(mockTransport.say).toHaveBeenCalledWith('#channel1', + 'Remembered somebody saying "second message".'); done(); }, 50); }); - it('should filter messages by a word if specified', function (done) { + it('should filter messages by a word if specified', (done) => { mockBot.emit('command', mockTransport, { replyto: '#channel1', command: 'remember', @@ -98,32 +101,35 @@ describe('Quote plugin', function () { replyto: '#channel1', text: 'first message', }), jasmine.any(Function)); - setTimeout(function () { - expect(mockTransport.say).toHaveBeenCalledWith('#channel1', 'Remembered somebody saying "first message".'); + setTimeout(() => { + expect(mockTransport.say).toHaveBeenCalledWith('#channel1', + 'Remembered somebody saying "first message".'); done(); }, 50); }); - it('should reply when a user is not found in the buffer', function () { + it('should reply when a user is not found in the buffer', () => { mockBot.emit('command', mockTransport, { replyto: '#channel1', command: 'remember', args: 'someguy', }); - expect(mockTransport.say).toHaveBeenCalledWith('#channel1', 'Sorry, I can\'t remember anything someguy said recently.'); + expect(mockTransport.say).toHaveBeenCalledWith('#channel1', + 'Sorry, I can\'t remember anything someguy said recently.'); }); - it('should reply when a word is not found in the buffer', function () { + it('should reply when a word is not found in the buffer', () => { mockBot.emit('command', mockTransport, { replyto: '#channel1', command: 'remember', args: 'someguy someword', }); - expect(mockTransport.say).toHaveBeenCalledWith('#channel1', 'Sorry, I can\'t remember what someguy said about "someword" recently.'); + expect(mockTransport.say).toHaveBeenCalledWith('#channel1', + 'Sorry, I can\'t remember what someguy said about "someword" recently.'); }); - describe('quote retrieval', function () { - beforeEach(function () { + describe('quote retrieval', () => { + beforeEach(() => { mockBot.emit('command', mockTransport, { replyto: '#channel1', command: 'remember', @@ -141,62 +147,67 @@ describe('Quote plugin', function () { }); }); - it('should return a random quote if no user is specified', function (done) { + it('should return a random quote if no user is specified', (done) => { mockBot.emit('command', mockTransport, { replyto: '#channel1', command: 'quote', args: '', }); - setTimeout(function () { - expect(mockTransport.say).toHaveBeenCalledWith('#channel1', jasmine.stringMatching(/^/)); + setTimeout(() => { + expect(mockTransport.say).toHaveBeenCalledWith('#channel1', + jasmine.stringMatching(/^/)); done(); }, 50); }); - it('should filter quotes by user if specified', function (done) { + it('should filter quotes by user if specified', (done) => { mockBot.emit('command', mockTransport, { replyto: '#channel1', command: 'quote', args: 'somebody', }); - setTimeout(function () { - expect(mockTransport.say).toHaveBeenCalledWith('#channel1', jasmine.stringMatching(/^/)); + setTimeout(() => { + expect(mockTransport.say).toHaveBeenCalledWith('#channel1', + jasmine.stringMatching(/^/)); done(); }, 50); }); - it('should filter quotes by a word if specified', function (done) { + it('should filter quotes by a word if specified', (done) => { mockBot.emit('command', mockTransport, { replyto: '#channel1', command: 'quote', args: 'somebody first', }); - setTimeout(function () { - expect(mockTransport.say).toHaveBeenCalledWith('#channel1', ' first message'); + setTimeout(() => { + expect(mockTransport.say).toHaveBeenCalledWith('#channel1', + ' first message'); done(); }, 50); }); - it('should reply when a user is not found in the database', function (done) { + it('should reply when a user is not found in the database', (done) => { mockBot.emit('command', mockTransport, { replyto: '#channel1', command: 'quote', args: 'someguy', }); - setTimeout(function () { - expect(mockTransport.say).toHaveBeenCalledWith('#channel1', 'Sorry, I don\'t have any quotes from someguy.'); + setTimeout(() => { + expect(mockTransport.say).toHaveBeenCalledWith('#channel1', + 'Sorry, I don\'t have any quotes from someguy.'); done(); }, 50); }); - it('should reply when a word is not found in the database', function (done) { + it('should reply when a word is not found in the database', (done) => { mockBot.emit('command', mockTransport, { replyto: '#channel1', command: 'quote', args: 'someguy someword', }); - setTimeout(function () { - expect(mockTransport.say).toHaveBeenCalledWith('#channel1', 'Sorry, I don\'t have any quotes from someguy about "someword".'); + setTimeout(() => { + expect(mockTransport.say).toHaveBeenCalledWith('#channel1', + 'Sorry, I don\'t have any quotes from someguy about "someword".'); done(); }, 50); }); diff --git a/spec/plugins/rss-spec.js b/spec/plugins/rss-spec.js index 784d8cf..585c9a3 100644 --- a/spec/plugins/rss-spec.js +++ b/spec/plugins/rss-spec.js @@ -1,27 +1,29 @@ -var fs = require('fs'); -var mkdirp = require('mkdirp'); -var nock = require('nock'); -var path = require('path'); +'use strict'; -var MockBot = require('../helpers/mock-bot'); -var MockTransport = require('../helpers/mock-transport'); +const fs = require('fs'); +const mkdirp = require('mkdirp'); +const nock = require('nock'); +const path = require('path'); +const MockBot = require('../helpers/mock-bot'); +const MockTransport = require('../helpers/mock-transport'); -describe('RSS feed plugin', function () { - var mockBot; - var irc1; - var irc2; - var db; - var rssFile = fs.readFileSync(__dirname + '/../data/feed.rss', {encoding: 'utf8'}); - var atomFile = fs.readFileSync(__dirname + '/../data/feed.atom', {encoding: 'utf8'}); +describe('RSS feed plugin', () => { + let mockBot; + let irc1; + let irc2; + let db; - beforeEach(function (done) { + const rssFile = fs.readFileSync(`${__dirname}/../data/feed.rss`, {encoding: 'utf8'}); + const atomFile = fs.readFileSync(`${__dirname}/../data/feed.atom`, {encoding: 'utf8'}); + + beforeEach((done) => { mkdirp.sync(path.join(__dirname, '../temp')); mockBot = new MockBot({ dbdir: path.join(__dirname, '../temp'), - 'plugins.rss.item_template': '[{{name}}] {{{title}}} | {{url}}' + 'plugins.rss.item_template': '[{{name}}] {{{title}}} | {{url}}', }); irc1 = mockBot.transports.irc1 = new MockTransport('irc1'); irc2 = mockBot.transports.irc2 = new MockTransport('irc2'); @@ -33,7 +35,7 @@ describe('RSS feed plugin', function () { // Clear the database and set up spies. db = mockBot.plugins.rss.db; - db.remove({}, {multi: true}, function () { + db.remove({}, {multi: true}, () => { spyOn(db, 'insert').and.callThrough(); spyOn(db, 'find').and.callThrough(); spyOn(db, 'remove').and.callThrough(); @@ -41,26 +43,26 @@ describe('RSS feed plugin', function () { }); }); - it('should quickly fetch a feed on command', function (done) { + it('should quickly fetch a feed on command', (done) => { mockBot.emit('command', irc1, { replyto: '#channel1', command: 'rss', args: 'quick http://feed.com/rss', }); - setTimeout(function () { + setTimeout(() => { expect(irc1.say).toHaveBeenCalledWith('#channel1', '[QUICK] Example entry | http://www.example.com/blog/post/1'); done(); }, 500); }); - it('should add a feed to the database on command', function (done) { + it('should add a feed to the database on command', (done) => { mockBot.emit('command', irc1, { replyto: '#channel1', command: 'rss', args: 'add newFeed http://feed.com/rss', }); - setTimeout(function () { + setTimeout(() => { expect(db.insert).toHaveBeenCalledWith(jasmine.objectContaining({ transport: 'irc1', target: '#channel1', @@ -71,7 +73,7 @@ describe('RSS feed plugin', function () { }, 100); }); - it('should remove a feed from the database on command', function () { + it('should remove a feed from the database on command', () => { mockBot.emit('command', irc1, { replyto: '#channel1', command: 'rss', @@ -84,8 +86,8 @@ describe('RSS feed plugin', function () { }), jasmine.any(Function)); }); - describe('list command', function () { - beforeEach(function (done) { + describe('list command', () => { + beforeEach((done) => { db.insert([ { transport: 'irc1', @@ -115,13 +117,13 @@ describe('RSS feed plugin', function () { ], done); }); - it('should list all feeds in the current channel on command', function (done) { + it('should list all feeds in the current channel on command', (done) => { mockBot.emit('command', irc1, { replyto: '#channel1', command: 'rss', args: 'list', }); - setTimeout(function () { + setTimeout(() => { expect(irc1.say).toHaveBeenCalledWith('#channel1', 'Found %d feed%s.', 2, 's'); expect(irc1.say).toHaveBeenCalledWith('#channel1', @@ -133,16 +135,18 @@ describe('RSS feed plugin', function () { }); }); - it('should fetch a feed and pass the latest item to a callback', function (done) { - mockBot.plugins.rss.fetchLatestItem({ - url: 'http://feed.com/rss', - color: 'dark_red', - }, function (err, item) { - expect(item).toEqual({ - title: 'Example entry', - url: 'http://www.example.com/blog/post/1', + it('should fetch a feed and pass the latest item to a callback', (done) => { + mockBot.plugins.rss.fetchLatestItem( + { + url: 'http://feed.com/rss', + color: 'dark_red', + }, + (err, item) => { + expect(item).toEqual({ + title: 'Example entry', + url: 'http://www.example.com/blog/post/1', + }); + done(); }); - done(); - }); }); }); diff --git a/spec/plugins/sed-spec.js b/spec/plugins/sed-spec.js index 5d812ab..d75abe6 100644 --- a/spec/plugins/sed-spec.js +++ b/spec/plugins/sed-spec.js @@ -1,19 +1,21 @@ -var Bot = require('../../bot/bot'); -var MockBot = require('../helpers/mock-bot'); -var MockTransport = require('../helpers/mock-transport'); +'use strict'; +const Bot = require('../../bot/bot'); +const MockBot = require('../helpers/mock-bot'); +const MockTransport = require('../helpers/mock-transport'); -describe('Search and replace plugin', function () { - var mockBot; - var mockTransport; - beforeEach(function () { +describe('Search and replace plugin', () => { + let mockBot; + let mockTransport; + + beforeEach(() => { mockBot = new MockBot({plugins: {sed: {}}}); - Bot.prototype.initBuffers.call(mockBot); // Add normal buffer functionality. + Bot.prototype.initBuffers.call(mockBot); // Add normal buffer functionality. mockTransport = new MockTransport(); }); - it('should check the buffer for messages on the same channel', function () { + it('should check the buffer for messages on the same channel', () => { mockBot.emit('message', mockTransport, { from: 'somebody', replyto: '#channel1', @@ -29,6 +31,7 @@ describe('Search and replace plugin', function () { replyto: '#channel1', text: 's/wrong/right/', }); - expect(mockTransport.say).toHaveBeenCalledWith('#channel1', 'somebody meant to say: This message is right!'); + expect(mockTransport.say).toHaveBeenCalledWith('#channel1', + 'somebody meant to say: This message is right!'); }); }); diff --git a/spec/plugins/url-spec.js b/spec/plugins/url-spec.js index de4364b..ace89ac 100644 --- a/spec/plugins/url-spec.js +++ b/spec/plugins/url-spec.js @@ -1,19 +1,21 @@ -var nock = require('nock'); +'use strict'; -var MockBot = require('../helpers/mock-bot'); -var MockTransport = require('../helpers/mock-transport'); +const nock = require('nock'); +const MockBot = require('../helpers/mock-bot'); +const MockTransport = require('../helpers/mock-transport'); -describe('URL plugin', function () { - var mockBot; - var mockTransport; - beforeEach(function () { +describe('URL plugin', () => { + let mockBot; + let mockTransport; + + beforeEach(() => { mockBot = new MockBot({plugins: {url: {}}}); mockTransport = new MockTransport(); }); - it('should parse urls and echo them to the same channel', function (done) { + it('should parse urls and echo them to the same channel', (done) => { nock('https://website.com') .get('/page?query=1') .reply(200, 'Page TitleBody'); @@ -23,8 +25,9 @@ describe('URL plugin', function () { text: 'Some text https://website.com/page?query=1 some more text', }); - setTimeout(function () { - expect(mockTransport.say).toHaveBeenCalledWith('#channel', '[ Page Title ] - website.com'); + setTimeout(() => { + expect(mockTransport.say).toHaveBeenCalledWith('#channel', + '[ Page Title ] - website.com'); done(); }, 50); }); diff --git a/spec/plugins/youtube-spec.js b/spec/plugins/youtube-spec.js index ce73b57..a6c19c5 100644 --- a/spec/plugins/youtube-spec.js +++ b/spec/plugins/youtube-spec.js @@ -1,30 +1,32 @@ -var fs = require('fs'); -var nock = require('nock'); -var path = require('path'); +'use strict'; -var MockBot = require('../helpers/mock-bot'); -var MockTransport = require('../helpers/mock-transport'); +const fs = require('fs'); +const nock = require('nock'); +const path = require('path'); +const MockBot = require('../helpers/mock-bot'); +const MockTransport = require('../helpers/mock-transport'); -describe('YouTube plugin', function () { - var mockBot; - var mockTransport; - var searchData = fs.readFileSync(path.join(__dirname, '../data/video-search.json')); - var videoData = fs.readFileSync(path.join(__dirname, '../data/video.json')); +describe('YouTube plugin', () => { + let mockBot; + let mockTransport; - beforeEach(function () { + const searchData = fs.readFileSync(path.join(__dirname, '../data/video-search.json')); + const videoData = fs.readFileSync(path.join(__dirname, '../data/video.json')); + + beforeEach(() => { mockBot = new MockBot({ 'plugins.youtube': { api_key: 'abcdefg', - timezone: 'America/Toronto' - } + timezone: 'America/Toronto', + }, }); mockTransport = new MockTransport(); }); - it('should grab YouTube URLs and return video info', function (done) { - var scope = nock('https://www.googleapis.com') + it('should grab YouTube URLs and return video info', (done) => { + const scope = nock('https://www.googleapis.com') .get('/youtube/v3/videos') .query({ part: 'id,snippet,contentDetails,statistics', @@ -38,15 +40,17 @@ describe('YouTube plugin', function () { text: 'Some text https://youtube.com/watch?v=123456789 some more text', }); - setTimeout(function () { + setTimeout(() => { expect(scope.isDone()).toBeTruthy(); - expect(mockTransport.say).toHaveBeenCalledWith('#channel', '[YouTube] Video Title | Length: 3:33 | Channel: AYouTubeChannel | Uploaded: 2013-10-06 | Views: 1016 | +7 -1 | Comments: 1'); + expect(mockTransport.say).toHaveBeenCalledWith('#channel', + '[YouTube] Video Title | Length: 3:33 | Channel: AYouTubeChannel | ' + + 'Uploaded: 2013-10-06 | Views: 1016 | +7 -1 | Comments: 1'); done(); }, 50); }); - it('should search for YouTube videos on command', function (done) { - var scope = nock('https://www.googleapis.com') + it('should search for YouTube videos on command', (done) => { + const scope = nock('https://www.googleapis.com') .get('/youtube/v3/search') .query({ part: 'id', @@ -70,9 +74,12 @@ describe('YouTube plugin', function () { args: 'search phrase', }); - setTimeout(function () { + setTimeout(() => { expect(scope.isDone()).toBeTruthy(); - expect(mockTransport.say).toHaveBeenCalledWith('#channel', '[YouTube] Video Title | https://www.youtube.com/watch?v=123456789 | Length: 3:33 | Channel: AYouTubeChannel | Uploaded: 2013-10-06 | Views: 1016 | +7 -1 | Comments: 1'); + expect(mockTransport.say).toHaveBeenCalledWith('#channel', + '[YouTube] Video Title | https://www.youtube.com/watch?v=123456789 | ' + + 'Length: 3:33 | Channel: AYouTubeChannel | Uploaded: 2013-10-06 | ' + + 'Views: 1016 | +7 -1 | Comments: 1'); done(); }, 50); }); From 7f6a06386c4c8545ea5d064a5099e86a7b845ea8 Mon Sep 17 00:00:00 2001 From: saltire sable Date: Tue, 12 Sep 2017 16:03:24 -0400 Subject: [PATCH 06/10] Update plugins to ES6 --- .eslintrc.json | 5 +- plugins/admin.js | 34 ++-- plugins/broadcast.js | 54 +++--- plugins/ddg-scraper.js | 36 ++-- plugins/echo.js | 6 +- plugins/eightball.js | 25 ++- plugins/errortest.js | 6 +- plugins/modes.js | 20 +- plugins/nickserv.js | 78 ++++---- plugins/quote.js | 121 ++++++------ plugins/rss.js | 401 +++++++++++++++++++++------------------ plugins/sed.js | 41 ++-- plugins/url.js | 47 +++-- plugins/youtube.js | 146 ++++++++------ spec/plugins/sed-spec.js | 2 +- 15 files changed, 566 insertions(+), 456 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 47bc5b6..38bbdf1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,13 +9,16 @@ "rules": { "brace-style": [2, "stroustrup"], "consistent-return": 0, + "curly": [2, "all"], "function-paren-newline": 0, "import/no-dynamic-require": 0, "indent": [2, 4], "max-len": [2, 100], "no-multi-assign": 0, + "no-multi-spaces": [2, {"ignoreEOLComments": true}], "no-shadow": [2, {"allow": ["err"]}], "no-underscore-dangle": [2, {"allowAfterThis": true}], - "object-curly-spacing": [2, "never"] + "object-curly-spacing": [2, "never"], + "radix": [2, "as-needed"] } } diff --git a/plugins/admin.js b/plugins/admin.js index eb6bbf2..470b229 100644 --- a/plugins/admin.js +++ b/plugins/admin.js @@ -1,35 +1,43 @@ -module.exports = function (plugin) { +'use strict'; + +module.exports = function adminPlugin(plugin) { // TODO: Currently we can only join or part channels on the same network // that we send the command from. We should be able to join/part channels // on other connected networks as well. Example: .join oftc/#torontocrypto // TODO: Admins should be configured on the transport level, not the global level. - plugin.addCommand(['join'], function (cmd) { - if (plugin.config.get('admins').indexOf(cmd.from) == -1) return; + plugin.addCommand(['join'], (cmd) => { + if (plugin.config.get('admins').indexOf(cmd.from) === -1) { + return; + } - var channel = cmd.args.split(/\s+/)[0]; + const channel = cmd.args.split(/\s+/)[0]; if (cmd.transport.channels.indexOf(channel) > -1) { return cmd.transport.say(cmd.replyto, "I'm already in that channel."); } - cmd.transport.join(channel, function () { + cmd.transport.join(channel, () => { cmd.transport.say(cmd.replyto, 'Joined %s.', channel); }); }); - plugin.addCommand(['part', 'leave'], function (cmd) { - if (plugin.config.get('admins').indexOf(cmd.from) == -1) return; + plugin.addCommand(['part', 'leave'], (cmd) => { + if (plugin.config.get('admins').indexOf(cmd.from) === -1) { + return; + } - var args = cmd.args.match(/^(\S+)(?:\s+(.*))?/); - if (!args) return; + const args = cmd.args.match(/^(\S+)(?:\s+(.*))?/); + if (!args) { + return; + } - var channel = args[1]; - var message = args[2] || null; + const channel = args[1]; + const message = args[2] || null; - if (cmd.transport.channels.indexOf(channel) == -1) { + if (cmd.transport.channels.indexOf(channel) === -1) { return cmd.transport.say(cmd.replyto, "I'm not in that channel."); } - cmd.transport.leave(channel, message, function () { + cmd.transport.leave(channel, message, () => { cmd.transport.say(cmd.replyto, 'Left %s.', channel); }); }); diff --git a/plugins/broadcast.js b/plugins/broadcast.js index 4329b5c..43b45b6 100644 --- a/plugins/broadcast.js +++ b/plugins/broadcast.js @@ -1,51 +1,57 @@ -var extend = require('extend'); -var handlebars = require('handlebars'); +'use strict'; +const extend = require('extend'); +const handlebars = require('handlebars'); -var default_template = '[{{{source}}}] <{{{from_name}}}> {{{text}}}'; -module.exports = function (plugin) { +const defaultTemplate = '[{{{source}}}] <{{{from_name}}}> {{{text}}}'; + +module.exports = function broadcastPlugin(plugin) { function broadcastToTargets(msg, targets) { - var render_template = handlebars.compile(plugin.config.get('plugins.broadcast.template', default_template)); + const renderTemplate = handlebars.compile( + plugin.config.get('plugins.broadcast.template', defaultTemplate)); - targets.forEach(function (target) { + targets.forEach((target) => { // Don't send to the same target the message came from. - if (target.transport == msg.transport.name && target.channel == msg.replyto) return; + if (target.transport === msg.transport.name && target.channel === msg.replyto) { + return; + } // Don't send to networks/channels we aren't currently in. - if (!(target.transport in plugin.transports)) return; - if (plugin.transports[target.transport].channels.indexOf(target.channel) === -1) return; + if (!plugin.transports[target.transport] || + plugin.transports[target.transport].channels.indexOf(target.channel) === -1) { + return; + } // 'source' is the channel name, with transport prepended if it's not the current one. - var data = extend({ - source: (target.transport != msg.transport.name ? (msg.transport.name + ':') : '') + - (msg.replyto_name || msg.replyto), + const data = extend({ + source: (target.transport !== msg.transport.name ? + `${msg.transport.name}:` : '') + (msg.replyto_name || msg.replyto), }, msg); - plugin.transports[target.transport].say(target.channel, render_template(data)); + plugin.transports[target.transport].say(target.channel, renderTemplate(data)); }); } - plugin.listen('.*', function (msg) { + plugin.listen('.*', (msg) => { if (plugin.config.get('plugins.broadcast.broadcast_all')) { // Build a set of targets from all joined channels in all connected networks. - var targets = []; - for (var tpname in plugin.transports) { - plugin.transports[tpname].channels.forEach(function (channel) { + const targets = []; + Object.keys(plugin.transports).forEach((tpName) => { + plugin.transports[tpName].channels.forEach((channel) => { targets.push({ - transport: tpname, - channel: channel, + transport: tpName, + channel, }); }); - } + }); broadcastToTargets(msg, targets); } else { // Iterate through the configured sets of targets that will be broadcast to each other. - plugin.config.get('plugins.broadcast.target_sets', []).forEach(function (targets) { + plugin.config.get('plugins.broadcast.target_sets', []).forEach((targets) => { // Check that the received message's target is in this set. - if (targets.some(function (target) { - return target.transport == msg.transport.name && target.channel == msg.replyto; - })) { + if (targets.some(target => (target.transport === msg.transport.name && + target.channel === msg.replyto))) { broadcastToTargets(msg, targets); } }); diff --git a/plugins/ddg-scraper.js b/plugins/ddg-scraper.js index 33a06cd..b5423b2 100644 --- a/plugins/ddg-scraper.js +++ b/plugins/ddg-scraper.js @@ -1,34 +1,42 @@ -var cheerio = require('cheerio'); -var handlebars = require('handlebars'); -var request = require('request'); +'use strict'; +const cheerio = require('cheerio'); +const handlebars = require('handlebars'); +const request = require('request'); -var default_template = '[DDG] {{{title}}} | {{url}}'; -module.exports = function (plugin) { - plugin.addCommand(['ddg', 'duck', 's', 'search'], function (cmd) { +const defaultTemplate = '[DDG] {{{title}}} | {{url}}'; + +module.exports = function ddgScraperPlugin(plugin) { + plugin.addCommand(['ddg', 'duck', 's', 'search'], (cmd) => { plugin.log('Got search term:', cmd.args); // DuckDuckGo has no public API for their syndicated search results. // So let's scrape the page! Of course this will break if they change their layout. - request.get('https://duckduckgo.com/html?q=' + cmd.args, function (err, res, body) { - if (err) return plugin.error('Error searching DuckDuckGo:', err.message); - if (res.statusCode != 200) return plugin.error('Got status code %d searching DuckDuckGo', res.statusCode); + request.get(`https://duckduckgo.com/html?q=${cmd.args}`, (err, res, body) => { + if (err) { + return plugin.error('Error searching DuckDuckGo:', err.message); + } + if (res.statusCode !== 200) { + return plugin.error('Got status code %d searching DuckDuckGo', res.statusCode); + } - var $ = cheerio.load(body), - $result = $('div#links .web-result').not('.web-result-sponsored').first().find('a.large'); + const $ = cheerio.load(body); + const $result = $('div#links .web-result').not('.web-result-sponsored').first() + .find('a.large'); if (!$result.length) { return cmd.transport.say(cmd.replyto, 'No search results found for %s.', cmd.args); } - var data = { + const data = { title: $result.text(), url: $result.attr('href'), }; - var render_template = handlebars.compile(plugin.config.get('plugins.ddg-scraper.template', default_template)); + const renderTemplate = handlebars.compile( + plugin.config.get('plugins.ddg-scraper.template', defaultTemplate)); plugin.log('Echoing search result for %s:', cmd.args, data.url); - cmd.transport.say(cmd.replyto, render_template(data)); + cmd.transport.say(cmd.replyto, renderTemplate(data)); }); }); }; diff --git a/plugins/echo.js b/plugins/echo.js index 16bac0d..8177deb 100644 --- a/plugins/echo.js +++ b/plugins/echo.js @@ -1,6 +1,8 @@ +'use strict'; + // simple echo plugin for testing purposes -module.exports = function (plugin) { - plugin.listen('.*', function (msg) { +module.exports = function echoPlugin(plugin) { + plugin.listen('.*', (msg) => { msg.transport.say(msg.replyto, msg.text); }); }; diff --git a/plugins/eightball.js b/plugins/eightball.js index 4a8cbed..7387d88 100644 --- a/plugins/eightball.js +++ b/plugins/eightball.js @@ -1,16 +1,23 @@ -var fs = require('fs'); +'use strict'; -module.exports = function (plugin) { - var filename = plugin.config.get('plugins.eightball.response_file', './plugins/data/eightball.txt'); +const fs = require('fs'); - fs.readFile(filename, {encoding: 'UTF-8'}, function (err, data) { - if (err) return plugin.error("Couldn't find 8-ball response file."); +module.exports = function eightballPlugin(plugin) { + const filename = plugin.config.get('plugins.eightball.response_file', + './plugins/data/eightball.txt'); - var data = (data || '').trim(); - if (!data) return plugin.error('No 8-ball responses found.'); + fs.readFile(filename, {encoding: 'UTF-8'}, (err, data) => { + if (err) { + return plugin.error("Couldn't find 8-ball response file."); + } - var resps = data.split('\n'); - plugin.addCommand(['8', '8ball', 'eightball'], function (cmd) { + const trimData = (data || '').trim(); + if (!trimData) { + return plugin.error('No 8-ball responses found.'); + } + + const resps = trimData.split('\n'); + plugin.addCommand(['8', '8ball', 'eightball'], (cmd) => { cmd.transport.say(cmd.replyto, resps[Math.floor(Math.random() * resps.length)]); }); }); diff --git a/plugins/errortest.js b/plugins/errortest.js index 9313a11..9ff5447 100644 --- a/plugins/errortest.js +++ b/plugins/errortest.js @@ -1,5 +1,7 @@ -module.exports = function (plugin) { - plugin.addCommand('error', function (cmd) { +'use strict'; + +module.exports = function errorTestPlugin(plugin) { + plugin.addCommand('error', () => { throw new Error('This is a test error.'); }); }; diff --git a/plugins/modes.js b/plugins/modes.js index 42d2048..e52e613 100644 --- a/plugins/modes.js +++ b/plugins/modes.js @@ -1,15 +1,17 @@ -module.exports = function (plugin) { - var modes = plugin.config.get('plugins.modes', {}); - for (tpname in modes) { - var transport = plugin.transports[tpname]; +'use strict'; + +module.exports = function modesPlugin(plugin) { + const modes = plugin.config.get('plugins.modes', {}); + Object.keys(modes).forEach((tpName) => { + const transport = plugin.transports[tpName]; if (!transport || !transport.irc) { - continue; + return; } // Wait for the IRC registered event, and send a raw MODE command. - transport.on('registered', function () { - plugin.log('Setting mode on %s:', tpname, modes[tpname]); - transport.irc.send('MODE', modes[tpname]); + transport.on('registered', () => { + plugin.log('Setting mode on %s:', tpName, modes[tpName]); + transport.irc.send('MODE', modes[tpName]); }); - } + }); }; diff --git a/plugins/nickserv.js b/plugins/nickserv.js index fe77015..d82a31c 100644 --- a/plugins/nickserv.js +++ b/plugins/nickserv.js @@ -1,46 +1,21 @@ -var extend = require('extend'); +'use strict'; +const extend = require('extend'); -var default_opts = { + +const defaultOpts = { nick_first: false, password: '', success: 'You are successfully identified', channels: [], - channel_keywords: {} -}; - - -module.exports = function (plugin) { - for (tpname in plugin.config.get('plugins.nickserv.networks', {})) { - var transport = plugin.transports[tpname]; - if (!transport || !transport.irc) { - continue; - } - - // Wait for IRC registered event, and send identify info. - transport.irc.on('registered', function () { - var nick = plugin.config.get('transports.' + tpname + '.nick', ''); - var password = plugin.config.get('plugins.nickserv.networks.' + tpname + '.password', - ''); - - plugin.log('%s: Sending IDENTIFY to NickServ.', tpname); - if (plugin.config.get('plugins.nickserv.networks.' + tpname + '.nick_first')) { - transport.say('NickServ', 'IDENTIFY', nick, password); - } - else { - transport.say('NickServ', 'IDENTIFY', password, nick); - } - - waitForIdentifySuccess(plugin, transport); - }); - } + channel_keywords: {}, }; function waitForIdentifySuccess(plugin, transport) { // Wait for an identification notice. - transport.irc.once('notice', function (from, to, text, msg) { - var nsOpts = extend(true, {}, default_opts, - plugin.config.get('plugins.nickserv.networks.' + transport.name, {})); + transport.irc.once('notice', (from, to, text) => { + const nsOpts = extend(true, {}, defaultOpts, + plugin.config.get(`plugins.nickserv.networks.${transport.name}`, {})); if (from !== 'NickServ' || !text.match(nsOpts.success)) { waitForIdentifySuccess(plugin, transport); @@ -50,7 +25,7 @@ function waitForIdentifySuccess(plugin, transport) { plugin.log('%s: Identified with NickServ.', transport.name); // If we don't have our configured nick, request it. - var nick = plugin.config.get('transports.' + transport.name + '.nick'); + const nick = plugin.config.get(`transports.${transport.name}.nick`); if (to !== nick) { plugin.log('%s: Requesting nick %s.', transport.name, nick); transport.irc.send('NICK', nick); @@ -61,12 +36,35 @@ function waitForIdentifySuccess(plugin, transport) { plugin.log('%s: Joining %d NickServ-only channels.', transport.name, nsOpts.channels.length); - nsOpts.channels.forEach(function (channel) { - if (channel in (nsOpts.channel_keywords || {})) { - channel += ' ' + nsOpts.channel_keywords[channel]; - } - transport.irc.join(channel); - }); + nsOpts.channels.forEach(channel => transport.irc.join( + (channel in (nsOpts.channel_keywords || {})) ? + `${channel} ${nsOpts.channel_keywords[channel]}` : channel)); } }); } + +module.exports = function nickServPlugin(plugin) { + Object.keys(plugin.config.get('plugins.nickserv.networks', {})).forEach((tpName) => { + const transport = plugin.transports[tpName]; + if (!transport || !transport.irc) { + return; + } + + // Wait for IRC registered event, and send identify info. + transport.irc.on('registered', () => { + const nick = plugin.config.get(`transports.${tpName}.nick`, ''); + const password = plugin.config.get(`plugins.nickserv.networks.${tpName}.password`, + ''); + + plugin.log('%s: Sending IDENTIFY to NickServ.', tpName); + if (plugin.config.get(`plugins.nickserv.networks.${tpName}.nick_first`)) { + transport.say('NickServ', 'IDENTIFY', nick, password); + } + else { + transport.say('NickServ', 'IDENTIFY', password, nick); + } + + waitForIdentifySuccess(plugin, transport); + }); + }); +}; diff --git a/plugins/quote.js b/plugins/quote.js index 76c201a..cf96e27 100644 --- a/plugins/quote.js +++ b/plugins/quote.js @@ -1,10 +1,12 @@ -var DataStore = require('nedb'); -var extend = require('extend'); -var handlebars = require('handlebars'); -var path = require('path'); +'use strict'; +const DataStore = require('nedb'); +const extend = require('extend'); +const handlebars = require('handlebars'); +const path = require('path'); -var default_templates = { + +const defaultTemplates = { remember: 'Remembered {{from}} saying "{{text}}".', cantRememberWord: 'Sorry, I can\'t remember what {{from}} said about "{{word}}" recently.', cantRememberFrom: 'Sorry, I can\'t remember anything {{from}} said recently.', @@ -16,102 +18,107 @@ var default_templates = { }; -module.exports = function (plugin) { +module.exports = function quotePlugin(plugin) { plugin.db = new DataStore({ - filename: path.join(plugin.config.get('dbdir', ''), 'quote.db'), - autoload: true + filename: path.join(plugin.config.get('dbdir', ''), 'quote.plugin.db'), + autoload: true, }); - var render_template; - - plugin.addCommand('remember', function (cmd) { - var args = cmd.args.split(/\s+/), - nick = args[0], - word = args[1]; + plugin.addCommand('remember', (cmd) => { + const args = cmd.args.split(/\s+/); + const nick = args[0]; + const word = args[1]; - var templates = extend({}, default_templates, + const templates = extend({}, defaultTemplates, plugin.config.get('plugins.quote.templates', {})); // Search the channel buffer for matching messages. - var buffer = (plugin.buffers[cmd.transport.name] || {})[cmd.replyto] || []; - if (!buffer.some(function (msg) { + const buffer = (plugin.buffers[cmd.transport.name] || {})[cmd.replyto] || []; + if (!buffer.some((msg) => { // Exclude command messages. - if (msg.command) return false; - - if ((!nick || msg.from == nick) && - (!word || msg.text.match(new RegExp('\\b' + word + '\\b', 'i')))) { - // Add the transport name. - msg.transport = cmd.transport.name; - - // Store the message in the quote database. - plugin.db.insert(msg, function (err, quote) { - if (err) { - return plugin.log('Error saving quote on %s/%s:', msg.transport, msg.replyto, msg.text); - } - render_template = handlebars.compile(templates.remember); - cmd.transport.say(cmd.replyto, render_template(quote)); - }); + if (msg.command) { + return false; + } + + if ((!nick || msg.from === nick) && + (!word || new RegExp(`\\b${word}\\b`, 'i').test(msg.text))) { + // Add the transport name and store the message in the quote database. + plugin.db.insert(Object.assign(msg, {transport: cmd.transport.name}), + (err, quote) => { + if (err) { + return plugin.log('Error saving quote on %s/%s:', + cmd.transport.name, msg.replyto, msg.text); + } + const renderTemplate = handlebars.compile(templates.remember); + cmd.transport.say(cmd.replyto, renderTemplate(quote)); + }); return true; } + return false; })) { + let renderTemplate; + if (word) { - render_template = handlebars.compile(templates.cantRememberWord); + renderTemplate = handlebars.compile(templates.cantRememberWord); } else if (nick) { - render_template = handlebars.compile(templates.cantRememberFrom); + renderTemplate = handlebars.compile(templates.cantRememberFrom); } else { - render_template = handlebars.compile(templates.cantRemember); + renderTemplate = handlebars.compile(templates.cantRemember); } - cmd.transport.say(cmd.replyto, render_template({ + cmd.transport.say(cmd.replyto, renderTemplate({ from: nick || '', word: word || '', })); } }); - plugin.addCommand('quote', function (cmd) { - var args = cmd.args.split(/\s+/), - nick = args[0], - word = args[1]; + plugin.addCommand('quote', (cmd) => { + const args = cmd.args.split(/\s+/); + const nick = args[0]; + const word = args[1]; - var filter = { + const filter = { transport: cmd.transport.name, replyto: cmd.replyto, }; - if (nick) filter.from = nick; + if (nick) { + filter.from = nick; + } + if (word) { + filter.text = new RegExp(`\\b${word}\\b`, 'i'); + } - plugin.db.find(filter, function (err, quotes) { - if (err) return plugin.error('Error fetching quote:', err.message); + plugin.db.find(filter, (err, quotes) => { + if (err) { + return plugin.error('Error fetching quote:', err.message); + } - var templates = extend({}, default_templates, + const templates = extend({}, defaultTemplates, plugin.config.get('plugins.quote.templates', {})); - var render_template; - - if (word) quotes = quotes.filter(function (quote) { - return quote.text.match(new RegExp('\\b' + word + '\\b', 'i')); - }); + let renderTemplate; if (!quotes.length) { if (word) { - render_template = handlebars.compile(templates.cantQuoteWord); + renderTemplate = handlebars.compile(templates.cantQuoteWord); } else if (nick) { - render_template = handlebars.compile(templates.cantQuoteFrom); + renderTemplate = handlebars.compile(templates.cantQuoteFrom); } else { - render_template = handlebars.compile(templates.cantQuote); + renderTemplate = handlebars.compile(templates.cantQuote); } - cmd.transport.say(cmd.replyto, render_template({ + cmd.transport.say(cmd.replyto, renderTemplate({ from: nick || '', word: word || '', })); return; } - var quote = quotes[Math.floor(Math.random() * quotes.length)]; - render_template = handlebars.compile(templates.quote); - cmd.transport.say(cmd.replyto, render_template(quote)); + const quote = quotes[Math.floor(Math.random() * quotes.length)]; + renderTemplate = handlebars.compile(templates.quote); + cmd.transport.say(cmd.replyto, renderTemplate(quote)); }); }); }; diff --git a/plugins/rss.js b/plugins/rss.js index a4511aa..db90b2a 100644 --- a/plugins/rss.js +++ b/plugins/rss.js @@ -1,36 +1,38 @@ -var Entities = require('html-entities').AllHtmlEntities; -var extend = require('extend'); -var FeedParser = require('feedparser'); -var handlebars = require('handlebars'); -var irc = require('irc'); -var DataStore = require('nedb'); -var path = require('path'); -var request = require('request'); +'use strict'; +const Entities = require('html-entities').AllHtmlEntities; +const extend = require('extend'); +const FeedParser = require('feedparser'); +const handlebars = require('handlebars'); +const irc = require('irc'); +const DataStore = require('nedb'); +const path = require('path'); +const request = require('request'); -var defaults = { + +const defaults = { interval: 10, item_template: '[{{color}}{{name}}{{reset}}] {{{title}}} | {{url}}', - list_template: ' {{transport}} {{target}} {{color}}{{name}}{{reset}} {{url}}' + list_template: ' {{transport}} {{target}} {{color}}{{name}}{{reset}} {{url}}', }; -var interval; -var intervalObj; +let interval; +let intervalObj; -var entities = new Entities(); +const entities = new Entities(); -module.exports = function (plugin) { +module.exports = function rssPlugin(plugin) { plugin.db = new DataStore({ filename: path.join(plugin.config.get('dbdir', ''), 'rss.db'), autoload: true, }); // fetch a feed and reply with the latest entry - plugin.fetchLatestItem = function (feed, callback) { + plugin.fetchLatestItem = (feed, callback) => { plugin.log('[%s] Fetching feed at', feed.name, feed.url); // Build cache validation headers. - var headers = {}; + const headers = {}; if (feed.etag) { plugin.log('[%s] Sending saved etag:', feed.name || '', feed.etag); headers['If-None-Match'] = feed.etag; @@ -41,107 +43,123 @@ module.exports = function (plugin) { headers['If-Modified-Since'] = feed.last_modified; } - var parser = new FeedParser({}); - - request.get({ - url: feed.url, - headers: headers - }) - .on('error', function (e) { - plugin.error('RSS request error:', e.message); - }) - .on('response', function (res) { - plugin.log('[%s] Received %d response', feed.name, res.statusCode); - - if (res.statusCode == 304) return; - if (res.statusCode != 200) return plugin.error('Bad status code %d from RSS feed', res.statusCode, feed.name); - - if (feed.name) { - // Save cache validation headers. - var update = null; - if (res.headers.etag) { - plugin.log('[%s] Got etag:', feed.name, res.headers.etag); - update = extend(update || {}, {etag: res.headers.etag}); + const parser = new FeedParser({}); + + request.get( + { + url: feed.url, + headers, + }) + .on('error', (err) => { + plugin.error('RSS request error:', err.message); + }) + .on('response', (res) => { + plugin.log('[%s] Received %d response', feed.name, res.statusCode); + + if (res.statusCode === 304) { + return; } - if (res.headers['last-modified']) { - plugin.log('[%s] Got last-modified date:', feed.name, res.headers['last-modified']); - update = extend(update || {}, {last_modified: res.headers['last-modified']}); + if (res.statusCode !== 200) { + return plugin.error('Bad status code %d from RSS feed', + res.statusCode, feed.name); } - if (update !== null) { - plugin.db.update({ - transport: feed.transport, - target: feed.target, - name: feed.name, - }, { - $set: update, - }, {}, function (err, count) { - if (count) plugin.log('[%s] Updated cache headers.', feed.name); - }); + + if (feed.name) { + // Save cache validation headers. + let update; + if (res.headers.etag) { + plugin.log('[%s] Got etag:', feed.name, res.headers.etag); + update = extend(update || {}, {etag: res.headers.etag}); + } + if (res.headers['last-modified']) { + plugin.log('[%s] Got last-modified date:', + feed.name, res.headers['last-modified']); + update = extend(update || {}, + {last_modified: res.headers['last-modified']}); + } + if (update) { + plugin.db.update( + { + transport: feed.transport, + target: feed.target, + name: feed.name, + }, + {$set: update}, + {}, + (err, count) => { + if (count) { + plugin.log('[%s] Updated cache headers.', feed.name); + } + }); + } } - } - res.pipe(parser); - }); + res.pipe(parser); + }); parser - .on('error', function (err) { - plugin.error('RSS parser error:', err.message); - callback(err, null); - }) - .once('readable', function () { - var item = parser.read(); - - plugin.log('[%s] Got latest item from feed.', feed.name || ''); - callback(null, { - title: entities.decode(item.title), - url: item.link, + .on('error', (err) => { + plugin.error('RSS parser error:', err.message); + callback(err, null); + }) + .once('readable', () => { + const item = parser.read(); + + plugin.log('[%s] Got latest item from feed.', feed.name || ''); + callback(null, { + title: entities.decode(item.title), + url: item.link, + }); }); - }); - }; + } - plugin.displayItem = function (feed, item) { - var render_item_template = handlebars.compile( + function displayItem(feed, item) { + const renderItemTemplate = handlebars.compile( plugin.config.get('plugins.rss.item_template', defaults.item_template)); - var data = extend({ + const data = extend({ name: feed.name, color: irc.colors.codes[feed.color] || feed.color, }, item, irc.colors.codes); plugin.log('[%s] Displaying link:', feed.name, item.link); - plugin.transports[feed.transport].say(feed.target, render_item_template(data)); - }; + plugin.transports[feed.transport].say(feed.target, renderItemTemplate(data)); + } - plugin.updateFeed = function (feed, item) { + function updateFeed(feed, item) { // Save latest item to database. - plugin.db.update({ - transport: feed.transport, - target: feed.target, - name: feed.name, - }, { - $set: { - latest: item, - } - }, {}, function (err, count) { - if (count) plugin.log('[%s] Saved latest item.', feed.name); - }); - }; + plugin.db.update( + { + transport: feed.transport, + target: feed.target, + name: feed.name, + }, + {$set: {latest: item}}, + {}, + (err, count) => { + if (count) { + plugin.log('[%s] Saved latest item.', feed.name); + } + }); + } - plugin.itemsEqual = function (item1, item2) { + function itemsEqual(item1, item2) { return item1 && item2 && (item1.title === item2.title) && (item1.url === item2.url); - }; + } function findFeeds(cmd, callback) { - var args = cmd.args.match(/^\S+(?:\s+("[^"]+"|[\w-]+))?/); + const args = cmd.args.match(/^\S+(?:\s+("[^"]+"|[\w-]+))?/); // fetch feeds from all channels (admin only) - var all = args[1] == 'all' && plugin.config.get('admins', []).indexOf(cmd.nick) > -1; + const all = args[1] === 'all' && plugin.config.get('admins', []).indexOf(cmd.nick) > -1; - var filter = { + const filter = { transport: cmd.transport.name, target: cmd.replyto, }; - if (args[1] && args[1] != 'all') filter.name = args[1].replace(/^"|"$/g, ''); + if (args[1] && args[1] !== 'all') { + filter.name = args[1].replace(/^"|"$/g, ''); + } plugin.db.find(all ? {} : filter, callback); } @@ -153,19 +171,21 @@ module.exports = function (plugin) { intervalObj = null; } - interval = newInterval || parseInt(plugin.config.get('plugins.rss.interval')) || interval || defaults.interval; + interval = newInterval || parseInt(plugin.config.get('plugins.rss.interval')) || + interval || defaults.interval; if (!intervalObj) { - intervalObj = setInterval(function () { + intervalObj = setInterval(() => { plugin.log('Fetching RSS feeds...'); - plugin.db.find({}, function (err, feeds) { - feeds.forEach(function (feed) { - plugin.fetchLatestItem(feed, function (err, item) { - if (feed.latest && plugin.itemsEqual(feed.latest, item)) { - return plugin.log('[%s] Latest item is the same as last fetch; skipping.', feed.name || ''); + plugin.db.find({}, (err, feeds) => { + feeds.forEach((feed) => { + plugin.fetchLatestItem(feed, (err, item) => { + if (feed.latest && itemsEqual(feed.latest, item)) { + return plugin.log(`[${feed.name || ''}]`, + 'Latest item is the same as last fetch; skipping.'); } - plugin.displayItem(feed, item); - plugin.updateFeed(feed, item); + displayItem(feed, item); + updateFeed(feed, item); }); }); }); @@ -176,148 +196,164 @@ module.exports = function (plugin) { } } - plugin.addCommand('rss', function (cmd) { - var m = cmd.args.match(/^(\w+)(?:\s+(.*))?$/); - var action = m && m[1]; - var args = m && m[2]; + plugin.addCommand('rss', (cmd) => { + const m = cmd.args.match(/^(\w+)(?:\s+(.*))?$/); + const action = m && m[1]; + const args = m && m[2]; switch (action) { - - case 'add': + case 'add': { // Feed names can contain spaces as long as they are wrapped in double quotes. - var addArgs = args && args.match(/^("[^"]+"|[\w-]+)\s+(https?:\/\/\S+\.\S+)(?:\s+(\w+))?/); + const addArgs = args && + args.match(/^("[^"]+"|[\w-]+)\s+(https?:\/\/\S+\.\S+)(?:\s+(\w+))?/); if (!addArgs) { cmd.transport.say(cmd.replyto, 'Usage: .rss add []'); break; } - var name = addArgs[1], - url = addArgs[2], - color = addArgs[3] || ''; + const rawName = addArgs[1]; + const url = addArgs[2]; + const color = addArgs[3] || ''; - if (name == 'all') { + if (rawName === 'all') { cmd.transport.say(cmd.replyto, 'Invalid feed name.'); break; } - name = name.replace(/^"|"$/g, ''); + const name = rawName.replace(/^"|"$/g, ''); - plugin.db.find({ - transport: cmd.transport.name, - target: cmd.replyto, - $or: [ - {name: name}, - {url: url}, - ], - }, function (err, feeds) { - if (err) return plugin.error('Error checking for feed:', err.message); - - // check against existing feeds for this target - if (feeds.length) { - cmd.transport.say(cmd.replyto, - 'A feed already exists with that %s.', feeds[0].name == name ? 'name' : 'URL'); - } - else { - // add the feed to the list for this target - plugin.db.insert({ - transport: cmd.transport.name, - target: cmd.replyto, - name: name, - url: url, - color: color, - }, function (err, feed) { - if (err) return plugin.error('Error saving feed:', err.message); - if (feed) cmd.transport.say(cmd.replyto, 'Added 1 feed.'); - }); - } - }); + plugin.db.find( + { + transport: cmd.transport.name, + target: cmd.replyto, + $or: [ + {name}, + {url}, + ], + }, + (err, feeds) => { + if (err) { + return plugin.error('Error checking for feed:', err.message); + } - break; + // check against existing feeds for this target + if (feeds.length) { + cmd.transport.say(cmd.replyto, 'A feed already exists with that %s.', + feeds[0].name === name ? 'name' : 'URL'); + } + else { + // add the feed to the list for this target + plugin.db.insert( + { + transport: cmd.transport.name, + target: cmd.replyto, + name, + url, + color, + }, + (err, feed) => { + if (err) { + return plugin.error('Error saving feed:', err.message); + } + if (feed) { + cmd.transport.say(cmd.replyto, 'Added 1 feed.'); + } + }); + } + }); + break; + } case 'del': case 'delete': case 'remove': - case 'rm': - var delArgs = args && args.match(/^("[^"]+"|[\w-]+)?/); + case 'rm': { + const delArgs = args && args.match(/^("[^"]+"|[\w-]+)?/); if (delArgs && delArgs[1]) { - plugin.db.remove({ - transport: cmd.transport.name, - target: cmd.replyto, - name: delArgs[1].replace(/^"|"$/g, ''), - }, function (err, count) { - if (count) cmd.transport.say(cmd.replyto, 'Removed 1 feed.'); - else cmd.transport.say(cmd.replyto, 'No matching feed found.'); - }); + plugin.db.remove( + { + transport: cmd.transport.name, + target: cmd.replyto, + name: delArgs[1].replace(/^"|"$/g, ''), + }, + (err, count) => { + if (count) { + cmd.transport.say(cmd.replyto, 'Removed 1 feed.'); + } + else { + cmd.transport.say(cmd.replyto, 'No matching feed found.'); + } + }); } break; - + } case 'list': - findFeeds(cmd, function (err, feeds) { + findFeeds(cmd, (err, feeds) => { cmd.transport.say(cmd.replyto, 'Found %d feed%s.', feeds.length, - feeds.length == 1 ? '' : 's'); + feeds.length === 1 ? '' : 's'); - var render_list_template = handlebars.compile(plugin.config.get('plugins.rss.list_template', defaults.list_template)); + const renderListTemplate = handlebars.compile( + plugin.config.get('plugins.rss.list_template', defaults.list_template)); - feeds.forEach(function (feed) { + feeds.forEach((feed) => { // add colors to feed info for use as template data - feed.color = irc.colors.codes[feed.color] || ''; - for (color in irc.colors.codes) { - feed[color] = irc.colors.codes[color]; - } + const colorFeed = Object.assign({}, feed, + {color: irc.colors.codes[feed.color] || ''}); + Object.keys(irc.colors.codes).forEach((color) => { + colorFeed[color] = irc.colors.codes[color]; + }); - cmd.transport.say(cmd.replyto, render_list_template(feed)); + cmd.transport.say(cmd.replyto, renderListTemplate(colorFeed)); }); }); break; - - case 'quick': - var feed = { + case 'quick': { + const feed = { name: 'QUICK', transport: cmd.transport.name, target: cmd.replyto, url: args, }; - plugin.fetchLatestItem(feed, function (err, item) { - plugin.displayItem(feed, item); - }); + plugin.fetchLatestItem(feed, (err, item) => displayItem(feed, item)); break; - + } case 'fetch': case 'latest': // Manually fetch feeds and display latest item. // 'latest' will always display; 'fetch' will only display if new. - findFeeds(cmd, function (err, feeds) { - feeds.forEach(function (feed) { - plugin.fetchLatestItem(feed, function (err, item) { - if (action === 'fetch' && feed.latest && plugin.itemsEqual(feed.latest, item)) { - return plugin.log('[%s] Latest item is the same as last fetch; skipping.', feed.name || ''); + findFeeds(cmd, (err, feeds) => { + feeds.forEach((feed) => { + plugin.fetchLatestItem(feed, (err, item) => { + if (action === 'fetch' && feed.latest && itemsEqual(feed.latest, item)) { + return plugin.log(`[${feed.name || ''}]`, + 'Latest item is the same as last fetch; skipping.'); } - plugin.displayItem(feed, item); - plugin.updateFeed(feed, item); + displayItem(feed, item); + updateFeed(feed, item); }); }); }); break; - - case 'start': - var started = startFetching(parseInt(args && args[1])); + case 'start': { + const started = startFetching(parseInt(args && args[1])); cmd.transport.say(cmd.replyto, - (started ? 'Starting to fetch feeds every %d minutes.' : 'Feeds are already being fetched every %d minutes.'), + (started ? 'Starting to fetch feeds every %d minutes.' : + 'Feeds are already being fetched every %d minutes.'), interval); break; - + } case 'stop': if (intervalObj) { @@ -328,14 +364,13 @@ module.exports = function (plugin) { break; - case 'colors': case 'colours': - cmd.transport.say(cmd.replyto, 'Available colors:', Object.keys(irc.colors.codes).join(', ')); + cmd.transport.say(cmd.replyto, 'Available colors:', + Object.keys(irc.colors.codes).join(', ')); break; - default: break; } diff --git a/plugins/sed.js b/plugins/sed.js index 0e5beb3..49f1141 100644 --- a/plugins/sed.js +++ b/plugins/sed.js @@ -1,32 +1,35 @@ -module.exports = function (plugin) { - var pattern = new RegExp( - '(?:(\\S+):\\s+)?' + // optional nick + colon - '\\bs\\/' + // s + slash - '((?:\\\\/|[^\/])+)\\/' + // search term + slash - '((?:\\\\/|[^\\/])*)' + // optional replacement - '(?:\\/(\\S+))?' // optional slash + flags - ); +'use strict'; - plugin.listen(pattern, function (msg) { - var other = msg.match[1], - search = msg.match[2], - replace = msg.match[3], - flags = msg.match[4]; +module.exports = function sedPlugin(plugin) { + const pattern = new RegExp( + '(?:(\\S+):\\s+)?' + // optional nick + colon + '\\bs\\/' + // s + slash + '((?:\\\\/|[^/])+)\\/' + // search term + slash + '((?:\\\\/|[^\\/])*)' + // optional replacement + '(?:\\/(\\S+))?'); // optional slash + flags - var regex = new RegExp(search, flags); + plugin.listen(pattern, (msg) => { + const other = msg.match[1]; + const search = msg.match[2]; + const replace = msg.match[3]; + const flags = msg.match[4]; - plugin.buffers[msg.transport.name][msg.replyto].slice(1).some(function (oldmsg) { + const regex = new RegExp(search, flags); + + plugin.buffers[msg.transport.name][msg.replyto].slice(1).some((oldmsg) => { // check for messages from the other nick if specified, else the same nick // try not to match other substitutions, i.e. messages starting with s/ - if (oldmsg.from == (other || msg.from) && oldmsg.text.slice(0, 2) != 's/') { - var m = oldmsg.text.match(regex); + if (oldmsg.from === (other || msg.from) && oldmsg.text.slice(0, 2) !== 's/') { + const m = oldmsg.text.match(regex); if (m) { // send a message back to the channel with the correction - var pre = other ? (msg.from + ' thinks ' + other) : msg.from - msg.transport.say(msg.replyto, pre + ' meant to say: ' + oldmsg.text.replace(regex, replace)); + msg.transport.say(msg.replyto, other ? `${msg.from} thinks ${other}` : msg.from, + 'meant to say:', oldmsg.text.replace(regex, replace)); return true; } } + + return false; }); }); }; diff --git a/plugins/url.js b/plugins/url.js index cb1234e..1e79dd6 100644 --- a/plugins/url.js +++ b/plugins/url.js @@ -1,37 +1,42 @@ -var handlebars = require('handlebars'); -var Entities = require('html-entities').AllHtmlEntities; -var request = require('request'); +'use strict'; +const handlebars = require('handlebars'); +const Entities = require('html-entities').AllHtmlEntities; +const request = require('request'); -var default_template = '[ {{{title}}} ] - {{domain}}'; -var entities = new Entities(); +const defaultTemplate = '[ {{{title}}} ] - {{domain}}'; -module.exports = function (plugin) { +const entities = new Entities(); + +module.exports = function urlPlugin(plugin) { // fetch title for URLs and echo it into the channel - plugin.listen(/https?:\/\/([^\/\s]+)\S*/i, function (msg) { + plugin.listen(/https?:\/\/([^/\s]+)\S*/i, (msg) => { // cancel if the message matches any patterns placed in url_exclusions by other plugins - if ((plugin.memory.url_exclusions || []).some(function (pattern) { - return !!msg.text.match(pattern); - })) return; + if ((plugin.memory.url_exclusions || []).some(pattern => !!msg.text.match(pattern))) { + return; + } - var url = msg.match[0], - domain = msg.match[1]; + const url = msg.match[0]; + const domain = msg.match[1]; plugin.log('Got URL:', url); - request.get(url, function (err, res, body) { - if (!err && res.statusCode == 200) { - var t = body.match(/\s*(.*?)\s*<\/title>/i); - if (!t || !t[1].length) return; + request.get(url, (err, res, body) => { + if (!err && res.statusCode === 200) { + const t = body.match(/<title>\s*(.*?)\s*<\/title>/i); + if (!t || !t[1].length) { + return; + } - var data = { - domain: domain, + const data = { + domain, title: entities.decode(t[1]), - url: url, + url, }; - var render_template = handlebars.compile(plugin.config.get('plugins.url.template', default_template)); + const renderTemplate = handlebars.compile(plugin.config.get('plugins.url.template', + defaultTemplate)); plugin.log('Echoing URL title:', data.title); - msg.transport.say(msg.replyto, render_template(data)); + msg.transport.say(msg.replyto, renderTemplate(data)); } }); }); diff --git a/plugins/youtube.js b/plugins/youtube.js index b70e9b8..af7f533 100644 --- a/plugins/youtube.js +++ b/plugins/youtube.js @@ -1,77 +1,56 @@ -var handlebars = require('handlebars'); -var moment = require('moment-timezone'); -var youtube = require('googleapis').youtube('v3'); +'use strict'; +const handlebars = require('handlebars'); +const moment = require('moment-timezone'); +const youtube = require('googleapis').youtube('v3'); -var defaults = { - url_template: '[YouTube] {{{title}}} | Length: {{length}} | Channel: {{channel}} | Uploaded: {{date}} | Views: {{views}} | +{{likes}} -{{dislikes}} | Comments: {{comments}}', - search_template: '[YouTube] {{{title}}} | {{url}} | Length: {{length}} | Channel: {{channel}} | Uploaded: {{date}} | Views: {{views}} | +{{likes}} -{{dislikes}} | Comments: {{comments}}', - date_format: 'YYYY-MM-DD' + +const defaults = { + url_template: '[YouTube] {{{title}}} | Length: {{length}} | Channel: {{channel}} | ' + + 'Uploaded: {{date}} | Views: {{views}} | +{{likes}} -{{dislikes}} | ' + + 'Comments: {{comments}}', + search_template: '[YouTube] {{{title}}} | {{url}} | Length: {{length}} | ' + + 'Channel: {{channel}} | Uploaded: {{date}} | Views: {{views}} | ' + + '+{{likes}} -{{dislikes}} | Comments: {{comments}}', + date_format: 'YYYY-MM-DD', }; -var url_pattern = /(?:youtube.com\/watch\S*v=|youtu.be\/)([\w-]+)/; +const urlPattern = /(?:youtube.com\/watch\S*v=|youtu.be\/)([\w-]+)/; -module.exports = function (plugin) { +module.exports = function youtubePlugin(plugin) { // add pattern to url exclusions so it doesn't also trigger the url plugin - if (!plugin.memory.url_exclusions) plugin.memory.url_exclusions = []; - plugin.memory.url_exclusions.push(url_pattern); - - plugin.listen(url_pattern, function (msg) { - plugin.log('Got YouTube URL:', msg.match[0]); - var render_template = handlebars.compile(plugin.config.get('plugins.youtube.url_template', defaults.url_template)); - getVideo(msg.match[1], function (data) { - msg.transport.say(msg.replyto, render_template(data)); - }); - }); - - plugin.addCommand(['youtube', 'yt'], function (cmd) { - var apiKey = plugin.config.get('plugins.youtube.api_key'); - if (!apiKey || !cmd.args) return; - - plugin.log('Got YouTube search:', cmd.args); - - youtube.search.list({ - auth: apiKey, - part: 'id', - q: cmd.args, - type: 'video', - maxResults: 1, - }, function (err, result) { - if (err) return plugin.error('Error searching videos:', err.message); - if (!Array.isArray(result.items)) return plugin.error('No items in YouTube results.'); - - plugin.log('Found %d result(s) for:', result.items.length, cmd.args); - if (!result.items.length) { - return cmd.transport.say(cmd.replyto, 'No video results found for %s.', cmd.args); - } - - getVideo(result.items[0].id.videoId, function (data) { - var render_template = handlebars.compile(plugin.config.get('plugins.youtube.search_template', defaults.search_template)); - cmd.transport.say(cmd.replyto, render_template(data)); - }); - }); - }); + if (!plugin.memory.url_exclusions) { + plugin.memory.url_exclusions = []; + } + plugin.memory.url_exclusions.push(urlPattern); function getVideo(id, callback) { - var apiKey = plugin.config.get('plugins.youtube.api_key'); - if (!apiKey) return; + const apiKey = plugin.config.get('plugins.youtube.api_key'); + if (!apiKey) { + return; + } youtube.videos.list({ auth: apiKey, part: 'id,snippet,contentDetails,statistics', - id: id, - }, function (err, result) { - if (err) return plugin.error('Error fetching video details:', err.message); - if (!result.items || !result.items.length) return plugin.error('No data in YouTube request for video', id); + id, + }, (err, result) => { + if (err) { + return plugin.error('Error fetching video details:', err.message); + } + if (!result.items || !result.items.length) { + return plugin.error('No data in YouTube request for video', id); + } plugin.log('Fetched YouTube info for video', id); - var video = result.items[0]; + const video = result.items[0]; // format length - var seconds = moment.duration(video.contentDetails.duration).asSeconds(), - length = moment.utc(0).seconds(seconds).format(seconds >= 3600 ? 'H:mm:ss' : 'm:ss'); + const seconds = moment.duration(video.contentDetails.duration).asSeconds(); + const length = moment.utc(0).seconds(seconds) + .format(seconds >= 3600 ? 'H:mm:ss' : 'm:ss'); - var publishDate = moment(video.snippet.publishedAt); - var timezone = plugin.config.get('plugins.youtube.timezone'); + const publishDate = moment(video.snippet.publishedAt); + const timezone = plugin.config.get('plugins.youtube.timezone'); if (timezone) { publishDate.tz(timezone); } @@ -81,11 +60,12 @@ module.exports = function (plugin) { id: video.id, title: video.snippet.title, description: video.snippet.description, - url: 'https://www.youtube.com/watch?v=' + video.id, - date: publishDate.format(plugin.config.get('plugins.youtube.date_format', defaults.date_format)), + url: `https://www.youtube.com/watch?v=${video.id}`, + date: publishDate.format(plugin.config.get('plugins.youtube.date_format', + defaults.date_format)), channel: video.snippet.channelTitle, tags: video.snippet.tags, - length: length, + length, views: video.statistics.viewCount, likes: video.statistics.likeCount, dislikes: video.statistics.dislikeCount, @@ -94,4 +74,48 @@ module.exports = function (plugin) { }); }); } + + plugin.listen(urlPattern, (msg) => { + plugin.log('Got YouTube URL:', msg.match[0]); + const renderTemplate = handlebars.compile(plugin.config.get('plugins.youtube.url_template', + defaults.url_template)); + getVideo(msg.match[1], (data) => { + msg.transport.say(msg.replyto, renderTemplate(data)); + }); + }); + + plugin.addCommand(['youtube', 'yt'], (cmd) => { + const apiKey = plugin.config.get('plugins.youtube.api_key'); + if (!apiKey || !cmd.args) { + return; + } + + plugin.log('Got YouTube search:', cmd.args); + + youtube.search.list({ + auth: apiKey, + part: 'id', + q: cmd.args, + type: 'video', + maxResults: 1, + }, (err, result) => { + if (err) { + return plugin.error('Error searching videos:', err.message); + } + if (!Array.isArray(result.items)) { + return plugin.error('No items in YouTube results.'); + } + + plugin.log('Found %d result(s) for:', result.items.length, cmd.args); + if (!result.items.length) { + return cmd.transport.say(cmd.replyto, 'No video results found for %s.', cmd.args); + } + + getVideo(result.items[0].id.videoId, (data) => { + const renderTemplate = handlebars.compile( + plugin.config.get('plugins.youtube.search_template', defaults.search_template)); + cmd.transport.say(cmd.replyto, renderTemplate(data)); + }); + }); + }); }; diff --git a/spec/plugins/sed-spec.js b/spec/plugins/sed-spec.js index d75abe6..c88517e 100644 --- a/spec/plugins/sed-spec.js +++ b/spec/plugins/sed-spec.js @@ -32,6 +32,6 @@ describe('Search and replace plugin', () => { text: 's/wrong/right/', }); expect(mockTransport.say).toHaveBeenCalledWith('#channel1', - 'somebody meant to say: This message is right!'); + 'somebody', 'meant to say:', 'This message is right!'); }); }); From e56cb59c4b20687d26d609c63b1519fb71168860 Mon Sep 17 00:00:00 2001 From: saltire sable <saltiresable@gmail.com> Date: Tue, 12 Sep 2017 16:29:07 -0400 Subject: [PATCH 07/10] Small fixes; bot.memory is now Map instead of object --- .eslintrc.json | 2 ++ bot/bot.js | 8 +++++--- plugins/rss.js | 2 +- plugins/url.js | 3 ++- plugins/youtube.js | 6 +++--- spec/helpers/mock-bot.js | 9 ++++----- spec/plugins/modes-spec.js | 3 ++- spec/plugins/rss-spec.js | 3 +-- 8 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 38bbdf1..b54f489 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,8 +12,10 @@ "curly": [2, "all"], "function-paren-newline": 0, "import/no-dynamic-require": 0, + "import/no-extraneous-dependencies": [2, {"devDependencies": ["**/*spec.js"]}], "indent": [2, 4], "max-len": [2, 100], + "newline-per-chained-call": 0, "no-multi-assign": 0, "no-multi-spaces": [2, {"ignoreEOLComments": true}], "no-shadow": [2, {"allow": ["err"]}], diff --git a/bot/bot.js b/bot/bot.js index 589e079..a366b4f 100644 --- a/bot/bot.js +++ b/bot/bot.js @@ -26,9 +26,9 @@ module.exports = class Bot extends EventEmitter { this.config = new Config(configfile); - this.memory = {}; + // A shared key/value store that all plugins can read and write to. + this.memory = new Map(); - // Include extra functionality. this.initLog(); this.initBuffers(); this.initTransports(); @@ -45,7 +45,9 @@ module.exports = class Bot extends EventEmitter { fs.mkdirSync(logdir); } catch (err) { - if (err.code !== 'EEXIST') throw err; + if (err.code !== 'EEXIST') { + throw err; + } } const dateFormat = this.config.get('log.date_format', logDefaults.dateFormat); diff --git a/plugins/rss.js b/plugins/rss.js index db90b2a..2a570b2 100644 --- a/plugins/rss.js +++ b/plugins/rss.js @@ -111,7 +111,7 @@ module.exports = function rssPlugin(plugin) { url: item.link, }); }); - } + }; function displayItem(feed, item) { const renderItemTemplate = handlebars.compile( diff --git a/plugins/url.js b/plugins/url.js index 1e79dd6..f3bec4d 100644 --- a/plugins/url.js +++ b/plugins/url.js @@ -13,7 +13,8 @@ module.exports = function urlPlugin(plugin) { // fetch title for URLs and echo it into the channel plugin.listen(/https?:\/\/([^/\s]+)\S*/i, (msg) => { // cancel if the message matches any patterns placed in url_exclusions by other plugins - if ((plugin.memory.url_exclusions || []).some(pattern => !!msg.text.match(pattern))) { + if ((plugin.memory.get('url_exclusions') || []) + .some(pattern => !!msg.text.match(pattern))) { return; } diff --git a/plugins/youtube.js b/plugins/youtube.js index af7f533..05c6cab 100644 --- a/plugins/youtube.js +++ b/plugins/youtube.js @@ -19,10 +19,10 @@ const urlPattern = /(?:youtube.com\/watch\S*v=|youtu.be\/)([\w-]+)/; module.exports = function youtubePlugin(plugin) { // add pattern to url exclusions so it doesn't also trigger the url plugin - if (!plugin.memory.url_exclusions) { - plugin.memory.url_exclusions = []; + if (!plugin.memory.has('url_exclusions')) { + plugin.memory.set('url_exclusions', []); } - plugin.memory.url_exclusions.push(urlPattern); + plugin.memory.get('url_exclusions').push(urlPattern); function getVideo(id, callback) { const apiKey = plugin.config.get('plugins.youtube.api_key'); diff --git a/spec/helpers/mock-bot.js b/spec/helpers/mock-bot.js index 178067b..f7f3ccb 100644 --- a/spec/helpers/mock-bot.js +++ b/spec/helpers/mock-bot.js @@ -14,18 +14,17 @@ module.exports = class MockBot extends EventEmitter { this.config = new Config(config || {}); - this.plugins = {}; - this.memory = {}; - // Mock out extra bot functionality. + this.memory = new Map(); this.log = winston; this.transports = transports || {}; this.buffers = {}; - - winston.level = 'error'; + this.plugins = {}; Object.keys(this.config.get('plugins', {})).forEach((pluginName) => { this.plugins[pluginName] = new Plugin(this, pluginName); }); + + winston.level = 'error'; } }; diff --git a/spec/plugins/modes-spec.js b/spec/plugins/modes-spec.js index 04f07f4..f9b04d1 100644 --- a/spec/plugins/modes-spec.js +++ b/spec/plugins/modes-spec.js @@ -5,6 +5,7 @@ const MockTransport = require('../helpers/mock-transport'); describe('IRC modes plugin', () => { + let mockBot; // eslint-disable-line no-unused-vars let mockIRC; beforeEach(() => { @@ -13,7 +14,7 @@ describe('IRC modes plugin', () => { send: jasmine.createSpy(), }; - const mockBot = new MockBot( + mockBot = new MockBot( {'plugins.modes.irc': '+B'}, {irc: mockIRC}, ); diff --git a/spec/plugins/rss-spec.js b/spec/plugins/rss-spec.js index 585c9a3..bf90423 100644 --- a/spec/plugins/rss-spec.js +++ b/spec/plugins/rss-spec.js @@ -12,7 +12,6 @@ const MockTransport = require('../helpers/mock-transport'); describe('RSS feed plugin', () => { let mockBot; let irc1; - let irc2; let db; const rssFile = fs.readFileSync(`${__dirname}/../data/feed.rss`, {encoding: 'utf8'}); @@ -26,7 +25,7 @@ describe('RSS feed plugin', () => { 'plugins.rss.item_template': '[{{name}}] {{{title}}} | {{url}}', }); irc1 = mockBot.transports.irc1 = new MockTransport('irc1'); - irc2 = mockBot.transports.irc2 = new MockTransport('irc2'); + mockBot.transports.irc2 = new MockTransport('irc2'); // Set up mock responses. nock('http://feed.com') From e2684e9d3260a4d9db9bebcae207cbbc068b7287 Mon Sep 17 00:00:00 2001 From: saltire sable <saltiresable@gmail.com> Date: Sun, 17 Sep 2017 21:47:49 -0400 Subject: [PATCH 08/10] Use default value if a config value is null --- bot/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/config.js b/bot/config.js index a46652f..8d94118 100644 --- a/bot/config.js +++ b/bot/config.js @@ -85,7 +85,7 @@ module.exports = class Config { get(path, defval) { const value = getValue(this.config, Array.isArray(path) ? path : path.split('.')); - return value !== undefined ? value : defval; + return (value === undefined || value === null) ? defval : value; } set(path, value) { From 5ea3732d4fcf28241c942fa4bdcecd378af5b9fb Mon Sep 17 00:00:00 2001 From: saltire sable <saltiresable@gmail.com> Date: Sun, 17 Sep 2017 23:25:01 -0400 Subject: [PATCH 09/10] Don't broadcast non-text messages or commands --- plugins/broadcast.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/plugins/broadcast.js b/plugins/broadcast.js index 43b45b6..b357827 100644 --- a/plugins/broadcast.js +++ b/plugins/broadcast.js @@ -33,8 +33,14 @@ module.exports = function broadcastPlugin(plugin) { } plugin.listen('.*', (msg) => { + // Don't broadcast non-text messages or commands. + if (!msg.text || msg.command) { + return; + } + if (plugin.config.get('plugins.broadcast.broadcast_all')) { // Build a set of targets from all joined channels in all connected networks. + // For IRC it's all joined channels. For Telegram it's the transport config's chat_ids. const targets = []; Object.keys(plugin.transports).forEach((tpName) => { plugin.transports[tpName].channels.forEach((channel) => { @@ -48,13 +54,10 @@ module.exports = function broadcastPlugin(plugin) { } else { // Iterate through the configured sets of targets that will be broadcast to each other. - plugin.config.get('plugins.broadcast.target_sets', []).forEach((targets) => { - // Check that the received message's target is in this set. - if (targets.some(target => (target.transport === msg.transport.name && - target.channel === msg.replyto))) { - broadcastToTargets(msg, targets); - } - }); + plugin.config.get('plugins.broadcast.target_sets', []) + .filter(targets => targets.some(target => + (target.transport === msg.transport.name && target.channel === msg.replyto))) + .forEach(targets => broadcastToTargets(msg, targets)); } }); }; From e59d3dfe4f563e2bc74ee2838bd786ca8372cc61 Mon Sep 17 00:00:00 2001 From: saltire sable <saltiresable@gmail.com> Date: Mon, 18 Sep 2017 00:37:17 -0400 Subject: [PATCH 10/10] Undo change to quote db filename --- plugins/quote.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/quote.js b/plugins/quote.js index cf96e27..8301406 100644 --- a/plugins/quote.js +++ b/plugins/quote.js @@ -20,7 +20,7 @@ const defaultTemplates = { module.exports = function quotePlugin(plugin) { plugin.db = new DataStore({ - filename: path.join(plugin.config.get('dbdir', ''), 'quote.plugin.db'), + filename: path.join(plugin.config.get('dbdir', ''), 'quote.db'), autoload: true, });