@skroz/telegram-bot 1.0.29 → 1.1.1

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