From 9cd663024f2da5fffe0cb5b1dc57c71ed9ac21e0 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Fri, 20 Jan 2017 15:02:12 +0300 Subject: [PATCH 01/28] pkg: Specify supported Node.js versions --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 34419790..322f95d7 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,9 @@ }, "author": "Yago Pérez ", "license": "MIT", + "engines": { + "node" : ">=0.12" + }, "dependencies": { "bl": "^1.1.2", "bluebird": "^3.3.4", From 584a388fc53ea7c7b614b711851fced0d69be545 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Fri, 20 Jan 2017 15:11:16 +0300 Subject: [PATCH 02/28] doc: Add project to community section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d62ab218..0f85ade2 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ Some things built using this library, and might interest you: * [tgfancy](https://github.com/GochoMugo/tgfancy): A Fancy, Higher-Level Wrapper for Telegram Bot API * [node-telegram-bot-api-middleware](https://github.com/idchlife/node-telegram-bot-api-middleware): Middleware for node-telegram-bot-api +* [teleirc](https://github.com/FruitieX/teleirc): A simple Telegram ↔ IRC gateway * * * From 235ea2276bcb9220e6bcdeeace8ff9978970e639 Mon Sep 17 00:00:00 2001 From: Mohammed Sohail Date: Wed, 25 Jan 2017 11:46:50 +0300 Subject: [PATCH 03/28] doc: Add Badge for supported Bot API Version (#264) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f85ade2..9329bb0d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -[![Build Status](https://travis-ci.org/yagop/node-telegram-bot-api.svg?branch=master)](https://travis-ci.org/yagop/node-telegram-bot-api) + [![Bot API](http://img.shields.io/badge/Bot API-v2.3.1-00aced.svg)](https://core.telegram.org/bots/api) + [![Build Status](https://travis-ci.org/yagop/node-telegram-bot-api.svg?branch=master)](https://travis-ci.org/yagop/node-telegram-bot-api) [![Build status](https://ci.appveyor.com/api/projects/status/ujko6bsum3g5msjh/branch/master?svg=true)](https://ci.appveyor.com/project/yagop/node-telegram-bot-api/branch/master) [![Coverage Status](https://coveralls.io/repos/yagop/node-telegram-bot-api/badge.svg?branch=master)](https://coveralls.io/r/yagop/node-telegram-bot-api?branch=master) [![bitHound Score](https://www.bithound.io/github/yagop/node-telegram-bot-api/badges/score.svg)](https://www.bithound.io/github/yagop/node-telegram-bot-api) From 99f364df11c16034b6ca9115295582202fd6b221 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Wed, 25 Jan 2017 13:53:56 +0300 Subject: [PATCH 04/28] doc/help: Add link to source --- doc/help.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/help.md b/doc/help.md index 6759bb2f..a634f01d 100644 --- a/doc/help.md +++ b/doc/help.md @@ -172,4 +172,9 @@ Sources: *Not Done. Send PR please!* +Sources: + + * Issue #219: https://github.com/yagop/node-telegram-bot-api/issues/219 + + --- From a32259b1a1207566b2027b073dbc728a80adf518 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Wed, 25 Jan 2017 18:59:06 +0300 Subject: [PATCH 05/28] src/telegram: Consider stream.path could not be parsed --- src/telegram.js | 4 +++- test/telegram.js | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/telegram.js b/src/telegram.js index f454c27c..8f6af71f 100644 --- a/src/telegram.js +++ b/src/telegram.js @@ -195,7 +195,9 @@ class TelegramBot extends EventEmitter { let fileName; let fileId; if (data instanceof stream.Stream) { - fileName = URL.parse(path.basename(data.path.toString())).pathname; + // Will be 'null' if could not be parsed. Default to 'filename'. + // For example, 'data.path' === '/?id=123' from 'request("https://example.com/?id=123")' + fileName = URL.parse(path.basename(data.path.toString())).pathname || 'filename'; formData = {}; formData[type] = { value: data, diff --git a/test/telegram.js b/test/telegram.js index 40c5ad5e..b2c60596 100644 --- a/test/telegram.js +++ b/test/telegram.js @@ -991,5 +991,10 @@ describe('TelegramBot', function telegramSuite() { assert.ok(err.response.body.indexOf('Bad Request') !== -1); }); }); + it('should allow stream.path that can not be parsed', function test() { + const stream = fs.createReadStream(`${__dirname}/data/photo.gif`); + stream.path = '/?id=123'; // for example, 'http://example.com/?id=666' + return bot.sendPhoto(USERID, stream); + }); }); }); // End Telegram From 535aa56345e8d4ea8c885197a1e1784b9f4310cf Mon Sep 17 00:00:00 2001 From: Ni2c2k Date: Wed, 25 Jan 2017 23:48:38 +0500 Subject: [PATCH 06/28] doc: Fix links for examples and help in README (#266) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9329bb0d..c9c71f30 100644 --- a/README.md +++ b/README.md @@ -64,13 +64,13 @@ _**Note**: Development is done against the **master** branch. Code for the lates resides on the **release** branch._ [usage-release]:https://github.com/yagop/node-telegram-bot-api/tree/release/doc/usage.md -[examples-release]:https://github.com/yagop/node-telegram-bot-api/tree/release/doc/help.md -[help-release]:https://github.com/yagop/node-telegram-bot-api/tree/release/examples +[examples-release]:https://github.com/yagop/node-telegram-bot-api/tree/release/examples +[help-release]:https://github.com/yagop/node-telegram-bot-api/tree/release/doc/help.md [api-release]:https://github.com/yagop/node-telegram-bot-api/tree/release/doc/api.md [usage-dev]:https://github.com/yagop/node-telegram-bot-api/tree/master/doc/usage.md -[examples-dev]:https://github.com/yagop/node-telegram-bot-api/tree/master/doc/help.md -[help-dev]:https://github.com/yagop/node-telegram-bot-api/tree/master/examples +[examples-dev]:https://github.com/yagop/node-telegram-bot-api/tree/master/examples +[help-dev]:https://github.com/yagop/node-telegram-bot-api/tree/master/doc/help.md [api-dev]:https://github.com/yagop/node-telegram-bot-api/tree/master/doc/api.md [contributing]:https://github.com/yagop/node-telegram-bot-api/tree/master/CONTRIBUTING.md From 3ed45fb624bb6dbab54820524c54a22988ac1980 Mon Sep 17 00:00:00 2001 From: Daniil Yastremskiy Date: Sun, 29 Jan 2017 18:55:11 +0300 Subject: [PATCH 07/28] examples: Add webhook example for Heroku (#271) --- examples/herokuWebHook.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 examples/herokuWebHook.js diff --git a/examples/herokuWebHook.js b/examples/herokuWebHook.js new file mode 100644 index 00000000..6ae5fc95 --- /dev/null +++ b/examples/herokuWebHook.js @@ -0,0 +1,36 @@ +/** + * This example demonstrates setting up webhook + * on the Heroku platform. + */ + + +const TOKEN = process.env.TELEGRAM_TOKEN || 'YOUR_TELEGRAM_BOT_TOKEN'; +const TelegramBot = require('..'); + +const options = { + webHook: { + //Port which you should bind to is assigned to $PORT variable + //See: https://devcenter.heroku.com/articles/dynos#local-environment-variables + port: process.env.PORT + // you do NOT need to set up certificates since Heroku provides + // the SSL certs already (https://.herokuapp.com) + // Also no need to pass IP because on Heroku you need to bind to 0.0.0.0 + } +}; +// Heroku routes from port :443 to $PORT +// Add URL of your app to env variable or enable Dyno Metadata +// to get this automatically +// See: https://devcenter.heroku.com/articles/dyno-metadata +const url = process.env.APP_URL || 'https://.herokuapp.com:443'; +const bot = new TelegramBot(TOKEN, options); + + +// This informs the Telegram servers of the new webhook. +// Note: we do not need to pass in the cert, as it already provided +bot.setWebHook(`${url}/bot${TOKEN}`); + + +// Just to ping! +bot.on('message', function onMessage(msg) { + bot.sendMessage(msg.chat.id, 'Running on Heroku!'); +}); From 24a3f6dadefcad80c09208ca1d6a0ec4b3f64ef0 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Sun, 29 Jan 2017 18:59:05 +0300 Subject: [PATCH 08/28] examples: Minor cleanup --- examples/herokuWebHook.js | 7 +++---- examples/httpsWebHook.js | 2 +- examples/openShiftWebHook.js | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/herokuWebHook.js b/examples/herokuWebHook.js index 6ae5fc95..a348c329 100644 --- a/examples/herokuWebHook.js +++ b/examples/herokuWebHook.js @@ -6,11 +6,10 @@ const TOKEN = process.env.TELEGRAM_TOKEN || 'YOUR_TELEGRAM_BOT_TOKEN'; const TelegramBot = require('..'); - const options = { webHook: { - //Port which you should bind to is assigned to $PORT variable - //See: https://devcenter.heroku.com/articles/dynos#local-environment-variables + // Port to which you should bind is assigned to $PORT variable + // See: https://devcenter.heroku.com/articles/dynos#local-environment-variables port: process.env.PORT // you do NOT need to set up certificates since Heroku provides // the SSL certs already (https://.herokuapp.com) @@ -32,5 +31,5 @@ bot.setWebHook(`${url}/bot${TOKEN}`); // Just to ping! bot.on('message', function onMessage(msg) { - bot.sendMessage(msg.chat.id, 'Running on Heroku!'); + bot.sendMessage(msg.chat.id, 'I am alive on Heroku!'); }); diff --git a/examples/httpsWebHook.js b/examples/httpsWebHook.js index ad0f40a4..c2a27ef7 100644 --- a/examples/httpsWebHook.js +++ b/examples/httpsWebHook.js @@ -26,5 +26,5 @@ bot.setWebHook(`${url}/bot${TOKEN}`, { // Just to ping! bot.on('message', function onMessage(msg) { - bot.sendMessage(msg.chat.id, "I'm alive!"); + bot.sendMessage(msg.chat.id, 'I am alive!'); }); diff --git a/examples/openShiftWebHook.js b/examples/openShiftWebHook.js index 90b15fa7..162d02a7 100644 --- a/examples/openShiftWebHook.js +++ b/examples/openShiftWebHook.js @@ -28,5 +28,5 @@ bot.setWebHook(`${url}/bot${TOKEN}`); // Just to ping! bot.on('message', function onMessage(msg) { - bot.sendMessage(msg.chat.id, "I'm alive on OpenShift!"); + bot.sendMessage(msg.chat.id, 'I am alive on OpenShift!'); }); From 92506f50e6b132700c60f63798ff63b6891c20de Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Mon, 30 Jan 2017 10:13:31 +0300 Subject: [PATCH 09/28] doc: Link to the latest documentation --- README.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c9c71f30..e62e46c2 100644 --- a/README.md +++ b/README.md @@ -54,24 +54,21 @@ bot.on('message', function (msg) { ## Documentation - * Usage ([release][usage-release] / [development][usage-dev]) - * Examples ([release][examples-release] / [development][examples-dev]) - * Help Information ([release][help-release] / [development][help-dev]) + * [Usage][usage] + * [Examples][examples] + * [Help Information][help] * API Reference ([release][api-release] / [development][api-dev]) * [Contributing to the Project][contributing] _**Note**: Development is done against the **master** branch. Code for the latest release resides on the **release** branch._ -[usage-release]:https://github.com/yagop/node-telegram-bot-api/tree/release/doc/usage.md -[examples-release]:https://github.com/yagop/node-telegram-bot-api/tree/release/examples -[help-release]:https://github.com/yagop/node-telegram-bot-api/tree/release/doc/help.md -[api-release]:https://github.com/yagop/node-telegram-bot-api/tree/release/doc/api.md -[usage-dev]:https://github.com/yagop/node-telegram-bot-api/tree/master/doc/usage.md -[examples-dev]:https://github.com/yagop/node-telegram-bot-api/tree/master/examples -[help-dev]:https://github.com/yagop/node-telegram-bot-api/tree/master/doc/help.md +[usage]:https://github.com/yagop/node-telegram-bot-api/tree/master/doc/usage.md +[examples]:https://github.com/yagop/node-telegram-bot-api/tree/master/examples +[help]:https://github.com/yagop/node-telegram-bot-api/tree/master/doc/help.md [api-dev]:https://github.com/yagop/node-telegram-bot-api/tree/master/doc/api.md +[api-release]:https://github.com/yagop/node-telegram-bot-api/tree/release/doc/api.md [contributing]:https://github.com/yagop/node-telegram-bot-api/tree/master/CONTRIBUTING.md From 3263c6c253b5f8b115557361cbf04650f898b08b Mon Sep 17 00:00:00 2001 From: Anton Mironov Date: Sun, 29 Jan 2017 21:59:40 +0300 Subject: [PATCH 10/28] Webhook health check endpoint --- doc/api.md | 1 + src/telegram.js | 1 + src/telegramWebHook.js | 31 ++++++++++++++++++++----------- test/telegram.js | 6 ++++++ test/utils.js | 27 +++++++++++++++++++++++---- 5 files changed, 51 insertions(+), 15 deletions(-) diff --git a/doc/api.md b/doc/api.md index 9fa7798f..4da53b85 100644 --- a/doc/api.md +++ b/doc/api.md @@ -80,6 +80,7 @@ Emits `message` when a message arrives. | [options.webHook.pfx] | String | | Path to file with PFX private key and certificate chain for webHook server. The file is read **synchronously**! | | [options.webHook.autoOpen] | Boolean | true | Open webHook immediately | | [options.webHook.https] | Object | | Options to be passed to `https.createServer()`. Note that `options.webHook.key`, `options.webHook.cert` and `options.webHook.pfx`, if provided, will be used to override `key`, `cert` and `pfx` in this object, respectively. See https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener for more information. | +| [options.webHook.healthEndpoint] | String | /healthz | An endpoint for health checks that always responds with 200 OK | | [options.onlyFirstMatch] | Boolean | false | Set to true to stop after first match. Otherwise, all regexps are executed | | [options.request] | Object | | Options which will be added for all requests to telegram api. See https://github.com/request/request#requestoptions-callback for more information. | | [options.baseApiUrl] | String | https://api.telegram.org | API Base URl; useful for proxying and testing | diff --git a/src/telegram.js b/src/telegram.js index 8f6af71f..1c4be1c3 100644 --- a/src/telegram.js +++ b/src/telegram.js @@ -59,6 +59,7 @@ class TelegramBot extends EventEmitter { * Note that `options.webHook.key`, `options.webHook.cert` and `options.webHook.pfx`, if provided, will be * used to override `key`, `cert` and `pfx` in this object, respectively. * See https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener for more information. + * @param {String} [options.webHook.healthEndpoint=/healthz] An endpoint for health checks that always responds with 200 OK * @param {Boolean} [options.onlyFirstMatch=false] Set to true to stop after first match. Otherwise, all regexps are executed * @param {Object} [options.request] Options which will be added for all requests to telegram api. * See https://github.com/request/request#requestoptions-callback for more information. diff --git a/src/telegramWebHook.js b/src/telegramWebHook.js index 6ee33f24..88e2f5b1 100644 --- a/src/telegramWebHook.js +++ b/src/telegramWebHook.js @@ -13,6 +13,7 @@ class TelegramBotWebHook { * @param {String} token Telegram API token * @param {Boolean|Object} options WebHook options * @param {Number} [options.port=8443] Port to bind to + * @param {String} [options.healthEndpoint=/healthz] An endpoint for health checks that always responds with 200 OK * @param {Function} callback Function for process a new update */ constructor(token, options, callback) { @@ -27,6 +28,7 @@ class TelegramBotWebHook { this.options.https = options.https || {}; this.callback = callback; this._regex = new RegExp(this.token); + this._healthRegex = new RegExp(options.healthEndpoint || '/healthz'); this._webServer = null; this._open = false; this._requestListener = this._requestListener.bind(this); @@ -133,20 +135,27 @@ class TelegramBotWebHook { debug('WebHook request URL: %s', req.url); debug('WebHook request headers: %j', req.headers); - // If there isn't token on URL - if (!this._regex.test(req.url)) { + if (this._regex.test(req.url)) { + if (req.method === 'POST') { + req + .pipe(bl(this._parseBody)) + .on('finish', () => res.end('OK')); + } else { + // Authorized but not a POST + debug('WebHook request isn\'t a POST'); + res.statusCode = 418; // I'm a teabot! + res.end(); + } + } else if (this._healthRegex.test(req.url)) { + // If this is a health check + debug('WebHook health check passed'); + res.statusCode = 200; + res.end('OK'); + } else if (!this._regex.test(req.url)) { + // If there isn't token on URL debug('WebHook request unauthorized'); res.statusCode = 401; res.end(); - } else if (req.method === 'POST') { - req - .pipe(bl(this._parseBody)) - .on('finish', () => res.end('OK')); - } else { - // Authorized but not a POST - debug('WebHook request isn\'t a POST'); - res.statusCode = 418; // I'm a teabot! - res.end(); } } } diff --git a/test/telegram.js b/test/telegram.js index b2c60596..82f2474e 100644 --- a/test/telegram.js +++ b/test/telegram.js @@ -133,6 +133,12 @@ describe('TelegramBot', function telegramSuite() { }); describe('WebHook', function webHookSuite() { + it('returns OK for healthz endpoint', function test(done) { + utils.sendWebHookRequest(webHookPort2, '/healthz', { json: false }).then(resp => { + assert.equal(resp, 'OK'); + return done(); + }); + }); it('returns 401 error if token is wrong', function test(done) { utils.sendWebHookMessage(webHookPort2, 'wrong-token').catch(resp => { assert.equal(resp.statusCode, 401); diff --git a/test/utils.js b/test/utils.js index c63c58ed..044bf09f 100644 --- a/test/utils.js +++ b/test/utils.js @@ -30,6 +30,17 @@ exports = module.exports = { * @return {Promise} */ isPollingMockServer, + /** + * Send a message to the webhook at the specified port and path. + * @param {Number} port + * @param {String} path + * @param {Object} [options] + * @param {String} [options.method=POST] Method to use + * @param {Object} [options.message] Message to send. Default to a generic text message + * @param {Boolean} [options.https=false] Use https + * @return {Promise} + */ + sendWebHookRequest, /** * Send a message to the webhook at the specified port. * @param {Number} port @@ -134,11 +145,11 @@ function hasOpenWebHook(port, reverse) { } -function sendWebHookMessage(port, token, options = {}) { +function sendWebHookRequest(port, path, options = {}) { assert.ok(port); - assert.ok(token); + assert.ok(path); const protocol = options.https ? 'https' : 'http'; - const url = `${protocol}://127.0.0.1:${port}/bot${token}`; + const url = `${protocol}://127.0.0.1:${port}${path}`; return request({ url, method: options.method || 'POST', @@ -146,11 +157,19 @@ function sendWebHookMessage(port, token, options = {}) { update_id: 1, message: options.message || { text: 'test' } }, - json: true, + json: options.json || true, }); } +function sendWebHookMessage(port, token, options = {}) { + assert.ok(port); + assert.ok(token); + const path = `/bot${token}`; + return sendWebHookRequest(port, path, options); +} + + function handleRatelimit(bot, methodName, suite) { const backupMethodName = `__${methodName}`; if (!bot[backupMethodName]) bot[backupMethodName] = bot[methodName]; From 9f031a72e5afd8a6593d4149c4874e71e02c12fe Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Mon, 30 Jan 2017 13:24:15 +0300 Subject: [PATCH 11/28] pr/272: Finish on PR --- CHANGELOG.md | 3 +++ src/telegramWebHook.js | 15 ++++++--------- test/telegram.js | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc873c3b..110fa9f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased][Unreleased] +Added: + +1. Add health-check endpoint (by @mironov) * * * diff --git a/src/telegramWebHook.js b/src/telegramWebHook.js index 88e2f5b1..d35c649b 100644 --- a/src/telegramWebHook.js +++ b/src/telegramWebHook.js @@ -136,23 +136,20 @@ class TelegramBotWebHook { debug('WebHook request headers: %j', req.headers); if (this._regex.test(req.url)) { - if (req.method === 'POST') { - req - .pipe(bl(this._parseBody)) - .on('finish', () => res.end('OK')); - } else { - // Authorized but not a POST + if (req.method !== 'POST') { debug('WebHook request isn\'t a POST'); res.statusCode = 418; // I'm a teabot! res.end(); + } else { + req + .pipe(bl(this._parseBody)) + .on('finish', () => res.end('OK')); } } else if (this._healthRegex.test(req.url)) { - // If this is a health check debug('WebHook health check passed'); res.statusCode = 200; res.end('OK'); - } else if (!this._regex.test(req.url)) { - // If there isn't token on URL + } else { debug('WebHook request unauthorized'); res.statusCode = 401; res.end(); diff --git a/test/telegram.js b/test/telegram.js index 82f2474e..7a5b89a6 100644 --- a/test/telegram.js +++ b/test/telegram.js @@ -133,7 +133,7 @@ describe('TelegramBot', function telegramSuite() { }); describe('WebHook', function webHookSuite() { - it('returns OK for healthz endpoint', function test(done) { + it('returns 200 OK for health endpoint', function test(done) { utils.sendWebHookRequest(webHookPort2, '/healthz', { json: false }).then(resp => { assert.equal(resp, 'OK'); return done(); From dad8697411d655f851f85b02b71e66f3226a131c Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Mon, 30 Jan 2017 13:44:07 +0300 Subject: [PATCH 12/28] doc: Document options.webHook.host --- doc/api.md | 1 + src/telegram.js | 1 + src/telegramWebHook.js | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/api.md b/doc/api.md index 4da53b85..5a084925 100644 --- a/doc/api.md +++ b/doc/api.md @@ -74,6 +74,7 @@ Emits `message` when a message arrives. | [options.polling.interval] | String | Number | 300 | Interval between requests in miliseconds | | [options.polling.autoStart] | Boolean | true | Start polling immediately | | [options.webHook] | Boolean | Object | false | Set true to enable WebHook or set options | +| [options.webHook.host] | String | 0.0.0.0 | Host to bind to | | [options.webHook.port] | Number | 8443 | Port to bind to | | [options.webHook.key] | String | | Path to file with PEM private key for webHook server. The file is read **synchronously**! | | [options.webHook.cert] | String | | Path to file with PEM certificate (public) for webHook server. The file is read **synchronously**! | diff --git a/src/telegram.js b/src/telegram.js index 1c4be1c3..ffbb419e 100644 --- a/src/telegram.js +++ b/src/telegram.js @@ -47,6 +47,7 @@ class TelegramBot extends EventEmitter { * @param {String|Number} [options.polling.interval=300] Interval between requests in miliseconds * @param {Boolean} [options.polling.autoStart=true] Start polling immediately * @param {Boolean|Object} [options.webHook=false] Set true to enable WebHook or set options + * @param {String} [options.webHook.host=0.0.0.0] Host to bind to * @param {Number} [options.webHook.port=8443] Port to bind to * @param {String} [options.webHook.key] Path to file with PEM private key for webHook server. * The file is read **synchronously**! diff --git a/src/telegramWebHook.js b/src/telegramWebHook.js index d35c649b..d4db943a 100644 --- a/src/telegramWebHook.js +++ b/src/telegramWebHook.js @@ -12,6 +12,7 @@ class TelegramBotWebHook { * * @param {String} token Telegram API token * @param {Boolean|Object} options WebHook options + * @param {String} [options.host=0.0.0.0] Host to bind to * @param {Number} [options.port=8443] Port to bind to * @param {String} [options.healthEndpoint=/healthz] An endpoint for health checks that always responds with 200 OK * @param {Function} callback Function for process a new update @@ -24,11 +25,13 @@ class TelegramBotWebHook { this.token = token; this.options = options; + this.options.host = options.host || '0.0.0.0'; this.options.port = options.port || 8443; this.options.https = options.https || {}; + this.options.healthEndpoint = options.healthEndpoint || '/healthz'; this.callback = callback; this._regex = new RegExp(this.token); - this._healthRegex = new RegExp(options.healthEndpoint || '/healthz'); + this._healthRegex = new RegExp(this.options.healthEndpoint); this._webServer = null; this._open = false; this._requestListener = this._requestListener.bind(this); From 2ee761677a30173a63096d554ca29cb467f73e68 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Tue, 31 Jan 2017 09:27:57 +0300 Subject: [PATCH 13/28] doc: Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 110fa9f4..72f95223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). Added: 1. Add health-check endpoint (by @mironov) + * `options.webHook.healthEndpoint` * * * From 2013f6cffa2d598ec1a2a45c226ff538a50ad6d0 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Wed, 1 Feb 2017 11:47:06 +0300 Subject: [PATCH 14/28] doc: Add templates for Issues and PRs --- .github/ISSUE_TEMPLATE.md | 68 ++++++++++++++++++++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 23 +++++++++++ 2 files changed, 91 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..531f1a0a --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,68 @@ + + + + + + +> Bug Report + +I have read: + +* [Usage information](https://github.com/yagop/node-telegram-bot-api/tree/master/doc/usage.md) +* [Help information](https://github.com/yagop/node-telegram-bot-api/tree/master/doc/help.md) + +I am using the latest version of the library. + +### Expected Behavior + + + +### Actual Behavior + + + +### Steps to reproduce the Behavior + + + + + + + +> Feature Request + +I have: + +* searched for such a feature request (https://github.com/yagop/node-telegram-bot-api/labels/enhancement) and found none + +### Introduction + + + +### Example + + + + + + + +> Question + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..e35ebd95 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ + +- [ ] All tests pass +- [ ] I have run `npm run gen-doc` + +### Description + + + +### References + + From 9d12bdfa41e5f22062543a33cd53798e61dc489f Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Fri, 3 Feb 2017 10:16:19 +0300 Subject: [PATCH 15/28] src/webhook: Use String#indexOf() instead of RegExp#test() to find token References: * Original PR: https://github.com/yagop/node-telegram-bot-api/pull/147 * Original Author: @AVVS --- CHANGELOG.md | 2 ++ src/telegramWebHook.js | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72f95223..908382f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ Added: 1. Add health-check endpoint (by @mironov) * `options.webHook.healthEndpoint` +1. Use *String#indexOf()*, instead of *RegExp#test()*, to + find token in webhook request (by @AVVS) * * * diff --git a/src/telegramWebHook.js b/src/telegramWebHook.js index d4db943a..b8f29f94 100644 --- a/src/telegramWebHook.js +++ b/src/telegramWebHook.js @@ -30,7 +30,6 @@ class TelegramBotWebHook { this.options.https = options.https || {}; this.options.healthEndpoint = options.healthEndpoint || '/healthz'; this.callback = callback; - this._regex = new RegExp(this.token); this._healthRegex = new RegExp(this.options.healthEndpoint); this._webServer = null; this._open = false; @@ -138,7 +137,7 @@ class TelegramBotWebHook { debug('WebHook request URL: %s', req.url); debug('WebHook request headers: %j', req.headers); - if (this._regex.test(req.url)) { + if (req.url.indexOf(this.token) !== -1) { if (req.method !== 'POST') { debug('WebHook request isn\'t a POST'); res.statusCode = 418; // I'm a teabot! From 14f37c71815827c888df1d0e627a9eaa414e7e5d Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Fri, 3 Feb 2017 10:47:41 +0300 Subject: [PATCH 16/28] test: Add test for TelegramBot#sendDocument() using 'fileOpts' param References: * Original PR: https://github.com/yagop/node-telegram-bot-api/pull/152 * Original Author: @evolun --- test/telegram.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/telegram.js b/test/telegram.js index 7a5b89a6..9c390dc0 100644 --- a/test/telegram.js +++ b/test/telegram.js @@ -522,6 +522,13 @@ describe('TelegramBot', function telegramSuite() { assert.ok(is.object(resp.document)); }); }); + it('should send a document with custom file options', function test() { + const document = fs.createReadStream(`${__dirname}/data/photo.gif`); + const fileOpts = { filename: 'customfilename.gif' }; + return bot.sendDocument(USERID, document, {}, fileOpts).then(resp => { + assert.equal(resp.document.file_name, fileOpts.filename); + }); + }); }); describe('#sendSticker', function sendStickerSuite() { From 3221d647ccdbcc58562faceabf94ee4c6dae6966 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Wed, 8 Feb 2017 11:33:08 +0300 Subject: [PATCH 17/28] src/polling: Fix bug #276 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: With environment variable, `NODE_ENV`, is set to 'development', 'request-promise' emits the process warning: Warning: a promise was created in a handler, but was not returned from it > If you know what you're doing and don't want to silence all > warnings, you can create runaway promises without causing this > warning by returning e.g. null: > > — http://bluebirdjs.com/docs/warning-explanations.html#warning-a-promise-was-created-in-a-handler-but-was-not-returned-from-it We believe we know what we are doing. References: * Issue #276: https://github.com/yagop/node-telegram-bot-api/issues/276 --- src/telegramPolling.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/telegramPolling.js b/src/telegramPolling.js index 91d6b45b..365c168c 100644 --- a/src/telegramPolling.js +++ b/src/telegramPolling.js @@ -104,6 +104,7 @@ class TelegramBotPolling { debug('updated offset: %s', this._offset); this.callback(update); }); + return null; }) .catch(err => { debug('polling error: %s', err.message); From 7e4cadb514346f1fc7262fb445363b9ab9beeec2 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Wed, 8 Feb 2017 11:44:42 +0300 Subject: [PATCH 18/28] src/polling: Fix bug #281 Bug: On certain errors, during polling, cause the following error to be thrown: TypeError: Cannot read property 'statusCode' of undefined This is caused when we try to access the 'response' property on the error object in the error handler (`catch(error)`). It goes missing when the error was fatal, for example, network error, thus no server response available. References: * Issue #281: https://github.com/yagop/node-telegram-bot-api/issues/281 * Reported-by: @dimawebmaker --- src/telegramPolling.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/telegramPolling.js b/src/telegramPolling.js index 365c168c..6943ceb0 100644 --- a/src/telegramPolling.js +++ b/src/telegramPolling.js @@ -146,7 +146,7 @@ class TelegramBotPolling { return this.request('getUpdates', opts) .catch(err => { - if (err.response.statusCode === ANOTHER_WEB_HOOK_USED) { + if (err.response && err.response.statusCode === ANOTHER_WEB_HOOK_USED) { return this._unsetWebHook(); } throw err; From d4a469df6b6eeae4c045faf7b6e63bdc765b0a15 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Wed, 8 Feb 2017 12:16:51 +0300 Subject: [PATCH 19/28] pkg: Fix bug #275 Bug: Node.js v4 does not support the ES6 syntax fully, thus we get the error: Block scoped declarations (let, const, function,class) not yet supported outside strict mode Fix: * Load transpiled code * Deprecate support for Node.js v4.x References: * Bug report: https://github.com/yagop/node-telegram-bot-api/issues/275 * PR: https://github.com/yagop/node-telegram-bot-api/pull/280 * Reported-by: @crazyabdul * PR-by: @crazyabdul --- index.js | 10 +++++----- test/telegram.js | 9 +++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 6165386a..8e0c99b2 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,12 @@ /** - * If running on Nodejs 0.12, we load the transpiled code. + * If running on Nodejs 4.x and below, we load the transpiled code. * Otherwise, we use the ES6 code. - * We are deprecating support for Node.js v0.x + * We are deprecating support for Node.js v4.x and below. */ -const majorVersion = process.versions.node.split('.')[0]; -if (majorVersion === '0') { +const majorVersion = parseInt(process.versions.node.split('.')[0], 10); +if (majorVersion <= 4) { const deprecate = require('depd')('node-telegram-bot-api'); - deprecate('Node.js v0.12 and below will no longer be supported in the future'); + deprecate('Node.js v4.x and below will no longer be supported in the future'); module.exports = require('./lib/telegram'); } else { module.exports = require('./src/telegram'); diff --git a/test/telegram.js b/test/telegram.js index 9c390dc0..6cd243c5 100644 --- a/test/telegram.js +++ b/test/telegram.js @@ -44,12 +44,13 @@ before(function beforeAll() { describe('module.exports', function moduleExportsSuite() { - it('is loaded from src/ if NOT on Node.js 0.12', function test() { - if (process.versions.node.split('.')[0] === '0') this.skip(); // skip on Node.js v0.12 + const nodeVersion = parseInt(process.versions.node.split('.')[0], 10); + it('is loaded from src/ on Node.js v5+', function test() { + if (nodeVersion <= 4) this.skip(); // skip on Node.js v4 and below assert.equal(TelegramBot, require('../src/telegram')); }); - it('is loaded from lib/ if on Node.js 0.12', function test() { - if (process.versions.node.split('.')[0] !== '0') this.skip(); // skip on newer versions + it('is loaded from lib/ on Node.js v4 and below', function test() { + if (nodeVersion > 4) this.skip(); // skip on newer versions assert.equal(TelegramBot, require('../lib/telegram')); }); }); From 0441c99b974acecd7e849d4c5296bb3e33c48890 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Wed, 8 Feb 2017 13:27:23 +0300 Subject: [PATCH 20/28] examples: Add webhook example for Zeit Now References: * Original PR #274: https://github.com/yagop/node-telegram-bot-api/pull/274 * PR-by: @Ferrari --- examples/nowWebHook.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 examples/nowWebHook.js diff --git a/examples/nowWebHook.js b/examples/nowWebHook.js new file mode 100644 index 00000000..9a94ef14 --- /dev/null +++ b/examples/nowWebHook.js @@ -0,0 +1,32 @@ +/** + * This example demonstrates setting up webhook on Zeit Now platform. + * Attention: You have to use webhook with Zeit Now only, polling doesn't + * work. + */ + + +const TOKEN = process.env.TELEGRAM_TOKEN || 'YOUR_TELEGRAM_BOT_TOKEN'; +const TelegramBot = require('..'); +const options = { + webHook: { + // Just use 443 directly + port: 443 + } +}; +// You can use 'now alias ' to assign fixed +// domain. +// See: https://zeit.co/blog/now-alias +// Or just use NOW_URL to get deployment url from env. +const url = 'YOUR_DOMAIN_ALIAS' || process.env.NOW_URL; +const bot = new TelegramBot(TOKEN, options); + + +// This informs the Telegram servers of the new webhook. +// Note: we do not need to pass in the cert, as it already provided +bot.setWebHook(`${url}/bot${TOKEN}`); + + +// Just to ping! +bot.on('message', function onMessage(msg) { + bot.sendMessage(msg.chat.id, 'I am alive on Zeit Now!'); +}); From 79de62a96e98e8516d2388da473d7a6a33f9d9b4 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Thu, 9 Feb 2017 15:07:08 +0300 Subject: [PATCH 21/28] src/telegram: Add TelegramBot#removeReplyListener() Feature: Please see the updated API Reference. References: * Author: @githugger (Frederic Schneider ) * Original PR: https://github.com/yagop/node-telegram-bot-api/pull/74 --- CHANGELOG.md | 2 ++ doc/api.md | 22 +++++++++++++++++++--- src/telegram.js | 30 ++++++++++++++++++++++++++---- test/telegram.js | 18 ++++++++++++++++++ 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 908382f1..f32fb43a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). Added: +1. Add methods: + * *TelegramBot#removeReplyListener()* (by @githugger) 1. Add health-check endpoint (by @mironov) * `options.webHook.healthEndpoint` 1. Use *String#indexOf()*, instead of *RegExp#test()*, to diff --git a/doc/api.md b/doc/api.md index 5a084925..2112e5fb 100644 --- a/doc/api.md +++ b/doc/api.md @@ -47,7 +47,8 @@ TelegramBot * [.getFileLink(fileId)](#TelegramBot+getFileLink) ⇒ Promise * [.downloadFile(fileId, downloadDir)](#TelegramBot+downloadFile) ⇒ Promise * [.onText(regexp, callback)](#TelegramBot+onText) - * [.onReplyToMessage(chatId, messageId, callback)](#TelegramBot+onReplyToMessage) + * [.onReplyToMessage(chatId, messageId, callback)](#TelegramBot+onReplyToMessage) ⇒ Number + * [.removeReplyListener(replyListenerId)](#TelegramBot+removeReplyListener) ⇒ Object * [.getChat(chatId)](#TelegramBot+getChat) ⇒ Promise * [.getChatAdministrators(chatId)](#TelegramBot+getChatAdministrators) ⇒ Promise * [.getChatMembersCount(chatId)](#TelegramBot+getChatMembersCount) ⇒ Promise @@ -594,16 +595,31 @@ Register a RegExp to test against an incomming text message. -### telegramBot.onReplyToMessage(chatId, messageId, callback) +### telegramBot.onReplyToMessage(chatId, messageId, callback) ⇒ Number Register a reply to wait for a message response. **Kind**: instance method of [TelegramBot](#TelegramBot) +**Returns**: Number - id The ID of the inserted reply listener. | Param | Type | Description | | --- | --- | --- | | chatId | Number | String | The chat id where the message cames from. | | messageId | Number | String | The message id to be replied. | -| callback | function | Callback will be called with the reply message. | +| callback | function | Callback will be called with the reply message. | + + + +### telegramBot.removeReplyListener(replyListenerId) ⇒ Object +Removes a reply that has been prev. registered for a message response. + +**Kind**: instance method of [TelegramBot](#TelegramBot) +**Returns**: Object - deletedListener The removed reply listener if + found. This object has `id`, `chatId`, `messageId` and `callback` + properties. If not found, returns `null`. + +| Param | Type | Description | +| --- | --- | --- | +| replyListenerId | Number | The ID of the reply listener. | diff --git a/src/telegram.js b/src/telegram.js index ffbb419e..405131d3 100644 --- a/src/telegram.js +++ b/src/telegram.js @@ -79,7 +79,8 @@ class TelegramBot extends EventEmitter { this.options.baseApiUrl = options.baseApiUrl || 'https://api.telegram.org'; this.options.filepath = (typeof options.filepath === 'undefined') ? true : options.filepath; this._textRegexpCallbacks = []; - this._onReplyToMessages = []; + this._replyListenerId = 0; + this._replyListeners = []; this._polling = null; this._webHook = null; @@ -483,7 +484,7 @@ class TelegramBot extends EventEmitter { } if (message.reply_to_message) { // Only callbacks waiting for this message - this._onReplyToMessages.forEach(reply => { + this._replyListeners.forEach(reply => { // Message from the same chat if (reply.chatId === message.chat.id) { // Responding to that message @@ -1011,14 +1012,35 @@ class TelegramBot extends EventEmitter { * @param {Number|String} chatId The chat id where the message cames from. * @param {Number|String} messageId The message id to be replied. * @param {Function} callback Callback will be called with the reply - * message. + * message. + * @return {Number} id The ID of the inserted reply listener. */ onReplyToMessage(chatId, messageId, callback) { - this._onReplyToMessages.push({ + const id = ++this._replyListenerId; + this._replyListeners.push({ + id, chatId, messageId, callback }); + return id; + } + + /** + * Removes a reply that has been prev. registered for a message response. + * @param {Number} replyListenerId The ID of the reply listener. + * @return {Object} deletedListener The removed reply listener if + * found. This object has `id`, `chatId`, `messageId` and `callback` + * properties. If not found, returns `null`. + */ + removeReplyListener(replyListenerId) { + const index = this._replyListeners.findIndex((replyListener) => { + return replyListener.id === replyListenerId; + }); + if (index === -1) { + return null; + } + return this._replyListeners.splice(index, 1)[0]; } /** diff --git a/test/telegram.js b/test/telegram.js index 6cd243c5..a509376c 100644 --- a/test/telegram.js +++ b/test/telegram.js @@ -890,6 +890,24 @@ describe('TelegramBot', function telegramSuite() { describe.skip('#onReplyToMessage', function onReplyToMessageSuite() {}); + describe('#removeReplyListener', function removeReplyListenerSuite() { + const chatId = -1234; + const messageId = 1; + const callback = function noop() {}; + it('returns the right reply-listener', function test() { + const id = bot.onReplyToMessage(chatId, messageId, callback); + const replyListener = bot.removeReplyListener(id); + assert.equal(id, replyListener.id); + assert.equal(chatId, replyListener.chatId); + assert.equal(messageId, replyListener.messageId); + assert.equal(callback, replyListener.callback); + }); + it('returns `null` if missing', function test() { + // NOTE: '0' is never a valid reply listener ID :) + assert.equal(null, bot.removeReplyListener(0)); + }); + }); + describe('#getChat', function getChatSuite() { before(function before() { utils.handleRatelimit(bot, 'getChat', this); From 6f5dad6e5b70825656f35c4e9d483d466d96c6c1 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Thu, 9 Feb 2017 15:25:38 +0300 Subject: [PATCH 22/28] src/telegram: Add shim for Array#findIndex() Bug: Array#findIndex() is unavailable in Node.js v0.x. Since we are deprecating Node.js v0.x already, we can use a shim in the mean time. Once we stop supporting that version range, we can drop the shim entirely. tags: deprecate/node-v0.x --- package.json | 3 ++- src/telegram.js | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 322f95d7..0fc46da4 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,10 @@ "author": "Yago Pérez ", "license": "MIT", "engines": { - "node" : ">=0.12" + "node": ">=0.12" }, "dependencies": { + "array.prototype.findindex": "^2.0.0", "bl": "^1.1.2", "bluebird": "^3.3.4", "debug": "^2.2.0", diff --git a/src/telegram.js b/src/telegram.js index 405131d3..28b1819b 100644 --- a/src/telegram.js +++ b/src/telegram.js @@ -1,3 +1,6 @@ +// shims +require('array.prototype.findindex').shim(); // for Node.js v0.x + const TelegramBotWebHook = require('./telegramWebHook'); const TelegramBotPolling = require('./telegramPolling'); const debug = require('debug')('node-telegram-bot-api'); From eed7c1e4d0a6e6d26a4a2c52d44ae880253afbaa Mon Sep 17 00:00:00 2001 From: Gocho Mugo Date: Thu, 9 Feb 2017 16:12:22 +0300 Subject: [PATCH 23/28] src: Add proper error handling (#283) Feature: Please see `doc/usage.md` for more information on error-handling. --- CHANGELOG.md | 1 + doc/usage.md | 60 ++++++++++++++++++++++++++++++++ src/errors.js | 59 +++++++++++++++++++++++++++++++ src/telegram.js | 48 ++++++++++--------------- src/telegramPolling.js | 42 +++++++++------------- src/telegramWebHook.js | 64 ++++++++++++++++------------------ test/telegram.js | 79 ++++++++++++++++++++++++++++++++++++++++-- test/utils.js | 14 ++++++-- 8 files changed, 272 insertions(+), 95 deletions(-) create mode 100644 src/errors.js diff --git a/CHANGELOG.md b/CHANGELOG.md index f32fb43a..cdbfb509 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Added: 1. Add methods: * *TelegramBot#removeReplyListener()* (by @githugger) +1. Add proper error handling (by @GochoMugo) 1. Add health-check endpoint (by @mironov) * `options.webHook.healthEndpoint` 1. Use *String#indexOf()*, instead of *RegExp#test()*, to diff --git a/doc/usage.md b/doc/usage.md index c2fb4f00..78043b02 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -3,6 +3,7 @@ 1. [Events](#events) 1. [WebHooks](#WebHooks) 1. [Sending files](#sending-files) +1. [Error handling](#error-handling) * * * @@ -29,6 +30,8 @@ 1. `edited_channel_post`: Received a new version of a channel post that is known to the bot and was edited 1. `edited_channel_post_text` 1. `edited_channel_post_caption` +1. `polling_error`: Error occurred during polling. See [polling errors](#polling-errors). +1. `webhook_error`: Error occurred handling a webhook request. See [webhook errors](#webhook-errors). **Tip:** Its much better to listen a specific event rather than on `message` in order to stay safe from the content. @@ -145,3 +148,60 @@ const bot = new TelegramBot(token, { filepath: false, }); ``` + + + +## Error handling + +Every `Error` object we pass back has the properties: + +* `code` (String): + * value is `EFATAL` if error was fatal e.g. network error + * value is `EPARSE` if response body could **not** be parsed + * value is `ETELEGRAM` if error was returned from Telegram servers +* `response` ([http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)): + * available if `error.code` is **not** `EFATAL` +* `response.body` (String|Object): Error response from Telegram + * type is `String` if `error.code` is `EPARSE` + * type is `Object` if `error.code` is `ETELEGRAM` + +For example, sending message to a non-existent user: + +```js +bot.sendMessage(nonExistentUserId, 'text').catch(error => { + console.log(error.code); // => 'ETELEGRAM' + console.log(error.response.body); // => { ok: false, error_code: 400, description: 'Bad Request: chat not found' } +}); +``` + + +#### Polling errors + +An error may occur during polling. It is up to you to handle it +as you see fit. You may decide to crash your bot after a maximum number +of polling errors occurring. **It is all up to you.** + +By default, the polling error is just logged to stderr, if you do +**not** handle this event yourself. + +Listen on the `'polling_error'` event. For example, + +```js +bot.on('polling_error', (error) => { + console.log(error.code); // => 'EFATAL' +}); +``` + + +#### WebHook errors + +Just like with [polling errors](#polling-errors), you decide on how to +handle it. By default, the error is logged to stderr. + +Listen on the `'webhook_error'` event. For example, + +```js +bot.on('webhook_error', (error) => { + console.log(error.code); // => 'EPARSE' +}); +``` diff --git a/src/errors.js b/src/errors.js new file mode 100644 index 00000000..a4bffd3c --- /dev/null +++ b/src/errors.js @@ -0,0 +1,59 @@ +exports.BaseError = class BaseError extends Error { + /** + * @class BaseError + * @constructor + * @private + * @param {String} code Error code + * @param {String} message Error message + */ + constructor(code, message) { + super(`${code}: ${message}`); + this.code = code; + } +}; + + +exports.FatalError = class FatalError extends exports.BaseError { + /** + * Fatal Error. Error code is `"EFATAL"`. + * @class FatalError + * @constructor + * @param {String|Error} data Error object or message + */ + constructor(data) { + const error = (typeof data === 'string') ? null : data; + const message = error ? error.message : data; + super('EFATAL', message); + if (error) this.stack = error.stack; + } +}; + + +exports.ParseError = class ParseError extends exports.BaseError { + /** + * Error during parsing. Error code is `"EPARSE"`. + * @class ParseError + * @constructor + * @param {String} message Error message + * @param {http.IncomingMessage} response Server response + */ + constructor(message, response) { + super('EPARSE', message); + this.response = response; + } +}; + + +exports.TelegramError = class TelegramError extends exports.BaseError { + /** + * Error returned from Telegram. Error code is `"ETELEGRAM"`. + * @class TelegramError + * @constructor + * @param {String} message Error message + * @param {http.IncomingMessage} response Server response + */ + constructor(message, response) { + super('ETELEGRAM', message); + this.response = response; + } +}; diff --git a/src/telegram.js b/src/telegram.js index 28b1819b..b44536b3 100644 --- a/src/telegram.js +++ b/src/telegram.js @@ -1,6 +1,7 @@ // shims require('array.prototype.findindex').shim(); // for Node.js v0.x +const errors = require('./errors'); const TelegramBotWebHook = require('./telegramWebHook'); const TelegramBotPolling = require('./telegramPolling'); const debug = require('debug')('node-telegram-bot-api'); @@ -31,6 +32,10 @@ Promise.config({ class TelegramBot extends EventEmitter { + static get errors() { + return errors; + } + static get messageTypes() { return _messageTypes; } @@ -136,7 +141,7 @@ class TelegramBot extends EventEmitter { */ _request(_path, options = {}) { if (!this.token) { - throw new Error('Telegram Bot Token not provided!'); + return Promise.reject(new errors.FatalError('Telegram Bot Token not provided!')); } if (this.options.request) { @@ -158,30 +163,22 @@ class TelegramBot extends EventEmitter { debug('HTTP request: %j', options); return request(options) .then(resp => { - if (resp.statusCode !== 200) { - const error = new Error(`${resp.statusCode} ${resp.body}`); - error.response = resp; - throw error; - } - let data; - try { - data = JSON.parse(resp.body); + data = resp.body = JSON.parse(resp.body); } catch (err) { - const error = new Error(`Error parsing Telegram response: ${resp.body}`); - error.response = resp; - throw error; + throw new errors.ParseError(`Error parsing Telegram response: ${resp.body}`, resp); } if (data.ok) { return data.result; } - const error = new Error(`${data.error_code} ${data.description}`); - error.response = resp; - error.response.body = data; - throw error; + throw new errors.TelegramError(`${data.error_code} ${data.description}`, resp); + }).catch(error => { + // TODO: why can't we do `error instanceof errors.BaseError`? + if (error.response) throw error; + throw new errors.FatalError(error); }); } @@ -215,7 +212,7 @@ class TelegramBot extends EventEmitter { } else if (Buffer.isBuffer(data)) { const filetype = fileType(data); if (!filetype) { - throw new Error('Unsupported Buffer file type'); + throw new errors.FatalError('Unsupported Buffer file type'); } formData = {}; formData[type] = { @@ -256,11 +253,11 @@ class TelegramBot extends EventEmitter { */ startPolling(options = {}) { if (this.hasOpenWebHook()) { - return Promise.reject(new Error('Polling and WebHook are mutually exclusive')); + return Promise.reject(new errors.FatalError('Polling and WebHook are mutually exclusive')); } options.restart = typeof options.restart === 'undefined' ? true : options.restart; if (!this._polling) { - this._polling = new TelegramBotPolling(this._request.bind(this), this.options.polling, this.processUpdate.bind(this)); + this._polling = new TelegramBotPolling(this); } return this._polling.start(options); } @@ -305,10 +302,10 @@ class TelegramBot extends EventEmitter { */ openWebHook() { if (this.isPolling()) { - return Promise.reject(new Error('WebHook and Polling are mutually exclusive')); + return Promise.reject(new errors.FatalError('WebHook and Polling are mutually exclusive')); } if (!this._webHook) { - this._webHook = new TelegramBotWebHook(this.token, this.options.webHook, this.processUpdate.bind(this)); + this._webHook = new TelegramBotWebHook(this); } return this._webHook.open(); } @@ -385,14 +382,7 @@ class TelegramBot extends EventEmitter { } } - return this._request('setWebHook', opts) - .then(resp => { - if (!resp) { - throw new Error(resp); - } - - return resp; - }); + return this._request('setWebHook', opts); } /** diff --git a/src/telegramPolling.js b/src/telegramPolling.js index 6943ceb0..dafd2172 100644 --- a/src/telegramPolling.js +++ b/src/telegramPolling.js @@ -5,29 +5,14 @@ const ANOTHER_WEB_HOOK_USED = 409; class TelegramBotPolling { /** * Handles polling against the Telegram servers. - * - * @param {Function} request Function used to make HTTP requests - * @param {Boolean|Object} options Polling options - * @param {Number} [options.timeout=10] Timeout in seconds for long polling - * @param {Number} [options.interval=300] Interval between requests in milliseconds - * @param {Function} callback Function for processing a new update - * @see https://core.telegram.org/bots/api#getupdates + * @param {TelegramBot} bot + * @see https://core.telegram.org/bots/api#getting-updates */ - constructor(request, options = {}, callback) { - /* eslint-disable no-param-reassign */ - if (typeof options === 'function') { - callback = options; - options = {}; - } else if (typeof options === 'boolean') { - options = {}; - } - /* eslint-enable no-param-reassign */ - - this.request = request; - this.options = options; - this.options.timeout = (typeof options.timeout === 'number') ? options.timeout : 10; - this.options.interval = (typeof options.interval === 'number') ? options.interval : 300; - this.callback = callback; + constructor(bot) { + this.bot = bot; + this.options = (typeof bot.options.polling === 'boolean') ? {} : bot.options.polling; + this.options.timeout = (typeof this.options.timeout === 'number') ? this.options.timeout : 10; + this.options.interval = (typeof this.options.interval === 'number') ? this.options.interval : 300; this._offset = 0; this._lastUpdate = 0; this._lastRequest = null; @@ -102,13 +87,18 @@ class TelegramBotPolling { updates.forEach(update => { this._offset = update.update_id; debug('updated offset: %s', this._offset); - this.callback(update); + this.bot.processUpdate(update); }); return null; }) .catch(err => { debug('polling error: %s', err.message); - throw err; + if (this.bot.listeners('polling_error').length) { + this.bot.emit('polling_error', err); + } else { + console.error(err); // eslint-disable-line no-console + } + return null; }) .finally(() => { if (this._abort) { @@ -128,7 +118,7 @@ class TelegramBotPolling { * @private */ _unsetWebHook() { - return this.request('setWebHook'); + return this.bot._request('setWebHook'); } /** @@ -144,7 +134,7 @@ class TelegramBotPolling { }; debug('polling with options: %j', opts); - return this.request('getUpdates', opts) + return this.bot._request('getUpdates', opts) .catch(err => { if (err.response && err.response.statusCode === ANOTHER_WEB_HOOK_USED) { return this._unsetWebHook(); diff --git a/src/telegramWebHook.js b/src/telegramWebHook.js index b8f29f94..2ca20a3d 100644 --- a/src/telegramWebHook.js +++ b/src/telegramWebHook.js @@ -1,3 +1,4 @@ +const errors = require('./errors'); const debug = require('debug')('node-telegram-bot-api'); const https = require('https'); const http = require('http'); @@ -9,27 +10,16 @@ const Promise = require('bluebird'); class TelegramBotWebHook { /** * Sets up a webhook to receive updates - * - * @param {String} token Telegram API token - * @param {Boolean|Object} options WebHook options - * @param {String} [options.host=0.0.0.0] Host to bind to - * @param {Number} [options.port=8443] Port to bind to - * @param {String} [options.healthEndpoint=/healthz] An endpoint for health checks that always responds with 200 OK - * @param {Function} callback Function for process a new update + * @param {TelegramBot} bot + * @see https://core.telegram.org/bots/api#getting-updates */ - constructor(token, options, callback) { - // define opts - if (typeof options === 'boolean') { - options = {}; // eslint-disable-line no-param-reassign - } - - this.token = token; - this.options = options; - this.options.host = options.host || '0.0.0.0'; - this.options.port = options.port || 8443; - this.options.https = options.https || {}; - this.options.healthEndpoint = options.healthEndpoint || '/healthz'; - this.callback = callback; + constructor(bot) { + this.bot = bot; + this.options = (typeof bot.options.webHook === 'boolean') ? {} : bot.options.webHook; + this.options.host = this.options.host || '0.0.0.0'; + this.options.port = this.options.port || 8443; + this.options.https = this.options.https || {}; + this.options.healthEndpoint = this.options.healthEndpoint || '/healthz'; this._healthRegex = new RegExp(this.options.healthEndpoint); this._webServer = null; this._open = false; @@ -100,31 +90,35 @@ class TelegramBotWebHook { return this._open; } - // used so that other funcs are not non-optimizable - _safeParse(json) { - try { - return JSON.parse(json); - } catch (err) { - debug(err); - return null; + /** + * Handle error thrown during processing of webhook request. + * @private + * @param {Error} error + */ + _error(error) { + if (!this.bot.listeners('webhook_error').length) { + return console.error(error); // eslint-disable-line no-console } + return this.bot.emit('webhook_error', error); } /** * Handle request body by passing it to 'callback' * @private */ - _parseBody(err, body) { - if (err) { - return debug(err); + _parseBody(error, body) { + if (error) { + return this._error(new errors.FatalError(error)); } - const data = this._safeParse(body); - if (data) { - return this.callback(data); + let data; + try { + data = JSON.parse(body.toString()); + } catch (parseError) { + return this._error(new errors.ParseError(parseError.message)); } - return null; + return this.bot.processUpdate(data); } /** @@ -137,7 +131,7 @@ class TelegramBotWebHook { debug('WebHook request URL: %s', req.url); debug('WebHook request headers: %j', req.headers); - if (req.url.indexOf(this.token) !== -1) { + if (req.url.indexOf(this.bot.token) !== -1) { if (req.method !== 'POST') { debug('WebHook request isn\'t a POST'); res.statusCode = 418; // I'm a teabot! diff --git a/test/telegram.js b/test/telegram.js index a509376c..345b6bf1 100644 --- a/test/telegram.js +++ b/test/telegram.js @@ -27,6 +27,7 @@ const pollingPort = portindex++; const webHookPort = portindex++; const pollingPort2 = portindex++; const webHookPort2 = portindex++; +const badTgServerPort = portindex++; const staticUrl = `http://127.0.0.1:${staticPort}`; const key = `${__dirname}/../examples/key.pem`; const cert = `${__dirname}/../examples/crt.pem`; @@ -39,6 +40,8 @@ before(function beforeAll() { return utils.startMockServer(pollingPort) .then(() => { return utils.startMockServer(pollingPort2); + }).then(() => { + return utils.startMockServer(badTgServerPort, { bad: true }); }); }); @@ -125,17 +128,33 @@ describe('TelegramBot', function telegramSuite() { return done(); }); }); + it('(polling) emits "polling_error" if error occurs during polling', function test(done) { + const myBot = new TelegramBot(12345, { polling: true }); + myBot.once('polling_error', (error) => { + assert.ok(error); + assert.equal(error.code, 'ETELEGRAM'); + return myBot.stopPolling().then(() => { done(); }).catch(done); + }); + }); it('(webhook) emits "message" on receiving message', function test(done) { botWebHook.once('message', () => { return done(); }); utils.sendWebHookMessage(webHookPort2, TOKEN); }); + it('(webhook) emits "webhook_error" if could not parse webhook request body', function test(done) { + botWebHook.once('webhook_error', (error) => { + assert.ok(error); + assert.equal(error.code, 'EPARSE'); + return done(); + }); + utils.sendWebHookMessage(webHookPort2, TOKEN, { update: 'unparseable!', json: false }); + }); }); describe('WebHook', function webHookSuite() { it('returns 200 OK for health endpoint', function test(done) { - utils.sendWebHookRequest(webHookPort2, '/healthz', { json: false }).then(resp => { + utils.sendWebHookRequest(webHookPort2, '/healthz').then(resp => { assert.equal(resp, 'OK'); return done(); }); @@ -186,6 +205,58 @@ describe('TelegramBot', function telegramSuite() { }); }); + describe('errors', function errorsSuite() { + const botParse = new TelegramBot('useless-token', { + baseApiUrl: `http://localhost:${badTgServerPort}`, + }); + it('FatalError is thrown if token is missing', function test() { + const myBot = new TelegramBot(null); + return myBot.sendMessage(USERID, 'text').catch(error => { + // FIX: assert.ok(error instanceof TelegramBot.errors.FatalError); + assert.equal(error.code, 'EFATAL'); + assert.ok(error.message.indexOf('not provided') > -1); + }); + }); + it('FatalError is thrown if file-type of Buffer could not be determined', function test() { + let buffer; + try { + buffer = Buffer.from('12345'); + } catch (ex) { + buffer = new Buffer('12345'); + } + return bot.sendPhoto(USERID, buffer).catch(error => { + // FIX: assert.ok(error instanceof TelegramBot.errors.FatalError); + assert.equal(error.code, 'EFATAL'); + assert.ok(error.message.indexOf('Unsupported') > -1); + }); + }); + it('FatalError is thrown on network error', function test() { + const myBot = new TelegramBot('useless-token', { + baseApiUrl: 'http://localhost:23', // are we sure this port is not bound to? + }); + return myBot.getMe().catch(error => { + // FIX: assert.ok(error instanceof TelegramBot.errors.FatalError); + assert.equal(error.code, 'EFATAL'); + }); + }); + it('ParseError is thrown if response body could not be parsed', function test() { + botParse.sendMessage(USERID, 'text').catch(error => { + // FIX: assert.ok(error instanceof TelegramBot.errors.ParseError); + assert.equal(error.code, 'EPARSE'); + assert.ok(typeof error.response === 'object'); + assert.ok(typeof error.response.body === 'string'); + }); + }); + it('TelegramError is thrown if error is from Telegram', function test() { + return bot.sendMessage('404', 'text').catch(error => { + // FIX: assert.ok(error instanceof TelegramBot.errors.TelegramError); + assert.equal(error.code, 'ETELEGRAM'); + assert.ok(typeof error.response === 'object'); + assert.ok(typeof error.response.body === 'object'); + }); + }); + }); + describe('#startPolling', function initPollingSuite() { it('initiates polling', function test() { return testbot.startPolling().then(() => { @@ -195,6 +266,8 @@ describe('TelegramBot', function telegramSuite() { it('returns error if using webhook', function test() { return botWebHook.startPolling().catch((err) => { // TODO: check for error in a better way + // FIX: assert.ok(err instanceof TelegramBot.errors.FatalError); + assert.equal(err.code, 'EFATAL'); assert.ok(err.message.indexOf('mutually exclusive') !== -1); }); }); @@ -235,6 +308,8 @@ describe('TelegramBot', function telegramSuite() { it('returns error if using polling', function test() { return botPolling.openWebHook().catch((err) => { // TODO: check for error in a better way + // FIX: assert.ok(err instanceof TelegramBot.errors.FatalError); + assert.equal(err.code, 'EFATAL'); assert.ok(err.message.indexOf('mutually exclusive') !== -1); }); }); @@ -1020,7 +1095,7 @@ describe('TelegramBot', function telegramSuite() { const photo = `${__dirname}/data/photo.gif`; return tgbot.sendPhoto(USERID, photo).catch(err => { // TODO: check for error in a better way - assert.ok(err.response.body.indexOf('Bad Request') !== -1); + assert.ok(err.response.body.description.indexOf('Bad Request') !== -1); }); }); it('should allow stream.path that can not be parsed', function test() { diff --git a/test/utils.js b/test/utils.js index 044bf09f..23df9d88 100644 --- a/test/utils.js +++ b/test/utils.js @@ -36,6 +36,7 @@ exports = module.exports = { * @param {String} path * @param {Object} [options] * @param {String} [options.method=POST] Method to use + * @param {Object} [options.update] Update object to send. * @param {Object} [options.message] Message to send. Default to a generic text message * @param {Boolean} [options.https=false] Use https * @return {Promise} @@ -47,6 +48,7 @@ exports = module.exports = { * @param {String} token * @param {Object} [options] * @param {String} [options.method=POST] Method to use + * @param {Object} [options.update] Update object to send. * @param {Object} [options.message] Message to send. Default to a generic text message * @param {Boolean} [options.https=false] Use https * @return {Promise} @@ -55,6 +57,9 @@ exports = module.exports = { /** * Start a mock server at the specified port. * @param {Number} port + * @param {Object} [options] + * @param {Boolean} [options.bad=false] Bad Mock Server; responding with + * unparseable messages * @return {Promise} */ startMockServer, @@ -76,10 +81,13 @@ const statics = require('node-static'); const servers = {}; -function startMockServer(port) { +function startMockServer(port, options = {}) { assert.ok(port); const server = http.Server((req, res) => { servers[port].polling = true; + if (options.bad) { + return res.end('can not be parsed with JSON.parse()'); + } return res.end(JSON.stringify({ ok: true, result: [{ @@ -153,11 +161,11 @@ function sendWebHookRequest(port, path, options = {}) { return request({ url, method: options.method || 'POST', - body: { + body: options.update || { update_id: 1, message: options.message || { text: 'test' } }, - json: options.json || true, + json: (typeof options.json === 'undefined') ? true : options.json, }); } From e75d51ca8fe46059e78a74e1ece7787c881157cb Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Thu, 9 Feb 2017 17:24:11 +0300 Subject: [PATCH 24/28] src/polling: Add constructor option 'options.polling.params' Feature: Please see the updated API reference for more information on this new option. Side-effects: * `options.timeout` is deprecated! References: * "Feature request": https://github.com/yagop/node-telegram-bot-api/issues/243 --- CHANGELOG.md | 2 ++ doc/api.md | 3 ++- src/telegram.js | 5 ++++- src/telegramPolling.js | 27 +++++++++++++-------------- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdbfb509..b80dca2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). Added: +1. Add constructor options: + * `options.polling.params` (by @GochoMugo) 1. Add methods: * *TelegramBot#removeReplyListener()* (by @githugger) 1. Add proper error handling (by @GochoMugo) diff --git a/doc/api.md b/doc/api.md index 2112e5fb..dd0d52b6 100644 --- a/doc/api.md +++ b/doc/api.md @@ -71,9 +71,10 @@ Emits `message` when a message arrives. | token | String | | Bot Token | | [options] | Object | | | | [options.polling] | Boolean | Object | false | Set true to enable polling or set options. If a WebHook has been set, it will be deleted automatically. | -| [options.polling.timeout] | String | Number | 10 | Timeout in seconds for long polling | +| [options.polling.timeout] | String | Number | 10 | *Deprecated. Use `options.polling.params` instead*. Timeout in seconds for long polling. | | [options.polling.interval] | String | Number | 300 | Interval between requests in miliseconds | | [options.polling.autoStart] | Boolean | true | Start polling immediately | +| [options.polling.params] | Object | | Parameters to be used in polling API requests. See https://core.telegram.org/bots/api#getupdates for more information. | | [options.webHook] | Boolean | Object | false | Set true to enable WebHook or set options | | [options.webHook.host] | String | 0.0.0.0 | Host to bind to | | [options.webHook.port] | Number | 8443 | Port to bind to | diff --git a/src/telegram.js b/src/telegram.js index b44536b3..51941804 100644 --- a/src/telegram.js +++ b/src/telegram.js @@ -51,9 +51,12 @@ class TelegramBot extends EventEmitter { * @param {Object} [options] * @param {Boolean|Object} [options.polling=false] Set true to enable polling or set options. * If a WebHook has been set, it will be deleted automatically. - * @param {String|Number} [options.polling.timeout=10] Timeout in seconds for long polling + * @param {String|Number} [options.polling.timeout=10] *Deprecated. Use `options.polling.params` instead*. + * Timeout in seconds for long polling. * @param {String|Number} [options.polling.interval=300] Interval between requests in miliseconds * @param {Boolean} [options.polling.autoStart=true] Start polling immediately + * @param {Object} [options.polling.params] Parameters to be used in polling API requests. + * See https://core.telegram.org/bots/api#getupdates for more information. * @param {Boolean|Object} [options.webHook=false] Set true to enable WebHook or set options * @param {String} [options.webHook.host=0.0.0.0] Host to bind to * @param {Number} [options.webHook.port=8443] Port to bind to diff --git a/src/telegramPolling.js b/src/telegramPolling.js index dafd2172..bcc83114 100644 --- a/src/telegramPolling.js +++ b/src/telegramPolling.js @@ -1,4 +1,5 @@ const debug = require('debug')('node-telegram-bot-api'); +const deprecate = require('depd')('node-telegram-bot-api'); const ANOTHER_WEB_HOOK_USED = 409; @@ -11,9 +12,15 @@ class TelegramBotPolling { constructor(bot) { this.bot = bot; this.options = (typeof bot.options.polling === 'boolean') ? {} : bot.options.polling; - this.options.timeout = (typeof this.options.timeout === 'number') ? this.options.timeout : 10; this.options.interval = (typeof this.options.interval === 'number') ? this.options.interval : 300; - this._offset = 0; + this.options.params = (typeof this.options.params === 'object') ? this.options.params : {}; + this.options.params.offset = (typeof this.options.params.offset === 'number') ? this.options.params.offset : 0; + if (typeof this.options.timeout === 'number') { + deprecate('`options.polling.timeout` is deprecated. Use `options.polling.params` instead.'); + this.options.params.timeout = this.options.timeout; + } else { + this.options.params.timeout = 10; + } this._lastUpdate = 0; this._lastRequest = null; this._abort = false; @@ -85,8 +92,8 @@ class TelegramBotPolling { this._lastUpdate = Date.now(); debug('polling data %j', updates); updates.forEach(update => { - this._offset = update.update_id; - debug('updated offset: %s', this._offset); + this.options.params.offset = update.update_id + 1; + debug('updated offset: %s', this.options.params.offset); this.bot.processUpdate(update); }); return null; @@ -125,16 +132,8 @@ class TelegramBotPolling { * Retrieve updates */ _getUpdates() { - const opts = { - qs: { - offset: this._offset + 1, - limit: this.options.limit, - timeout: this.options.timeout - }, - }; - debug('polling with options: %j', opts); - - return this.bot._request('getUpdates', opts) + debug('polling with options: %j', this.options.params); + return this.bot._request('getUpdates', this.options.params) .catch(err => { if (err.response && err.response.statusCode === ANOTHER_WEB_HOOK_USED) { return this._unsetWebHook(); From 9a9dfa9560eaf0bddb732868809be4c0e21585c4 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Thu, 9 Feb 2017 19:06:38 +0300 Subject: [PATCH 25/28] src: Minor fixes --- doc/api.md | 1 + src/telegram.js | 1 + src/telegramPolling.js | 2 +- test/telegram.js | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/api.md b/doc/api.md index dd0d52b6..e29e7445 100644 --- a/doc/api.md +++ b/doc/api.md @@ -75,6 +75,7 @@ Emits `message` when a message arrives. | [options.polling.interval] | String | Number | 300 | Interval between requests in miliseconds | | [options.polling.autoStart] | Boolean | true | Start polling immediately | | [options.polling.params] | Object | | Parameters to be used in polling API requests. See https://core.telegram.org/bots/api#getupdates for more information. | +| [options.polling.params.timeout] | Number | 10 | Timeout in seconds for long polling. | | [options.webHook] | Boolean | Object | false | Set true to enable WebHook or set options | | [options.webHook.host] | String | 0.0.0.0 | Host to bind to | | [options.webHook.port] | Number | 8443 | Port to bind to | diff --git a/src/telegram.js b/src/telegram.js index 51941804..e30d4a0a 100644 --- a/src/telegram.js +++ b/src/telegram.js @@ -57,6 +57,7 @@ class TelegramBot extends EventEmitter { * @param {Boolean} [options.polling.autoStart=true] Start polling immediately * @param {Object} [options.polling.params] Parameters to be used in polling API requests. * See https://core.telegram.org/bots/api#getupdates for more information. + * @param {Number} [options.polling.params.timeout=10] Timeout in seconds for long polling. * @param {Boolean|Object} [options.webHook=false] Set true to enable WebHook or set options * @param {String} [options.webHook.host=0.0.0.0] Host to bind to * @param {Number} [options.webHook.port=8443] Port to bind to diff --git a/src/telegramPolling.js b/src/telegramPolling.js index bcc83114..43ed751a 100644 --- a/src/telegramPolling.js +++ b/src/telegramPolling.js @@ -133,7 +133,7 @@ class TelegramBotPolling { */ _getUpdates() { debug('polling with options: %j', this.options.params); - return this.bot._request('getUpdates', this.options.params) + return this.bot.getUpdates(this.options.params) .catch(err => { if (err.response && err.response.statusCode === ANOTHER_WEB_HOOK_USED) { return this._unsetWebHook(); diff --git a/test/telegram.js b/test/telegram.js index 345b6bf1..a099c978 100644 --- a/test/telegram.js +++ b/test/telegram.js @@ -48,7 +48,7 @@ before(function beforeAll() { describe('module.exports', function moduleExportsSuite() { const nodeVersion = parseInt(process.versions.node.split('.')[0], 10); - it('is loaded from src/ on Node.js v5+', function test() { + it('is loaded from src/ on Node.js v5+ and above', function test() { if (nodeVersion <= 4) this.skip(); // skip on Node.js v4 and below assert.equal(TelegramBot, require('../src/telegram')); }); From 130f6940ceb9972a7b0ac0636ac16e7e53984a46 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Fri, 10 Feb 2017 12:40:47 +0300 Subject: [PATCH 26/28] src/polling: Fix bug #284 Bug: During polling, deleting the already-set webhook, caused the `TelegramBotPolling#_getUpdates()` return an unexpected value. We expect the method to return an array (in the `.then()` clause). However, deleting the webhook returns its value, which is an object, from the method `_getUpdates()`. Fix: Simply retry the polling request and return the promise. Notes: Should we use recursion? I do not think so. Why? The chances of getting the error (having a webhook set) AGAIN is quite rare. And if it happens, there must be some problem with different instances invoking polling and webhook simultaneously. In that case, we wont struggle to recover from such a scenario. User is on their own! Isht! References: * Bug report: https://github.com/yagop/node-telegram-bot-api/issues/284 * Reported by: @dcparga --- src/telegramPolling.js | 5 ++++- test/telegram.js | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/telegramPolling.js b/src/telegramPolling.js index 43ed751a..abd4a7cc 100644 --- a/src/telegramPolling.js +++ b/src/telegramPolling.js @@ -125,6 +125,7 @@ class TelegramBotPolling { * @private */ _unsetWebHook() { + debug('unsetting webhook'); return this.bot._request('setWebHook'); } @@ -136,7 +137,9 @@ class TelegramBotPolling { return this.bot.getUpdates(this.options.params) .catch(err => { if (err.response && err.response.statusCode === ANOTHER_WEB_HOOK_USED) { - return this._unsetWebHook(); + return this._unsetWebHook().then(() => { + return this.bot.getUpdates(this.options.params); + }); } throw err; }); diff --git a/test/telegram.js b/test/telegram.js index a099c978..288fe025 100644 --- a/test/telegram.js +++ b/test/telegram.js @@ -30,6 +30,7 @@ const webHookPort2 = portindex++; const badTgServerPort = portindex++; const staticUrl = `http://127.0.0.1:${staticPort}`; const key = `${__dirname}/../examples/key.pem`; +const ip = '216.58.210.174'; // Google IP ¯\_(ツ)_/¯ const cert = `${__dirname}/../examples/crt.pem`; let FILE_ID; let GAME_CHAT_ID; @@ -122,6 +123,21 @@ describe('TelegramBot', function telegramSuite() { return utils.hasOpenWebHook(webHookPort, true); }); + it('correctly deletes the webhook if polling', function test() { + const myBot = new TelegramBot(TOKEN, { + polling: { autoStart: false, params: { timeout: 0 } }, + }); + utils.handleRatelimit(myBot, 'setWebHook', this); + myBot.on('polling_error', (error) => { + assert.ifError(error); + }); + return myBot.setWebHook(ip).then(() => { + return myBot.startPolling(); + }).then(() => { + return myBot.stopPolling(); + }); + }); + describe('Events', function eventsSuite() { it('(polling) emits "message" on receiving message', function test(done) { botPolling.once('message', () => { @@ -353,12 +369,10 @@ describe('TelegramBot', function telegramSuite() { }); describe('#setWebHook', function setWebHookSuite() { - const ip = '216.58.210.174'; before(function before() { utils.handleRatelimit(bot, 'setWebHook', this); }); it('should set a webHook', function test() { - // Google IP ¯\_(ツ)_/¯ return bot .setWebHook(ip) .then(resp => { From 3edb6d9b6e6e78b3b40dc85361c515c2937b4c96 Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Fri, 10 Feb 2017 17:45:55 +0300 Subject: [PATCH 27/28] doc: Update changelog --- CHANGELOG.md | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b80dca2c..ed9feb86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,15 +8,37 @@ This project adheres to [Semantic Versioning](http://semver.org/). Added: 1. Add constructor options: - * `options.polling.params` (by @GochoMugo) + * (#243) `options.polling.params` (by @GochoMugo, requested-by @sidelux) 1. Add methods: - * *TelegramBot#removeReplyListener()* (by @githugger) -1. Add proper error handling (by @GochoMugo) -1. Add health-check endpoint (by @mironov) + * (#74) *TelegramBot#removeReplyListener()* (by @githugger) +1. (#283) Add proper error handling (by @GochoMugo) +1. (#272) Add health-check endpoint (by @mironov) * `options.webHook.healthEndpoint` -1. Use *String#indexOf()*, instead of *RegExp#test()*, to +1. (#152) Add test for TelegramBot#sendDocument() using 'fileOpts' + param (by @evolun) +1. Document `options.webHook.host` (by @GochoMugo) +1. (#264) Add Bot API version to README (by @kamikazechaser) +1. Add examples: + - (#271) WebHook on Heroku (by @TheBeastOfCaerbannog) + - (#274) WebHook on Zeit Now (by @Ferrari) + +Changed: + +1. (#147) Use *String#indexOf()*, instead of *RegExp#test()*, to find token in webhook request (by @AVVS) +Fixed: + +* Fix bug: + - (#275, #280) fix es6 syntax error on Node.js v4.x (by @crazyabdul) + - (#276) promise.warning from `request-promise` (by @GochoMugo, + reported-by @preco21) + - (#281) fix handling error during polling (by @GochoMugo, + reported-by @dimawebmaker) + - (#284) fix error during deletion of already-set webhook, during + polling (by @GochoMugo, reported-by @dcparga) +1. Fix links in documentation (by @Ni2c2k) + * * * From b2afdeb6a88b35aa0e0c15e214d45cbff052596d Mon Sep 17 00:00:00 2001 From: GochoMugo Date: Fri, 10 Feb 2017 17:50:50 +0300 Subject: [PATCH 28/28] doc: Prepare for release v0.27.0 --- CHANGELOG.md | 10 ++- package.json | 201 +++++++++++++++++++++++++++++---------------------- 2 files changed, 123 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed9feb86..bf2a7c56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased][Unreleased] + + + +* * * + +## [0.27.0][0.27.0] - 2016-02-10 + Added: 1. Add constructor options: @@ -100,4 +107,5 @@ Fixed: [0.25.0]:https://github.com/yagop/node-telegram-bot-api/releases/tag/v0.25.0 [0.26.0]:https://github.com/yagop/node-telegram-bot-api/releases/tag/v0.26.0 -[Unreleased]:https://github.com/yagop/node-telegram-bot-api/compare/v0.26.0...master +[0.27.0]:https://github.com/yagop/node-telegram-bot-api/releases/tag/v0.27.0 +[Unreleased]:https://github.com/yagop/node-telegram-bot-api/compare/v0.27.0...master diff --git a/package.json b/package.json index 0fc46da4..821f3a9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-telegram-bot-api", - "version": "0.26.0", + "version": "0.27.0", "description": "Telegram Bot API", "main": "./index.js", "directories": { @@ -73,14 +73,32 @@ "homepage": "https://github.com/yagop/node-telegram-bot-api", "contributors": [ { - "name": "Mohammed Sohail", - "email": "sohailsameja@gmail.com", - "url": "https://github.com/kamikazechaser", + "name": "Anton Mironov", + "email": "ant.mironov@gmail.com", + "url": "https://github.com/mironov", "contributions": 1, - "additions": 18, - "deletions": 4, + "additions": 51, + "deletions": 15, + "hireable": true + }, + { + "name": "Daniil Yastremskiy", + "email": "Catharsis@post.cz", + "url": "https://github.com/TheBeastOfCaerbannog", + "contributions": 1, + "additions": 36, + "deletions": 0, "hireable": true }, + { + "name": null, + "email": null, + "url": "https://github.com/Ni2c2k", + "contributions": 1, + "additions": 4, + "deletions": 4, + "hireable": null + }, { "name": "Alexander Tarmolov", "email": "tarmolov@gmail.com", @@ -99,6 +117,15 @@ "deletions": 0, "hireable": null }, + { + "name": "Ola Flisbäck", + "email": null, + "url": "https://github.com/oflisback", + "contributions": 1, + "additions": 3, + "deletions": 3, + "hireable": true + }, { "name": null, "email": null, @@ -126,15 +153,6 @@ "deletions": 5, "hireable": null }, - { - "name": "Ola Flisbäck", - "email": null, - "url": "https://github.com/oflisback", - "contributions": 1, - "additions": 3, - "deletions": 3, - "hireable": true - }, { "name": "Horus Lugo", "email": "horusgoul@gmail.com", @@ -162,6 +180,15 @@ "deletions": 1, "hireable": null }, + { + "name": "Aleksandr L.", + "email": "w.siteee@gmail.com", + "url": "https://github.com/w-site", + "contributions": 1, + "additions": 24, + "deletions": 0, + "hireable": null + }, { "name": "Matthew Brandly", "email": "matt@brandly.me", @@ -189,15 +216,6 @@ "deletions": 2, "hireable": true }, - { - "name": "Aleksandr L.", - "email": "w.siteee@gmail.com", - "url": "https://github.com/w-site", - "contributions": 1, - "additions": 24, - "deletions": 0, - "hireable": null - }, { "name": "Guido García", "email": "palmerabollo@gmail.com", @@ -216,6 +234,24 @@ "deletions": 1, "hireable": null }, + { + "name": "Mohammed Sohail", + "email": "sohail@forfuture.tech", + "url": "https://github.com/kamikazechaser", + "contributions": 2, + "additions": 20, + "deletions": 5, + "hireable": true + }, + { + "name": "Jishnu Mohan", + "email": "jishnu7@gmail.com", + "url": "https://github.com/jishnu7", + "contributions": 2, + "additions": 84, + "deletions": 0, + "hireable": true + }, { "name": "Jérémy Gotteland", "email": null, @@ -234,15 +270,6 @@ "deletions": 2, "hireable": true }, - { - "name": "Iiro Jäppinen", - "email": null, - "url": "https://github.com/iiroj", - "contributions": 2, - "additions": 40, - "deletions": 0, - "hireable": null - }, { "name": "Dardan Neziri", "email": "dard.ne@gmail.com", @@ -253,22 +280,22 @@ "hireable": true }, { - "name": "Jishnu Mohan", - "email": "jishnu7@gmail.com", - "url": "https://github.com/jishnu7", + "name": "Cristian Baldi", + "email": "bld.cris.96@gmail.com", + "url": "https://github.com/crisbal", "contributions": 2, - "additions": 84, - "deletions": 0, + "additions": 26, + "deletions": 1, "hireable": true }, { - "name": "TJ Horner", - "email": "me@tjhorner.com", - "url": "https://github.com/tjhorner", + "name": "Vitaly Aminev", + "email": null, + "url": "https://github.com/AVVS", "contributions": 2, - "additions": 223, - "deletions": 1, - "hireable": null + "additions": 1065, + "deletions": 1001, + "hireable": true }, { "name": null, @@ -280,13 +307,31 @@ "hireable": null }, { - "name": "Vitaly Aminev", + "name": "Iiro Jäppinen", "email": null, - "url": "https://github.com/AVVS", + "url": "https://github.com/iiroj", "contributions": 2, - "additions": 1065, - "deletions": 1001, - "hireable": true + "additions": 40, + "deletions": 0, + "hireable": null + }, + { + "name": "TJ Horner", + "email": "me@tjhorner.com", + "url": "https://github.com/tjhorner", + "contributions": 2, + "additions": 223, + "deletions": 1, + "hireable": null + }, + { + "name": "Rafael Kr", + "email": null, + "url": "https://github.com/RafaelKr", + "contributions": 3, + "additions": 3, + "deletions": 2, + "hireable": null }, { "name": "Vítor Augusto da Silva Vasconcellos", @@ -306,15 +351,6 @@ "deletions": 25, "hireable": null }, - { - "name": "Rafael Kr", - "email": null, - "url": "https://github.com/RafaelKr", - "contributions": 3, - "additions": 3, - "deletions": 2, - "hireable": null - }, { "name": "Ivan Skorokhodov", "email": "iskorokhodov@gmail.com", @@ -343,13 +379,13 @@ "hireable": true }, { - "name": "Chris54721", - "email": null, - "url": "https://github.com/chris54721", - "contributions": 5, - "additions": 22, - "deletions": 6, - "hireable": null + "name": "Yago", + "email": "yago@yago.me", + "url": "https://github.com/yagop", + "contributions": 194, + "additions": 3014, + "deletions": 1173, + "hireable": true }, { "name": "Ilias Ismanalijev", @@ -360,32 +396,23 @@ "deletions": 10, "hireable": true }, + { + "name": "Chris54721", + "email": null, + "url": "https://github.com/chris54721", + "contributions": 5, + "additions": 22, + "deletions": 6, + "hireable": null + }, { "name": "Gocho Mugo", "email": "mugo@forfuture.co.ke", "url": "https://github.com/GochoMugo", - "contributions": 56, - "additions": 3779, - "deletions": 2167, - "hireable": true - }, - { - "name": "Cristian Baldi", - "email": "bld.cris.96@gmail.com", - "url": "https://github.com/crisbal", - "contributions": 2, - "additions": 26, - "deletions": 1, - "hireable": true - }, - { - "name": "Yago", - "email": "yago@yago.me", - "url": "https://github.com/yagop", - "contributions": 194, - "additions": 3014, - "deletions": 1173, + "contributions": 80, + "additions": 4590, + "deletions": 2377, "hireable": true } ] -} +} \ No newline at end of file