mirror of
https://github.com/yagop/node-telegram-bot-api
synced 2025-08-29 13:27:44 +00:00
Update codebase
Details: * Update documentation on constructors and methods * Make code more modular * Improve code (generally)
This commit is contained in:
parent
4e5a493cad
commit
daab34d98d
18
README.md
18
README.md
@ -71,6 +71,7 @@ TelegramBot
|
|||||||
|
|
||||||
* [TelegramBot](#TelegramBot)
|
* [TelegramBot](#TelegramBot)
|
||||||
* [new TelegramBot(token, [options])](#new_TelegramBot_new)
|
* [new TelegramBot(token, [options])](#new_TelegramBot_new)
|
||||||
|
* [.initPolling()](#TelegramBot+initPolling)
|
||||||
* [.stopPolling()](#TelegramBot+stopPolling) ⇒ <code>Promise</code>
|
* [.stopPolling()](#TelegramBot+stopPolling) ⇒ <code>Promise</code>
|
||||||
* [.getMe()](#TelegramBot+getMe) ⇒ <code>Promise</code>
|
* [.getMe()](#TelegramBot+getMe) ⇒ <code>Promise</code>
|
||||||
* [.setWebHook(url, [cert])](#TelegramBot+setWebHook)
|
* [.setWebHook(url, [cert])](#TelegramBot+setWebHook)
|
||||||
@ -122,14 +123,21 @@ Emits `message` when a message arrives.
|
|||||||
| token | <code>String</code> | | Bot Token |
|
| token | <code>String</code> | | Bot Token |
|
||||||
| [options] | <code>Object</code> | | |
|
| [options] | <code>Object</code> | | |
|
||||||
| [options.polling] | <code>Boolean</code> | <code>Object</code> | <code>false</code> | Set true to enable polling or set options |
|
| [options.polling] | <code>Boolean</code> | <code>Object</code> | <code>false</code> | Set true to enable polling or set options |
|
||||||
| [options.polling.timeout] | <code>String</code> | <code>Number</code> | <code>10</code> | Polling time in seconds |
|
| [options.polling.timeout] | <code>String</code> | <code>Number</code> | <code>10</code> | Timeout in seconds for long polling |
|
||||||
| [options.polling.interval] | <code>String</code> | <code>Number</code> | <code>2000</code> | Interval between requests in miliseconds |
|
| [options.polling.interval] | <code>String</code> | <code>Number</code> | <code>300</code> | Interval between requests in miliseconds |
|
||||||
| [options.webHook] | <code>Boolean</code> | <code>Object</code> | <code>false</code> | Set true to enable WebHook or set options |
|
| [options.webHook] | <code>Boolean</code> | <code>Object</code> | <code>false</code> | Set true to enable WebHook or set options |
|
||||||
| [options.webHook.key] | <code>String</code> | | PEM private key to webHook server. |
|
| [options.webHook.port] | <code>Number</code> | <code>8443</code> | Port to bind to |
|
||||||
| [options.webHook.cert] | <code>String</code> | | PEM certificate (public) to webHook server. |
|
| [options.webHook.key] | <code>String</code> | | Path to file with PEM private key for webHook server. (Read synchronously!) |
|
||||||
|
| [options.webHook.cert] | <code>String</code> | | Path to file with PEM certificate (public) for webHook server. (Read synchronously!) |
|
||||||
| [options.onlyFirstMatch] | <code>Boolean</code> | <code>false</code> | Set to true to stop after first match. Otherwise, all regexps are executed |
|
| [options.onlyFirstMatch] | <code>Boolean</code> | <code>false</code> | Set to true to stop after first match. Otherwise, all regexps are executed |
|
||||||
| [options.request] | <code>Object</code> | | Options which will be added for all requests to telegram api. See https://github.com/request/request#requestoptions-callback for more information. |
|
| [options.request] | <code>Object</code> | | Options which will be added for all requests to telegram api. See https://github.com/request/request#requestoptions-callback for more information. |
|
||||||
|
|
||||||
|
<a name="TelegramBot+initPolling"></a>
|
||||||
|
|
||||||
|
### telegramBot.initPolling()
|
||||||
|
Start polling
|
||||||
|
|
||||||
|
**Kind**: instance method of <code>[TelegramBot](#TelegramBot)</code>
|
||||||
<a name="TelegramBot+stopPolling"></a>
|
<a name="TelegramBot+stopPolling"></a>
|
||||||
|
|
||||||
### telegramBot.stopPolling() ⇒ <code>Promise</code>
|
### telegramBot.stopPolling() ⇒ <code>Promise</code>
|
||||||
|
@ -14,7 +14,8 @@
|
|||||||
"bot"
|
"bot"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepublish": "babel -d ./lib src",
|
"build": "babel -d ./lib src",
|
||||||
|
"prepublish": "npm run build",
|
||||||
"test": "istanbul cover ./node_modules/mocha/bin/_mocha -- -R spec --timeout 10000",
|
"test": "istanbul cover ./node_modules/mocha/bin/_mocha -- -R spec --timeout 10000",
|
||||||
"prepublish:test": "npm run prepublish && npm run test",
|
"prepublish:test": "npm run prepublish && npm run test",
|
||||||
"gen-doc": "jsdoc2md --src src/telegram.js -t README.hbs > README.md",
|
"gen-doc": "jsdoc2md --src src/telegram.js -t README.hbs > README.md",
|
||||||
|
255
src/telegram.js
255
src/telegram.js
@ -20,6 +20,11 @@ const _messageTypes = [
|
|||||||
'new_chat_photo', 'delete_chat_photo', 'group_chat_created'
|
'new_chat_photo', 'delete_chat_photo', 'group_chat_created'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// enable cancellation
|
||||||
|
Promise.config({
|
||||||
|
cancellation: true,
|
||||||
|
});
|
||||||
|
|
||||||
class TelegramBot extends EventEmitter {
|
class TelegramBot extends EventEmitter {
|
||||||
|
|
||||||
static get messageTypes() {
|
static get messageTypes() {
|
||||||
@ -36,22 +41,22 @@ class TelegramBot extends EventEmitter {
|
|||||||
* @param {String} token Bot Token
|
* @param {String} token Bot Token
|
||||||
* @param {Object} [options]
|
* @param {Object} [options]
|
||||||
* @param {Boolean|Object} [options.polling=false] Set true to enable polling or set options
|
* @param {Boolean|Object} [options.polling=false] Set true to enable polling or set options
|
||||||
* @param {String|Number} [options.polling.timeout=10] Polling time in seconds
|
* @param {String|Number} [options.polling.timeout=10] Timeout in seconds for long polling
|
||||||
* @param {String|Number} [options.polling.interval=2000] Interval between requests in miliseconds
|
* @param {String|Number} [options.polling.interval=300] Interval between requests in miliseconds
|
||||||
* @param {Boolean|Object} [options.webHook=false] Set true to enable WebHook or set options
|
* @param {Boolean|Object} [options.webHook=false] Set true to enable WebHook or set options
|
||||||
* @param {String} [options.webHook.key] PEM private key to webHook server.
|
* @param {Number} [options.webHook.port=8443] Port to bind to
|
||||||
* @param {String} [options.webHook.cert] PEM certificate (public) to webHook server.
|
* @param {String} [options.webHook.key] Path to file with PEM private key for webHook server. (Read synchronously!)
|
||||||
|
* @param {String} [options.webHook.cert] Path to file with PEM certificate (public) for webHook server. (Read synchronously!)
|
||||||
* @param {Boolean} [options.onlyFirstMatch=false] Set to true to stop after first match. Otherwise, all regexps are executed
|
* @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.
|
* @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.
|
||||||
* See https://github.com/request/request#requestoptions-callback for more information.
|
|
||||||
* @see https://core.telegram.org/bots/api
|
* @see https://core.telegram.org/bots/api
|
||||||
*/
|
*/
|
||||||
constructor(token, options = {}) {
|
constructor(token, options = {}) {
|
||||||
super();
|
super();
|
||||||
this.options = options;
|
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.textRegexpCallbacks = [];
|
this.options = options;
|
||||||
this.onReplyToMessages = [];
|
this._textRegexpCallbacks = [];
|
||||||
|
this._onReplyToMessages = [];
|
||||||
|
|
||||||
if (options.polling) {
|
if (options.polling) {
|
||||||
this.initPolling();
|
this.initPolling();
|
||||||
@ -62,27 +67,13 @@ class TelegramBot extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initPolling() {
|
|
||||||
if (this._polling) {
|
|
||||||
this._polling.abort = true;
|
|
||||||
this._polling.lastRequest.cancel('Polling restart');
|
|
||||||
}
|
|
||||||
this._polling = new TelegramBotPolling(this.token, this.options.polling, this.processUpdate.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops polling after the last polling request resolves
|
* Process an update; emitting the proper events and executing regexp
|
||||||
*
|
* callbacks
|
||||||
* @return {Promise} promise Promise, of last polling request
|
* @param {Object} update
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
stopPolling() {
|
_processUpdate(update) {
|
||||||
if (this._polling) {
|
|
||||||
return this._polling.stopPolling();
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
processUpdate(update) {
|
|
||||||
debug('Process Update %j', update);
|
debug('Process Update %j', update);
|
||||||
const message = update.message;
|
const message = update.message;
|
||||||
const editedMessage = update.edited_message;
|
const editedMessage = update.edited_message;
|
||||||
@ -97,27 +88,28 @@ class TelegramBot extends EventEmitter {
|
|||||||
this.emit('message', message);
|
this.emit('message', message);
|
||||||
const processMessageType = messageType => {
|
const processMessageType = messageType => {
|
||||||
if (message[messageType]) {
|
if (message[messageType]) {
|
||||||
debug('Emtting %s: %j', messageType, message);
|
debug('Emitting %s: %j', messageType, message);
|
||||||
this.emit(messageType, message);
|
this.emit(messageType, message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
TelegramBot.messageTypes.forEach(processMessageType);
|
TelegramBot.messageTypes.forEach(processMessageType);
|
||||||
if (message.text) {
|
if (message.text) {
|
||||||
debug('Text message');
|
debug('Text message');
|
||||||
this.textRegexpCallbacks.some(reg => {
|
this._textRegexpCallbacks.some(reg => {
|
||||||
debug('Matching %s with %s', message.text, reg.regexp);
|
debug('Matching %s with %s', message.text, reg.regexp);
|
||||||
const result = reg.regexp.exec(message.text);
|
const result = reg.regexp.exec(message.text);
|
||||||
if (result) {
|
if (!result) {
|
||||||
debug('Matches %s', reg.regexp);
|
return false;
|
||||||
reg.callback(message, result);
|
|
||||||
// returning truthy value exits .some
|
|
||||||
return this.options.onlyFirstMatch;
|
|
||||||
}
|
}
|
||||||
|
debug('Matches %s', reg.regexp);
|
||||||
|
reg.callback(message, result);
|
||||||
|
// returning truthy value exits .some
|
||||||
|
return this.options.onlyFirstMatch;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (message.reply_to_message) {
|
if (message.reply_to_message) {
|
||||||
// Only callbacks waiting for this message
|
// Only callbacks waiting for this message
|
||||||
this.onReplyToMessages.forEach(reply => {
|
this._onReplyToMessages.forEach(reply => {
|
||||||
// Message from the same chat
|
// Message from the same chat
|
||||||
if (reply.chatId === message.chat.id) {
|
if (reply.chatId === message.chat.id) {
|
||||||
// Responding to that message
|
// Responding to that message
|
||||||
@ -139,7 +131,7 @@ class TelegramBot extends EventEmitter {
|
|||||||
}
|
}
|
||||||
} else if (channelPost) {
|
} else if (channelPost) {
|
||||||
debug('Process Update channel_post %j', channelPost);
|
debug('Process Update channel_post %j', channelPost);
|
||||||
this.emit('channel_post', channelPost);
|
this.emit('channel_post', channelPost);
|
||||||
} else if (editedChannelPost) {
|
} else if (editedChannelPost) {
|
||||||
debug('Process Update edited_channel_post %j', editedChannelPost);
|
debug('Process Update edited_channel_post %j', editedChannelPost);
|
||||||
this.emit('edited_channel_post', editedChannelPost);
|
this.emit('edited_channel_post', editedChannelPost);
|
||||||
@ -148,7 +140,7 @@ class TelegramBot extends EventEmitter {
|
|||||||
}
|
}
|
||||||
if (editedChannelPost.caption) {
|
if (editedChannelPost.caption) {
|
||||||
this.emit('edited_channel_post_caption', editedChannelPost);
|
this.emit('edited_channel_post_caption', editedChannelPost);
|
||||||
}
|
}
|
||||||
} else if (inlineQuery) {
|
} else if (inlineQuery) {
|
||||||
debug('Process Update inline_query %j', inlineQuery);
|
debug('Process Update inline_query %j', inlineQuery);
|
||||||
this.emit('inline_query', inlineQuery);
|
this.emit('inline_query', inlineQuery);
|
||||||
@ -161,24 +153,42 @@ class TelegramBot extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// used so that other funcs are not non-optimizable
|
/**
|
||||||
_safeParse(json) {
|
* Generates url with bot token and provided path/method you want to be got/executed by bot
|
||||||
try {
|
* @param {String} path
|
||||||
return JSON.parse(json);
|
* @return {String} url
|
||||||
} catch (err) {
|
* @private
|
||||||
throw new Error(`Error parsing Telegram response: ${String(json)}`);
|
* @see https://core.telegram.org/bots/api#making-requests
|
||||||
}
|
*/
|
||||||
|
_buildURL(_path) {
|
||||||
|
return URL.format({
|
||||||
|
protocol: 'https',
|
||||||
|
host: 'api.telegram.org',
|
||||||
|
pathname: `/bot${this.token}/${_path}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix 'reply_markup' parameter by making it JSON-serialized, as
|
||||||
|
* required by the Telegram Bot API
|
||||||
|
* @param {Object} obj Object; either 'form' or 'qs'
|
||||||
|
* @private
|
||||||
|
* @see https://core.telegram.org/bots/api#sendmessage
|
||||||
|
*/
|
||||||
_fixReplyMarkup(obj) {
|
_fixReplyMarkup(obj) {
|
||||||
const replyMarkup = obj.reply_markup;
|
const replyMarkup = obj.reply_markup;
|
||||||
if (replyMarkup && typeof replyMarkup !== 'string') {
|
if (replyMarkup && typeof replyMarkup !== 'string') {
|
||||||
// reply_markup must be passed as JSON stringified to Telegram
|
|
||||||
obj.reply_markup = JSON.stringify(replyMarkup);
|
obj.reply_markup = JSON.stringify(replyMarkup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// request-promise
|
/**
|
||||||
|
* Make request against the API
|
||||||
|
* @param {String} _path API endpoint
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @private
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
_request(_path, options = {}) {
|
_request(_path, options = {}) {
|
||||||
if (!this.token) {
|
if (!this.token) {
|
||||||
throw new Error('Telegram Bot Token not provided!');
|
throw new Error('Telegram Bot Token not provided!');
|
||||||
@ -194,6 +204,7 @@ class TelegramBot extends EventEmitter {
|
|||||||
if (options.qs) {
|
if (options.qs) {
|
||||||
this._fixReplyMarkup(options.qs);
|
this._fixReplyMarkup(options.qs);
|
||||||
}
|
}
|
||||||
|
|
||||||
options.url = this._buildURL(_path);
|
options.url = this._buildURL(_path);
|
||||||
options.simple = false;
|
options.simple = false;
|
||||||
options.resolveWithFullResponse = true;
|
options.resolveWithFullResponse = true;
|
||||||
@ -202,31 +213,108 @@ class TelegramBot extends EventEmitter {
|
|||||||
return request(options)
|
return request(options)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
if (resp.statusCode !== 200) {
|
if (resp.statusCode !== 200) {
|
||||||
throw new Error(`${resp.statusCode} ${resp.body}`);
|
const error = new Error(`${resp.statusCode} ${resp.body}`);
|
||||||
|
error.response = resp;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data;
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = JSON.parse(resp.body);
|
||||||
|
} catch (err) {
|
||||||
|
const error = new Error(`Error parsing Telegram response: ${resp.body}`);
|
||||||
|
error.response = resp;
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = this._safeParse(resp.body);
|
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
return data.result;
|
return data.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`${data.error_code} ${data.description}`);
|
const error = new Error(`${data.error_code} ${data.description}`);
|
||||||
|
error.response = resp;
|
||||||
|
error.response.body = data;
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates url with bot token and provided path/method you want to be got/executed by bot
|
* Format data to be uploaded; handles file paths, streams and buffers
|
||||||
* @return {String} url
|
* @param {String} type
|
||||||
* @param {String} path
|
* @param {String|stream.Stream|Buffer} data
|
||||||
|
* @return {Array} formatted
|
||||||
|
* @return {Object} formatted[0] formData
|
||||||
|
* @return {String} formatted[1] fileId
|
||||||
* @private
|
* @private
|
||||||
* @see https://core.telegram.org/bots/api#making-requests
|
|
||||||
*/
|
*/
|
||||||
_buildURL(_path) {
|
_formatSendData(type, data) {
|
||||||
return URL.format({
|
let formData;
|
||||||
protocol: 'https',
|
let fileName;
|
||||||
host: 'api.telegram.org',
|
let fileId;
|
||||||
pathname: `/bot${this.token}/${_path}`
|
if (data instanceof stream.Stream) {
|
||||||
});
|
fileName = URL.parse(path.basename(data.path.toString())).pathname;
|
||||||
|
formData = {};
|
||||||
|
formData[type] = {
|
||||||
|
value: data,
|
||||||
|
options: {
|
||||||
|
filename: qs.unescape(fileName),
|
||||||
|
contentType: mime.lookup(fileName)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if (Buffer.isBuffer(data)) {
|
||||||
|
const filetype = fileType(data);
|
||||||
|
if (!filetype) {
|
||||||
|
throw new Error('Unsupported Buffer file type');
|
||||||
|
}
|
||||||
|
formData = {};
|
||||||
|
formData[type] = {
|
||||||
|
value: data,
|
||||||
|
options: {
|
||||||
|
filename: `data.${filetype.ext}`,
|
||||||
|
contentType: filetype.mime
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if (fs.existsSync(data)) {
|
||||||
|
fileName = path.basename(data);
|
||||||
|
formData = {};
|
||||||
|
formData[type] = {
|
||||||
|
value: fs.createReadStream(data),
|
||||||
|
options: {
|
||||||
|
filename: fileName,
|
||||||
|
contentType: mime.lookup(fileName)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
fileId = data;
|
||||||
|
}
|
||||||
|
return [formData, fileId];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start polling
|
||||||
|
*/
|
||||||
|
initPolling() {
|
||||||
|
if (this._polling) {
|
||||||
|
this._polling.stopPolling({
|
||||||
|
cancel: true,
|
||||||
|
reason: 'Polling restart',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._polling = new TelegramBotPolling(this._request.bind(this), this.options.polling, this._processUpdate.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops polling after the last polling request resolves
|
||||||
|
* @return {Promise} promise Promise, of last polling request
|
||||||
|
*/
|
||||||
|
stopPolling() {
|
||||||
|
if (!this._polling) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
const polling = this._polling;
|
||||||
|
delete this._polling;
|
||||||
|
return polling.stopPolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -330,49 +418,6 @@ class TelegramBot extends EventEmitter {
|
|||||||
return this._request('forwardMessage', { form });
|
return this._request('forwardMessage', { form });
|
||||||
}
|
}
|
||||||
|
|
||||||
_formatSendData(type, data) {
|
|
||||||
let formData;
|
|
||||||
let fileName;
|
|
||||||
let fileId;
|
|
||||||
if (data instanceof stream.Stream) {
|
|
||||||
fileName = URL.parse(path.basename(data.path.toString())).pathname;
|
|
||||||
formData = {};
|
|
||||||
formData[type] = {
|
|
||||||
value: data,
|
|
||||||
options: {
|
|
||||||
filename: qs.unescape(fileName),
|
|
||||||
contentType: mime.lookup(fileName)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else if (Buffer.isBuffer(data)) {
|
|
||||||
const filetype = fileType(data);
|
|
||||||
if (!filetype) {
|
|
||||||
throw new Error('Unsupported Buffer file type');
|
|
||||||
}
|
|
||||||
formData = {};
|
|
||||||
formData[type] = {
|
|
||||||
value: data,
|
|
||||||
options: {
|
|
||||||
filename: `data.${filetype.ext}`,
|
|
||||||
contentType: filetype.mime
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else if (fs.existsSync(data)) {
|
|
||||||
fileName = path.basename(data);
|
|
||||||
formData = {};
|
|
||||||
formData[type] = {
|
|
||||||
value: fs.createReadStream(data),
|
|
||||||
options: {
|
|
||||||
filename: fileName,
|
|
||||||
contentType: mime.lookup(fileName)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
fileId = data;
|
|
||||||
}
|
|
||||||
return [formData, fileId];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send photo
|
* Send photo
|
||||||
* @param {Number|String} chatId Unique identifier for the message recipient
|
* @param {Number|String} chatId Unique identifier for the message recipient
|
||||||
@ -776,7 +821,7 @@ class TelegramBot extends EventEmitter {
|
|||||||
* the `msg` and the result of executing `regexp.exec` on message text.
|
* the `msg` and the result of executing `regexp.exec` on message text.
|
||||||
*/
|
*/
|
||||||
onText(regexp, callback) {
|
onText(regexp, callback) {
|
||||||
this.textRegexpCallbacks.push({ regexp, callback });
|
this._textRegexpCallbacks.push({ regexp, callback });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -787,7 +832,7 @@ class TelegramBot extends EventEmitter {
|
|||||||
* message.
|
* message.
|
||||||
*/
|
*/
|
||||||
onReplyToMessage(chatId, messageId, callback) {
|
onReplyToMessage(chatId, messageId, callback) {
|
||||||
this.onReplyToMessages.push({
|
this._onReplyToMessages.push({
|
||||||
chatId,
|
chatId,
|
||||||
messageId,
|
messageId,
|
||||||
callback
|
callback
|
||||||
|
@ -1,48 +1,69 @@
|
|||||||
const Promise = require('bluebird');
|
|
||||||
const debug = require('debug')('node-telegram-bot-api');
|
const debug = require('debug')('node-telegram-bot-api');
|
||||||
const request = require('request-promise');
|
|
||||||
const URL = require('url');
|
|
||||||
const ANOTHER_WEB_HOOK_USED = 409;
|
const ANOTHER_WEB_HOOK_USED = 409;
|
||||||
|
|
||||||
|
|
||||||
class TelegramBotPolling {
|
class TelegramBotPolling {
|
||||||
|
/**
|
||||||
constructor(token, options = {}, callback) {
|
* Handles polling against the Telegram servers.
|
||||||
// enable cancellation
|
*
|
||||||
Promise.config({
|
* @param {Function} request Function used to make HTTP requests
|
||||||
cancellation: true,
|
* @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
|
||||||
|
*/
|
||||||
|
constructor(request, options = {}, callback) {
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
if (typeof options === 'function') {
|
if (typeof options === 'function') {
|
||||||
callback = options; // eslint-disable-line no-param-reassign
|
callback = options;
|
||||||
options = {}; // eslint-disable-line no-param-reassign
|
options = {};
|
||||||
|
} else if (typeof options === 'boolean') {
|
||||||
|
options = {};
|
||||||
}
|
}
|
||||||
|
/* eslint-enable no-param-reassign */
|
||||||
|
|
||||||
this.offset = 0;
|
this.request = request;
|
||||||
this.token = token;
|
this.options = options;
|
||||||
|
this.options.timeout = options.timeout || 10;
|
||||||
|
this.options.interval = (typeof options.interval === 'number') ? options.interval : 300;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.timeout = options.timeout || 10;
|
this._offset = 0;
|
||||||
this.interval = (typeof options.interval === 'number') ? options.interval : 300;
|
this._lastUpdate = 0;
|
||||||
this.lastUpdate = 0;
|
this._lastRequest = null;
|
||||||
this.lastRequest = null;
|
this._abort = false;
|
||||||
this.abort = false;
|
|
||||||
this._polling();
|
this._polling();
|
||||||
}
|
}
|
||||||
|
|
||||||
stopPolling() {
|
/**
|
||||||
this.abort = true;
|
* Stop polling
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {Boolean} [options.cancel] Cancel current request
|
||||||
|
* @param {String} [options.reason] Reason for stopping polling
|
||||||
|
*/
|
||||||
|
stopPolling(options = {}) {
|
||||||
|
this._abort = true;
|
||||||
|
if (options.cancel) {
|
||||||
|
const reason = options.reason || 'Polling stop';
|
||||||
|
return this._lastRequest.cancel(reason);
|
||||||
|
}
|
||||||
// wait until the last request is fulfilled
|
// wait until the last request is fulfilled
|
||||||
return this.lastRequest;
|
return this._lastRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes polling (with recursion!)
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
_polling() {
|
_polling() {
|
||||||
this.lastRequest = this
|
this._lastRequest = this
|
||||||
._getUpdates()
|
._getUpdates()
|
||||||
.then(updates => {
|
.then(updates => {
|
||||||
this.lastUpdate = Date.now();
|
this._lastUpdate = Date.now();
|
||||||
debug('polling data %j', updates);
|
debug('polling data %j', updates);
|
||||||
updates.forEach(update => {
|
updates.forEach(update => {
|
||||||
this.offset = update.update_id;
|
this._offset = update.update_id;
|
||||||
debug('updated offset: %s', this.offset);
|
debug('updated offset: %s', this._offset);
|
||||||
this.callback(update);
|
this.callback(update);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -51,83 +72,46 @@ class TelegramBotPolling {
|
|||||||
throw err;
|
throw err;
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
if (this.abort) {
|
if (this._abort) {
|
||||||
debug('Polling is aborted!');
|
debug('Polling is aborted!');
|
||||||
} else {
|
} else {
|
||||||
debug('setTimeout for %s miliseconds', this.interval);
|
debug('setTimeout for %s miliseconds', this.options.interval);
|
||||||
setTimeout(() => this._polling(), this.interval);
|
setTimeout(() => this._polling(), this.options.interval);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// used so that other funcs are not non-optimizable
|
/**
|
||||||
_safeParse(json) {
|
* Unset current webhook. Used when we detect that a webhook has been set
|
||||||
try {
|
* and we are trying to poll. Polling and WebHook are mutually exclusive.
|
||||||
return JSON.parse(json);
|
* @see https://core.telegram.org/bots/api#getting-updates
|
||||||
} catch (err) {
|
* @private
|
||||||
throw new Error(`Error parsing Telegram response: ${String(json)}`);
|
*/
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_unsetWebHook() {
|
_unsetWebHook() {
|
||||||
return request({
|
return this.request('setWebHook');
|
||||||
url: URL.format({
|
|
||||||
protocol: 'https',
|
|
||||||
host: 'api.telegram.org',
|
|
||||||
pathname: `/bot${this.token}/setWebHook`
|
|
||||||
}),
|
|
||||||
simple: false,
|
|
||||||
resolveWithFullResponse: true
|
|
||||||
})
|
|
||||||
.promise()
|
|
||||||
.then(resp => {
|
|
||||||
if (!resp) {
|
|
||||||
throw new Error(resp);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve updates
|
||||||
|
*/
|
||||||
_getUpdates() {
|
_getUpdates() {
|
||||||
const opts = {
|
const opts = {
|
||||||
qs: {
|
qs: {
|
||||||
offset: this.offset + 1,
|
offset: this._offset + 1,
|
||||||
limit: this.limit,
|
limit: this.options.limit,
|
||||||
timeout: this.timeout
|
timeout: this.options.timeout
|
||||||
},
|
},
|
||||||
url: URL.format({
|
|
||||||
protocol: 'https',
|
|
||||||
host: 'api.telegram.org',
|
|
||||||
pathname: `/bot${this.token}/getUpdates`
|
|
||||||
}),
|
|
||||||
simple: false,
|
|
||||||
resolveWithFullResponse: true,
|
|
||||||
forever: true,
|
|
||||||
};
|
};
|
||||||
debug('polling with options: %j', opts);
|
debug('polling with options: %j', opts);
|
||||||
|
|
||||||
return request(opts)
|
return this.request('getUpdates', opts)
|
||||||
.promise()
|
.catch(err => {
|
||||||
.timeout((10 + this.timeout) * 1000)
|
if (err.response.statusCode === ANOTHER_WEB_HOOK_USED) {
|
||||||
.then(resp => {
|
|
||||||
if (resp.statusCode === ANOTHER_WEB_HOOK_USED) {
|
|
||||||
return this._unsetWebHook();
|
return this._unsetWebHook();
|
||||||
}
|
}
|
||||||
|
throw err;
|
||||||
if (resp.statusCode !== 200) {
|
|
||||||
throw new Error(`${resp.statusCode} ${resp.body}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = this._safeParse(resp.body);
|
|
||||||
|
|
||||||
if (data.ok) {
|
|
||||||
return data.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`${data.error_code} ${data.description}`);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TelegramBotPolling;
|
module.exports = TelegramBotPolling;
|
||||||
|
@ -4,18 +4,28 @@ const http = require('http');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const bl = require('bl');
|
const bl = require('bl');
|
||||||
|
|
||||||
|
|
||||||
class TelegramBotWebHook {
|
class TelegramBotWebHook {
|
||||||
|
/**
|
||||||
|
* Sets up a webhook to receive updates
|
||||||
|
*
|
||||||
|
* @param {String} token Telegram API token
|
||||||
|
* @param {Boolean|Object} options WebHook options
|
||||||
|
* @param {Number} [options.port=8443] Port to bind to
|
||||||
|
* @param {Function} callback Function for process a new update
|
||||||
|
*/
|
||||||
constructor(token, options, callback) {
|
constructor(token, options, callback) {
|
||||||
this.token = token;
|
|
||||||
this.callback = callback;
|
|
||||||
this.regex = new RegExp(this.token);
|
|
||||||
|
|
||||||
// define opts
|
// define opts
|
||||||
if (typeof options === 'boolean') {
|
if (typeof options === 'boolean') {
|
||||||
options = {}; // eslint-disable-line no-param-reassign
|
options = {}; // eslint-disable-line no-param-reassign
|
||||||
}
|
}
|
||||||
options.port = options.port || 8443;
|
|
||||||
|
this.token = token;
|
||||||
|
this.options = options;
|
||||||
|
this.options.port = options.port || 8443;
|
||||||
|
this.callback = callback;
|
||||||
|
this._regex = new RegExp(this.token);
|
||||||
|
this._webServer = null;
|
||||||
|
|
||||||
if (options.key && options.cert) { // HTTPS Server
|
if (options.key && options.cert) { // HTTPS Server
|
||||||
debug('HTTPS WebHook enabled');
|
debug('HTTPS WebHook enabled');
|
||||||
@ -44,7 +54,10 @@ class TelegramBotWebHook {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pipe+parse body
|
/**
|
||||||
|
* Handle request body by passing it to 'callback'
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
_parseBody = (err, body) => {
|
_parseBody = (err, body) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return debug(err);
|
return debug(err);
|
||||||
@ -58,13 +71,18 @@ class TelegramBotWebHook {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// bound req listener
|
/**
|
||||||
|
* Listener for 'request' event on server
|
||||||
|
* @private
|
||||||
|
* @see https://nodejs.org/docs/latest/api/http.html#http_http_createserver_requestlistener
|
||||||
|
* @see https://nodejs.org/docs/latest/api/https.html#https_https_createserver_options_requestlistener
|
||||||
|
*/
|
||||||
_requestListener = (req, res) => {
|
_requestListener = (req, res) => {
|
||||||
debug('WebHook request URL: %s', req.url);
|
debug('WebHook request URL: %s', req.url);
|
||||||
debug('WebHook request headers: %j', req.headers);
|
debug('WebHook request headers: %j', req.headers);
|
||||||
|
|
||||||
// If there isn't token on URL
|
// If there isn't token on URL
|
||||||
if (!this.regex.test(req.url)) {
|
if (!this._regex.test(req.url)) {
|
||||||
debug('WebHook request unauthorized');
|
debug('WebHook request unauthorized');
|
||||||
res.statusCode = 401;
|
res.statusCode = 401;
|
||||||
res.end();
|
res.end();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user