@zero-bot.net/tg-bot-api 1.3.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 +33 -3
- package/src/telegram.js +140 -23
- 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 -5262
- package/lib/telegram.js +0 -4179
- package/lib/telegramPolling.js +0 -196
- package/lib/telegramWebHook.js +0 -156
- package/lib/utils.js +0 -3
package/src/telegram.d.ts
CHANGED
|
@@ -2688,6 +2688,16 @@ export interface TelegramBotOptions {
|
|
|
2688
2688
|
filepath?: boolean;
|
|
2689
2689
|
/** Set to true for forward-compatibility on unhandled rejections. */
|
|
2690
2690
|
badRejection?: boolean;
|
|
2691
|
+
/**
|
|
2692
|
+
* Apply latency-oriented network tuning at construction (prefer IPv4 and
|
|
2693
|
+
* shorten Node's IPv6 fallback window). Mutates process-global DNS settings.
|
|
2694
|
+
*/
|
|
2695
|
+
ipv4First?: boolean;
|
|
2696
|
+
/**
|
|
2697
|
+
* Warm up the connection (DNS/TCP/TLS) with a `getMe()` call at startup so
|
|
2698
|
+
* the first real request is not a cold start.
|
|
2699
|
+
*/
|
|
2700
|
+
prewarm?: boolean;
|
|
2691
2701
|
}
|
|
2692
2702
|
|
|
2693
2703
|
/** Polling options. */
|
|
@@ -3170,6 +3180,14 @@ export default class TelegramBot extends EventEmitter {
|
|
|
3170
3180
|
|
|
3171
3181
|
/** The types of message updates the library handles. */
|
|
3172
3182
|
static messageTypes: string[];
|
|
3183
|
+
/**
|
|
3184
|
+
* Apply latency-oriented network tuning (prefer IPv4, shorten Node's IPv6
|
|
3185
|
+
* fallback window). Mutates process-global settings — call once at startup.
|
|
3186
|
+
*/
|
|
3187
|
+
static applyNetworkTuning(options?: {
|
|
3188
|
+
ipv4First?: boolean;
|
|
3189
|
+
autoSelectFamilyAttemptTimeout?: number;
|
|
3190
|
+
}): { ipv4First: boolean; autoSelectFamilyAttemptTimeout: number };
|
|
3173
3191
|
|
|
3174
3192
|
// --- Event Overloads --------------------------------------------------------
|
|
3175
3193
|
|
|
@@ -3362,6 +3380,11 @@ export default class TelegramBot extends EventEmitter {
|
|
|
3362
3380
|
|
|
3363
3381
|
/** A simple method for testing your bot's authentication token. */
|
|
3364
3382
|
getMe(form?: FormQueryOptions): Promise<User>;
|
|
3383
|
+
/**
|
|
3384
|
+
* Warm up the connection (DNS/TCP/TLS) with a lightweight `getMe()` call so
|
|
3385
|
+
* the first real request is not a cold start. Resolves with the bot instance.
|
|
3386
|
+
*/
|
|
3387
|
+
preheat(options?: { suppressErrors?: boolean }): Promise<this>;
|
|
3365
3388
|
|
|
3366
3389
|
/** Log out from the cloud Bot API server. */
|
|
3367
3390
|
logOut(form?: FormQueryOptions): Promise<boolean>;
|
|
@@ -4999,10 +5022,17 @@ export default class TelegramBot extends EventEmitter {
|
|
|
4999
5022
|
answerGuestQuery(guestQueryId: string, text: string, form?: FormQueryOptions): Promise<boolean>;
|
|
5000
5023
|
|
|
5001
5024
|
/**
|
|
5002
|
-
* Remove
|
|
5003
|
-
*
|
|
5025
|
+
* Remove up to 10000 recent reactions in a group or supergroup added by a
|
|
5026
|
+
* given user or chat.
|
|
5027
|
+
*
|
|
5028
|
+
* Pass the user id positionally, or an options object with `user_id` and/or
|
|
5029
|
+
* `actor_chat_id`.
|
|
5030
|
+
* @see https://core.telegram.org/bots/api#deleteallmessagereactions
|
|
5004
5031
|
*/
|
|
5005
|
-
deleteAllMessageReactions(
|
|
5032
|
+
deleteAllMessageReactions(
|
|
5033
|
+
chatId: number | string,
|
|
5034
|
+
userIdOrOptions?: number | string | FormQueryOptions,
|
|
5035
|
+
): Promise<boolean>;
|
|
5006
5036
|
|
|
5007
5037
|
/**
|
|
5008
5038
|
* Remove a reaction from a message.
|
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');
|
|
@@ -224,6 +233,13 @@ class TelegramBot extends EventEmitter {
|
|
|
224
233
|
* **if and only if** the Node.js version you're using terminates the
|
|
225
234
|
* process on unhandled rejections. This option is only for
|
|
226
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.
|
|
227
243
|
* @see https://core.telegram.org/bots/api
|
|
228
244
|
*/
|
|
229
245
|
constructor(token, options = {}) {
|
|
@@ -235,12 +251,17 @@ class TelegramBot extends EventEmitter {
|
|
|
235
251
|
this.options.baseApiUrl = options.baseApiUrl || 'https://api.telegram.org';
|
|
236
252
|
this.options.filepath = (typeof options.filepath === 'undefined') ? true : options.filepath;
|
|
237
253
|
this.options.badRejection = (typeof options.badRejection === 'undefined') ? false : options.badRejection;
|
|
254
|
+
this.options.request = Object.assign({}, options.request);
|
|
238
255
|
this._textRegexpCallbacks = [];
|
|
239
256
|
this._replyListenerId = 0;
|
|
240
257
|
this._replyListeners = [];
|
|
241
258
|
this._polling = null;
|
|
242
259
|
this._webHook = null;
|
|
243
260
|
|
|
261
|
+
if (options.ipv4First) {
|
|
262
|
+
applyNetworkTuning({ ipv4First: true });
|
|
263
|
+
}
|
|
264
|
+
|
|
244
265
|
if (options.polling) {
|
|
245
266
|
const autoStart = options.polling.autoStart;
|
|
246
267
|
if (typeof autoStart === 'undefined' || autoStart === true) {
|
|
@@ -254,6 +275,23 @@ class TelegramBot extends EventEmitter {
|
|
|
254
275
|
this.openWebHook();
|
|
255
276
|
}
|
|
256
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);
|
|
257
295
|
}
|
|
258
296
|
|
|
259
297
|
/**
|
|
@@ -358,7 +396,13 @@ class TelegramBot extends EventEmitter {
|
|
|
358
396
|
'link_preview_options',
|
|
359
397
|
];
|
|
360
398
|
for (const field of jsonFields) {
|
|
361
|
-
|
|
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') {
|
|
362
406
|
obj[field] = stringify(obj[field]);
|
|
363
407
|
}
|
|
364
408
|
}
|
|
@@ -398,13 +442,14 @@ class TelegramBot extends EventEmitter {
|
|
|
398
442
|
options.resolveWithFullResponse = true;
|
|
399
443
|
options.forever = true;
|
|
400
444
|
debug('HTTP request: %j', options);
|
|
401
|
-
|
|
445
|
+
const raw = request(options);
|
|
446
|
+
const promise = raw
|
|
402
447
|
.then(resp => {
|
|
403
448
|
let data;
|
|
404
449
|
try {
|
|
405
450
|
data = resp.body = JSON.parse(resp.body);
|
|
406
|
-
} catch (
|
|
407
|
-
throw new errors.ParseError(`Error parsing response: ${
|
|
451
|
+
} catch (parseError) {
|
|
452
|
+
throw new errors.ParseError(`Error parsing response: ${parseError.message}`, resp);
|
|
408
453
|
}
|
|
409
454
|
|
|
410
455
|
if (data.ok) {
|
|
@@ -417,6 +462,13 @@ class TelegramBot extends EventEmitter {
|
|
|
417
462
|
if (error.response) throw error;
|
|
418
463
|
throw new errors.FatalError(error);
|
|
419
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;
|
|
420
472
|
}
|
|
421
473
|
|
|
422
474
|
/**
|
|
@@ -457,7 +509,7 @@ class TelegramBot extends EventEmitter {
|
|
|
457
509
|
filename = 'data';
|
|
458
510
|
}
|
|
459
511
|
if (!contentType) {
|
|
460
|
-
const filetype =
|
|
512
|
+
const filetype = detectFileType(data);
|
|
461
513
|
if (filetype) {
|
|
462
514
|
contentType = filetype.mime;
|
|
463
515
|
const ext = filetype.ext;
|
|
@@ -483,7 +535,7 @@ class TelegramBot extends EventEmitter {
|
|
|
483
535
|
}
|
|
484
536
|
|
|
485
537
|
filename = filename || 'filename';
|
|
486
|
-
contentType = contentType ||
|
|
538
|
+
contentType = contentType || lookupMime(filename);
|
|
487
539
|
if (process.env.NTBA_FIX_350) {
|
|
488
540
|
contentType = contentType || 'application/octet-stream';
|
|
489
541
|
} else {
|
|
@@ -1057,6 +1109,34 @@ class TelegramBot extends EventEmitter {
|
|
|
1057
1109
|
return this._request('getMe', { form });
|
|
1058
1110
|
}
|
|
1059
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
|
+
|
|
1060
1140
|
/**
|
|
1061
1141
|
* This method log out your bot from the cloud Bot API server before launching the bot locally.
|
|
1062
1142
|
* You must log out the bot before running it locally, otherwise there is no guarantee that the bot will receive updates.
|
|
@@ -1131,7 +1211,7 @@ class TelegramBot extends EventEmitter {
|
|
|
1131
1211
|
forwardMessages(chatId, fromChatId, messageIds, form = {}) {
|
|
1132
1212
|
form.chat_id = chatId;
|
|
1133
1213
|
form.from_chat_id = fromChatId;
|
|
1134
|
-
form.message_ids = messageIds;
|
|
1214
|
+
form.message_ids = stringify(messageIds);
|
|
1135
1215
|
return this._request('forwardMessages', { form });
|
|
1136
1216
|
}
|
|
1137
1217
|
|
|
@@ -4022,14 +4102,15 @@ class TelegramBot extends EventEmitter {
|
|
|
4022
4102
|
* Use this method to remove multiple reactions from a message.
|
|
4023
4103
|
*
|
|
4024
4104
|
* @param {Number|String} chatId Unique identifier for the target chat or username of the target channel (in the format `@channelusername`)
|
|
4025
|
-
* @param {Number}
|
|
4026
|
-
* @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`)
|
|
4027
4106
|
* @return {Promise} True on success
|
|
4028
|
-
* @see https://core.telegram.org/bots/api#
|
|
4107
|
+
* @see https://core.telegram.org/bots/api#deleteallmessagereactions
|
|
4029
4108
|
*/
|
|
4030
|
-
deleteAllMessageReactions(chatId,
|
|
4109
|
+
deleteAllMessageReactions(chatId, options = {}) {
|
|
4110
|
+
const form = (typeof options === 'number' || typeof options === 'string')
|
|
4111
|
+
? { user_id: options }
|
|
4112
|
+
: options;
|
|
4031
4113
|
form.chat_id = chatId;
|
|
4032
|
-
form.message_id = messageId;
|
|
4033
4114
|
return this._request('deleteAllMessageReactions', { form });
|
|
4034
4115
|
}
|
|
4035
4116
|
|
|
@@ -4224,12 +4305,48 @@ class TelegramBot extends EventEmitter {
|
|
|
4224
4305
|
* @return {Promise} On success, the edited Message object is returned
|
|
4225
4306
|
* @see https://core.telegram.org/bots/api#editephemeralmessagemedia
|
|
4226
4307
|
*/
|
|
4227
|
-
editEphemeralMessageMedia(chatId, ephemeralMessageId, media, form = {}
|
|
4308
|
+
editEphemeralMessageMedia(chatId, ephemeralMessageId, media, form = {}) {
|
|
4228
4309
|
if (!chatId) return Promise.reject(new Error('chatId is required'));
|
|
4229
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
|
+
|
|
4230
4346
|
form.chat_id = chatId;
|
|
4231
4347
|
form.ephemeral_message_id = ephemeralMessageId;
|
|
4232
4348
|
form.media = stringify(media);
|
|
4349
|
+
|
|
4233
4350
|
return this._request('editEphemeralMessageMedia', { form });
|
|
4234
4351
|
}
|
|
4235
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
|
-
-->
|