@skroz/telegram-bot 1.0.28 → 1.1.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.
@@ -0,0 +1,433 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const indexBackup = {};
4
+ exports.default = indexBackup;
5
+ /*
6
+ import TelegramApi from '@skroz/telegram-api';
7
+ import {
8
+ TG_BotCommand,
9
+ TG_CallbackQuery,
10
+ TG_ChatResponse,
11
+ TG_DeleteMessageInput,
12
+ TG_Message,
13
+ TG_PreCheckoutQuery,
14
+ TG_SendActionInput,
15
+ TG_SendMessageInput,
16
+ TG_SendMessageResponse,
17
+ TG_SendPhotoInput,
18
+ TG_SetMyCommandsResponse,
19
+ TG_SuccessfulPayment,
20
+ TG_Update,
21
+ } from '@skroz/telegram-api/dist/TelegramTypes';
22
+
23
+ /!* eslint-disable @typescript-eslint/naming-convention *!/
24
+
25
+ /!* хранит уникальные колбэки, чтобы исключить повторное нажатие *!/
26
+ const processedCallbacks = new Set();
27
+ /!* разделитель для параметров колбэка *!/
28
+ const callbackDivider = '^:^:^';
29
+ /!* по умолчанию истекающее сообщение *!/
30
+ const expiresSecondsDefault = 5 * 60; // 5 минут
31
+
32
+ /!* не больше 64 символов по документации, внутри защита от повторных кликов *!/
33
+ export const createCallbackData = (
34
+ telegramId: number | string,
35
+ data: (string | number)[]
36
+ ): string =>
37
+ [`${Date.now()}_${telegramId}`, ...data].join(callbackDivider).slice(0, 64);
38
+
39
+ /!* не больше 64 символов по документации, внутри защита от повторных кликов *!/
40
+ export const getCallbackData = (data: string): string[] =>
41
+ data.split(callbackDivider).slice(1);
42
+
43
+ const expiredMessagesList: {
44
+ message: TG_Message;
45
+ // user: TG_User;
46
+ expiresAt: number;
47
+ onExpire: (message: TG_Message) => void;
48
+ }[] = [];
49
+
50
+ const pushMessageToExpireList = (
51
+ message: TG_Message,
52
+ // user: TG_User,
53
+ expires: ExpiresInput
54
+ ) => {
55
+ const expiresAt =
56
+ Date.now() + (expires.seconds || expiresSecondsDefault) * 1000;
57
+
58
+ expiredMessagesList.push({
59
+ message,
60
+ // user,
61
+ expiresAt,
62
+ onExpire: expires.onExpire,
63
+ });
64
+ };
65
+
66
+ // забирает и сразу удаляет
67
+ const shiftExpiredMessages = (): {
68
+ message: TG_Message;
69
+ // user: TG_User;
70
+ // from: TG_User;
71
+ onExpire: (
72
+ message: TG_Message
73
+ // , user: TG_User
74
+ ) => void;
75
+ }[] => {
76
+ const now = Date.now();
77
+ const expired = expiredMessagesList.filter((msg) => msg.expiresAt <= now);
78
+ expiredMessagesList.splice(0, expired.length);
79
+ return expired;
80
+ };
81
+
82
+ // убираем из списка удаленные сообщения
83
+ const deleteMessageFromExpiringList = (message: TG_Message) => {
84
+ const index = expiredMessagesList.findIndex(
85
+ (msg) => msg.message.message_id === message.message_id
86
+ );
87
+ if (index !== -1) {
88
+ expiredMessagesList.splice(index, 1);
89
+ }
90
+ };
91
+
92
+ /!* обрабатываем истекшие сообщения по таймеру *!/
93
+ setInterval(async () => {
94
+ const expired = shiftExpiredMessages();
95
+ if (expired.length > 0) {
96
+ await Promise.all(
97
+ expired.map(async (ex) => {
98
+ await ex.onExpire(ex.message);
99
+ })
100
+ );
101
+ }
102
+ }, 15000);
103
+
104
+ type ExpiresInput = {
105
+ seconds?: number;
106
+ onExpire: (resultMessage: TG_Message) => void;
107
+ };
108
+
109
+ /!* Тип функции отправки в телеграм *!/
110
+ /!* type TelegramSendFunction<TInput, TResponse> = (
111
+ input: TInput
112
+ ) => Promise<TResponse>; *!/
113
+
114
+ /!* Тип типов функций отправки *!/
115
+ /!* type TG_SendFunction =
116
+ | TelegramSendFunction<TG_SendMessageInput, TG_SendMessageResponse>
117
+ | TelegramSendFunction<TG_SendPhotoInput, TG_SendMessageResponse>; *!/
118
+ // в будущем:
119
+ // | TelegramSendFunction<TG_SendDocumentInput, TG_SendDocumentResponse>
120
+
121
+ // interface TelegramMessageHandler<TInput, TResponse> {
122
+ // send: (input: TInput) => Promise<TResponse>;
123
+ // }
124
+
125
+ class TelegramBot {
126
+ constructor(
127
+ private readonly botToken: string,
128
+ /!* messages: {
129
+ text?: Record<
130
+ string,
131
+ (input: TG_SendMessageInput) => TG_SendMessageInput
132
+ >;
133
+ photo?: Record<string, (input: TG_SendPhotoInput) => TG_SendPhotoInput>;
134
+ }, *!/
135
+ private readonly expiresMessageSeconds?: number,
136
+ private readonly protect_content?: boolean
137
+ ) {
138
+ /!* this.messages = {
139
+ text: this.wrapTextMessages(messages.text ?? {}),
140
+ photo: this.wrapPhotoMessages(messages.photo ?? {}),
141
+ }; *!/
142
+ }
143
+
144
+ // публичный контейнер
145
+ /!* readonly messages: {
146
+ text: Record<
147
+ string,
148
+ {
149
+ send: (
150
+ input: TG_SendMessageInput,
151
+ message?: TG_Message,
152
+ onExpire?: (resultMessage: TG_Message) => void
153
+ ) => Promise<TG_SendMessageResponse>;
154
+ }
155
+ >;
156
+ photo: Record<
157
+ string,
158
+ {
159
+ send: (
160
+ input: TG_SendPhotoInput,
161
+ message?: TG_Message,
162
+ onExpire?: (resultMessage: TG_Message) => void
163
+ ) => Promise<TG_SendMessageResponse>;
164
+ }
165
+ >;
166
+ }; *!/
167
+
168
+ async handleUpdate(
169
+ update: TG_Update,
170
+ handleCallback: (
171
+ callbackQuery: TG_CallbackQuery,
172
+ callbackDataParams: string[]
173
+ ) => Promise<void>,
174
+ handleMessage: (message: TG_Message) => void,
175
+ handlePreCheckoutQuery: (
176
+ preCheckoutQuery: TG_PreCheckoutQuery
177
+ ) => Promise<{ ok: boolean; error_message?: string }>,
178
+ handleSuccessfulPayment: (
179
+ successfulPayment: TG_SuccessfulPayment,
180
+ message: TG_Message
181
+ ) => Promise<void>
182
+ ): Promise<void> {
183
+ const callbackQuery = update.callback_query;
184
+ if (callbackQuery) {
185
+ const { data } = callbackQuery;
186
+
187
+ if (!data) {
188
+ // 1. Коллбэк пришел от WebApp
189
+ // 2. Игровая кнопка
190
+ // 3. Кнопка с удаленным сообщением
191
+ return; // todo sendError
192
+ }
193
+
194
+ // data - всегда уникален, потому что в начале стоит текущий Date.now()
195
+ if (processedCallbacks.has(data)) {
196
+ // чтобы избежать дублирования нажатия на кнопку
197
+ return; // Уже обработан
198
+ }
199
+ processedCallbacks.add(data);
200
+
201
+ // Удаляем из памяти через 60 секунд
202
+ setTimeout(() => {
203
+ processedCallbacks.delete(data);
204
+ }, 60 * 1000);
205
+
206
+ await handleCallback(callbackQuery, getCallbackData(data));
207
+ }
208
+ /!* Часть оплаты *!/
209
+ const preCheckoutQuery = update.pre_checkout_query;
210
+ if (preCheckoutQuery) {
211
+ /!* Нужно ответить в течение 10 секунд внутри метода или заказ отменяется автоматически *!/
212
+ const { ok, error_message } = await handlePreCheckoutQuery(
213
+ preCheckoutQuery
214
+ );
215
+
216
+ await TelegramApi.answerPreCheckoutQuery(
217
+ {
218
+ pre_checkout_query_id: preCheckoutQuery.id,
219
+ ok,
220
+ error_message,
221
+ },
222
+ this.botToken
223
+ );
224
+ return;
225
+ }
226
+
227
+ /!* обработаем сообщение *!/
228
+ const { message } = update; // его нет, если это нажатие на кнопку callback_query или при редактировании предыдущего сообщения
229
+
230
+ if (message) {
231
+ const { successful_payment } = message;
232
+ if (successful_payment) {
233
+ /!* Успешная оплата *!/
234
+ await handleSuccessfulPayment(successful_payment, message);
235
+ return;
236
+ }
237
+
238
+ if (!callbackQuery) {
239
+ /!* разбираем входящий текст *!/
240
+ await handleMessage(message);
241
+ }
242
+ }
243
+ }
244
+
245
+ async sendPhoto(
246
+ input: TG_SendPhotoInput,
247
+ message?: TG_Message,
248
+ onExpire?: (resultMessage: TG_Message) => void
249
+ ): Promise<TG_SendMessageResponse> {
250
+ let sendResult: TG_SendMessageResponse;
251
+
252
+ const sendPhotoFunction = () =>
253
+ TelegramApi.sendPhoto(
254
+ {
255
+ protect_content: this.protect_content, // переписывается инпутом
256
+ parse_mode: 'HTML',
257
+ ...input,
258
+ },
259
+ this.botToken
260
+ );
261
+
262
+ if (message) {
263
+ try {
264
+ sendResult = await TelegramApi.editMessageMedia(
265
+ {
266
+ media: {
267
+ type: 'photo',
268
+ media: input.photo as string,
269
+ caption: input.caption,
270
+ parse_mode: 'HTML',
271
+ },
272
+ ...input,
273
+ message_id: message.message_id,
274
+ },
275
+ this.botToken
276
+ );
277
+ } catch (e) {
278
+ sendResult = await sendPhotoFunction();
279
+ }
280
+ } else {
281
+ sendResult = await sendPhotoFunction();
282
+ }
283
+
284
+ if (sendResult.ok && sendResult.result) {
285
+ if (onExpire) {
286
+ pushMessageToExpireList(sendResult.result, {
287
+ seconds: this.expiresMessageSeconds,
288
+ onExpire,
289
+ });
290
+ } else {
291
+ deleteMessageFromExpiringList(sendResult.result);
292
+ }
293
+ }
294
+
295
+ return sendResult;
296
+ }
297
+
298
+ async sendMessage(
299
+ input: TG_SendMessageInput,
300
+ message?: TG_Message,
301
+ onExpire?: (resultMessage: TG_Message) => void
302
+ ): Promise<TG_SendMessageResponse> {
303
+ let sendResult: TG_SendMessageResponse;
304
+
305
+ const sendPhotoFunction = () =>
306
+ TelegramApi.sendMessage(
307
+ {
308
+ parse_mode: 'HTML',
309
+ protect_content: this.protect_content, // переписывается инпутом
310
+ ...input,
311
+ },
312
+ this.botToken
313
+ );
314
+
315
+ if (message) {
316
+ try {
317
+ sendResult = await TelegramApi.editMessageText(
318
+ {
319
+ message_id: message.message_id,
320
+ ...input,
321
+ },
322
+ this.botToken
323
+ );
324
+ } catch (e) {
325
+ sendResult = await sendPhotoFunction();
326
+ }
327
+ } else {
328
+ sendResult = await sendPhotoFunction();
329
+ }
330
+
331
+ if (sendResult.ok && sendResult.result) {
332
+ if (onExpire) {
333
+ pushMessageToExpireList(sendResult.result, {
334
+ seconds: this.expiresMessageSeconds,
335
+ onExpire,
336
+ });
337
+ } else {
338
+ deleteMessageFromExpiringList(sendResult.result);
339
+ }
340
+ }
341
+
342
+ return sendResult;
343
+ }
344
+
345
+ async getUpdates(offset?: number) {
346
+ return TelegramApi.getUpdates(this.botToken, offset);
347
+ }
348
+
349
+ async setWebhook(webhookUrl: string) {
350
+ return TelegramApi.setWebhook(this.botToken, webhookUrl);
351
+ }
352
+
353
+ async deleteWebhook() {
354
+ return TelegramApi.deleteWebhook(this.botToken);
355
+ }
356
+
357
+ async setMyCommands(
358
+ commands: TG_BotCommand[]
359
+ ): Promise<TG_SetMyCommandsResponse> {
360
+ return TelegramApi.setMyCommands(commands, this.botToken);
361
+ }
362
+
363
+ async getChatInfo(telegramId: string | number): Promise<TG_ChatResponse> {
364
+ return TelegramApi.getChat(telegramId, this.botToken);
365
+ }
366
+
367
+ async sendChatAction(input: TG_SendActionInput): Promise<boolean> {
368
+ return TelegramApi.sendChatAction(input, this.botToken);
369
+ }
370
+
371
+ async deleteMessage(input: TG_DeleteMessageInput): Promise<boolean> {
372
+ return TelegramApi.deleteMessage(input, this.botToken);
373
+ }
374
+
375
+ // генераторы типовых методов
376
+ /!* private wrapTextMessages(
377
+ defs: Record<string, (input: TG_SendMessageInput) => TG_SendMessageInput>
378
+ ) {
379
+ const result: Record<
380
+ string,
381
+ {
382
+ send: (
383
+ input: TG_SendMessageInput,
384
+ message?: TG_Message,
385
+ onExpire?: (m: TG_Message) => void
386
+ ) => Promise<TG_SendMessageResponse>;
387
+ }
388
+ > = {};
389
+
390
+ Object.entries(defs).forEach(([key, builder]) => {
391
+ result[key] = {
392
+ send: (input, message, onExpire) =>
393
+ this.sendMessage(
394
+ builder(input ?? ({} as TG_SendMessageInput)),
395
+ message,
396
+ onExpire
397
+ ),
398
+ };
399
+ });
400
+ return result;
401
+ }
402
+
403
+ private wrapPhotoMessages(
404
+ defs: Record<string, (input: TG_SendPhotoInput) => TG_SendPhotoInput>
405
+ ) {
406
+ const result: Record<
407
+ string,
408
+ {
409
+ send: (
410
+ input: TG_SendPhotoInput,
411
+ message?: TG_Message,
412
+ onExpire?: (m: TG_Message) => void
413
+ ) => Promise<TG_SendMessageResponse>;
414
+ }
415
+ > = {};
416
+
417
+ Object.entries(defs).forEach(([key, builder]) => {
418
+ result[key] = {
419
+ send: (input, message, onExpire) =>
420
+ this.sendPhoto(
421
+ builder(input ?? ({} as TG_SendPhotoInput)),
422
+ message,
423
+ onExpire
424
+ ),
425
+ };
426
+ });
427
+ return result;
428
+ } *!/
429
+ }
430
+
431
+ export default TelegramBot;
432
+ */
433
+ //# sourceMappingURL=index_backup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index_backup.js","sourceRoot":"","sources":["../src/index_backup.ts"],"names":[],"mappings":";;AAAA,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,kBAAe,WAAW,CAAC;AAC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2aE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skroz/telegram-bot",
3
- "version": "1.0.28",
3
+ "version": "1.1.0",
4
4
  "license": "MIT",
5
5
  "repository": "git@gitlab.com:skroz/libs/utils.git",
6
6
  "main": "dist/index.js",
@@ -18,8 +18,5 @@
18
18
  "publishConfig": {
19
19
  "access": "public"
20
20
  },
21
- "dependencies": {
22
- "@skroz/telegram-api": "^1.0.14"
23
- },
24
- "gitHead": "0c39573cfdd1a4bcb4666b8ce6dbda86d12e4e08"
21
+ "gitHead": "c1fdf97b9d0f1755497f1ce8fa4eeb59b61e2e1a"
25
22
  }
@@ -0,0 +1,13 @@
1
+ import { Readable } from 'stream';
2
+
3
+ interface Options {
4
+ encoding?: string;
5
+ highWaterMark?: number;
6
+ }
7
+
8
+ export interface FileUpload {
9
+ filename: string;
10
+ mimetype: string;
11
+ encoding: string;
12
+ createReadStream: (options?: Options) => Readable;
13
+ }
@@ -0,0 +1,7 @@
1
+ class ResultAndDescription {
2
+ ok!: boolean;
3
+
4
+ description?: string;
5
+ }
6
+
7
+ export default ResultAndDescription;