Source: /home/shernshiou/Programming/github/node-telegram-bot/lib/Bot.js

Source: /home/shernshiou/Programming/github/node-telegram-bot/lib/Bot.js

'use strict'

var EventEmitter = require('events').EventEmitter
  , debug = require('debug')('node-telegram-bot')
  , util = require('util')
  , request = require('request')
  , fs = require('fs')
  , path = require('path')
  , qs = require('querystring')
  , Q = require('q')
  , mime = require('mime');

/**
 * Constructor for Telegram Bot API Client.
 *
 * @class Bot
 * @constructor
 * @param {Object} options        Configurations for the client
 * @param {String} options.token  Bot token
 *
 * @see https://core.telegram.org/bots/api
 */
function Bot(options) {
  this.base_url = 'https://api.telegram.org/';
  this.polling = false;
  this.id = '';
  this.first_name = '';
  this.username = '';
  this.token = options.token;
  this.offset = options.offset ? options.offset : 0;
  this.interval = options.interval ? options.interval : 500;
	this.webhook = options.webhook ? options.webhook : false;
  this.parseCommand = options.parseCommand ? options.parseCommand : true;
  this.maxAttempts = options.maxAttempts ? options.maxAttempts : 5;
  this.polling = false;
}

util.inherits(Bot, EventEmitter);

/**
 * This callback occur after client request for a certain webservice.
 *
 * @callback Bot~requestCallback
 * @param {Error}   Error during request
 * @param {Object}  Response from Telegram service
 */
Bot.prototype._get = function (options, callback) {
  var self = this;
  var url = this.base_url + 'bot' + this.token + '/' + options.method;

  if (options.params) {
    url += '?' + qs.stringify(options.params);
  }

  var attempt = 1;

  function retry() {
    request.get({
      url: url,
      json: true
    }, function (err, res, body) {
      if (err) {
        if (err.code === 'ENOTFOUND' && attempt < self.maxAttempts) {
        ++attempt;
        self.emit('retry', attempt);
        retry();
        } else {
          callback(err);
        }
      } else {
        callback(null, body);
      }
    });
  }

  retry();

  return this;
};

/**
 * To perform multipart request e.g. file upload
 *
 * @callback Bot~requestCallback
 * @param {Error}   Error during request
 * @param {Object}  Response from Telegram service
 */
Bot.prototype._multipart = function (options, callback) {
  var self = this;
  var url = this.base_url + 'bot' + this.token + '/' + options.method;

  var attempt = 1;

  function retry() {
    var req = request.post(url, function (err, res, body) {
      if (err) {
        if (err.code === 'ENOTFOUND' && attempt < self.maxAttempts) {
          ++attempt;
          self.emit('retry', attempt);
          retry();
        } else {
          callback(err);
        }
      } else {
        var contentType = res.headers['content-type'];

        if (contentType.indexOf('application/json') >= 0) {
          try {
            body = JSON.parse(body);
          } catch (e) {
            callback(e, body);
          }
        }

        callback(null, body);
      }
    });

    var form = req.form()
      , filename
      , type
      , stream
      , contentType;

    var arr = Object.keys(options.files);

    if (arr.indexOf('stream') > -1) {
      type = options.files['type'];
      filename = options.files['filename'];
      stream = options.files['stream'];
      contentType = options.files['contentType'];
    } else {
      arr.forEach(function (key) {
        var file = options.files[key];
        type = key;
        filename = path.basename(file);
        stream = fs.createReadStream(file);
        contentType = mime.lookup(file);
      })
    }

    form.append(type, stream, {
      filename: filename,
      contentType: contentType
    });

    Object.keys(options.params).forEach(function (key) {
      if (options.params[key]) {
        form.append(key, options.params[key]);
      }
    });
  }

  retry();

  return this;
};

/**
 * Temporary solution to set webhook
 *
 * @param {Error}   Error during request
 * @param {Object}  Response from Telegram service
 */
Bot.prototype._setWebhook = function (webhook) {
  var self = this;
  var url = this.base_url + 'bot' + this.token + '/setWebhook' + "?" + qs.stringify({url: webhook});

  request.get({
    url: url,
    json: true
  }, function (err, res, body) {
    if (!err && res.statusCode === 200) {
      if (body.ok) {
      	debug("Set webhook to " + self.webhook);
			} else {
				debug("Body not ok");
				debug(body);
			}
    } else if(res && res.hasOwnProperty('statusCode') && res.statusCode === 401){
      debug(err);
      debug("Failed to set webhook with code" + res.statusCode);
    } else {
			debug(err);
			debug("Failed to set webhook with code" + res.statusCode);
		}
  });
}

/**
 * Start polling for messages
 *
 * @return {Bot} Self
 */
Bot.prototype._poll = function () {
  var self = this;
  var url = this.base_url + 'bot' + this.token + '/getUpdates?timeout=60&offset=' + this.offset;

  request.get({
    url: url,
    json: true
  }, function (err, res, body) {
    if (err) {
      self.emit('error', err);
    } else if (res.statusCode === 200) {
      if (body.ok) {
        body.result.forEach(function (msg) {
          if (msg.update_id >= self.offset) {
            self.offset = msg.update_id + 1;

            if (self.parseCommand) {
              if (msg.message.text && msg.message.text.charAt(0) === '/') {
                var command = msg.message.text.split(' ', 2)[0];
                command = command.replace(/[^a-zA-Z0-9 ]/g, "");
                if (msg.message.text.split(' ')[1]) {
                  var arg = msg.message.text.split(' ');
                  arg.shift();
                  self.emit(command, msg.message, arguments);
                } else{
                  self.emit(command, msg.message);
                }
              }
            }

            self.emit('message', msg.message);
          }
        });
      }
    } else if(res && res.hasOwnProperty('statusCode') && res.statusCode === 401) {
      self.emit('error', 'Invalid token');
    } else {
      self.emit('error', 'Unknown error, ' + err.stack);
    }

    if (self.polling) {
      self._poll();
    }
  });

  return this;
};

/**
 * Bot start receiving activities
 *
 * @return {Bot} Self
 */
Bot.prototype.start = function () {
  var self = this;

	if (self.webhook) {
		self._setWebhook(this.webhook);
	} else {
	  self._poll();
  	self.polling = true;
	}

  return self;
};

/**
 * End polling for messages
 *
 * @return {Bot} Self
 */
Bot.prototype.stop = function () {
  var self = this;
	self._setWebhook("");
  self.polling = false;

  return self;
};

/**
 * Returns basic information about the bot in form of a User object.
 *
 * @param {Bot~requestCallback} callback    The callback that handles the response.
 * @return {Promise}  Q Promise
 *
 * @see https://core.telegram.org/bots/api#getme
 */
Bot.prototype.getMe = function (callback) {
  var self = this
    , deferred = Q.defer();

  this._get({ method: 'getMe' }, function (err, res) {
    if (err) {
      return deferred.reject(err);
    }

    if (res.ok) {
      self.id = res.result.id;
      self.first_name = res.result.first_name;
      self.username = res.result.username;

      deferred.resolve(res.result);
    } else {
      deferred.reject(res);
    }
  });

  return deferred.promise.nodeify(callback);
};

/**
 * Use this method to get a list of profile pictures for a user.
 *
 * @param {Object}              options           Options
 * @param {Integer}             options.user_id   Unique identifier of the target user
 * @param {String=}             options.offset    Sequential number of the first photo to be returned. By default, all photos are returned.
 * @param {Integer=}            options.limit     Limits the number of photos to be retrieved. Values between 1—100 are accepted. Defaults to 100.
 * @param {Bot~requestCallback} callback          The callback that handles the response.
 * @return {Promise}  Q Promise
 *
 * @see https://core.telegram.org/bots/api#getuserprofilephotos
 */
Bot.prototype.getUserProfilePhotos = function (options, callback) {
  var self = this
    , deferred = Q.defer();

  this._get({
    method: 'getUserProfilePhotos',
    params: {
      user_id: options.user_id,
      offset: options.offset,
      limit: options.limit
    }
   }, function (err, res) {
    if (err) {
      return deferred.reject(err);
    }

    if (res.ok) {
      deferred.resolve(res.result);
    } else {
      deferred.reject(res);
    }
  });

  return deferred.promise.nodeify(callback);
};

/**
 * Use this method to get basic info about a file and prepare it for downloading. For the moment, bots can download files of up to 20MB in size.
 *
 * @param {Object}              options           Options
 * @param {String}              options.file_id   File identifier to get info about
 * @param {String=}             options.dir       Directory the file to be stored (if it is not specified, no file willbe downloaded)
 * @param {Bot~requestCallback} callback          The callback that handles the response.
 * @return {Promise}  Q Promise
 *
 * @see https://core.telegram.org/bots/api#getfile
 */
Bot.prototype.getFile = function (options, callback) {
  var self = this
    , deferred = Q.defer();

  this._get({
    method: 'getFile',
    params: {
      file_id: options.file_id
    }
  }, function (err, res) {
    if (err) {
      return deferred.reject(err);
    }

    if (res.ok) {
      var filename = path.basename(res.result.file_path);
      if (options.dir) {
        var filepath  = path.join(options.dir, filename);
        var url = self.base_url + 'file/bot' + self.token + '/' + res.result.file_path;
        var destination = fs.createWriteStream(filepath);
        request(url)
        .pipe(destination)
        .on('finish', function () {
          deferred.resolve({
            destination: filepath,
            url: url
          });
        })
        .on('error', function(error){
          deferred.reject(error);
        });
      } else {
        deferred.resolve({
          url: url
        });
      }
    } else {
      deferred.reject(res);
    }
  });

  return deferred.promise.nodeify(callback);
};

/**
 * Use this method to send text messages.
 *
 * @param {Object}              options           Options
 * @param {Integer}             options.chat_id   Unique identifier for the message recipient — User or GroupChat id
 * @param {String}              options.text      Text of the message to be sent
 * @param {String}              options.parse_mode  Send Markdown, if you want Telegram apps to show bold, italic and inline URLs in your bot's message.
 * @param {Boolean=}            options.disable_web_page_preview    Disables link previews for links in this message
 * @param {Integer=}            options.reply_to_message_id   If the message is a reply, ID of the original message
 * @param {Object=}             options.reply_markup    Additional interface options. {@link https://core.telegram.org/bots/api/#replykeyboardmarkup| ReplyKeyboardMarkup}
 * @param {Bot~requestCallback} callback          The callback that handles the response.
 * @return {Promise}  Q Promise
 *
 * @see https://core.telegram.org/bots/api#sendmessage
 */
Bot.prototype.sendMessage = function (options, callback) {
  var self = this
    , deferred = Q.defer();

  this._get({
    method: 'sendMessage',
    params: {
      chat_id: options.chat_id,
      text: options.text,
      parse_mode: options.parse_mode,
      disable_web_page_preview: options.disable_web_page_preview,
      reply_to_message_id: options.reply_to_message_id,
      reply_markup: JSON.stringify(options.reply_markup)
    }
  }, function (err, res) {
    if (err) {
      return deferred.reject(err);
    }

    if (res.ok) {
      deferred.resolve(res.result);
    } else {
      deferred.reject(res);
    }
  });

  return deferred.promise.nodeify(callback);
};

/**
 * Use this method to forward messages of any kind.
 *
 * @param {Object}              options           Options
 * @param {Integer}             options.chat_id   Unique identifier for the message recipient — User or GroupChat id
 * @param {Integer}             options.from_chat_id    Unique identifier for the chat where the original message was sent — User or GroupChat id
 * @param {Integer}             options.message_id    Unique message identifier
 * @param {Bot~requestCallback} callback          The callback that handles the response.
 * @return {Promise}  Q Promise
 *
 * @see https://core.telegram.org/bots/api#forwardmessage
 */
Bot.prototype.forwardMessage = function (options, callback) {
  var self = this
    , deferred = Q.defer();

  this._get({
    method: 'forwardMessage',
    params: {
      chat_id: options.chat_id,
      from_chat_id: options.from_chat_id,
      message_id: options.message_id
    }
  }, function (err, res) {
    if (err) {
      return deferred.reject(err);
    }

    if (res.ok) {
      deferred.resolve(res.result);
    } else {
      deferred.reject(res);
    }
  });

  return deferred.promise.nodeify(callback);
};

/**
 * Use this method to send photos.
 *
 * @param {Object}              options           Options
 * @param {Integer}             options.chat_id   Unique identifier for the message recipient — User or GroupChat id
 * @param {String}              options.photo     Path to photo file (Library will create a stream if the path exist)
 * @param {String=}             options.file_id   If file_id is passed, method will use this instead
 * @param {String=}             options.caption   Photo caption (may also be used when resending photos by file_id).
 * @param {Integer=}            options.reply_to_message_id   If the message is a reply, ID of the original message
 * @param {Object=}             options.reply_markup    Additional interface options. {@link https://core.telegram.org/bots/api/#replykeyboardmarkup| ReplyKeyboardMarkup}
 * @param {Bot~requestCallback} callback          The callback that handles the response.
 * @return {Promise}  Q Promise
 *
 * @see https://core.telegram.org/bots/api#sendphoto
 */
Bot.prototype.sendPhoto = function (options, callback) {
  var self = this
    , deferred = Q.defer();

  if (options.file_id) {
    this._get({
      method: 'sendPhoto',
      params: {
        chat_id: options.chat_id,
        caption: options.caption,
        photo: options.file_id,
        reply_to_message_id: options.reply_to_message_id,
        reply_markup: JSON.stringify(options.reply_markup)
      }
    }, function (err, res) {
      if (err) {
        return deferred.reject(err);
      }

      if (res.ok) {
        deferred.resolve(res.result);
      } else {
        deferred.reject(res);
      }
    });
  } else {
    var files;
    if (options.files.stream) {
      files = {
        type: 'photo',
        filename: options.files.filename,
        contentType: options.files.contentType,
        stream: options.files.stream
      }
    } else {
      files = {
       photo: options.files.photo
      }
    }

    this._multipart({
      method: 'sendPhoto',
      params: {
        chat_id: options.chat_id,
        caption: options.caption,
        reply_to_message_id: options.reply_to_message_id,
        reply_markup: JSON.stringify(options.reply_markup)
      },
      files: files
    }, function (err, res) {
      if (err) {
        return deferred.reject(err);
      }

      if (res.ok) {
        deferred.resolve(res.result);
      } else {
        deferred.reject(res);
      }
    });
  }

  return deferred.promise.nodeify(callback);
};

/**
 * Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message.
 *
 * @param {Object}              options           Options
 * @param {Integer}             options.chat_id   Unique identifier for the message recipient — User or GroupChat id
 * @param {String}              options.audio     Path to audio file (Library will create a stream if the path exist)
 * @param {String=}             options.file_id   If file_id is passed, method will use this instead
 * @param {Integer=}            options.reply_to_message_id   If the message is a reply, ID of the original message
 * @param {Object=}             options.reply_markup    Additional interface options. {@link https://core.telegram.org/bots/api/#replykeyboardmarkup| ReplyKeyboardMarkup}
 * @param {Bot~requestCallback} callback          The callback that handles the response.
 * @return {Promise}  Q Promise
 *
 * @see https://core.telegram.org/bots/api#sendaudio
 */
Bot.prototype.sendAudio = function (options, callback) {
  var self = this
    , deferred = Q.defer();

  if (options.file_id) {
    this._get({
      method: 'sendAudio',
      params: {
        chat_id: options.chat_id,
        audio: options.file_id,
        reply_to_message_id: options.reply_to_message_id,
        reply_markup: JSON.stringify(options.reply_markup)
      }
    }, function (err, res) {
      if (err) {
        return deferred.reject(err);
      }

      if (res.ok) {
        deferred.resolve(res.result);
      } else {
        deferred.reject(res);
      }
    });
  } else {
    var files;
    if (options.files.stream) {
      files = {
        type: 'audio',
        filename: options.files.filename,
        contentType: options.files.contentType,
        stream: options.files.stream
      }
    } else if (mime.lookup(options.files.audio) !== 'audio/ogg') {
      return Q.reject(new Error('Invalid file type'))
      .nodeify(callback);
    } else {
      files = {
        audio: options.files.audio
      }
    }

    this._multipart({
      method: 'sendAudio',
      params: {
        chat_id: options.chat_id,
        reply_to_message_id: options.reply_to_message_id,
        reply_markup: JSON.stringify(options.reply_markup)
      },
      files: files
    }, function (err, res) {
      if (err) {
        return deferred.reject(err);
      }

      if (res.ok) {
        deferred.resolve(res.result);
      } else {
        deferred.reject(res);
      }
    });
  }

  return deferred.promise.nodeify(callback);
};

/**
 * Use this method to send general files.
 *
 * @param {Object}              options           Options
 * @param {Integer}             options.chat_id   Unique identifier for the message recipient — User or GroupChat id
 * @param {String}              options.document  Path to document file (Library will create a stream if the path exist)
 * @param {String=}             options.file_id   If file_id is passed, method will use this instead
 * @param {Integer=}            options.reply_to_message_id   If the message is a reply, ID of the original message
 * @param {Object=}             options.reply_markup    Additional interface options. {@link https://core.telegram.org/bots/api/#replykeyboardmarkup| ReplyKeyboardMarkup}
 * @param {Bot~requestCallback} callback          The callback that handles the response.
 * @return {Promise}  Q Promise
 *
 * @see https://core.telegram.org/bots/api#senddocument
 */
Bot.prototype.sendDocument = function (options, callback) {
  var self = this
    , deferred = Q.defer();

  if (options.file_id) {
    this._get({
      method: 'sendDocument',
      params: {
        chat_id: options.chat_id,
        document: options.file_id,
        reply_to_message_id: options.reply_to_message_id,
        reply_markup: JSON.stringify(options.reply_markup)
      }
    }, function (err, res) {
      if (err) {
        return deferred.reject(err);
      }

      if (res.ok) {
        deferred.resolve(res.result);
      } else {
        deferred.reject(res);
      }
    });
  } else {
    var files;
    if (options.files.stream) {
      files = {
        type: 'document',
        filename: options.files.filename,
        contentType: options.files.contentType,
        stream: options.files.stream
      }
    } else {
      files = {
        document: options.files.document
      }
    }

    this._multipart({
      method: 'sendDocument',
      params: {
        chat_id: options.chat_id,
        reply_to_message_id: options.reply_to_message_id,
        reply_markup: JSON.stringify(options.reply_markup)
      },
      files: files
    }, function (err, res) {
      if (err) {
        return deferred.reject(err);
      }

      if (res.ok) {
        deferred.resolve(res.result);
      } else {
        deferred.reject(res);
      }
    });
  }

  return deferred.promise.nodeify(callback);
};

/**
 * Use this method to send .webp stickers.
 *
 * @param {Object}              options           Options
 * @param {Integer}             options.chat_id   Unique identifier for the message recipient — User or GroupChat id
 * @param {String}              options.sticker   Path to sticker file (Library will create a stream if the path exist)
 * @param {String=}             options.file_id   If file_id is passed, method will use this instead
 * @param {Integer=}            options.reply_to_message_id   If the message is a reply, ID of the original message
 * @param {Object=}             options.reply_markup    Additional interface options. {@link https://core.telegram.org/bots/api/#replykeyboardmarkup| ReplyKeyboardMarkup}
 * @param {Bot~requestCallback} callback          The callback that handles the response.
 * @return {Promise}  Q Promise
 *
 * @see https://core.telegram.org/bots/api#sendsticker
 */
Bot.prototype.sendSticker = function (options, callback) {
  var self = this
    , deferred = Q.defer();

  if (options.file_id) {
    this._get({
      method: 'sendSticker',
      params: {
        chat_id: options.chat_id,
        sticker: options.file_id,
        reply_to_message_id: options.reply_to_message_id,
        reply_markup: JSON.stringify(options.reply_markup)
      }
    }, function (err, res) {
      if (err) {
        return deferred.reject(err);
      }

      if (res.ok) {
        deferred.resolve(res.result);
      } else {
        deferred.reject(res);
      }
    });
  } else {
    if (mime.lookup(options.files.sticker) !== 'image/webp') {
      return Q.reject(new Error('Invalid file type'))
      .nodeify(callback);
    }

    this._multipart({
      method: 'sendSticker',
      params: {
        chat_id: options.chat_id,
        reply_to_message_id: options.reply_to_message_id,
        reply_markup: JSON.stringify(options.reply_markup)
      },
      files: {
        sticker: options.files.sticker
      }
    }, function (err, res) {
      if (err) {
        return deferred.reject(err);
      }

      if (res.ok) {
        deferred.resolve(res.result);
      } else {
        deferred.reject(res);
      }
    });
  }

  return deferred.promise.nodeify(callback);
};

/**
 * Use this method to send video files, Telegram clients support mp4 video.
 *
 * @param {Object}              options           Options
 * @param {Integer}             options.chat_id   Unique identifier for the message recipient — User or GroupChat id
 * @param {String}              options.video   Path to video file (Library will create a stream if the path exist)
 * @param {String=}             options.file_id   If file_id is passed, method will use this instead
 * @param {Integer=}            options.reply_to_message_id   If the message is a reply, ID of the original message
 * @param {Object=}             options.reply_markup    Additional interface options. {@link https://core.telegram.org/bots/api/#replykeyboardmarkup| ReplyKeyboardMarkup}
 * @param {Bot~requestCallback} callback          The callback that handles the response.
 * @return {Promise}  Q Promise
 *
 * @see https://core.telegram.org/bots/api#sendvideo
 */
Bot.prototype.sendVideo = function (options, callback) {
  var self = this
    , deferred = Q.defer();

  if (options.file_id) {
    this._get({
      method: 'sendSticker',
      params: {
        chat_id: options.chat_id,
        video: options.file_id,
        reply_to_message_id: options.reply_to_message_id,
        reply_markup: JSON.stringify(options.reply_markup)
      }
    }, function (err, res) {
      if (err) {
        return deferred.reject(err);
      }

      if (res.ok) {
        deferred.resolve(res.result);
      } else {
        deferred.reject(res);
      }
    });
  } else {
    var files;
    if (options.files.stream) {
      files = {
        type: 'video',
        filename: options.files.filename,
        contentType: options.files.contentType,
        stream: options.files.stream
      }
    } else if (mime.lookup(options.files.video.filename) !== 'video/mp4') {
      return Q.reject(new Error('Invalid file type'))
      .nodeify(callback);
    } else {
      files = {
        video: options.files.video
      }
    }

    this._multipart({
      method: 'sendVideo',
      params: {
        chat_id: options.chat_id,
        reply_to_message_id: options.reply_to_message_id,
        reply_markup: JSON.stringify(options.reply_markup)
      },
      files: files
    }, function (err, res) {
      if (err) {
        return deferred.reject(err);
      }

      if (res.ok) {
        deferred.resolve(res.result);
      } else {
        deferred.reject(res);
      }
    });
  }

  return deferred.promise.nodeify(callback);
};

/**
 * Use this method to send point on the map.
 *
 * @param {Object}              options           Options
 * @param {Integer}             options.chat_id   Unique identifier for the message recipient — User or GroupChat id
 * @param {Float}               options.latitude  Latitude of location
 * @param {Float}               options.longitude Longitude of location
 * @param {Integer=}            options.reply_to_message_id   If the message is a reply, ID of the original message
 * @param {Object=}             options.reply_markup    Additional interface options. {@link https://core.telegram.org/bots/api/#replykeyboardmarkup| ReplyKeyboardMarkup}
 * @param {Bot~requestCallback} callback          The callback that handles the response.
 * @return {Promise}  Q Promise
 *
 * @see https://core.telegram.org/bots/api#sendlocation
 */
Bot.prototype.sendLocation = function (options, callback) {
  var self = this
    , deferred = Q.defer();

  this._get({
    method: 'sendLocation',
    params: {
      chat_id: options.chat_id,
      latitude: options.latitude,
      longitude: options.longitude,
      reply_to_message_id: options.reply_to_message_id,
      reply_markup: JSON.stringify(options.reply_markup)
    }
  }, function (err, res) {
    if (err) {
      return deferred.reject(err);
    }

    if (res.ok) {
      deferred.resolve(res.result);
    } else {
      deferred.reject(res);
    }
  });

  return deferred.promise.nodeify(callback);
};

/**
 * Use this method when you need to tell the user that something is happening on the bot's side.
 *
 * @param {Object}              options           Options
 * @param {Integer}             options.chat_id   Unique identifier for the message recipient — User or GroupChat id
 * @param {String}              options.action    Type of action to broadcast.
 * @param {Bot~requestCallback} callback          The callback that handles the response.
 * @return {Promise}  Q Promise
 *
 * @see https://core.telegram.org/bots/api#sendchataction
 */
Bot.prototype.sendChatAction = function (options, callback) {
  var self = this
    , deferred = Q.defer();

  this._get({
    method: 'sendChatAction',
    params: {
      chat_id: options.chat_id,
      action: options.action
    }
  }, function (err, res) {
    if (err) {
      return deferred.reject(err);
    }

    if (res.ok) {
      deferred.resolve(res.result);
    } else {
      deferred.reject(res);
    }
  });

  return deferred.promise.nodeify(callback);
};

module.exports = Bot;