@zero-bot.net/tg-bot-api 1.2.0 → 1.5.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.
- package/README.md +100 -81
- package/index.js +5 -11
- package/package.json +19 -37
- package/src/errors.js +33 -3
- package/src/fileTypes.js +188 -0
- package/src/network.js +47 -0
- package/src/telegram.d.ts +372 -9
- package/src/telegram.js +192 -24
- package/src/telegramPolling.js +9 -3
- package/src/telegramWebHook.js +53 -4
- package/.github/ISSUE_TEMPLATE.md +0 -68
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -23
- package/CHANGELOG.md +0 -515
- package/CODE_OF_CONDUCT.md +0 -74
- package/CONTRIBUTING.md +0 -45
- package/doc/api.hbs +0 -19
- package/doc/api.md +0 -3473
- package/doc/experimental.md +0 -28
- package/doc/help.md +0 -151
- package/doc/usage.md +0 -269
- package/lib/errors.js +0 -62
- package/lib/telegram.d.ts +0 -4929
- package/lib/telegram.js +0 -4142
- package/lib/telegramPolling.js +0 -196
- package/lib/telegramWebHook.js +0 -156
- package/lib/utils.js +0 -3
package/src/telegram.js
CHANGED
|
@@ -1,23 +1,32 @@
|
|
|
1
|
-
// shims
|
|
2
|
-
require('array.prototype.findindex').shim(); // for Node.js v0.x
|
|
3
|
-
|
|
4
1
|
const errors = require('./errors');
|
|
5
2
|
const TelegramBotWebHook = require('./telegramWebHook');
|
|
6
3
|
const TelegramBotPolling = require('./telegramPolling');
|
|
7
4
|
const debug = require('debug')('@zero-bot.net/tg-bot-api');
|
|
8
5
|
const EventEmitter = require('eventemitter3');
|
|
9
|
-
const
|
|
6
|
+
const { detectFileType, lookupMime } = require('./fileTypes');
|
|
7
|
+
const { applyNetworkTuning } = require('./network');
|
|
10
8
|
const requestBase = require('@zero-bot.net/request');
|
|
11
|
-
const request = (options) =>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const request = (options) => {
|
|
10
|
+
let underlying = null;
|
|
11
|
+
const promise = new Promise((resolve, reject) => {
|
|
12
|
+
underlying = requestBase(options, (err, response) => {
|
|
13
|
+
if (err) reject(err);
|
|
14
|
+
else resolve(response);
|
|
15
|
+
});
|
|
15
16
|
});
|
|
16
|
-
|
|
17
|
+
// Expose cancellation so polling (and any caller) can abort an in-flight
|
|
18
|
+
// request. Aborting surfaces through the callback as a rejection.
|
|
19
|
+
promise.cancel = (reason) => {
|
|
20
|
+
if (underlying && typeof underlying.abort === 'function') {
|
|
21
|
+
underlying.abort();
|
|
22
|
+
}
|
|
23
|
+
return reason;
|
|
24
|
+
};
|
|
25
|
+
return promise;
|
|
26
|
+
};
|
|
17
27
|
const streamedRequest = requestBase;
|
|
18
28
|
const qs = require('querystring');
|
|
19
29
|
const stream = require('stream');
|
|
20
|
-
const mime = require('mime');
|
|
21
30
|
const path = require('path');
|
|
22
31
|
const URL = require('url');
|
|
23
32
|
const fs = require('fs');
|
|
@@ -108,6 +117,22 @@ const _messageTypes = [
|
|
|
108
117
|
// Bot API 10.3
|
|
109
118
|
'stopped_message_generation',
|
|
110
119
|
'community_chat_joined',
|
|
120
|
+
// Bot API 8.3+
|
|
121
|
+
'giveaway_created',
|
|
122
|
+
'giveaway',
|
|
123
|
+
'giveaway_winners',
|
|
124
|
+
'giveaway_completed',
|
|
125
|
+
'chat_boost_added',
|
|
126
|
+
'story',
|
|
127
|
+
'chat_background_set',
|
|
128
|
+
'forum_topic_created',
|
|
129
|
+
'forum_topic_closed',
|
|
130
|
+
'forum_topic_reopened',
|
|
131
|
+
'forum_topic_edited',
|
|
132
|
+
'general_forum_topic_hidden',
|
|
133
|
+
'general_forum_topic_unhidden',
|
|
134
|
+
'write_access_allowed',
|
|
135
|
+
'boost',
|
|
111
136
|
];
|
|
112
137
|
|
|
113
138
|
const _deprecatedMessageTypes = [
|
|
@@ -208,6 +233,13 @@ class TelegramBot extends EventEmitter {
|
|
|
208
233
|
* **if and only if** the Node.js version you're using terminates the
|
|
209
234
|
* process on unhandled rejections. This option is only for
|
|
210
235
|
* *forward-compatibility purposes*.
|
|
236
|
+
* @param {Boolean} [options.ipv4First=false] Apply latency-oriented network
|
|
237
|
+
* tuning at construction: prefer IPv4 and shorten Node's IPv6 fallback
|
|
238
|
+
* window. This mutates process-global DNS settings — see
|
|
239
|
+
* {@link TelegramBot.applyNetworkTuning}.
|
|
240
|
+
* @param {Boolean} [options.prewarm=false] Eagerly open the DNS/TCP/TLS
|
|
241
|
+
* connection with a lightweight `getMe()` call so the first real request is
|
|
242
|
+
* not a cold start.
|
|
211
243
|
* @see https://core.telegram.org/bots/api
|
|
212
244
|
*/
|
|
213
245
|
constructor(token, options = {}) {
|
|
@@ -219,12 +251,17 @@ class TelegramBot extends EventEmitter {
|
|
|
219
251
|
this.options.baseApiUrl = options.baseApiUrl || 'https://api.telegram.org';
|
|
220
252
|
this.options.filepath = (typeof options.filepath === 'undefined') ? true : options.filepath;
|
|
221
253
|
this.options.badRejection = (typeof options.badRejection === 'undefined') ? false : options.badRejection;
|
|
254
|
+
this.options.request = Object.assign({}, options.request);
|
|
222
255
|
this._textRegexpCallbacks = [];
|
|
223
256
|
this._replyListenerId = 0;
|
|
224
257
|
this._replyListeners = [];
|
|
225
258
|
this._polling = null;
|
|
226
259
|
this._webHook = null;
|
|
227
260
|
|
|
261
|
+
if (options.ipv4First) {
|
|
262
|
+
applyNetworkTuning({ ipv4First: true });
|
|
263
|
+
}
|
|
264
|
+
|
|
228
265
|
if (options.polling) {
|
|
229
266
|
const autoStart = options.polling.autoStart;
|
|
230
267
|
if (typeof autoStart === 'undefined' || autoStart === true) {
|
|
@@ -238,6 +275,23 @@ class TelegramBot extends EventEmitter {
|
|
|
238
275
|
this.openWebHook();
|
|
239
276
|
}
|
|
240
277
|
}
|
|
278
|
+
|
|
279
|
+
if (options.prewarm) {
|
|
280
|
+
// Fire-and-forget; a failed warm-up must not break construction.
|
|
281
|
+
this.preheat();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Apply latency-oriented network tuning (prefer IPv4, shorten Node's IPv6
|
|
287
|
+
* fallback window). Mutates process-global settings — call once at startup.
|
|
288
|
+
*
|
|
289
|
+
* @param {Object} [options] See {@link module:network.applyNetworkTuning}
|
|
290
|
+
* @return {Object} The applied settings
|
|
291
|
+
* @see https://nodejs.org/api/dns.html#dnssetdefaultresultorderorder
|
|
292
|
+
*/
|
|
293
|
+
static applyNetworkTuning(options) {
|
|
294
|
+
return applyNetworkTuning(options);
|
|
241
295
|
}
|
|
242
296
|
|
|
243
297
|
/**
|
|
@@ -342,7 +396,13 @@ class TelegramBot extends EventEmitter {
|
|
|
342
396
|
'link_preview_options',
|
|
343
397
|
];
|
|
344
398
|
for (const field of jsonFields) {
|
|
345
|
-
|
|
399
|
+
// Skip null/undefined: `stringify(null)` would produce the string "null" and
|
|
400
|
+
// corrupt sentinel values such as `photo: null` set by sendPhoto() when
|
|
401
|
+
// uploading a Buffer/Stream (Telegram would then treat it as a file_id).
|
|
402
|
+
if (obj.hasOwnProperty(field)
|
|
403
|
+
&& obj[field] !== null
|
|
404
|
+
&& obj[field] !== undefined
|
|
405
|
+
&& typeof obj[field] !== 'string') {
|
|
346
406
|
obj[field] = stringify(obj[field]);
|
|
347
407
|
}
|
|
348
408
|
}
|
|
@@ -382,13 +442,14 @@ class TelegramBot extends EventEmitter {
|
|
|
382
442
|
options.resolveWithFullResponse = true;
|
|
383
443
|
options.forever = true;
|
|
384
444
|
debug('HTTP request: %j', options);
|
|
385
|
-
|
|
445
|
+
const raw = request(options);
|
|
446
|
+
const promise = raw
|
|
386
447
|
.then(resp => {
|
|
387
448
|
let data;
|
|
388
449
|
try {
|
|
389
450
|
data = resp.body = JSON.parse(resp.body);
|
|
390
|
-
} catch (
|
|
391
|
-
throw new errors.ParseError(`Error parsing response: ${
|
|
451
|
+
} catch (parseError) {
|
|
452
|
+
throw new errors.ParseError(`Error parsing response: ${parseError.message}`, resp);
|
|
392
453
|
}
|
|
393
454
|
|
|
394
455
|
if (data.ok) {
|
|
@@ -401,6 +462,13 @@ class TelegramBot extends EventEmitter {
|
|
|
401
462
|
if (error.response) throw error;
|
|
402
463
|
throw new errors.FatalError(error);
|
|
403
464
|
});
|
|
465
|
+
|
|
466
|
+
// Keep cancellation available on the promise chain returned to callers.
|
|
467
|
+
if (typeof raw.cancel === 'function') {
|
|
468
|
+
promise.cancel = (reason) => raw.cancel(reason);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return promise;
|
|
404
472
|
}
|
|
405
473
|
|
|
406
474
|
/**
|
|
@@ -441,7 +509,7 @@ class TelegramBot extends EventEmitter {
|
|
|
441
509
|
filename = 'data';
|
|
442
510
|
}
|
|
443
511
|
if (!contentType) {
|
|
444
|
-
const filetype =
|
|
512
|
+
const filetype = detectFileType(data);
|
|
445
513
|
if (filetype) {
|
|
446
514
|
contentType = filetype.mime;
|
|
447
515
|
const ext = filetype.ext;
|
|
@@ -467,7 +535,7 @@ class TelegramBot extends EventEmitter {
|
|
|
467
535
|
}
|
|
468
536
|
|
|
469
537
|
filename = filename || 'filename';
|
|
470
|
-
contentType = contentType ||
|
|
538
|
+
contentType = contentType || lookupMime(filename);
|
|
471
539
|
if (process.env.NTBA_FIX_350) {
|
|
472
540
|
contentType = contentType || 'application/octet-stream';
|
|
473
541
|
} else {
|
|
@@ -900,6 +968,34 @@ class TelegramBot extends EventEmitter {
|
|
|
900
968
|
debug('Process Update removed_chat_boost %j', removedChatBoost);
|
|
901
969
|
this.emit('removed_chat_boost', removedChatBoost);
|
|
902
970
|
}
|
|
971
|
+
|
|
972
|
+
// Update-level types not tied to message content
|
|
973
|
+
const purchasedPaidMedia = update.purchased_paid_media;
|
|
974
|
+
const subscription = update.subscription;
|
|
975
|
+
const managedBot = update.managed_bot;
|
|
976
|
+
const guestMessage = update.guest_message;
|
|
977
|
+
const stoppedMessageGeneration = update.stopped_message_generation;
|
|
978
|
+
const reaction = update.reaction;
|
|
979
|
+
|
|
980
|
+
if (purchasedPaidMedia) {
|
|
981
|
+
debug('Process Update purchased_paid_media %j', purchasedPaidMedia);
|
|
982
|
+
this.emit('purchased_paid_media', purchasedPaidMedia);
|
|
983
|
+
} else if (subscription) {
|
|
984
|
+
debug('Process Update subscription %j', subscription);
|
|
985
|
+
this.emit('subscription', subscription);
|
|
986
|
+
} else if (managedBot) {
|
|
987
|
+
debug('Process Update managed_bot %j', managedBot);
|
|
988
|
+
this.emit('managed_bot', managedBot);
|
|
989
|
+
} else if (guestMessage) {
|
|
990
|
+
debug('Process Update guest_message %j', guestMessage);
|
|
991
|
+
this.emit('guest_message', guestMessage);
|
|
992
|
+
} else if (stoppedMessageGeneration) {
|
|
993
|
+
debug('Process Update stopped_message_generation %j', stoppedMessageGeneration);
|
|
994
|
+
this.emit('stopped_message_generation', stoppedMessageGeneration);
|
|
995
|
+
} else if (reaction) {
|
|
996
|
+
debug('Process Update reaction %j', reaction);
|
|
997
|
+
this.emit('reaction', reaction);
|
|
998
|
+
}
|
|
903
999
|
}
|
|
904
1000
|
|
|
905
1001
|
/** Start Telegram Bot API methods */
|
|
@@ -1013,6 +1109,34 @@ class TelegramBot extends EventEmitter {
|
|
|
1013
1109
|
return this._request('getMe', { form });
|
|
1014
1110
|
}
|
|
1015
1111
|
|
|
1112
|
+
/**
|
|
1113
|
+
* Warm up the connection so the first real request is not a cold start.
|
|
1114
|
+
*
|
|
1115
|
+
* Issues a lightweight `getMe()` call, which establishes DNS, the TCP socket
|
|
1116
|
+
* and the TLS session and populates the keep-alive pool. Worth calling once
|
|
1117
|
+
* at startup for webhook / broadcast bots.
|
|
1118
|
+
*
|
|
1119
|
+
* By default a failed warm-up is swallowed (emitting `preheat_error`) so it
|
|
1120
|
+
* is always safe to `await` at startup.
|
|
1121
|
+
*
|
|
1122
|
+
* @param {Object} [options]
|
|
1123
|
+
* @param {Boolean} [options.suppressErrors=true] Resolve instead of rejecting
|
|
1124
|
+
* when the warm-up request fails.
|
|
1125
|
+
* @return {Promise<TelegramBot>} Resolves with the bot instance
|
|
1126
|
+
* @see https://core.telegram.org/bots/api#getme
|
|
1127
|
+
*/
|
|
1128
|
+
preheat({ suppressErrors = true } = {}) {
|
|
1129
|
+
const warm = this.getMe();
|
|
1130
|
+
if (!suppressErrors) {
|
|
1131
|
+
return warm.then(() => this);
|
|
1132
|
+
}
|
|
1133
|
+
return warm.then(() => this).catch((error) => {
|
|
1134
|
+
debug('preheat failed: %s', error.message);
|
|
1135
|
+
this.emit('preheat_error', error);
|
|
1136
|
+
return this;
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1016
1140
|
/**
|
|
1017
1141
|
* This method log out your bot from the cloud Bot API server before launching the bot locally.
|
|
1018
1142
|
* You must log out the bot before running it locally, otherwise there is no guarantee that the bot will receive updates.
|
|
@@ -1087,7 +1211,7 @@ class TelegramBot extends EventEmitter {
|
|
|
1087
1211
|
forwardMessages(chatId, fromChatId, messageIds, form = {}) {
|
|
1088
1212
|
form.chat_id = chatId;
|
|
1089
1213
|
form.from_chat_id = fromChatId;
|
|
1090
|
-
form.message_ids = messageIds;
|
|
1214
|
+
form.message_ids = stringify(messageIds);
|
|
1091
1215
|
return this._request('forwardMessages', { form });
|
|
1092
1216
|
}
|
|
1093
1217
|
|
|
@@ -3303,7 +3427,14 @@ class TelegramBot extends EventEmitter {
|
|
|
3303
3427
|
* @see https://core.telegram.org/bots/api#sendgift
|
|
3304
3428
|
*/
|
|
3305
3429
|
sendGift(userId, giftId, form = {}) {
|
|
3306
|
-
|
|
3430
|
+
if (typeof userId === 'number' || typeof userId === 'string') {
|
|
3431
|
+
// Check if it looks like a chat ID (negative numbers or @channel)
|
|
3432
|
+
if (String(userId).charAt(0) === '-' || String(userId).charAt(0) === '@') {
|
|
3433
|
+
form.chat_id = userId;
|
|
3434
|
+
} else {
|
|
3435
|
+
form.user_id = userId;
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3307
3438
|
form.gift_id = giftId;
|
|
3308
3439
|
return this._request('sendGift', { form });
|
|
3309
3440
|
}
|
|
@@ -3971,14 +4102,15 @@ class TelegramBot extends EventEmitter {
|
|
|
3971
4102
|
* Use this method to remove multiple reactions from a message.
|
|
3972
4103
|
*
|
|
3973
4104
|
* @param {Number|String} chatId Unique identifier for the target chat or username of the target channel (in the format `@channelusername`)
|
|
3974
|
-
* @param {Number}
|
|
3975
|
-
* @param {Object} [options] Additional Telegram query options
|
|
4105
|
+
* @param {Number|Object} [options] Identifier of the user whose reactions are removed, or an options object (`user_id`/`actor_chat_id`)
|
|
3976
4106
|
* @return {Promise} True on success
|
|
3977
|
-
* @see https://core.telegram.org/bots/api#
|
|
4107
|
+
* @see https://core.telegram.org/bots/api#deleteallmessagereactions
|
|
3978
4108
|
*/
|
|
3979
|
-
deleteAllMessageReactions(chatId,
|
|
4109
|
+
deleteAllMessageReactions(chatId, options = {}) {
|
|
4110
|
+
const form = (typeof options === 'number' || typeof options === 'string')
|
|
4111
|
+
? { user_id: options }
|
|
4112
|
+
: options;
|
|
3980
4113
|
form.chat_id = chatId;
|
|
3981
|
-
form.message_id = messageId;
|
|
3982
4114
|
return this._request('deleteAllMessageReactions', { form });
|
|
3983
4115
|
}
|
|
3984
4116
|
|
|
@@ -4173,12 +4305,48 @@ class TelegramBot extends EventEmitter {
|
|
|
4173
4305
|
* @return {Promise} On success, the edited Message object is returned
|
|
4174
4306
|
* @see https://core.telegram.org/bots/api#editephemeralmessagemedia
|
|
4175
4307
|
*/
|
|
4176
|
-
editEphemeralMessageMedia(chatId, ephemeralMessageId, media, form = {}
|
|
4308
|
+
editEphemeralMessageMedia(chatId, ephemeralMessageId, media, form = {}) {
|
|
4177
4309
|
if (!chatId) return Promise.reject(new Error('chatId is required'));
|
|
4178
4310
|
if (!ephemeralMessageId) return Promise.reject(new Error('ephemeralMessageId is required'));
|
|
4311
|
+
|
|
4312
|
+
const regexAttach = /attach:\/\/.+/;
|
|
4313
|
+
|
|
4314
|
+
if (typeof media.media === 'string' && regexAttach.test(media.media)) {
|
|
4315
|
+
const opts = { qs: form };
|
|
4316
|
+
opts.formData = {};
|
|
4317
|
+
|
|
4318
|
+
const payload = Object.assign({}, media);
|
|
4319
|
+
delete payload.media;
|
|
4320
|
+
|
|
4321
|
+
try {
|
|
4322
|
+
const attachName = String(0);
|
|
4323
|
+
const [formData] = this._formatSendData(
|
|
4324
|
+
attachName,
|
|
4325
|
+
media.media.replace('attach://', ''),
|
|
4326
|
+
media.fileOptions
|
|
4327
|
+
);
|
|
4328
|
+
|
|
4329
|
+
if (formData) {
|
|
4330
|
+
opts.formData[attachName] = formData[attachName];
|
|
4331
|
+
payload.media = `attach://${attachName}`;
|
|
4332
|
+
} else {
|
|
4333
|
+
throw new errors.FatalError(`Failed to process the replacement action for your ${media.type}`);
|
|
4334
|
+
}
|
|
4335
|
+
} catch (ex) {
|
|
4336
|
+
return Promise.reject(ex);
|
|
4337
|
+
}
|
|
4338
|
+
|
|
4339
|
+
opts.qs.chat_id = chatId;
|
|
4340
|
+
opts.qs.ephemeral_message_id = ephemeralMessageId;
|
|
4341
|
+
opts.qs.media = stringify(payload);
|
|
4342
|
+
|
|
4343
|
+
return this._request('editEphemeralMessageMedia', opts);
|
|
4344
|
+
}
|
|
4345
|
+
|
|
4179
4346
|
form.chat_id = chatId;
|
|
4180
4347
|
form.ephemeral_message_id = ephemeralMessageId;
|
|
4181
4348
|
form.media = stringify(media);
|
|
4349
|
+
|
|
4182
4350
|
return this._request('editEphemeralMessageMedia', { form });
|
|
4183
4351
|
}
|
|
4184
4352
|
|
package/src/telegramPolling.js
CHANGED
|
@@ -23,6 +23,7 @@ class TelegramBotPolling {
|
|
|
23
23
|
}
|
|
24
24
|
this._lastUpdate = 0;
|
|
25
25
|
this._lastRequest = null;
|
|
26
|
+
this._currentRequest = null;
|
|
26
27
|
this._abort = false;
|
|
27
28
|
this._pollingTimeout = null;
|
|
28
29
|
}
|
|
@@ -60,11 +61,15 @@ class TelegramBotPolling {
|
|
|
60
61
|
return Promise.resolve();
|
|
61
62
|
}
|
|
62
63
|
const lastRequest = this._lastRequest;
|
|
64
|
+
const currentRequest = this._currentRequest;
|
|
63
65
|
this._lastRequest = null;
|
|
66
|
+
this._currentRequest = null;
|
|
64
67
|
clearTimeout(this._pollingTimeout);
|
|
65
68
|
if (options.cancel) {
|
|
66
69
|
const reason = options.reason || 'Polling stop';
|
|
67
|
-
|
|
70
|
+
if (currentRequest && typeof currentRequest.cancel === 'function') {
|
|
71
|
+
currentRequest.cancel(reason);
|
|
72
|
+
}
|
|
68
73
|
return Promise.resolve();
|
|
69
74
|
}
|
|
70
75
|
this._abort = true;
|
|
@@ -98,8 +103,9 @@ class TelegramBotPolling {
|
|
|
98
103
|
* @private
|
|
99
104
|
*/
|
|
100
105
|
_polling() {
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
const currentRequest = this._getUpdates();
|
|
107
|
+
this._currentRequest = currentRequest;
|
|
108
|
+
this._lastRequest = currentRequest
|
|
103
109
|
.then(updates => {
|
|
104
110
|
this._lastUpdate = Date.now();
|
|
105
111
|
debug('polling data %j', updates);
|
package/src/telegramWebHook.js
CHANGED
|
@@ -3,7 +3,9 @@ const debug = require('debug')('@zero-bot.net/tg-bot-api');
|
|
|
3
3
|
const https = require('https');
|
|
4
4
|
const http = require('http');
|
|
5
5
|
const fs = require('fs');
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
/** Default maximum accepted webhook body size (10 MiB). */
|
|
8
|
+
const DEFAULT_MAX_BODY_SIZE = 10 * 1024 * 1024;
|
|
7
9
|
|
|
8
10
|
class TelegramBotWebHook {
|
|
9
11
|
/**
|
|
@@ -18,11 +20,13 @@ class TelegramBotWebHook {
|
|
|
18
20
|
this.options.port = this.options.port || 8443;
|
|
19
21
|
this.options.https = this.options.https || {};
|
|
20
22
|
this.options.healthEndpoint = this.options.healthEndpoint || '/healthz';
|
|
23
|
+
this.options.maxBodySize = this.options.maxBodySize || DEFAULT_MAX_BODY_SIZE;
|
|
21
24
|
this._healthRegex = new RegExp(this.options.healthEndpoint);
|
|
22
25
|
this._webServer = null;
|
|
23
26
|
this._open = false;
|
|
24
27
|
this._requestListener = this._requestListener.bind(this);
|
|
25
28
|
this._parseBody = this._parseBody.bind(this);
|
|
29
|
+
this._collectBody = this._collectBody.bind(this);
|
|
26
30
|
|
|
27
31
|
if (this.options.key && this.options.cert) {
|
|
28
32
|
debug('HTTPS WebHook enabled (by key/cert)');
|
|
@@ -104,6 +108,40 @@ class TelegramBotWebHook {
|
|
|
104
108
|
return this.bot.emit('webhook_error', error);
|
|
105
109
|
}
|
|
106
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Buffer an incoming request body, enforcing the configured size limit.
|
|
113
|
+
* @private
|
|
114
|
+
* @param {http.IncomingMessage} req
|
|
115
|
+
* @param {Function} callback `(error, body)`
|
|
116
|
+
*/
|
|
117
|
+
_collectBody(req, callback) {
|
|
118
|
+
const chunks = [];
|
|
119
|
+
let size = 0;
|
|
120
|
+
let done = false;
|
|
121
|
+
|
|
122
|
+
const finish = (error, body) => {
|
|
123
|
+
if (done) return;
|
|
124
|
+
done = true;
|
|
125
|
+
callback(error, body);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
req.on('data', (chunk) => {
|
|
129
|
+
if (done) return undefined;
|
|
130
|
+
size += chunk.length;
|
|
131
|
+
if (size > this.options.maxBodySize) {
|
|
132
|
+
// Stop reading; the request listener answers with 413.
|
|
133
|
+
req.pause();
|
|
134
|
+
return finish(new errors.FatalError('WebHook request body exceeds maxBodySize'));
|
|
135
|
+
}
|
|
136
|
+
chunks.push(chunk);
|
|
137
|
+
return undefined;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
req.on('end', () => finish(null, Buffer.concat(chunks)));
|
|
141
|
+
req.on('error', (error) => finish(error));
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
107
145
|
/**
|
|
108
146
|
* Handle request body by passing it to 'callback'
|
|
109
147
|
* @private
|
|
@@ -139,9 +177,20 @@ class TelegramBotWebHook {
|
|
|
139
177
|
res.statusCode = 418; // I'm a teabot!
|
|
140
178
|
res.end();
|
|
141
179
|
} else {
|
|
142
|
-
req
|
|
143
|
-
|
|
144
|
-
|
|
180
|
+
this._collectBody(req, (error, body) => {
|
|
181
|
+
if (res.writableEnded) return undefined;
|
|
182
|
+
if (error) {
|
|
183
|
+
const tooLarge = /maxBodySize/.test(error.message);
|
|
184
|
+
debug('WebHook body rejected: %s', error.message);
|
|
185
|
+
res.statusCode = tooLarge ? 413 : 400;
|
|
186
|
+
res.setHeader('Connection', 'close');
|
|
187
|
+
res.end(tooLarge ? 'Payload Too Large' : 'Bad Request');
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
this._parseBody(null, body);
|
|
191
|
+
res.end('OK');
|
|
192
|
+
return undefined;
|
|
193
|
+
});
|
|
145
194
|
}
|
|
146
195
|
} else if (this._healthRegex.test(req.url)) {
|
|
147
196
|
debug('WebHook health check passed');
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
This template includes three sections:
|
|
3
|
-
1. Bug reporting
|
|
4
|
-
2. Feature request
|
|
5
|
-
3. Question
|
|
6
|
-
|
|
7
|
-
Please remove sections that do not apply to your issue
|
|
8
|
-
-->
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<!--********************************************************************
|
|
13
|
-
Reporting a Bug.
|
|
14
|
-
*********************************************************************-->
|
|
15
|
-
|
|
16
|
-
> Bug Report
|
|
17
|
-
|
|
18
|
-
I have read:
|
|
19
|
-
|
|
20
|
-
* [Usage information](https://github.com/ZeroBot-net/tg-bot-api/tree/master/doc/usage.md)
|
|
21
|
-
* [Help information](https://github.com/ZeroBot-net/tg-bot-api/tree/master/doc/help.md)
|
|
22
|
-
|
|
23
|
-
I am using the latest version of the library.
|
|
24
|
-
|
|
25
|
-
### Expected Behavior
|
|
26
|
-
|
|
27
|
-
<!-- Explain what you are trying to achieve -->
|
|
28
|
-
|
|
29
|
-
### Actual Behavior
|
|
30
|
-
|
|
31
|
-
<!-- Explain what happens, contrary to what you expected -->
|
|
32
|
-
|
|
33
|
-
### Steps to reproduce the Behavior
|
|
34
|
-
|
|
35
|
-
<!-- Explain how we can reproduce the bug -->
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
<!--********************************************************************
|
|
40
|
-
Feature Request.
|
|
41
|
-
*********************************************************************-->
|
|
42
|
-
|
|
43
|
-
> Feature Request
|
|
44
|
-
|
|
45
|
-
I have:
|
|
46
|
-
|
|
47
|
-
* searched for such a feature request (https://github.com/ZeroBot-net/tg-bot-api/labels/enhancement) and found none
|
|
48
|
-
|
|
49
|
-
### Introduction
|
|
50
|
-
|
|
51
|
-
<!-- Describe what value this feature would add, and in which use case,
|
|
52
|
-
or scenario -->
|
|
53
|
-
|
|
54
|
-
### Example
|
|
55
|
-
|
|
56
|
-
<!-- A code snippet of how this feature would work, were it already
|
|
57
|
-
implemented -->
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<!--********************************************************************
|
|
62
|
-
Question.
|
|
63
|
-
*********************************************************************-->
|
|
64
|
-
|
|
65
|
-
> Question
|
|
66
|
-
|
|
67
|
-
<!-- Ask your question here. Please be precise, adding as much detail
|
|
68
|
-
as necessary. Also, add a code snippet(s) if possible. -->
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
Mark whichever option below applies to this PR.
|
|
3
|
-
For example, if your PR passes all tests, you would mark the option as so:
|
|
4
|
-
- [x] All tests pass
|
|
5
|
-
Note the 'x' in between the square brackets '[]'
|
|
6
|
-
-->
|
|
7
|
-
- [ ] All tests pass
|
|
8
|
-
- [ ] I have run `npm run doc`
|
|
9
|
-
|
|
10
|
-
### Description
|
|
11
|
-
|
|
12
|
-
<!-- Explain what you are trying to achieve with this PR -->
|
|
13
|
-
|
|
14
|
-
### References
|
|
15
|
-
|
|
16
|
-
<!--
|
|
17
|
-
Add references to other documents/pages that are relevant to this
|
|
18
|
-
PR, such as related issues, documentation, etc.
|
|
19
|
-
|
|
20
|
-
For example,
|
|
21
|
-
* Issue #1: https://github.com/ZeroBot-net/tg-bot-api/issues/1
|
|
22
|
-
* Telegram Bot API - Getting updates: https://core.telegram.org/bots/api#getting-updates
|
|
23
|
-
-->
|