Add support for WebHooks

This commit is contained in:
Mahdi Dibaiee
2015-06-30 02:50:34 +04:30
parent 4ac725ea58
commit b55fb3c7d6
12 changed files with 278 additions and 126 deletions

View File

@ -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) {

View File

@ -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);
});
}

View File

@ -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);
});
}
}

19
lib/poll.js Normal file
View File

@ -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);
});
};

24
lib/webhook.js Normal file
View File

@ -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);
});
}