<commit_before>const fs = require('fs');
const mongojs = require('mongojs');
const discord = require('discordie');
const handlebars = require('handlebars');
const Screenshot = require('screenshot-stream');
const winston = require('winston');

/** A Discord bot, which encourages players to join voice channels. */
class Kirito {
  constructor() {
    // Prepare discord api
    this.discordApi = new discord({autoReconnect: true});
    this.discordApi.connect({ token: process.env.AUTH_TOKEN });

    // Prepare database
    this.db = mongojs(process.env.DATABASE, ['users']);

    // Prepare application state
    this.connectedUsers = [];
    this.subscribeToEvents();
  }

  /**
   * Registers callback functions to certain events which are triggered
   * through the DiscordApi.
   */
  subscribeToEvents() {
    // Bind this into callback functions
    this.prepareBot = this.prepareBot.bind(this);
    this.joinServer = this.joinServer.bind(this);
    this.userConnected = this.userConnected.bind(this);
    this.userDisconnected = this.userDisconnected.bind(this);
    this.handleChatCommand = this.handleChatCommand.bind(this);

    // Subscribe to events
    this.discordApi.Dispatcher.on(discord.Events.GATEWAY_READY, this.prepareBot);
    this.discordApi.Dispatcher.on(discord.Events.GUILD_CREATE, this.joinServer);
    this.discordApi.Dispatcher.on(discord.Events.VOICE_CHANNEL_JOIN, this.userConnected);
    this.discordApi.Dispatcher.on(discord.Events.VOICE_CHANNEL_LEAVE, this.userDisconnected);
    this.discordApi.Dispatcher.on(discord.Events.MESSAGE_CREATE, this.handleChatCommand);
  }

  /**
   * Initializes the bot using the Discord API.
   */
  prepareBot() {
    this.discordApi.User.setGame('together with Asuna');
    this.addCurrentlyConnectedUsers();
  }

  /**
   * Adds all currently connected users to the list of connected users.
   */
  addCurrentlyConnectedUsers() {
    this.discordApi.Guilds.forEach((server) => {
      server.voiceChannels.forEach((channel) => {
        channel.members.forEach((user) => {
          this.addConnectedUser(user, server);
        });
      });
    });
  }

  /**
   * Prints an introduction message.
   * @param {Event} e - Discord API event.
   */
  joinServer(e) {
    e.guild.generalChannel.sendTyping();
    const introduction = [
      'Hi!',
      'I am Kirito, your personal voice chat coach. From now on, every time you join',
      'a voice channel on this Server you will receive experience and therefore level.',
      'Additionally, you can ask me things through so called commands. Currently,',
      'I know two commands. Command number one is `ping`, which obviously starts a',
      'table tennis match, and command number two is called `profile` where I will',
      'print an image of your current player profile.',
      '',
      'Enjoy your time in voice channels :).'
    ]
    e.guild.generalChannel.sendMessage(introduction);
  }

  /**
   * Handles users connecting to voice channels.
   * @param {Event} e - Discord API event.
   */
  userConnected(e) {
    this.addConnectedUser(e.user, e.channel.guild);
  }

  /**
   * Adds a user to the list of users in voice channels (connected users).
   * @param {Object} user - Discord API user object.
   * @param {Object} server - Discord API guild object.
   */
  addConnectedUser(user, server) {
    const rewardInterval = 60000;
    const experiencePerMinute = 1;
    const connectedUser = {};
    connectedUser.experienceTimer = setInterval(() => { this.addExperience(user, server, experiencePerMinute) }, rewardInterval);
    this.connectedUsers[user.id] = connectedUser;
    winston.log("info", 'User ' + user.username + ' connected to server ' + server.name + '.');
  }

  /**
   * Handles users disconnecting from voice channels.
   * @param {Event} e - Discord API event.
   */
  userDisconnected(e) {
    this.removeConnectedUser(e.user);
  }

  /**
   * Removes a user from the list of users in voice channels (connected users).
   * @param {Object} user - Discord API user object.
   */
  removeConnectedUser(user) {
    if (!this.connectedUsers[user.id]) {
      winston.log("warn", 'User ' + user.username + ' not found in list of online users.');
      return;
    }
    clearInterval(this.connectedUsers[user.id].experienceTimer);
    delete this.connectedUsers[user.id];
    winston.log("info", 'User ' + user.username + ' disconnected.');
  }

  /**
   * Adds experience to the user account.
   * @param {Object} user - Discord API user object.
   * @param {Object} server - Discord API guild object.
   * @param {Number} experience - Amount of experience to add.
   */
  addExperience(user, server, experience) {
    this.db.users.update({id: user.id}, { $setOnInsert: user, $inc: { experience: experience } }, {upsert: true});
    this.db.users.update({id: user.id, 'servers.id': server.id}, {$inc: {'servers.$.experience': experience}} );
    this.db.users.update({id: user.id, 'servers.id': {$ne: server.id}}, { $push: {'servers': {id: server.id, 'experience': experience}} }, (err, res) => {
      winston.log("info", 'Added ' + experience + ' experience to user ' + user.username + '.');
      winston.log("info", user.username + ' has now a total experience of ' + res.experience + '.');
    });
  }

  /**
   * Handles received chat messages and checks for chat commands.
   * @param {Event} e - Discord API event.
   */
  handleChatCommand(e) {
    switch (e.message.content) {
      case 'ping':
        e.message.channel.sendTyping();
        e.message.channel.sendMessage('pong');
        break;

      case 'profile':
        this.getProfile(e.message.author, (profile) => {
          e.message.channel.sendTyping();
          e.message.channel.uploadFile(profile, 'profile.png');
        });
        break;
    }
  }

  /**
   * Gathers necessary user data and prepares the image stream to print
   * a users profile.
   * @param {Object} user - Discord API user object.
   * @param {Callback} callback - Called when image stream is ready.
   */
  getProfile(user, callback) {
    const newUser = JSON.parse(JSON.stringify(user));
    newUser.experience = 0;
    winston.log("info", 'Printing profile for user ' + user.username + '.');
    this.db.users.findAndModify({query: {id: user.id}, update: { $setOnInsert: newUser }, upsert: true, new: true}, (err, result) => {
      const profileInformation = {
        username: user.username,
        level: this.calculateLevel(result.experience),
        levelProgress: this.calculateLevelProgress(result.experience),
        avatar: this.getAvatarUrl(user),
        servers: this.getAdditionalServerData(result.servers)
      };
      winston.log("info", 'username:' + user.username);
      winston.log("info", 'level:' + this.calculateLevel(result.experience));
      winston.log("info", 'experience:' + result.experience);
      winston.log("info", 'levelProgress:' + this.calculateLevelProgress(result.experience));
      winston.log("info", 'servers:' + this.getAdditionalServerData(result.servers));
      const profileHtmlFilename = 'profiles/' + user.id + '.html';
      this.createProfileHtml(profileHtmlFilename, profileInformation);
      const stream = this.createProfileImageStream(profileHtmlFilename);

      /**
       * Is called when the profile creation finished and the image stream
       * is ready.
       * @callback callback
       * @param {String} stream - Image stream containing the user profile.
       */
      callback(stream);
    });
  }

  /**
   * Calculates the level given a total amount of experience.
   * @param {Number} experience - Amount of experience.
   * @return {Number} Level corresponding to experience.
   */
  calculateLevel(experience) {
    return Math.floor(Math.sqrt(experience / 3 + 36) - 5);
  }

  /**
   * Calculates the progress of the current level given a total amount
   * of experience.
   * @param {Number} experience - Amount of experience.
   * @return {Number} Level progress in percent (0 - 100).
   */
  calculateLevelProgress(experience) {
    const userLevel = this.calculateLevel(experience);
    const currentLevelMinExperience = this.minLevelExperience(userLevel);
    const nextLevelMinExperience = this.minLevelExperience(userLevel + 1);
    const currentLevelExperience = nextLevelMinExperience - currentLevelMinExperience;
    const userLevelExperience = experience - currentLevelMinExperience;
    return userLevelExperience * 100 / currentLevelExperience;
  }

  /**
   * If the user has an avatar, the avatar is returned, otherwise an URL to
   * a default avatar is returned.
   * @param {Object} user - Discord API user object.
   * @return {String} Avatar URL.
   */
  getAvatarUrl(user) {
    let avatarUrl = '';
    if (user.avatar) {
      avatarUrl = 'https://cdn.discordapp.com/avatars/' + user.id + '/' + user.avatar + '.png';
    } else {
      const defaultAvatarIndex = Number(user.id) % 100;
      avatarUrl = './images/superheroes/heroes-and-villains-' + defaultAvatarIndex + '.png'
    }
    return avatarUrl;
  }

  /**
   * If the server has an icon, the icon is returned, otherwise an URL to
   * a default icon is returned.
   * @param {Object} server - Discord API server object.
   * @return {String} Icon URL.
   */
  getIconUrl(server) {
    let iconUrl = '';
    if (server.icon) {
      iconUrl = 'https://cdn.discordapp.com/icons/' + server.id + '/' + server.icon + '.png';
    } else {
      const defaultIconIndex = Number(server.id) % 100;
      iconUrl = './images/superheroes/heroes-and-villains-' + defaultIconIndex + '.png'
    }
    return iconUrl;
  }

  /**
   * Calculates the minimal amount of experience needed for a given level.
   * @param {Number} level - The level, which the experience is calculated for.
   * @return {Number} Amount of Experience needed for the level.
   */
  minLevelExperience(level) {
    return (3 * level * (level + 10) - 33);
  }

  /**
   * Fetches additional information for a given list of Discord servers
   * through the Discord API.
   * @param {Array} servers - List of objects containing Discord server ids.
   * @return {Array} List of Discord servers.
   */
  getAdditionalServerData(servers) {
    let serverList = [];
    if (!servers) return [];

    servers.map((currentServer) => {
      const server = this.discordApi.Guilds.get(currentServer.id);
      const databaseServer = JSON.parse(JSON.stringify(server));
      databaseServer.iconURL = this.getIconUrl(server);
      databaseServer.experience = currentServer.experience;
      serverList.push(databaseServer);
    });

    serverList.sort((a, b) => {b.experience - a.experience});
    return serverList;
  }

  /**
   * Creates a HTML profile file containing user information based on
   * a profile template.
   * @param {String} filename - Name and path of the HTML file.
   * @param {Object} profileInformation - Information to fill the HTML profile.
   */
  createProfileHtml(filename, profileInformation) {
    const templateFile = fs.readFileSync('Profile/Profile.html').toString();
    const template = handlebars.compile(templateFile);
    const html = template(profileInformation);
    fs.writeFileSync(filename, html);
  }

  /**
   * Creates an image stream given an HTML file.
   * @param {String} filename - Name and path of the HTML file.
   * @return {String} Image stream.
   */
  createProfileImageStream(filename) {
    return Screenshot(filename, '500x1000', {crop: true, selector: '.profile'});
  }

}

const kiritoInstance = new Kirito();
<commit_msg>Fixed minor code smells<commit_after>const fs = require('fs');
const mongojs = require('mongojs');
const Discordie = require('discordie');
const handlebars = require('handlebars');
const Screenshot = require('screenshot-stream');
const winston = require('winston');

/** A Discord bot, which encourages players to join voice channels. */
class Kirito {
  constructor() {
    // Prepare discord api
    this.discordApi = new Discordie({autoReconnect: true});
    this.discordApi.connect({ token: process.env.AUTH_TOKEN });

    // Prepare database
    this.db = mongojs(process.env.DATABASE, ['users']);

    // Prepare application state
    this.connectedUsers = [];
    this.subscribeToEvents();
  }

  /**
   * Registers callback functions to certain events which are triggered
   * through the DiscordApi.
   */
  subscribeToEvents() {
    // Bind this into callback functions
    this.prepareBot = this.prepareBot.bind(this);
    this.joinServer = this.joinServer.bind(this);
    this.userConnected = this.userConnected.bind(this);
    this.userDisconnected = this.userDisconnected.bind(this);
    this.handleChatCommand = this.handleChatCommand.bind(this);

    // Subscribe to events
    this.discordApi.Dispatcher.on(discord.Events.GATEWAY_READY, this.prepareBot);
    this.discordApi.Dispatcher.on(discord.Events.GUILD_CREATE, this.joinServer);
    this.discordApi.Dispatcher.on(discord.Events.VOICE_CHANNEL_JOIN, this.userConnected);
    this.discordApi.Dispatcher.on(discord.Events.VOICE_CHANNEL_LEAVE, this.userDisconnected);
    this.discordApi.Dispatcher.on(discord.Events.MESSAGE_CREATE, this.handleChatCommand);
  }

  /**
   * Initializes the bot using the Discord API.
   */
  prepareBot() {
    this.discordApi.User.setGame('together with Asuna');
    this.addCurrentlyConnectedUsers();
  }

  /**
   * Adds all currently connected users to the list of connected users.
   */
  addCurrentlyConnectedUsers() {
    this.discordApi.Guilds.forEach((server) => {
      server.voiceChannels.forEach((channel) => {
        channel.members.forEach((user) => {
          this.addConnectedUser(user, server);
        });
      });
    });
  }

  /**
   * Prints an introduction message.
   * @param {Event} e - Discord API event.
   */
  joinServer(e) {
    e.guild.generalChannel.sendTyping();
    const introduction = [
      'Hi!',
      'I am Kirito, your personal voice chat coach. From now on, every time you join',
      'a voice channel on this Server you will receive experience and therefore level.',
      'Additionally, you can ask me things through so called commands. Currently,',
      'I know two commands. Command number one is `ping`, which obviously starts a',
      'table tennis match, and command number two is called `profile` where I will',
      'print an image of your current player profile.',
      '',
      'Enjoy your time in voice channels :).'
    ];
    e.guild.generalChannel.sendMessage(introduction);
  }

  /**
   * Handles users connecting to voice channels.
   * @param {Event} e - Discord API event.
   */
  userConnected(e) {
    this.addConnectedUser(e.user, e.channel.guild);
  }

  /**
   * Adds a user to the list of users in voice channels (connected users).
   * @param {Object} user - Discord API user object.
   * @param {Object} server - Discord API guild object.
   */
  addConnectedUser(user, server) {
    const rewardInterval = 60000;
    const experiencePerMinute = 1;
    const connectedUser = {};
    connectedUser.experienceTimer = setInterval(() => { this.addExperience(user, server, experiencePerMinute); }, rewardInterval);
    this.connectedUsers[user.id] = connectedUser;
    winston.log("info", 'User ' + user.username + ' connected to server ' + server.name + '.');
  }

  /**
   * Handles users disconnecting from voice channels.
   * @param {Event} e - Discord API event.
   */
  userDisconnected(e) {
    this.removeConnectedUser(e.user);
  }

  /**
   * Removes a user from the list of users in voice channels (connected users).
   * @param {Object} user - Discord API user object.
   */
  removeConnectedUser(user) {
    if (!this.connectedUsers[user.id]) {
      winston.log("warn", 'User ' + user.username + ' not found in list of online users.');
      return;
    }
    clearInterval(this.connectedUsers[user.id].experienceTimer);
    delete this.connectedUsers[user.id];
    winston.log("info", 'User ' + user.username + ' disconnected.');
  }

  /**
   * Adds experience to the user account.
   * @param {Object} user - Discord API user object.
   * @param {Object} server - Discord API guild object.
   * @param {Number} experience - Amount of experience to add.
   */
  addExperience(user, server, experience) {
    this.db.users.update({id: user.id}, { $setOnInsert: user, $inc: { experience } }, {upsert: true});
    this.db.users.update({id: user.id, 'servers.id': server.id}, {$inc: {'servers.$.experience': experience}} );
    this.db.users.update({id: user.id, 'servers.id': {$ne: server.id}}, { $push: {'servers': {id: server.id, experience}} }, (err, res) => {
      winston.log("info", 'Added ' + experience + ' experience to user ' + user.username + '.');
      winston.log("info", user.username + ' has now a total experience of ' + res.experience + '.');
    });
  }

  /**
   * Handles received chat messages and checks for chat commands.
   * @param {Event} e - Discord API event.
   */
  handleChatCommand(e) {
    switch (e.message.content) {
      case 'ping':
        e.message.channel.sendTyping();
        e.message.channel.sendMessage('pong');
        break;

      case 'profile':
        this.getProfile(e.message.author, (profile) => {
          e.message.channel.sendTyping();
          e.message.channel.uploadFile(profile, 'profile.png');
        });
        break;
    }
  }

  /**
   * Gathers necessary user data and prepares the image stream to print
   * a users profile.
   * @param {Object} user - Discord API user object.
   * @param {Callback} callback - Called when image stream is ready.
   */
  getProfile(user, callback) {
    const newUser = JSON.parse(JSON.stringify(user));
    newUser.experience = 0;
    winston.log("info", 'Printing profile for user ' + user.username + '.');
    this.db.users.findAndModify({query: {id: user.id}, update: { $setOnInsert: newUser }, upsert: true, new: true}, (err, result) => {
      const profileInformation = {
        username: user.username,
        level: this.calculateLevel(result.experience),
        levelProgress: this.calculateLevelProgress(result.experience),
        avatar: this.getAvatarUrl(user),
        servers: this.getAdditionalServerData(result.servers)
      };
      winston.log("info", 'username:' + user.username);
      winston.log("info", 'level:' + this.calculateLevel(result.experience));
      winston.log("info", 'experience:' + result.experience);
      winston.log("info", 'levelProgress:' + this.calculateLevelProgress(result.experience));
      winston.log("info", 'servers:' + this.getAdditionalServerData(result.servers));
      const profileHtmlFilename = 'profiles/' + user.id + '.html';
      this.createProfileHtml(profileHtmlFilename, profileInformation);
      const stream = this.createProfileImageStream(profileHtmlFilename);

      /**
       * Is called when the profile creation finished and the image stream
       * is ready.
       * @callback callback
       * @param {String} stream - Image stream containing the user profile.
       */
      callback(stream);
    });
  }

  /**
   * Calculates the level given a total amount of experience.
   * @param {Number} experience - Amount of experience.
   * @return {Number} Level corresponding to experience.
   */
  calculateLevel(experience) {
    return Math.floor(Math.sqrt(experience / 3 + 36) - 5);
  }

  /**
   * Calculates the progress of the current level given a total amount
   * of experience.
   * @param {Number} experience - Amount of experience.
   * @return {Number} Level progress in percent (0 - 100).
   */
  calculateLevelProgress(experience) {
    const userLevel = this.calculateLevel(experience);
    const currentLevelMinExperience = this.minLevelExperience(userLevel);
    const nextLevelMinExperience = this.minLevelExperience(userLevel + 1);
    const currentLevelExperience = nextLevelMinExperience - currentLevelMinExperience;
    const userLevelExperience = experience - currentLevelMinExperience;
    return userLevelExperience * 100 / currentLevelExperience;
  }

  /**
   * If the user has an avatar, the avatar is returned, otherwise an URL to
   * a default avatar is returned.
   * @param {Object} user - Discord API user object.
   * @return {String} Avatar URL.
   */
  getAvatarUrl(user) {
    let avatarUrl = '';
    if (user.avatar) {
      avatarUrl = 'https://cdn.discordapp.com/avatars/' + user.id + '/' + user.avatar + '.png';
    } else {
      const defaultAvatarIndex = Number(user.id) % 100;
      avatarUrl = './images/superheroes/heroes-and-villains-' + defaultAvatarIndex + '.png'
    }
    return avatarUrl;
  }

  /**
   * If the server has an icon, the icon is returned, otherwise an URL to
   * a default icon is returned.
   * @param {Object} server - Discord API server object.
   * @return {String} Icon URL.
   */
  getIconUrl(server) {
    let iconUrl = '';
    if (server.icon) {
      iconUrl = 'https://cdn.discordapp.com/icons/' + server.id + '/' + server.icon + '.png';
    } else {
      const defaultIconIndex = Number(server.id) % 100;
      iconUrl = './images/superheroes/heroes-and-villains-' + defaultIconIndex + '.png'
    }
    return iconUrl;
  }

  /**
   * Calculates the minimal amount of experience needed for a given level.
   * @param {Number} level - The level, which the experience is calculated for.
   * @return {Number} Amount of Experience needed for the level.
   */
  minLevelExperience(level) {
    return (3 * level * (level + 10) - 33);
  }

  /**
   * Fetches additional information for a given list of Discord servers
   * through the Discord API.
   * @param {Array} servers - List of objects containing Discord server ids.
   * @return {Array} List of Discord servers.
   */
  getAdditionalServerData(servers) {
    let serverList = [];
    if (!servers) return [];

    servers.map((currentServer) => {
      const server = this.discordApi.Guilds.get(currentServer.id);
      const databaseServer = JSON.parse(JSON.stringify(server));
      databaseServer.iconURL = this.getIconUrl(server);
      databaseServer.experience = currentServer.experience;
      serverList.push(databaseServer);
    });

    serverList.sort((a, b) => {b.experience - a.experience});
    return serverList;
  }

  /**
   * Creates a HTML profile file containing user information based on
   * a profile template.
   * @param {String} filename - Name and path of the HTML file.
   * @param {Object} profileInformation - Information to fill the HTML profile.
   */
  createProfileHtml(filename, profileInformation) {
    const templateFile = fs.readFileSync('Profile/Profile.html').toString();
    const template = handlebars.compile(templateFile);
    const html = template(profileInformation);
    fs.writeFileSync(filename, html);
  }

  /**
   * Creates an image stream given an HTML file.
   * @param {String} filename - Name and path of the HTML file.
   * @return {String} Image stream.
   */
  createProfileImageStream(filename) {
    return Screenshot(filename, '500x1000', {crop: true, selector: '.profile'});
  }

}

const kiritoInstance = new Kirito();
<|endoftext|>


<commit_before>require('es6-promise').polyfill();
require('isomorphic-fetch');
var _ = require('lodash');
var superagent = require('superagent');
var FormData = require('form-data');

var api_url = 'https://api.cosmicjs.com';
var api_version = 'v1';

module.exports = {

  getBucket: function(config, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/?read_key=' + config.bucket.read_key;
    fetch(endpoint)
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      return callback(false, response);
    });
  },

  getObjects: function(config, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/objects?read_key=' + config.bucket.read_key;
    fetch(endpoint)
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      // Constructor
      var cosmic = {};
      var objects = response.objects;
      cosmic.objects = {};
      cosmic.objects.all = objects;
      cosmic.objects.type = _.groupBy(objects, 'type_slug');
      cosmic.object = _.map(objects, keyMetafields);
      cosmic.object = _.keyBy(cosmic.object, 'slug');
      return callback(false, cosmic);
    });
  },

  getObjectsByType: function(config, object, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/object-type/' + object.type_slug + '?read_key=' + config.bucket.read_key;
    if (object.limit) endpoint += '&limit=' + object.limit;
    if (object.skip) endpoint +=  '&skip=' + object.skip;
    if (object.locale) endpoint += '&locale=' + object.locale;
    fetch(endpoint)
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          "message" : "There was an error with this request."
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      // Constructor
      var cosmic = {};
      var objects = response.objects;
      cosmic.objects = {};
      cosmic.objects.all = objects;
      cosmic.object = _.map(objects, keyMetafields);
      cosmic.object = _.keyBy(cosmic.object, "slug");
      cosmic.total = response.total;
      return callback(false, cosmic);
    });
  },

  // DEPRECATE THIS
  getObjectType: function(config, object, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/object-type/' + object.type_slug + '?read_key=' + config.bucket.read_key;
    if (object.limit) endpoint += '&limit=' + object.limit;
    if (object.skip) endpoint +=  '&skip=' + object.skip;
    if (object.locale) endpoint += '&locale=' + object.locale;
      fetch(endpoint)
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          "message" : "There was an error with this request."
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      // Constructor
      var cosmic = {};
      var objects = response.objects;
      cosmic.objects = {};
      cosmic.objects.all = objects;
      cosmic.object = _.map(objects, keyMetafields);
      cosmic.object = _.keyBy(cosmic.object, "slug");
      cosmic.total = response.total;
      return callback(false, cosmic);
    });
  },

  getObject: function(config, object, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/object/' + object.slug + '?read_key=' + config.bucket.read_key;
    if (object._id) {
      endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/object-by-id/' + object._id + '?read_key=' + config.bucket.read_key;
    }
    fetch(endpoint)
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      // Constructor
      var cosmic = {};
      var object = response.object;
      var metafields = object.metafields;
      if(metafields){
        object.metafield = _.keyBy(metafields, "key");
      }
      cosmic.object = object;
      return callback(false, cosmic);
    });
  },

  getObjectsBySearch: function(config, object , callback){
    var searchParams = '/search?metafield_key=' + object.metafield_key;
    if (object.metafield_value) searchParams += '&metafield_value=' + object.metafield_value;
    else if (object.metafield_object_slug) searchParams += '&metafield_object_slug=' + object.metafield_object_slug;
    else searchParams += '&metafield_value_has=' + object.metafield_value_has;
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/object-type/' + object.type_slug + searchParams + '&read_key=' + config.bucket.read_key;
    if (object.limit) endpoint += '&limit=' + object.limit;
    if (object.skip) endpoint +=  '&skip=' + object.skip;
    if (object.locale) endpoint += '&locale=' + object.locale;
      fetch(endpoint)
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          "message" : "There was an error with this request."
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      // Constructor
      var cosmic = {};
      var objects = response.objects;
      cosmic.objects = {};
      cosmic.objects.all = objects;
      cosmic.object = _.map(objects, keyMetafields);
      cosmic.object = _.keyBy(cosmic.object, "slug");
      cosmic.total = response.total;
      return callback(false, cosmic);
    });
  },

  getMedia: function(config, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/media?read_key=' + config.bucket.read_key;
    fetch(endpoint)
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      return callback(false, response);
    });
  },

  addObject: function(config, object, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/add-object';
    fetch(endpoint, {
      method: 'post',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(object)
    })
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      return callback(false, response);
    });
  },

  editObject: function(config, object, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/edit-object';
    fetch(endpoint, {
      method: 'put',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(object)
    })
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      return callback(false, response);
    });
  },

  deleteObject: function(config, object, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/delete-object';
    fetch(endpoint, {
      method: 'post',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(object)
    })
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      return callback(false, response);
    });
  },

  addMedia: function(config, params, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/media';
    var form = new FormData();
    if (config.bucket.write_key)
      form.append('write_key', config.bucket.write_key);
    form.append('media', params.media);
    if (params.folder)
      form.append('folder', params.folder);
    superagent.post(endpoint)
      .send(form)
      .end(function(err, response) {
        if (response.status >= 400) {
          var err = {
            'message': 'There was an error with this request.'
          }
          return callback(err, false);
        }
        return callback(false, response);
    });
  },

  deleteMedia: function(config, object, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/media/' + object.media_id;
    fetch(endpoint, {
      method: 'delete',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(object)
    })
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      return callback(false, response);
    });
  }
};

// Functions
function keyMetafields(object){
  var metafields = object.metafields;
  if(metafields){
    object.metafield = _.keyBy(metafields, 'key');
  }
  return object;
}
<commit_msg>added sort in getObjectType<commit_after>require('es6-promise').polyfill();
require('isomorphic-fetch');
var _ = require('lodash');
var superagent = require('superagent');
var FormData = require('form-data');

var api_url = 'https://api.cosmicjs.com';
var api_version = 'v1';

module.exports = {

  getBucket: function(config, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/?read_key=' + config.bucket.read_key;
    fetch(endpoint)
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      return callback(false, response);
    });
  },

  getObjects: function(config, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/objects?read_key=' + config.bucket.read_key;
    fetch(endpoint)
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      // Constructor
      var cosmic = {};
      var objects = response.objects;
      cosmic.objects = {};
      cosmic.objects.all = objects;
      cosmic.objects.type = _.groupBy(objects, 'type_slug');
      cosmic.object = _.map(objects, keyMetafields);
      cosmic.object = _.keyBy(cosmic.object, 'slug');
      return callback(false, cosmic);
    });
  },

  getObjectsByType: function(config, object, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/object-type/' + object.type_slug + '?read_key=' + config.bucket.read_key;
    if (object.limit) endpoint += '&limit=' + object.limit;
    if (object.skip) endpoint +=  '&skip=' + object.skip;
    if (object.locale) endpoint += '&locale=' + object.locale;
    fetch(endpoint)
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          "message" : "There was an error with this request."
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      // Constructor
      var cosmic = {};
      var objects = response.objects;
      cosmic.objects = {};
      cosmic.objects.all = objects;
      cosmic.object = _.map(objects, keyMetafields);
      cosmic.object = _.keyBy(cosmic.object, "slug");
      cosmic.total = response.total;
      return callback(false, cosmic);
    });
  },

  // DEPRECATE THIS
  getObjectType: function(config, object, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/object-type/' + object.type_slug + '?read_key=' + config.bucket.read_key;
    if (object.limit) endpoint += '&limit=' + object.limit;
    if (object.skip) endpoint +=  '&skip=' + object.skip;
    if (object.locale) endpoint += '&locale=' + object.locale;
    if (object.sort) endpoint += '&sort=' + object.sort;
      fetch(endpoint)
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          "message" : "There was an error with this request."
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      // Constructor
      var cosmic = {};
      var objects = response.objects;
      cosmic.objects = {};
      cosmic.objects.all = objects;
      cosmic.object = _.map(objects, keyMetafields);
      cosmic.object = _.keyBy(cosmic.object, "slug");
      cosmic.total = response.total;
      return callback(false, cosmic);
    });
  },

  getObject: function(config, object, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/object/' + object.slug + '?read_key=' + config.bucket.read_key;
    if (object._id) {
      endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/object-by-id/' + object._id + '?read_key=' + config.bucket.read_key;
    }
    fetch(endpoint)
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      // Constructor
      var cosmic = {};
      var object = response.object;
      var metafields = object.metafields;
      if(metafields){
        object.metafield = _.keyBy(metafields, "key");
      }
      cosmic.object = object;
      return callback(false, cosmic);
    });
  },

  getObjectsBySearch: function(config, object , callback){
    var searchParams = '/search?metafield_key=' + object.metafield_key;
    if (object.metafield_value) searchParams += '&metafield_value=' + object.metafield_value;
    else if (object.metafield_object_slug) searchParams += '&metafield_object_slug=' + object.metafield_object_slug;
    else searchParams += '&metafield_value_has=' + object.metafield_value_has;
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/object-type/' + object.type_slug + searchParams + '&read_key=' + config.bucket.read_key;
    if (object.limit) endpoint += '&limit=' + object.limit;
    if (object.skip) endpoint +=  '&skip=' + object.skip;
    if (object.locale) endpoint += '&locale=' + object.locale;
      fetch(endpoint)
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          "message" : "There was an error with this request."
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      // Constructor
      var cosmic = {};
      var objects = response.objects;
      cosmic.objects = {};
      cosmic.objects.all = objects;
      cosmic.object = _.map(objects, keyMetafields);
      cosmic.object = _.keyBy(cosmic.object, "slug");
      cosmic.total = response.total;
      return callback(false, cosmic);
    });
  },

  getMedia: function(config, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/media?read_key=' + config.bucket.read_key;
    fetch(endpoint)
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      return callback(false, response);
    });
  },

  addObject: function(config, object, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/add-object';
    fetch(endpoint, {
      method: 'post',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(object)
    })
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      return callback(false, response);
    });
  },

  editObject: function(config, object, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/edit-object';
    fetch(endpoint, {
      method: 'put',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(object)
    })
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      return callback(false, response);
    });
  },

  deleteObject: function(config, object, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/delete-object';
    fetch(endpoint, {
      method: 'post',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(object)
    })
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      return callback(false, response);
    });
  },

  addMedia: function(config, params, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/media';
    var form = new FormData();
    if (config.bucket.write_key)
      form.append('write_key', config.bucket.write_key);
    form.append('media', params.media);
    if (params.folder)
      form.append('folder', params.folder);
    superagent.post(endpoint)
      .send(form)
      .end(function(err, response) {
        if (response.status >= 400) {
          var err = {
            'message': 'There was an error with this request.'
          }
          return callback(err, false);
        }
        return callback(false, response);
    });
  },

  deleteMedia: function(config, object, callback){
    var endpoint = api_url + '/' + api_version + '/' + config.bucket.slug + '/media/' + object.media_id;
    fetch(endpoint, {
      method: 'delete',
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(object)
    })
    .then(function(response){
      if (response.status >= 400) {
        var err = {
          'message': 'There was an error with this request.'
        }
        return callback(err, false);
      }
      return response.json()
    })
    .then(function(response){
      return callback(false, response);
    });
  }
};

// Functions
function keyMetafields(object){
  var metafields = object.metafields;
  if(metafields){
    object.metafield = _.keyBy(metafields, 'key');
  }
  return object;
}
<|endoftext|>


<commit_before>'use strict'

const Bridge = require('./lib/Bridge')
const Errors = require('./lib/Errors')
const fs = require('fs')
const Gap = require('./lib/Gap')
const Operations = require('./lib/Operations')

/**
 * TODO
 *      - Finish unit testing
 *      - Ability to define arrays in url for creating multiple identical gaps
 *      - Ability to define operation sets within a bridge to be references by gaps
 *      - Multi-file support
 *      - Response status can be set or mapped with an object in gap response?
 */

class Versioner {

    /**
     * Constructor
     *
     * @param {object} options option hash
     * @param {string} options.path optional, path to a json file containing an api-bridge config
     * @param {Bridge[]} options.bridges optional, any programatically generated Bridges
     */
    constructor({ path, bridges = [] } = {}) {
        this.bridges = []

        if (!Array.isArray(bridges)) {
            bridges = [bridges]
        }

        if (typeof path === 'string') {
            bridges = bridges.concat(this.loadFromFile(path))
        }

        this.addBridges(bridges)
    }

    /**
     * Adds new API bridges to the array of ordered bridges
     *
     * @param {(Bridge|Bridge[])} bridge
     *
     * @throws {TypeError}
     */
    addBridges(bridges = []) {
        if (!Array.isArray(bridges)) bridges = [bridges]

        for (let _bridge of bridges) {
            if (!_bridge instanceof Bridge) {
                throw new TypeError('Bridges must be instances of the Bridge class')
            }

            this.bridges.push(_bridge)
        }

        this.bridges.sort((a, b) => a.compareVersions(b))
    }

    /**
     * Express middleware for automatically applying formatting
     * to the server's error responses
     *
     * @see ExpressJS middleware error handler
     *
     * @param {*} err error object
     * @param {express.Request} req
     * @param {express.Response} res
     * @param {function} next
     *
     * @returns {function[]} Middlewares in ascending order of version
     */
    errorHandler() {
        return (err, req, res, next) => {
            let headers = {}
            for (let key of Object.keys(res._headerNames)) {
                headers[res._headerNames[key]] = res._headers[key]
            }

            for (let _gap of res.__versioner_parameters.response_gaps) {
                // TODO test if these break when not set on the Gap
                _gap.error('headers').process(headers)
                err = _gap.error('body').process(err)
            }

            res._headers = {}
            res._headerNames = {}
            res.set(headers)

            res.__versioner_parameters.skip_formatting = true
            res.send(err) // 2nd argument for skipping response formatting
        }
    }

    /**
     * Generates API bridges from the given json file
     *
     * @param {string} path location of json file
     *
     * @returns {Bridge[]}
     * @throws {FormattingError} errors thrown for invalid file formatting
     * @throws {SyntaxError} errors thrown parsing JSON
     * @throws {Error} errors thrown by file io
     */
    loadFromFile(path) {
        let data = JSON.parse(fs.readFileSync(path))
        let bridges = []

        if (!Array.isArray(data)) data = [data]

        for (let _data of data) {
            if (!typeof _data === 'object' || Array.isArray(_data)) {
                throw new Error.FormattingError('Bridges must be defined as objects, got: ' + JSON.stringify(_data))
            }

            let _bridge = new Bridge({
                version: _data.version,
                description: _data.description
            })

            for (let _gap of _data.gaps) {
                _bridge.addGap(new Gap(_gap))
            }

            bridges.push(_bridge)
        }

        return bridges
    }

    /**
     * Express middlewares for automatically applying formatting
     * to the server's requests and responses
     *
     * @returns {function[]} Middlewares in ascending order of version
     */
    middleware() {
        return (req, res, next) => {
            res.__versioner_parameters = {
                response_gaps: [] // Will be in reverse order from bridges
            }

            for (let bridge of this.bridges) {
                if (bridge.compareVersions(res.locals.version) <= 0) continue

                let gap = bridge.getGap(req.method, req.path)
                if (gap === undefined) continue
                if (gap.shouldReply(res)) {
                    // Response was sent from inside Gap#shouldReply
                    return
                }

                // TODO test if these break when not set on the Gap
                gap.request('headers').process(req.headers)
                gap.request('body').process(req.body)
                gap.request('query').process(req.query)
                gap.request('params').process(req.params)

                res.__versioner_parameters.response_gaps.unshift(gap)
            }

            let tmp = res.send.bind(res)
            res.send = function(data) {
                if (res.__versioner_parameters.skip_formatting) {
                    return tmp(data)
                }
                let headers = {}
                for (let key of Object.keys(res._headerNames)) {
                    headers[res._headerNames[key]] = res._headers[key]
                }

                for (let _gap of res.__versioner_parameters.response_gaps) {
                    headers = _gap.response('headers').process(headers)
                    data = _gap.response('body').process(data)
                }

                res._headers = {}
                res._headerNames = {}
                res.set(headers)

                tmp(data)
            }

            next()
        }
    }
}

module.exports = Versioner
module.exports.Bridge = Bridge
module.exports.Gap = Gap
module.exports.InvalidOperationError = Errors.InvalidOperationError
module.exports.FormattingError = Errors.FormattingError<commit_msg>fixed bug causing gaps to be traversed in reverse order<commit_after>'use strict'

const Bridge = require('./lib/Bridge')
const Errors = require('./lib/Errors')
const fs = require('fs')
const Gap = require('./lib/Gap')
const Operations = require('./lib/Operations')

/**
 * TODO
 *      - Finish unit testing
 *      - Ability to define arrays in url for creating multiple identical gaps
 *      - Ability to define operation sets within a bridge to be references by gaps
 *      - Multi-file support
 *      - Response status can be set or mapped with an object in gap response?
 */

class Versioner {

    /**
     * Constructor
     *
     * @param {object} options option hash
     * @param {string} options.path optional, path to a json file containing an api-bridge config
     * @param {Bridge[]} options.bridges optional, any programatically generated Bridges
     */
    constructor({ path, bridges = [] } = {}) {
        this.bridges = []

        if (!Array.isArray(bridges)) {
            bridges = [bridges]
        }

        if (typeof path === 'string') {
            bridges = bridges.concat(this.loadFromFile(path))
        }

        this.addBridges(bridges)
    }

    /**
     * Adds new API bridges to the array of ordered bridges
     *
     * @param {(Bridge|Bridge[])} bridge
     *
     * @throws {TypeError}
     */
    addBridges(bridges = []) {
        if (!Array.isArray(bridges)) bridges = [bridges]

        for (let _bridge of bridges) {
            if (!_bridge instanceof Bridge) {
                throw new TypeError('Bridges must be instances of the Bridge class')
            }

            this.bridges.push(_bridge)
        }

        this.bridges.sort((a, b) => a.compareVersions(b))
    }

    /**
     * Express middleware for automatically applying formatting
     * to the server's error responses
     *
     * @see ExpressJS middleware error handler
     *
     * @param {*} err error object
     * @param {express.Request} req
     * @param {express.Response} res
     * @param {function} next
     *
     * @returns {function[]} Middlewares in ascending order of version
     */
    errorHandler() {
        return (err, req, res, next) => {
            let headers = {}
            for (let key of Object.keys(res._headerNames)) {
                headers[res._headerNames[key]] = res._headers[key]
            }

            for (let _gap of res.__versioner_parameters.response_gaps) {
                // TODO test if these break when not set on the Gap
                _gap.error('headers').process(headers)
                err = _gap.error('body').process(err)
            }

            res._headers = {}
            res._headerNames = {}
            res.set(headers)

            res.__versioner_parameters.skip_formatting = true
            res.send(err) // 2nd argument for skipping response formatting
        }
    }

    /**
     * Generates API bridges from the given json file
     *
     * @param {string} path location of json file
     *
     * @returns {Bridge[]}
     * @throws {FormattingError} errors thrown for invalid file formatting
     * @throws {SyntaxError} errors thrown parsing JSON
     * @throws {Error} errors thrown by file io
     */
    loadFromFile(path) {
        let data = JSON.parse(fs.readFileSync(path))
        let bridges = []

        if (!Array.isArray(data)) data = [data]

        for (let _data of data) {
            if (!typeof _data === 'object' || Array.isArray(_data)) {
                throw new Error.FormattingError('Bridges must be defined as objects, got: ' + JSON.stringify(_data))
            }

            let _bridge = new Bridge({
                version: _data.version,
                description: _data.description
            })

            for (let _gap of _data.gaps) {
                _bridge.addGap(new Gap(_gap))
            }

            bridges.push(_bridge)
        }

        return bridges
    }

    /**
     * Express middlewares for automatically applying formatting
     * to the server's requests and responses
     *
     * @returns {function[]} Middlewares in ascending order of version
     */
    middleware() {
        return (req, res, next) => {
            res.__versioner_parameters = {
                response_gaps: [] // Will be in reverse order from bridges
            }

            for (let bridge of this.bridges) {
                if (bridge.compareVersions(res.locals.version) <= 0) continue

                let gap = bridge.getGap(req.method, req.path)
                if (gap === undefined) continue
                if (gap.shouldReply(res)) {
                    // Response was sent from inside Gap#shouldReply
                    return
                }

                // TODO test if these break when not set on the Gap
                gap.request('headers').process(req.headers)
                gap.request('body').process(req.body)
                gap.request('query').process(req.query)
                gap.request('params').process(req.params)

                res.__versioner_parameters.response_gaps.push(gap)
            }

            let tmp = res.send.bind(res)
            res.send = function(data) {
                if (res.__versioner_parameters.skip_formatting) {
                    return tmp(data)
                }
                let headers = {}
                for (let key of Object.keys(res._headerNames)) {
                    headers[res._headerNames[key]] = res._headers[key]
                }

                for (let _gap of res.__versioner_parameters.response_gaps) {
                    headers = _gap.response('headers').process(headers)
                    data = _gap.response('body').process(data)
                }

                res._headers = {}
                res._headerNames = {}
                res.set(headers)

                tmp(data)
            }

            next()
        }
    }
}

module.exports = Versioner
module.exports.Bridge = Bridge
module.exports.Gap = Gap
module.exports.InvalidOperationError = Errors.InvalidOperationError
module.exports.FormattingError = Errors.FormattingError<|endoftext|>


<commit_before>/**
 *
 * @source: https://github.com/voxity/voxity-chrome-extension/blob/master/index.js
 *
 * @licstart  The following is the entire license notice for the
 *  JavaScript code in this page.
 *
 * Copyright (C) 2014  Voxity
 *
 *
 * The JavaScript code in this page is free software: you can
 * redistribute it and/or modify it under the terms of the GNU
 * General Public License (GNU GPL) as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option)
 * any later version.  The code is distributed WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.
 *
 * As additional permission under GNU GPL version 3 section 7, you
 * may distribute non-source (e.g., minimized or compacted) forms of
 * that code without the copy of the GNU GPL normally required by
 * section 4, provided you include this license notice and a URL
 * through which recipients can access the Corresponding Source.
 *
 * @licend  The above is the entire license notice
 * for the JavaScript code in this page.
 *
 */

var base_url = 'https://api.voxity.fr';

var gh = (function() {
  'use strict';

  var access_token;

  var tokenFetcher = (function() {
    var clientID = 'ch2NtN3S25ImoYHaSDsr';
    var redirectUri = chrome.identity.getRedirectURL();
    var redirectRe = new RegExp(redirectUri + '[#\?](.*)');
    access_token = null;

    return {
      getToken: function(interactive, callback) {
        // In case we already have an access_token cached, simply return it.
        var options;

        chrome.storage.sync.get({
          access_token: null,
        }, function(items) {
          access_token = items.access_token;
          options = {
            'interactive': interactive,
            url: base_url + '/api/v1/dialog/authorize?client_id=' + clientID +
                '&response_type=token' +
                '&redirect_uri=' + redirectUri
          };
          if(access_token) {
            callback(null, access_token);
            return;
          }
          chrome.identity.launchWebAuthFlow(options, function(responseUri) {
            if (chrome.runtime.lastError) {
              callback(new Error(chrome.runtime.lastError));
              return;
            }
            // Upon success the response is appended to redirectUri, e.g.
            // https://{app_id}.chromiumapp.org/provider_cb#access_token={value}
            //     &refresh_token={value}
            // or:
            // https://{app_id}.chromiumapp.org/provider_cb#code={value}
            var matches = responseUri.match(redirectRe);
            if (matches && matches.length > 1)
              handleProviderResponse(parseRedirectFragment(matches[1]));
            else
              callback(new Error('Invalid redirect URI'));
          });
        });

        function parseRedirectFragment(fragment) {
          var pairs = fragment.split(/&/);
          var values = {};

          pairs.forEach(function(pair) {
            var nameval = pair.split(/=/);
            values[nameval[0]] = nameval[1];
          });

          return values;
        }

        function handleProviderResponse(values) {
          if (values.hasOwnProperty('access_token')) {
            setAccessToken(values.access_token);
          } else {
            callback(new Error('No access_token available.'));
          }
        }

        function setAccessToken(token) {
          access_token = token;
          chrome.storage.sync.set({
            access_token: token
          }, function() {
            callback(null, access_token);
          });
        }
      },

      removeCachedToken: function(token_to_remove) {
        if (access_token === token_to_remove) {
          chrome.storage.sync.remove("access_token"); //when the token is expired we must delete it from the storage in goal to ask a new one
          access_token = null;
        }
      }
    };
  })();

  function xhrWithAuth(method, url, params, interactive, callback) {
    var retry = true;
    getToken();

    function getToken() {
      tokenFetcher.getToken(interactive, function(error, token) {
        if (error) {
          callback(error);
          return;
        }
        access_token = token;
        requestStart();
      });
    }

    function requestStart() {
      var xhr = new XMLHttpRequest();
      var requestBody = params;
      xhr.open(method, url);
      xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
      xhr.setRequestHeader('Authorization', 'Bearer ' + access_token);
      xhr.onload = requestComplete;
      xhr.send(requestBody);
    }

    function requestComplete() {
      // if ( && this.response === "Unauthorized")
      if (this.status == 401 && retry) {
        retry = false;
        tokenFetcher.removeCachedToken(access_token);
        access_token = null;
        getToken();
      } else {
        callback(null, this.status, this.response);
      }
    }
  }

  function interactiveSignIn() {
    tokenFetcher.getToken(true, function(error, access_token) {

    });
  }

  return {
    signIn: function () {
      interactiveSignIn();
    },
    makeCall: function (exten) {
      var message = {
        action: base_url + '/api/v1/channel',
        method: "POST",
        parameters: JSON.stringify({'exten': exten})
      };
      function onCallSuccess(val, status, response) {
        response = JSON.parse(response);
        var title = null;
        var message = null;

        if(response.status === 1){
          title = 'Demande validée';
          message = 'Votre téléphone va sonner d\'ici quelques instants.';
        }else{
          title = response.data.title;
          message = response.data.message;
        }

        var notification_vars = {
          type: 'basic',
          iconUrl: 'icon128.png',
          title: title,
          message: message
          }

        chrome.notifications.getAll(function(notifications){
          if(notifications.clicktocall){
            chrome.notifications.clear('clicktocall', function(e){});
          }
        })

        chrome.notifications.create(
          'clicktocall',
          notification_vars,
          function(n) {} );
      }
      xhrWithAuth(message.method, message.action, message.parameters, true, onCallSuccess);
    }
  };
})();

chrome.runtime.onInstalled.addListener(function() {
  var context = "selection";
  var title = "Appeler le numéro <%s>";
  var id = chrome.contextMenus.create({"title": title, "contexts":[context],
                                         "id": "context" + context});
});

chrome.contextMenus.onClicked.addListener(onClickHandler);

function onClickHandler(info, tab) {
  try{
    if (info.selectionText) {
      gh.makeCall(info.selectionText);
    }
  } catch(ex){
    console.log(ex);
    gh.signIn();
  }
};
<commit_msg>adding a click listener on the plugin icon that redirects to client.voxity.fr<commit_after>/**
 *
 * @source: https://github.com/voxity/voxity-chrome-extension/blob/master/index.js
 *
 * @licstart  The following is the entire license notice for the
 *  JavaScript code in this page.
 *
 * Copyright (C) 2014  Voxity
 *
 *
 * The JavaScript code in this page is free software: you can
 * redistribute it and/or modify it under the terms of the GNU
 * General Public License (GNU GPL) as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option)
 * any later version.  The code is distributed WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.
 *
 * As additional permission under GNU GPL version 3 section 7, you
 * may distribute non-source (e.g., minimized or compacted) forms of
 * that code without the copy of the GNU GPL normally required by
 * section 4, provided you include this license notice and a URL
 * through which recipients can access the Corresponding Source.
 *
 * @licend  The above is the entire license notice
 * for the JavaScript code in this page.
 *
 */

var base_url = 'https://api.voxity.fr';

var gh = (function() {
  'use strict';

  var access_token;

  var tokenFetcher = (function() {
    var clientID = 'ch2NtN3S25ImoYHaSDsr';
    var redirectUri = chrome.identity.getRedirectURL();
    var redirectRe = new RegExp(redirectUri + '[#\?](.*)');
    access_token = null;

    return {
      getToken: function(interactive, callback) {
        // In case we already have an access_token cached, simply return it.
        var options;

        chrome.storage.sync.get({
          access_token: null,
        }, function(items) {
          access_token = items.access_token;
          options = {
            'interactive': interactive,
            url: base_url + '/api/v1/dialog/authorize?client_id=' + clientID +
                '&response_type=token' +
                '&redirect_uri=' + redirectUri
          };
          if(access_token) {
            callback(null, access_token);
            return;
          }
          chrome.identity.launchWebAuthFlow(options, function(responseUri) {
            if (chrome.runtime.lastError) {
              callback(new Error(chrome.runtime.lastError));
              return;
            }
            // Upon success the response is appended to redirectUri, e.g.
            // https://{app_id}.chromiumapp.org/provider_cb#access_token={value}
            //     &refresh_token={value}
            // or:
            // https://{app_id}.chromiumapp.org/provider_cb#code={value}
            var matches = responseUri.match(redirectRe);
            if (matches && matches.length > 1)
              handleProviderResponse(parseRedirectFragment(matches[1]));
            else
              callback(new Error('Invalid redirect URI'));
          });
        });

        function parseRedirectFragment(fragment) {
          var pairs = fragment.split(/&/);
          var values = {};

          pairs.forEach(function(pair) {
            var nameval = pair.split(/=/);
            values[nameval[0]] = nameval[1];
          });

          return values;
        }

        function handleProviderResponse(values) {
          if (values.hasOwnProperty('access_token')) {
            setAccessToken(values.access_token);
          } else {
            callback(new Error('No access_token available.'));
          }
        }

        function setAccessToken(token) {
          access_token = token;
          chrome.storage.sync.set({
            access_token: token
          }, function() {
            callback(null, access_token);
          });
        }
      },

      removeCachedToken: function(token_to_remove) {
        if (access_token === token_to_remove) {
          chrome.storage.sync.remove("access_token"); //when the token is expired we must delete it from the storage in goal to ask a new one
          access_token = null;
        }
      }
    };
  })();

  function xhrWithAuth(method, url, params, interactive, callback) {
    var retry = true;
    getToken();

    function getToken() {
      tokenFetcher.getToken(interactive, function(error, token) {
        if (error) {
          callback(error);
          return;
        }
        access_token = token;
        requestStart();
      });
    }

    function requestStart() {
      var xhr = new XMLHttpRequest();
      var requestBody = params;
      xhr.open(method, url);
      xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
      xhr.setRequestHeader('Authorization', 'Bearer ' + access_token);
      xhr.onload = requestComplete;
      xhr.send(requestBody);
    }

    function requestComplete() {
      // if ( && this.response === "Unauthorized")
      if (this.status == 401 && retry) {
        retry = false;
        tokenFetcher.removeCachedToken(access_token);
        access_token = null;
        getToken();
      } else {
        callback(null, this.status, this.response);
      }
    }
  }

  function interactiveSignIn() {
    tokenFetcher.getToken(true, function(error, access_token) {

    });
  }

  return {
    signIn: function () {
      interactiveSignIn();
    },
    makeCall: function (exten) {
      var message = {
        action: base_url + '/api/v1/channel',
        method: "POST",
        parameters: JSON.stringify({'exten': exten})
      };
      function onCallSuccess(val, status, response) {
        response = JSON.parse(response);
        var title = null;
        var message = null;

        if(response.status === 1){
          title = 'Demande validée';
          message = 'Votre téléphone va sonner d\'ici quelques instants.';
        }else{
          title = response.data.title;
          message = response.data.message;
        }

        var notification_vars = {
          type: 'basic',
          iconUrl: 'icon128.png',
          title: title,
          message: message
          }

        chrome.notifications.getAll(function(notifications){
          if(notifications.clicktocall){
            chrome.notifications.clear('clicktocall', function(e){});
          }
        })

        chrome.notifications.create(
          'clicktocall',
          notification_vars,
          function(n) {} );
      }
      xhrWithAuth(message.method, message.action, message.parameters, true, onCallSuccess);
    }
  };
})();

chrome.runtime.onInstalled.addListener(function() {
  var context = "selection";
  var title = "Appeler le numéro <%s>";
  var id = chrome.contextMenus.create({"title": title, "contexts":[context],
                                         "id": "context" + context});
});

chrome.contextMenus.onClicked.addListener(onClickHandler);

function onClickHandler(info, tab) {
  try{
    if (info.selectionText) {
      gh.makeCall(info.selectionText);
    }
  } catch(ex){
    console.log(ex);
    gh.signIn();
  }
};

chrome.browserAction.onClicked.addListener(function (tab) { //Fired when User Clicks ICON
  chrome.tabs.update({url: "https://client.voxity.fr"});
});
<|endoftext|>


<commit_before>'use strict'

const plugin = require('./package'),
  Noble = require('noble')
var Homebridge, Service, Characteristic

module.exports = function(homebridge) {
  console.log("Homebridge API version: " + homebridge.version)

  Homebridge = homebridge
  Service = homebridge.hap.Service
  Characteristic = homebridge.hap.Characteristic

  homebridge.registerAccessory(plugin.name, "satellite-fan", FanLightAccessory)
}

function trimAddress(address) {
  return address.toLowerCase().replace(/:/g, "")
}

function trimUUID(uuid) {
  return uuid.toLowerCase().replace(/:/g, "").replace(/-/g, "")
}

class FanLightAccessory {

  constructor (log, config) {
    this.log = log

    this.name = config.name || "Ceiling Fan"
    if (!config.address) {
      throw new Error(this.prefix + " Missing mandatory config 'address'")
    }
    this.address = trimAddress(config.address)
    if (!config.ble) {
      throw new Error(this.prefix + " Missing mandatory config 'ble'")
    }
    this.manufacturerPrefix = config.ble.prefix || 0
    if (!config.ble.serviceUUID) {
      throw new Error(this.prefix + " Missing mandatory config 'ble.serviceUUID'")
    }
    this.serviceUUID = trimUUID(config.ble.serviceUUID)
    if (!config.ble.writeCharacteristicUUID) {
      throw new Error(this.prefix + " Missing mandatory config 'ble.writeCharacteristicUUID'")
    }
    this.writeCharacteristicUUID = trimUUID(config.ble && config.ble.writeCharacteristicUUID)
    if (!config.ble.notifyCharacteristicUUID) {
      throw new Error(this.prefix + " Missing mandatory config 'ble.notifyCharacteristicUUID'")
    }
    this.notifyCharacteristicUUID = trimUUID(config.ble && config.ble.notifyCharacteristicUUID)

    this.informationService = this.makeInformationService(config)
    this.fanService = this.makeFanService(config)
    this.lightService = this.makeLightService(config)

    Homebridge.on('didFinishLaunching', this.onDidFinishLaunching.bind(this))
  }

  identify (callback) {
    this.log('Device identified!')
    callback()
  }

  // MARK: -

  onDidFinishLaunching() {
    this.log.info("Received did finish launching")
    Noble.on('stateChange', this.onStateChange.bind(this))
  }

  onStateChange(state) {
    if (state != 'poweredOn') {
      this.log.debug("Stopped scanning: " + state)
      Noble.stopScanning()
    }

    this.log.debug("Started scanning: " + state)
    Noble.startScanning([ this.serviceUUID ], false)
    Noble.on('discover', this.onDiscover.bind(this))
  }

  onDiscover(peripheral) {
    this.log.info('found ' + peripheral.address)
    if (trimAddress(peripheral.address) !== this.address) { return }
    Noble.stopScanning()
  }

  // MARK: -

  makeInformationService(config) {
    const service = new Service.AccessoryInformation()

    service
      .setCharacteristic(Characteristic.Manufacturer, config.device && config.device.manufacturer)
      .setCharacteristic(Characteristic.Model, config.device && config.device.model)
      .setCharacteristic(Characteristic.SerialNumber, config.device && config.device.serial)
      .setCharacteristic(Characteristic.FirmwareRevision, (config.device && config.device.revision) || plugin.version)

    return service
  }

  makeFanService(config) {
    const service = new Service.Fan(this.name)

    service.getCharacteristic(Characteristic.On)
      .on('get', function(callback) {
        callback(null, false)
      })
      .on('set', (function(value, callback) {
        this.log.debug('fan on set to ' + value)
        callback(null)
      }).bind(this))

    service.getCharacteristic(Characteristic.RotationSpeed)
      .on('get', function(callback) {
        callback(null, 100)
      })
      .on('set', (function(value, callback) {
        this.log.debug('fan speed set to ' + value)
        callback(null)
      }).bind(this))

    return service
  }

  makeLightService(config) {
    const service = new Service.Lightbulb(this.name)

    service.getCharacteristic(Characteristic.On)
      .on('get', function(callback) {
        callback(null, false)
      })
      .on('set', (function(value, callback) {
        this.log.debug('light on set to ' + value)
        callback(null)
      }).bind(this))

    service.getCharacteristic(Characteristic.Brightness)
      .on('get', function(callback) {
        callback(null, 100)
      })
      .on('set', (function(value, callback) {
        this.log.debug('light bright set to ' + value)
        callback(null)
      }).bind(this))

    return service
  }

  getServices () {
    return [ this.informationService, this.fanService, this.lightService ]
  }

}
<commit_msg>Implement initial connect/get/set<commit_after>'use strict'

const plugin = require('./package'),
  Noble = require('noble'),
  EventEmitter = require('events').EventEmitter
var Homebridge, Service, Characteristic

module.exports = function(homebridge) {
  console.log("Homebridge API version: " + homebridge.version)

  Homebridge = homebridge
  Service = homebridge.hap.Service
  Characteristic = homebridge.hap.Characteristic

  homebridge.registerAccessory(plugin.name, "satellite-fan", FanLightAccessory)
}

function trimAddress(address) {
  return address.toLowerCase().replace(/:/g, "")
}

function trimUUID(uuid) {
  return uuid.toLowerCase().replace(/:/g, "").replace(/-/g, "")
}

// MARK: -

class FanRequest {

  writeInto(buffer) {
    throw new TypeError('Must override method')
  }

  toPrefixedBuffer(prefix) {
    var buffer
    if (prefix > 0) {
      buffer = new Buffer(13)
      buffer.writeUInt8(prefix)
      this.writeInto(buffer.slice(1))
    } else {
      buffer = new Buffer(12)
      this.writeInto(buffer)
    }

    const checksum = buffer.slice(0, buffer.length - 1).reduce(function(a, b){
      return a + b
    }, 0) & 255

    buffer.writeUInt8(checksum, buffer.length - 1)
    return buffer
  }
}

class FanGetStateRequest extends FanRequest {
  writeInto(buffer) {
    buffer.fill(0)
    buffer.writeUInt8(160)
  }
}

Math.clamp = function(number, min, max) {
  return Math.max(min, Math.min(number, max))
}

class FanUpdateLightRequest extends FanRequest {

  constructor(isOn, level) {
    super()
    this.on = isOn ? 1 : 0
    this.level = Math.clamp(level, 0, 100)
  }

  writeInto(buffer) {
    buffer.fill(0)
    buffer.writeUInt8(161)
    buffer.writeUInt8(255, 4)
    buffer.writeUInt8(100, 5)
    buffer.writeUInt8((this.on << 7) | this.level, 6)
    buffer.fill(255, 7, 10)
  }

}

class FanUpdateLevelRequest extends FanRequest {

  constructor(level) {
    super()
    this.level = Math.clamp(level, 0, 3)
  }

  writeInto(buffer) {
    buffer.fill(0)
    buffer.writeUInt8(161)
    buffer.writeUInt8(this.level, 4)
    buffer.fill(255, 5, 10)
  }

}

class FanResponse {

  static fromPrefixedBuffer(prefix, buffer) {
    if (prefix > 0) {
      buffer = buffer.slice(1)
    }

    if (buffer.readUInt8(0) != 176) { return null }
    const response = new FanResponse()

    const windVelocity = buffer.readUInt8(2)
    response.supportsFanReversal = (windVelocity & 0b00100000) != 0
    response.maximumFanLevel     =  windVelocity & 0b00011111

    const currentWindVelocity = buffer.readUInt8(4)
    response.isFanReversed     = (currentWindVelocity & 0b10000000) != 0
    response.fanLevel          =  currentWindVelocity & 0b00011111

    const currentBrightness = buffer.readUInt8(6)
    response.lightIsOn       = (currentBrightness & 0b10000000) != 0
    response.lightBrightness = (currentBrightness & 0b01111111)

    return response
  }

}

// MARK: -

class FanLightAccessory extends EventEmitter {

  constructor (log, config) {
    super()
    this.log = log
    this.name = config.name || "Ceiling Fan"
    if (!config.address) {
      throw new Error(this.prefix + " Missing mandatory config 'address'")
    }
    this.address = trimAddress(config.address)
    if (!config.ble) {
      throw new Error(this.prefix + " Missing mandatory config 'ble'")
    }
    this.manufacturerPrefix = config.ble.prefix || 0
    if (!config.ble.serviceUUID) {
      throw new Error(this.prefix + " Missing mandatory config 'ble.serviceUUID'")
    }
    this.serviceUUID = trimUUID(config.ble.serviceUUID)
    if (!config.ble.writeCharacteristicUUID) {
      throw new Error(this.prefix + " Missing mandatory config 'ble.writeCharacteristicUUID'")
    }
    this.writeCharacteristicUUID = trimUUID(config.ble && config.ble.writeCharacteristicUUID)
    this.writeCharacteristic = null
    if (!config.ble.notifyCharacteristicUUID) {
      throw new Error(this.prefix + " Missing mandatory config 'ble.notifyCharacteristicUUID'")
    }
    this.notifyCharacteristicUUID = trimUUID(config.ble && config.ble.notifyCharacteristicUUID)
    this.notifyCharacteristic = null

    this.informationService = this.makeInformationService(config)
    this.fanService = this.makeFanService(config)
    this.lightService = this.makeLightService(config)

    this.maximumFanLevel = 3

    Homebridge.on('didFinishLaunching', this.onDidFinishLaunching.bind(this))
  }

  identify (callback) {
    this.log('Device identified!')
    callback()
  }

  // MARK: -

  onDidFinishLaunching() {
    this.log.info("Received did finish launching")
    Noble.on('stateChange', this.onStateChange.bind(this))
  }

  onStateChange(state) {
    if (state != 'poweredOn') {
      this.log.debug("Stopped scanning: " + state)
      Noble.stopScanning()
    }

    this.log.debug("Started scanning: " + state)
    Noble.startScanning([ this.serviceUUID ], false)
    Noble.on('discover', this.onDiscover.bind(this))
  }

  onDiscover(peripheral) {
    this.log.info('found ' + peripheral.address)
    if (trimAddress(peripheral.address) !== this.address) {
      this.log.debug("Ignoring " + peripheral.address + " (RSSI " + peripheral.rssi + "dB)")
      return
    }

    this.log.debug("Found " + peripheral.address + " (RSSI " + peripheral.rssi + "dB)")
    peripheral.connect(function(error) {
      this.onConnect(error, peripheral)
    }.bind(this))
  }

  onConnect(error, peripheral) {
    if (error) {
      this.log.error("Connecting to " + peripheral.address + " failed: " + error)
    }
    this.log.debug("Connected to " + peripheral.address)

    peripheral.once('disconnect', function(error) { this.onDisconnect(peripheral, error) }.bind(this))
    peripheral.discoverSomeServicesAndCharacteristics([ this.serviceUUID ], [ this.writeCharacteristicUUID, this.notifyCharacteristicUUID ], this.onDiscoverCharacteristics.bind(this));
  }

  onDiscoverCharacteristics(error, services, characteristics) {
    if (error) {
      this.log.error(this.prefix, "Discover services failed: " + error)
      return
    }

    this.writeCharacteristic = characteristics[0]
    this.notifyCharacteristic = characteristics[1]

    this.notifyCharacteristic.on('data', this.onNotify.bind(this));
    this.notifyCharacteristic.subscribe(function (error) {
      if (error) {
        this.log.warn("Subscribe to notify characteristic failed")
      }
    }.bind(this));
  }

  onDisconnect(peripheral, error) {
    peripheral.removeAllListeners()

    if (this.writeCharacteristic) {
      this.writeCharacteristic.removeAllListeners()
    }
    this.writeCharacteristic = null

    if (this.notifyCharacteristic) {
      this.notifyCharacteristic.unsubscribe(null)
      this.notifyCharacteristic.removeAllListeners()
    }
    this.notifyCharacteristic = null

    this.emit('stateChange', error || new Error('Not connected'))

    this.log.info("Disconnected")
    Noble.startScanning([ this.serviceUUID ], false)
  }

  onNotify(data, isNotification) {
    if (!isNotification) { return }
    const response = FanResponse.fromPrefixedBuffer(this.manufacturerPrefix, data)
    if (!response) { return }

    this.maximumFanLevel = response.maximumFanLevel
    this.fanService.getCharacteristic(Characteristic.On).updateValue(response.fanLevel != 0)
    this.fanService.getCharacteristic(Characteristic.RotationSpeed).updateValue(Math.floor((response.fanLevel / response.maximumFanLevel) * 100))
    this.lightService.getCharacteristic(Characteristic.On).updateValue(response.lightIsOn)
    this.lightService.getCharacteristic(Characteristic.Brightness).updateValue(response.lightBrightness)

    this.emit('updateState', null)
  }

  // MARK: -

  sendCommand(command, callback) {
    if (!this.notifyCharacteristic || !this.writeCharacteristic) {
      callback(new Error('Not connected'))
      return
    }

    const buffer = command.toPrefixedBuffer(this.manufacturerPrefix)
    this.writeCharacteristic.write(buffer, false, callback)
  }

  getNextFanState(callback) {
    const shouldSend = this.listeners('updateState').length == 0

    this.once('updateState', callback)

    if (shouldSend) {
      const command = new FanGetStateRequest()
      this.sendCommand(command, function(error){
        if (!error) { return }
        this.emit('updateState', error)
      }.bind(this))
    }
  }

  getFanOn(callback) {
    this.getNextFanState(function(error){
      if (error) {
        callback(error, null)
      } else {
        callback(null, this.fanService.getCharacteristic(Characteristic.On).value)
      }
    }.bind(this))
  }

  setFanOn(newValue, callback) {
    const level = newValue ? this.fanService.getCharacteristic(Characteristic.RotationSpeed).value : 0
    this.log.info('Fan on to level ' + newValue)
    const command = new FanUpdateLevelRequest(level)
    this.sendCommand(command, callback)
  }

  getFanRotationSpeed(callback) {
    this.getNextFanState(function(error){
      if (error) {
        callback(error, null)
      } else {
        callback(null, this.fanService.getCharacteristic(Characteristic.RotationSpeed).value)
      }
    }.bind(this))
  }

  setFanRotationSpeed(newValue, callback) {
    const level = Math.floor(newValue * (this.maximumFanLevel / 100))
    this.log.info('Fan speed: ' + level)

    const command = new FanUpdateLevelRequest(level)
    this.sendCommand(command, callback)
  }

  getLightOn(callback) {
    this.getNextFanState(function(error){
      if (error) {
        callback(error, null)
      } else {
        callback(null, this.lightService.getCharacteristic(Characteristic.On).value)
      }
    }.bind(this))
  }

  setLightOn(newValue, callback) {
    this.log.info('Light on: ' + newValue)
    const brightness = this.lightService.getCharacteristic(Characteristic.Brightness).value
    const command = new FanUpdateLightRequest(newValue, brightness)
    this.sendCommand(command, callback)
  }

  getLightBrightness(callback) {
    this.getNextFanState(function(error){
      if (error) {
        callback(error, null)
      } else {
        callback(null, this.lightService.getCharacteristic(Characteristic.Brightness).value)
      }
    }.bind(this))
  }

  setLightBrightness(newValue, callback) {
    this.log.info('Light brightness: ' + newValue)
    const isOn = this.lightService.getCharacteristic(Characteristic.On).value
    const command = new FanUpdateLightRequest(isOn, newValue)
    this.sendCommand(command, callback)
  }

  // MARK: -

  makeInformationService(config) {
    const service = new Service.AccessoryInformation()

    service
      .setCharacteristic(Characteristic.Manufacturer, config.device && config.device.manufacturer)
      .setCharacteristic(Characteristic.Model, config.device && config.device.model)
      .setCharacteristic(Characteristic.SerialNumber, config.device && config.device.serial)
      .setCharacteristic(Characteristic.FirmwareRevision, (config.device && config.device.revision) || plugin.version)

    return service
  }

  makeFanService(config) {
    const service = new Service.Fan(this.name)

    service.getCharacteristic(Characteristic.On)
      .on('get', this.getFanOn.bind(this))
      .on('set', this.setFanOn.bind(this))

    service.getCharacteristic(Characteristic.RotationSpeed)
      .on('get', this.getFanRotationSpeed.bind(this))
      .on('set', this.setFanRotationSpeed.bind(this))

    return service
  }

  makeLightService(config) {
    const service = new Service.Lightbulb(this.name)

    service.getCharacteristic(Characteristic.On)
      .on('get', this.getLightOn.bind(this))
      .on('set', this.setLightOn.bind(this))

    service.getCharacteristic(Characteristic.Brightness)
      .on('get', this.getLightBrightness.bind(this))
      .on('set', this.setLightBrightness.bind(this))

    return service
  }

  getServices () {
    return [ this.informationService, this.fanService, this.lightService ]
  }

}
<|endoftext|>


<commit_before>/* jshint node: true */
'use strict';

var path = require('path'),
  util = require('util'),
  extend = util._extend,
  mergeTrees = require('broccoli-merge-trees'),
  Funnel = require('broccoli-funnel');

var defaultOptions = {
  importBootstrapTheme: false,
  importBootstrapCSS: true,
  importBootstrapFont: true
};

module.exports = {
  name: 'ember-bootstrap',

  included: function included(app) {
    // workaround for https://github.com/ember-cli/ember-cli/issues/3718
    if (typeof app.import !== 'function' && app.app) {
      app = app.app;
    }
    this.app = app;

    var options = extend(defaultOptions, app.options['ember-bootstrap']);
    var bootstrapPath = path.join(app.bowerDirectory, 'bootstrap/dist');

    // Import css from bootstrap
    if (options.importBootstrapCSS) {
      app.import(path.join(bootstrapPath, 'css/bootstrap.css'));
      app.import(path.join(bootstrapPath, 'css/bootstrap.css.map'), {destDir: 'assets'});
    }

    if (options.importBootstrapTheme) {
      app.import(path.join(bootstrapPath, 'css/bootstrap-theme.css'));
    }

    // Import glyphicons
    if (options.importBootstrapFont) {
      app.import(path.join(bootstrapPath, 'fonts/glyphicons-halflings-regular.eot'), {destDir: '/fonts'});
      app.import(path.join(bootstrapPath, 'fonts/glyphicons-halflings-regular.svg'), {destDir: '/fonts'});
      app.import(path.join(bootstrapPath, 'fonts/glyphicons-halflings-regular.ttf'), {destDir: '/fonts'});
      app.import(path.join(bootstrapPath, 'fonts/glyphicons-halflings-regular.woff'), {destDir: '/fonts'});
      app.import(path.join(bootstrapPath, 'fonts/glyphicons-halflings-regular.woff2'), {destDir: '/fonts'});
    }

    if (!process.env.EMBER_CLI_FASTBOOT) {
      app.import('vendor/transition.js');
    }
  },

  treeForStyles: function treeForStyles(tree) {
    var styleTrees = [];

    if (this.app.project.findAddonByName('ember-cli-less')) {
      var lessTree = new Funnel(path.join(this.app.bowerDirectory, 'bootstrap/less'), {
        destDir: 'ember-bootstrap'
      });
      styleTrees.push(lessTree);
    }

    if (tree) {
      styleTrees.push(tree);
    }

    return mergeTrees(styleTrees, { overwrite: true });
  }
};
<commit_msg>Fix(Build): import map file along with bootstrap-theme.css<commit_after>/* jshint node: true */
'use strict';

var path = require('path'),
  util = require('util'),
  extend = util._extend,
  mergeTrees = require('broccoli-merge-trees'),
  Funnel = require('broccoli-funnel');

var defaultOptions = {
  importBootstrapTheme: false,
  importBootstrapCSS: true,
  importBootstrapFont: true
};

module.exports = {
  name: 'ember-bootstrap',

  included: function included(app) {
    // workaround for https://github.com/ember-cli/ember-cli/issues/3718
    if (typeof app.import !== 'function' && app.app) {
      app = app.app;
    }
    this.app = app;

    var options = extend(defaultOptions, app.options['ember-bootstrap']);
    var bootstrapPath = path.join(app.bowerDirectory, 'bootstrap/dist');

    // Import css from bootstrap
    if (options.importBootstrapCSS) {
      app.import(path.join(bootstrapPath, 'css/bootstrap.css'));
      app.import(path.join(bootstrapPath, 'css/bootstrap.css.map'), {destDir: 'assets'});
    }

    if (options.importBootstrapTheme) {
      app.import(path.join(bootstrapPath, 'css/bootstrap-theme.css'));
      app.import(path.join(bootstrapPath, 'css/bootstrap-theme.css.map'), {destDir: 'assets'});
    }

    // Import glyphicons
    if (options.importBootstrapFont) {
      app.import(path.join(bootstrapPath, 'fonts/glyphicons-halflings-regular.eot'), {destDir: '/fonts'});
      app.import(path.join(bootstrapPath, 'fonts/glyphicons-halflings-regular.svg'), {destDir: '/fonts'});
      app.import(path.join(bootstrapPath, 'fonts/glyphicons-halflings-regular.ttf'), {destDir: '/fonts'});
      app.import(path.join(bootstrapPath, 'fonts/glyphicons-halflings-regular.woff'), {destDir: '/fonts'});
      app.import(path.join(bootstrapPath, 'fonts/glyphicons-halflings-regular.woff2'), {destDir: '/fonts'});
    }

    if (!process.env.EMBER_CLI_FASTBOOT) {
      app.import('vendor/transition.js');
    }
  },

  treeForStyles: function treeForStyles(tree) {
    var styleTrees = [];

    if (this.app.project.findAddonByName('ember-cli-less')) {
      var lessTree = new Funnel(path.join(this.app.bowerDirectory, 'bootstrap/less'), {
        destDir: 'ember-bootstrap'
      });
      styleTrees.push(lessTree);
    }

    if (tree) {
      styleTrees.push(tree);
    }

    return mergeTrees(styleTrees, { overwrite: true });
  }
};
<|endoftext|>


<commit_before>var path = require('path'),
    fs = require('fs'),
    express = require('express'),
    nodeca = require('nodeca-lib'),
    Promise = nodeca.Promise,
    $$ = nodeca.Utilities;

// Outputs error to stderr and terminates process with given code
var halt = function halt(err, code) {
  console.error(err.stack.toString());
  process.exit(code || 1);
};

// register unexpected exceptions handler
process.on('uncaughtException', function(err) {
  console.log('!!! UNCAUGHT EXCEPTION !!!');
  halt(err);
});

// error codes
var ERR_INIT    = 128;
var ERR_START   = 129;

var app, // Application instance
    cfg; // Application config

// initialize application
try {
  app = new nodeca.Application(__dirname, function bootstrapper() {
    this.addHook('schemas-loaded', function () {
      this.log("Schemas were loaded! Cool!");
    });
  });

  cfg = app.readConfig('application', app.env);

  app.init(cfg);
} catch (err) {
  // we need to have some logger available before init, to be able write errors
  // upon init stage (for example when application is managed by cluster).
  halt(err, ERR_START);
}

// application is ready to start
app.ready(function (err) {
  if (err) {
    // spmething went wrong during app initializtion
    app.fail(halt);
    return;
  }

  // create server and run it
  var server = express.createServer();

  // set view engine and some default options
  server.set('view engine', 'jade');
  server.set('view options', {layout: 'layouts/default'});

  // set request handlers chain
  server.use(express.static(path.join(__dirname, 'public')));
  server.use(express.bodyParser());
  server.use(express.methodOverride());
  server.use(express.cookieParser());

  server.helper('__', function __(str, params, context) {
    // basically we can get req and res from `this` afaik
    return str;
  });

  server.use(server.router);

  // last handler starts new cycle with error
  server.use(function RouteNotFound(req, res, next) {
    var err  = new Error('Not Found');
    err.code = 404;
    return next(err);
  });

  // register rerror handler should be configured
  server.error(function(err, req, res, next) {
    app.log.error(err, req).error(err.stack);

    req.originalController  = req.controller;
    req.originalAction      = req.action;
    req.controller          = 'errors';
    req.action              = 'error';
    req.error               = err;

    app.dispatcher.dispatch(req, res, next);
  });

  // start server
  var listen = $$.merge({port: 8000}, app.config.listen);
  server.listen(listen.port, listen.host);
});

////////////////////////////////////////////////////////////////////////////////
// vim:ts=2:sw=2
////////////////////////////////////////////////////////////////////////////////
<commit_msg>Fixed starter bugs<commit_after>var path = require('path'),
    fs = require('fs'),
    express = require('express'),
    nodeca = require('nodeca-lib'),
    Promise = nodeca.Promise,
    $$ = nodeca.Utilities;

// Outputs error to stderr and terminates process with given code
var halt = function halt(err, code) {
  console.error(err.stack.toString());
  process.exit(code || 1);
};

// register unexpected exceptions handler
process.on('uncaughtException', function(err) {
  console.log('!!! UNCAUGHT EXCEPTION !!!');
  halt(err);
});

// error codes
var ERR_INIT    = 128;
var ERR_START   = 129;

var app, // Application instance
    cfg; // Application config

// initialize application
try {
  app = new nodeca.Application(__dirname, function bootstrapper() {
    this.addHook('schemas-loaded', function () {
      this.log.debug("Schemas were loaded! Cool!");
    });
  });

  cfg = app.readConfig('application', app.env);

  app.init(cfg);
} catch (err) {
  // we need to have some logger available before init, to be able write errors
  // upon init stage (for example when application is managed by cluster).
  halt(err, ERR_START);
}

// application is ready to start
app.ready(function (err) {
  if (err) {
    // spmething went wrong during app initializtion
    halt(err, ERR_START);
    return;
  }

  // create server and run it
  var server = express.createServer();

  // set view engine and some default options
  server.set('view engine', 'jade');
  server.set('view options', {layout: 'layouts/default'});

  // set request handlers chain
  server.use(express.static(path.join(__dirname, 'public')));
  server.use(express.bodyParser());
  server.use(express.methodOverride());
  server.use(express.cookieParser());

  server.helpers({
    // basically we can get req and res from `this` afaik
    __: function __(str, params, context) { return str; }
  });

  server.use(server.router);

  // last handler starts new cycle with error
  server.use(function RouteNotFound(req, res, next) {
    var err  = new Error('Not Found');
    err.code = 404;
    return next(err);
  });

  // register rerror handler should be configured
  server.error(function(err, req, res, next) {
    app.log.error(err, req).error(err.stack);

    req.originalController  = req.controller;
    req.originalAction      = req.action;
    req.controller          = 'errors';
    req.action              = 'error';
    req.error               = err;

    app.dispatcher.dispatch(req, res, next);
  });

  // start server
  var listen = $$.merge({port: 8000}, app.config.listen);
  server.listen(listen.port, listen.host);
});

////////////////////////////////////////////////////////////////////////////////
// vim:ts=2:sw=2
////////////////////////////////////////////////////////////////////////////////
<|endoftext|>


<commit_before>var bodyParser = require('body-parser');
var express = require('express');
var app = express();

var socket = require("./socket");
var post = require("./post")

var argv = require('minimist')(process.argv.slice(2));

/*
var cors = require('cors');

// use it before all route definitions
app.use(cors({origin: 'http://localhost:8080'}));*/

app.use(bodyParser.json());

app.set('port', (process.env.PORT || process.env.EM_PORT || 5000));

app.use(express.static(__dirname + '/public'));

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

app.get('/swagger/event_mapping.json', function (req, res) {
  var json = require("./modules/core/apiStructure");
  res.send(json);
})

var server = app.listen(app.get('port'), function () {
  console.log('Node app is running on port', app.get('port'));
});

socket.init(server);
post.init(app);
<commit_msg>create path .../changebasemap to change basemaps<commit_after>var bodyParser = require('body-parser');
var express = require('express');
var app = express();

var socket = require("./socket");
var post = require("./post")

var argv = require('minimist')(process.argv.slice(2));

/*
var cors = require('cors');

// use it before all route definitions
app.use(cors({origin: 'http://localhost:8080'}));*/

app.use(bodyParser.json());

app.set('port', (process.env.PORT || process.env.EM_PORT || 5000));

app.use(express.static(__dirname + '/public'));

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

app.get('/swagger/event_mapping.json', function (req, res) {
  var json = require("./modules/core/apiStructure");
  res.send(json);
})

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

var server = app.listen(app.get('port'), function () {
  console.log('Node app is running on port', app.get('port'));
});

socket.init(server);
post.init(app);
<|endoftext|>