<commit_before>/**
 Copyright 2016 IBM. All Rights Reserved.
 Developer: David Thomason
 */

let http = require('http');
let locationUtil = require('./locationUtil'), dateUtil = require('./dateUtil');

/**
 * Provides weather utility to send a request to the Weather Underground
 * and receive get a textual response
 */
let weatherUtil = (function() {

  return {

    /**
     * Uses Weather Underground API, documented: https://www.wunderground.com/weather/api
     *
     * Given, e.g.:
     * WU_API_KEY = <your weather underground API key>
     * condition = "Weather"
     * city = "San Francisco"
     * state = "California" (or "" or null)
     * date = "2016-05-28" (or "current")
     * debug = true or false, prints some console.log statements
     * callback = your callback function, which handles the responses like:
     *   { tell: 'the weather in San Francisco California on Wednesday..' }
     *   { ask: 'Which state?' }
     *
     */
    makeWeatherRequest: function(WU_API_KEY, condition, city, state, date, debug, callback) {
      // Clean up city, state and date input parameters
      condition = condition || 'Weather';

      if (state) {
        // state = state.toProperCase();
        if (!city) city = locationUtil.getProbableCity(state.toProperCase());
      }
      if (!city) {
        let in_state = state ? ' in ' + state : '';
        callback({ask: 'Which city' + in_state + ' are you interested in?'});
      } else {

        city = city.toProperCase();
        let city_param = locationUtil.formatCity(city);
        if (debug) console.log(city_param);

        date = date || 'current';
        let dateObject = date;
        if (!date.dataFeature) dateObject = dateUtil.getDateObject(date);
        if (dateObject.error) weatherResponseCallback(callback, new Error(dateObject.error));
        else {

          if (debug) console.log(dateObject);

          let original_state = state;
          state = locationUtil.getProbableState(city, state);

          // Construct the URL from the input parameters
          let endpoint = 'http://api.wunderground.com/api/' + WU_API_KEY + '/';
          let query = '/q/';
          if (state) query += state + '/';
          query += city_param + '.json';
          let url = endpoint + dateObject.dataFeature + query;
          if (debug) console.log('URL = ' + url);

          // Perform the HTTP Get request
          http.get(url, function(response) {

            let weatherResponseString = '';

            if (response.statusCode != 200)
              weatherResponseCallback(callback, new Error('I\'m sorry, but the Weather Underground request returned a non-200 Response'));

            response.on('data', function(data) {
              weatherResponseString += data;
            });

            response.on('end', function() {
              let weatherResponseObject = JSON.parse(weatherResponseString);
              let interpretation = interpretResponse(weatherResponseObject, city, original_state);
              if (interpretation.error) weatherResponseCallback(callback, new Error(interpretation.error));
              else weatherResponseCallback(callback, null, interpretation, condition, city, state, dateObject, WU_API_KEY, debug);
            });
          }).on('error', function(e) {
            console.error(e);
            weatherResponseCallback(callback, new Error('I\'m sorry, but there was a communication error with the Weather Underground.'));
          });
        }
      }
    }
  };
})();

/**
 * Returns an object with status and appropriate data
 * Used to perform the next step in the weatherResponseCallback function
 */
function interpretResponse(weatherResponseObject, city) {

  if (weatherResponseObject.response.error) {
    let msg = weatherResponseObject.response.error.description;
    if (weatherResponseObject.response.error.type == 'querynotfound') msg += ' for ' + city;
    if (weatherResponseObject.response.error.type == 'keynotfound') msg = 'Please provide a Weather Underground API key.';
    return {error: msg};
  } else {

    if (weatherResponseObject['current_observation']) {
      return {
        status: 'successful',
        state: weatherResponseObject['current_observation']['display_location']['state'],
        current: weatherResponseObject['current_observation']
      };
    } else if (weatherResponseObject.forecast) {
      return {
        status: 'successful',
        forecast: weatherResponseObject.forecast
      };
    }
    else {
      let options = weatherResponseObject.response.results;
      let states = [];
      let countries = [];
      for (let o in options) {
        if (!countries.includes(options[o]['country'])) countries.push(options[o]['country']);
        states.push(options[o]['state']);
      }
      if (countries.length == 1) {
        let country_name = options[0]['country_name'];
        return {
          status: 'single_country_multiple_states',
          country_name: country_name,
          options: options
        };

      } else {
        return {
          status: 'multiple_countries',
          options: options
        };
      }
    }
  }
}

function weatherResponseCallback(callback, error, weatherResponse, condition, city, state, date, WU_API_KEY, debug) {
  let weatherReply;
  if (error) {
    callback({error: error.message});
  } else {

    if (weatherResponse.status == 'successful') {

      let full_state = state;
      if (weatherResponse.current && weatherResponse.current['display_location']) {
        city = weatherResponse.current['display_location']['city'];
        full_state = weatherResponse.current['display_location']['state_name'];
      }

      let displayCondition = (condition == 'UV') ? condition : condition.toLowerCase();
      if (condition == 'UV') displayCondition += ' index';
      else if (condition == 'FeelsLike') displayCondition = 'temperature';
      // feels like cannot be forecast, but the temperature

      if ((condition == 'Weather' || condition == 'Rain' || condition == 'Snow') && weatherResponse.current) weatherReply = 'In ' + city;
      else weatherReply = 'The ' + displayCondition + ' in ' + city;
      // let full_state = locationUtil.getStateName(state);  && !city.includes(full_state)
      if (full_state) weatherReply += ', ' + full_state;

      if ((condition == 'Weather' || condition == 'Snow') && weatherResponse.current) weatherReply += ' it ';
      if (!((condition == 'UV' && weatherResponse.forecast) || (condition == 'Rain' && weatherResponse.current))) {
        if (condition == 'FeelsLike' && weatherResponse.current) weatherReply += ' currently ';
        else weatherReply += date.displayDate;
      }

      let forecast;
      if (weatherResponse.forecast && condition != 'Weather') {
        forecast = weatherResponse.forecast['simpleforecast']['forecastday'].filter(function(day) {
          return day.date.year == date.year && day.date.month == date.month && day.date.day == date.day;
        })[0];
      }
      switch (condition) {
      case 'Weather':
        if (weatherResponse.current) {
          weatherReply += weatherResponse.current['weather'];
          weatherReply += ' and the temperature is ' + weatherResponse.current['temp_f'] + ' degrees Fahrenheit';
        } else {
          forecast = weatherResponse.forecast['txt_forecast']['forecastday'].filter(function(day) {
            return day.period == date.period;
          })[0];
          let weather = forecast['fcttext'];
          weather = weather.replace(/([0-9]*)F/g, '$1 degrees Fahrenheit');
          weather = weather.replace(/Winds ([A-Z]*)/g, replaceCardinalPoint);
          weatherReply += weather;
        }
        break;
      case 'Temperature':
        if (weatherResponse.current) {
          weatherReply += weatherResponse.current['temp_f'] + ' degrees Fahrenheit';
        } else {
          weatherReply += 'a high of ' + forecast['high']['fahrenheit'] + ' degrees fahrenheit';
          weatherReply += ' and a low of ' + forecast['low']['fahrenheit'] + ' degrees fahrenheit';
        }
        break;
      case 'Rain':
        if (weatherResponse.current) {
          let rain_inches = weatherResponse.current['precip_1hr_in'];
          rain_inches = rain_inches < 0 ? 0 : rain_inches;  // correct for -999 errors in rain data
          weatherReply += ' there has been ' + parseFloat(rain_inches) + ' inches of rain in the past hour';
        } else
            weatherReply += parseFloat(forecast['qpf_allday']['in']) + ' inches.';

        break;
      case 'Snow':
        if (weatherResponse.current) {
          weatherReply += weatherResponse.current['weather'];
          if (weatherResponse.current['weather'].toLowerCase().indexOf('snow') < 0) weatherReply += ' so there is no snow.';
        } else {
          weatherReply += parseFloat(forecast['snow_allday']['in']) + ' inches.';
        }
        break;
      case 'UV':
        if (weatherResponse.current) {
          let uv_index = Number(weatherResponse.current['UV']);
          weatherReply += uv_index + ' which is ' + getRisk(uv_index) + ' risk';
        } else weatherReply += ' cannot be forecast, but the weather will be ' + forecast['conditions'] + '.';
        break;
      case 'Wind':
        if (weatherResponse.current) {
          weatherReply += weatherResponse.current['wind_string'];
          weatherReply = weatherReply.replace(/From the ([A-Z]*)/g, replaceCardinalPoint);
          weatherReply = weatherReply.replace(/\sMPH/g, ' mph');
        }
        else weatherReply += 'from the ' + SPOKEN_CARDINAL_POINT[forecast['avewind'].dir] +
            ' at ' + forecast['avewind']['mph'] + ' gusting to ' + forecast['maxwind']['mph'] + ' mph.';
        break;
      case 'Humidity':
        if (weatherResponse.current) weatherReply += weatherResponse.current['relative_humidity'];
        else weatherReply += forecast['avehumidity'] + '%.';
        break;
      case 'FeelsLike':
        if (weatherResponse.current) weatherReply += 'feels like ' + weatherResponse.current['feelslike_f'] + ' degrees Fahrenheit.';
        else {
          weatherReply += 'a high of ' + forecast['high']['fahrenheit'] + ' degrees fahrenheit';
          weatherReply += ' and a low of ' + forecast['low']['fahrenheit'] + ' degrees fahrenheit';
          weatherReply += ' but I cannot yet tell you exactly what it will feel like.';
        }
        break;
      }
      callback({
        tell: weatherReply
        //TODO image: weatherResponse.current ? weatherResponse.current.icon_url : forecast.icon_url
      }, city);

    } else if (weatherResponse.status == 'redo') {
      state = weatherResponse.state;
      if (debug) console.log('redoing for ' + city + ', ' + state);
      weatherUtil.makeWeatherRequest(WU_API_KEY, condition, city, state, date, debug, callback);

    } else if (weatherResponse.status == 'single_country_multiple_states') {
      if (debug) {
        console.log('Got ' + weatherResponse.options.length + ' cities named ' + city + ' in ' + weatherResponse.country_name);
      }
      let state_names = [], state_abbrevs = [];
      weatherResponse.options.forEach(function(city) {
        state_names.push(locationUtil.getStateName(city.state));
        state_abbrevs.push(city.state);
      });
      let state_string = '';
      for (let i = 0; i < state_names.length; i++) {
        state_string += state_names[i] + ' (' + state_abbrevs[i] + ')';
        if (i < state_names.length - 1) state_string += ', ';
      }
      let options = state_names.concat(state_abbrevs);
      for (let i = 0; i < options.length; i++) {
        if (options[i] !== undefined)
          options[i] = options[i].toLowerCase();
      }
      callback({
        ask: [
          'I found ' + state_names.length + ' states in ' + weatherResponse.country_name + ' with a city named ' + city + ':',
          state_string,
          'Which state are you interested in?'],
        options: options
      });
    } else if (weatherResponse.status == 'multiple_countries') {
      if (debug) {
        console.log('Got ' + weatherResponse.options.length + ' cities named ' + city + ' in the world.');
      }
      let country_names = [], country_abbrevs = [];
      weatherResponse.options.forEach(function(city) { if (!country_abbrevs.includes(city['country'])) {
        country_names.push(city['country_name']);
        country_abbrevs.push(city['country']);
      }});
      let country_string = '';
      for (let i = 0; i < country_names.length; i++) {
        country_string += country_names[i] + ' (' + country_abbrevs[i] + ')';
        if (i < country_names.length - 1) country_string += ', ';
      }
      let options = country_names.concat(country_abbrevs);
      for (let i = 0; i < options.length; i++) {
        if (options[i] !== undefined)
          options[i] = options[i].toLowerCase();
      }
      callback({
        ask: ['I found ' + country_names.length + ' countries in the world with a city named ' + city + ':',
          country_string,
          'Which country are you interested in?'],
        options: options
      });
    } else
      console.log(weatherResponse);

  }
}

/**
 * Returns risk factor from UV index
 */
function getRisk(uv_index) {
  if (uv_index < 3) return 'low';
  else if (uv_index < 6) return 'moderate';
  else if (uv_index < 8) return 'high';
  else if (uv_index < 11) return 'very high';
  else return 'extreme';
}

function replaceCardinalPoint(match, p1) {
  let prefix = match.includes('Winds') ? 'Winds ' : 'from the ';
  if (p1 in SPOKEN_CARDINAL_POINT) return prefix + SPOKEN_CARDINAL_POINT[p1];
  else return match;
}

let SPOKEN_CARDINAL_POINT = {
  'N': 'North',
  'NNE': 'North-northeast',
  'NE': 'Northeast',
  'ENE': 'East-northeast',
  'E': 'East',
  'ESE': 'East-southeast',
  'SE': 'Southeast',
  'SSE': 'South-southeast',
  'S': 'South',
  'SSW': 'South-southwest',
  'SW': 'Southwest',
  'WSW': 'West-southwest',
  'W': 'West',
  'WNW': 'West-northwest',
  'NW': 'Northwest',
  'NNW': 'North-northwest'
};

String.prototype.toProperCase = function() {
  return this.replace(/\w\S*/g, function(txt) {
    if (txt.toLowerCase() == 'of') return txt;
    else return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
  });
};

module.exports = weatherUtil;<commit_msg>Automatic disambiguation of numerical states Fixes #2<commit_after>/**
 Copyright 2016 IBM. All Rights Reserved.
 Developer: David Thomason
 */

let http = require('http');
let locationUtil = require('./locationUtil'), dateUtil = require('./dateUtil');

/**
 * Provides weather utility to send a request to the Weather Underground
 * and receive get a textual response
 */
let weatherUtil = (function() {

  return {

    /**
     * Uses Weather Underground API, documented: https://www.wunderground.com/weather/api
     *
     * Given, e.g.:
     * WU_API_KEY = <your weather underground API key>
     * condition = "Weather"
     * city = "San Francisco"
     * state = "California" (or "" or null)
     * date = "2016-05-28" (or "current")
     * debug = true or false, prints some console.log statements
     * callback = your callback function, which handles the responses like:
     *   { tell: 'the weather in San Francisco California on Wednesday..' }
     *   { ask: 'Which state?' }
     *
     */
    makeWeatherRequest: function(WU_API_KEY, condition, city, state, date, debug, callback) {
      // Clean up city, state and date input parameters
      condition = condition || 'Weather';

      if (state) {
        // state = state.toProperCase();
        if (!city) city = locationUtil.getProbableCity(state.toProperCase());
      }
      if (!city) {
        let in_state = state ? ' in ' + state : '';
        callback({ask: 'Which city' + in_state + ' are you interested in?'});
      } else {

        city = city.toProperCase();
        let city_param = locationUtil.formatCity(city);
        if (debug) console.log(city_param);

        date = date || 'current';
        let dateObject = date;
        if (!date.dataFeature) dateObject = dateUtil.getDateObject(date);
        if (dateObject.error) weatherResponseCallback(callback, new Error(dateObject.error));
        else {

          if (debug) console.log(dateObject);

          state = locationUtil.getProbableState(city, state);

          // Construct the URL from the input parameters
          let endpoint = 'http://api.wunderground.com/api/' + WU_API_KEY + '/';
          let prefix = endpoint + dateObject.dataFeature;
          let query = '/q/';
          if (state) query += state + '/';
          query += city_param + '.json';
          let url = prefix + query;

          requestWeather(url, prefix, condition, city, state, dateObject, WU_API_KEY, debug, callback);
        }
      }
    }
  };
})();

function requestWeather(url, prefix, condition, city, state, dateObject, WU_API_KEY, debug, callback){
  if (debug) console.log('URL = ' + url);

  // Perform the HTTP Get request
  http.get(url, function(response) {

    let weatherResponseString = '';

    if (response.statusCode != 200)
      weatherResponseCallback(callback, new Error('I\'m sorry, but the Weather Underground request returned a non-200 Response'));

    response.on('data', function(data) {
      weatherResponseString += data;
    });

    response.on('end', function() {
      let weatherResponseObject = JSON.parse(weatherResponseString);
      let interpretation = interpretResponse(weatherResponseObject, city, prefix);
      if (interpretation.error) weatherResponseCallback(callback, new Error(interpretation.error));
      else if (interpretation.disambiguate){
        url = prefix + interpretation.options[0]['l'] + '.json';
        requestWeather(url, prefix, condition, city, state, dateObject, WU_API_KEY, debug, callback);
      }
      else weatherResponseCallback(callback, null, interpretation, condition, city, state, dateObject, WU_API_KEY, debug);
    });
  }).on('error', function(e) {
    console.error(e);
    weatherResponseCallback(callback, new Error('I\'m sorry, but there was a communication error with the Weather Underground.'));
  });
}

/**
 * Returns an object with status and appropriate data
 * Used to perform the next step in the weatherResponseCallback function
 */
function interpretResponse(weatherResponseObject, city) {

  if (weatherResponseObject.response.error) {
    let msg = weatherResponseObject.response.error.description;
    if (weatherResponseObject.response.error.type == 'querynotfound') msg += ' for ' + city;
    if (weatherResponseObject.response.error.type == 'keynotfound') msg = 'Please provide a Weather Underground API key.';
    return {error: msg};
  } else {

    if (weatherResponseObject['current_observation']) {
      return {
        status: 'successful',
        state: weatherResponseObject['current_observation']['display_location']['state'],
        current: weatherResponseObject['current_observation']
      };
    } else if (weatherResponseObject.forecast) {
      return {
        status: 'successful',
        forecast: weatherResponseObject.forecast
      };
    }
    else {
      let options = weatherResponseObject.response.results;
      let states = [];
      let countries = [];
      for (let o in options) {
        if (!countries.includes(options[o]['country'])) countries.push(options[o]['country']);
        states.push(options[o]['state']);
      }
      if (countries.length == 1) {
        // Check to see if the states are all numbers
        let redundant = true;
        for (let s in states){
          if(isNaN(parseInt(states[s]))){
            redundant = false;
          }
        }
        if(redundant){
          return {
            disambiguate: true,
            options: options
          };
        } else {
          let country_name = options[0]['country_name'];
          return {
            status: 'single_country_multiple_states',
            country_name: country_name,
            options: options
          };
        }

      } else {
        return {
          status: 'multiple_countries',
          options: options
        };
      }
    }
  }
}

function weatherResponseCallback(callback, error, weatherResponse, condition, city, state, date, WU_API_KEY, debug) {
  let weatherReply;
  if (error) {
    callback({error: error.message});
  } else {

    if (weatherResponse.status == 'successful') {

      let full_state = state;
      if (weatherResponse.current && weatherResponse.current['display_location']) {
        city = weatherResponse.current['display_location']['city'];
        full_state = weatherResponse.current['display_location']['state_name'];
      }

      let displayCondition = (condition == 'UV') ? condition : condition.toLowerCase();
      if (condition == 'UV') displayCondition += ' index';
      else if (condition == 'FeelsLike') displayCondition = 'temperature';
      // feels like cannot be forecast, but the temperature

      if ((condition == 'Weather' || condition == 'Rain' || condition == 'Snow') && weatherResponse.current) weatherReply = 'In ' + city;
      else weatherReply = 'The ' + displayCondition + ' in ' + city;
      // let full_state = locationUtil.getStateName(state);  && !city.includes(full_state)
      if (full_state) weatherReply += ', ' + full_state;

      if ((condition == 'Weather' || condition == 'Snow') && weatherResponse.current) weatherReply += ' it ';
      if (!((condition == 'UV' && weatherResponse.forecast) || (condition == 'Rain' && weatherResponse.current))) {
        if (condition == 'FeelsLike' && weatherResponse.current) weatherReply += ' currently ';
        else weatherReply += date.displayDate;
      }

      let forecast;
      if (weatherResponse.forecast && condition != 'Weather') {
        forecast = weatherResponse.forecast['simpleforecast']['forecastday'].filter(function(day) {
          return day.date.year == date.year && day.date.month == date.month && day.date.day == date.day;
        })[0];
      }
      switch (condition) {
      case 'Weather':
        if (weatherResponse.current) {
          weatherReply += weatherResponse.current['weather'];
          weatherReply += ' and the temperature is ' + weatherResponse.current['temp_f'] + ' degrees Fahrenheit';
        } else {
          forecast = weatherResponse.forecast['txt_forecast']['forecastday'].filter(function(day) {
            return day.period == date.period;
          })[0];
          let weather = forecast['fcttext'];
          weather = weather.replace(/([0-9]*)F/g, '$1 degrees Fahrenheit');
          weather = weather.replace(/Winds ([A-Z]*)/g, replaceCardinalPoint);
          weatherReply += weather;
        }
        break;
      case 'Temperature':
        if (weatherResponse.current) {
          weatherReply += weatherResponse.current['temp_f'] + ' degrees Fahrenheit';
        } else {
          weatherReply += 'a high of ' + forecast['high']['fahrenheit'] + ' degrees fahrenheit';
          weatherReply += ' and a low of ' + forecast['low']['fahrenheit'] + ' degrees fahrenheit';
        }
        break;
      case 'Rain':
        if (weatherResponse.current) {
          let rain_inches = weatherResponse.current['precip_1hr_in'];
          rain_inches = rain_inches < 0 ? 0 : rain_inches;  // correct for -999 errors in rain data
          weatherReply += ' there has been ' + parseFloat(rain_inches) + ' inches of rain in the past hour';
        } else
            weatherReply += parseFloat(forecast['qpf_allday']['in']) + ' inches.';

        break;
      case 'Snow':
        if (weatherResponse.current) {
          weatherReply += weatherResponse.current['weather'];
          if (weatherResponse.current['weather'].toLowerCase().indexOf('snow') < 0) weatherReply += ' so there is no snow.';
        } else {
          weatherReply += parseFloat(forecast['snow_allday']['in']) + ' inches.';
        }
        break;
      case 'UV':
        if (weatherResponse.current) {
          let uv_index = Number(weatherResponse.current['UV']);
          weatherReply += uv_index + ' which is ' + getRisk(uv_index) + ' risk';
        } else weatherReply += ' cannot be forecast, but the weather will be ' + forecast['conditions'] + '.';
        break;
      case 'Wind':
        if (weatherResponse.current) {
          weatherReply += weatherResponse.current['wind_string'];
          weatherReply = weatherReply.replace(/From the ([A-Z]*)/g, replaceCardinalPoint);
          weatherReply = weatherReply.replace(/\sMPH/g, ' mph');
        }
        else weatherReply += 'from the ' + SPOKEN_CARDINAL_POINT[forecast['avewind'].dir] +
            ' at ' + forecast['avewind']['mph'] + ' gusting to ' + forecast['maxwind']['mph'] + ' mph.';
        break;
      case 'Humidity':
        if (weatherResponse.current) weatherReply += weatherResponse.current['relative_humidity'];
        else weatherReply += forecast['avehumidity'] + '%.';
        break;
      case 'FeelsLike':
        if (weatherResponse.current) weatherReply += 'feels like ' + weatherResponse.current['feelslike_f'] + ' degrees Fahrenheit.';
        else {
          weatherReply += 'a high of ' + forecast['high']['fahrenheit'] + ' degrees fahrenheit';
          weatherReply += ' and a low of ' + forecast['low']['fahrenheit'] + ' degrees fahrenheit';
          weatherReply += ' but I cannot yet tell you exactly what it will feel like.';
        }
        break;
      }
      callback({
        tell: weatherReply
        //TODO image: weatherResponse.current ? weatherResponse.current.icon_url : forecast.icon_url
      }, city);

    } else if (weatherResponse.status == 'redo') {
      state = weatherResponse.state;
      if (debug) console.log('redoing for ' + city + ', ' + state);
      weatherUtil.makeWeatherRequest(WU_API_KEY, condition, city, state, date, debug, callback);

    } else if (weatherResponse.status == 'single_country_multiple_states') {
      if (debug) {
        console.log('Got ' + weatherResponse.options.length + ' cities named ' + city + ' in ' + weatherResponse.country_name);
      }
      let state_names = [], state_abbrevs = [];
      weatherResponse.options.forEach(function(city) {
        state_names.push(locationUtil.getStateName(city.state));
        state_abbrevs.push(city.state);
      });
      let state_string = '';
      for (let i = 0; i < state_names.length; i++) {
        state_string += state_names[i] + ' (' + state_abbrevs[i] + ')';
        if (i < state_names.length - 1) state_string += ', ';
      }
      let options = state_names.concat(state_abbrevs);
      for (let i = 0; i < options.length; i++) {
        if (options[i] !== undefined)
          options[i] = options[i].toLowerCase();
      }
      callback({
        ask: [
          'I found ' + state_names.length + ' states in ' + weatherResponse.country_name + ' with a city named ' + city + ':',
          state_string,
          'Which state are you interested in?'],
        options: options
      });
    } else if (weatherResponse.status == 'multiple_countries') {
      if (debug) {
        console.log('Got ' + weatherResponse.options.length + ' cities named ' + city + ' in the world.');
      }
      let country_names = [], country_abbrevs = [];
      weatherResponse.options.forEach(function(city) { if (!country_abbrevs.includes(city['country'])) {
        country_names.push(city['country_name']);
        country_abbrevs.push(city['country']);
      }});
      let country_string = '';
      for (let i = 0; i < country_names.length; i++) {
        country_string += country_names[i] + ' (' + country_abbrevs[i] + ')';
        if (i < country_names.length - 1) country_string += ', ';
      }
      let options = country_names.concat(country_abbrevs);
      for (let i = 0; i < options.length; i++) {
        if (options[i] !== undefined)
          options[i] = options[i].toLowerCase();
      }
      callback({
        ask: ['I found ' + country_names.length + ' countries in the world with a city named ' + city + ':',
          country_string,
          'Which country are you interested in?'],
        options: options
      });
    } else
      console.log(weatherResponse);

  }
}

/**
 * Returns risk factor from UV index
 */
function getRisk(uv_index) {
  if (uv_index < 3) return 'low';
  else if (uv_index < 6) return 'moderate';
  else if (uv_index < 8) return 'high';
  else if (uv_index < 11) return 'very high';
  else return 'extreme';
}

function replaceCardinalPoint(match, p1) {
  let prefix = match.includes('Winds') ? 'Winds ' : 'from the ';
  if (p1 in SPOKEN_CARDINAL_POINT) return prefix + SPOKEN_CARDINAL_POINT[p1];
  else return match;
}

let SPOKEN_CARDINAL_POINT = {
  'N': 'North',
  'NNE': 'North-northeast',
  'NE': 'Northeast',
  'ENE': 'East-northeast',
  'E': 'East',
  'ESE': 'East-southeast',
  'SE': 'Southeast',
  'SSE': 'South-southeast',
  'S': 'South',
  'SSW': 'South-southwest',
  'SW': 'Southwest',
  'WSW': 'West-southwest',
  'W': 'West',
  'WNW': 'West-northwest',
  'NW': 'Northwest',
  'NNW': 'North-northwest'
};

String.prototype.toProperCase = function() {
  return this.replace(/\w\S*/g, function(txt) {
    if (txt.toLowerCase() == 'of') return txt;
    else return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
  });
};

module.exports = weatherUtil;<|endoftext|>


<commit_before>var Container = require('wantsit').Container,
  ObjectFactory = require('wantsit').ObjectFactory,
  logger = require('andlog')

var BossWeb = function() {
  process.title = 'boss-web'

  // make errors a little more descriptive
  process.on('uncaughtException', function(error) {
    container.find('logger').error('Uncaught error', error && error.stack ? error.stack : 'No stack trace available')

    throw error
  }.bind(this))

  // create container
  var container = new Container({
    timeout: 0
  })

  // parse configuration
  container.createAndRegister('config', require('./components/Configuration'))
  container.register('logger', logger)
  container.register('posix', require('posix'))
  container.register('remote', require('../remote'))
  container.register('webSocketResponder', {
    broadcast: function() {}
  })
  container.register('moonbootsConfig', {
    'isDev': process.env.NODE_ENV == 'development'
  })
  container.createAndRegister('hostDataFactory', ObjectFactory, [require('./domain/HostData')])
  container.createAndRegister('processDataFactory', ObjectFactory, [require('./domain/ProcessData')])
  container.createAndRegister('hostList', require('./components/HostList'))
  container.createAndRegister('server', require('./Server'))

  // optional dependency, don't care if it fails
  try {
    container.register('mdns', require('boss-mdns'))
  } catch(e) {}
}

module.exports = BossWeb
<commit_msg>print errors when container does not have components<commit_after>var Container = require('wantsit').Container,
  ObjectFactory = require('wantsit').ObjectFactory,
  logger = require('andlog')

var BossWeb = function() {
  process.title = 'boss-web'

  // make errors a little more descriptive
  process.on('uncaughtException', function(error) {
    container.find('logger').error('Uncaught error', error && error.stack ? error.stack : 'No stack trace available')

    throw error
  }.bind(this))

  // create container
  var container = new Container({
    timeout: 0
  })
  container.on('error', function(error) {
    console.warn('Container error:', error.message || error.message.stack)
  })

  // parse configuration
  container.createAndRegister('config', require('./components/Configuration'))
  container.register('logger', logger)
  container.register('posix', require('posix'))
  container.register('remote', require('../remote'))
  container.register('webSocketResponder', {
    broadcast: function() {}
  })
  container.register('moonbootsConfig', {
    'isDev': process.env.NODE_ENV == 'development'
  })
  container.createAndRegister('hostDataFactory', ObjectFactory, [require('./domain/HostData')])
  container.createAndRegister('processDataFactory', ObjectFactory, [require('./domain/ProcessData')])
  container.createAndRegister('hostList', require('./components/HostList'))
  container.createAndRegister('server', require('./Server'))

  // optional dependency, don't care if it fails
  try {
    container.register('mdns', require('boss-mdns'))
  } catch(e) {}
}

module.exports = BossWeb
<|endoftext|>


<commit_before>var request = require('request-promise');
var API_BASE_URL = "https://api.tnyu.org/v1.0/";

module.exports = {
  showEvents: function(req, res, next, urlFor) {
    request({
      url: API_BASE_URL + "events/public-check-in-eligible",
      json: true,
      rejectUnauthorized: false
    }, function(err, response, body) {
      res.render('showEvents', {events: body.events});
    });
  },

  showConfiguredEvent: function(req, res, next, urlFor) {
    request({
      url: API_BASE_URL + "events/" + req.params.event_id,
      json: true,
      rejectUnauthorized: false
    }, function(err, response, body) {
      res.render('showEvents', {events: body.events, fromConfig: true});
    });
  },

  checkin: function(req, res, next, urlFor) {
    res.render('checkin');
  },

  thanks: function(req, res, next, urlFor) {
    res.render('thanks');
  },

  configure: function(req, res, next, urlFor) {
    request({
      url: "https://api.tnyu.org/v1.0/events/next-10-public",
      json: true,
      rejectUnauthorized: false
    }, function(err, response, body) {
      var events = body.events instanceof Array ? body.events : [body.events];

      events.forEach(function(event) {
        event.checkInUrl = urlFor("showConfiguredEvent", {absolute: true, params: {event_id: event.id}});
      })

      res.render('configure', {events: events});
    });
  }
}<commit_msg>Support new route naming<commit_after>var request = require('request-promise');
var API_BASE_URL = "https://api.tnyu.org/v1.0/";

module.exports = {
  showEvents: function(req, res, next, urlFor) {
    request({
      url: API_BASE_URL + "events/public-check-in-eligible",
      json: true,
      rejectUnauthorized: false
    }, function(err, response, body) {
      res.render('showEvents', {events: body.events});
    });
  },

  showConfiguredEvent: function(req, res, next, urlFor) {
    request({
      url: API_BASE_URL + "events/" + req.params.event_id,
      json: true,
      rejectUnauthorized: false
    }, function(err, response, body) {
      res.render('showEvents', {events: body.events, fromConfig: true});
    });
  },

  checkin: function(req, res, next, urlFor) {
    res.render('checkin');
  },

  thanks: function(req, res, next, urlFor) {
    res.render('thanks');
  },

  configure: function(req, res, next, urlFor) {
    request({
      url: "https://api.tnyu.org/v1.0/events/next-10-publicly",
      json: true,
      rejectUnauthorized: false
    }, function(err, response, body) {
      var events = body.events instanceof Array ? body.events : [body.events];

      events.forEach(function(event) {
        event.checkInUrl = urlFor("showConfiguredEvent", {absolute: true, params: {event_id: event.id}});
      })

      res.render('configure', {events: events});
    });
  }
}<|endoftext|>


<commit_before>
var net = require('net');
var util = require('util');
var fs = require('fs');

function lookup (query, options, callback) {

    if (typeof options == 'function') {
        callback = options;
        options = this.options;
    } else {
        options = util._extend(this.options, options);
    }

    var client = net.connect(options.port, options.host, function() {
        client.write(query + '\n', 'ascii');
    });

    var content = [];

    client.on('data', function(data){
        content.push(data);
    });

    client.on('close', function(err) {
        if (err) {
            callback(err);
        } else {
            var parser = './parser/' + options.host + '.js';

            var bc = Buffer.concat(content);

            fs.exists(parser, function(exists) {
                if (exists) {
                    callback(null, require(parser).parse(bc), bc);

                } else {
                    callback(null, {}, bc);
                }
            });

        }
    });
};

exports.lookup = lookup;

exports.options = {
    host: 'whois.iana.org',
    port: 43
};
<commit_msg>modernize whois client<commit_after>
var net = require('net');
var util = require('util');
var fs = require('fs');

async function lookup (query, options) {
    if (options) {
        options = util._extend(this.options, options);
    }

    let client = net.connect(options.port, options.host, function() {
        client.write(query + '\n', 'ascii');
    });

    let content = [];

    client.on('data', function(data){
        content.push(data);
    });

    return new Promise((resolve, reject) => {
        client.on('close', function(err) {
            if (err) {
                reject(err);
            } else {
                let parser = './parser/' + options.host + '.js';

                let bc = Buffer.concat(content);

                fs.exists(parser, function(exists) {
                    if (exists) {
                      resolve(require(parser).parse(bc));
                    } else {
                      resolve({});
                    }
                });
            }
        })
    });
}

exports.lookup = lookup;

exports.options = {
    host: 'whois.iana.org',
    port: 43
};
<|endoftext|>


<commit_before>
/**
 * @fileoverview Request Sender
 */

/**
 * @augments JsSIP
 * @class Class creating a request sender.
 * @param {Object} applicant
 * @param {JsSIP.UA} ua
 */

JsSIP.RequestSender = function(applicant, ua) {
  this.ua = ua;
  this.applicant = applicant;
  this.method = applicant.request.method;
  this.request = applicant.request;
  this.credentials = null;
  this.challenged = false;
  this.staled = false;

  // If ua is in closing process or even closed just allow sending Bye and ACK
  if (ua.status === JsSIP.c.UA_STATUS_USER_CLOSED && (this.method !== JsSIP.c.BYE || this.method !== JsSIP.c.ACK)) {
    this.onTransportError();
  }

  this.credentials = ua.getCredentials(this.request);
};

/**
* Create the client transaction and send the message.
*/
JsSIP.RequestSender.prototype = {
  send: function() {
    if (this.credentials) {
      if (this.request.method === JsSIP.c.REGISTER) {
        this.request.setHeader('authorization', this.credentials.authenticate());
      } else {
        this.request.setHeader('proxy-authorization', this.credentials.authenticate());
      }
    }

    switch(this.method) {
      case "INVITE":
        this.clientTransaction = new JsSIP.Transactions.InviteClientTransaction(this, this.request, this.ua.transport);
        break;
      case "ACK":
        this.clientTransaction = new JsSIP.Transactions.AckClientTransaction(this, this.request, this.ua.transport);
        break;
      default:
        this.clientTransaction = new JsSIP.Transactions.NonInviteClientTransaction(this, this.request, this.ua.transport);
    }
    this.clientTransaction.send();
  },

  /**
  * Callback fired when receiving a request timeout error from the client transaction.
  * To be re-defined by the applicant.
  * @event
  */
  onRequestTimeout: function() {
    this.applicant.onRequestTimeout();
  },

  /**
  * Callback fired when receiving a transport error from the client transaction.
  * To be re-defined by the applicant.
  * @event
  */
  onTransportError: function() {
    this.applicant.onTransportError();
  },

  /**
  * Called from client transaction when receiving a correct response to the request.
  * Authenticate request if needed or pass the response back to the applicant.
  * @param {JsSIP.IncomingResponse} response
  */
  receiveResponse: function(response) {
    var cseq, challenge,
      status_code = response.status_code;

    /*
    * Authentication
    * Authenticate once. _challenged_ flag used to avoid infinite authentications.
    */
    if ((status_code === 401 || status_code === 407) && this.ua.configuration.password !== null) {

      if (status_code === 401) {
        challenge = response.s('WWW-Authenticate');
      } else {
        challenge = response.s('Proxy-Authenticate');
      }

      if ( !this.challenged || (this.challenged && !this.staled && challenge.stale) ) {
        if (!this.credentials) {
          this.credentials = new JsSIP.DigestAuthentication(this.ua, this.request, response);
        } else {
          this.credentials.update(response);
        }

        if (response.method === JsSIP.c.REGISTER) {
          cseq = this.applicant.cseq += 1;
        } else if (this.request.dialog){
          cseq = this.request.dialog.local_seqnum += 1;
        } else {
          cseq = this.request.headers.CSeq.toString().split(' ')[0];
          cseq = parseInt(cseq,10) +1;
        }

        this.request.setHeader('cseq', cseq +' '+ this.method);
        this.challenged = true;
        this.send();
      }
    } else {
        if (this.challenged && response.status_code >= 200) {
          this.ua.saveCredentials(this.credentials);
        }
        this.applicant.receiveResponse(response);
    }
  }
};<commit_msg>Prevent outgoing CANCEL messages from being authenticated<commit_after>
/**
 * @fileoverview Request Sender
 */

/**
 * @augments JsSIP
 * @class Class creating a request sender.
 * @param {Object} applicant
 * @param {JsSIP.UA} ua
 */

JsSIP.RequestSender = function(applicant, ua) {
  this.ua = ua;
  this.applicant = applicant;
  this.method = applicant.request.method;
  this.request = applicant.request;
  this.credentials = null;
  this.challenged = false;
  this.staled = false;

  // If ua is in closing process or even closed just allow sending Bye and ACK
  if (ua.status === JsSIP.c.UA_STATUS_USER_CLOSED && (this.method !== JsSIP.c.BYE || this.method !== JsSIP.c.ACK)) {
    this.onTransportError();
  }

  this.credentials = ua.getCredentials(this.request);
};

/**
* Create the client transaction and send the message.
*/
JsSIP.RequestSender.prototype = {
  send: function() {
    if (this.credentials) {
      if (this.request.method === JsSIP.c.REGISTER) {
        this.request.setHeader('authorization', this.credentials.authenticate());
      } else if (this.request.method !== JsSIP.c.CANCEL) {
        this.request.setHeader('proxy-authorization', this.credentials.authenticate());
      }
    }

    switch(this.method) {
      case "INVITE":
        this.clientTransaction = new JsSIP.Transactions.InviteClientTransaction(this, this.request, this.ua.transport);
        break;
      case "ACK":
        this.clientTransaction = new JsSIP.Transactions.AckClientTransaction(this, this.request, this.ua.transport);
        break;
      default:
        this.clientTransaction = new JsSIP.Transactions.NonInviteClientTransaction(this, this.request, this.ua.transport);
    }
    this.clientTransaction.send();
  },

  /**
  * Callback fired when receiving a request timeout error from the client transaction.
  * To be re-defined by the applicant.
  * @event
  */
  onRequestTimeout: function() {
    this.applicant.onRequestTimeout();
  },

  /**
  * Callback fired when receiving a transport error from the client transaction.
  * To be re-defined by the applicant.
  * @event
  */
  onTransportError: function() {
    this.applicant.onTransportError();
  },

  /**
  * Called from client transaction when receiving a correct response to the request.
  * Authenticate request if needed or pass the response back to the applicant.
  * @param {JsSIP.IncomingResponse} response
  */
  receiveResponse: function(response) {
    var cseq, challenge,
      status_code = response.status_code;

    /*
    * Authentication
    * Authenticate once. _challenged_ flag used to avoid infinite authentications.
    */
    if ((status_code === 401 || status_code === 407) && this.ua.configuration.password !== null) {

      if (status_code === 401) {
        challenge = response.s('WWW-Authenticate');
      } else {
        challenge = response.s('Proxy-Authenticate');
      }

      if ( !this.challenged || (this.challenged && !this.staled && challenge.stale) ) {
        if (!this.credentials) {
          this.credentials = new JsSIP.DigestAuthentication(this.ua, this.request, response);
        } else {
          this.credentials.update(response);
        }

        if (response.method === JsSIP.c.REGISTER) {
          cseq = this.applicant.cseq += 1;
        } else if (this.request.dialog){
          cseq = this.request.dialog.local_seqnum += 1;
        } else {
          cseq = this.request.headers.CSeq.toString().split(' ')[0];
          cseq = parseInt(cseq,10) +1;
        }

        this.request.setHeader('cseq', cseq +' '+ this.method);
        this.challenged = true;
        this.send();
      }
    } else {
        if (this.challenged && response.status_code >= 200) {
          this.ua.saveCredentials(this.credentials);
        }
        this.applicant.receiveResponse(response);
    }
  }
};<|endoftext|>


<commit_before>var express = require('express');
var router = express.Router();
var http = require('http');
var config = require('../../config/config.js').gc2;

router.post('/api/sql/:db', function (req, response) {
    var db = req.params.db, q = req.body.q, srs = req.body.srs, lifetime = req.body.lifetime, client_encoding = req.body.client_encoding, url, data = [], jsfile = "";
    url = config.host + "/api/v1/sql/" + db + "?q=" + q + "&srs=" + srs +  "&lifetime=" + lifetime + "&client_encoding=" + client_encoding;
    console.log(url);
    http.get(url, function (res) {
        if (res.statusCode != 200) {
            response.header('content-type', 'application/json');
            response.status(res.statusCode).send({
                success: false,
                message: "Could not get the sql data."
            });
            return;
        }
        var chunks = [];
        response.header('content-type', 'application/json');
        res.on('data', function (chunk) {
            chunks.push(chunk);
        });
        res.on("end", function () {
            jsfile = new Buffer.concat(chunks);
            response.send(jsfile);
        });
    }).on("error", function (err) {
        console.log(err);
    });
});
module.exports = router;<commit_msg>no message<commit_after>var express = require('express');
var router = express.Router();
var http = require('http');
var config = require('../../config/config.js').gc2;

router.post('/api/sql/:db', function (req, response) {
    var db = req.params.db, q = req.body.q, srs = req.body.srs, lifetime = req.body.lifetime, client_encoding = req.body.client_encoding, url, data = [], jsfile = "";
    url = config.host + "/api/v1/sql/" + db + "?q=" + q + "&srs=" + srs +  "&lifetime=" + lifetime + "&client_encoding=" + client_encoding;
    console.log(url);
    http.get(url, function (res) {
        if (res.statusCode != 200) {
            response.header('content-type', 'application/json');
            response.status(res.statusCode).send({
                success: false,
                message: "Could not get the sql data."
            });
            return;
        }
        var chunks = [];
        response.header('content-type', 'application/json');
        response.header('Cache-Control', 'no-cache, no-store, must-revalidate');
        response.header('Expires', '0');
        response.header('X-Powered-By', 'MapCentia Vidi');
        res.on('data', function (chunk) {
            chunks.push(chunk);
        });
        res.on("end", function () {
            jsfile = new Buffer.concat(chunks);
            response.send(jsfile);
        });
    }).on("error", function (err) {
        console.log(err);
    });
});
module.exports = router;<|endoftext|>


<commit_before>/*
 * Note: In order for S3 to work, the following environment variables
 * must be set: http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html
 * AWS_ACCESS_KEY_ID
 * AWS_SECRET_ACCESS_KEY
 */

/* TODO:
 * Custom collections have a username prefix, e.g.: vanwars_images
 * Create /endpoints/ listing w/list of all collections in DB.
 */
var helpers = require("../lib/helpers");
var mongodb = require("mongodb");
var im = require('imagemagick');
var ObjectID = mongodb.ObjectID;
var thumbnailer = require("../lib/thumbnailer");
var fileHandler = require("../lib/fileHandler");

// SEE: http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html
var S3_BUCKET = process.env.AWS_S3_BUCKET,
    fs = require('fs'),
    AWS = require('aws-sdk'),
    fs = require('fs'),
    flow = require('flow');

exports.list = function (req, res) {
    'use strict';
    var COLLECTION = req.params.collection,
        query = req.query;
        /*fields = ['username', 'description', 'genre'],
        i,
        field,
        val;
    for (i = 0; i < fields.length; i++) {
        field = fields[i];
        val = req.query[field];
        if (val) {
            query[field] = val;
        }
    }
    if (req.params.username) {
        query.username = req.params.username;
    }*/
    req.db.collection(COLLECTION).find(query).toArray(function (err, docs) {
        if (err) {
            req.handleError(res, err.message, "Failed to get images.");
        } else {
            res.status(200).json(docs);
        }
    });
};

exports.post = function (req, res) {
    'use strict';
    var COLLECTION = req.params.collection,
        requiredFields = ['username'],
        isValid,
        newImage = req.body;
    // ensure if someone accidentally posts an _id, that it doesn't
    // interfere w/MongoDB's indexing system. Delete _id from dictionary:
    delete req.body._id;
    req.body.username = req.body.username || req.params.username;

    isValid = helpers.validateCreateUpdate(requiredFields, req, res);
    if (!isValid) {
        return;
    }
    flow.exec(
        // generate thumbnails and transfer to S3:
        function () {
            if (req.files && req.files.image) {
                thumbnailer.generateThumbnails({
                    req: req,
                    res: res,
                    image: req.files.image,
                    index: 0
                }, this);
            } else {
                this();
            }
        },
        // append thumbnail data to JSON object:
        function (images) {
            if (images) {
                newImage.image = {
                    items: images,
                    original_file_name: req.files.image.name
                };
            }
            this();
        },
        // transfer audio to S3:
        function () {
            if (req.files && req.files.audio) {
                fileHandler.transferFile({
                    req: req,
                    res: res,
                    file: req.files.audio
                }, this);
            } else {
                this();
            }
        },
        // append audio data to JSON object:
        function (audioData) {
            if (audioData) {
                newImage.audio = audioData;
            }
            this();
        },
        // transfer file to S3:
        function () {
            if (req.files && req.files.file) {
                fileHandler.transferFile({
                    req: req,
                    res: res,
                    file: req.files.file
                }, this);
            } else {
                this();
            }
        },
        // append file data data to JSON object:
        function (fileData) {
            if (fileData) {
                newImage.file = fileData;
            }
            this();
        },
        // finally, save to database:
        function () {
            newImage.createDate = new Date();
            //finally, insert a new record:
            req.db.collection(COLLECTION).insertOne(newImage, function (err, doc) {
                if (err) {
                    req.handleError(res, err.message, "Failed to create new resource.");
                } else {
                    var d = doc.ops[0];
                    d.message = 'Resource successfully created';
                    console.log(doc.ops[0]);
                    res.status(201).json(d);
                }
            });
        }
    );
};

exports.get = function (req, res) {
    'use strict';
    var COLLECTION = req.params.collection;
    req.db.collection(COLLECTION).findOne({ _id: new ObjectID(req.params.id) }, function (err, doc) {
        if (err) {
            req.handleError(res, err.message, "Failed to find the requested resource");
        } else {
            console.log("Retrieved one:", doc);
            res.status(200).json(doc);
        }
    });
};

exports.put = function (req, res) {
    'use strict';
    var COLLECTION = req.params.collection,
        updateDoc = req.body,
        d;
    delete updateDoc._id;

    req.db.collection(COLLECTION).updateOne({_id: new ObjectID(req.params.id)}, updateDoc, function (err, doc) {
        if (err) {
            req.handleError(res, err.message, "Failed to update resource");
        } else {
            console.log("UPDATED DOC:", d);
            d = doc;
            res.status(204).json(d);
        }
    });
};

exports.delete = function (req, res) {
    'use strict';
    var COLLECTION = req.params.collection;
    console.log(req.params.id, req.params.collection);
    flow.exec(
        function () {
            // get image from database:
            req.db.collection(COLLECTION).findOne({ _id: new ObjectID(req.params.id) }, this);
            //req.db.collection(COLLECTION).findOne({"_id" : new ObjectID("583e4736afabfc00104b9ace")}, this);
        },
        function (err, record) {
            // store image in local variable or throw error:
            if (err) {
                req.handleError(res, err.message, "Failed to delete the requested resource");
            } else if (!record) {
                req.handleError(res, null, "Failed to locate the requested resource");
            } else {
                console.log(record);
                //image = record;
                this(record);
            }
        },
        function (record) {
            if (record.image) {
                //remove thumbnails from S3
                thumbnailer.deleteThumbnails({
                    req: req,
                    res: res,
                    record: record,
                    index: 0
                }, this);
            } else {
                this(record);
            }
        },
        function (record) {
            if (record.audio) {
                //remove audio file from S3
                fileHandler.deleteFile({
                    req: req,
                    res: res,
                    key: record.audio.key,
                    record: record
                }, this);
            } else {
                this(record);
            }
        },
        function (record) {
            if (record.file) {
                //remove audio file from S3
                fileHandler.deleteFile({
                    req: req,
                    res: res,
                    key: record.file.key,
                    record: record
                }, this);
            } else {
                this(record);
            }
        },
        function () {
            req.db.collection(COLLECTION).deleteOne({_id: new ObjectID(req.params.id)}, function (err, result) {
                if (err) {
                    req.handleError(res, err.message, "Failed to delete the requested resource");
                } else {
                    res.status(204).json(result).end();
                }
            });
        }
    );
};

exports.deleteAll = function (req, res) {
    'use strict';
    var COLLECTION = req.params.collection;
    req.db.collection(COLLECTION).deleteMany({}, function (err, result) {
        if (err) {
            req.handleError(res, err.message, "Failed to delete the requested resource");
        } else {
            res.status(204).json(result).end();
        }
    });
};<commit_msg>converted to case-insensitive "like" query<commit_after>/*
 * Note: In order for S3 to work, the following environment variables
 * must be set: http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html
 * AWS_ACCESS_KEY_ID
 * AWS_SECRET_ACCESS_KEY
 */

/* TODO:
 * Custom collections have a username prefix, e.g.: vanwars_images
 * Create /endpoints/ listing w/list of all collections in DB.
 */
var helpers = require("../lib/helpers");
var mongodb = require("mongodb");
var im = require('imagemagick');
var ObjectID = mongodb.ObjectID;
var thumbnailer = require("../lib/thumbnailer");
var fileHandler = require("../lib/fileHandler");

// SEE: http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html
var S3_BUCKET = process.env.AWS_S3_BUCKET,
    fs = require('fs'),
    AWS = require('aws-sdk'),
    fs = require('fs'),
    flow = require('flow');

exports.list = function (req, res) {
    'use strict';
    var COLLECTION = req.params.collection,
        query = req.query,
        key;
    // convert to case-insensitive 'like' query using regex:
    for (key in query) {
        query[key] = new RegExp(query[key], "ig");
    }
    req.db.collection(COLLECTION).find(query).toArray(function (err, docs) {
        if (err) {
            req.handleError(res, err.message, "Failed to get images.");
        } else {
            res.status(200).json(docs);
        }
    });
};

exports.post = function (req, res) {
    'use strict';
    var COLLECTION = req.params.collection,
        requiredFields = ['username'],
        isValid,
        newImage = req.body;
    // ensure if someone accidentally posts an _id, that it doesn't
    // interfere w/MongoDB's indexing system. Delete _id from dictionary:
    delete req.body._id;
    req.body.username = req.body.username || req.params.username;

    isValid = helpers.validateCreateUpdate(requiredFields, req, res);
    if (!isValid) {
        return;
    }
    flow.exec(
        // generate thumbnails and transfer to S3:
        function () {
            if (req.files && req.files.image) {
                thumbnailer.generateThumbnails({
                    req: req,
                    res: res,
                    image: req.files.image,
                    index: 0
                }, this);
            } else {
                this();
            }
        },
        // append thumbnail data to JSON object:
        function (images) {
            if (images) {
                newImage.image = {
                    items: images,
                    original_file_name: req.files.image.name
                };
            }
            this();
        },
        // transfer audio to S3:
        function () {
            if (req.files && req.files.audio) {
                fileHandler.transferFile({
                    req: req,
                    res: res,
                    file: req.files.audio
                }, this);
            } else {
                this();
            }
        },
        // append audio data to JSON object:
        function (audioData) {
            if (audioData) {
                newImage.audio = audioData;
            }
            this();
        },
        // transfer file to S3:
        function () {
            if (req.files && req.files.file) {
                fileHandler.transferFile({
                    req: req,
                    res: res,
                    file: req.files.file
                }, this);
            } else {
                this();
            }
        },
        // append file data data to JSON object:
        function (fileData) {
            if (fileData) {
                newImage.file = fileData;
            }
            this();
        },
        // finally, save to database:
        function () {
            newImage.createDate = new Date();
            //finally, insert a new record:
            req.db.collection(COLLECTION).insertOne(newImage, function (err, doc) {
                if (err) {
                    req.handleError(res, err.message, "Failed to create new resource.");
                } else {
                    var d = doc.ops[0];
                    d.message = 'Resource successfully created';
                    console.log(doc.ops[0]);
                    res.status(201).json(d);
                }
            });
        }
    );
};

exports.get = function (req, res) {
    'use strict';
    var COLLECTION = req.params.collection;
    req.db.collection(COLLECTION).findOne({ _id: new ObjectID(req.params.id) }, function (err, doc) {
        if (err) {
            req.handleError(res, err.message, "Failed to find the requested resource");
        } else {
            console.log("Retrieved one:", doc);
            res.status(200).json(doc);
        }
    });
};

exports.put = function (req, res) {
    'use strict';
    var COLLECTION = req.params.collection,
        updateDoc = req.body,
        d;
    delete updateDoc._id;

    req.db.collection(COLLECTION).updateOne({_id: new ObjectID(req.params.id)}, updateDoc, function (err, doc) {
        if (err) {
            req.handleError(res, err.message, "Failed to update resource");
        } else {
            console.log("UPDATED DOC:", d);
            d = doc;
            res.status(204).json(d);
        }
    });
};

exports.delete = function (req, res) {
    'use strict';
    var COLLECTION = req.params.collection;
    console.log(req.params.id, req.params.collection);
    flow.exec(
        function () {
            // get image from database:
            req.db.collection(COLLECTION).findOne({ _id: new ObjectID(req.params.id) }, this);
            //req.db.collection(COLLECTION).findOne({"_id" : new ObjectID("583e4736afabfc00104b9ace")}, this);
        },
        function (err, record) {
            // store image in local variable or throw error:
            if (err) {
                req.handleError(res, err.message, "Failed to delete the requested resource");
            } else if (!record) {
                req.handleError(res, null, "Failed to locate the requested resource");
            } else {
                console.log(record);
                //image = record;
                this(record);
            }
        },
        function (record) {
            if (record.image) {
                //remove thumbnails from S3
                thumbnailer.deleteThumbnails({
                    req: req,
                    res: res,
                    record: record,
                    index: 0
                }, this);
            } else {
                this(record);
            }
        },
        function (record) {
            if (record.audio) {
                //remove audio file from S3
                fileHandler.deleteFile({
                    req: req,
                    res: res,
                    key: record.audio.key,
                    record: record
                }, this);
            } else {
                this(record);
            }
        },
        function (record) {
            if (record.file) {
                //remove audio file from S3
                fileHandler.deleteFile({
                    req: req,
                    res: res,
                    key: record.file.key,
                    record: record
                }, this);
            } else {
                this(record);
            }
        },
        function () {
            req.db.collection(COLLECTION).deleteOne({_id: new ObjectID(req.params.id)}, function (err, result) {
                if (err) {
                    req.handleError(res, err.message, "Failed to delete the requested resource");
                } else {
                    res.status(204).json(result).end();
                }
            });
        }
    );
};

exports.deleteAll = function (req, res) {
    'use strict';
    var COLLECTION = req.params.collection;
    req.db.collection(COLLECTION).deleteMany({}, function (err, result) {
        if (err) {
            req.handleError(res, err.message, "Failed to delete the requested resource");
        } else {
            res.status(204).json(result).end();
        }
    });
};<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */
var async = require('async');
var UserDao = require('./../dao').UserDao;
var TeamDao = require('./../dao').TeamDao;

/**
 * Render index view
 */
exports.list = function(req, res) {

    UserDao.getList({
        criteria: {
            teams: {
                $elemMatch: {
                    id: req.user.team_now
                }
            }
        }
    }, {
        'createdTime': '-1'
    }, function(err, list) {
        if (!err) {
            return res.render('members/list', {
                user: req.user,
                members: list,
                url: req.protocol + '://' + req.headers.host,
                nav_members: true
            });
        } else {
            return res.render('500', {
                layout: 'error-layout',
                message: err.message,
                error: err
            });
        }
    });

};

/**
 * 添加新的成员到团队
 */
exports.add = function(req, res) {

    async.auto({
        getUserByEmail: function(cb) { //验证用户是否存在
            UserDao.getModelByQuery({
                email: req.body.email
            }, cb);
        },
        getTeamById: function(cb) { //获取该团队
            TeamDao.findById({
                _id: req.user.team_now
            }, cb);
        },
        isUserInTeam: ['getUserByEmail', 'getTeamById', function(cb, results) {
            var resUser = results.getUserByEmail,
                resTeam = results.getTeamById;
            if (resUser && resTeam) { //用户存在,且成功查询到团队信息
                //检查该用户是否已经加入该团队
                var teams = resUser.teams,
                    j = 0;
                for (var i = 0; i < teams.length; i++) {
                    if (req.user.team_now.toString() === teams[i].id.toString()) {
                        j++;
                    }
                }

                if (j) {
                    cb();
                } else {
                    var resTeamNow = resUser.team_now;
                    if (!resTeamNow) { //若该用户尚无team_now
                        resTeamNow = resTeam._id;
                    }
                    //在用户的信息中加入该团队
                    UserDao.update({
                        email: req.body.email
                    }, {
                        $set: {
                            team_now: resTeamNow
                        },
                        $push: {
                            teams: {
                                name: resTeam.name,
                                id: resTeam._id
                            }
                        }
                    }, cb);
                }

            }
        }]
    }, function(cb, results) {
        if (!results.getUserByEmail || !results.getTeamById || !results.isUserInTeam) {
            return res.errMsg([{
                msg: '添加失败，请稍后重试'
            }]);
        } else {
            return res.sucMsg();
        }
    });

};
<commit_msg>Bug fixes:   Verify email when adding member in team;<commit_after>/**
 * Module dependencies.
 */
var async = require('async');
var UserDao = require('./../dao').UserDao;
var TeamDao = require('./../dao').TeamDao;

/**
 * Render index view
 */
exports.list = function(req, res) {

    UserDao.getList({
        criteria: {
            teams: {
                $elemMatch: {
                    id: req.user.team_now
                }
            }
        }
    }, {
        'createdTime': '-1'
    }, function(err, list) {
        if (!err) {
            return res.render('members/list', {
                user: req.user,
                members: list,
                url: req.protocol + '://' + req.headers.host,
                nav_members: true
            });
        } else {
            return res.render('500', {
                layout: 'error-layout',
                message: err.message,
                error: err
            });
        }
    });

};

/**
 * 添加新的成员到团队
 */
exports.add = function(req, res) {

    async.auto({
        getUserByEmail: function(cb) { //验证用户是否存在
            UserDao.getModelByQuery({
                email: req.body.email
            }, cb);
        },
        getTeamById: function(cb) { //获取该团队
            TeamDao.findById({
                _id: req.user.team_now
            }, cb);
        },
        isUserInTeam: ['getUserByEmail', 'getTeamById', function(cb, results) {
            var resUser = results.getUserByEmail,
                resTeam = results.getTeamById;

            if (resUser && resTeam) { //用户存在,且成功查询到团队信息
                //检查该用户是否已经加入该团队
                var teams = resUser.teams,
                    j = 0;
                for (var i = 0; i < teams.length; i++) {
                    if (req.user.team_now.toString() === teams[i].id.toString()) {
                        j++;
                    }
                }

                if (j) {
                    cb();
                } else {
                    var resTeamNow = resUser.team_now;
                    if (!resTeamNow) { //若该用户尚无team_now
                        resTeamNow = resTeam._id;
                    }
                    //在用户的信息中加入该团队
                    UserDao.update({
                        email: req.body.email
                    }, {
                        $set: {
                            team_now: resTeamNow
                        },
                        $push: {
                            teams: {
                                name: resTeam.name,
                                id: resTeam._id
                            }
                        }
                    }, cb);
                }

            }else{
                cb();
            }
        }]
    }, function(cb, results) {
        if (!results.getUserByEmail || !results.getTeamById || !results.isUserInTeam) {
            return res.errMsg([{
                msg: '添加失败，请稍后重试'
            }]);
        } else {
            return res.sucMsg();
        }
    });

};
<|endoftext|>


<commit_before>app.controller('message', function ($scope, $timeout, $filter) {
	$scope.header="Message!!";

	var asciiToHex = function(ascii){
		hex = ascii.charCodeAt(0).toString(16);

		return hex;
	}

	$scope.m = [];
	$scope.process = function(){
		$scope.m = [];
		m = $scope.message.split('');
		for(i=0;i<$scope.message.length;i++){
			$scope.m.push({ascii:m[i],hex:asciiToHex(m[i])});
		}

	}

	console.log($scope.m);

});
<commit_msg>modified hex converstion functions<commit_after>app.controller('message', function ($scope, $timeout, $filter) {
	$scope.header="Message!!";

	var asciiToHex = function(ascii){
		return ascii.charCodeAt(0).toString(16);
	}

	var hexToascii = function(hex){
		return String.fromCharCode(parseInt(n, 16));
	}

	$scope.m = [];
	$scope.process = function(){
		$scope.m = [];
		m = $scope.message.split('');
		for(i=0;i<$scope.message.length;i++){
			$scope.m.push({ascii:m[i],hex:asciiToHex(m[i])});
		}

	}

	console.log($scope.m);

});
<|endoftext|>


<commit_before>/*
Copyright 2015 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var MatrixClientPeg = require("./MatrixClientPeg");
var dis = require("./dispatcher");

var reject = function(msg) {
    return {
        error: msg
    };
};

var success = function(promise) {
    return {
        promise: promise
    };
};

var commands = {
    // Change your nickname
    nick: function(room_id, args) {
        if (args) {
            return success(
                MatrixClientPeg.get().setDisplayName(args)
            );
        }
        return reject("Usage: /nick <display_name>");
    },

    // Invite a user
    invite: function(room_id, args) {
        if (args) {
            var matches = args.match(/^(\S+)$/);
            if (matches) {
                return success(
                    MatrixClientPeg.get().invite(room_id, matches[1])
                );
            }
        }
        return reject("Usage: /invite <userId>");
    },

    // Join a room
    join: function(room_id, args) {
        if (args) {
            var matches = args.match(/^(\S+)$/);
            if (matches) {
                var room_alias = matches[1];
                // Try to find a room with this alias
                var rooms = MatrixClientPeg.get().getRooms();
                var roomId;
                for (var i = 0; i < rooms.length; i++) {
                    var aliasEvents = rooms[i].currentState.getStateEvents(
                        "m.room.aliases"
                    );
                    for (var j = 0; j < aliasEvents.length; j++) {
                        var aliases = aliasEvents[j].getContent().aliases || [];
                        for (var k = 0; k < aliases.length; k++) {
                            if (aliases[k] === room_alias) {
                                roomId = rooms[i].roomId;
                                break;
                            }
                        }
                        if (roomId) { break; }
                    }
                    if (roomId) { break; }
                }
                if (roomId) { // we've already joined this room, view it.
                    dis.dispatch({
                        action: 'view_room',
                        room_id: roomId
                    });
                    return success();
                }
                else {
                    // attempt to join this alias.
                    return success(
                        MatrixClientPeg.get().joinRoom(room_alias).done(
                        function(room) {
                            dis.dispatch({
                                action: 'view_room',
                                room_id: room.roomId
                            });
                        }, function(err) {
                            console.error(
                                "Failed to join room: %s", JSON.stringify(err)
                            );
                        })
                    );
                }
            }
        }
        return reject("Usage: /join <room_alias> [NOT IMPLEMENTED]");
    },

    // Kick a user from the room with an optional reason
    kick: function(room_id, args) {
        if (args) {
            var matches = args.match(/^(\S+?)( +(.*))?$/);
            if (matches) {
                return success(
                    MatrixClientPeg.get().kick(room_id, matches[1], matches[3])
                );
            }
        }
        return reject("Usage: /kick <userId> [<reason>]");
    },

    // Ban a user from the room with an optional reason
    ban: function(room_id, args) {
        if (args) {
            var matches = args.match(/^(\S+?)( +(.*))?$/);
            if (matches) {
                return success(
                    MatrixClientPeg.get().ban(room_id, matches[1], matches[3])
                );
            }
        }
        return reject("Usage: /ban <userId> [<reason>]");
    },

    // Unban a user from the room
    unban: function(room_id, args) {
        if (args) {
            var matches = args.match(/^(\S+)$/);
            if (matches) {
                // Reset the user membership to "leave" to unban him
                return success(
                    MatrixClientPeg.get().unban(room_id, matches[1])
                );
            }
        }
        return reject("Usage: /unban <userId>");
    },

    // Define the power level of a user
    op: function(room_id, args) {
        if (args) {
            var matches = args.match(/^(\S+?)( +(\d+))?$/);
            var powerLevel = 50; // default power level for op
            if (matches) {
                var user_id = matches[1];
                if (matches.length === 4 && undefined !== matches[3]) {
                    powerLevel = parseInt(matches[3]);
                }
                if (powerLevel !== NaN) {
                    var room = MatrixClientPeg.get().getRoom(room_id);
                    if (!room) {
                        return reject("Bad room ID: " + room_id);
                    }
                    var powerLevelEvent = room.currentState.getStateEvents(
                        "m.room.power_levels", ""
                    );
                    return success(
                        MatrixClientPeg.get().setPowerLevel(
                            room_id, user_id, powerLevel, powerLevelEvent
                        )
                    );
                }
            }
        }
        return reject("Usage: /op <userId> [<power level>]");
    },

    // Reset the power level of a user
    deop: function(room_id, args) {
        if (args) {
            var matches = args.match(/^(\S+)$/);
            if (matches) {
                var room = MatrixClientPeg.get().getRoom(room_id);
                if (!room) {
                    return reject("Bad room ID: " + room_id);
                }

                var powerLevelEvent = room.currentState.getStateEvents(
                    "m.room.power_levels", ""
                );
                return success(
                    MatrixClientPeg.get().setPowerLevel(
                        room_id, args, undefined, powerLevelEvent
                    )
                );
            }
        }
        return reject("Usage: /deop <userId>");
    }
};

module.exports = {
    /**
     * Process the given text for /commands and perform them.
     * @param {string} roomId The room in which the command was performed.
     * @param {string} input The raw text input by the user.
     * @return {Object|null} An object with the property 'error' if there was an error
     * processing the command, or 'promise' if a request was sent out.
     * Returns null if the input didn't match a command.
     */
    processInput: function(roomId, input) {
        // trim any trailing whitespace, as it can confuse the parser for
        // IRC-style commands
        input = input.replace(/\s+$/, "");
        if (input[0] === "/" && input[1] !== "/") {
            var bits = input.match(/^(\S+?)( +(.*))?$/);
            var cmd = bits[1].substring(1).toLowerCase();
            var args = bits[3];
            if (commands[cmd]) {
                return commands[cmd](roomId, args);
            }
        }
        return null; // not a command
    }
};<commit_msg>/topic<commit_after>/*
Copyright 2015 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var MatrixClientPeg = require("./MatrixClientPeg");
var dis = require("./dispatcher");

var reject = function(msg) {
    return {
        error: msg
    };
};

var success = function(promise) {
    return {
        promise: promise
    };
};

var commands = {
    // Change your nickname
    nick: function(room_id, args) {
        if (args) {
            return success(
                MatrixClientPeg.get().setDisplayName(args)
            );
        }
        return reject("Usage: /nick <display_name>");
    },

    // Change the room topic
    topic: function(room_id, args) {
        if (args) {
            return success(
                MatrixClientPeg.get().setRoomTopic(room_id, args)
            );
        }
        return reject("Usage: /topic <topic>");
    },

    // Invite a user
    invite: function(room_id, args) {
        if (args) {
            var matches = args.match(/^(\S+)$/);
            if (matches) {
                return success(
                    MatrixClientPeg.get().invite(room_id, matches[1])
                );
            }
        }
        return reject("Usage: /invite <userId>");
    },

    // Join a room
    join: function(room_id, args) {
        if (args) {
            var matches = args.match(/^(\S+)$/);
            if (matches) {
                var room_alias = matches[1];
                // Try to find a room with this alias
                var rooms = MatrixClientPeg.get().getRooms();
                var roomId;
                for (var i = 0; i < rooms.length; i++) {
                    var aliasEvents = rooms[i].currentState.getStateEvents(
                        "m.room.aliases"
                    );
                    for (var j = 0; j < aliasEvents.length; j++) {
                        var aliases = aliasEvents[j].getContent().aliases || [];
                        for (var k = 0; k < aliases.length; k++) {
                            if (aliases[k] === room_alias) {
                                roomId = rooms[i].roomId;
                                break;
                            }
                        }
                        if (roomId) { break; }
                    }
                    if (roomId) { break; }
                }
                if (roomId) { // we've already joined this room, view it.
                    dis.dispatch({
                        action: 'view_room',
                        room_id: roomId
                    });
                    return success();
                }
                else {
                    // attempt to join this alias.
                    return success(
                        MatrixClientPeg.get().joinRoom(room_alias).done(
                        function(room) {
                            dis.dispatch({
                                action: 'view_room',
                                room_id: room.roomId
                            });
                        }, function(err) {
                            console.error(
                                "Failed to join room: %s", JSON.stringify(err)
                            );
                        })
                    );
                }
            }
        }
        return reject("Usage: /join <room_alias> [NOT IMPLEMENTED]");
    },

    // Kick a user from the room with an optional reason
    kick: function(room_id, args) {
        if (args) {
            var matches = args.match(/^(\S+?)( +(.*))?$/);
            if (matches) {
                return success(
                    MatrixClientPeg.get().kick(room_id, matches[1], matches[3])
                );
            }
        }
        return reject("Usage: /kick <userId> [<reason>]");
    },

    // Ban a user from the room with an optional reason
    ban: function(room_id, args) {
        if (args) {
            var matches = args.match(/^(\S+?)( +(.*))?$/);
            if (matches) {
                return success(
                    MatrixClientPeg.get().ban(room_id, matches[1], matches[3])
                );
            }
        }
        return reject("Usage: /ban <userId> [<reason>]");
    },

    // Unban a user from the room
    unban: function(room_id, args) {
        if (args) {
            var matches = args.match(/^(\S+)$/);
            if (matches) {
                // Reset the user membership to "leave" to unban him
                return success(
                    MatrixClientPeg.get().unban(room_id, matches[1])
                );
            }
        }
        return reject("Usage: /unban <userId>");
    },

    // Define the power level of a user
    op: function(room_id, args) {
        if (args) {
            var matches = args.match(/^(\S+?)( +(\d+))?$/);
            var powerLevel = 50; // default power level for op
            if (matches) {
                var user_id = matches[1];
                if (matches.length === 4 && undefined !== matches[3]) {
                    powerLevel = parseInt(matches[3]);
                }
                if (powerLevel !== NaN) {
                    var room = MatrixClientPeg.get().getRoom(room_id);
                    if (!room) {
                        return reject("Bad room ID: " + room_id);
                    }
                    var powerLevelEvent = room.currentState.getStateEvents(
                        "m.room.power_levels", ""
                    );
                    return success(
                        MatrixClientPeg.get().setPowerLevel(
                            room_id, user_id, powerLevel, powerLevelEvent
                        )
                    );
                }
            }
        }
        return reject("Usage: /op <userId> [<power level>]");
    },

    // Reset the power level of a user
    deop: function(room_id, args) {
        if (args) {
            var matches = args.match(/^(\S+)$/);
            if (matches) {
                var room = MatrixClientPeg.get().getRoom(room_id);
                if (!room) {
                    return reject("Bad room ID: " + room_id);
                }

                var powerLevelEvent = room.currentState.getStateEvents(
                    "m.room.power_levels", ""
                );
                return success(
                    MatrixClientPeg.get().setPowerLevel(
                        room_id, args, undefined, powerLevelEvent
                    )
                );
            }
        }
        return reject("Usage: /deop <userId>");
    }
};

module.exports = {
    /**
     * Process the given text for /commands and perform them.
     * @param {string} roomId The room in which the command was performed.
     * @param {string} input The raw text input by the user.
     * @return {Object|null} An object with the property 'error' if there was an error
     * processing the command, or 'promise' if a request was sent out.
     * Returns null if the input didn't match a command.
     */
    processInput: function(roomId, input) {
        // trim any trailing whitespace, as it can confuse the parser for
        // IRC-style commands
        input = input.replace(/\s+$/, "");
        if (input[0] === "/" && input[1] !== "/") {
            var bits = input.match(/^(\S+?)( +(.*))?$/);
            var cmd = bits[1].substring(1).toLowerCase();
            var args = bits[3];
            if (commands[cmd]) {
                return commands[cmd](roomId, args);
            }
        }
        return null; // not a command
    }
};<|endoftext|>