diff --git a/build/api.js b/build/api.js index f232f2e..0852b14 100644 --- a/build/api.js +++ b/build/api.js @@ -39,7 +39,7 @@ API.prototype.request = function request(method, data) { return (0, _fetch2['default'])(this.token + '/' + method, data); }; -var methods = ['getMe', 'sendMessage', 'forwardMessage', 'sendPhoto', 'sendAudio', 'sendDocument', 'sendSticker', 'sendVideo', 'sendLocation', 'sendChatAction', 'getUserProfilePhotos', 'getUpdates']; +var methods = ['getMe', 'sendMessage', 'forwardMessage', 'sendPhoto', 'sendAudio', 'sendDocument', 'sendSticker', 'sendVideo', 'sendLocation', 'sendChatAction', 'getUserProfilePhotos', 'getUpdates', 'setWebhook']; methods.forEach(function (method) { API.prototype[method] = function (data) { diff --git a/build/fetch.js b/build/fetch.js index b0f60b1..e73f1ca 100644 --- a/build/fetch.js +++ b/build/fetch.js @@ -4,6 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true }); exports['default'] = fetch; +exports.getBody = getBody; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } @@ -19,8 +20,6 @@ function fetch(path, data) { var post = _qs2['default'].stringify(data); return new Promise(function (resolve, reject) { - var res = ''; - var req = _https2['default'].request({ hostname: 'api.telegram.org', method: data ? 'POST' : 'GET', @@ -29,19 +28,15 @@ function fetch(path, data) { 'Content-Type': 'application/x-www-form-urlencoded' } }, function (response) { - response.on('data', function (chunk) { - res += chunk; - }); - - response.on('end', function () { + return getBody(response).then(function (res) { try { var json = JSON.parse(res); resolve(json); } catch (e) { reject(e); } - }); - }).on('error', reject); + })['catch'](reject); + }); if (post) { req.write(post); @@ -52,4 +47,18 @@ function fetch(path, data) { }); } -module.exports = exports['default']; +function getBody(stream) { + var data = ''; + + return new Promise(function (resolve, reject) { + stream.on('data', function (chunk) { + data += chunk; + }); + + stream.on('end', function () { + resolve(data); + }); + + stream.on('error', reject); + }); +} diff --git a/build/index.js b/build/index.js index 5021ed8..5e6ddbc 100644 --- a/build/index.js +++ b/build/index.js @@ -20,6 +20,14 @@ var _api = require('./api'); var _api2 = _interopRequireDefault(_api); +var _webhook = require('./webhook'); + +var _webhook2 = _interopRequireDefault(_webhook); + +var _poll = require('./poll'); + +var _poll2 = _interopRequireDefault(_poll); + var _events = require('events'); var DEFAULTS = { @@ -75,62 +83,38 @@ var Bot = (function (_EventEmitter) { key: 'start', /** - * Gets information about the bot and then starts polling updates from API + * Gets information about the bot and then + * 1) starts polling updates from API + * 2) sets a webhook as defined by the first parameter and listens for updates * Emits an `update` event after polling with the response from server * Returns a promise which is resolved after the bot information is received * and set to it's `info` property i.e. bot.info + * + * @param {object} hook An object containg options passed to webhook + * properties: + * - url: HTTPS url to listen on POST requests coming + * from the Telegram API + * - port: the port to listen to, defaults to 443 + * - server: An object passed to https.createServer + * * @return {promise} A promise which is resolved with the response of getMe */ - value: function start() { - var _this2 = this; - - var poll = (function () { - var _this = this; - - return this.api.getUpdates(this.update).then(function (response) { - var again = wait(_this.update.timeout * 1000).then(poll); - - var result = response.result; - if (!result.length) { - return again; - } - - if (!_this.update.offset) { - var updateId = result[result.length - 1].update_id; - _this.update.offset = updateId; - } - if (_this.update) { - _this.update.offset += 1; - } - - _this.emit('update', result); - - result.forEach(function (res) { - var text = res.message.text; - if (text.startsWith('/')) { - // Commands are sent in /command@botusername format in groups - var regex = new RegExp('@' + _this.info.username + '$'); - text = text.replace(regex, ''); - } - - var ev = _this._userEvents.find(function (_ref) { - var pattern = _ref.pattern; - return pattern.test(text); - }); - - if (!ev) { - return; - } - ev.listener(res.message); - }); - - return again; - }); - }).bind(this); + value: function start(hook) { + var _this = this; + if (hook) { + return (0, _webhook2['default'])(hook, this); + } return this.api.getMe().then(function (response) { - _this2.info = response.result; - return poll(); + _this.info = response.result; + + _this.on('update', _this._update); + + if (hook) { + return (0, _webhook2['default'])(hook, _this); + } else { + return (0, _poll2['default'])(_this); + } }); } }, { @@ -186,16 +170,42 @@ var Bot = (function (_EventEmitter) { value: function send(message) { return message.send(this)['catch'](console.error); } + }, { + key: '_update', + value: function _update(update) { + var _this2 = this; + + if (!this.update.offset) { + var updateId = update[update.length - 1].update_id; + this.update.offset = updateId; + } + if (this.update) { + this.update.offset += 1; + } + + update.forEach(function (res) { + var text = res.message.text; + if (text.startsWith('/')) { + // Commands are sent in /command@thisusername format in groups + var regex = new RegExp('@' + _this2.info.username + '$'); + text = text.replace(regex, ''); + } + + var ev = _this2._userEvents.find(function (_ref) { + var pattern = _ref.pattern; + return pattern.test(text); + }); + + if (!ev) { + return; + } + ev.listener(res.message); + }); + } }]); return Bot; })(_events.EventEmitter); exports['default'] = Bot; - -var wait = function wait(miliseconds) { - return new Promise(function (resolve) { - setTimeout(resolve, miliseconds); - }); -}; module.exports = exports['default']; diff --git a/build/poll.js b/build/poll.js new file mode 100644 index 0000000..c080f47 --- /dev/null +++ b/build/poll.js @@ -0,0 +1,27 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); +exports['default'] = poll; + +function poll(bot) { + return bot.api.getUpdates(bot.update).then(function (response) { + var again = wait(bot.update.timeout * 1000, bot).then(poll); + if (!response.result.length) { + return again; + } + bot.emit('update', response.result); + + return again; + }); +} + +var wait = function wait(miliseconds, value) { + return new Promise(function (resolve) { + setTimeout(function () { + resolve(value); + }, miliseconds); + }); +}; +module.exports = exports['default']; diff --git a/build/webhook.js b/build/webhook.js new file mode 100644 index 0000000..c00c59f --- /dev/null +++ b/build/webhook.js @@ -0,0 +1,42 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); +exports['default'] = webhook; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +var _http = require('http'); + +var _http2 = _interopRequireDefault(_http); + +var _qs = require('qs'); + +var _qs2 = _interopRequireDefault(_qs); + +var _fetch = require('./fetch'); + +var DEFAULTS = { + server: {}, + port: 443 +}; + +function webhook(options, bot) { + if (options === undefined) options = {}; + + options = Object.assign(DEFAULTS, options); + + return bot.api.setWebhook(options.url).then(function () { + + _http2['default'].createServer(options.server, function (req, res) { + return (0, _fetch.getBody)(req).then(function (data) { + bot.emit('update', _qs2['default'].parse(data).result); + + res.end('OK'); + }); + }).listen(options.port); + }); +} + +module.exports = exports['default']; diff --git a/demo.js b/demo.js index e82caad..a4527dc 100644 --- a/demo.js +++ b/demo.js @@ -5,7 +5,7 @@ var Message = require('telegram-api/types/Message'); var Question = require('telegram-api/types/Question'); var bot = new Bot({ - token: 'YOUR_KEY' + token: '121143906:AAE6pcpBoARNZZjr3fUpvKuLInJ5Eee5Ajk' }); bot.start().catch(err => { diff --git a/lib/api.js b/lib/api.js index 72c4222..8492f08 100644 --- a/lib/api.js +++ b/lib/api.js @@ -26,7 +26,7 @@ API.prototype.request = function request(method, data) { const methods = ['getMe', 'sendMessage', 'forwardMessage', 'sendPhoto', 'sendAudio', 'sendDocument', 'sendSticker', 'sendVideo', 'sendLocation', 'sendChatAction', 'getUserProfilePhotos', -'getUpdates']; +'getUpdates', 'setWebhook']; methods.forEach(method => { API.prototype[method] = function(data) { diff --git a/lib/fetch.js b/lib/fetch.js index 3f4eafc..ab4d972 100644 --- a/lib/fetch.js +++ b/lib/fetch.js @@ -5,8 +5,6 @@ export default function fetch(path, data) { const post = qs.stringify(data); return new Promise((resolve, reject) => { - let res = ''; - const req = https.request({ hostname: 'api.telegram.org', method: data ? 'POST' : 'GET', @@ -15,19 +13,15 @@ export default function fetch(path, data) { 'Content-Type': 'application/x-www-form-urlencoded' } }, response => { - response.on('data', chunk => { - res += chunk; - }); - - response.on('end', () => { + return getBody(response).then(res => { try { let json = JSON.parse(res); resolve(json); } catch(e) { reject(e); } - }); - }).on('error', reject); + }).catch(reject); + }); if (post) { req.write(post); @@ -37,3 +31,19 @@ export default function fetch(path, data) { console.error('Error sending request', err); }); } + +export function getBody(stream) { + let data = ''; + + return new Promise((resolve, reject) => { + stream.on('data', chunk => { + data += chunk; + }); + + stream.on('end', () => { + resolve(data); + }); + + stream.on('error', reject); + }); +} diff --git a/lib/index.js b/lib/index.js index ceeef42..5f3fdc0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,5 +1,7 @@ import 'babel/polyfill'; import API from './api'; +import webhook from './webhook'; +import poll from './poll'; import {EventEmitter} from 'events'; const DEFAULTS = { @@ -44,55 +46,38 @@ export default class Bot extends EventEmitter { } /** - * Gets information about the bot and then starts polling updates from API + * Gets information about the bot and then + * 1) starts polling updates from API + * 2) sets a webhook as defined by the first parameter and listens for updates * Emits an `update` event after polling with the response from server * Returns a promise which is resolved after the bot information is received * and set to it's `info` property i.e. bot.info + * + * @param {object} hook An object containg options passed to webhook + * properties: + * - url: HTTPS url to listen on POST requests coming + * from the Telegram API + * - port: the port to listen to, defaults to 443 + * - server: An object passed to https.createServer + * * @return {promise} A promise which is resolved with the response of getMe */ - start() { - let poll = function() { - return this.api.getUpdates(this.update).then(response => { - const again = wait(this.update.timeout * 1000).then(poll); - - const result = response.result; - if (!result.length) { - return again; - } - - if (!this.update.offset) { - const updateId = result[result.length - 1].update_id; - this.update.offset = updateId; - } - if (this.update) { - this.update.offset += 1; - } - - this.emit('update', result); - - result.forEach(res => { - let text = res.message.text; - if (text.startsWith('/')) { - // Commands are sent in /command@botusername format in groups - const regex = new RegExp(`@${this.info.username}$`); - text = text.replace(regex, ''); - } - - let ev = this._userEvents.find(({pattern}) => pattern.test(text)); - - if (!ev) { - return; - } - ev.listener(res.message); - }); - - return again; - }); - }.bind(this); - + start(hook) { + if (hook) { + return webhook(hook, this); + } return this.api.getMe().then(response => { this.info = response.result; - return poll(); + + this.on('update', this._update); + + if (hook) { + return webhook(hook, this); + } else { + return poll(this); + } + + }); } @@ -142,10 +127,36 @@ export default class Bot extends EventEmitter { send(message) { return message.send(this).catch(console.error); } -} -const wait = (miliseconds) => { - return new Promise(resolve => { - setTimeout(resolve, miliseconds); - }); -}; + /** + * The internal update event listener, used to parse messages and fire + * command/get events - YOU SHOULD NOT USE THIS + * + * @param {object} update + */ + _update(update) { + if (!this.update.offset) { + const updateId = update[update.length - 1].update_id; + this.update.offset = updateId; + } + if (this.update) { + this.update.offset += 1; + } + + update.forEach(res => { + let text = res.message.text; + if (text.startsWith('/')) { + // Commands are sent in /command@thisusername format in groups + const regex = new RegExp(`@${this.info.username}$`); + text = text.replace(regex, ''); + } + + let ev = this._userEvents.find(({pattern}) => pattern.test(text)); + + if (!ev) { + return; + } + ev.listener(res.message); + }); + } +} diff --git a/lib/poll.js b/lib/poll.js new file mode 100644 index 0000000..a530e7b --- /dev/null +++ b/lib/poll.js @@ -0,0 +1,19 @@ +export default function poll(bot) { + return bot.api.getUpdates(bot.update).then(response => { + const again = wait(bot.update.timeout * 1000, bot).then(poll); + if (!response.result.length) { + return again; + } + bot.emit('update', response.result); + + return again; + }); +} + +const wait = (miliseconds, value) => { + return new Promise(resolve => { + setTimeout(() => { + resolve(value); + }, miliseconds); + }); +}; diff --git a/lib/webhook.js b/lib/webhook.js new file mode 100644 index 0000000..795978a --- /dev/null +++ b/lib/webhook.js @@ -0,0 +1,24 @@ +import https from 'http'; +import qs from 'qs'; +import {getBody} from './fetch'; + +const DEFAULTS = { + server: {}, + port: 443 +}; + +export default function webhook(options = {}, bot) { + options = Object.assign(DEFAULTS, options); + + return bot.api.setWebhook(options.url).then(() => { + + https.createServer(options.server, (req, res) => { + return getBody(req).then(data => { + bot.emit('update', qs.parse(data).result); + + res.end('OK'); + }); + }).listen(options.port); + + }); +} diff --git a/package.json b/package.json index 2f6672c..18e374c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "telegram-api", - "version": "0.2.2", + "version": "0.3.2", "description": "Control Telegram bots easily using the new Telegram API", "main": "index.js", "scripts": {