<commit_before>#!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const Mqtt = require('mqtt');
const express = require('express');
const bodyParser = require('body-parser');
const basicAuth = require('express-basic-auth');

const app = express();

const log = require('yalm');
const HAP = require('hap-nodejs');
const pkgHap = require('./node_modules/hap-nodejs/package.json');
const pkg = require('./package.json');
const config = require('./config.js');

log.setLevel(config.verbosity);

log(pkg.name + ' ' + pkg.version + ' starting');

const mqttStatus = {};        // Holds the payloads of the last-received message, keys are the topics.
const mqttCallbacks = {};     // Holds arrays of subscription callbacks, keys are the topics.
let mqttConnected;

let bridgeListening;

log.info('mqtt trying to connect', config.url);
const mqtt = Mqtt.connect(config.url, {will: {topic: config.name + '/connected', payload: '0', retain: true}});

mqtt.on('connect', () => {
    mqttConnected = true;
    log.info('mqtt connected ' + config.url);
    /* istanbul ignore if */
    if (!bridgeListening) {
        mqttPub(config.name + '/connected', '1', {retain: true});
    }
});

/* istanbul ignore next */
mqtt.on('reconnect', () => {
    log.info('mqtt reconnect');
});

/* istanbul ignore next */
mqtt.on('offline', () => {
    log.info('mqtt offline');
});

/* istanbul ignore next */
mqtt.on('close', () => {
    if (mqttConnected) {
        mqttConnected = false;
        log.info('mqtt closed ' + config.url);
    }
});

/* istanbul ignore next */
mqtt.on('error', err => {
    log.error('mqtt error ' + err);
});

function typeGuess(payload) {
    let state;
    // Nasty type guessing.
    // TODO clarify Do we really want/need to cast the strings "true" and "false" to bool? https://github.com/hobbyquaker/homekit2mqtt/issues/66
    if (payload === 'true') {
        state = true;
    } else if (payload === 'false') {
        state = false;
    } else if (isNaN(payload)) {
        state = payload;
    } else {
        state = parseFloat(payload);
    }
    return state;
}

// MQTT subscribe function that provides a callback on incoming messages.
// Not meant to be used with wildcards!
function mqttSub(topic, callback) {
    topic = String(topic);
    /* istanbul ignore else */
    if (typeof callback === 'function') {
        /* istanbul ignore if */
        if (mqttCallbacks[topic]) {
            mqttCallbacks[topic].push(callback);
        } else {
            mqttCallbacks[topic] = [callback];
            log.debug('mqtt subscribe', topic);
            mqtt.subscribe(topic);
        }
    } else {
        log.debug('mqtt subscribe', topic);
        mqtt.subscribe(topic);
    }
}

// MQTT publish function, checks for valid topic and converts payload to string in a meaningful manner.
function mqttPub(topic, payload, options) {
    /* istanbul ignore if */
    if (!topic || (typeof topic !== 'string')) {
        log.error('mqttPub invalid topic', topic);
    } else {
        /* istanbul ignore if */
        if (typeof payload === 'object') {
            payload = JSON.stringify(payload);
        } else if (typeof payload !== 'string') {
            payload = String(payload);
        }
        log.debug('> mqtt', topic, payload);
        mqtt.publish(topic, payload, options, err => {
            /* istanbul ignore next */
            if (err) {
                log.error('mqtt publish error ' + err);
            }
        });
    }
}

log.info('using hap-nodejs version', pkgHap.version);

const uuid = HAP.uuid;
const Bridge = HAP.Bridge;
const Accessory = HAP.Accessory;
const Service = HAP.Service;
const Characteristic = HAP.Characteristic;

/* istanbul ignore next */
if (config.storagedir) {
    log.info('using directory ' + config.storagedir + ' for persistent storage');
}
// If storagedir is not set it uses HAP-Nodejs default
// (usually /usr/local/lib/node_modules/homekit2mqtt/node_modules/node-persist/persist)
HAP.init(config.storagedir || undefined);

// Create Bridge which will host all Accessories
const bridge = new Bridge(config.bridgename, uuid.generate(config.bridgename));

// Listen for Bridge identification event
/* istanbul ignore next */
bridge.on('identify', (paired, callback) => {
    log('< hap bridge identify', paired ? '(paired)' : '(unpaired)');
    callback();
});

// Handler for Accessory identification events
/* istanbul ignore next */
function identify(settings, paired, callback) {
    log.debug('< hap identify', settings.name, paired ? '(paired)' : '(unpaired)');
    if (settings.topic.identify) {
        log.debug('> mqtt', settings.topic.identify, settings.payload.identify);
        mqttPub(settings.topic.identify, settings.payload.identify);
    }
    callback();
}

function newAccessory(settings) {
    log.debug('creating new accessory', settings.name);
    const acc = new Accessory(settings.name, uuid.generate(settings.id));
    if (settings.manufacturer || settings.model || settings.serial) {
        acc.getService(Service.AccessoryInformation)
            .setCharacteristic(Characteristic.Manufacturer, settings.manufacturer || '-')
            .setCharacteristic(Characteristic.Model, settings.model || '-')
            .setCharacteristic(Characteristic.SerialNumber, settings.serial || '-');
    }
    if (!settings.payload) {
        settings.payload = {};
    }
    if (!settings.config) {
        settings.config = {};
    }
    /* istanbul ignore next */
    acc.on('identify', (paired, callback) => {
        identify(settings, paired, callback);
    });
    return acc;
}

const addService = {};

function loadService(service) {
    const file = 'services/' + service + '.js';
    log.debug('loading', file);
    addService[service] = require(path.join(__dirname, file))({mqttPub, mqttSub, mqttStatus, log, Service, Characteristic});
}

let mapping;
let accCount;

function createBridge() {
    mqtt.on('message', (topic, payload) => {
        payload = payload.toString();
        let state;
        if (payload.indexOf('{') === -1 || config.disableJsonParse) {
            state = typeGuess(payload);
        } else {
            try {
                // We got an Object - let's hope it follows mqtt-smarthome architecture and has an attribute "val"
                // see https://github.com/mqtt-smarthome/mqtt-smarthome/blob/master/Architecture.md
                state = JSON.parse(payload).val;
                // TODO make attribute configurable to support non-mqtt-smarthome json payloads https://github.com/hobbyquaker/homekit2mqtt/issues/67
                if (typeof state === 'undefined') {
                    // :-( there is no "val" attribute
                    throw new TypeError('attribute val undefined');
                }
            } catch (err) {
                state = typeGuess(payload);
            }
        }

        log.debug('< mqtt', topic, state);
        mqttStatus[topic] = state;
        /* istanbul ignore else */
        if (mqttCallbacks[topic]) {
            mqttCallbacks[topic].forEach(cb => {
                cb(state);
            });
        }
    });

// Load and create all accessories
    log.info('loading HomeKit to MQTT mapping file ' + config.mapfile);
    mapping = require(config.mapfile);
    accCount = 0;
    Object.keys(mapping).forEach(id => {
        const accConfig = mapping[id];
        const acc = newAccessory(accConfig);
        accConfig.id = id;

        const services = accConfig.services || [];

        if (accConfig.service) {
            // Keep compatibility with old scheme (version <= 0.8)
            services.unshift({
                name: accConfig.name,
                service: accConfig.service,
                topic: accConfig.topic || {},
                payload: accConfig.payload || {},
                config: accConfig.config || {},
                props: accConfig.props || {}
            });
        }

        services.forEach(s => {
            if (!addService[s.service]) {
                loadService(s.service);
            }
            log.debug('adding service', s.service, 'to accessory', accConfig.name);
            addService[s.service](acc, s);
        });

        log.debug('addBridgedAccessory ' + accConfig.name);
        bridge.addBridgedAccessory(acc);
        accCount++;
    });
    log.info('hap created', accCount, 'Accessories');

    log('hap publishing bridge "' + config.bridgename + '" username=' + config.username, 'port=' + config.port, 'pincode=' + config.c);
    bridge.publish({
        username: config.username,
        port: config.port,
        pincode: config.c,
        category: Accessory.Categories.OTHER
    });

    bridge._server.on('listening', () => {
        bridgeListening = true;
        mqttPub(config.name + '/connected', '2', {retain: true});
        log('hap Bridge listening on port', config.port);
    });

    bridge._server.on('pair', username => {
        log('hap paired', username);
    });

    /* istanbul ignore next */
    bridge._server.on('unpair', username => {
        log('hap unpaired', username);
    });

    /* istanbul ignore next */
    bridge._server.on('verify', () => {
        log('hap verify');
    });
}

let isStarted = false;

function start() {
    if (isStarted) {
        log.error('already started');
        return;
    }
    isStarted = true;
    log.debug('mqtt unsubscribe #');
    mqtt.unsubscribe('#');
    createBridge();
}

if (config.disableWeb) {
    createBridge();
} else {
    // Get all retained messages (used for autocomplete in web ui)
    log.debug('mqtt subscribe #');
    mqtt.subscribe('#');
    const topics = [];
    let retainTimeout = setTimeout(start, 1000);
    mqtt.on('message', (topic, payload, msg) => {
        if (isStarted) {
            return;
        }
        if (msg.retain) {
            clearTimeout(retainTimeout);
            retainTimeout = setTimeout(start, 1000);
        }
        if (topics.indexOf(topic) === -1 && topic !== config.name + '/connected') {
            topics.push(topic);
        }
    });

    app.listen(config.webPort, () => {
        log.info('http server listening on port', config.webPort);
    });

    app.use(basicAuth({
        users: {homekit: config.pincode},
        challenge: true,
        realm: 'homekit2mqtt ui'
    }));

    app.get('/', (req, res) => {
        res.redirect(301, '/ui');
    });
    app.use('/ui', express.static(path.join(__dirname, '/ui')));
    app.use('/node_modules', express.static(path.join(__dirname, '/node_modules')));
    app.use('/services.json', express.static(path.join(__dirname, '/services.json')));

    app.get('/topics', (req, res) => {
        log.info('http > topics');
        res.send(JSON.stringify(topics));
    });

    app.get('/config', (req, res) => {
        log.info('http > config');
        res.send(JSON.stringify(mapping));
    });

    app.post('/config', bodyParser.json(), (req, res) => {
        log.info('http < config');
        mapping = req.body;
        fs.writeFileSync(config.mapfile, JSON.stringify(req.body, null, '  '));
        log.info('saved config to', config.mapfile);
        res.send('ok');
    });

    app.get('/quit', (req, res) => {
        log.info('http < quit');
        res.send('ok');
        process.exit(0);
    });
}
<commit_msg>add service name (#56)<commit_after>#!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const Mqtt = require('mqtt');
const express = require('express');
const bodyParser = require('body-parser');
const basicAuth = require('express-basic-auth');

const app = express();

const log = require('yalm');
const HAP = require('hap-nodejs');
const pkgHap = require('./node_modules/hap-nodejs/package.json');
const pkg = require('./package.json');
const config = require('./config.js');

log.setLevel(config.verbosity);

log(pkg.name + ' ' + pkg.version + ' starting');

const mqttStatus = {};        // Holds the payloads of the last-received message, keys are the topics.
const mqttCallbacks = {};     // Holds arrays of subscription callbacks, keys are the topics.
let mqttConnected;

let bridgeListening;

log.info('mqtt trying to connect', config.url);
const mqtt = Mqtt.connect(config.url, {will: {topic: config.name + '/connected', payload: '0', retain: true}});

mqtt.on('connect', () => {
    mqttConnected = true;
    log.info('mqtt connected ' + config.url);
    /* istanbul ignore if */
    if (!bridgeListening) {
        mqttPub(config.name + '/connected', '1', {retain: true});
    }
});

/* istanbul ignore next */
mqtt.on('reconnect', () => {
    log.info('mqtt reconnect');
});

/* istanbul ignore next */
mqtt.on('offline', () => {
    log.info('mqtt offline');
});

/* istanbul ignore next */
mqtt.on('close', () => {
    if (mqttConnected) {
        mqttConnected = false;
        log.info('mqtt closed ' + config.url);
    }
});

/* istanbul ignore next */
mqtt.on('error', err => {
    log.error('mqtt error ' + err);
});

function typeGuess(payload) {
    let state;
    // Nasty type guessing.
    // TODO clarify Do we really want/need to cast the strings "true" and "false" to bool? https://github.com/hobbyquaker/homekit2mqtt/issues/66
    if (payload === 'true') {
        state = true;
    } else if (payload === 'false') {
        state = false;
    } else if (isNaN(payload)) {
        state = payload;
    } else {
        state = parseFloat(payload);
    }
    return state;
}

// MQTT subscribe function that provides a callback on incoming messages.
// Not meant to be used with wildcards!
function mqttSub(topic, callback) {
    topic = String(topic);
    /* istanbul ignore else */
    if (typeof callback === 'function') {
        /* istanbul ignore if */
        if (mqttCallbacks[topic]) {
            mqttCallbacks[topic].push(callback);
        } else {
            mqttCallbacks[topic] = [callback];
            log.debug('mqtt subscribe', topic);
            mqtt.subscribe(topic);
        }
    } else {
        log.debug('mqtt subscribe', topic);
        mqtt.subscribe(topic);
    }
}

// MQTT publish function, checks for valid topic and converts payload to string in a meaningful manner.
function mqttPub(topic, payload, options) {
    /* istanbul ignore if */
    if (!topic || (typeof topic !== 'string')) {
        log.error('mqttPub invalid topic', topic);
    } else {
        /* istanbul ignore if */
        if (typeof payload === 'object') {
            payload = JSON.stringify(payload);
        } else if (typeof payload !== 'string') {
            payload = String(payload);
        }
        log.debug('> mqtt', topic, payload);
        mqtt.publish(topic, payload, options, err => {
            /* istanbul ignore next */
            if (err) {
                log.error('mqtt publish error ' + err);
            }
        });
    }
}

log.info('using hap-nodejs version', pkgHap.version);

const uuid = HAP.uuid;
const Bridge = HAP.Bridge;
const Accessory = HAP.Accessory;
const Service = HAP.Service;
const Characteristic = HAP.Characteristic;

/* istanbul ignore next */
if (config.storagedir) {
    log.info('using directory ' + config.storagedir + ' for persistent storage');
}
// If storagedir is not set it uses HAP-Nodejs default
// (usually /usr/local/lib/node_modules/homekit2mqtt/node_modules/node-persist/persist)
HAP.init(config.storagedir || undefined);

// Create Bridge which will host all Accessories
const bridge = new Bridge(config.bridgename, uuid.generate(config.bridgename));

// Listen for Bridge identification event
/* istanbul ignore next */
bridge.on('identify', (paired, callback) => {
    log('< hap bridge identify', paired ? '(paired)' : '(unpaired)');
    callback();
});

// Handler for Accessory identification events
/* istanbul ignore next */
function identify(settings, paired, callback) {
    log.debug('< hap identify', settings.name, paired ? '(paired)' : '(unpaired)');
    if (settings.topic.identify) {
        log.debug('> mqtt', settings.topic.identify, settings.payload.identify);
        mqttPub(settings.topic.identify, settings.payload.identify);
    }
    callback();
}

function newAccessory(settings) {
    log.debug('creating new accessory', settings.name);
    const acc = new Accessory(settings.name, uuid.generate(settings.id));
    if (settings.manufacturer || settings.model || settings.serial) {
        acc.getService(Service.AccessoryInformation)
            .setCharacteristic(Characteristic.Manufacturer, settings.manufacturer || '-')
            .setCharacteristic(Characteristic.Model, settings.model || '-')
            .setCharacteristic(Characteristic.SerialNumber, settings.serial || '-');
    }
    if (!settings.payload) {
        settings.payload = {};
    }
    if (!settings.config) {
        settings.config = {};
    }
    /* istanbul ignore next */
    acc.on('identify', (paired, callback) => {
        identify(settings, paired, callback);
    });
    return acc;
}

const addService = {};

function loadService(service) {
    const file = 'services/' + service + '.js';
    log.debug('loading', file);
    addService[service] = require(path.join(__dirname, file))({mqttPub, mqttSub, mqttStatus, log, Service, Characteristic});
}

let mapping;
let accCount;

function createBridge() {
    mqtt.on('message', (topic, payload) => {
        payload = payload.toString();
        let state;
        if (payload.indexOf('{') === -1 || config.disableJsonParse) {
            state = typeGuess(payload);
        } else {
            try {
                // We got an Object - let's hope it follows mqtt-smarthome architecture and has an attribute "val"
                // see https://github.com/mqtt-smarthome/mqtt-smarthome/blob/master/Architecture.md
                state = JSON.parse(payload).val;
                // TODO make attribute configurable to support non-mqtt-smarthome json payloads https://github.com/hobbyquaker/homekit2mqtt/issues/67
                if (typeof state === 'undefined') {
                    // :-( there is no "val" attribute
                    throw new TypeError('attribute val undefined');
                }
            } catch (err) {
                state = typeGuess(payload);
            }
        }

        log.debug('< mqtt', topic, state);
        mqttStatus[topic] = state;
        /* istanbul ignore else */
        if (mqttCallbacks[topic]) {
            mqttCallbacks[topic].forEach(cb => {
                cb(state);
            });
        }
    });

// Load and create all accessories
    log.info('loading HomeKit to MQTT mapping file ' + config.mapfile);
    mapping = require(config.mapfile);
    accCount = 0;
    Object.keys(mapping).forEach(id => {
        const accConfig = mapping[id];
        const acc = newAccessory(accConfig);
        accConfig.id = id;

        const services = accConfig.services || [];

        if (accConfig.service) {
            // Keep compatibility with old scheme (version <= 0.8)
            services.unshift({
                service: accConfig.service,
                topic: accConfig.topic || {},
                payload: accConfig.payload || {},
                config: accConfig.config || {},
                props: accConfig.props || {}
            });
        }

        services.forEach(s => {
            if (!addService[s.service]) {
                loadService(s.service);
            }
            log.debug('adding service', s.service, 'to accessory', accConfig.name);
            addService[s.service](acc, s);
            if (s.name) {
                acc.getService(s.service).setCharacteristic(Characteristic.Name, s.name);
            }
        });

        log.debug('addBridgedAccessory ' + accConfig.name);
        bridge.addBridgedAccessory(acc);
        accCount++;
    });
    log.info('hap created', accCount, 'Accessories');

    log('hap publishing bridge "' + config.bridgename + '" username=' + config.username, 'port=' + config.port, 'pincode=' + config.c);
    bridge.publish({
        username: config.username,
        port: config.port,
        pincode: config.c,
        category: Accessory.Categories.OTHER
    });

    bridge._server.on('listening', () => {
        bridgeListening = true;
        mqttPub(config.name + '/connected', '2', {retain: true});
        log('hap Bridge listening on port', config.port);
    });

    bridge._server.on('pair', username => {
        log('hap paired', username);
    });

    /* istanbul ignore next */
    bridge._server.on('unpair', username => {
        log('hap unpaired', username);
    });

    /* istanbul ignore next */
    bridge._server.on('verify', () => {
        log('hap verify');
    });
}

let isStarted = false;

function start() {
    if (isStarted) {
        log.error('already started');
        return;
    }
    isStarted = true;
    log.debug('mqtt unsubscribe #');
    mqtt.unsubscribe('#');
    createBridge();
}

if (config.disableWeb) {
    createBridge();
} else {
    // Get all retained messages (used for autocomplete in web ui)
    log.debug('mqtt subscribe #');
    mqtt.subscribe('#');
    const topics = [];
    let retainTimeout = setTimeout(start, 1000);
    mqtt.on('message', (topic, payload, msg) => {
        if (isStarted) {
            return;
        }
        if (msg.retain) {
            clearTimeout(retainTimeout);
            retainTimeout = setTimeout(start, 1000);
        }
        if (topics.indexOf(topic) === -1 && topic !== config.name + '/connected') {
            topics.push(topic);
        }
    });

    app.listen(config.webPort, () => {
        log.info('http server listening on port', config.webPort);
    });

    app.use(basicAuth({
        users: {homekit: config.pincode},
        challenge: true,
        realm: 'homekit2mqtt ui'
    }));

    app.get('/', (req, res) => {
        res.redirect(301, '/ui');
    });
    app.use('/ui', express.static(path.join(__dirname, '/ui')));
    app.use('/node_modules', express.static(path.join(__dirname, '/node_modules')));
    app.use('/services.json', express.static(path.join(__dirname, '/services.json')));

    app.get('/topics', (req, res) => {
        log.info('http > topics');
        res.send(JSON.stringify(topics));
    });

    app.get('/config', (req, res) => {
        log.info('http > config');
        res.send(JSON.stringify(mapping));
    });

    app.post('/config', bodyParser.json(), (req, res) => {
        log.info('http < config');
        mapping = req.body;
        fs.writeFileSync(config.mapfile, JSON.stringify(req.body, null, '  '));
        log.info('saved config to', config.mapfile);
        res.send('ok');
    });

    app.get('/quit', (req, res) => {
        log.info('http < quit');
        res.send('ok');
        process.exit(0);
    });
}
<|endoftext|>


<commit_before>var map = require('map-stream');
var util = require('util');
var path = require('path');
var table = require('text-table');
var f = require('sprintf-js').sprintf;
var chalk = require('chalk');
var plur = require('plur');
var symb = require('log-symbols');
var extend = require('extend');
var strLen = require('string-length');

function log(output, opts) {
  var stats = opts.stats;
  var symbol = (opts.symbol) ? opts.symbol : symb[opts.type];

  var patternFileCount = '%' + ('' + stats.files.total).length + 'd';
  var patternProblemCount = '%' + ('' + stats.problems.total).length + 'd';

  var patternFiles = '  %s  ' + patternFileCount + ' %s %s';
  var patternFilePct = '(%5.2f%%)';
  var patternProblems = '     %s  ' + patternProblemCount + ' %s total';

  var files = stats.files[opts.type];
  var filePct = stats.files[opts.type + 'Pct'];
  var problems = stats.problems[opts.type];

  if (stats.files[opts.type] > 0) {
    output.push([
        f(patternFiles, symbol, files, plur('file', files), opts.desc),
      (!opts.showFilePct) ? '' :
        f(patternFilePct, filePct),
      (!opts.showProblems) ? '' :
        f(patternProblems, symbol, problems, plur(opts.type, problems))
    ]);
  }
}

function pct(i, n) {
  return i / n * 100;
}

function statistics(results, config) {
  var problemCounts = { 'I': 0, 'W': 0, 'E': 0 };
  var problemFiles  = { 'I': new Set(), 'W': new Set(), 'E': new Set() };
  var successFiles  = new Set();

  config.map(function(conf) {
    successFiles.add(conf.file);
  });

  results.map(function(result) {
    problemCounts[result.error.code[0]]++;
    problemFiles[result.error.code[0]].add(result.file);
    successFiles.delete(result.file);
  });

  var stats = { files: {}, problems: {} };
  stats.files.total   = config.length;
  stats.files.success = successFiles.size;
  stats.files.warning = problemFiles['I'].size + problemFiles['W'].size;
  stats.files.error   = problemFiles['E'].size;
  stats.files.problem = stats.files.total - stats.files.success;
  stats.files.successPct = pct(stats.files.success, stats.files.total);
  stats.files.warningPct = pct(stats.files.warning, stats.files.total);
  stats.files.errorPct   = pct(stats.files.error,   stats.files.total);
  stats.files.problemPct = pct(stats.files.problem, stats.files.total);
  stats.problems.warning = problemCounts['I'] + problemCounts['W'];
  stats.problems.error   = problemCounts['E'];
  stats.problems.total   = stats.problems.warning + stats.problems.error;

  return stats;
}

function reporter(results, config, options) {
  options = extend({}, {
    showSummaryOnSuccess: false,
    showSummaryHeader: false,
    showWarningTotals: false,
    showErrorTotals: false,
    print: console.log
  }, options);

  var stats = statistics(results, config);

  if (stats.files.problem === 0 && !options.showSummaryOnSuccess) {
    return;
  }
  var tableOutput = [];
  log(tableOutput, {
    stats: stats,
    symbol: symb.info,
    type: 'total',
    desc: 'checked',
    showFilePct: false,
    showProblems: false
  });
  log(tableOutput, {
    stats: stats,
    type: 'success',
    desc: 'without problems',
    showFilePct: true,
    showProblems: false
  });
  log(tableOutput, {
    stats: stats,
    type: 'warning',
    desc: 'with warnings',
    showFilePct: true,
    showProblems: options.showWarningTotals
  });
  log(tableOutput, {
    stats: stats,
    type: 'error',
    desc: 'with errors',
    showFilePct: true,
    showProblems: options.showErrorTotals
  });

  var output = '';
  if (options.showSummaryHeader) {
    var header = (options.summaryHeader) ? options.summaryHeader :
      'SUMMARY' + ((options.stat) ? ': ' + options.stat : '');
    output += '\n' + chalk.inverse(' ' + header + ' ') + '\n\n';
  }
  output += table(tableOutput, {
    hsep: '   ',
    stringLength: strLen
  });
  options.print(output + '\n');
}

function parseArguments(args) {
  var opts = { };
  if (args.length === 1) {
    if (util.isString(args[0])) {
      opts = { stat: args[0] };
    } else {
      opts = args[0];
    }
  } else if (args.length === 2) {
    opts = args[1];
    opts.stat = args[0];
  }
  if (!opts.stat) {
    opts.stat =  null;
  }
  return opts;
}

function JSHintStylishSummary() {
  var stats = {};

  this.reporter = reporter;

  this.collect = function() {
    var opts = parseArguments(arguments);
    return map(function(file, cb) {
      stats[opts.stat] = stats[opts.stat] || { results: [], config: [] };
      if (file.jshint.success) {
        stats[opts.stat].config.push({
          file: path.relative(file.cwd, file.path)
        });
      } else {
        file.jshint.results.map(function(result) {
          stats[opts.stat].results.push(result);
        });
        stats[opts.stat].config.push(file.jshint.data[0]);
      }
      cb(null, file);
    });
  };

  this.summarize = function() {
    var opts = parseArguments(arguments);
    return function() {
      reporter(
        stats[opts.stat].results,
        stats[opts.stat].config,
        extend({}, {
          showSummaryOnSuccess: true,
          showSummaryHeader: true,
          showWarningTotals: true,
          showErrorTotals: true
        }, opts)
      );
    };
   };

   this.stats = function(stat) {
     var opts = parseArguments(arguments);
     return statistics(stats[opts.stat].results, stats[opts.stat].config);
   };

   this.create = function() {
     return new JSHintStylishSummary();
   };

   this.toString = function() {
     return __filename;
   };
}

module.exports = new JSHintStylishSummary();
<commit_msg>Remove unused parameter for stats()<commit_after>var map = require('map-stream');
var util = require('util');
var path = require('path');
var table = require('text-table');
var f = require('sprintf-js').sprintf;
var chalk = require('chalk');
var plur = require('plur');
var symb = require('log-symbols');
var extend = require('extend');
var strLen = require('string-length');

function log(output, opts) {
  var stats = opts.stats;
  var symbol = (opts.symbol) ? opts.symbol : symb[opts.type];

  var patternFileCount = '%' + ('' + stats.files.total).length + 'd';
  var patternProblemCount = '%' + ('' + stats.problems.total).length + 'd';

  var patternFiles = '  %s  ' + patternFileCount + ' %s %s';
  var patternFilePct = '(%5.2f%%)';
  var patternProblems = '     %s  ' + patternProblemCount + ' %s total';

  var files = stats.files[opts.type];
  var filePct = stats.files[opts.type + 'Pct'];
  var problems = stats.problems[opts.type];

  if (stats.files[opts.type] > 0) {
    output.push([
        f(patternFiles, symbol, files, plur('file', files), opts.desc),
      (!opts.showFilePct) ? '' :
        f(patternFilePct, filePct),
      (!opts.showProblems) ? '' :
        f(patternProblems, symbol, problems, plur(opts.type, problems))
    ]);
  }
}

function pct(i, n) {
  return i / n * 100;
}

function statistics(results, config) {
  var problemCounts = { 'I': 0, 'W': 0, 'E': 0 };
  var problemFiles  = { 'I': new Set(), 'W': new Set(), 'E': new Set() };
  var successFiles  = new Set();

  config.map(function(conf) {
    successFiles.add(conf.file);
  });

  results.map(function(result) {
    problemCounts[result.error.code[0]]++;
    problemFiles[result.error.code[0]].add(result.file);
    successFiles.delete(result.file);
  });

  var stats = { files: {}, problems: {} };
  stats.files.total   = config.length;
  stats.files.success = successFiles.size;
  stats.files.warning = problemFiles['I'].size + problemFiles['W'].size;
  stats.files.error   = problemFiles['E'].size;
  stats.files.problem = stats.files.total - stats.files.success;
  stats.files.successPct = pct(stats.files.success, stats.files.total);
  stats.files.warningPct = pct(stats.files.warning, stats.files.total);
  stats.files.errorPct   = pct(stats.files.error,   stats.files.total);
  stats.files.problemPct = pct(stats.files.problem, stats.files.total);
  stats.problems.warning = problemCounts['I'] + problemCounts['W'];
  stats.problems.error   = problemCounts['E'];
  stats.problems.total   = stats.problems.warning + stats.problems.error;

  return stats;
}

function reporter(results, config, options) {
  options = extend({}, {
    showSummaryOnSuccess: false,
    showSummaryHeader: false,
    showWarningTotals: false,
    showErrorTotals: false,
    print: console.log
  }, options);

  var stats = statistics(results, config);

  if (stats.files.problem === 0 && !options.showSummaryOnSuccess) {
    return;
  }
  var tableOutput = [];
  log(tableOutput, {
    stats: stats,
    symbol: symb.info,
    type: 'total',
    desc: 'checked',
    showFilePct: false,
    showProblems: false
  });
  log(tableOutput, {
    stats: stats,
    type: 'success',
    desc: 'without problems',
    showFilePct: true,
    showProblems: false
  });
  log(tableOutput, {
    stats: stats,
    type: 'warning',
    desc: 'with warnings',
    showFilePct: true,
    showProblems: options.showWarningTotals
  });
  log(tableOutput, {
    stats: stats,
    type: 'error',
    desc: 'with errors',
    showFilePct: true,
    showProblems: options.showErrorTotals
  });

  var output = '';
  if (options.showSummaryHeader) {
    var header = (options.summaryHeader) ? options.summaryHeader :
      'SUMMARY' + ((options.stat) ? ': ' + options.stat : '');
    output += '\n' + chalk.inverse(' ' + header + ' ') + '\n\n';
  }
  output += table(tableOutput, {
    hsep: '   ',
    stringLength: strLen
  });
  options.print(output + '\n');
}

function parseArguments(args) {
  var opts = { };
  if (args.length === 1) {
    if (util.isString(args[0])) {
      opts = { stat: args[0] };
    } else {
      opts = args[0];
    }
  } else if (args.length === 2) {
    opts = args[1];
    opts.stat = args[0];
  }
  if (!opts.stat) {
    opts.stat =  null;
  }
  return opts;
}

function JSHintStylishSummary() {
  var stats = {};

  this.reporter = reporter;

  this.collect = function() {
    var opts = parseArguments(arguments);
    return map(function(file, cb) {
      stats[opts.stat] = stats[opts.stat] || { results: [], config: [] };
      if (file.jshint.success) {
        stats[opts.stat].config.push({
          file: path.relative(file.cwd, file.path)
        });
      } else {
        file.jshint.results.map(function(result) {
          stats[opts.stat].results.push(result);
        });
        stats[opts.stat].config.push(file.jshint.data[0]);
      }
      cb(null, file);
    });
  };

  this.summarize = function() {
    var opts = parseArguments(arguments);
    return function() {
      reporter(
        stats[opts.stat].results,
        stats[opts.stat].config,
        extend({}, {
          showSummaryOnSuccess: true,
          showSummaryHeader: true,
          showWarningTotals: true,
          showErrorTotals: true
        }, opts)
      );
    };
   };

   this.stats = function() {
     var opts = parseArguments(arguments);
     return statistics(stats[opts.stat].results, stats[opts.stat].config);
   };

   this.create = function() {
     return new JSHintStylishSummary();
   };

   this.toString = function() {
     return __filename;
   };
}

module.exports = new JSHintStylishSummary();
<|endoftext|>


<commit_before>module.exports = throttle

function throttle(fn, opts) {
  opts = opts || {}
  var queue = []
  const window = opts.window || 1
  const limit = opts.limit || 1
  const exact = opts.exact || false

  var timer;

  var msBetweenCalls = Math.ceil((window / limit) * 1000)
  if (!exact) msBetweenCalls += 10

  if (isNaN(msBetweenCalls))
    throw new Error('opts.window and opts.limit must both be numbers')

  function enqueue(args) {
    return queue.push(args)
  }

  function dequeue() {
    return queue.shift()
  }

  function kickQueue() {
    if (timer) return timer
    timer = setInterval(runQueue, msBetweenCalls)
    return timer
  }

  function runQueue() {
    const args = dequeue()

    if (queue.length == 0 || !args) {
      clearInterval(timer)
      timer = null
    }

    const result = fn.apply(null, args)
    return result
  }

  const throttled = function () {
    const args = [].slice.call(arguments)
    const position = enqueue(args)
    const timer = kickQueue()

    return {
      position: position,
      queuedAt: Date.now(),
      timeUntilCall: position * msBetweenCalls
    }
  }

  return throttled
}
<commit_msg>Remove 10ms padding<commit_after>module.exports = throttle

function throttle(fn, opts) {
  opts = opts || {}
  var queue = []
  const window = opts.window || 1
  const limit = opts.limit || 1
  const exact = opts.exact || false

  var timer;

  var msBetweenCalls = Math.ceil((window / limit) * 1000)

  if (isNaN(msBetweenCalls))
    throw new Error('opts.window and opts.limit must both be numbers')

  function enqueue(args) {
    return queue.push(args)
  }

  function dequeue() {
    return queue.shift()
  }

  function kickQueue() {
    if (timer) return timer
    timer = setInterval(runQueue, msBetweenCalls)
    return timer
  }

  function runQueue() {
    const args = dequeue()

    if (queue.length == 0 || !args) {
      clearInterval(timer)
      timer = null
    }

    const result = fn.apply(null, args)
    return result
  }

  const throttled = function () {
    const args = [].slice.call(arguments)
    const position = enqueue(args)
    const timer = kickQueue()

    return {
      position: position,
      queuedAt: Date.now(),
      timeUntilCall: position * msBetweenCalls
    }
  }

  return throttled
}
<|endoftext|>


<commit_before>/**
 * Originally ported from https://github.com/keeganstreet/specificity/blob/866bf7ab4e7f62a7179c15b13a95af4e1c7b1afa/specificity.js
 *
 * Calculates the specificity of CSS selectors
 * http://www.w3.org/TR/css3-selectors/#specificity
 *
 * Returns a selector integer value
 */

// The following regular expressions assume that selectors matching the preceding regular expressions have been removed
var attributeRegex = /(\[[^\]]+\])/g;
var idRegex = /(#[^\s\+>~\.\[:]+)/g;
var classRegex = /(\.[^\s\+>~\.\[:]+)/g;
var pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/g;
var pseudoClassRegex = /(:[^\s\+>~\.\[:]+)/g;
var elementRegex = /([^\s\+>~\.\[:]+)/g;
var notRegex = /:not\(([^\)]*)\)/g;
var ruleRegex = /\{[^]*/gm;
var separatorRegex = /[\*\s\+>~]/g;
var straysRegex = /[#\.]/g;

// Find matches for a regular expression in a string and push their details to parts
// Type is "a" for IDs, "b" for classes, attributes and pseudo-classes and "c" for elements and pseudo-elements
var findMatch = function(regex, type, types, selector) {
  var matches = selector.match(regex);
  if (matches) {
    for (var i = 0; i < matches.length; i++) {
      types[type]++;
      // Replace this simple selector with whitespace so it won't be counted in further simple selectors
      selector = selector.replace(matches[i], ' ');
    }
  }

  return selector;
}

// Calculate the specificity for a selector by dividing it into simple selectors and counting them
var calculate = function(selector) {
  var commaIndex = selector.indexOf(',');
  if (commaIndex !== -1) {
    selector = selector.substring(0, commaIndex);
  }

  var  types = {
    a: 0,
    b: 0,
    c: 0
  };

  // Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument
  selector = selector.replace(notRegex, ' $1 ');

  // Remove anything after a left brace in case a user has pasted in a rule, not just a selector
  selector = selector.replace(ruleRegex, ' ');

  // Add attribute selectors to parts collection (type b)
  selector = findMatch(attributeRegex, 'b', types, selector);

  // Add ID selectors to parts collection (type a)
  selector = findMatch(idRegex, 'a', types, selector);

  // Add class selectors to parts collection (type b)
  selector = findMatch(classRegex, 'b', types, selector);

  // Add pseudo-element selectors to parts collection (type c)
  selector = findMatch(pseudoElementRegex, 'c', types, selector);

  // Add pseudo-class selectors to parts collection (type b)
  selector = findMatch(pseudoClassRegex, 'b', types, selector);

  // Remove universal selector and separator characters
  selector = selector.replace(separatorRegex, ' ');

  // Remove any stray dots or hashes which aren't attached to words
  // These may be present if the user is live-editing this selector
  selector = selector.replace(straysRegex, ' ');

  // The only things left should be element selectors (type c)
  findMatch(elementRegex, 'c', types, selector);

  return (types.a * 100) + (types.b * 10) + (types.c * 1);
}

var specificityCache = {};

exports.calculateSpecificity = function(selector) {
  var specificity = specificityCache[selector];
  if (specificity === undefined) {
    specificity = calculate(selector);
    specificityCache[selector] = specificity;
  }
  return specificity;
}

if (global.document) {
  var validSelectorCache = {};
  var testSelectorElement = global.document.createElement('div');

  exports.isSelectorValid = function(selector) {
    var valid = validSelectorCache[selector];
    if (valid === undefined) {
      try {
        testSelectorElement.querySelector(selector);
        valid = true;
      } catch (error) {
        valid = false;
      }
      validSelectorCache[selector] = valid;
    }
    return valid;
  }

  exports.validateSelector = function(selector) {
    if (!exports.isSelectorValid(selector)) {
      var error = new SyntaxError(selector + ' is not a valid selector');
      error.code = 'EBADSELECTOR';
      throw error;
    }
  }
}
<commit_msg>Enable snapshot<commit_after>/**
 * Originally ported from https://github.com/keeganstreet/specificity/blob/866bf7ab4e7f62a7179c15b13a95af4e1c7b1afa/specificity.js
 *
 * Calculates the specificity of CSS selectors
 * http://www.w3.org/TR/css3-selectors/#specificity
 *
 * Returns a selector integer value
 */

// The following regular expressions assume that selectors matching the preceding regular expressions have been removed
var attributeRegex = /(\[[^\]]+\])/g;
var idRegex = /(#[^\s\+>~\.\[:]+)/g;
var classRegex = /(\.[^\s\+>~\.\[:]+)/g;
var pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/g;
var pseudoClassRegex = /(:[^\s\+>~\.\[:]+)/g;
var elementRegex = /([^\s\+>~\.\[:]+)/g;
var notRegex = /:not\(([^\)]*)\)/g;
var ruleRegex = /\{[^]*/gm;
var separatorRegex = /[\*\s\+>~]/g;
var straysRegex = /[#\.]/g;

// Find matches for a regular expression in a string and push their details to parts
// Type is "a" for IDs, "b" for classes, attributes and pseudo-classes and "c" for elements and pseudo-elements
var findMatch = function(regex, type, types, selector) {
  var matches = selector.match(regex);
  if (matches) {
    for (var i = 0; i < matches.length; i++) {
      types[type]++;
      // Replace this simple selector with whitespace so it won't be counted in further simple selectors
      selector = selector.replace(matches[i], ' ');
    }
  }

  return selector;
}

// Calculate the specificity for a selector by dividing it into simple selectors and counting them
var calculate = function(selector) {
  var commaIndex = selector.indexOf(',');
  if (commaIndex !== -1) {
    selector = selector.substring(0, commaIndex);
  }

  var  types = {
    a: 0,
    b: 0,
    c: 0
  };

  // Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument
  selector = selector.replace(notRegex, ' $1 ');

  // Remove anything after a left brace in case a user has pasted in a rule, not just a selector
  selector = selector.replace(ruleRegex, ' ');

  // Add attribute selectors to parts collection (type b)
  selector = findMatch(attributeRegex, 'b', types, selector);

  // Add ID selectors to parts collection (type a)
  selector = findMatch(idRegex, 'a', types, selector);

  // Add class selectors to parts collection (type b)
  selector = findMatch(classRegex, 'b', types, selector);

  // Add pseudo-element selectors to parts collection (type c)
  selector = findMatch(pseudoElementRegex, 'c', types, selector);

  // Add pseudo-class selectors to parts collection (type b)
  selector = findMatch(pseudoClassRegex, 'b', types, selector);

  // Remove universal selector and separator characters
  selector = selector.replace(separatorRegex, ' ');

  // Remove any stray dots or hashes which aren't attached to words
  // These may be present if the user is live-editing this selector
  selector = selector.replace(straysRegex, ' ');

  // The only things left should be element selectors (type c)
  findMatch(elementRegex, 'c', types, selector);

  return (types.a * 100) + (types.b * 10) + (types.c * 1);
}

var specificityCache = {};

exports.calculateSpecificity = function(selector) {
  var specificity = specificityCache[selector];
  if (specificity === undefined) {
    specificity = calculate(selector);
    specificityCache[selector] = specificity;
  }
  return specificity;
}

var validSelectorCache = {};
var testSelectorElement = null;

exports.isSelectorValid = function(selector) {
  var valid = validSelectorCache[selector];
  if (valid === undefined) {
    if (testSelectorElement == null) {
      testSelectorElement = document.createElement('div')
    }

    try {
      testSelectorElement.querySelector(selector);
      valid = true;
    } catch (error) {
      valid = false;
    }
    validSelectorCache[selector] = valid;
  }
  return valid;
}

exports.validateSelector = function(selector) {
  if (!exports.isSelectorValid(selector)) {
    var error = new SyntaxError(selector + ' is not a valid selector');
    error.code = 'EBADSELECTOR';
    throw error;
  }
}
<|endoftext|>


<commit_before>var extend = require('extend')
var fs = require('fs')
var Handlebars = require('handlebars')

module.exports = plugin

function plugin (options) {
  options = extend({
    directory: 'helpers'
  }, options || {})

  return function (files, metalsmith, done) {
    fs.readdir(metalsmith.path(options.directory), function (err, files) {
      if (err) throw err

      files.forEach(function (file) {
        var helperContents
        var path
        var templateName

        path = metalsmith.path(options.directory, file)
        helperContents = require(path)

        switch (typeof helperContents) {
        case 'function':
          templateName = file.split('.').shift()
          Handlebars.registerHelper(templateName, helperContents)
          break
        case 'object':
          Handlebars.registerHelper(helperContents)
          break
        }
      })

      done()
    })
  }
}
<commit_msg>Change preferred templateName to helper methods name<commit_after>var extend = require('extend')
var fs = require('fs')
var Handlebars = require('handlebars')

module.exports = plugin

function plugin (options) {
  options = extend({
    directory: 'helpers'
  }, options || {})

  return function (files, metalsmith, done) {
    fs.readdir(metalsmith.path(options.directory), function (err, files) {
      if (err) throw err

      files.forEach(function (file) {
        var helperContents
        var path
        var templateName

        path = metalsmith.path(options.directory, file)
        helperContents = require(path)

        switch (typeof helperContents) {
          case 'function':
            templateName = helperContents.name || file.split('.').shift()
            Handlebars.registerHelper(templateName, helperContents)
            break
          case 'object':
            Handlebars.registerHelper(helperContents)
            break
        }
      })

      done()
    })
  }
}
<|endoftext|>


<commit_before>var q = require('q'),
    assign = require('object-assign'),
    path = require('path'),
    glob = require('glob');

/**
 * Execute the Runner's test cases through Cucumber.
 *
 * @param {Runner} runner The current Protractor Runner.
 * @param {Array} specs Array of Directory Path Strings.
 * @return {q.Promise} Promise resolved with the test results
 */
exports.run = function(runner, specs) {
  // TODO - add the event interface for cucumber.
  var Cucumber = require('cucumber');
  var configDir = runner.getConfig().configDir;

  // Set up exec options for Cucumber
  var execOptions = assign({
    format: [],
    tags: [],
    require: [],
    compiler: []
  }, runner.getConfig().cucumberOpts);

  ['compiler', 'format', 'tags', 'require'].forEach(function (option) {
    // Make sure that options values are arrays
    if (!Array.isArray(execOptions[option])) {
      execOptions[option] = [ execOptions[option] ];
    }
    if (option === 'require') {
      execOptions[option] =
        execOptions[option].map(function(path) {
          // Handle glob matching
          return glob.sync(path, {cwd: configDir});
        }).reduce(function(opts, globPaths) {
          // Combine paths into flattened array
          return opts.concat(globPaths);
        }, []).map(function(requirePath) {
          // Resolve require absolute path
          return path.resolve(configDir, requirePath)
        });
    }
  });

  var testResult = [];
  var stepResults = {
    description: null,
    assertions: [],
    duration: 0
  };
  var scenarioFailed = false;

  var failedCount = 0;
  // Add a listener into cucumber so that protractor can find out which
  // steps passed/failed
  var addResultListener = function(formatter) {
    var feature = { getName: function() { return ''; } };
    var originalHandleBeforeFeatureEvent = formatter.handleBeforeFeatureEvent;
    formatter.handleBeforeFeatureEvent = function(event, callback) {
      feature = event.getPayloadItem('feature');
      if (typeof originalHandleBeforeFeatureEvent == 'function') {
        originalHandleBeforeFeatureEvent.apply(formatter, arguments);
      } else {
        callback();
      }
    };
    var originalHandleAfterScenarioEvent = formatter.handleAfterScenarioEvent;
    formatter.handleAfterScenarioEvent = function(event, callback) {
      var scenarioInfo = {
        name: event.getPayloadItem('scenario').getName(),
        category: feature.getName()
      };
      stepResults.description = scenarioInfo.name;
      if (scenarioFailed) {
        ++failedCount;
        runner.emit('testFail', scenarioInfo);
      } else {
        runner.emit('testPass', scenarioInfo);
      }

      testResult.push(stepResults);
      stepResults = {
        description: null,
        assertions: [],
        duration: 0
      };
      scenarioFailed = false;

      if (originalHandleAfterScenarioEvent
          && typeof(originalHandleAfterScenarioEvent) === 'function') {
            originalHandleAfterScenarioEvent(event, callback);
      } else {
        callback();
      }
    };

    var originalHandleStepResultEvent = formatter.handleStepResultEvent;
    formatter.handleStepResultEvent = function(event, callback) {
      var stepResult = event.getPayloadItem('stepResult');
      var isStepFailed = stepResult.getStatus() === Cucumber.Status.FAILED;
      var isStepSuccessful = stepResult.getStatus() === Cucumber.Status.PASSED;

      if (isStepSuccessful) {
        stepResults.assertions.push({
          passed: true
        });
        stepResults.duration += stepResult.getDuration();
      } else if (isStepFailed) {
        scenarioFailed = true;
        var failureMessage = stepResult.getFailureException();
        stepResults.assertions.push({
          passed: false,
          errorMsg: failureMessage.message,
          stackTrace: failureMessage.stack
        });
        stepResults.duration += stepResult.getDuration();
      }

      if (originalHandleStepResultEvent
          && typeof(originalHandleStepResultEvent) === 'function') {
            originalHandleStepResultEvent(event, callback);
      } else {
        callback();
      }
    };
  };

  return runner.runTestPreparer().then(function() {
    return q.promise(function(resolve, reject) {
      var cucumberConf = Cucumber.Cli.Configuration(execOptions, specs);
      var runtime = Cucumber.Runtime(cucumberConf);
      var formatters = cucumberConf.getFormatters();

      addResultListener(formatters[0]);
      formatters.forEach(runtime.attachListener.bind(runtime));

      runtime.start(function() {
        try {
          if (runner.getConfig().onComplete) {
            runner.getConfig().onComplete();
          }
          resolve({
            failedCount: failedCount,
            specResults: testResult
          });
        } catch (err) {
          reject(err);
        }
      });
    });
  });
};
<commit_msg>Filter duplicate requires to avoid cucumber ambiguous step matches<commit_after>var q = require('q'),
    assign = require('object-assign'),
    path = require('path'),
    glob = require('glob');

/**
 * Execute the Runner's test cases through Cucumber.
 *
 * @param {Runner} runner The current Protractor Runner.
 * @param {Array} specs Array of Directory Path Strings.
 * @return {q.Promise} Promise resolved with the test results
 */
exports.run = function(runner, specs) {
  // TODO - add the event interface for cucumber.
  var Cucumber = require('cucumber');
  var configDir = runner.getConfig().configDir;

  // Set up exec options for Cucumber
  var execOptions = assign({
    format: [],
    tags: [],
    require: [],
    compiler: []
  }, runner.getConfig().cucumberOpts);

  ['compiler', 'format', 'tags', 'require'].forEach(function (option) {
    // Make sure that options values are arrays
    if (!Array.isArray(execOptions[option])) {
      execOptions[option] = [ execOptions[option] ];
    }
    if (option === 'require') {
      execOptions[option] =
        execOptions[option].map(function(path) {
          // Handle glob matching
          return glob.sync(path, {cwd: configDir});
        }).reduce(function(opts, globPaths) {
          // Combine paths into flattened array
          return opts.concat(globPaths);
        }, []).map(function(requirePath) {
          // Resolve require absolute path
          return path.resolve(configDir, requirePath)
        }).filter(function(item, pos, orig) {
          // Make sure requires are unique
          return orig.indexOf(item) == pos;
        });
    }
  });

  var testResult = [];
  var stepResults = {
    description: null,
    assertions: [],
    duration: 0
  };
  var scenarioFailed = false;

  var failedCount = 0;
  // Add a listener into cucumber so that protractor can find out which
  // steps passed/failed
  var addResultListener = function(formatter) {
    var feature = { getName: function() { return ''; } };
    var originalHandleBeforeFeatureEvent = formatter.handleBeforeFeatureEvent;
    formatter.handleBeforeFeatureEvent = function(event, callback) {
      feature = event.getPayloadItem('feature');
      if (typeof originalHandleBeforeFeatureEvent == 'function') {
        originalHandleBeforeFeatureEvent.apply(formatter, arguments);
      } else {
        callback();
      }
    };
    var originalHandleAfterScenarioEvent = formatter.handleAfterScenarioEvent;
    formatter.handleAfterScenarioEvent = function(event, callback) {
      var scenarioInfo = {
        name: event.getPayloadItem('scenario').getName(),
        category: feature.getName()
      };
      stepResults.description = scenarioInfo.name;
      if (scenarioFailed) {
        ++failedCount;
        runner.emit('testFail', scenarioInfo);
      } else {
        runner.emit('testPass', scenarioInfo);
      }

      testResult.push(stepResults);
      stepResults = {
        description: null,
        assertions: [],
        duration: 0
      };
      scenarioFailed = false;

      if (originalHandleAfterScenarioEvent
          && typeof(originalHandleAfterScenarioEvent) === 'function') {
            originalHandleAfterScenarioEvent(event, callback);
      } else {
        callback();
      }
    };

    var originalHandleStepResultEvent = formatter.handleStepResultEvent;
    formatter.handleStepResultEvent = function(event, callback) {
      var stepResult = event.getPayloadItem('stepResult');
      var isStepFailed = stepResult.getStatus() === Cucumber.Status.FAILED;
      var isStepSuccessful = stepResult.getStatus() === Cucumber.Status.PASSED;

      if (isStepSuccessful) {
        stepResults.assertions.push({
          passed: true
        });
        stepResults.duration += stepResult.getDuration();
      } else if (isStepFailed) {
        scenarioFailed = true;
        var failureMessage = stepResult.getFailureException();
        stepResults.assertions.push({
          passed: false,
          errorMsg: failureMessage.message,
          stackTrace: failureMessage.stack
        });
        stepResults.duration += stepResult.getDuration();
      }

      if (originalHandleStepResultEvent
          && typeof(originalHandleStepResultEvent) === 'function') {
            originalHandleStepResultEvent(event, callback);
      } else {
        callback();
      }
    };
  };

  return runner.runTestPreparer().then(function() {
    return q.promise(function(resolve, reject) {
      var cucumberConf = Cucumber.Cli.Configuration(execOptions, specs);
      var runtime = Cucumber.Runtime(cucumberConf);
      var formatters = cucumberConf.getFormatters();

      addResultListener(formatters[0]);
      formatters.forEach(runtime.attachListener.bind(runtime));

      runtime.start(function() {
        try {
          if (runner.getConfig().onComplete) {
            runner.getConfig().onComplete();
          }
          resolve({
            failedCount: failedCount,
            specResults: testResult
          });
        } catch (err) {
          reject(err);
        }
      });
    });
  });
};
<|endoftext|>


<commit_before>const express = require('express')
const request = require('request')
const bodyParser = require('body-parser')
const path = require('path')
const crypto = require('crypto')

const app = express()
const port = process.env.PORT || 3000
const token = process.env.TOKEN || 'null'

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true}))

let wanted = require('./wanted.js')
let hint = require('./hint.js')
let map = require('./map.js')
let ill = require('./ill.js')

let chanel =
`章魚單車頻道：4747
御魂拾層頻道：1010
碎片挑戰頻道：818 (桃花/鐮鼬)
覺醒材料頻道：886
懸賞交換頻道：1700`
let help =
`懸賞 大天狗 - 顯示大天狗的分布情形
線索 蒲公英 - 顯示線索為蒲公英的目標，多個線索請以空白分隔
副本 第六章 - 顯示副本第六章的分布與金錢經驗掉落
圖鑑 螢草 - 顯示螢草的圖鑑資料
頻道 - 顯示特定用途的頻道代碼
留言 Bug - 對開發者留下訊息，例如報錯
幫助 - 顯示本訊息`
let autoReply =
`已將您的留言紀錄，但開發者不一定會回覆，請見諒`
let cmdNotFound =
`無法辨識指令，輸入 幫助 查看哪些指令可以用`

app.post('/in', (req, res) => {
  let data = req.body
  console.log(JSON.stringify(data))
  console.log(JSON.stringify(req.headers))
  if (data.object === 'page') {
    data.entry.forEach((entry) => {
      entry.messaging.forEach((event) => {
        if (event.message) {
          let reply = looker(event.message.text || 'Except', event.sender.id)
          reply.forEach((msg) => {
            if (typeof (msg) === 'string') {
              sendMsg(event.sender.id, msg)
            } else if (typeof (msg) === 'object') {
              sendMsg(event.sender.id, msg.payload, msg.type)
            } else {
              console.log(' Bad Msg, type should be string or object')
            }
          })
        } else if (event.postback) {
          let reply = looker(event.postback.payload || 'Except')
          reply.forEach((msg) => {
            if (typeof (msg) === 'string') {
              sendMsg(event.sender.id, msg)
            } else if (typeof (msg) === 'object') {
              sendMsg(event.sender.id, msg.payload, msg.type)
            } else {
              console.log(' Bad Msg, type should be string or object')
            }
          })
        } else { console.log(' Error ') }
      })
    })
  }
  res.sendStatus(200)
})

app.get('/pp.html', (req, res) => {
  res.sendFile(path.join(__dirname, 'pp.html'))
})

app.get('/in', (req, res) => {
  if (req.query['hub.mode'] === 'subscribe') {
    if (req.query['hub.verify_token'] === 'YalaBomm') res.send(req.query['hub.challenge'])
    else res.status(403)
  }
})

app.all('/', (req, res) => {
  res.sendStatus(403)
})

function looker (input, sender) {
  let spilt = input.match(/\S+/g)
  let reply = []
  switch (spilt[0]) {
    case '幫助':
      reply = reply.concat(help)
      break
    case '懸賞':
      reply = reply.concat(wanted[spilt[1]] || '查無資料，請確定目標名稱正確，查詢線索請使用 線索 (條件)')
      break
    case '副本':
      reply = reply.concat(mapLooker(spilt))
      break
    case '圖鑑':
      reply = reply.concat(illLooker(spilt[1]))
      break
    case '線索':
      reply = reply.concat(wantedLooker(spilt))
      break
    case '頻道':
      reply = reply.concat(chanel)
      break
    case '留言':
      comment(input, sender)
      reply = reply.concat(autoReply)
      break
    case '下載':
      reply = reply.concat('https://pa-da.github.io/onmyojibot/1_0_35.apk')
      reply = reply.concat('此 APK 由開發者製作，安裝 APK 有其風險，使用前請詳閱公開說明書(x)使用前請確知其風險。\nAPK 版本 1.0.35')
      break
    case '回覆':
      if (sender !== process.env.DEVID) {
        reply = reply.concat('Permission Denied')
      } else {
        replycomment(spilt)
        reply = reply.concat('已送出')
      }
      break
    default:
      reply = reply.concat(cmdNotFound)
  }
  return reply
}

function wantedLooker (dex) {
  let result = []
  dex.splice(0, 1)
  if (dex[0]) {
    let mat = hint[dex[0]] || []
    if (dex.length > 1) {
      dex.forEach((clue) => {
        let tmp = hint[clue]
        mat.forEach((sp) => {
          if (!(sp in tmp)) { mat.splice(mat.indexOf(sp), 1) }
        })
      })
    }
    result = result.concat('查詢結果為: ' + (mat.join(',') || '無資料'))
    if (mat.length === 1) { result = result.concat(wanted[mat[0]]) }
  } else { result = result.concat('沒有給予條件') }
  return result
}

let chint = '一二三四五六七八九十'

function mapLooker (dex) {
  let result = []
  dex.splice(0, 1)
  if (dex[0]) {
    let mapID
    map.alias.forEach((alias) => {
      if (dex[0] in alias) {
        mapID = alias[0]
      }
    })
    if (!mapID) {
      map.alias.forEach((alias) => {
        (function () {
          alias.forEach((name) => {
            if (dex[0].match(name)) {
              mapID = alias[0]
              if (!isNaN(dex[1])) {
                mapID += dex[1]
              } else {
                mapID += chint.indexOf(dex[1]) + 1
              }
              return 0
            }
          })
        })()
      })
    }
    result = result.concat(map.data[mapID] || '查無資料，若查詢御魂請以空白間隔層數（如：御魂 10），秘聞尚無資料')
  } else {
    result = result.concat('沒有給予條件')
  }
  return result
}

function illLooker (dex) {
  let result = []
  if (dex) {
    result = result.concat(ill[dex] || 'Sorry, 尚未新增資料' || '查無資料，請輸入式神全名（大天狗 ✔；狗狗 ✗)')
  } else {
    result = result.concat('沒有給予條件')
  }
  return result
}

function sendMsg (sender, payload, type = 'text') {
  request({
    uri: 'https://graph.facebook.com/v2.6/me/messages',
    qs: { access_token: token },
    method: 'POST',
    json: {
      recipient: { id: sender },
      message: type === 'text' ? { text: payload } : {attachment: { type: type, payload: payload }}
    }
  })
}

function replycomment (dex) {
  sendMsg(dex[1], dex[2])
}

function comment (input, sender) {
  sendMsg(process.env.DEVID, input)
  sendMsg(process.env.DEVID, `來自${sender}`)
}

app.listen(port, () => { console.log(`Listening to ${port}`) })
<commit_msg>verify<commit_after>const express = require('express')
const request = require('request')
const bodyParser = require('body-parser')
const path = require('path')
const crypto = require('crypto')

const app = express()
const port = process.env.PORT || 3000
const token = process.env.TOKEN || 'null'
const appToken = process.env.APPTOKEN || 'null'

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true}))

let wanted = require('./wanted.js')
let hint = require('./hint.js')
let map = require('./map.js')
let ill = require('./ill.js')

let chanel =
`章魚單車頻道：4747
御魂拾層頻道：1010
碎片挑戰頻道：818 (桃花/鐮鼬)
覺醒材料頻道：886
懸賞交換頻道：1700`
let help =
`懸賞 大天狗 - 顯示大天狗的分布情形
線索 蒲公英 - 顯示線索為蒲公英的目標，多個線索請以空白分隔
副本 第六章 - 顯示副本第六章的分布與金錢經驗掉落
圖鑑 螢草 - 顯示螢草的圖鑑資料
頻道 - 顯示特定用途的頻道代碼
留言 Bug - 對開發者留下訊息，例如報錯
幫助 - 顯示本訊息`
let autoReply =
`已將您的留言紀錄，但開發者不一定會回覆，請見諒`
let cmdNotFound =
`無法辨識指令，輸入 幫助 查看哪些指令可以用`

app.post('/in', (req, res) => {
  let verify = crypto.createHmac('sha1', appToken)
  let data = req.body
  verify.update(data, 'utf-8')
  console.log(verify.digest('hex'))
  console.log(JSON.stringify(data))
  console.log(JSON.stringify(req.headers))
  if (data.object === 'page') {
    data.entry.forEach((entry) => {
      entry.messaging.forEach((event) => {
        if (event.message) {
          let reply = looker(event.message.text || 'Except', event.sender.id)
          reply.forEach((msg) => {
            if (typeof (msg) === 'string') {
              sendMsg(event.sender.id, msg)
            } else if (typeof (msg) === 'object') {
              sendMsg(event.sender.id, msg.payload, msg.type)
            } else {
              console.log(' Bad Msg, type should be string or object')
            }
          })
        } else if (event.postback) {
          let reply = looker(event.postback.payload || 'Except')
          reply.forEach((msg) => {
            if (typeof (msg) === 'string') {
              sendMsg(event.sender.id, msg)
            } else if (typeof (msg) === 'object') {
              sendMsg(event.sender.id, msg.payload, msg.type)
            } else {
              console.log(' Bad Msg, type should be string or object')
            }
          })
        } else { console.log(' Error ') }
      })
    })
  }
  res.sendStatus(200)
})

app.get('/pp.html', (req, res) => {
  res.sendFile(path.join(__dirname, 'pp.html'))
})

app.get('/in', (req, res) => {
  if (req.query['hub.mode'] === 'subscribe') {
    if (req.query['hub.verify_token'] === 'YalaBomm') res.send(req.query['hub.challenge'])
    else res.status(403)
  }
})

app.all('/', (req, res) => {
  res.sendStatus(403)
})

function looker (input, sender) {
  let spilt = input.match(/\S+/g)
  let reply = []
  switch (spilt[0]) {
    case '幫助':
      reply = reply.concat(help)
      break
    case '懸賞':
      reply = reply.concat(wanted[spilt[1]] || '查無資料，請確定目標名稱正確，查詢線索請使用 線索 (條件)')
      break
    case '副本':
      reply = reply.concat(mapLooker(spilt))
      break
    case '圖鑑':
      reply = reply.concat(illLooker(spilt[1]))
      break
    case '線索':
      reply = reply.concat(wantedLooker(spilt))
      break
    case '頻道':
      reply = reply.concat(chanel)
      break
    case '留言':
      comment(input, sender)
      reply = reply.concat(autoReply)
      break
    case '下載':
      reply = reply.concat('https://pa-da.github.io/onmyojibot/1_0_35.apk')
      reply = reply.concat('此 APK 由開發者製作，安裝 APK 有其風險，使用前請詳閱公開說明書(x)使用前請確知其風險。\nAPK 版本 1.0.35')
      break
    case '回覆':
      if (sender !== process.env.DEVID) {
        reply = reply.concat('Permission Denied')
      } else {
        replycomment(spilt)
        reply = reply.concat('已送出')
      }
      break
    default:
      reply = reply.concat(cmdNotFound)
  }
  return reply
}

function wantedLooker (dex) {
  let result = []
  dex.splice(0, 1)
  if (dex[0]) {
    let mat = hint[dex[0]] || []
    if (dex.length > 1) {
      dex.forEach((clue) => {
        let tmp = hint[clue]
        mat.forEach((sp) => {
          if (!(sp in tmp)) { mat.splice(mat.indexOf(sp), 1) }
        })
      })
    }
    result = result.concat('查詢結果為: ' + (mat.join(',') || '無資料'))
    if (mat.length === 1) { result = result.concat(wanted[mat[0]]) }
  } else { result = result.concat('沒有給予條件') }
  return result
}

let chint = '一二三四五六七八九十'

function mapLooker (dex) {
  let result = []
  dex.splice(0, 1)
  if (dex[0]) {
    let mapID
    map.alias.forEach((alias) => {
      if (dex[0] in alias) {
        mapID = alias[0]
      }
    })
    if (!mapID) {
      map.alias.forEach((alias) => {
        (function () {
          alias.forEach((name) => {
            if (dex[0].match(name)) {
              mapID = alias[0]
              if (!isNaN(dex[1])) {
                mapID += dex[1]
              } else {
                mapID += chint.indexOf(dex[1]) + 1
              }
              return 0
            }
          })
        })()
      })
    }
    result = result.concat(map.data[mapID] || '查無資料，若查詢御魂請以空白間隔層數（如：御魂 10），秘聞尚無資料')
  } else {
    result = result.concat('沒有給予條件')
  }
  return result
}

function illLooker (dex) {
  let result = []
  if (dex) {
    result = result.concat(ill[dex] || 'Sorry, 尚未新增資料' || '查無資料，請輸入式神全名（大天狗 ✔；狗狗 ✗)')
  } else {
    result = result.concat('沒有給予條件')
  }
  return result
}

function sendMsg (sender, payload, type = 'text') {
  request({
    uri: 'https://graph.facebook.com/v2.6/me/messages',
    qs: { access_token: token },
    method: 'POST',
    json: {
      recipient: { id: sender },
      message: type === 'text' ? { text: payload } : {attachment: { type: type, payload: payload }}
    }
  })
}

function replycomment (dex) {
  sendMsg(dex[1], dex[2])
}

function comment (input, sender) {
  sendMsg(process.env.DEVID, input)
  sendMsg(process.env.DEVID, `來自${sender}`)
}

app.listen(port, () => { console.log(`Listening to ${port}`) })
<|endoftext|>


<commit_before>'use strict';

var express    = require('express')
  , env        = require('envoodoo')
  , fs         = require('fs')

env()

var filename = process.env.GAME_FILE_NAME

var app = express()

app.post('/game', function (req, res) {
  var write = fs.createWriteStream([
    __dirname,
    'save-game-files',
    filename
  ].join('/'))

  req.pipe(write)
  req.on('end', function () {
    console.log('[INFO]', 'data receive complete')
    res.send({success: true})
  })

  console.log('[INFO]', 'receiving data...')
})

app.get('/sync/:modified', function (req, res) {
  console.log('[INFO]', 'client requested sync')

  var filepath = [__dirname, 'save-game-files', filename].join('/')

  fs.exists(filepath, function (exists) {

    if (!exists) {
      res.status(304)
      return res.send('')
    }

    var stats = fs.stat(filepath, function (err, stats) {
      if (err) throw err

      var modified = stats.mtime.getTime()

      if (modified > req.params.modified) {
        console.log('[INFO]', 'client out of date, syncing...')
        var readstream = fs.createReadStream(filepath)
        .on('end', function () {
          console.log('[INFO]', 'synced')
        })

        readstream.pipe(res)
      }
      else {
        res.status(304)
        res.send('')
      }
    })
  })
})

app.listen(process.env.PORT)
console.log('CIV 5 Multiplayer server listening on port', process.env.PORT)
<commit_msg>Add semicolons for heroku<commit_after>'use strict';

var express    = require('express')
  , env        = require('envoodoo')
  , fs         = require('fs')

env();

var filename = process.env.GAME_FILE_NAME;

var app = express();

app.post('/game', function (req, res) {
  var write = fs.createWriteStream([
    __dirname,
    'save-game-files',
    filename
  ].join('/'))

  req.pipe(write)
  req.on('end', function () {
    console.log('[INFO]', 'data receive complete')
    res.send({success: true})
  })

  console.log('[INFO]', 'receiving data...')
})

app.get('/sync/:modified', function (req, res) {
  console.log('[INFO]', 'client requested sync')

  var filepath = [__dirname, 'save-game-files', filename].join('/')

  fs.exists(filepath, function (exists) {

    if (!exists) {
      res.status(304)
      return res.send('')
    }

    var stats = fs.stat(filepath, function (err, stats) {
      if (err) throw err

      var modified = stats.mtime.getTime()

      if (modified > req.params.modified) {
        console.log('[INFO]', 'client out of date, syncing...')
        var readstream = fs.createReadStream(filepath)
        .on('end', function () {
          console.log('[INFO]', 'synced')
        })

        readstream.pipe(res)
      }
      else {
        res.status(304)
        res.send('')
      }
    })
  })
})

app.listen(process.env.PORT)
console.log('CIV 5 Multiplayer server listening on port', process.env.PORT)
<|endoftext|>


<commit_before>// So you can `var request = require("superagent-es6-promise")`
var superagent = module.exports = require("superagent");
superagent.Promise = Promise;
var Request = superagent.Request;

// Create custom error type.
// Create a new object, that prototypally inherits from the Error constructor.
var SuperagentPromiseError = superagent.SuperagentPromiseError = function (message) {
  this.name = 'SuperagentPromiseError';
  this.message = message || 'Bad request';
};

SuperagentPromiseError.prototype = new Error();
SuperagentPromiseError.prototype.constructor = SuperagentPromiseError;

/**
 * @namespace utils
 * @class Superagent
 */

/**
 *
 * Add promise support for superagent/supertest
 *
 * Call .promise() to return promise for the request
 *
 * @method then
 * @return {Promise}
 */
Request.prototype.promise = function() {
  var req = this;
  var error;

  return new superagent.Promise(function(resolve, reject) {
      req.end(function(err, res) {
        if (err) {
          error = new SuperagentPromiseError(msg);
          error.status = res.status;
          error.body = res.body;
          error.res = res;
          reject(error);
        } else {
          resolve(res);
        }
      });
    });
};

/**
 *
 * Make superagent requests Promises/A+ conformant
 *
 * Call .then([onFulfilled], [onRejected]) to register callbacks
 *
 * @method then
 * @param {function} [onFulfilled]
 * @param {function} [onRejected]
 * @return {Bluebird.Promise}
 */
Request.prototype.then = function() {
  var promise = this.promise();
  return promise.then.apply(promise, arguments);
};
<commit_msg>change error<commit_after>// So you can `var request = require("superagent-es6-promise")`
var superagent = module.exports = require("superagent");
superagent.Promise = Promise;
var Request = superagent.Request;

// Create custom error type.
// Create a new object, that prototypally inherits from the Error constructor.
var SuperagentPromiseError = superagent.SuperagentPromiseError = function (message) {
  this.name = 'SuperagentPromiseError';
  this.message = message || 'Bad request';
};

SuperagentPromiseError.prototype = new Error();
SuperagentPromiseError.prototype.constructor = SuperagentPromiseError;

/**
 * @namespace utils
 * @class Superagent
 */

/**
 *
 * Add promise support for superagent/supertest
 *
 * Call .promise() to return promise for the request
 *
 * @method then
 * @return {Promise}
 */
Request.prototype.promise = function() {
  var req = this;
  var error;

  return new superagent.Promise(function(resolve, reject) {
      req.end(function(err, res) {
        if (err) {
          error = new SuperagentPromiseError(msg);
          if (res) {
            error.status = res.status;
            error.body = res.body;
            error.res = res;
          }
          reject(error);
        } else {
          resolve(res);
        }
      });
    });
};

/**
 *
 * Make superagent requests Promises/A+ conformant
 *
 * Call .then([onFulfilled], [onRejected]) to register callbacks
 *
 * @method then
 * @param {function} [onFulfilled]
 * @param {function} [onRejected]
 * @return {Bluebird.Promise}
 */
Request.prototype.then = function() {
  var promise = this.promise();
  return promise.then.apply(promise, arguments);
};
<|endoftext|>


<commit_before>const express = require('express')
const request = require('request')
const bodyParser = require('body-parser')
const path = require('path')

const app = express()
const port = process.env.PORT || 3000
const token = process.env.TOKEN || 'null'

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true}))

let wanted = require('./wanted.js')
let hint = require('./hint.js')
let map = require('./map.js')

let chanel =
`章魚單車頻道：4747
御魂拾層頻道：1010
碎片挑戰頻道：818 (桃花/鐮鼬)
覺醒材料頻道：886
懸賞交換頻道：1700`
let help =
`懸賞 大天狗 - 顯示大天狗的分布情形
線索 蒲公英 - 顯示線索為蒲公英的目標，多個線索請以空白分隔
副本 第六章 - 顯示副本第六章的分布與金錢經驗掉落
圖鑑 螢草 - 顯示螢草的圖鑑資料
頻道 - 顯示特定用途的頻道代碼
留言 Bug - 對開發者留下訊息，例如報錯
幫助 - 顯示本訊息`
let autoReply =
`已將您的留言紀錄，但開發者不一定會回覆，請見諒`
let cmdNotFound =
`無法辨識指令，輸入 幫助 查看哪些指令可以用`

app.post('/in', (req, res) => {
  let data = req.body
  if (data.object === 'page') {
    data.entry.forEach((entry) => {
      entry.messaging.forEach((event) => {
        if (event.message) {
          let reply = looker(event.message.text)
          reply.forEach((msg) => {
            sendMsg(event.sender.id, msg)
          })
        } else { console.log(' Error ') }
      })
    })
  }
  res.sendStatus(200)
})

app.get('/pp.html', (req, res) => {
  res.sendFile(path.join(__dirname, 'pp.html'))
})

app.get('/in', (req, res) => {
  if (req.query['hub.mode'] === 'subscribe') {
    if (req.query['hub.verify_token'] === 'YalaBomm') res.send(req.query['hub.challenge'])
    else res.status(403)
  }
})

app.all('/', (req, res) => {
  res.sendStatus(403)
})

function looker (input) {
  let spilt = input.match(/\S+/g)
  let reply = []
  switch (spilt[0]) {
    case '幫助':
      reply = reply.concat(help)
      break
    case '懸賞':
      reply = reply.concat(wanted[spilt[1]] || '查無資料，請確定目標名稱正確，查詢線索請使用 線索 (條件)')
      break
    case '副本':
      reply = reply.concat(mapLooker(spilt) || '查無資料，請確定目標名稱正確')
      break
    case '圖鑑':
      reply = reply.concat(illLooker(spilt[1]))
      break
    case '線索':
      reply = reply.concat(wantedLooker(spilt))
      break
    case '頻道':
      reply = reply.concat(chanel)
      break
    case '留言':
      comment(input)
      reply = reply.concat(autoReply)
      break
    default:
      reply = reply.concat(cmdNotFound)
  }
  return reply
}

function wantedLooker (dex) {
  let result = []
  dex.splice(0, 1)
  if (dex[0]) {
    let mat = hint[dex[0]]
    if (dex.length > 1) {
      dex.forEach((clue) => {
        let tmp = hint[clue]
        mat.forEach((sp) => {
          if (!(sp in tmp)) { mat.splice(mat.indexOf(sp), 1) }
        })
      })
    }
    result = result.concat('查詢結果為: ' + (mat.join(',') || '無資料'))
    if (mat.length === 1) { result = result.concat(wanted[mat[0]]) }
  } else { result = result.concat('沒有給予條件') }
  return result
}

let chint = '一二三四五六七八九十'

function mapLooker (dex) {
  let result = []
  console.log(dex)
  dex.splice(0, 1)
  if (dex[0]) {
    let mapID
    map.alias.forEach((alias) => {
      if (dex[0] in alias) {
        mapID = alias[0]
      }
    })
    if (!mapID) {
      map.alias.forEach((alias) => {
        alias.forEach((name) => {
          if (dex[0].match(name)) {
            mapID = alias[0]
          }
          console.log(dex[1])
          console.log(mapID)
          if (isNaN(dex[1])) {
            mapID += dex[1]
          } else {
            mapID += chint.indexOf(dex[1]) + 1
          }
          console.log(mapID)
        })
      })
    }
    result = result.concat(map.data[mapID] || '查無資料，若查詢御魂請以空白間隔層數（如：御魂 10），秘聞尚無資料')
  } else {
    result = result.concat('沒有給予條件')
  }
  return result
}

function illLooker (dex) {
  let result = []
  dex.splice(0, 1)
  if (dex[0]) {
    result = result.concat(ill[dex[0]] || '查無資料，請輸入式神全名（大天狗 ✔；狗狗 ✗)')
  } else {
    result = result.concat('沒有給予條件')
  }
  return result
}

function sendMsg (sender, message, isImg = false) {
  request({
    uri: 'https://graph.facebook.com/v2.6/me/messages',
    qs: { access_token: token },
    method: 'POST',
    json: {
      recipient: { id: sender },
      message: isImg ? {attachment: { type: 'image', payload: message }} : { text: message }
    }
  })
}

function comment (input) {
  sendMsg(process.env.DEVID, input)
}

app.listen(port, () => { console.log(`Listening to ${port}`) })
<commit_msg>Fixed Map<commit_after>const express = require('express')
const request = require('request')
const bodyParser = require('body-parser')
const path = require('path')

const app = express()
const port = process.env.PORT || 3000
const token = process.env.TOKEN || 'null'

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true}))

let wanted = require('./wanted.js')
let hint = require('./hint.js')
let map = require('./map.js')

let chanel =
`章魚單車頻道：4747
御魂拾層頻道：1010
碎片挑戰頻道：818 (桃花/鐮鼬)
覺醒材料頻道：886
懸賞交換頻道：1700`
let help =
`懸賞 大天狗 - 顯示大天狗的分布情形
線索 蒲公英 - 顯示線索為蒲公英的目標，多個線索請以空白分隔
副本 第六章 - 顯示副本第六章的分布與金錢經驗掉落
圖鑑 螢草 - 顯示螢草的圖鑑資料
頻道 - 顯示特定用途的頻道代碼
留言 Bug - 對開發者留下訊息，例如報錯
幫助 - 顯示本訊息`
let autoReply =
`已將您的留言紀錄，但開發者不一定會回覆，請見諒`
let cmdNotFound =
`無法辨識指令，輸入 幫助 查看哪些指令可以用`

app.post('/in', (req, res) => {
  let data = req.body
  if (data.object === 'page') {
    data.entry.forEach((entry) => {
      entry.messaging.forEach((event) => {
        if (event.message) {
          let reply = looker(event.message.text)
          reply.forEach((msg) => {
            sendMsg(event.sender.id, msg)
          })
        } else { console.log(' Error ') }
      })
    })
  }
  res.sendStatus(200)
})

app.get('/pp.html', (req, res) => {
  res.sendFile(path.join(__dirname, 'pp.html'))
})

app.get('/in', (req, res) => {
  if (req.query['hub.mode'] === 'subscribe') {
    if (req.query['hub.verify_token'] === 'YalaBomm') res.send(req.query['hub.challenge'])
    else res.status(403)
  }
})

app.all('/', (req, res) => {
  res.sendStatus(403)
})

function looker (input) {
  let spilt = input.match(/\S+/g)
  let reply = []
  switch (spilt[0]) {
    case '幫助':
      reply = reply.concat(help)
      break
    case '懸賞':
      reply = reply.concat(wanted[spilt[1]] || '查無資料，請確定目標名稱正確，查詢線索請使用 線索 (條件)')
      break
    case '副本':
      reply = reply.concat(mapLooker(spilt) || '查無資料，請確定目標名稱正確')
      break
    case '圖鑑':
      reply = reply.concat(illLooker(spilt[1]))
      break
    case '線索':
      reply = reply.concat(wantedLooker(spilt))
      break
    case '頻道':
      reply = reply.concat(chanel)
      break
    case '留言':
      comment(input)
      reply = reply.concat(autoReply)
      break
    default:
      reply = reply.concat(cmdNotFound)
  }
  return reply
}

function wantedLooker (dex) {
  let result = []
  dex.splice(0, 1)
  if (dex[0]) {
    let mat = hint[dex[0]]
    if (dex.length > 1) {
      dex.forEach((clue) => {
        let tmp = hint[clue]
        mat.forEach((sp) => {
          if (!(sp in tmp)) { mat.splice(mat.indexOf(sp), 1) }
        })
      })
    }
    result = result.concat('查詢結果為: ' + (mat.join(',') || '無資料'))
    if (mat.length === 1) { result = result.concat(wanted[mat[0]]) }
  } else { result = result.concat('沒有給予條件') }
  return result
}

let chint = '一二三四五六七八九十'

function mapLooker (dex) {
  let result = []
  console.log(dex)
  dex.splice(0, 1)
  if (dex[0]) {
    let mapID
    map.alias.forEach((alias) => {
      if (dex[0] in alias) {
        mapID = alias[0]
      }
    })
    if (!mapID) {
      map.alias.forEach((alias) => {
        function () {
          alias.forEach((name) => {
            if (dex[0].match(name)) {
              mapID = alias[0]
              if (isNaN(dex[1])) {
                mapID += dex[1]
              } else {
                mapID += chint.indexOf(dex[1]) + 1
              }
              return 0
            }
          })
        })()
      })
    }
    result = result.concat(map.data[mapID] || '查無資料，若查詢御魂請以空白間隔層數（如：御魂 10），秘聞尚無資料')
  } else {
    result = result.concat('沒有給予條件')
  }
  return result
}

function illLooker (dex) {
  let result = []
  dex.splice(0, 1)
  if (dex[0]) {
    result = result.concat(ill[dex[0]] || '查無資料，請輸入式神全名（大天狗 ✔；狗狗 ✗)')
  } else {
    result = result.concat('沒有給予條件')
  }
  return result
}

function sendMsg (sender, message, isImg = false) {
  request({
    uri: 'https://graph.facebook.com/v2.6/me/messages',
    qs: { access_token: token },
    method: 'POST',
    json: {
      recipient: { id: sender },
      message: isImg ? {attachment: { type: 'image', payload: message }} : { text: message }
    }
  })
}

function comment (input) {
  sendMsg(process.env.DEVID, input)
}

app.listen(port, () => { console.log(`Listening to ${port}`) })
<|endoftext|>


<commit_before>/**
 * archiver-utils
 *
 * Copyright (c) 2015 Chris Talkington.
 * Licensed under the MIT license.
 * https://github.com/archiverjs/archiver-utils/blob/master/LICENSE
 */
var fs = require('fs');
var path = require('path');
var nutil = require('util');
var lazystream = require('lazystream');
var normalizePath = require('normalize-path');
var _ = require('lodash');

var Stream = require('stream').Stream;
var PassThrough = require('readable-stream').PassThrough;

var utils = module.exports = {};
utils.file = require('./file.js');

function assertPath(path) {
  if (typeof path !== 'string') {
    throw new TypeError('Path must be a string. Received ' + nutils.inspect(path));
  }
}

utils.collectStream = function(source, callback) {
  var collection = [];
  var size = 0;

  source.on('error', callback);

  source.on('data', function(chunk) {
    collection.push(chunk);
    size += chunk.length;
  });

  source.on('end', function() {
    var buf = new Buffer(size, 'utf8');
    var offset = 0;

    collection.forEach(function(data) {
      data.copy(buf, offset);
      offset += data.length;
    });

    callback(null, buf);
  });
};

utils.dateify = function(dateish) {
  dateish = dateish || new Date();

  if (dateish instanceof Date) {
    dateish = dateish;
  } else if (typeof dateish === 'string') {
    dateish = new Date(dateish);
  } else {
    dateish = new Date();
  }

  return dateish;
};

// this is slightly different from lodash version
utils.defaults = function(object, source, guard) {
  var args = arguments;
  args[0] = args[0] || {};

  return _.defaults.apply(_, args);
};

utils.isStream = function(source) {
  return source instanceof Stream;
};

utils.lazyReadStream = function(filepath) {
  return new lazystream.Readable(function() {
    return fs.createReadStream(filepath);
  });
};

utils.normalizeInputSource = function(source) {
  if (source === null) {
    return new Buffer(0);
  } else if (typeof source === 'string') {
    return new Buffer(source);
  } else if (utils.isStream(source) && !source._readableState) {
    var normalized = new PassThrough();
    source.pipe(normalized);

    return normalized;
  }

  return source;
};

utils.sanitizePath = function(filepath) {
  return normalizePath(filepath, false).replace(/^\w+:/, '').replace(/^(\.\.\/|\/)+/, '');
};

utils.trailingSlashIt = function(str) {
  return str.slice(-1) !== '/' ? str + '/' : str;
};

utils.unixifyPath = function(filepath) {
  return normalizePath(filepath, false).replace(/^\w+:/, '');
};

utils.walkdir = function(dirpath, base, callback) {
  var results = [];

  if (typeof base === 'function') {
    callback = base;
    base = dirpath;
  }

  fs.readdir(dirpath, function(err, list) {
    var i = 0;
    var file;
    var filepath;

    if (err) {
      return callback(err);
    }

    (function next() {
      file = list[i++];

      if (!file) {
        return callback(null, results);
      }

      filepath = path.join(dirpath, file);

      fs.stat(filepath, function(err, stats) {
        results.push({
          path: filepath,
          relative: path.relative(base, filepath).replace(/\\/g, '/'),
          stats: stats
        });

        if (stats && stats.isDirectory()) {
          utils.walkdir(filepath, base, function(err, res) {
            results = results.concat(res);
            next();
          });
        } else {
          next();
        }
      });
    })();
  });
};<commit_msg>switch to Array.push instead of concat for performance. closes #1.<commit_after>/**
 * archiver-utils
 *
 * Copyright (c) 2015 Chris Talkington.
 * Licensed under the MIT license.
 * https://github.com/archiverjs/archiver-utils/blob/master/LICENSE
 */
var fs = require('fs');
var path = require('path');
var nutil = require('util');
var lazystream = require('lazystream');
var normalizePath = require('normalize-path');
var _ = require('lodash');

var Stream = require('stream').Stream;
var PassThrough = require('readable-stream').PassThrough;

var utils = module.exports = {};
utils.file = require('./file.js');

function assertPath(path) {
  if (typeof path !== 'string') {
    throw new TypeError('Path must be a string. Received ' + nutils.inspect(path));
  }
}

utils.collectStream = function(source, callback) {
  var collection = [];
  var size = 0;

  source.on('error', callback);

  source.on('data', function(chunk) {
    collection.push(chunk);
    size += chunk.length;
  });

  source.on('end', function() {
    var buf = new Buffer(size, 'utf8');
    var offset = 0;

    collection.forEach(function(data) {
      data.copy(buf, offset);
      offset += data.length;
    });

    callback(null, buf);
  });
};

utils.dateify = function(dateish) {
  dateish = dateish || new Date();

  if (dateish instanceof Date) {
    dateish = dateish;
  } else if (typeof dateish === 'string') {
    dateish = new Date(dateish);
  } else {
    dateish = new Date();
  }

  return dateish;
};

// this is slightly different from lodash version
utils.defaults = function(object, source, guard) {
  var args = arguments;
  args[0] = args[0] || {};

  return _.defaults.apply(_, args);
};

utils.isStream = function(source) {
  return source instanceof Stream;
};

utils.lazyReadStream = function(filepath) {
  return new lazystream.Readable(function() {
    return fs.createReadStream(filepath);
  });
};

utils.normalizeInputSource = function(source) {
  if (source === null) {
    return new Buffer(0);
  } else if (typeof source === 'string') {
    return new Buffer(source);
  } else if (utils.isStream(source) && !source._readableState) {
    var normalized = new PassThrough();
    source.pipe(normalized);

    return normalized;
  }

  return source;
};

utils.sanitizePath = function(filepath) {
  return normalizePath(filepath, false).replace(/^\w+:/, '').replace(/^(\.\.\/|\/)+/, '');
};

utils.trailingSlashIt = function(str) {
  return str.slice(-1) !== '/' ? str + '/' : str;
};

utils.unixifyPath = function(filepath) {
  return normalizePath(filepath, false).replace(/^\w+:/, '');
};

utils.walkdir = function(dirpath, base, callback) {
  var results = [];

  if (typeof base === 'function') {
    callback = base;
    base = dirpath;
  }

  fs.readdir(dirpath, function(err, list) {
    var i = 0;
    var file;
    var filepath;

    if (err) {
      return callback(err);
    }

    (function next() {
      file = list[i++];

      if (!file) {
        return callback(null, results);
      }

      filepath = path.join(dirpath, file);

      fs.stat(filepath, function(err, stats) {
        results.push({
          path: filepath,
          relative: path.relative(base, filepath).replace(/\\/g, '/'),
          stats: stats
        });

        if (stats && stats.isDirectory()) {
          utils.walkdir(filepath, base, function(err, res) {
            Array.prototype.push.apply(results, res);
            next();
          });
        } else {
          next();
        }
      });
    })();
  });
};<|endoftext|>