@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.
package/src/index.ts CHANGED
@@ -1,22 +1,37 @@
1
- import TelegramApi from '@skroz/telegram-api';
2
1
  import {
2
+ TG_AnswerPreCheckoutQueryInput,
3
+ TG_AnswerPreCheckoutQueryResponse,
3
4
  TG_BotCommand,
4
5
  TG_CallbackQuery,
5
6
  TG_ChatResponse,
7
+ TG_CreateInvoiceInput,
6
8
  TG_DeleteMessageInput,
9
+ TG_EditMessageCaptionInput,
10
+ TG_EditMessageMediaInput,
11
+ TG_EditMessageTextInput,
12
+ TG_GetFileResponse,
13
+ TG_GetMeResponse,
14
+ TG_GetUpdatesResponse,
7
15
  TG_Message,
8
16
  TG_PreCheckoutQuery,
9
17
  TG_SendActionInput,
18
+ TG_SendMediaGroupInput,
19
+ TG_SendMediaGroupResponse,
10
20
  TG_SendMessageInput,
11
21
  TG_SendMessageResponse,
12
22
  TG_SendPhotoInput,
23
+ TG_SendVideoInput,
13
24
  TG_SetMyCommandsResponse,
14
25
  TG_SuccessfulPayment,
15
26
  TG_Update,
16
- } from '@skroz/telegram-api/dist/TelegramTypes';
27
+ } from './TelegramTypes';
28
+ import ResultAndDescription from './ResultAndDescription';
17
29
 
18
30
  /* eslint-disable @typescript-eslint/naming-convention */
19
31
 
32
+ const TELEGRAM_MAX_TEXT_LENGTH = 4096; // Максимальная длина текста в телеграм сообщении
33
+ const TELEGRAM_MAX_CAPTION_LENGTH = 1024;
34
+
20
35
  /* хранит уникальные колбэки, чтобы исключить повторное нажатие */
21
36
  const processedCallbacks = new Set();
22
37
  /* разделитель для параметров колбэка */
@@ -101,64 +116,211 @@ type ExpiresInput = {
101
116
  onExpire: (resultMessage: TG_Message) => void;
102
117
  };
103
118
 
104
- /* Тип функции отправки в телеграм */
105
- /* type TelegramSendFunction<TInput, TResponse> = (
106
- input: TInput
107
- ) => Promise<TResponse>; */
108
-
109
- /* Тип типов функций отправки */
110
- /* type TG_SendFunction =
111
- | TelegramSendFunction<TG_SendMessageInput, TG_SendMessageResponse>
112
- | TelegramSendFunction<TG_SendPhotoInput, TG_SendMessageResponse>; */
113
- // в будущем:
114
- // | TelegramSendFunction<TG_SendDocumentInput, TG_SendDocumentResponse>
115
-
116
- // interface TelegramMessageHandler<TInput, TResponse> {
117
- // send: (input: TInput) => Promise<TResponse>;
118
- // }
119
-
120
119
  class TelegramBot {
121
120
  constructor(
122
121
  private readonly botToken: string,
123
- messages: {
124
- text?: Record<
125
- string,
126
- (input: TG_SendMessageInput) => TG_SendMessageInput
127
- >;
128
- photo?: Record<string, (input: TG_SendPhotoInput) => TG_SendPhotoInput>;
129
- },
130
122
  private readonly expiresMessageSeconds?: number,
131
123
  private readonly protect_content?: boolean
132
- ) {
133
- this.messages = {
134
- text: this.wrapTextMessages(messages.text ?? {}),
135
- photo: this.wrapPhotoMessages(messages.photo ?? {}),
124
+ ) {}
125
+
126
+ /* Отправка данных в Телеграм */
127
+ private async request<T extends { ok: boolean; description?: string }>(
128
+ method: string,
129
+ options: {
130
+ method?: 'GET' | 'POST';
131
+ body?: any;
132
+ queryParams?: Record<string, any>;
133
+ } = {}
134
+ ): Promise<T> {
135
+ let url = `https://api.telegram.org/bot${this.botToken}/${method}`;
136
+ if (options.queryParams) {
137
+ const params = new URLSearchParams();
138
+ Object.keys(options.queryParams).forEach((key) => {
139
+ const value = options.queryParams![key];
140
+ if (value !== undefined) {
141
+ params.append(key, String(value));
142
+ }
143
+ });
144
+ url += `?${params.toString()}`;
145
+ }
146
+
147
+ const fetchOptions: any = {
148
+ method: options.method || 'POST',
136
149
  };
150
+
151
+ if (options.body) {
152
+ fetchOptions.headers = {
153
+ 'Content-Type': 'application/json',
154
+ };
155
+ fetchOptions.body = JSON.stringify(options.body);
156
+ }
157
+
158
+ try {
159
+ const response = await fetch(url, fetchOptions);
160
+ const data = await response.json();
161
+ return data as T;
162
+ } catch (error: any) {
163
+ // console.error(`Error in ${method}:`, error.message);
164
+ return {
165
+ ok: false,
166
+ description: `Request failed: ${error.message}`,
167
+ } as unknown as T;
168
+ }
137
169
  }
138
170
 
139
- // публичный контейнер
140
- readonly messages: {
141
- text: Record<
142
- string,
171
+ async editMessageText(
172
+ input: TG_EditMessageTextInput
173
+ ): Promise<TG_SendMessageResponse> {
174
+ if (input.text && input.text.length > TELEGRAM_MAX_TEXT_LENGTH)
175
+ throw new Error(
176
+ `Текст сообщения не может превышать ${TELEGRAM_MAX_TEXT_LENGTH} символов`
177
+ );
178
+
179
+ return this.request<TG_SendMessageResponse>('editMessageText', {
180
+ body: input,
181
+ });
182
+ }
183
+
184
+ async editMessageCaption(
185
+ input: TG_EditMessageCaptionInput
186
+ ): Promise<TG_SendMessageResponse> {
187
+ if (input.caption && input.caption.length > TELEGRAM_MAX_CAPTION_LENGTH)
188
+ throw new Error(
189
+ `Подпись не может превышать ${TELEGRAM_MAX_CAPTION_LENGTH} символов`
190
+ );
191
+
192
+ return this.request<TG_SendMessageResponse>('editMessageCaption', {
193
+ body: input,
194
+ });
195
+ }
196
+
197
+ async sendVideo(input: TG_SendVideoInput): Promise<TG_SendMessageResponse> {
198
+ if (input.caption && input.caption.length > TELEGRAM_MAX_CAPTION_LENGTH)
199
+ throw new Error(
200
+ `Текст рядом с видео по правилам Телеграм не может превышать ${TELEGRAM_MAX_CAPTION_LENGTH} символов`
201
+ );
202
+
203
+ return this.request<TG_SendMessageResponse>('sendVideo', {
204
+ body: input,
205
+ });
206
+ }
207
+
208
+ async editMessageMedia(
209
+ input: TG_EditMessageMediaInput
210
+ ): Promise<TG_SendMessageResponse> {
211
+ return this.request<TG_SendMessageResponse>('editMessageMedia', {
212
+ body: input,
213
+ });
214
+ }
215
+
216
+ async sendMediaGroup(
217
+ input: TG_SendMediaGroupInput
218
+ ): Promise<TG_SendMediaGroupResponse> {
219
+ if (input.media.length < 2)
220
+ throw new Error('Минимум 2 фото для метода sendMediaGroup');
221
+
222
+ const { caption } = input.media[0];
223
+ if (caption && caption.length > TELEGRAM_MAX_CAPTION_LENGTH)
224
+ throw new Error(
225
+ `Текст рядом с фото по правилам Телеграм не может превышать ${TELEGRAM_MAX_CAPTION_LENGTH} символов`
226
+ );
227
+
228
+ return this.request<TG_SendMediaGroupResponse>('sendMediaGroup', {
229
+ body: input,
230
+ });
231
+ }
232
+
233
+ async getMe(): Promise<TG_GetMeResponse> {
234
+ return this.request<TG_GetMeResponse>('getMe', {
235
+ method: 'GET',
236
+ });
237
+ }
238
+
239
+ async setWebhook(webhookUrl: string): Promise<ResultAndDescription> {
240
+ return this.request<ResultAndDescription>('setWebhook', {
241
+ method: 'POST',
242
+ queryParams: { url: webhookUrl },
243
+ });
244
+ }
245
+
246
+ async setMyCommands(
247
+ commands: TG_BotCommand[]
248
+ ): Promise<TG_SetMyCommandsResponse> {
249
+ return this.request<TG_SetMyCommandsResponse>('setMyCommands', {
250
+ body: { commands },
251
+ });
252
+ }
253
+
254
+ async getUpdates(offset?: number): Promise<TG_GetUpdatesResponse> {
255
+ return this.request<TG_GetUpdatesResponse>('getUpdates', {
256
+ method: 'GET',
257
+ queryParams: offset ? { offset } : undefined,
258
+ });
259
+ }
260
+
261
+ async deleteWebhook(): Promise<void> {
262
+ const data = await this.request<{ ok: boolean; description?: string }>(
263
+ 'deleteWebhook',
143
264
  {
144
- send: (
145
- input: TG_SendMessageInput,
146
- message?: TG_Message,
147
- onExpire?: (resultMessage: TG_Message) => void
148
- ) => Promise<TG_SendMessageResponse>;
265
+ method: 'GET',
149
266
  }
150
- >;
151
- photo: Record<
152
- string,
267
+ );
268
+ if (data.ok) {
269
+ console.log('Вебхук успешно удален');
270
+ } else {
271
+ console.error('Ошибка при удалении вебхука:', data.description);
272
+ }
273
+ }
274
+
275
+ async sendChatAction(input: TG_SendActionInput): Promise<boolean> {
276
+ const data = await this.request<{ ok: boolean }>('sendChatAction', {
277
+ body: input,
278
+ });
279
+ return data.ok;
280
+ }
281
+
282
+ async getChat(chatId: string | number): Promise<TG_ChatResponse> {
283
+ return this.request<TG_ChatResponse>('getChat', {
284
+ body: { chat_id: chatId },
285
+ });
286
+ }
287
+
288
+ async getFile(fileId: string): Promise<TG_GetFileResponse> {
289
+ const data = await this.request<TG_GetFileResponse>('getFile', {
290
+ method: 'GET',
291
+ queryParams: { file_id: fileId },
292
+ });
293
+ if (!data.ok) {
294
+ throw new Error(`Failed to fetch file: ${data.description}`);
295
+ }
296
+ return data;
297
+ }
298
+
299
+ async deleteMessage(input: TG_DeleteMessageInput): Promise<boolean> {
300
+ const data = await this.request<{ ok: boolean }>('deleteMessage', {
301
+ body: input,
302
+ });
303
+ return data.ok;
304
+ }
305
+
306
+ async createInvoiceLink(
307
+ input: TG_CreateInvoiceInput
308
+ ): Promise<{ ok: boolean; result: string }> {
309
+ return this.request<{ ok: boolean; result: string }>('createInvoiceLink', {
310
+ body: input,
311
+ });
312
+ }
313
+
314
+ async answerPreCheckoutQuery(
315
+ input: TG_AnswerPreCheckoutQueryInput
316
+ ): Promise<TG_AnswerPreCheckoutQueryResponse> {
317
+ return this.request<TG_AnswerPreCheckoutQueryResponse>(
318
+ 'answerPreCheckoutQuery',
153
319
  {
154
- send: (
155
- input: TG_SendPhotoInput,
156
- message?: TG_Message,
157
- onExpire?: (resultMessage: TG_Message) => void
158
- ) => Promise<TG_SendMessageResponse>;
320
+ body: input,
159
321
  }
160
- >;
161
- };
322
+ );
323
+ }
162
324
 
163
325
  async handleUpdate(
164
326
  update: TG_Update,
@@ -208,14 +370,11 @@ class TelegramBot {
208
370
  preCheckoutQuery
209
371
  );
210
372
 
211
- await TelegramApi.answerPreCheckoutQuery(
212
- {
213
- pre_checkout_query_id: preCheckoutQuery.id,
214
- ok,
215
- error_message,
216
- },
217
- this.botToken
218
- );
373
+ await this.answerPreCheckoutQuery({
374
+ pre_checkout_query_id: preCheckoutQuery.id,
375
+ ok,
376
+ error_message,
377
+ });
219
378
  return;
220
379
  }
221
380
 
@@ -245,29 +404,30 @@ class TelegramBot {
245
404
  let sendResult: TG_SendMessageResponse;
246
405
 
247
406
  const sendPhotoFunction = () =>
248
- TelegramApi.sendPhoto(
249
- {
407
+ this.request<TG_SendMessageResponse>('sendPhoto', {
408
+ body: {
250
409
  protect_content: this.protect_content, // переписывается инпутом
251
410
  parse_mode: 'HTML',
252
411
  ...input,
253
412
  },
254
- this.botToken
255
- );
413
+ });
256
414
 
257
415
  if (message) {
258
416
  try {
259
- sendResult = await TelegramApi.editMessageMedia(
417
+ sendResult = await this.request<TG_SendMessageResponse>(
418
+ 'editMessageMedia',
260
419
  {
261
- media: {
262
- type: 'photo',
263
- media: input.photo as string,
264
- caption: input.caption,
265
- parse_mode: 'HTML',
420
+ body: {
421
+ media: {
422
+ type: 'photo',
423
+ media: input.photo as string,
424
+ caption: input.caption,
425
+ parse_mode: 'HTML',
426
+ },
427
+ ...input,
428
+ message_id: message.message_id,
266
429
  },
267
- ...input,
268
- message_id: message.message_id,
269
- },
270
- this.botToken
430
+ }
271
431
  );
272
432
  } catch (e) {
273
433
  sendResult = await sendPhotoFunction();
@@ -295,26 +455,30 @@ class TelegramBot {
295
455
  message?: TG_Message,
296
456
  onExpire?: (resultMessage: TG_Message) => void
297
457
  ): Promise<TG_SendMessageResponse> {
458
+ if (input.text.length > TELEGRAM_MAX_TEXT_LENGTH)
459
+ throw new Error(`Текст превышает ${TELEGRAM_MAX_TEXT_LENGTH} символов`);
460
+
298
461
  let sendResult: TG_SendMessageResponse;
299
462
 
300
463
  const sendPhotoFunction = () =>
301
- TelegramApi.sendMessage(
302
- {
464
+ this.request<TG_SendMessageResponse>('sendMessage', {
465
+ body: {
303
466
  parse_mode: 'HTML',
304
467
  protect_content: this.protect_content, // переписывается инпутом
305
468
  ...input,
306
469
  },
307
- this.botToken
308
- );
470
+ });
309
471
 
310
472
  if (message) {
311
473
  try {
312
- sendResult = await TelegramApi.editMessageText(
474
+ sendResult = await this.request<TG_SendMessageResponse>(
475
+ 'editMessageText',
313
476
  {
314
- message_id: message.message_id,
315
- ...input,
316
- },
317
- this.botToken
477
+ body: {
478
+ message_id: message.message_id,
479
+ ...input,
480
+ },
481
+ }
318
482
  );
319
483
  } catch (e) {
320
484
  sendResult = await sendPhotoFunction();
@@ -337,38 +501,12 @@ class TelegramBot {
337
501
  return sendResult;
338
502
  }
339
503
 
340
- async getUpdates(offset?: number) {
341
- return TelegramApi.getUpdates(this.botToken, offset);
342
- }
343
-
344
- async setWebhook(webhookUrl: string) {
345
- return TelegramApi.setWebhook(this.botToken, webhookUrl);
346
- }
347
-
348
- async deleteWebhook() {
349
- return TelegramApi.deleteWebhook(this.botToken);
350
- }
351
-
352
- async setMyCommands(
353
- commands: TG_BotCommand[]
354
- ): Promise<TG_SetMyCommandsResponse> {
355
- return TelegramApi.setMyCommands(commands, this.botToken);
356
- }
357
-
358
504
  async getChatInfo(telegramId: string | number): Promise<TG_ChatResponse> {
359
- return TelegramApi.getChat(telegramId, this.botToken);
360
- }
361
-
362
- async sendChatAction(input: TG_SendActionInput): Promise<boolean> {
363
- return TelegramApi.sendChatAction(input, this.botToken);
364
- }
365
-
366
- async deleteMessage(input: TG_DeleteMessageInput): Promise<boolean> {
367
- return TelegramApi.deleteMessage(input, this.botToken);
505
+ return this.getChat(telegramId);
368
506
  }
369
507
 
370
508
  // генераторы типовых методов
371
- private wrapTextMessages(
509
+ /* private wrapTextMessages(
372
510
  defs: Record<string, (input: TG_SendMessageInput) => TG_SendMessageInput>
373
511
  ) {
374
512
  const result: Record<
@@ -420,7 +558,7 @@ class TelegramBot {
420
558
  };
421
559
  });
422
560
  return result;
423
- }
561
+ } */
424
562
  }
425
563
 
426
564
  export default TelegramBot;