<commit_before>// closure
var serviceHandler = function (service) {
  return function (data, params, fn) {
    /*
     * - data (object): request data payload
     * - params (object): use to replace the tokens in the url
     * - fn (function): callback to used, with a default which emits 'success' or 'error'
     *   event depending on res.ok value
     *
     * support these different format calls:
     *
     *  - function (data, fn)
     *  - function (data, params, fn)
     */
    // function (data, fn) used
    if ('function' === typeof params && !fn) {
      params = undefined;
      fn = params;
    }
    var req = this.request(service, data, params).end(fn ? fn : function (res) {
      req.emit(res.ok ? "success" : "error", res, data, params);
    });
    return req;
  };
};

function Api(config) {
  this.config = config;

  // create a hash-liked object where all the services handlers are registered
  this.api = Object.create(null);

  for (var name in config.services) {
    if (!Object.prototype.hasOwnProperty(this, name)) {
      // syntatic sugar: install a service handler available on
      // the api instance with service name
      this.api[name] = serviceHandler(name).bind(this);
    }
  }
}

Api.prototype = {
  paramsPattern: /:(\w+)/g,

  service: function (id) {
    return this.config.services[id];
  },

  url: function (id) {
    var url = this.config.baseUrl;
    var resource = this.service(id);

    // from time being it"s a simple map
    if (resource) {
      var path;

      if (typeof resource === "string") {
        path = resource;
      }

      if (typeof resource === "object" && resource.path !== undefined) {
        path = resource.path;
      }

      if (!path) {
        throw new Error("path is not defined for route $" + id);
      }

      url += path[0] === "/" ? path : "/" + path;
    }

    return url;
  },

  replaceUrl: function (url, params) {
    var tokens = url.match(this.paramsPattern);

    if (!tokens || !tokens.length) {
      return url;
    }

    for (var i = 0, len = tokens.length; i < len; i += 1) {
      var token = tokens[i];
      var name = token.substring(1);
      if (params[name]) {
        url = url.replace(token, params[name]);
      }
    }
    return url;
  },

  buildUrlQuery: function (query) {
    if (!query) {
      return '';
    }

    var queryString;

    if (typeof query === "string") {
      queryString = query;
    }
    else {
      var queryArgs = [];
      for (var queryArg in query) {
        queryArgs.push(queryArg + '=' + query[queryArg]);
      }
      queryString = queryArgs.join("&");
    }
    return queryString ? '?' + queryString : '';
  },

  buildUrl: function (id, params, query) {
    var url = this.url(id);

    if (params) {
      url = this.replaceUrl(url, params);
    }

    if (query) {
      url += this.buildUrlQuery(query);
    }

    return url;
  },

  request: function (id, data, params) {
    var service = this.service(id);
    var method = (typeof service === "string" ? "get" : service.method || "get").toLowerCase();
    // fix for delete being a reserved word
    if (method === "delete") {
      method = "del";
    }

    if (!this.agent) {
      throw new Error('missing superagent or any api compatible agent.')
    }

    var request = this.agent[method];
    if (!request) {
      throw new Error("Invalid method [" + service.method + "]");
    }

    var _req = request(this.buildUrl(id, params), data);

    // add global headers
    this._setHeaders(_req, this.config.headers);

    // add global options to request headers
    this._setOptions(_req, this.config.options);

    // add service options to request headers
    this._setOptions(_req, service.options);

    // add service headers
    this._setHeaders(_req, service.headers);

    // add runtime headers
    this._setHeaders(_req, this.headers);

    // set credentials
    if (this.config.withCredentials) {
      _req.withCredentials();
    }

    return _req;
  },

  addHeader: function (name, value) {
    this.headers = this.headers || {};
    this.headers[name] = value;
  },

  removeHeader: function (name) {
    if (this.headers && this.headers[name]) {
      delete this.headers[name];
    }
  },

  _setHeaders: function (req, headers) {
    for (var header in headers) {
      req.set(header, headers[header]);
    }
  },

  _setOptions: function (req, options) {
    for (var option in options) {
      req[option](options[option]);
    }
  }
};

export default Api;
<commit_msg>feat(request): add support for query string<commit_after>// closure
var serviceHandler = function (service) {
  return function (data, params, fn) {
    /*
     * - data (object): request data payload
     * - params (object): use to replace the tokens in the url
     * - fn (function): callback to used, with a default which emits 'success' or 'error'
     *   event depending on res.ok value
     *
     * support these different format calls:
     *
     *  - function (data, fn)
     *  - function (data, params, fn)
     */
    // function (data, fn) used
    if ('function' === typeof params && !fn) {
      params = undefined;
      fn = params;
    }
    var req = this.request(service, data, params).end(fn ? fn : function (res) {
      req.emit(res.ok ? "success" : "error", res, data, params);
    });
    return req;
  };
};

function Api(config) {
  this.config = config;

  // create a hash-liked object where all the services handlers are registered
  this.api = Object.create(null);

  for (var name in config.services) {
    if (!Object.prototype.hasOwnProperty(this, name)) {
      // syntatic sugar: install a service handler available on
      // the api instance with service name
      this.api[name] = serviceHandler(name).bind(this);
    }
  }
}

Api.prototype = {
  paramsPattern: /:(\w+)/g,

  service: function (id) {
    return this.config.services[id];
  },

  url: function (id) {
    var url = this.config.baseUrl;
    var resource = this.service(id);

    // from time being it"s a simple map
    if (resource) {
      var path;

      if (typeof resource === "string") {
        path = resource;
      }

      if (typeof resource === "object" && resource.path !== undefined) {
        path = resource.path;
      }

      if (!path) {
        throw new Error("path is not defined for route $" + id);
      }

      url += path[0] === "/" ? path : "/" + path;
    }

    return url;
  },

  replaceUrl: function (url, params) {
    var tokens = url.match(this.paramsPattern);

    if (!tokens || !tokens.length) {
      return url;
    }

    for (var i = 0, len = tokens.length; i < len; i += 1) {
      var token = tokens[i];
      var name = token.substring(1);
      if (params[name]) {
        url = url.replace(token, params[name]);
      }
    }
    return url;
  },

  buildUrlQuery: function (query) {
    if (!query) {
      return '';
    }

    var queryString;

    if (typeof query === "string") {
      queryString = query;
    }
    else {
      var queryArgs = [];
      for (var queryArg in query) {
        queryArgs.push(queryArg + '=' + query[queryArg]);
      }
      queryString = queryArgs.join("&");
    }
    return queryString ? '?' + queryString : '';
  },

  buildUrl: function (id, params, query) {
    var url = this.url(id);

    if (params) {
      url = this.replaceUrl(url, params);
    }

    if (query) {
      url += this.buildUrlQuery(query);
    }

    return url;
  },

  request: function (id, data, params, query) {
    var service = this.service(id);
    var method = (typeof service === "string" ? "get" : service.method || "get").toLowerCase();
    // fix for delete being a reserved word
    if (method === "delete") {
      method = "del";
    }

    if (!this.agent) {
      throw new Error('missing superagent or any api compatible agent.')
    }

    var request = this.agent[method];
    if (!request) {
      throw new Error("Invalid method [" + service.method + "]");
    }

    var _req = request(this.buildUrl(id, params, query), data);

    // add global headers
    this._setHeaders(_req, this.config.headers);

    // add global options to request headers
    this._setOptions(_req, this.config.options);

    // add service options to request headers
    this._setOptions(_req, service.options);

    // add service headers
    this._setHeaders(_req, service.headers);

    // add runtime headers
    this._setHeaders(_req, this.headers);

    // set credentials
    if (this.config.withCredentials) {
      _req.withCredentials();
    }

    return _req;
  },

  addHeader: function (name, value) {
    this.headers = this.headers || {};
    this.headers[name] = value;
  },

  removeHeader: function (name) {
    if (this.headers && this.headers[name]) {
      delete this.headers[name];
    }
  },

  _setHeaders: function (req, headers) {
    for (var header in headers) {
      req.set(header, headers[header]);
    }
  },

  _setOptions: function (req, options) {
    for (var option in options) {
      req[option](options[option]);
    }
  }
};

export default Api;
<|endoftext|>


<commit_before>// dependencies
var fs = require('fs')
  , PNG = require('pngjs').PNG
  ;

/**
 *  ImageToAscii
 *  Convert images to ASCII art strings.
 *
 *  ===============================
 *  Licensed under the MIT license:
 *  The MIT License (MIT)
 *
 *  Copyright (c) 2014 Ionică Bizău
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all
 *  copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  SOFTWARE.
 *
 */
var ImageToAscii = function (options) {

    // use 'new'
    if (this.constructor !== ImageToAscii) {
        throw new Error ("Use 'new' keyword to create the ImageToAscii instance");
    }

    // force options to be an object
    options = Object (options);
    options.pixels = (String(options.pixels || "") || " .,:;i1tfLCG08@").split("");
    options.multiplyWidth = Number (options.multiplyWidth) || 2;

    // globals
    var precision = 1020 / (options.pixels.length - 1)
      , asciiPixels = options.pixels
      ;

    // reverse pixels
    if (options.reverse) {
        options.pixels.reverse();
    }

    // multiply width
    if (options.multiplyWidth) {

        // empty the asciiPixels array
        asciiPixels = [];

        // aspect ratio fix
        for (var i = 0; i < options.pixels.length; ++i) {

            // how many times we need to multiply the pixel
            for (var ii = 0, cPixel = ""; ii < options.multiplyWidth; ++ii) {
                cPixel += options.pixels[i];
            }

            // push the pixel
            asciiPixels.push(cPixel);
        }
    }

    return {
        /**
         *  ImageToAscii#convert
         *  Converts a png image to ASCII art
         *
         *  Arguments
         *    @imagePath: the path to the PNG image
         *    @callback: the callback function
         *
         */
        convert: function (imagePath, callback) {

            // force image path to be a string
            imagePath = String (imagePath);

            // force callback to be a function
            callback = callback || function () {};
            if (typeof callback !== "function") {
                throw new Error ("Callback must be a function");
            }

            // read the image
            var stream = fs.createReadStream(imagePath).pipe(new PNG({ filterType: 4 }));

            // image parsed
            stream.on('parsed', function () {

                // each pixel
                for (var y = 0, converted; y < this.height; y++) {
                    for (var x = 0; x < this.width; x++) {

                        // get the index, the sum of rgb and build the ASCII pixel
                        var idx = (this.width * y + x) << 2
                          , value = this.data[idx] + this.data[idx + 1] + this.data[idx + 2] + this.data[idx + 3]
                          , thisPixel = asciiPixels[Math.round(value / precision)]
                          ;

                        // add the new pixel to the image
                        converted += thisPixel;
                    }

                    // add new line
                    converted += "\n";
                }

                callback (null, converted);
            });
        }
    }
};

module.exports = ImageToAscii;
<commit_msg>Initialize converted variable as empty string<commit_after>// dependencies
var fs = require('fs')
  , PNG = require('pngjs').PNG
  ;

/**
 *  ImageToAscii
 *  Convert images to ASCII art strings.
 *
 *  ===============================
 *  Licensed under the MIT license:
 *  The MIT License (MIT)
 *
 *  Copyright (c) 2014 Ionică Bizău
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all
 *  copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  SOFTWARE.
 *
 */
var ImageToAscii = function (options) {

    // use 'new'
    if (this.constructor !== ImageToAscii) {
        throw new Error ("Use 'new' keyword to create the ImageToAscii instance");
    }

    // force options to be an object
    options = Object (options);
    options.pixels = (String(options.pixels || "") || " .,:;i1tfLCG08@").split("");
    options.multiplyWidth = Number (options.multiplyWidth) || 2;

    // globals
    var precision = 1020 / (options.pixels.length - 1)
      , asciiPixels = options.pixels
      ;

    // reverse pixels
    if (options.reverse) {
        options.pixels.reverse();
    }

    // multiply width
    if (options.multiplyWidth) {

        // empty the asciiPixels array
        asciiPixels = [];

        // aspect ratio fix
        for (var i = 0; i < options.pixels.length; ++i) {

            // how many times we need to multiply the pixel
            for (var ii = 0, cPixel = ""; ii < options.multiplyWidth; ++ii) {
                cPixel += options.pixels[i];
            }

            // push the pixel
            asciiPixels.push(cPixel);
        }
    }

    return {
        /**
         *  ImageToAscii#convert
         *  Converts a png image to ASCII art
         *
         *  Arguments
         *    @imagePath: the path to the PNG image
         *    @callback: the callback function
         *
         */
        convert: function (imagePath, callback) {

            // force image path to be a string
            imagePath = String (imagePath);

            // force callback to be a function
            callback = callback || function () {};
            if (typeof callback !== "function") {
                throw new Error ("Callback must be a function");
            }

            // read the image
            var stream = fs.createReadStream(imagePath).pipe(new PNG({ filterType: 4 }));

            // image parsed
            stream.on('parsed', function () {

                // each pixel
                for (var y = 0, converted = ""; y < this.height; y++) {
                    for (var x = 0; x < this.width; x++) {

                        // get the index, the sum of rgb and build the ASCII pixel
                        var idx = (this.width * y + x) << 2
                          , value = this.data[idx] + this.data[idx + 1] + this.data[idx + 2] + this.data[idx + 3]
                          , thisPixel = asciiPixels[Math.round(value / precision)]
                          ;

                        // add the new pixel to the image
                        converted += thisPixel;
                    }

                    // add new line
                    converted += "\n";
                }

                callback (null, converted);
            });
        }
    }
};

module.exports = ImageToAscii;
<|endoftext|>


<commit_before>'use strict';
/* eslint-disable no-console */
const native = require('assert');
const YAML = require('yamljs');

console.log('TAP version 13');

let tests = 0;
let passes = 0;
let failures = 0;

function printError(err) {
  const message = {
    name: err.name,
    message: err.message,
    stack: err.stack.split('\n'),
  };
  console.log('  ---');
  console.log(YAML.stringify(message, 4).replace(/^/gm, '  '));
  console.log('  ...');
}

function test(message, fn) {
  return (() => {
    const args = Array.prototype.slice.call(arguments);
    tests++;
    try {
      fn.apply(native, [args]);
      console.log(`ok ${tests} ${message || ''}`);
      passes++;
    } catch (err) {
      console.log(`not ok ${tests} ${message || ''}`);
      failures++;
      printError(err);
    }
  })();
}

const assert = (value, message) => test(message, () => native(value, message));

assert.deepEqual = (actual, expected, message) => {
  test(message, () => native.deepEqual(actual, expected, message));
};

assert.deepStrictEqual = (actual, expected, message) => {
  test(message, () => native.deepStrictEqual(actual, expected, message));
};

assert.doesNotThrow = (block, error, message) => {
  test(message, () => native.doesNotThrow(block, error, message));
};

assert.equal = (actual, expected, message) => {
  test(message, () => native.equal(actual, expected, message));
};

assert.fail = (actual, expected, message, operator) => {
  test(message, () => native.fail(actual, expected, message, operator));
};

assert.ifError = (value) => {
  test('', () => native.ifError(value));
};

assert.notDeepEqual = (actual, expected, message) => {
  test(message, () => native.notDeepEqual(actual, expected, message));
};

assert.notDeepStrictEqual = (actual, expected, message) => {
  test(message, () => native.notDeepStrictEqual(actual, expected, message));
};

assert.ok = (value, message) => {
  test(message, () => native.ok(value, message));
};

assert.throws = (block, error, message) => {
  test(message, () => native.throws(block, error, message));
};

process.on('uncaughtException', (err) => {
  console.log(`Bail out! Uncaught exception ${err.name}`);
  printError(err);
  process.exit(1);
});

process.on('exit', (code) => {
  if (code === 0) {
    console.log(`1..${tests}`);
    console.log(`# tests ${tests}`);
    console.log(`# pass ${passes}`);
    console.log(`# fail ${failures}`);
  }
});

module.exports = assert;
<commit_msg>exit code when tests fail<commit_after>'use strict';
/* eslint-disable no-console */
const native = require('assert');
const YAML = require('yamljs');

console.log('TAP version 13');

let tests = 0;
let passes = 0;
let failures = 0;

function printError(err) {
  const message = {
    name: err.name,
    message: err.message,
    stack: err.stack.split('\n'),
  };
  console.log('  ---');
  console.log(YAML.stringify(message, 4).replace(/^/gm, '  '));
  console.log('  ...');
}

function test(message, fn) {
  return (() => {
    const args = Array.prototype.slice.call(arguments);
    tests++;
    try {
      fn.apply(native, [args]);
      console.log(`ok ${tests} ${message || ''}`);
      passes++;
    } catch (err) {
      console.log(`not ok ${tests} ${message || ''}`);
      failures++;
      printError(err);
    }
  })();
}

const assert = (value, message) => test(message, () => native(value, message));

assert.deepEqual = (actual, expected, message) => {
  test(message, () => native.deepEqual(actual, expected, message));
};

assert.deepStrictEqual = (actual, expected, message) => {
  test(message, () => native.deepStrictEqual(actual, expected, message));
};

assert.doesNotThrow = (block, error, message) => {
  test(message, () => native.doesNotThrow(block, error, message));
};

assert.equal = (actual, expected, message) => {
  test(message, () => native.equal(actual, expected, message));
};

assert.fail = (actual, expected, message, operator) => {
  test(message, () => native.fail(actual, expected, message, operator));
};

assert.ifError = (value) => {
  test('', () => native.ifError(value));
};

assert.notDeepEqual = (actual, expected, message) => {
  test(message, () => native.notDeepEqual(actual, expected, message));
};

assert.notDeepStrictEqual = (actual, expected, message) => {
  test(message, () => native.notDeepStrictEqual(actual, expected, message));
};

assert.ok = (value, message) => {
  test(message, () => native.ok(value, message));
};

assert.throws = (block, error, message) => {
  test(message, () => native.throws(block, error, message));
};

process.on('uncaughtException', (err) => {
  console.log(`Bail out! Uncaught exception ${err.name}`);
  printError(err);
  process.exit(1);
});

process.on('exit', (code) => {
  if (code === 0) {
    console.log(`1..${tests}`);
    console.log(`# tests ${tests}`);
    console.log(`# pass ${passes}`);
    console.log(`# fail ${failures}`);
    if (failures > 0) {
      process.reallyExit(1);
    }
  }
});

module.exports = assert;
<|endoftext|>


<commit_before>// Routing
var request = require('request');
var express = require('express');
var path = require('path');
var app = express()
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));
app.listen(process.env.PORT || 3000, function() {});

// Firebase
var firebase = require('firebase');
firebase.initializeApp({
  apiKey: process.env.FIREBASE_API_KEY,
  authDomain: process.env.FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.FIREBASE_DATABASE_URL,
  storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID
});

// Botkit
var Botkit = require('botkit');
var controller = Botkit.slackbot();
controller.configureSlackApp({
  clientId: process.env.SLACK_CLIENT_ID,
  clientSecret: process.env.SLACK_CLIENT_SECRET,
  redirectUri: 'http://localhost:3000/slack/callback',
  scopes: ['commands']
});

// Genius
var Genius = require('node-genius')
var genius = new Genius(process.env.GENIUS_ACCESS_TOKEN)

// Mixpanel
var Mixpanel = require('mixpanel');
var mixpanel = Mixpanel.init(process.env.MIXPANEL_KEY);

// Routes
app.get('/', function(req, res) {
  res.sendFile(__dirname + '/public/index.html')
})

app.get('*', function(req, res) {
  res.sendFile(__dirname + '/public/index.html')
})

app.get('/success', function (req, res) {
    res.sendFile(__dirname + '/public/success.html')
});

app.get('/fail', function (req, res) {
    res.sendFile(__dirname + '/public/fail.html')
});

app.get('/privacy', function (req, res) {
    res.sendFile(__dirname + '/public/privacy.html')
});

// Slack Authentication
app.get('/slack/callback', function(req, res) {
  var url = 'https://slack.com/api/oauth.access?';
  url += 'client_id=' + process.env.SLACK_CLIENT_ID;
  url += '&client_secret=' + process.env.SLACK_CLIENT_SECRET;
  url += '&code=' + req.query.code;

  request(url, function (error, response, body) {
    if (error || response.statusCode != 200) {
      console.log('Request Error: ' + error)
      res.sendFile(__dirname + '/public/failure.html')
    } else {
      var data = JSON.parse(body)
      if (!data.ok) {
        console.log('Slack OAuth Error: ' + data.error)
        res.sendFile(__dirname + '/public/failure.html')
      } else {
        var team = {}
        team[data.team_id] = {
          access_token: data.access_token,
          scope: data.scope,
          user_id: data.user_id,
          team_id: data.team_id,
          team_name: data.team_name
        }

        firebase.database().ref('/slack/').update(team, function(error) {
          if (error) {
            console.log('Firebase Error: ' + error)
            res.sendFile(__dirname + '/public/failure.html')
          } else {
            console.log(team[data.team_id].team_name + ' was added to Firebase')
            res.sendFile(__dirname + '/public/success.html')
          }
        });
      }
    }
  });
});

// Slack Firesong
app.post('/slack/firesong', function(req, res) {
  genius.search('fire', function (error, results) {
    var dataJSON = JSON.parse(results);
    var hits = dataJSON.response.hits;

    // Get Random Genius Hit
    var i = (hits.length < 4 ? hits.length : 3);
    var random = Math.floor(Math.random() * i);

    // Get Genius song from random song id
    var geniusId = hits[random].result.id
    genius.getSong(geniusId, function (error, song) {
      if (error) {
        console.error('Get random Genius song error: ', error);
      } else {
        var songJSON = JSON.parse(song);

        // Find the Spotify Media
        var media = songJSON.response.song.media
        for (index in media) {
          if (media[index].provider == 'spotify') {
            var spotifyURL = media[index].url
            var spotifyId = spotifyURL.substr(37)

            // Send message to Slack
            var message = {
              'response_type': 'in_channel',
              'text': spotifyURL
            }
            res.send(message)

            // Record in Mixpanel
            recordMixpanelEvent(req, spotifyId)
            break
          }
        }
      }
    });
  });
});

function recordMixpanelEvent(req, spotify_id) {
  mixpanel.track('/firesong', {
    distinct_id: req.body.response_url.substr(33),
    user_id: req.body.user_id,
    user_name: req.body.user_name,
    channel_id: req.body.channel_id,
    team_id: req.body.team_id,
    team_domain: req.body.team_domain,
    text: req.body.text,
    song: spotify_id
  });
}
<commit_msg>fix routing<commit_after>// Routing
var request = require('request');
var express = require('express');
var path = require('path');
var app = express()
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));
app.listen(process.env.PORT || 3000, function() {});

// Firebase
var firebase = require('firebase');
firebase.initializeApp({
  apiKey: process.env.FIREBASE_API_KEY,
  authDomain: process.env.FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.FIREBASE_DATABASE_URL,
  storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID
});

// Botkit
var Botkit = require('botkit');
var controller = Botkit.slackbot();
controller.configureSlackApp({
  clientId: process.env.SLACK_CLIENT_ID,
  clientSecret: process.env.SLACK_CLIENT_SECRET,
  redirectUri: 'http://localhost:3000/slack/callback',
  scopes: ['commands']
});

// Genius
var Genius = require('node-genius')
var genius = new Genius(process.env.GENIUS_ACCESS_TOKEN)

// Mixpanel
var Mixpanel = require('mixpanel');
var mixpanel = Mixpanel.init(process.env.MIXPANEL_KEY);

// Routes
app.get('/', function(req, res) {
  res.sendFile(__dirname + '/public/index.html')
})

app.get('/?ref=*', function(req, res) {
  res.sendFile(__dirname + '/public/index.html')
})

app.get('/success', function (req, res) {
    res.sendFile(__dirname + '/public/success.html')
});

app.get('/fail', function (req, res) {
    res.sendFile(__dirname + '/public/fail.html')
});

app.get('/privacy', function (req, res) {
    res.sendFile(__dirname + '/public/privacy.html')
});

// Slack Authentication
app.get('/slack/callback', function(req, res) {
  var url = 'https://slack.com/api/oauth.access?';
  url += 'client_id=' + process.env.SLACK_CLIENT_ID;
  url += '&client_secret=' + process.env.SLACK_CLIENT_SECRET;
  url += '&code=' + req.query.code;

  request(url, function (error, response, body) {
    if (error || response.statusCode != 200) {
      console.log('Request Error: ' + error)
      res.sendFile(__dirname + '/public/failure.html')
    } else {
      var data = JSON.parse(body)
      if (!data.ok) {
        console.log('Slack OAuth Error: ' + data.error)
        res.sendFile(__dirname + '/public/failure.html')
      } else {
        var team = {}
        team[data.team_id] = {
          access_token: data.access_token,
          scope: data.scope,
          user_id: data.user_id,
          team_id: data.team_id,
          team_name: data.team_name
        }

        firebase.database().ref('/slack/').update(team, function(error) {
          if (error) {
            console.log('Firebase Error: ' + error)
            res.sendFile(__dirname + '/public/failure.html')
          } else {
            console.log(team[data.team_id].team_name + ' was added to Firebase')
            res.sendFile(__dirname + '/public/success.html')
          }
        });
      }
    }
  });
});

// Slack Firesong
app.post('/slack/firesong', function(req, res) {
  genius.search(req.body.text, function (error, results) {
    var dataJSON = JSON.parse(results);
    var hits = dataJSON.response.hits;

    // Get Random Genius Hit
    var i = (hits.length < 4 ? hits.length : 3);
    var random = Math.floor(Math.random() * i);

    // Get Genius song from random song id
    var geniusId = hits[random].result.id
    genius.getSong(geniusId, function (error, song) {
      if (error) {
        console.error('Get random Genius song error: ', error);
      } else {
        var songJSON = JSON.parse(song);

        // Find the Spotify Media
        var media = songJSON.response.song.media
        for (index in media) {
          if (media[index].provider == 'spotify') {
            var spotifyURL = media[index].url
            var spotifyId = spotifyURL.substr(37)
            console.log(spotifyURL)

            // Send message to Slack
            var message = {
              'response_type': 'in_channel',
              'text': spotifyURL
            }
            res.send(message)

            // Record in Mixpanel
            recordMixpanelEvent(req, spotifyId)
            break
          }
        }
      }
    });
  });
});

genius.search('fire', function (error, results) {
    var dataJSON = JSON.parse(results);
    var hits = dataJSON.response.hits;

    // Get Random Genius Hit
    var i = (hits.length < 4 ? hits.length : 3);
    var random = Math.floor(Math.random() * i);

    // Get Genius song from random song id
    var geniusId = hits[random].result.id
    genius.getSong(geniusId, function (error, song) {
      if (error) {
        console.error('Get random Genius song error: ', error);
      } else {
        var songJSON = JSON.parse(song);

        // Find the Spotify Media
        var media = songJSON.response.song.media
        for (index in media) {
          if (media[index].provider == 'spotify') {
            var spotifyURL = media[index].url
            var spotifyId = spotifyURL.substr(37)
            console.log(spotifyURL)

            // Send message to Slack
            var message = {
              'response_type': 'in_channel',
              'text': spotifyURL
            }
            res.send(message)

            // Record in Mixpanel
            recordMixpanelEvent(req, spotifyId)
            break
          }
        }
      }
    });
  });

function recordMixpanelEvent(req, spotify_id) {
  mixpanel.track('/firesong', {
    distinct_id: req.body.response_url.substr(33),
    user_id: req.body.user_id,
    user_name: req.body.user_name,
    channel_id: req.body.channel_id,
    team_id: req.body.team_id,
    team_domain: req.body.team_domain,
    text: req.body.text,
    song: spotify_id
  });
}
<|endoftext|>


<commit_before>"use strict";

let through = require("through2");
let rollup = require("rollup");
let fs = require("fs");
let Path = require("path");
let File = require("vinyl");

/* ====== Utility functions ====== */
function mapIterable(iterable, func) {
	let ret = [];
	for (let element of iterable) {
		ret.push(func(element));
	}
	return ret;
}

function difference(a, b) {
  let difference = new Set(a);
  for (let element of b) {
    difference.delete(element);
  }

  return difference;
}

function clone(a) {
	let ret = {};
	for (let prop in a) {
		ret[prop] = a[prop];
	}
	return ret;
}

/* ====== Exported helper functions ====== */

// Reads the file at the given filepath and then returns a JSON-parsed representation of the file.
function defaultParser(filepath) {
	let data = fs.readFileSync(filepath, "utf8");
    return JSON.parse(data);
}

// Returns the "targets" properties of the passed object
function defaultIdentifier(object) {
	return Promise.resolve(object.targets);
}

// Returns the set of javascript targets for the chrome extension manifest object parameter
function chromeExtensionIdentifier(manifest) {
	let targets = new Set();

	let addScript = targets.add.bind(targets);
	// Get all targets from content scripts
	if (manifest.content_scripts) {
		for (let contentScript of manifest.content_scripts) {
			if (contentScript.js) {
				contentScript.js.map(addScript);
			}
		}
	}

	// Get all targets from background scripts
	if (manifest.background && manifest.background.scripts) {
		manifest.background.scripts.map(addScript);
	}

	// Get all targets from browser action
	if (manifest.browser_action && manifest.browser_action.js) {
		manifest.browser_action.js.map(addScript);
	}

	// Get all targets from page action
	if (manifest.page_action && manifest.page_action.js) {
		manifest.page_action.js.map(addScript);
	}

	// Get all targets from web accessible resources
	if (manifest.web_accessible_resources) {
		for (let resource of manifest.web_accessible_resources) {
			if (resource.indexOf(".js")) { addScript(resource); }
		}
	}

	return targets;
}

let _cacheMap = Symbol("cacheMap");
class FileCache {
	constructor() {
		this[_cacheMap] = new Map();
	}

	add(filepath, file) {
		this[_cacheMap].set(filepath, file);
	}

	has(filepath) {
		return this[_cacheMap].has(filepath);
	}

	remove(filepath) {
		this[_cacheMap].delete(filepath);
	}

	load(filepath) {
		if (this[_cacheMap].has(filepath)) {
			return this[_cacheMap].get(filepath);
		}
		let file = fs.readFileSync(filepath, { encoding : "utf-8" });
		this[_cacheMap].set(filepath, file);
		return file;
	}
}

let _analyzeTargets = Symbol("analyzeTargets");
let _bundleTarget = Symbol("bundleTarget");
let _bundleTargets = Symbol("bundleTargets");
let _dependencies = Symbol("dependencies");
let _generate = Symbol("generate");
let _resolveTargets = Symbol("resolveTargets");
let _targetFile = Symbol("targetFile");
let _targetIdentifier = Symbol("targetIdentifier");
let _targetParser = Symbol("targetParser");
let _targets = Symbol("targets");
/*
options.targetFile
	Specifies the path to the file containing the target definitions. If relative, uses cwd.

options.srcDir
	Specifies the path from the definition file to the source root. Defaults to the same directory as targetFile

*/
class Project {
	constructor(options) {
		this.srcDir = options.srcDir || ".";
		this.rollupOptions = options.rollup;
		this.generateOptions = options.generate;
		Object.defineProperty(this,
			"cache", { value : new FileCache() });

		if (options.targets) {
			let targets = [];
			for (let target of options.targets) {
				targets.push(Path.resolve(options.srcDir, target));
			}
			this[_targets] = targets;
		} else if (options.targetFile) {
			this[_targetFile] = Path.resolve(options.targetFile);
			this[_targetParser] = options.targetParser || defaultParser;
			this[_targetIdentifier] = options.targetIdentifier || defaultIdentifier;
		}

		this[_dependencies] = new Map();
		if (this[_targetFile]) {
			let object = this[_targetParser](this[_targetFile]);
			this[_targets] = this[_resolveTargets](object);
		}
	}

	[_resolveTargets](object) {
		let targets = [];
		for (let target of this[_targetIdentifier](object)) {
			targets.push(Path.resolve(Path.dirname(this[_targetFile]), this.srcDir, target));
		}
		return targets;
	}

	/*
	Reads targets from the target source file if it exists, and returns the added/removed targets
	*/
	[_analyzeTargets]() {
		if (this[_targetFile]) {
			let object = this[_targetParser](this[_targetFile])
			let targets = this[_resolveTargets](object);
			let add = difference(targets, this[_targets]);
			let remove = difference(this[_targets], targets);
			this[_targets] = targets;
			return {add, remove};
		} else {
			return {add : {}, remove : {}};
		}
	}

	/*
	Creates a rollup bundle for the target file parameter, returns a promise with the bundle
	*/
	[_bundleTarget](target) {
		let options = clone(this.generateOptions);
		options.entry = target;
		options.load = function ( id, opts ) {
			const source = this.cache.load(id);

			return opts.transform.reduce( function(source, transformer) {
				return transformer( source, id );
			}.bind(this), source );
		}.bind(this);

		console.log("==> Bundling: " + target);
		return rollup.rollup(options)
		.then(function(bundle) {
			this[_dependencies].set(target, bundle.modules);
			return { bundle, target };
		}.bind(this))
	}

	/*
	Creates rollup bundles for the targets passed to the function or all targets for this Project, returns a promise with all bundles.
	*/
	[_bundleTargets](targets) {
		let targetSet = targets || this[_targets];
		return Promise.all(mapIterable(targetSet, this[_bundleTarget].bind(this)));
	}

	/*
	Takes an iterable of bundles and returns an array of generated source+map target objects.
	*/
	[_generate](targetBundles) {
		let gen = [];
		for (let targetBundle of targetBundles) {
			console.log("==> Generating: " + targetBundle.target);
			let generated = targetBundle.bundle.generate();
			gen.push({
				code : generated.code,
				map : generated.map,
				target : targetBundle.target
			});
		}
		return gen;
	}

	/**
	 * Determines if the given filepath refers to the target source
	 * @param {string} filepath
	 * @return {boolean} isTargetSource
	 */
	isTargetSource(filepath) {
		return filepath === this[_targetFile];
	}

	/**
	 * Determines if the given filepath refers to a target
	 * @param {string} filepath
	 * @return {boolean} isTarget
	 */
	isTarget(filepath) {
		return this[_targets].indexOf(filepath) >= 0;
	}

	/**
	 * Determines if the given filepath is a dependency of the provided target, or a dependency of any target if no target passed.
	 * @param {string} filepath
	 * @param {string} target
	 * @return {boolean} isDependency
	 */
	isDependency(filepath, target) {
		// If specific target, return true if that target has this file as a dependency
		if (target) {
			let targetDependencies = this[_dependencies].get(target);
			if (filepath != target && targetDependencies) {
				for (let dependency of targetDependencies) {
					if (dependency.id === filepath) return true;
				}
			}
		} else {
			for (let target of this[_targets]) {
				let targetDependencies = this[_dependencies].get(target);
				if (filepath != target && targetDependencies) {
					for (let dependency of targetDependencies) {
						if (dependency.id === filepath) return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * Returns all targets that have the given file as a dependency.
	 * @param {string} filepath
	 * @return {Array} targetsForFile
	 */
	getTargetsForFile(filepath) {
		let ret = [];
		for (let target of this[_targets]) {
			let targetDependencies = this[_dependencies].get(target);
			if (targetDependencies) {
				for (let dependency of targetDependencies) {
					if (dependency.id === filepath) ret.push(target);
				}
			}
		}

		return ret;
	}

	getSourceRoot() {
		return Path.resolve(Path.dirname(this[_targetFile]), this.srcDir);
	}

	/*
	 * Builds all targets of this project and returns a promise containing an array of generated objects
	 *
	 * Generated objects are of the form:
	 * {
	 * 	{string} code - the generated code,
	 * 	{string} map - the sourcemap,
	 *	{string} target - the original filepath
	 * }
	 * @return {Promise<Array>} the array of generated objects
	 */
	build() {
		console.log("Building project");
		return this[_bundleTargets]()
			.then(this[_generate]);
	}

	/*
	 * Handles a change to the given filepath and returns a promise containing an array of generated objects.
	 *
	 * On manifest change, re-analyzes all targets and rebuilds new targets.
	 * On target change, rebuilds target.
	 * On dependency change, rebuilds all targets dependent on the file.
	 *
	 * Generated objects are of the form
	 * {
	 * 	{string} code - the generated code,
	 * 	{string} map - the sourcemap,
	 *	{string} target - the original filepath
	 * }
	 * @return {Promise<Array>} the array of generated objects
	 */
	change(filepath) {
		console.log("Handling change to: " + filepath);
		// If source, re-analyze targets and rebuild changed/added targets
		if (this.isTargetSource(filepath)) {
			let targetDelta = this[_analyzeTargets]();
			return this[_bundleTargets](targetDelta.add)
				.then(this[_generate].bind(this));
		} else {
			let buildTargets = [];
			buildTargets = this.getTargetsForFile(filepath);

			return this[_bundleTargets](buildTargets)
				.then(this[_generate].bind(this));
		}
	}
}

let projects = new Map();

let ProjectStream = {
	/*
	*/
	build : function(options) {
		if (!projects.has(Path.resolve(options.targetFile))) {
			projects.set(Path.resolve(options.targetFile), new Project(options));
		}
		let project = projects.get(Path.resolve(options.targetFile));

		return through.obj(function(file, enc, callback) {
			project.cache.add(file.path, file.contents.toString("utf-8"));
			callback();
		}, function(callback) {
			project.build()
			.then(function(generatedFiles) {
				for (let generated of generatedFiles) {
					this.push(new File({
						cwd : process.cwd,
						base : project.getSourceRoot(),
						path : generated.target,
						contents : new Buffer(generated.code)
					}));

					if (options.map) {
						this.push(new File({
							cwd : process.cwd,
							base : project.getSourceRoot(),
							path : generated.target + ".map",
							contents : new Buffer(generated.map)
						}));
					}
				}
				console.log("Project build completed");
				callback();
			}.bind(this))
			.catch(function(err) {
				callback(err);
			})
		});
	},

	change : function(options) {
		if (!projects.has(Path.resolve(options.targetFile))) {
			projects.set(Path.resolve(options.targetFile), new Project(options));
		}
		let project = projects.get(Path.resolve(options.targetFile));

		return through.obj(function(file, enc, callback) {
			if (file.event) {
				if (file.event === "unlink") project.cache.remove(file.path);
				else project.cache.add(file.path, file.contents.toString("utf-8"));
			}
			project.change(file.path)
			.then(function(generatedFiles) {
				for (let generated of generatedFiles) {
					this.push(new File({
						cwd : process.cwd,
						base : project.getSourceRoot(),
						path : generated.target,
						contents : new Buffer(generated.code)
					}))

					if (options.map) {
						this.push(new File({
							cwd : process.cwd,
							base : project.getSourceRoot(),
							path : generated.target + ".map",
							contents : new Buffer(generated.map)
						}));
					}
				}
				callback();
			}.bind(this))
			.catch(function(err) {
				callback(err);
			})
		});
	}
}

module.exports = {Project, build : ProjectStream.build, change : ProjectStream.change, defaultParser, defaultIdentifier, chromeExtensionIdentifier};<commit_msg>Added some console output on change<commit_after>"use strict";

let through = require("through2");
let rollup = require("rollup");
let fs = require("fs");
let Path = require("path");
let File = require("vinyl");

/* ====== Utility functions ====== */
function mapIterable(iterable, func) {
	let ret = [];
	for (let element of iterable) {
		ret.push(func(element));
	}
	return ret;
}

function difference(a, b) {
  let difference = new Set(a);
  for (let element of b) {
    difference.delete(element);
  }

  return difference;
}

function clone(a) {
	let ret = {};
	for (let prop in a) {
		ret[prop] = a[prop];
	}
	return ret;
}

/* ====== Exported helper functions ====== */

// Reads the file at the given filepath and then returns a JSON-parsed representation of the file.
function defaultParser(filepath) {
	let data = fs.readFileSync(filepath, "utf8");
    return JSON.parse(data);
}

// Returns the "targets" properties of the passed object
function defaultIdentifier(object) {
	return Promise.resolve(object.targets);
}

// Returns the set of javascript targets for the chrome extension manifest object parameter
function chromeExtensionIdentifier(manifest) {
	let targets = new Set();

	let addScript = targets.add.bind(targets);
	// Get all targets from content scripts
	if (manifest.content_scripts) {
		for (let contentScript of manifest.content_scripts) {
			if (contentScript.js) {
				contentScript.js.map(addScript);
			}
		}
	}

	// Get all targets from background scripts
	if (manifest.background && manifest.background.scripts) {
		manifest.background.scripts.map(addScript);
	}

	// Get all targets from browser action
	if (manifest.browser_action && manifest.browser_action.js) {
		manifest.browser_action.js.map(addScript);
	}

	// Get all targets from page action
	if (manifest.page_action && manifest.page_action.js) {
		manifest.page_action.js.map(addScript);
	}

	// Get all targets from web accessible resources
	if (manifest.web_accessible_resources) {
		for (let resource of manifest.web_accessible_resources) {
			if (resource.indexOf(".js")) { addScript(resource); }
		}
	}

	return targets;
}

let _cacheMap = Symbol("cacheMap");
class FileCache {
	constructor() {
		this[_cacheMap] = new Map();
	}

	add(filepath, file) {
		this[_cacheMap].set(filepath, file);
	}

	has(filepath) {
		return this[_cacheMap].has(filepath);
	}

	remove(filepath) {
		this[_cacheMap].delete(filepath);
	}

	load(filepath) {
		if (this[_cacheMap].has(filepath)) {
			return this[_cacheMap].get(filepath);
		}
		let file = fs.readFileSync(filepath, { encoding : "utf-8" });
		this[_cacheMap].set(filepath, file);
		return file;
	}
}

let _analyzeTargets = Symbol("analyzeTargets");
let _bundleTarget = Symbol("bundleTarget");
let _bundleTargets = Symbol("bundleTargets");
let _dependencies = Symbol("dependencies");
let _generate = Symbol("generate");
let _resolveTargets = Symbol("resolveTargets");
let _targetFile = Symbol("targetFile");
let _targetIdentifier = Symbol("targetIdentifier");
let _targetParser = Symbol("targetParser");
let _targets = Symbol("targets");
/*
options.targetFile
	Specifies the path to the file containing the target definitions. If relative, uses cwd.

options.srcDir
	Specifies the path from the definition file to the source root. Defaults to the same directory as targetFile

*/
class Project {
	constructor(options) {
		this.srcDir = options.srcDir || ".";
		this.rollupOptions = options.rollup;
		this.generateOptions = options.generate;
		Object.defineProperty(this,
			"cache", { value : new FileCache() });

		if (options.targets) {
			let targets = [];
			for (let target of options.targets) {
				targets.push(Path.resolve(options.srcDir, target));
			}
			this[_targets] = targets;
		} else if (options.targetFile) {
			this[_targetFile] = Path.resolve(options.targetFile);
			this[_targetParser] = options.targetParser || defaultParser;
			this[_targetIdentifier] = options.targetIdentifier || defaultIdentifier;
		}

		this[_dependencies] = new Map();
		if (this[_targetFile]) {
			let object = this[_targetParser](this[_targetFile]);
			this[_targets] = this[_resolveTargets](object);
		}
	}

	[_resolveTargets](object) {
		let targets = [];
		for (let target of this[_targetIdentifier](object)) {
			targets.push(Path.resolve(Path.dirname(this[_targetFile]), this.srcDir, target));
		}
		return targets;
	}

	/*
	Reads targets from the target source file if it exists, and returns the added/removed targets
	*/
	[_analyzeTargets]() {
		if (this[_targetFile]) {
			let object = this[_targetParser](this[_targetFile])
			let targets = this[_resolveTargets](object);
			let add = difference(targets, this[_targets]);
			let remove = difference(this[_targets], targets);
			this[_targets] = targets;
			return {add, remove};
		} else {
			return {add : {}, remove : {}};
		}
	}

	/*
	Creates a rollup bundle for the target file parameter, returns a promise with the bundle
	*/
	[_bundleTarget](target) {
		let options = clone(this.generateOptions);
		options.entry = target;
		options.load = function ( id, opts ) {
			const source = this.cache.load(id);

			return opts.transform.reduce( function(source, transformer) {
				return transformer( source, id );
			}.bind(this), source );
		}.bind(this);

		console.log("==> Bundling: " + target);
		return rollup.rollup(options)
		.then(function(bundle) {
			this[_dependencies].set(target, bundle.modules);
			return { bundle, target };
		}.bind(this))
	}

	/*
	Creates rollup bundles for the targets passed to the function or all targets for this Project, returns a promise with all bundles.
	*/
	[_bundleTargets](targets) {
		let targetSet = targets || this[_targets];
		return Promise.all(mapIterable(targetSet, this[_bundleTarget].bind(this)));
	}

	/*
	Takes an iterable of bundles and returns an array of generated source+map target objects.
	*/
	[_generate](targetBundles) {
		let gen = [];
		for (let targetBundle of targetBundles) {
			console.log("==> Generating: " + targetBundle.target);
			let generated = targetBundle.bundle.generate();
			gen.push({
				code : generated.code,
				map : generated.map,
				target : targetBundle.target
			});
		}
		return gen;
	}

	/**
	 * Determines if the given filepath refers to the target source
	 * @param {string} filepath
	 * @return {boolean} isTargetSource
	 */
	isTargetSource(filepath) {
		return filepath === this[_targetFile];
	}

	/**
	 * Determines if the given filepath refers to a target
	 * @param {string} filepath
	 * @return {boolean} isTarget
	 */
	isTarget(filepath) {
		return this[_targets].indexOf(filepath) >= 0;
	}

	/**
	 * Determines if the given filepath is a dependency of the provided target, or a dependency of any target if no target passed.
	 * @param {string} filepath
	 * @param {string} target
	 * @return {boolean} isDependency
	 */
	isDependency(filepath, target) {
		// If specific target, return true if that target has this file as a dependency
		if (target) {
			let targetDependencies = this[_dependencies].get(target);
			if (filepath != target && targetDependencies) {
				for (let dependency of targetDependencies) {
					if (dependency.id === filepath) return true;
				}
			}
		} else {
			for (let target of this[_targets]) {
				let targetDependencies = this[_dependencies].get(target);
				if (filepath != target && targetDependencies) {
					for (let dependency of targetDependencies) {
						if (dependency.id === filepath) return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * Returns all targets that have the given file as a dependency.
	 * @param {string} filepath
	 * @return {Array} targetsForFile
	 */
	getTargetsForFile(filepath) {
		let ret = [];
		for (let target of this[_targets]) {
			let targetDependencies = this[_dependencies].get(target);
			if (targetDependencies) {
				for (let dependency of targetDependencies) {
					if (dependency.id === filepath) ret.push(target);
				}
			}
		}

		return ret;
	}

	getSourceRoot() {
		return Path.resolve(Path.dirname(this[_targetFile]), this.srcDir);
	}

	/*
	 * Builds all targets of this project and returns a promise containing an array of generated objects
	 *
	 * Generated objects are of the form:
	 * {
	 * 	{string} code - the generated code,
	 * 	{string} map - the sourcemap,
	 *	{string} target - the original filepath
	 * }
	 * @return {Promise<Array>} the array of generated objects
	 */
	build() {
		console.log("Building project");
		return this[_bundleTargets]()
			.then(this[_generate]);
	}

	/*
	 * Handles a change to the given filepath and returns a promise containing an array of generated objects.
	 *
	 * On manifest change, re-analyzes all targets and rebuilds new targets.
	 * On target change, rebuilds target.
	 * On dependency change, rebuilds all targets dependent on the file.
	 *
	 * Generated objects are of the form
	 * {
	 * 	{string} code - the generated code,
	 * 	{string} map - the sourcemap,
	 *	{string} target - the original filepath
	 * }
	 * @return {Promise<Array>} the array of generated objects
	 */
	change(filepath) {
		console.log("Handling change to: " + filepath);
		// If source, re-analyze targets and rebuild changed/added targets
		if (this.isTargetSource(filepath)) {
			let targetDelta = this[_analyzeTargets]();
			return this[_bundleTargets](targetDelta.add)
				.then(this[_generate].bind(this));
		} else {
			let buildTargets = [];
			buildTargets = this.getTargetsForFile(filepath);

			return this[_bundleTargets](buildTargets)
				.then(this[_generate].bind(this));
		}
	}
}

let projects = new Map();

let ProjectStream = {
	/*
	*/
	build : function(options) {
		if (!projects.has(Path.resolve(options.targetFile))) {
			projects.set(Path.resolve(options.targetFile), new Project(options));
		}
		let project = projects.get(Path.resolve(options.targetFile));

		return through.obj(function(file, enc, callback) {
			project.cache.add(file.path, file.contents.toString("utf-8"));
			callback();
		}, function(callback) {
			project.build()
			.then(function(generatedFiles) {
				for (let generated of generatedFiles) {
					this.push(new File({
						cwd : process.cwd,
						base : project.getSourceRoot(),
						path : generated.target,
						contents : new Buffer(generated.code)
					}));

					if (options.map) {
						this.push(new File({
							cwd : process.cwd,
							base : project.getSourceRoot(),
							path : generated.target + ".map",
							contents : new Buffer(generated.map)
						}));
					}
				}
				console.log("Project build completed");
				callback();
			}.bind(this))
			.catch(function(err) {
				callback(err);
			})
		});
	},

	change : function(options) {
		if (!projects.has(Path.resolve(options.targetFile))) {
			projects.set(Path.resolve(options.targetFile), new Project(options));
		}
		let project = projects.get(Path.resolve(options.targetFile));

		return through.obj(function(file, enc, callback) {
			if (file.event) {
				if (file.event === "unlink") project.cache.remove(file.path);
				else project.cache.add(file.path, file.contents.toString("utf-8"));
			}
			project.change(file.path)
			.then(function(generatedFiles) {
				for (let generated of generatedFiles) {
					this.push(new File({
						cwd : process.cwd,
						base : project.getSourceRoot(),
						path : generated.target,
						contents : new Buffer(generated.code)
					}))

					if (options.map) {
						this.push(new File({
							cwd : process.cwd,
							base : project.getSourceRoot(),
							path : generated.target + ".map",
							contents : new Buffer(generated.map)
						}));
					}
				}
				console.log("Delta build completed");
				callback();
			}.bind(this))
			.catch(function(err) {
				callback(err);
			})
		});
	}
}

module.exports = {Project, build : ProjectStream.build, change : ProjectStream.change, defaultParser, defaultIdentifier, chromeExtensionIdentifier};<|endoftext|>


<commit_before>var chalk = require('chalk'),
  fs = require('fs'),
  utils = require('./time-track-utils');

function TaskManager() {
  this.tasks = [];
  this.currentTask = null;
  this.file = createFileName(toDay(new Date()));

  setupCurrentDay(this, this.file);
}

TaskManager.prototype.startNewTask = function(taskName) {
  var newTask = {
    id: this.tasks.length + 1,
    name: taskName,
    times: [{
      start: new Date()
    }]
  };

  this.tasks.push(newTask);

  if (this.currentTask) {
    endLastSlot(this.currentTask);
    newTask.prev = this.currentTask.id;
  }

  this.currentTask = newTask;

  utils.printTask(this.currentTask);

  persistTasks(this.file, this.tasks, this.currentTask);

  return true;
};

TaskManager.prototype.stopCurrentTask = function(comment) {
  if (!this.currentTask) {
    return true;
  }

  if (this.currentTask.paused) {
    this.currentTask.paused = false;
  } else {
    endLastSlot(this.currentTask);
  }

  //add the comment to the taskname if specified
  if (comment) {
    this.currentTask.name += ' ' + comment;
  }

  if (this.currentTask.prev) {
    //If we have a previous task we have to add another time slot
    var prevTask = this.tasks[this.currentTask.prev - 1];
    prevTask.times.push({
      start: new Date()
    });

    this.currentTask = prevTask;

    utils.printTask(this.currentTask);
  } else {
    this.currentTask = null;
    console.log(chalk.red('No task is running!'));
  }

  persistTasks(this.file, this.tasks, this.currentTask);

  return true;
};

/**
 * End the current tasks last timeslot
 */
TaskManager.prototype.pause = function() {
  if (this.currentTask) {
    endLastSlot(this.currentTask);
    this.currentTask.paused = true;

    utils.printTask(this.currentTask);

    persistTasks(this.file, this.tasks, this.currentTask);
  }

  return true;
};

/**
 * End the current tasks last timeslot
 */
TaskManager.prototype.resume = function() {
  if (this.currentTask) {
    this.currentTask.times.push({
      start: new Date()
    });

    this.currentTask.paused = false;

    utils.printTask(this.currentTask);

    persistTasks(this.file, this.tasks, this.currentTask);
  }

  return true;
};

TaskManager.prototype.stopAllTasks = function() {
  while (this.currentTask) {
    this.stopCurrentTask();
  }

  persistTasks(this.file, this.tasks, this.currentTask);

  return false;
};

TaskManager.prototype.print = function(day) {
  //Simply export the tasks to the command line
  return this.exportTasks(day, 'commandline');
};

TaskManager.prototype.exportTasks = function(day, exporterName) {
  if (!day) {
    console.log(chalk.red('please specify a day (yyyy-mm-dd) to use'));
    return true;
  }

  var data = parseFile(createFileName(day));

  if (!data) {
    console.log(chalk.red('Data for day ' + day + ' not found!'));

    return true;
  }

  try {
    var fullExporterName = 'time-track-' + exporterName + '-exporter';

    var exporter = require(fullExporterName);
    exporter(data.tasks);
  } catch (exception) {
    console.log(chalk.red(exception));
    console.log(chalk.red('Maybe you have not installed the required exporter?'));
    console.log(chalk.red('Run "npm install -g time-track-' + exporterName + '-exporter" to install it'));
    console.log(chalk.red('If the module is already installed, check if the NODE_PATH environment variable is set.'));
  }

  return true;
};

function persistTasks(file, tasks, currentTask) {
  var toSave = {
    tasks: tasks
  };

  if (currentTask) {
    toSave.currentId = currentTask.id;
  }

  fs.writeFile(file, JSON.stringify(toSave), function(err) {
    if (err) console.log(chalk.red(err));
  });
}

function endLastSlot(task) {
  //Set the end date for the last time slot in the task
  var lastTimeSlot = task.times[task.times.length - 1];
  lastTimeSlot.end = new Date();
}

function toDay(date) {
  return date.getFullYear() + '-' + pad((date.getMonth() + 1)) + '-' + pad(date.getDate());
}

function createFileName(day) {
  var dir = process.cwd();

  return dir + '/' + day + '.json';
}

function parseFile(file) {
  if (fs.existsSync(file)) {
    var content = fs.readFileSync(file, {
      encoding: 'utf8'
    });

    return JSON.parse(content, function(key, v) {
      if (key === 'start' || key === 'end') {
        return new Date(v);
      }

      return v;
    });
  }
}

function setupCurrentDay(taskManager, file) {
  var data = parseFile(file);

  if (!data) {
    return;
  }

  taskManager.tasks = data.tasks;

  if (data.currentId) {
    taskManager.currentTask = taskManager.tasks[data.currentId - 1];

    printTaskName(taskManager.currentTask);
  }
}

function pad(number) {
  if (number < 10) {
    return '0' + number;
  }
  return number;
}

module.exports = TaskManager;
<commit_msg>if a not paused task is resumed nothing will happen. fixes #6<commit_after>var chalk = require('chalk'),
  fs = require('fs'),
  utils = require('./time-track-utils');

function TaskManager() {
  this.tasks = [];
  this.currentTask = null;
  this.file = createFileName(toDay(new Date()));

  setupCurrentDay(this, this.file);
}

TaskManager.prototype.startNewTask = function(taskName) {
  var newTask = {
    id: this.tasks.length + 1,
    name: taskName,
    times: [{
      start: new Date()
    }]
  };

  this.tasks.push(newTask);

  if (this.currentTask) {
    endLastSlot(this.currentTask);
    newTask.prev = this.currentTask.id;
  }

  this.currentTask = newTask;

  utils.printTask(this.currentTask);

  persistTasks(this.file, this.tasks, this.currentTask);

  return true;
};

TaskManager.prototype.stopCurrentTask = function(comment) {
  if (!this.currentTask) {
    return true;
  }

  if (this.currentTask.paused) {
    this.currentTask.paused = false;
  } else {
    endLastSlot(this.currentTask);
  }

  //add the comment to the taskname if specified
  if (comment) {
    this.currentTask.name += ' ' + comment;
  }

  if (this.currentTask.prev) {
    //If we have a previous task we have to add another time slot
    var prevTask = this.tasks[this.currentTask.prev - 1];
    prevTask.times.push({
      start: new Date()
    });

    this.currentTask = prevTask;

    utils.printTask(this.currentTask);
  } else {
    this.currentTask = null;
    console.log(chalk.red('No task is running!'));
  }

  persistTasks(this.file, this.tasks, this.currentTask);

  return true;
};

/**
 * End the current tasks last timeslot
 */
TaskManager.prototype.pause = function() {
  if (this.currentTask) {
    endLastSlot(this.currentTask);
    this.currentTask.paused = true;

    utils.printTask(this.currentTask);

    persistTasks(this.file, this.tasks, this.currentTask);
  }

  return true;
};

/**
 * End the current tasks last timeslot
 */
TaskManager.prototype.resume = function() {
  if (this.currentTask && this.currentTask.paused) {
    this.currentTask.times.push({
      start: new Date()
    });

    this.currentTask.paused = false;

    persistTasks(this.file, this.tasks, this.currentTask);
  }

  utils.printTask(this.currentTask);

  return true;
};

TaskManager.prototype.stopAllTasks = function() {
  while (this.currentTask) {
    this.stopCurrentTask();
  }

  persistTasks(this.file, this.tasks, this.currentTask);

  return false;
};

TaskManager.prototype.print = function(day) {
  //Simply export the tasks to the command line
  return this.exportTasks(day, 'commandline');
};

TaskManager.prototype.exportTasks = function(day, exporterName) {
  if (!day) {
    console.log(chalk.red('please specify a day (yyyy-mm-dd) to use'));
    return true;
  }

  var data = parseFile(createFileName(day));

  if (!data) {
    console.log(chalk.red('Data for day ' + day + ' not found!'));

    return true;
  }

  try {
    var fullExporterName = 'time-track-' + exporterName + '-exporter';

    var exporter = require(fullExporterName);
    exporter(data.tasks);
  } catch (exception) {
    console.log(chalk.red(exception));
    console.log(chalk.red('Maybe you have not installed the required exporter?'));
    console.log(chalk.red('Run "npm install -g time-track-' + exporterName + '-exporter" to install it'));
    console.log(chalk.red('If the module is already installed, check if the NODE_PATH environment variable is set.'));
  }

  return true;
};

function persistTasks(file, tasks, currentTask) {
  var toSave = {
    tasks: tasks
  };

  if (currentTask) {
    toSave.currentId = currentTask.id;
  }

  fs.writeFile(file, JSON.stringify(toSave), function(err) {
    if (err) console.log(chalk.red(err));
  });
}

function endLastSlot(task) {
  //Set the end date for the last time slot in the task
  var lastTimeSlot = task.times[task.times.length - 1];
  lastTimeSlot.end = new Date();
}

function toDay(date) {
  return date.getFullYear() + '-' + pad((date.getMonth() + 1)) + '-' + pad(date.getDate());
}

function createFileName(day) {
  var dir = process.cwd();

  return dir + '/' + day + '.json';
}

function parseFile(file) {
  if (fs.existsSync(file)) {
    var content = fs.readFileSync(file, {
      encoding: 'utf8'
    });

    return JSON.parse(content, function(key, v) {
      if (key === 'start' || key === 'end') {
        return new Date(v);
      }

      return v;
    });
  }
}

function setupCurrentDay(taskManager, file) {
  var data = parseFile(file);

  if (!data) {
    return;
  }

  taskManager.tasks = data.tasks;

  if (data.currentId) {
    taskManager.currentTask = taskManager.tasks[data.currentId - 1];

    printTaskName(taskManager.currentTask);
  }
}

function pad(number) {
  if (number < 10) {
    return '0' + number;
  }
  return number;
}

module.exports = TaskManager;
<|endoftext|>


<commit_before>var exec = require("child_process").exec;
var Accessory, Service, Characteristic, UUIDGen;

module.exports = function(homebridge) {
  Accessory = homebridge.platformAccessory;
  Service = homebridge.hap.Service;
  Characteristic = homebridge.hap.Characteristic;
  UUIDGen = homebridge.hap.uuid;

  homebridge.registerPlatform("homebridge-cmdswitch2", "cmdSwitch2", cmdSwitchPlatform, true);
}

function cmdSwitchPlatform(log, config, api) {
  var self = this;

  self.log = log;
  self.config = config || {"platform": "cmdSwitch2"};
  self.switches = self.config.switches || [];

  self.accessories = {};

  if (api) {
    self.api = api;

    self.api.on('didFinishLaunching', self.didFinishLaunching.bind(this));
  }
}

// Method to restore accessories from cache
cmdSwitchPlatform.prototype.configureAccessory = function(accessory) {
  var self = this;

  accessory.reachable = true

  accessory = self.setService(accessory);

  var accessoryName = accessory.context.name;
  self.accessories[accessoryName] = accessory;
}

// Method to setup accesories from config.json
cmdSwitchPlatform.prototype.didFinishLaunching = function() {
  var self = this;

  for (var i in self.switches) {
    var data = self.switches[i];
    self.addAccessory(data);
  }
}

// Method to add and update HomeKit accessories
cmdSwitchPlatform.prototype.addAccessory = function(data) {
  var self = this;

  if (!self.accessories[data.name]) {
    var uuid = UUIDGen.generate(data.name);

    // Setup accessory as SWITCH (8) category.
    var newAccessory = new Accessory(data.name, uuid, 8);

    // Store and initialize variables into context
    newAccessory.context.name = data.name;
    newAccessory.context.on_cmd = data.on_cmd;
    newAccessory.context.off_cmd = data.off_cmd;
    newAccessory.context.state_cmd = data.state_cmd;
    newAccessory.context.state = false;

    // Setup HomeKit switch service
    newAccessory.addService(Service.Switch, data.name);

    // Setup HomeKit accessory information
    newAccessory = self.setAccessoryInfo(newAccessory, data);

    // Setup listeners for different switch events
    newAccessory = self.setService(newAccessory);

    // Retrieve initial state
    newAccessory = self.getInitState(newAccessory);

    // Register accessory in HomeKit
    self.api.registerPlatformAccessories("homebridge-cmdswitch2", "cmdSwitch2", [newAccessory]);
  } else {
    var newAccessory = self.accessories[data.name];

    // Update variables in context
    newAccessory.context.on_cmd = data.on_cmd;
    newAccessory.context.off_cmd = data.off_cmd;
    newAccessory.context.state_cmd = data.state_cmd;

    // Update HomeKit accessory information
    newAccessory = self.setAccessoryInfo(newAccessory, data);

    // Update initial state
    newAccessory = self.getInitState(newAccessory);

    // Update accessory in HomeKit
    self.api.updatePlatformAccessories([newAccessory]);
  }

  // Store accessory in cache
  self.accessories[data.name] = newAccessory;
}

// Method to remove accessories from HomeKit
cmdSwitchPlatform.prototype.removeAccessory = function(accessory) {
  if (accessory) {
    var name = accessory.context.name;
    this.api.unregisterPlatformAccessories("homebridge-cmdswitch2", "cmdSwitch2", [accessory]);
    delete this.accessories[name];
  }
}

// Method to setup HomeKit accessory information
cmdSwitchPlatform.prototype.setAccessoryInfo = function(accessory, data) {
  var info = accessory.getService(Service.AccessoryInformation);

  if (data.manufacturer) {
    accessory.context.manufacturer = data.manufacturer;
    info.setCharacteristic(Characteristic.Manufacturer, data.manufacturer.toString());
  }

  if (data.model) {
    accessory.context.model = data.model;
    info.setCharacteristic(Characteristic.Model, data.model.toString());
  }

  if (data.serial) {
    accessory.context.serial = data.serial;
    info.setCharacteristic(Characteristic.SerialNumber, data.serial.toString());
  }

  return accessory;
}

// Method to setup listeners for different events
cmdSwitchPlatform.prototype.setService = function(accessory) {
  var self = this;

  accessory
    .getService(Service.Switch)
    .getCharacteristic(Characteristic.On)
    .on('get', self.getPowerState.bind(this, accessory.context))
    .on('set', self.setPowerState.bind(this, accessory.context));

  accessory.on('identify', self.identify.bind(this, accessory.context));

  return accessory;
}

// Method to setup listeners for different events
cmdSwitchPlatform.prototype.getInitState = function(accessory) {

  accessory
    .getService(Service.Switch)
    .getCharacteristic(Characteristic.On)
    .getValue();

  return accessory;
}

// Method to determine current state
cmdSwitchPlatform.prototype.getPowerState = function(data, callback) {
  var self = this;
  var name = "[" + data.name + "] ";

  // Execute command to detect state
  if (data.state_cmd) {
    exec(data.state_cmd, function(error, stdout, stderr) {
      data.state = stdout ? true : false;
      self.log(name + "Current state: " + (data.state ? "On." : "Off."));
      callback(null, data.state);
    });
  } else {
    self.log(name + "Current state: " + (data.state ? "On." : "Off."));
    callback(null, data.state);
  }
}

// Method to set state
cmdSwitchPlatform.prototype.setPowerState = function(data, state, callback) {
  var self = this;
  var name = "[" + data.name + "] ";

  var cmd = state ? data.on_cmd : data.off_cmd;
  var tout = null;

  // Execute command to set state
  if (cmd) {
    exec(cmd, function(error, stdout, stderr) {
      // Error detection
      if (error && (state != data.state)) {
        self.log(name + "Failed to turn " + (state ? "on!" : "off!"));
      } else {
        self.log(name + "Turned " + (state ? "on." : "off."));
        data.state = state;
        error = null;
      }

      if (tout) {
        clearTimeout(tout);
        callback(error);
      }
    });

    // Allow 2s to set state but otherwise assumes success
    tout = setTimeout(function() {
      tout = null;
      self.log(name + "Turning " + (state ? "on" : "off") + " took too long, assuming success." );
      callback();
    }, 2000);
  } else {
    self.log(name + "Turned " + (state ? "on" : "off"));
    data.state = state;
    callback();
  }
}

// Method to handle identify request
cmdSwitchPlatform.prototype.identify = function(data, paired, callback) {
  var self = this;
  var name = "[" + data.name + "] ";

  self.log(name + "Identify requested!");
  callback();
}

// Method to handle plugin configuration in HomeKit app
cmdSwitchPlatform.prototype.configurationRequestHandler = function(context, request, callback) {
  if (request && request.type === "Terminate") {
    return;
  }

  // Instruction
  if (!context.step) {
    var instructionResp = {
      "type": "Interface",
      "interface": "instruction",
      "title": "Before You Start...",
      "detail": "Please make sure homebridge is running with elevated privileges.",
      "showNextButton": true
    }

    context.step = 1;
    callback(instructionResp);
  } else {
    switch (context.step) {
      case 1:
        // Operation choices
        var respDict = {
          "type": "Interface",
          "interface": "list",
          "title": "What do you want to do?",
          "items": [
            "Add New Switch",
            "Modify Existing Switch",
            "Remove Existing Switch"
          ]
        }

        context.step = 2;
        callback(respDict);
        break;
      case 2:
        var selection = request.response.selections[0];
        if (selection === 0) {
          // Info for new accessory
          var respDict = {
            "type": "Interface",
            "interface": "input",
            "title": "New Switch",
            "items": [{
              "id": "name",
              "title": "Name (Required)",
              "placeholder": "HTPC"
            }, {
              "id": "on_cmd",
              "title": "CMD to Turn On",
              "placeholder": "wakeonlan XX:XX:XX:XX:XX:XX"
            }, {
              "id": "off_cmd",
              "title": "CMD to Turn Off",
              "placeholder": "net rpc shutdown -I XXX.XXX.XXX.XXX -U user%password"
            }, {
              "id": "state_cmd",
              "title": "CMD to Check ON State",
              "placeholder": "ping -c 2 -W 1 XXX.XXX.XXX.XXX | grep -i '2 received'"
            }, {
              "id": "manufacturer",
              "title": "Manufacturer",
              "placeholder": "Default-Manufacturer"
            }, {
              "id": "model",
              "title": "Model",
              "placeholder": "Default-Model"
            }, {
              "id": "serial",
              "title": "Serial Number",
              "placeholder": "Default-SerialNumber"
            }]
          };

          context.step = 3;
          callback(respDict);
        } else {
          var self = this;
          var switches = Object.keys(self.accessories).map(function(k) {return self.accessories[k]});
          var names = switches.map(function(k) {return k.displayName});

          if (names.length > 0) {
            // Select existing accessory for modification or removal
            if (selection === 1) {
              var title = "Witch switch do you want to modify?";
              context.modify = 1;
            } else {
              var title = "Witch switch do you want to remove?";
              context.modify = 0;
            }
            var respDict = {
              "type": "Interface",
              "interface": "list",
              "title": title,
              "items": names
            };

            context.switches = switches;
            context.step = 4;
          } else {
            var respDict = {
              "type": "Interface",
              "interface": "instruction",
              "title": "Unavailable",
              "detail": "No switch is configured.",
              "showNextButton": true
            };

            context.step = 1;
          }
          callback(respDict);
        }
        break;
      case 3:
        var userInputs = request.response.inputs;
        var newSwitch = {};

        // Setup input for addAccessory
        if (context.selected) {
          var accessory = this.accessories[context.selected];
          newSwitch.name = context.selected;
          newSwitch.on_cmd = userInputs.on_cmd || accessory.context.on_cmd;
          newSwitch.off_cmd = userInputs.off_cmd || accessory.context.off_cmd;
          newSwitch.state_cmd = userInputs.state_cmd || accessory.context.state_cmd;
          newSwitch.manufacturer = userInputs.manufacturer;
          newSwitch.model = userInputs.model;
          newSwitch.serial = userInputs.serial;
        } else {
          newSwitch.name = userInputs.name;
          newSwitch.on_cmd = userInputs.on_cmd;
          newSwitch.off_cmd = userInputs.off_cmd;
          newSwitch.state_cmd = userInputs.state_cmd;
          newSwitch.manufacturer = userInputs.manufacturer;
          newSwitch.model = userInputs.model;
          newSwitch.serial = userInputs.serial;
        }

        if (newSwitch.name) {
          // Register or update accessory in HomeKit
          this.addAccessory(newSwitch);
          var respDict = {
            "type": "Interface",
            "interface": "instruction",
            "title": "Success",
            "detail": "The new switch is now updated.",
            "showNextButton": true
          };

          context.step = 5;
        } else {
          // Error if required info is missing
          var respDict = {
            "type": "Interface",
            "interface": "instruction",
            "title": "Error",
            "detail": "Name of the switch is missing.",
            "showNextButton": true
          };

          context.step = 1;
        }
        callback(respDict);
        break;
      case 4:
        var selection = request.response.selections[0];
        var accessory = context.switches[selection];
        if (context.modify) {
          // Modify info of selected accessory
          var respDict = {
            "type": "Interface",
            "interface": "input",
            "title": accessory.displayName.toString(),
            "items": [{
              "id": "on_cmd",
              "title": "CMD to Turn On",
              "placeholder": "Leave blank if unchanged"
            }, {
              "id": "off_cmd",
              "title": "CMD to Turn Off",
              "placeholder": "Leave blank if unchanged"
            }, {
              "id": "state_cmd",
              "title": "CMD to Check ON State",
              "placeholder": "Leave blank if unchanged"
            }, {
              "id": "manufacturer",
              "title": "Manufacturer",
              "placeholder": "Leave blank if unchanged"
            }, {
              "id": "model",
              "title": "Model",
              "placeholder": "Leave blank if unchanged"
            }, {
              "id": "serial",
              "title": "Serial",
              "placeholder": "Leave blank if unchanged"
            }]
          };

          context.selected = accessory.context.name;
          context.step = 3;
        } else {
          // Remove selected accessory from HomeKit
          this.removeAccessory(accessory);
          var respDict = {
            "type": "Interface",
            "interface": "instruction",
            "title": "Success",
            "detail": "The switch is now removed.",
            "showNextButton": true
          };

          context.step = 5;
        }
        callback(respDict);
        break;
      case 5:
        // Update config.json accordingly
        var self = this;
        delete context.step;
        var newConfig = self.config;
        var newSwitches = Object.keys(self.accessories).map(function(k) {
          var accessory = self.accessories[k];
          var data = {
            'name': accessory.context.name,
            'on_cmd': accessory.context.on_cmd,
            'off_cmd': accessory.context.off_cmd,
            'state_cmd': accessory.context.state_cmd,
            'manufacturer': accessory.context.manufacturer,
            'model': accessory.context.model,
            'serial': accessory.context.serial
          };
          return data;
        });

        newConfig.switches = newSwitches;
        callback(null, "platform", true, newConfig);
        break;
    }
  }
}
<commit_msg>Minor clean up<commit_after>var exec = require("child_process").exec;
var Accessory, Service, Characteristic, UUIDGen;

module.exports = function(homebridge) {
  Accessory = homebridge.platformAccessory;
  Service = homebridge.hap.Service;
  Characteristic = homebridge.hap.Characteristic;
  UUIDGen = homebridge.hap.uuid;

  homebridge.registerPlatform("homebridge-cmdswitch2", "cmdSwitch2", cmdSwitchPlatform, true);
}

function cmdSwitchPlatform(log, config, api) {
  var self = this;

  self.log = log;
  self.config = config || {"platform": "cmdSwitch2"};
  self.switches = self.config.switches || [];

  self.accessories = {};

  if (api) {
    self.api = api;

    self.api.on('didFinishLaunching', self.didFinishLaunching.bind(this));
  }
}

// Method to restore accessories from cache
cmdSwitchPlatform.prototype.configureAccessory = function(accessory) {
  var self = this;

  accessory.reachable = true

  accessory = self.setService(accessory);

  var accessoryName = accessory.context.name;
  self.accessories[accessoryName] = accessory;
}

// Method to setup accesories from config.json
cmdSwitchPlatform.prototype.didFinishLaunching = function() {
  var self = this;

  for (var i in self.switches) {
    var data = self.switches[i];
    self.addAccessory(data);
  }
}

// Method to add and update HomeKit accessories
cmdSwitchPlatform.prototype.addAccessory = function(data) {
  var self = this;

  if (!self.accessories[data.name]) {
    var uuid = UUIDGen.generate(data.name);

    // Setup accessory as SWITCH (8) category.
    var newAccessory = new Accessory(data.name, uuid, 8);

    // Store and initialize variables into context
    newAccessory.context.name = data.name;
    newAccessory.context.on_cmd = data.on_cmd;
    newAccessory.context.off_cmd = data.off_cmd;
    newAccessory.context.state_cmd = data.state_cmd;
    newAccessory.context.state = false;

    // Setup HomeKit switch service
    newAccessory.addService(Service.Switch, data.name);

    // Setup listeners for different switch events
    newAccessory = self.setService(newAccessory);

    // Retrieve initial state
    newAccessory = self.getInitState(newAccessory, data);

    // Register accessory in HomeKit
    self.api.registerPlatformAccessories("homebridge-cmdswitch2", "cmdSwitch2", [newAccessory]);
  } else {
    var newAccessory = self.accessories[data.name];

    // Update variables in context
    newAccessory.context.on_cmd = data.on_cmd;
    newAccessory.context.off_cmd = data.off_cmd;
    newAccessory.context.state_cmd = data.state_cmd;

    // Update initial state
    newAccessory = self.getInitState(newAccessory, data);

    // Update accessory in HomeKit
    self.api.updatePlatformAccessories([newAccessory]);
  }

  // Store accessory in cache
  self.accessories[data.name] = newAccessory;
}

// Method to remove accessories from HomeKit
cmdSwitchPlatform.prototype.removeAccessory = function(accessory) {
  if (accessory) {
    var name = accessory.context.name;
    this.api.unregisterPlatformAccessories("homebridge-cmdswitch2", "cmdSwitch2", [accessory]);
    delete this.accessories[name];
    this.log("[" + name + "] Removed from HomeBridge.");
  }
}

// Method to setup listeners for different events
cmdSwitchPlatform.prototype.setService = function(accessory) {
  var self = this;

  accessory
    .getService(Service.Switch)
    .getCharacteristic(Characteristic.On)
    .on('get', self.getPowerState.bind(this, accessory.context))
    .on('set', self.setPowerState.bind(this, accessory.context));

  accessory.on('identify', self.identify.bind(this, accessory.context));

  return accessory;
}

// Method to retrieve initial state
cmdSwitchPlatform.prototype.getInitState = function(accessory, data) {
  var info = accessory.getService(Service.AccessoryInformation);

  if (data.manufacturer) {
    accessory.context.manufacturer = data.manufacturer;
    info.setCharacteristic(Characteristic.Manufacturer, data.manufacturer.toString());
  }

  if (data.model) {
    accessory.context.model = data.model;
    info.setCharacteristic(Characteristic.Model, data.model.toString());
  }

  if (data.serial) {
    accessory.context.serial = data.serial;
    info.setCharacteristic(Characteristic.SerialNumber, data.serial.toString());
  }

  accessory
    .getService(Service.Switch)
    .getCharacteristic(Characteristic.On)
    .getValue();

  return accessory;
}

// Method to determine current state
cmdSwitchPlatform.prototype.getPowerState = function(data, callback) {
  var self = this;
  var name = "[" + data.name + "] ";

  // Execute command to detect state
  if (data.state_cmd) {
    exec(data.state_cmd, function(error, stdout, stderr) {
      data.state = stdout ? true : false;
      self.log(name + "Current state: " + (data.state ? "On." : "Off."));
      callback(null, data.state);
    });
  } else {
    self.log(name + "Current state: " + (data.state ? "On." : "Off."));
    callback(null, data.state);
  }
}

// Method to set state
cmdSwitchPlatform.prototype.setPowerState = function(data, state, callback) {
  var self = this;
  var name = "[" + data.name + "] ";

  var cmd = state ? data.on_cmd : data.off_cmd;
  var tout = null;

  // Execute command to set state
  if (cmd) {
    exec(cmd, function(error, stdout, stderr) {
      // Error detection
      if (error && (state != data.state)) {
        self.log(name + "Failed to turn " + (state ? "on!" : "off!"));
      } else {
        self.log(name + "Turned " + (state ? "on." : "off."));
        data.state = state;
        error = null;
      }

      if (tout) {
        clearTimeout(tout);
        callback(error);
      }
    });

    // Allow 2s to set state but otherwise assumes success
    tout = setTimeout(function() {
      tout = null;
      self.log(name + "Turning " + (state ? "on" : "off") + " took too long, assuming success." );
      callback();
    }, 2000);
  } else {
    self.log(name + "Turned " + (state ? "on" : "off"));
    data.state = state;
    callback();
  }
}

// Method to handle identify request
cmdSwitchPlatform.prototype.identify = function(data, paired, callback) {
  var self = this;
  var name = "[" + data.name + "] ";

  self.log(name + "Identify requested!");
  callback();
}

// Method to handle plugin configuration in HomeKit app
cmdSwitchPlatform.prototype.configurationRequestHandler = function(context, request, callback) {
  if (request && request.type === "Terminate") {
    return;
  }

  // Instruction
  if (!context.step) {
    var instructionResp = {
      "type": "Interface",
      "interface": "instruction",
      "title": "Before You Start...",
      "detail": "Please make sure homebridge is running with elevated privileges.",
      "showNextButton": true
    }

    context.step = 1;
    callback(instructionResp);
  } else {
    switch (context.step) {
      case 1:
        // Operation choices
        var respDict = {
          "type": "Interface",
          "interface": "list",
          "title": "What do you want to do?",
          "items": [
            "Add New Switch",
            "Modify Existing Switch",
            "Remove Existing Switch"
          ]
        }

        context.step = 2;
        callback(respDict);
        break;
      case 2:
        var selection = request.response.selections[0];
        if (selection === 0) {
          // Info for new accessory
          var respDict = {
            "type": "Interface",
            "interface": "input",
            "title": "New Switch",
            "items": [{
              "id": "name",
              "title": "Name (Required)",
              "placeholder": "HTPC"
            }, {
              "id": "on_cmd",
              "title": "CMD to Turn On",
              "placeholder": "wakeonlan XX:XX:XX:XX:XX:XX"
            }, {
              "id": "off_cmd",
              "title": "CMD to Turn Off",
              "placeholder": "net rpc shutdown -I XXX.XXX.XXX.XXX -U user%password"
            }, {
              "id": "state_cmd",
              "title": "CMD to Check ON State",
              "placeholder": "ping -c 2 -W 1 XXX.XXX.XXX.XXX | grep -i '2 received'"
            }, {
              "id": "manufacturer",
              "title": "Manufacturer",
              "placeholder": "Default-Manufacturer"
            }, {
              "id": "model",
              "title": "Model",
              "placeholder": "Default-Model"
            }, {
              "id": "serial",
              "title": "Serial Number",
              "placeholder": "Default-SerialNumber"
            }]
          };

          context.step = 3;
          callback(respDict);
        } else {
          var self = this;
          var switches = Object.keys(self.accessories).map(function(k) {return self.accessories[k]});
          var names = switches.map(function(k) {return k.displayName});

          if (names.length > 0) {
            // Select existing accessory for modification or removal
            if (selection === 1) {
              var title = "Witch switch do you want to modify?";
              context.modify = 1;
            } else {
              var title = "Witch switch do you want to remove?";
              context.modify = 0;
            }
            var respDict = {
              "type": "Interface",
              "interface": "list",
              "title": title,
              "items": names
            };

            context.switches = switches;
            context.step = 4;
          } else {
            var respDict = {
              "type": "Interface",
              "interface": "instruction",
              "title": "Unavailable",
              "detail": "No switch is configured.",
              "showNextButton": true
            };

            context.step = 1;
          }
          callback(respDict);
        }
        break;
      case 3:
        var userInputs = request.response.inputs;
        var newSwitch = {};

        // Setup input for addAccessory
        if (context.selected) {
          var accessory = this.accessories[context.selected];
          newSwitch.name = context.selected;
          newSwitch.on_cmd = userInputs.on_cmd || accessory.context.on_cmd;
          newSwitch.off_cmd = userInputs.off_cmd || accessory.context.off_cmd;
          newSwitch.state_cmd = userInputs.state_cmd || accessory.context.state_cmd;
          newSwitch.manufacturer = userInputs.manufacturer;
          newSwitch.model = userInputs.model;
          newSwitch.serial = userInputs.serial;
        } else {
          newSwitch.name = userInputs.name;
          newSwitch.on_cmd = userInputs.on_cmd;
          newSwitch.off_cmd = userInputs.off_cmd;
          newSwitch.state_cmd = userInputs.state_cmd;
          newSwitch.manufacturer = userInputs.manufacturer;
          newSwitch.model = userInputs.model;
          newSwitch.serial = userInputs.serial;
        }

        if (newSwitch.name) {
          // Register or update accessory in HomeKit
          this.addAccessory(newSwitch);
          var respDict = {
            "type": "Interface",
            "interface": "instruction",
            "title": "Success",
            "detail": "The new switch is now updated.",
            "showNextButton": true
          };

          context.step = 5;
        } else {
          // Error if required info is missing
          var respDict = {
            "type": "Interface",
            "interface": "instruction",
            "title": "Error",
            "detail": "Name of the switch is missing.",
            "showNextButton": true
          };

          context.step = 1;
        }
        callback(respDict);
        break;
      case 4:
        var selection = request.response.selections[0];
        var accessory = context.switches[selection];
        if (context.modify) {
          // Modify info of selected accessory
          var respDict = {
            "type": "Interface",
            "interface": "input",
            "title": accessory.displayName.toString(),
            "items": [{
              "id": "on_cmd",
              "title": "CMD to Turn On",
              "placeholder": "Leave blank if unchanged"
            }, {
              "id": "off_cmd",
              "title": "CMD to Turn Off",
              "placeholder": "Leave blank if unchanged"
            }, {
              "id": "state_cmd",
              "title": "CMD to Check ON State",
              "placeholder": "Leave blank if unchanged"
            }, {
              "id": "manufacturer",
              "title": "Manufacturer",
              "placeholder": "Leave blank if unchanged"
            }, {
              "id": "model",
              "title": "Model",
              "placeholder": "Leave blank if unchanged"
            }, {
              "id": "serial",
              "title": "Serial",
              "placeholder": "Leave blank if unchanged"
            }]
          };

          context.selected = accessory.context.name;
          context.step = 3;
        } else {
          // Remove selected accessory from HomeKit
          this.removeAccessory(accessory);
          var respDict = {
            "type": "Interface",
            "interface": "instruction",
            "title": "Success",
            "detail": "The switch is now removed.",
            "showNextButton": true
          };

          context.step = 5;
        }
        callback(respDict);
        break;
      case 5:
        // Update config.json accordingly
        var self = this;
        delete context.step;
        var newConfig = self.config;
        var newSwitches = Object.keys(self.accessories).map(function(k) {
          var accessory = self.accessories[k];
          var data = {
            'name': accessory.context.name,
            'on_cmd': accessory.context.on_cmd,
            'off_cmd': accessory.context.off_cmd,
            'state_cmd': accessory.context.state_cmd,
            'manufacturer': accessory.context.manufacturer,
            'model': accessory.context.model,
            'serial': accessory.context.serial
          };
          return data;
        });

        newConfig.switches = newSwitches;
        callback(null, "platform", true, newConfig);
        break;
    }
  }
}
<|endoftext|>