node-telegram-utils 0.0.1-security → 0.64.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-utils might be problematic. Click here for more details.

@@ -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
@@ -0,0 +1,3 @@
1
+ const util = require('util');
2
+ // Native deprecation warning
3
+ exports.deprecate = (msg) => util.deprecate(() => { }, msg, 'node-telegram-bot-api')();