node-telegram-util 0.0.1-security → 0.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of node-telegram-util might be problematic. Click here for more details.
- package/.github/ISSUE_TEMPLATE.md +68 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +23 -0
- package/CHANGELOG.md +475 -0
- package/CODE_OF_CONDUCT.md +74 -0
- package/CONTRIBUTING.md +45 -0
- package/LICENSE.md +21 -0
- package/README.md +128 -3
- package/doc/api.hbs +19 -0
- package/doc/api.md +0 -0
- package/doc/experimental.md +28 -0
- package/doc/help.md +151 -0
- package/doc/tutorials.md +12 -0
- package/doc/usage.md +269 -0
- package/index.js +13 -0
- package/lib/errors.js +104 -0
- package/lib/telegram.js +1356 -0
- package/lib/telegramPolling.js +237 -0
- package/lib/telegramWebHook.js +187 -0
- package/lib/utils.js +7 -0
- package/package.json +77 -3
- package/src/errors.js +65 -0
- package/src/telegram.js +2998 -0
- package/src/telegramPolling.js +202 -0
- package/src/telegramWebHook.js +158 -0
- package/src/utils.js +3 -0
@@ -0,0 +1,202 @@
|
|
1
|
+
const errors = require('./errors');
|
2
|
+
const debug = require('debug')('node-telegram-bot-api');
|
3
|
+
const deprecate = require('./utils').deprecate;
|
4
|
+
const ANOTHER_WEB_HOOK_USED = 409;
|
5
|
+
|
6
|
+
|
7
|
+
class TelegramBotPolling {
|
8
|
+
/**
|
9
|
+
* Handles polling against the Telegram servers.
|
10
|
+
* @param {TelegramBot} bot
|
11
|
+
* @see https://core.telegram.org/bots/api#getting-updates
|
12
|
+
*/
|
13
|
+
constructor(bot) {
|
14
|
+
this.bot = bot;
|
15
|
+
this.options = (typeof bot.options.polling === 'boolean') ? {} : bot.options.polling;
|
16
|
+
this.options.interval = (typeof this.options.interval === 'number') ? this.options.interval : 300;
|
17
|
+
this.options.params = (typeof this.options.params === 'object') ? this.options.params : {};
|
18
|
+
this.options.params.offset = (typeof this.options.params.offset === 'number') ? this.options.params.offset : 0;
|
19
|
+
this.options.params.timeout = (typeof this.options.params.timeout === 'number') ? this.options.params.timeout : 10;
|
20
|
+
if (typeof this.options.timeout === 'number') {
|
21
|
+
deprecate('`options.polling.timeout` is deprecated. Use `options.polling.params` instead.');
|
22
|
+
this.options.params.timeout = this.options.timeout;
|
23
|
+
}
|
24
|
+
this._lastUpdate = 0;
|
25
|
+
this._lastRequest = null;
|
26
|
+
this._abort = false;
|
27
|
+
this._pollingTimeout = null;
|
28
|
+
}
|
29
|
+
|
30
|
+
/**
|
31
|
+
* Start polling
|
32
|
+
* @param {Object} [options]
|
33
|
+
* @param {Object} [options.restart]
|
34
|
+
* @return {Promise}
|
35
|
+
*/
|
36
|
+
start(options = {}) {
|
37
|
+
if (this._lastRequest) {
|
38
|
+
if (!options.restart) {
|
39
|
+
return Promise.resolve();
|
40
|
+
}
|
41
|
+
return this.stop({
|
42
|
+
cancel: true,
|
43
|
+
reason: 'Polling restart',
|
44
|
+
}).then(() => {
|
45
|
+
return this._polling();
|
46
|
+
});
|
47
|
+
}
|
48
|
+
return this._polling();
|
49
|
+
}
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Stop polling
|
53
|
+
* @param {Object} [options] Options
|
54
|
+
* @param {Boolean} [options.cancel] Cancel current request
|
55
|
+
* @param {String} [options.reason] Reason for stopping polling
|
56
|
+
* @return {Promise}
|
57
|
+
*/
|
58
|
+
stop(options = {}) {
|
59
|
+
if (!this._lastRequest) {
|
60
|
+
return Promise.resolve();
|
61
|
+
}
|
62
|
+
const lastRequest = this._lastRequest;
|
63
|
+
this._lastRequest = null;
|
64
|
+
clearTimeout(this._pollingTimeout);
|
65
|
+
if (options.cancel) {
|
66
|
+
const reason = options.reason || 'Polling stop';
|
67
|
+
lastRequest.cancel(reason);
|
68
|
+
return Promise.resolve();
|
69
|
+
}
|
70
|
+
this._abort = true;
|
71
|
+
return lastRequest.finally(() => {
|
72
|
+
this._abort = false;
|
73
|
+
});
|
74
|
+
}
|
75
|
+
|
76
|
+
/**
|
77
|
+
* Return `true` if is polling. Otherwise, `false`.
|
78
|
+
*/
|
79
|
+
isPolling() {
|
80
|
+
return !!this._lastRequest;
|
81
|
+
}
|
82
|
+
|
83
|
+
/**
|
84
|
+
* Handle error thrown during polling.
|
85
|
+
* @private
|
86
|
+
* @param {Error} error
|
87
|
+
*/
|
88
|
+
_error(error) {
|
89
|
+
if (!this.bot.listeners('polling_error').length) {
|
90
|
+
return console.error('error: [polling_error] %j', error); // eslint-disable-line no-console
|
91
|
+
}
|
92
|
+
return this.bot.emit('polling_error', error);
|
93
|
+
}
|
94
|
+
|
95
|
+
/**
|
96
|
+
* Invokes polling (with recursion!)
|
97
|
+
* @return {Promise} promise of the current request
|
98
|
+
* @private
|
99
|
+
*/
|
100
|
+
_polling() {
|
101
|
+
this._lastRequest = this
|
102
|
+
._getUpdates()
|
103
|
+
.then(updates => {
|
104
|
+
this._lastUpdate = Date.now();
|
105
|
+
debug('polling data %j', updates);
|
106
|
+
updates.forEach(update => {
|
107
|
+
this.options.params.offset = update.update_id + 1;
|
108
|
+
debug('updated offset: %s', this.options.params.offset);
|
109
|
+
try {
|
110
|
+
this.bot.processUpdate(update);
|
111
|
+
} catch (err) {
|
112
|
+
err._processing = true;
|
113
|
+
throw err;
|
114
|
+
}
|
115
|
+
});
|
116
|
+
return null;
|
117
|
+
})
|
118
|
+
.catch(err => {
|
119
|
+
debug('polling error: %s', err.message);
|
120
|
+
if (!err._processing) {
|
121
|
+
return this._error(err);
|
122
|
+
}
|
123
|
+
delete err._processing;
|
124
|
+
/*
|
125
|
+
* An error occured while processing the items,
|
126
|
+
* i.e. in `this.bot.processUpdate()` above.
|
127
|
+
* We need to mark the already-processed items
|
128
|
+
* to avoid fetching them again once the application
|
129
|
+
* is restarted, or moves to next polling interval
|
130
|
+
* (in cases where unhandled rejections do not terminate
|
131
|
+
* the process).
|
132
|
+
* See https://github.com/yagop/node-telegram-bot-api/issues/36#issuecomment-268532067
|
133
|
+
*/
|
134
|
+
if (!this.bot.options.badRejection) {
|
135
|
+
return this._error(err);
|
136
|
+
}
|
137
|
+
const opts = {
|
138
|
+
offset: this.options.params.offset,
|
139
|
+
limit: 1,
|
140
|
+
timeout: 0,
|
141
|
+
};
|
142
|
+
return this.bot.getUpdates(opts).then(() => {
|
143
|
+
return this._error(err);
|
144
|
+
}).catch(requestErr => {
|
145
|
+
/*
|
146
|
+
* We have been unable to handle this error.
|
147
|
+
* We have to log this to stderr to ensure devops
|
148
|
+
* understands that they may receive already-processed items
|
149
|
+
* on app restart.
|
150
|
+
* We simply can not rescue this situation, emit "error"
|
151
|
+
* event, with the hope that the application exits.
|
152
|
+
*/
|
153
|
+
/* eslint-disable no-console */
|
154
|
+
const bugUrl = 'https://github.com/yagop/node-telegram-bot-api/issues/36#issuecomment-268532067';
|
155
|
+
console.error('error: Internal handling of The Offset Infinite Loop failed');
|
156
|
+
console.error(`error: Due to error '${requestErr}'`);
|
157
|
+
console.error('error: You may receive already-processed updates on app restart');
|
158
|
+
console.error(`error: Please see ${bugUrl} for more information`);
|
159
|
+
/* eslint-enable no-console */
|
160
|
+
return this.bot.emit('error', new errors.FatalError(err));
|
161
|
+
});
|
162
|
+
})
|
163
|
+
.finally(() => {
|
164
|
+
if (this._abort) {
|
165
|
+
debug('Polling is aborted!');
|
166
|
+
} else {
|
167
|
+
debug('setTimeout for %s miliseconds', this.options.interval);
|
168
|
+
this._pollingTimeout = setTimeout(() => this._polling(), this.options.interval);
|
169
|
+
}
|
170
|
+
});
|
171
|
+
return this._lastRequest;
|
172
|
+
}
|
173
|
+
|
174
|
+
/**
|
175
|
+
* Unset current webhook. Used when we detect that a webhook has been set
|
176
|
+
* and we are trying to poll. Polling and WebHook are mutually exclusive.
|
177
|
+
* @see https://core.telegram.org/bots/api#getting-updates
|
178
|
+
* @private
|
179
|
+
*/
|
180
|
+
_unsetWebHook() {
|
181
|
+
debug('unsetting webhook');
|
182
|
+
return this.bot._request('setWebHook');
|
183
|
+
}
|
184
|
+
|
185
|
+
/**
|
186
|
+
* Retrieve updates
|
187
|
+
*/
|
188
|
+
_getUpdates() {
|
189
|
+
debug('polling with options: %j', this.options.params);
|
190
|
+
return this.bot.getUpdates(this.options.params)
|
191
|
+
.catch(err => {
|
192
|
+
if (err.response && err.response.statusCode === ANOTHER_WEB_HOOK_USED) {
|
193
|
+
return this._unsetWebHook().then(() => {
|
194
|
+
return this.bot.getUpdates(this.options.params);
|
195
|
+
});
|
196
|
+
}
|
197
|
+
throw err;
|
198
|
+
});
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
module.exports = TelegramBotPolling;
|
@@ -0,0 +1,158 @@
|
|
1
|
+
const errors = require('./errors');
|
2
|
+
const debug = require('debug')('node-telegram-bot-api');
|
3
|
+
const https = require('https');
|
4
|
+
const http = require('http');
|
5
|
+
const fs = require('fs');
|
6
|
+
const bl = require('bl');
|
7
|
+
|
8
|
+
class TelegramBotWebHook {
|
9
|
+
/**
|
10
|
+
* Sets up a webhook to receive updates
|
11
|
+
* @param {TelegramBot} bot
|
12
|
+
* @see https://core.telegram.org/bots/api#getting-updates
|
13
|
+
*/
|
14
|
+
constructor(bot) {
|
15
|
+
this.bot = bot;
|
16
|
+
this.options = (typeof bot.options.webHook === 'boolean') ? {} : bot.options.webHook;
|
17
|
+
this.options.host = this.options.host || '0.0.0.0';
|
18
|
+
this.options.port = this.options.port || 8443;
|
19
|
+
this.options.https = this.options.https || {};
|
20
|
+
this.options.healthEndpoint = this.options.healthEndpoint || '/healthz';
|
21
|
+
this._healthRegex = new RegExp(this.options.healthEndpoint);
|
22
|
+
this._webServer = null;
|
23
|
+
this._open = false;
|
24
|
+
this._requestListener = this._requestListener.bind(this);
|
25
|
+
this._parseBody = this._parseBody.bind(this);
|
26
|
+
|
27
|
+
if (this.options.key && this.options.cert) {
|
28
|
+
debug('HTTPS WebHook enabled (by key/cert)');
|
29
|
+
this.options.https.key = fs.readFileSync(this.options.key);
|
30
|
+
this.options.https.cert = fs.readFileSync(this.options.cert);
|
31
|
+
this._webServer = https.createServer(this.options.https, this._requestListener);
|
32
|
+
} else if (this.options.pfx) {
|
33
|
+
debug('HTTPS WebHook enabled (by pfx)');
|
34
|
+
this.options.https.pfx = fs.readFileSync(this.options.pfx);
|
35
|
+
this._webServer = https.createServer(this.options.https, this._requestListener);
|
36
|
+
} else if (Object.keys(this.options.https).length) {
|
37
|
+
debug('HTTPS WebHook enabled by (https)');
|
38
|
+
this._webServer = https.createServer(this.options.https, this._requestListener);
|
39
|
+
} else {
|
40
|
+
debug('HTTP WebHook enabled');
|
41
|
+
this._webServer = http.createServer(this._requestListener);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
/**
|
46
|
+
* Open WebHook by listening on the port
|
47
|
+
* @return {Promise}
|
48
|
+
*/
|
49
|
+
open() {
|
50
|
+
if (this.isOpen()) {
|
51
|
+
return Promise.resolve();
|
52
|
+
}
|
53
|
+
return new Promise((resolve, reject) => {
|
54
|
+
this._webServer.listen(this.options.port, this.options.host, () => {
|
55
|
+
debug('WebHook listening on port %s', this.options.port);
|
56
|
+
this._open = true;
|
57
|
+
return resolve();
|
58
|
+
});
|
59
|
+
|
60
|
+
this._webServer.once('error', (err) => {
|
61
|
+
reject(err);
|
62
|
+
});
|
63
|
+
});
|
64
|
+
}
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Close the webHook
|
68
|
+
* @return {Promise}
|
69
|
+
*/
|
70
|
+
close() {
|
71
|
+
if (!this.isOpen()) {
|
72
|
+
return Promise.resolve();
|
73
|
+
}
|
74
|
+
return new Promise((resolve, reject) => {
|
75
|
+
this._webServer.close(error => {
|
76
|
+
if (error) return reject(error);
|
77
|
+
this._open = false;
|
78
|
+
return resolve();
|
79
|
+
});
|
80
|
+
});
|
81
|
+
}
|
82
|
+
|
83
|
+
/**
|
84
|
+
* Return `true` if server is listening. Otherwise, `false`.
|
85
|
+
*/
|
86
|
+
isOpen() {
|
87
|
+
// NOTE: Since `http.Server.listening` was added in v5.7.0
|
88
|
+
// and we still need to support Node v4,
|
89
|
+
// we are going to fallback to 'this._open'.
|
90
|
+
// The following LOC would suffice for newer versions of Node.js
|
91
|
+
// return this._webServer.listening;
|
92
|
+
return this._open;
|
93
|
+
}
|
94
|
+
|
95
|
+
/**
|
96
|
+
* Handle error thrown during processing of webhook request.
|
97
|
+
* @private
|
98
|
+
* @param {Error} error
|
99
|
+
*/
|
100
|
+
_error(error) {
|
101
|
+
if (!this.bot.listeners('webhook_error').length) {
|
102
|
+
return console.error('error: [webhook_error] %j', error); // eslint-disable-line no-console
|
103
|
+
}
|
104
|
+
return this.bot.emit('webhook_error', error);
|
105
|
+
}
|
106
|
+
|
107
|
+
/**
|
108
|
+
* Handle request body by passing it to 'callback'
|
109
|
+
* @private
|
110
|
+
*/
|
111
|
+
_parseBody(error, body) {
|
112
|
+
if (error) {
|
113
|
+
return this._error(new errors.FatalError(error));
|
114
|
+
}
|
115
|
+
|
116
|
+
let data;
|
117
|
+
try {
|
118
|
+
data = JSON.parse(body.toString());
|
119
|
+
} catch (parseError) {
|
120
|
+
return this._error(new errors.ParseError(parseError.message));
|
121
|
+
}
|
122
|
+
|
123
|
+
return this.bot.processUpdate(data);
|
124
|
+
}
|
125
|
+
|
126
|
+
/**
|
127
|
+
* Listener for 'request' event on server
|
128
|
+
* @private
|
129
|
+
* @see https://nodejs.org/docs/latest/api/http.html#http_http_createserver_requestlistener
|
130
|
+
* @see https://nodejs.org/docs/latest/api/https.html#https_https_createserver_options_requestlistener
|
131
|
+
*/
|
132
|
+
_requestListener(req, res) {
|
133
|
+
debug('WebHook request URL: %s', req.url);
|
134
|
+
debug('WebHook request headers: %j', req.headers);
|
135
|
+
|
136
|
+
if (req.url.indexOf(this.bot.token) !== -1) {
|
137
|
+
if (req.method !== 'POST') {
|
138
|
+
debug('WebHook request isn\'t a POST');
|
139
|
+
res.statusCode = 418; // I'm a teabot!
|
140
|
+
res.end();
|
141
|
+
} else {
|
142
|
+
req
|
143
|
+
.pipe(bl(this._parseBody))
|
144
|
+
.on('finish', () => res.end('OK'));
|
145
|
+
}
|
146
|
+
} else if (this._healthRegex.test(req.url)) {
|
147
|
+
debug('WebHook health check passed');
|
148
|
+
res.statusCode = 200;
|
149
|
+
res.end('OK');
|
150
|
+
} else {
|
151
|
+
debug('WebHook request unauthorized');
|
152
|
+
res.statusCode = 401;
|
153
|
+
res.end();
|
154
|
+
}
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
module.exports = TelegramBotWebHook;
|
package/src/utils.js
ADDED