├── index.js ├── .travis.yml ├── examples ├── 0477.ogg ├── logo.png ├── logo.webp ├── sendPhoto.js ├── poll.js ├── getFile.js ├── webhook.js ├── analyticsUseExample.js └── echoBotExample.js ├── .editorconfig ├── .gitignore ├── jsdoc.conf.json ├── test └── index.js ├── gulpfile.js ├── LICENSE ├── package.json ├── README.md └── lib └── Bot.js /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/Bot'); -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | - "0.11" 5 | - "iojs" -------------------------------------------------------------------------------- /examples/0477.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/depoio/node-telegram-bot/HEAD/examples/0477.ogg -------------------------------------------------------------------------------- /examples/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/depoio/node-telegram-bot/HEAD/examples/logo.png -------------------------------------------------------------------------------- /examples/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/depoio/node-telegram-bot/HEAD/examples/logo.webp -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /examples/sendPhoto.js: -------------------------------------------------------------------------------- 1 | 2 | var Bot = require('../lib/Bot') 3 | , fs = require('fs'); 4 | 5 | bot.sendPhoto({ 6 | chat_id: USER_ID, 7 | caption: 'Telegram Logo', 8 | files: { 9 | photo: './examples/logo.png' 10 | } 11 | }, function (err, msg) { 12 | console.log(err); 13 | console.log(msg); 14 | }); 15 | -------------------------------------------------------------------------------- /examples/poll.js: -------------------------------------------------------------------------------- 1 | var Bot = require('../lib/Bot'); 2 | 3 | var bot = new Bot({ 4 | token: 'TOKEN_HERE' 5 | }) 6 | .on('message', function (message) { 7 | console.log(message); 8 | }) 9 | .on('stop', function (message) { 10 | console.log('stop'); 11 | bot.stop(); 12 | }) 13 | .on('start', function (message) { 14 | bot.start(); 15 | }) 16 | .on('error', function (message) { 17 | console.log(message); 18 | }) 19 | .start(); 20 | -------------------------------------------------------------------------------- /examples/getFile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Bot = require('../lib/Bot'); 4 | 5 | var bot = new Bot({ 6 | token: 'TOKEN_HERE' 7 | }) 8 | .on('message', function (message) { 9 | console.log(message); 10 | if (message.sticker) { 11 | bot.getFile({ 12 | file_id: message.sticker.file_id, 13 | dir: '.' 14 | }) 15 | .then(function (res) { 16 | console.log(res); 17 | }); 18 | } 19 | }) 20 | .on('error', function (err) { 21 | console.error(err); 22 | }) 23 | .start(); 24 | -------------------------------------------------------------------------------- /examples/webhook.js: -------------------------------------------------------------------------------- 1 | var Bot = require('../lib/Bot'); 2 | 3 | var bot = new Bot({ 4 | token: 'TOKEN HERE' 5 | webhook: 'URL here' 6 | }).start(); 7 | 8 | // Telegram will send POST requests to the specified url. 9 | // Keep in mind (from Bots API): 10 | /* 11 | Notes 12 | 1. You will not be able to receive updates using getUpdates for as long as an outgoing webhook is set up. 13 | 2. We currently do not support self-signed certificates. 14 | 3. Ports currently supported for Webhooks: 443, 80, 88, 8443. 15 | */ 16 | -------------------------------------------------------------------------------- /examples/analyticsUseExample.js: -------------------------------------------------------------------------------- 1 | var Bot = require('../lib/Bot'); 2 | var tokenBotanIO = '1234qwer-tyuio-56789-asdf'; 3 | var tokenTelegram = '123456789:qwertyuiopasdf'; 4 | 5 | var bot = new Bot({ 6 | token: tokenTelegram 7 | }) 8 | .enableAnalytics(token) 9 | .on('message', function (message) { 10 | // Not necessary use analytics.track 11 | if (message.text == '/levelup') { 12 | this.analytics.track(message, 'Get new level'); 13 | } 14 | if (message.text == '/exit') { 15 | this.analytics.track(message, 'Left users'); 16 | } 17 | }}) 18 | .start(); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | .token 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Directory for instrumented libs generated by jsdoc 15 | doc 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directory 30 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 31 | node_modules 32 | 33 | # idea 34 | .idea/* 35 | -------------------------------------------------------------------------------- /jsdoc.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags" : { 3 | "allowUnknownTags" : true 4 | }, 5 | "plugins" : [], 6 | "templates" : { 7 | "cleverLinks" : false, 8 | "monospaceLinks" : false, 9 | "default": { 10 | "outputSourceFiles": true 11 | }, 12 | "dateFormat" : "ddd MMM Do YYYY", 13 | "outputSourceFiles" : true, 14 | "outputSourcePath" : true, 15 | "systemName" : "DocStrap", 16 | "footer" : "", 17 | "copyright" : "", 18 | "navType" : "vertical", 19 | "theme" : "cosmo", 20 | "linenums" : true, 21 | "collapseSymbols" : false, 22 | "inverseNav" : true, 23 | "highlightTutorialCode" : true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai') 2 | , nock = require('nock') 3 | , should = chai.should() 4 | , Bot = require('../lib/Bot'); 5 | 6 | var TOKEN = '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11'; 7 | 8 | describe('Telegram Bot client general test', function () { 9 | var bot; 10 | 11 | before(function (done) { 12 | bot = new Bot({ token: TOKEN }); 13 | done(); 14 | }); 15 | 16 | it('should instantiate Telegram Bot client with correct token and values', function (done) { 17 | bot.base_url.should.equal('https://api.telegram.org/'); 18 | bot.polling.should.equal(false); 19 | bot.token.should.equal(TOKEN); 20 | done(); 21 | }); 22 | 23 | it('should have the essential functions', function (done) { 24 | bot._get.should.exist; 25 | bot._multipart.should.exist; 26 | bot._poll.should.exist; 27 | done(); 28 | }); 29 | }); 30 | 31 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | , eslint = require('gulp-eslint') 3 | , mocha = require('gulp-mocha') 4 | , shell = require('gulp-shell') 5 | , ghpages = require('gh-pages') 6 | , path = require('path'); 7 | 8 | gulp.task('test', function () { 9 | return gulp.src('test/index.js', { read: false }) 10 | .pipe(mocha({reporter: 'spec'})); 11 | }); 12 | 13 | gulp.task('lint', function () { 14 | return gulp.src(['lib/*.js']) 15 | .pipe(eslint()) 16 | .pipe(eslint.format()) 17 | .pipe(eslint.failOnError()); 18 | }); 19 | 20 | gulp.task('doc', shell.task([ 21 | './node_modules/jsdoc/jsdoc.js lib/*.js -d doc -t ./node_modules/ink-docstrap/template -c jsdoc.conf.json' 22 | ])); 23 | 24 | gulp.task('publish', ['doc'], function () { 25 | ghpages.publish(path.join(__dirname, 'doc'), console.error); 26 | }); 27 | 28 | gulp.task('default', ['lint'], function () { 29 | // This will only run if the lint task is successful... 30 | }); 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 depoio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-telegram-bot", 3 | "version": "0.1.9", 4 | "description": "Telegram Bot API Wrapper for nodejs", 5 | "main": "index.js", 6 | "keywords": [ 7 | "telegram", 8 | "telegram bot", 9 | "telegram bot api", 10 | "bot" 11 | ], 12 | "scripts": { 13 | "test": "gulp test" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/depoio/node-telegram-bot.git" 18 | }, 19 | "author": [ 20 | "Shern Shiou Tan (http://blog.shernshiou.com/)", 21 | "Chin Ning Gan " 22 | ], 23 | "contributors": [ 24 | "Alberto Bottarini (http://www.albertobottarini.com)", 25 | "Sebastian Arena (http://mobile-tonic.com)" 26 | ], 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/depoio/node-telegram-bot/issues" 30 | }, 31 | "homepage": "https://github.com/depoio/node-telegram-bot#readme", 32 | "dependencies": { 33 | "botanio-node": "^1.0.2", 34 | "debug": "^2.2.0", 35 | "mime": "^1.3.4", 36 | "q": "^1.4.1", 37 | "request": "^2.58.0" 38 | }, 39 | "devDependencies": { 40 | "chai": "^3.0.0", 41 | "gh-pages": "^0.3.1", 42 | "gulp": "^3.9.0", 43 | "gulp-eslint": "^1.0.0", 44 | "gulp-mocha": "^2.1.2", 45 | "gulp-shell": "^0.4.2", 46 | "ink-docstrap": "^0.5.2", 47 | "jsdoc": "git+https://github.com/jsdoc3/jsdoc.git", 48 | "nock": "^2.6.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/echoBotExample.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by longstone on 28/06/15. 3 | */ 4 | 'use strict'; 5 | 6 | var fs = require('fs'); 7 | var Bot = require('../lib/Bot'); 8 | 9 | /** 10 | * this sample helps understand how the bot works, can also be used for integration tests ;) 11 | */ 12 | var bot = new Bot({ 13 | token: 'TOKEN_HERE' 14 | }) 15 | .on('message', function (message) { 16 | switch (message.text) { 17 | case "/sendMessage": 18 | bot.sendMessage({ 19 | chat_id: message.chat.id, 20 | text: 'echo : ' + message.text 21 | }); 22 | break; 23 | case "/sendPhoto": 24 | bot.sendPhoto({ 25 | chat_id: message.chat.id, 26 | caption: 'trololo', 27 | files: { 28 | photo: './logo.png' 29 | } 30 | }); 31 | break; 32 | case "/sendDocument": 33 | bot.sendDocument({ 34 | chat_id: message.chat.id, 35 | files: { 36 | filename: 'scream', 37 | contentType: 'audio/ogg', 38 | stream: fs.createReadStream('./0477.ogg') 39 | } 40 | }, console.error); 41 | break; 42 | case "/sendLocation": 43 | bot.sendLocation({ 44 | chat_id: message.chat.id, 45 | latitude: -27.121192, 46 | longitude: -109.366424, 47 | reply_to_message_id: message.message_id 48 | }); 49 | break; 50 | } 51 | }) 52 | .on('message', function (message) { 53 | console.log(message); 54 | }) 55 | //Command without argument 56 | .on('test', function (message){ 57 | bot.sendMessage({ 58 | chat_id: message.chat.id, 59 | text: 'You\'ve send command: ' + command 60 | }); 61 | }) 62 | //Command with argument: 63 | .on('arg', function (args, message){ 64 | bot.sendMessage({ 65 | chat_id: message.chat.id, 66 | text: 'You\'ve send command with arguments: ' + args 67 | }); 68 | }) 69 | .start(); 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-telegram-bot 2 | ================= 3 | 4 | [![Join the chat at https://gitter.im/depoio/node-telegram-bot](https://badges.gitter.im/depoio/node-telegram-bot.svg)](https://gitter.im/depoio/node-telegram-bot?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | [![build status](https://img.shields.io/travis/depoio/node-telegram-bot.svg?style=flat-square)](https://travis-ci.org/depoio/node-telegram-bot)[![dependencies](https://img.shields.io/david/depoio/node-telegram-bot.svg?style=flat-square)](https://david-dm.org/depoio/node-telegram-bot)[![node version](https://img.shields.io/node/v/gh-badges.svg?style=flat-square)](https://www.npmjs.com/package/node-telegram-bot)[![npm version](http://img.shields.io/npm/v/gh-badges.svg?style=flat-square)](https://www.npmjs.com/package/node-telegram-bot) 6 | 7 | ## Changelog 8 | 9 | - 0.1.9 Improved error 409 by kaminoo 10 | - 0.1.8 Timeout defaults to 60000ms 11 | - 0.1.7 Supports splitting command target (#45) 12 | - 0.1.6 Fixes #53 13 | - 0.1.5 Add analytics botan.io 14 | - 0.1.4 Fixes #48 15 | - 0.1.2 Merge #40, Use debug for logging 16 | - 0.1 Setwebhook, disable command parsing & maxattempts 17 | - 0.0.19 Added webhook support instead of polling 18 | - 0.0.18 Merged bug fix 19 | - 0.0.17 support multiple command arguments 20 | - 0.0.16 support file id for audio, document, sticker and video 21 | - 0.0.15 emit command (message that starts with '/') 22 | - 0.0.14 sendLocation 23 | - 0.0.13 getUserProfilePhotos 24 | - 0.0.12 sendDocument 25 | - 0.0.9 sendAudio 26 | - 0.0.8 sendPhoto 27 | - 0.0.7 forwardMessage 28 | - 0.0.6 sendChatAction 29 | - 0.0.3 Longpoll 30 | - 0.0.2 getMessage 31 | - 0.0.1 getMe 32 | 33 | ## Documentation 34 | 35 | Documentation is built using jsdoc with DocStrap. More to come (including examples)! 36 | [Documentation Link](http://depoio.github.io/node-telegram-bot/Bot.html) 37 | 38 | ## Sending files (including photo, audio, document, video and sticker) 39 | 40 | Now only require filepath, 41 | 42 | ```javascript 43 | 44 | bot.sendPhoto({ 45 | chat_id: USER_ID, 46 | caption: 'Telegram Logo', 47 | files: { 48 | photo: './examples/logo.png' 49 | } 50 | }, function (err, msg) { 51 | console.log(err); 52 | console.log(msg); 53 | }); 54 | 55 | ``` 56 | 57 | 58 | Previously, 59 | 60 | ```javascript 61 | 62 | bot.sendPhoto({ 63 | chat_id: USER_ID, 64 | caption: 'Telegram Logo', 65 | files: { 66 | photo: { 67 | filename: './examples/logo.png', 68 | stream: fs.createReadStream('./examples/logo.png') 69 | } 70 | } 71 | }, function (err, msg) { 72 | console.log(err); 73 | console.log(msg); 74 | }); 75 | 76 | ``` 77 | 78 | 79 | ## Here's an example: 80 | 81 | ```javascript 82 | 83 | var Bot = require('node-telegram-bot'); 84 | 85 | var bot = new Bot({ 86 | token: 'TOKEN HERE' 87 | }) 88 | .on('message', function (message) { 89 | console.log(message); 90 | }) 91 | .start(); 92 | 93 | ``` 94 | 95 | ## How to use Botan.io analytics: 96 | ```javascript 97 | 98 | var bot = new Bot({ 99 | token: 'Telegram token' 100 | }) 101 | .enableAnalytics('Botan.io token') 102 | .on('message', function (message) { 103 | console.log(message); 104 | }) 105 | .start(); 106 | 107 | ``` 108 | 109 | ## Credits 110 | [Sample sound](http://www.bigsoundbank.com/sound-0477-wilhelm-scream.html) 111 | -------------------------------------------------------------------------------- /lib/Bot.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var EventEmitter = require('events').EventEmitter 4 | , debug = require('debug')('node-telegram-bot') 5 | , util = require('util') 6 | , request = require('request') 7 | , fs = require('fs') 8 | , path = require('path') 9 | , qs = require('querystring') 10 | , Q = require('q') 11 | , botanio = require('botanio-node') 12 | , mime = require('mime'); 13 | 14 | /** 15 | * Constructor for Telegram Bot API Client. 16 | * 17 | * @class Bot 18 | * @constructor 19 | * @param {Object} options Configurations for the client 20 | * @param {String} options.token Bot token 21 | * 22 | * @see https://core.telegram.org/bots/api 23 | */ 24 | function Bot(options) { 25 | this.base_url = 'https://api.telegram.org/'; 26 | this.id = ''; 27 | this.first_name = ''; 28 | this.username = ''; 29 | this.token = options.token; 30 | this.offset = options.offset ? options.offset : 0; 31 | this.interval = options.interval ? options.interval : 500; 32 | this.webhook = options.webhook ? options.webhook : false; 33 | this.parseCommand = options.parseCommand ? options.parseCommand : true; 34 | this.maxAttempts = options.maxAttempts ? options.maxAttempts : 5; 35 | this.polling = false; 36 | this.pollingRequest = null; 37 | this.analytics = null; 38 | this.timeout = options.timeout ? options.timeout : 60; //specify in seconds 39 | // define the messageType's 40 | this.NORMAL_MESSAGE = 1; 41 | this.EDITED_MESSAGE = 2; 42 | } 43 | 44 | util.inherits(Bot, EventEmitter); 45 | 46 | /** 47 | * This callback occur after client request for a certain webservice. 48 | * 49 | * @callback Bot~requestCallback 50 | * @param {Error} Error during request 51 | * @param {Object} Response from Telegram service 52 | */ 53 | Bot.prototype._get = function (options, callback) { 54 | var self = this; 55 | var url = this.base_url + 'bot' + this.token + '/' + options.method; 56 | 57 | if (options.params) { 58 | url += '?' + qs.stringify(options.params); 59 | } 60 | 61 | var attempt = 1; 62 | 63 | function retry() { 64 | request.get({ 65 | url: url, 66 | json: true 67 | }, function (err, res, body) { 68 | if (err) { 69 | if (err.code === 'ENOTFOUND' && attempt < self.maxAttempts) { 70 | ++attempt; 71 | self.emit('retry', attempt); 72 | retry(); 73 | } else { 74 | callback(err); 75 | } 76 | } else { 77 | callback(null, body); 78 | } 79 | }); 80 | } 81 | 82 | retry(); 83 | 84 | return this; 85 | }; 86 | 87 | /** 88 | * To perform multipart request e.g. file upload 89 | * 90 | * @callback Bot~requestCallback 91 | * @param {Error} Error during request 92 | * @param {Object} Response from Telegram service 93 | */ 94 | Bot.prototype._multipart = function (options, callback) { 95 | var self = this; 96 | var url = this.base_url + 'bot' + this.token + '/' + options.method; 97 | 98 | var attempt = 1; 99 | 100 | function retry() { 101 | var req = request.post(url, function (err, res, body) { 102 | if (err) { 103 | if (err.code === 'ENOTFOUND' && attempt < self.maxAttempts) { 104 | ++attempt; 105 | self.emit('retry', attempt); 106 | retry(); 107 | } else { 108 | callback(err); 109 | } 110 | } else { 111 | var contentType = res.headers['content-type']; 112 | 113 | if (contentType.indexOf('application/json') >= 0) { 114 | try { 115 | body = JSON.parse(body); 116 | } catch (e) { 117 | callback(e, body); 118 | } 119 | } 120 | 121 | callback(null, body); 122 | } 123 | }); 124 | 125 | var form = req.form() 126 | , filename 127 | , type 128 | , stream 129 | , contentType; 130 | 131 | var arr = Object.keys(options.files); 132 | 133 | if (arr.indexOf('stream') > -1) { 134 | type = options.files['type']; 135 | filename = options.files['filename']; 136 | stream = options.files['stream']; 137 | contentType = options.files['contentType']; 138 | } else { 139 | arr.forEach(function (key) { 140 | var file = options.files[key]; 141 | type = key; 142 | filename = path.basename(file); 143 | stream = fs.createReadStream(file); 144 | contentType = mime.lookup(file); 145 | }) 146 | } 147 | 148 | form.append(type, stream, { 149 | filename: filename, 150 | contentType: contentType 151 | }); 152 | 153 | Object.keys(options.params).forEach(function (key) { 154 | if (options.params[key]) { 155 | form.append(key, options.params[key]); 156 | } 157 | }); 158 | } 159 | 160 | retry(); 161 | 162 | return this; 163 | }; 164 | 165 | /** 166 | * Temporary solution to set webhook 167 | * 168 | * @param {Error} Error during request 169 | * @param {Object} Response from Telegram service 170 | */ 171 | Bot.prototype._setWebhook = function (webhook) { 172 | var self = this; 173 | var url = this.base_url + 'bot' + this.token + '/setWebhook' + "?" + qs.stringify({url: webhook}); 174 | 175 | request.get({ 176 | url: url, 177 | json: true 178 | }, function (err, res, body) { 179 | if (!err && res && res.statusCode === 200) { 180 | if (body.ok) { 181 | debug("Set webhook to " + self.webhook); 182 | } else { 183 | debug("Body not ok"); 184 | debug(body); 185 | } 186 | } else if(res && res.hasOwnProperty('statusCode') && res.statusCode === 401){ 187 | debug(err); 188 | debug("Failed to set webhook with code" + res.statusCode); 189 | } else { 190 | debug(err); 191 | debug("Failed to set webhook with unknown error"); 192 | } 193 | }); 194 | } 195 | 196 | /** 197 | * Start polling for messages 198 | * 199 | * @return {Bot} Self 200 | */ 201 | Bot.prototype._poll = function () { 202 | var self = this; 203 | var url = self.base_url + 'bot' + self.token + '/getUpdates?timeout=' + self.timeout + '&offset=' + self.offset; 204 | 205 | self.pollingRequest = null; 206 | if (self.polling) { 207 | debug("Poll"); 208 | 209 | self.pollingRequest = request.get({ 210 | url: url, 211 | timeout: self.timeout * 1000, 212 | json: true 213 | }, function (err, res, body) { 214 | 215 | if (err && err.code !== 'ETIMEDOUT') { 216 | self.emit('error', err); 217 | } else if (res && res.statusCode === 200) { 218 | if (body.ok) { 219 | body.result.forEach(function (msg) { 220 | if (msg.update_id >= self.offset) { 221 | self.offset = msg.update_id + 1; 222 | var message; 223 | var messageType = 0; 224 | if (!!msg.message){ 225 | message = msg.message; 226 | messageType = 1; // We are a normal message 227 | }else if (!!msg.edited_message){ 228 | message = msg.edited_message 229 | messageType = 2; // We are an edited message 230 | } 231 | if(messageType > 0) { 232 | if (self.parseCommand) { 233 | if (message && message.text && message.text.charAt(0) === '/') { 234 | /** 235 | * Split the message on space and @ 236 | * Zero part = complete message 237 | * First part = command with leading / 238 | * Third part = target or empty "" 239 | * Fourth part = arguments or empty "" 240 | */ 241 | var messageParts = message.text.match(/([^@ ]*)([^ ]*)[ ]?(.*)/); 242 | 243 | // Filter everything not alphaNum out of the command 244 | var command = messageParts[1].replace(/[^a-zA-Z0-9 ]/g, ""); 245 | // Target incl @ sign or null 246 | var target = (messageParts[2] !== "" ? messageParts[2]: null); 247 | // Optional arguments or null 248 | var args = (messageParts[3] !== "" ? messageParts[3].split(' '): null); 249 | 250 | self.emit(command, message, args, target); 251 | } 252 | } 253 | 254 | if (self.analytics !== null) { 255 | self.analytics.track(message); 256 | } 257 | message.messageType = messageType; 258 | self.emit('message', message); 259 | } 260 | } 261 | }); 262 | } 263 | 264 | if (self.polling) { 265 | self._poll(); 266 | } 267 | 268 | } else if(res && res.hasOwnProperty('statusCode') && res.statusCode === 401) { 269 | self.emit('error', new Error('Invalid token.')); 270 | } else if(res && res.hasOwnProperty('statusCode') && res.statusCode === 409) { 271 | self.emit('error', new Error('Duplicate token.')); 272 | } else if(res && res.hasOwnProperty('statusCode') && res.statusCode === 502) { 273 | self.emit('error', new Error('Gateway error.')); 274 | } else if(self.pollingRequest && !self.pollingRequest._aborted) { //Skip error throwing, this is an abort due to stopping 275 | self.emit('error', new Error(util.format('Unknown error'))); 276 | } 277 | }); 278 | } 279 | 280 | return self; 281 | }; 282 | 283 | /** 284 | * Bot start receiving activities 285 | * 286 | * @return {Bot} Self 287 | */ 288 | Bot.prototype.start = function () { 289 | var self = this; 290 | if (self.webhook) { 291 | self._setWebhook(this.webhook); 292 | } else if (!self.polling) { 293 | self.polling = true; 294 | self._poll(); 295 | } 296 | return self; 297 | }; 298 | 299 | /** 300 | * End polling for messages 301 | * 302 | * @return {Bot} Self 303 | */ 304 | Bot.prototype.stop = function () { 305 | var self = this; 306 | self.polling = false; 307 | if (self.pollingRequest) { 308 | self.pollingRequest.abort(); 309 | } 310 | return self; 311 | }; 312 | 313 | /** 314 | * Returns basic information about the bot in form of a User object. 315 | * 316 | * @param {Bot~requestCallback} callback The callback that handles the response. 317 | * @return {Promise} Q Promise 318 | * 319 | * @see https://core.telegram.org/bots/api#getme 320 | */ 321 | Bot.prototype.getMe = function (callback) { 322 | var self = this 323 | , deferred = Q.defer(); 324 | 325 | this._get({ method: 'getMe' }, function (err, res) { 326 | if (err) { 327 | return deferred.reject(err); 328 | } 329 | 330 | if (res.ok) { 331 | self.id = res.result.id; 332 | self.first_name = res.result.first_name; 333 | self.username = res.result.username; 334 | 335 | deferred.resolve(res.result); 336 | } else { 337 | deferred.reject(res); 338 | } 339 | }); 340 | 341 | return deferred.promise.nodeify(callback); 342 | }; 343 | 344 | /** 345 | * Use this method to get a list of profile pictures for a user. 346 | * 347 | * @param {Object} options Options 348 | * @param {Integer} options.user_id Unique identifier of the target user 349 | * @param {String=} options.offset Sequential number of the first photo to be returned. By default, all photos are returned. 350 | * @param {Integer=} options.limit Limits the number of photos to be retrieved. Values between 1—100 are accepted. Defaults to 100. 351 | * @param {Bot~requestCallback} callback The callback that handles the response. 352 | * @return {Promise} Q Promise 353 | * 354 | * @see https://core.telegram.org/bots/api#getuserprofilephotos 355 | */ 356 | Bot.prototype.getUserProfilePhotos = function (options, callback) { 357 | var self = this 358 | , deferred = Q.defer(); 359 | 360 | this._get({ 361 | method: 'getUserProfilePhotos', 362 | params: { 363 | user_id: options.user_id, 364 | offset: options.offset, 365 | limit: options.limit 366 | } 367 | }, function (err, res) { 368 | if (err) { 369 | return deferred.reject(err); 370 | } 371 | 372 | if (res.ok) { 373 | deferred.resolve(res.result); 374 | } else { 375 | deferred.reject(res); 376 | } 377 | }); 378 | 379 | return deferred.promise.nodeify(callback); 380 | }; 381 | 382 | /** 383 | * 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. 384 | * 385 | * @param {Object} options Options 386 | * @param {String} options.file_id File identifier to get info about 387 | * @param {String=} options.dir Directory the file to be stored (if it is not specified, no file willbe downloaded) 388 | * @param {Bot~requestCallback} callback The callback that handles the response. 389 | * @return {Promise} Q Promise 390 | * 391 | * @see https://core.telegram.org/bots/api#getfile 392 | */ 393 | Bot.prototype.getFile = function (options, callback) { 394 | var self = this 395 | , deferred = Q.defer(); 396 | 397 | this._get({ 398 | method: 'getFile', 399 | params: { 400 | file_id: options.file_id 401 | } 402 | }, function (err, res) { 403 | if (err) { 404 | return deferred.reject(err); 405 | } 406 | 407 | if (res.ok) { 408 | var filename = path.basename(res.result.file_path); 409 | var url = self.getFileURLFromFilePath(res.result.file_path); 410 | if (options.dir) { 411 | var filepath = path.join(options.dir, filename); 412 | var destination = fs.createWriteStream(filepath); 413 | request(url) 414 | .pipe(destination) 415 | .on('finish', function () { 416 | deferred.resolve({ 417 | destination: filepath, 418 | url: url 419 | }); 420 | }) 421 | .on('error', function(error){ 422 | deferred.reject(error); 423 | }); 424 | } else { 425 | deferred.resolve({ 426 | url: url 427 | }); 428 | } 429 | } else { 430 | deferred.reject(res); 431 | } 432 | }); 433 | 434 | return deferred.promise.nodeify(callback); 435 | }; 436 | 437 | 438 | /** 439 | * Use this method to get the download URL for a Telegram file when file_path is provided along file_id. Usually happens with forwarded messages to which you already called getFile. 440 | * 441 | * @param {String} file_path File path provided by Telegram 442 | * @return {String} Download URL 443 | * 444 | */ 445 | Bot.prototype.getFileURLFromFilePath = function (file_path) { 446 | var self = this; 447 | return self.base_url + 'file/bot' + self.token + '/' + file_path; 448 | }; 449 | 450 | 451 | /** 452 | * Use this method to send text messages. 453 | * 454 | * @param {Object} options Options 455 | * @param {Integer} options.chat_id Unique identifier for the message recipient — User or GroupChat id 456 | * @param {String} options.text Text of the message to be sent 457 | * @param {String} options.parse_mode Send Markdown, if you want Telegram apps to show bold, italic and inline URLs in your bot's message. 458 | * @param {Boolean=} options.disable_web_page_preview Disables link previews for links in this message 459 | * @param {Integer=} options.reply_to_message_id If the message is a reply, ID of the original message 460 | * @param {Object=} options.reply_markup Additional interface options. {@link https://core.telegram.org/bots/api/#replykeyboardmarkup| ReplyKeyboardMarkup} 461 | * @param {Bot~requestCallback} callback The callback that handles the response. 462 | * @return {Promise} Q Promise 463 | * 464 | * @see https://core.telegram.org/bots/api#sendmessage 465 | */ 466 | Bot.prototype.sendMessage = function (options, callback) { 467 | var self = this 468 | , deferred = Q.defer(); 469 | 470 | this._get({ 471 | method: 'sendMessage', 472 | params: { 473 | chat_id: options.chat_id, 474 | text: options.text, 475 | parse_mode: options.parse_mode, 476 | disable_web_page_preview: options.disable_web_page_preview, 477 | reply_to_message_id: options.reply_to_message_id, 478 | reply_markup: JSON.stringify(options.reply_markup) 479 | } 480 | }, function (err, res) { 481 | if (err) { 482 | return deferred.reject(err); 483 | } 484 | 485 | if (res.ok) { 486 | deferred.resolve(res.result); 487 | } else { 488 | deferred.reject(res); 489 | } 490 | }); 491 | 492 | return deferred.promise.nodeify(callback); 493 | }; 494 | 495 | // https://tlgrm.ru/docs/bots/api#sendcontact 496 | Bot.prototype.sendContact = function (options, callback) { 497 | var self = this 498 | , deferred = Q.defer(); 499 | 500 | this._get({ 501 | method: 'sendContact', 502 | params: { 503 | chat_id: options.chat_id, 504 | phone_number: options.phone_number, 505 | first_name: options.first_name, 506 | last_name: options.last_name, 507 | disable_notification: options.disable_notification, 508 | reply_to_message_id: options.reply_to_message_id, 509 | reply_markup: JSON.stringify(options.reply_markup) 510 | } 511 | }, function (err, res) { 512 | if (err) { 513 | return deferred.reject(err); 514 | } 515 | 516 | if (res.ok) { 517 | deferred.resolve(res.result); 518 | } else { 519 | deferred.reject(res); 520 | } 521 | }); 522 | 523 | return deferred.promise.nodeify(callback); 524 | }; 525 | 526 | /** 527 | * Use this method to forward messages of any kind. 528 | * 529 | * @param {Object} options Options 530 | * @param {Integer} options.chat_id Unique identifier for the message recipient — User or GroupChat id 531 | * @param {Integer} options.from_chat_id Unique identifier for the chat where the original message was sent — User or GroupChat id 532 | * @param {Integer} options.message_id Unique message identifier 533 | * @param {Bot~requestCallback} callback The callback that handles the response. 534 | * @return {Promise} Q Promise 535 | * 536 | * @see https://core.telegram.org/bots/api#forwardmessage 537 | */ 538 | Bot.prototype.forwardMessage = function (options, callback) { 539 | var self = this 540 | , deferred = Q.defer(); 541 | 542 | this._get({ 543 | method: 'forwardMessage', 544 | params: { 545 | chat_id: options.chat_id, 546 | from_chat_id: options.from_chat_id, 547 | message_id: options.message_id 548 | } 549 | }, function (err, res) { 550 | if (err) { 551 | return deferred.reject(err); 552 | } 553 | 554 | if (res.ok) { 555 | deferred.resolve(res.result); 556 | } else { 557 | deferred.reject(res); 558 | } 559 | }); 560 | 561 | return deferred.promise.nodeify(callback); 562 | }; 563 | 564 | /** 565 | * Use this method to send photos. 566 | * 567 | * @param {Object} options Options 568 | * @param {Integer} options.chat_id Unique identifier for the message recipient — User or GroupChat id 569 | * @param {String} options.photo Path to photo file (Library will create a stream if the path exist) 570 | * @param {String=} options.file_id If file_id is passed, method will use this instead 571 | * @param {String=} options.caption Photo caption (may also be used when resending photos by file_id). 572 | * @param {Integer=} options.reply_to_message_id If the message is a reply, ID of the original message 573 | * @param {Object=} options.reply_markup Additional interface options. {@link https://core.telegram.org/bots/api/#replykeyboardmarkup| ReplyKeyboardMarkup} 574 | * @param {Bot~requestCallback} callback The callback that handles the response. 575 | * @return {Promise} Q Promise 576 | * 577 | * @see https://core.telegram.org/bots/api#sendphoto 578 | */ 579 | Bot.prototype.sendPhoto = function (options, callback) { 580 | var self = this 581 | , deferred = Q.defer(); 582 | 583 | if (options.file_id) { 584 | this._get({ 585 | method: 'sendPhoto', 586 | params: { 587 | chat_id: options.chat_id, 588 | caption: options.caption, 589 | photo: options.file_id, 590 | reply_to_message_id: options.reply_to_message_id, 591 | reply_markup: JSON.stringify(options.reply_markup) 592 | } 593 | }, function (err, res) { 594 | if (err) { 595 | return deferred.reject(err); 596 | } 597 | 598 | if (res.ok) { 599 | deferred.resolve(res.result); 600 | } else { 601 | deferred.reject(res); 602 | } 603 | }); 604 | } else { 605 | var files; 606 | if (options.files.stream) { 607 | files = { 608 | type: 'photo', 609 | filename: options.files.filename, 610 | contentType: options.files.contentType, 611 | stream: options.files.stream 612 | } 613 | } else { 614 | files = { 615 | photo: options.files.photo 616 | } 617 | } 618 | 619 | this._multipart({ 620 | method: 'sendPhoto', 621 | params: { 622 | chat_id: options.chat_id, 623 | caption: options.caption, 624 | reply_to_message_id: options.reply_to_message_id, 625 | reply_markup: JSON.stringify(options.reply_markup) 626 | }, 627 | files: files 628 | }, function (err, res) { 629 | if (err) { 630 | return deferred.reject(err); 631 | } 632 | 633 | if (res.ok) { 634 | deferred.resolve(res.result); 635 | } else { 636 | deferred.reject(res); 637 | } 638 | }); 639 | } 640 | 641 | return deferred.promise.nodeify(callback); 642 | }; 643 | 644 | /** 645 | * Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message. 646 | * 647 | * @param {Object} options Options 648 | * @param {Integer} options.chat_id Unique identifier for the message recipient — User or GroupChat id 649 | * @param {String} options.audio Path to audio file (Library will create a stream if the path exist) 650 | * @param {String=} options.file_id If file_id is passed, method will use this instead 651 | * @param {Integer=} options.reply_to_message_id If the message is a reply, ID of the original message 652 | * @param {Object=} options.reply_markup Additional interface options. {@link https://core.telegram.org/bots/api/#replykeyboardmarkup| ReplyKeyboardMarkup} 653 | * @param {Bot~requestCallback} callback The callback that handles the response. 654 | * @return {Promise} Q Promise 655 | * 656 | * @see https://core.telegram.org/bots/api#sendaudio 657 | */ 658 | Bot.prototype.sendAudio = function (options, callback) { 659 | var self = this 660 | , deferred = Q.defer(); 661 | 662 | if (options.file_id) { 663 | this._get({ 664 | method: 'sendAudio', 665 | params: { 666 | chat_id: options.chat_id, 667 | audio: options.file_id, 668 | reply_to_message_id: options.reply_to_message_id, 669 | reply_markup: JSON.stringify(options.reply_markup) 670 | } 671 | }, function (err, res) { 672 | if (err) { 673 | return deferred.reject(err); 674 | } 675 | 676 | if (res.ok) { 677 | deferred.resolve(res.result); 678 | } else { 679 | deferred.reject(res); 680 | } 681 | }); 682 | } else { 683 | var files; 684 | if (options.files.stream) { 685 | files = { 686 | type: 'audio', 687 | filename: options.files.filename, 688 | contentType: options.files.contentType, 689 | stream: options.files.stream 690 | } 691 | } else if (mime.lookup(options.files.audio) !== 'audio/ogg') { 692 | return Q.reject(new Error('Invalid file type')) 693 | .nodeify(callback); 694 | } else { 695 | files = { 696 | audio: options.files.audio 697 | } 698 | } 699 | 700 | this._multipart({ 701 | method: 'sendAudio', 702 | params: { 703 | chat_id: options.chat_id, 704 | reply_to_message_id: options.reply_to_message_id, 705 | reply_markup: JSON.stringify(options.reply_markup) 706 | }, 707 | files: files 708 | }, function (err, res) { 709 | if (err) { 710 | return deferred.reject(err); 711 | } 712 | 713 | if (res.ok) { 714 | deferred.resolve(res.result); 715 | } else { 716 | deferred.reject(res); 717 | } 718 | }); 719 | } 720 | 721 | return deferred.promise.nodeify(callback); 722 | }; 723 | 724 | /** 725 | * Use this method to send general files. 726 | * 727 | * @param {Object} options Options 728 | * @param {Integer} options.chat_id Unique identifier for the message recipient — User or GroupChat id 729 | * @param {String} options.document Path to document file (Library will create a stream if the path exist) 730 | * @param {String=} options.file_id If file_id is passed, method will use this instead 731 | * @param {Integer=} options.reply_to_message_id If the message is a reply, ID of the original message 732 | * @param {Object=} options.reply_markup Additional interface options. {@link https://core.telegram.org/bots/api/#replykeyboardmarkup| ReplyKeyboardMarkup} 733 | * @param {Bot~requestCallback} callback The callback that handles the response. 734 | * @return {Promise} Q Promise 735 | * 736 | * @see https://core.telegram.org/bots/api#senddocument 737 | */ 738 | Bot.prototype.sendDocument = function (options, callback) { 739 | var self = this 740 | , deferred = Q.defer(); 741 | 742 | if (options.file_id) { 743 | this._get({ 744 | method: 'sendDocument', 745 | params: { 746 | chat_id: options.chat_id, 747 | document: options.file_id, 748 | reply_to_message_id: options.reply_to_message_id, 749 | reply_markup: JSON.stringify(options.reply_markup) 750 | } 751 | }, function (err, res) { 752 | if (err) { 753 | return deferred.reject(err); 754 | } 755 | 756 | if (res.ok) { 757 | deferred.resolve(res.result); 758 | } else { 759 | deferred.reject(res); 760 | } 761 | }); 762 | } else { 763 | var files; 764 | if (options.files.stream) { 765 | files = { 766 | type: 'document', 767 | filename: options.files.filename, 768 | contentType: options.files.contentType, 769 | stream: options.files.stream 770 | } 771 | } else { 772 | files = { 773 | document: options.files.document 774 | } 775 | } 776 | 777 | this._multipart({ 778 | method: 'sendDocument', 779 | params: { 780 | chat_id: options.chat_id, 781 | reply_to_message_id: options.reply_to_message_id, 782 | reply_markup: JSON.stringify(options.reply_markup) 783 | }, 784 | files: files 785 | }, function (err, res) { 786 | if (err) { 787 | return deferred.reject(err); 788 | } 789 | 790 | if (res.ok) { 791 | deferred.resolve(res.result); 792 | } else { 793 | deferred.reject(res); 794 | } 795 | }); 796 | } 797 | 798 | return deferred.promise.nodeify(callback); 799 | }; 800 | 801 | /** 802 | * Use this method to send .webp stickers. 803 | * 804 | * @param {Object} options Options 805 | * @param {Integer} options.chat_id Unique identifier for the message recipient — User or GroupChat id 806 | * @param {String} options.sticker Path to sticker file (Library will create a stream if the path exist) 807 | * @param {String=} options.file_id If file_id is passed, method will use this instead 808 | * @param {Integer=} options.reply_to_message_id If the message is a reply, ID of the original message 809 | * @param {Object=} options.reply_markup Additional interface options. {@link https://core.telegram.org/bots/api/#replykeyboardmarkup| ReplyKeyboardMarkup} 810 | * @param {Bot~requestCallback} callback The callback that handles the response. 811 | * @return {Promise} Q Promise 812 | * 813 | * @see https://core.telegram.org/bots/api#sendsticker 814 | */ 815 | Bot.prototype.sendSticker = function (options, callback) { 816 | var self = this 817 | , deferred = Q.defer(); 818 | 819 | if (options.file_id) { 820 | this._get({ 821 | method: 'sendSticker', 822 | params: { 823 | chat_id: options.chat_id, 824 | sticker: options.file_id, 825 | reply_to_message_id: options.reply_to_message_id, 826 | reply_markup: JSON.stringify(options.reply_markup) 827 | } 828 | }, function (err, res) { 829 | if (err) { 830 | return deferred.reject(err); 831 | } 832 | 833 | if (res.ok) { 834 | deferred.resolve(res.result); 835 | } else { 836 | deferred.reject(res); 837 | } 838 | }); 839 | } else { 840 | if (mime.lookup(options.files.sticker) !== 'image/webp') { 841 | return Q.reject(new Error('Invalid file type')) 842 | .nodeify(callback); 843 | } 844 | 845 | this._multipart({ 846 | method: 'sendSticker', 847 | params: { 848 | chat_id: options.chat_id, 849 | reply_to_message_id: options.reply_to_message_id, 850 | reply_markup: JSON.stringify(options.reply_markup) 851 | }, 852 | files: { 853 | sticker: options.files.sticker 854 | } 855 | }, function (err, res) { 856 | if (err) { 857 | return deferred.reject(err); 858 | } 859 | 860 | if (res.ok) { 861 | deferred.resolve(res.result); 862 | } else { 863 | deferred.reject(res); 864 | } 865 | }); 866 | } 867 | 868 | return deferred.promise.nodeify(callback); 869 | }; 870 | 871 | /** 872 | * Use this method to send video files, Telegram clients support mp4 video. 873 | * 874 | * @param {Object} options Options 875 | * @param {Integer} options.chat_id Unique identifier for the message recipient — User or GroupChat id 876 | * @param {String} options.video Path to video file (Library will create a stream if the path exist) 877 | * @param {String=} options.file_id If file_id is passed, method will use this instead 878 | * @param {Integer=} options.reply_to_message_id If the message is a reply, ID of the original message 879 | * @param {Object=} options.reply_markup Additional interface options. {@link https://core.telegram.org/bots/api/#replykeyboardmarkup| ReplyKeyboardMarkup} 880 | * @param {Bot~requestCallback} callback The callback that handles the response. 881 | * @return {Promise} Q Promise 882 | * 883 | * @see https://core.telegram.org/bots/api#sendvideo 884 | */ 885 | Bot.prototype.sendVideo = function (options, callback) { 886 | var self = this 887 | , deferred = Q.defer(); 888 | 889 | if (options.file_id) { 890 | this._get({ 891 | method: 'sendSticker', 892 | params: { 893 | chat_id: options.chat_id, 894 | video: options.file_id, 895 | reply_to_message_id: options.reply_to_message_id, 896 | reply_markup: JSON.stringify(options.reply_markup) 897 | } 898 | }, function (err, res) { 899 | if (err) { 900 | return deferred.reject(err); 901 | } 902 | 903 | if (res.ok) { 904 | deferred.resolve(res.result); 905 | } else { 906 | deferred.reject(res); 907 | } 908 | }); 909 | } else { 910 | var files; 911 | if (options.files.stream) { 912 | files = { 913 | type: 'video', 914 | filename: options.files.filename, 915 | contentType: options.files.contentType, 916 | stream: options.files.stream 917 | } 918 | } else if (mime.lookup(options.files.video.filename) !== 'video/mp4') { 919 | return Q.reject(new Error('Invalid file type')) 920 | .nodeify(callback); 921 | } else { 922 | files = { 923 | video: options.files.video 924 | } 925 | } 926 | 927 | this._multipart({ 928 | method: 'sendVideo', 929 | params: { 930 | chat_id: options.chat_id, 931 | reply_to_message_id: options.reply_to_message_id, 932 | reply_markup: JSON.stringify(options.reply_markup) 933 | }, 934 | files: files 935 | }, function (err, res) { 936 | if (err) { 937 | return deferred.reject(err); 938 | } 939 | 940 | if (res.ok) { 941 | deferred.resolve(res.result); 942 | } else { 943 | deferred.reject(res); 944 | } 945 | }); 946 | } 947 | 948 | return deferred.promise.nodeify(callback); 949 | }; 950 | 951 | /** 952 | * Use this method to send point on the map. 953 | * 954 | * @param {Object} options Options 955 | * @param {Integer} options.chat_id Unique identifier for the message recipient — User or GroupChat id 956 | * @param {Float} options.latitude Latitude of location 957 | * @param {Float} options.longitude Longitude of location 958 | * @param {Integer=} options.reply_to_message_id If the message is a reply, ID of the original message 959 | * @param {Object=} options.reply_markup Additional interface options. {@link https://core.telegram.org/bots/api/#replykeyboardmarkup| ReplyKeyboardMarkup} 960 | * @param {Bot~requestCallback} callback The callback that handles the response. 961 | * @return {Promise} Q Promise 962 | * 963 | * @see https://core.telegram.org/bots/api#sendlocation 964 | */ 965 | Bot.prototype.sendLocation = function (options, callback) { 966 | var self = this 967 | , deferred = Q.defer(); 968 | 969 | this._get({ 970 | method: 'sendLocation', 971 | params: { 972 | chat_id: options.chat_id, 973 | latitude: options.latitude, 974 | longitude: options.longitude, 975 | reply_to_message_id: options.reply_to_message_id, 976 | reply_markup: JSON.stringify(options.reply_markup) 977 | } 978 | }, function (err, res) { 979 | if (err) { 980 | return deferred.reject(err); 981 | } 982 | 983 | if (res.ok) { 984 | deferred.resolve(res.result); 985 | } else { 986 | deferred.reject(res); 987 | } 988 | }); 989 | 990 | return deferred.promise.nodeify(callback); 991 | }; 992 | 993 | /** 994 | * Use this method when you need to tell the user that something is happening on the bot's side. 995 | * 996 | * @param {Object} options Options 997 | * @param {Integer} options.chat_id Unique identifier for the message recipient — User or GroupChat id 998 | * @param {String} options.action Type of action to broadcast. 999 | * @param {Bot~requestCallback} callback The callback that handles the response. 1000 | * @return {Promise} Q Promise 1001 | * 1002 | * @see https://core.telegram.org/bots/api#sendchataction 1003 | */ 1004 | Bot.prototype.sendChatAction = function (options, callback) { 1005 | var self = this 1006 | , deferred = Q.defer(); 1007 | 1008 | this._get({ 1009 | method: 'sendChatAction', 1010 | params: { 1011 | chat_id: options.chat_id, 1012 | action: options.action 1013 | } 1014 | }, function (err, res) { 1015 | if (err) { 1016 | return deferred.reject(err); 1017 | } 1018 | 1019 | if (res.ok) { 1020 | deferred.resolve(res.result); 1021 | } else { 1022 | deferred.reject(res); 1023 | } 1024 | }); 1025 | 1026 | return deferred.promise.nodeify(callback); 1027 | }; 1028 | 1029 | /** 1030 | * Analytics from http://botan.io/ 1031 | * Allows all incoming messages, and you can make tagging, for specific messages 1032 | * bot.analytics.track(message, 'Specific tag'); 1033 | * 1034 | * @param {String} token You can take this token here: https://appmetrica.yandex.com/ 1035 | * @return {Bot} Self 1036 | * 1037 | * @see https://github.com/botanio/sdk 1038 | */ 1039 | Bot.prototype.enableAnalytics = function(token) { 1040 | this.analytics = botanio(token); 1041 | 1042 | return this; 1043 | }; 1044 | 1045 | module.exports = Bot; 1046 | --------------------------------------------------------------------------------