@elcrm/tb 0.0.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/README.md +523 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.es.js +180 -0
- package/dist/index.umd.js +203 -0
- package/package.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
# @elcrm/tb - Telegram Bot библиотека
|
|
2
|
+
|
|
3
|
+
Библиотека для работы с Telegram Bot API на основе Bun runtime. Предоставляет простой и удобный интерфейс для создания Telegram ботов без использования классов.
|
|
4
|
+
|
|
5
|
+
## Особенности
|
|
6
|
+
|
|
7
|
+
- 🚀 Простое использование без создания экземпляров
|
|
8
|
+
- 🔧 Автоматическое использование токена из переменных окружения
|
|
9
|
+
- 📦 Типизированный API с полной поддержкой TypeScript
|
|
10
|
+
- ⚡ Ленивая инициализация - экземпляр создается при первом использовании
|
|
11
|
+
- 🎯 Поддержка webhook для получения обновлений
|
|
12
|
+
- 🔐 Валидация данных Telegram WebApp
|
|
13
|
+
- ⏱️ Автоматическое ограничение скорости - максимум 30 запросов в секунду
|
|
14
|
+
- 📋 Очередь запросов - все запросы автоматически ставятся в очередь и обрабатываются последовательно
|
|
15
|
+
|
|
16
|
+
## Установка
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @elcrm/tb
|
|
20
|
+
# или
|
|
21
|
+
bun add @elcrm/tb
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Быстрый старт
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { tb } from "@elcrm/tb";
|
|
28
|
+
|
|
29
|
+
// Использование напрямую без создания экземпляра
|
|
30
|
+
// Токен автоматически берется из process.env.TELEGRAM
|
|
31
|
+
|
|
32
|
+
// Регистрация обработчика сообщений
|
|
33
|
+
tb.onMessage(async (update) => {
|
|
34
|
+
if (update.message?.text) {
|
|
35
|
+
const chatId = update.message.from!.id;
|
|
36
|
+
await tb.sendMessage(chatId, `Вы написали: ${update.message.text}`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Использование webhook
|
|
41
|
+
const routes = tb.webhook("telegram-webhook");
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API Документация
|
|
45
|
+
|
|
46
|
+
### `tb` - Объект Telegram Bot
|
|
47
|
+
|
|
48
|
+
Глобальный объект для работы с Telegram Bot API. Токен автоматически берется из переменной окружения `process.env.TELEGRAM`.
|
|
49
|
+
|
|
50
|
+
**Использование:**
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { tb } from "@elcrm/tb";
|
|
54
|
+
|
|
55
|
+
// Использование напрямую без создания экземпляра
|
|
56
|
+
tb.onMessage(async (update) => {
|
|
57
|
+
// обработка сообщений
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
tb.sendMessage(123456789, "Привет!");
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Примечание:** Если нужно создать несколько экземпляров с разными токенами, используйте функцию `createTelegramBot(token)`:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { createTelegramBot } from "@elcrm/tb";
|
|
67
|
+
|
|
68
|
+
const bot1 = createTelegramBot("TOKEN_1");
|
|
69
|
+
const bot2 = createTelegramBot("TOKEN_2");
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
### Методы TelegramBot
|
|
75
|
+
|
|
76
|
+
#### `onMessage(callback: (update: TelegramUpdate) => void | Promise<void>): void`
|
|
77
|
+
|
|
78
|
+
Регистрирует обработчик для всех входящих сообщений.
|
|
79
|
+
|
|
80
|
+
**Параметры:**
|
|
81
|
+
|
|
82
|
+
- `callback` - Функция-обработчик, которая вызывается при получении сообщения
|
|
83
|
+
|
|
84
|
+
**Пример:**
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
tb.onMessage(async (update) => {
|
|
88
|
+
if (update.message?.text === "/start") {
|
|
89
|
+
const chatId = update.message.from!.id;
|
|
90
|
+
await tb.sendMessage(chatId, "Добро пожаловать!");
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
#### `onCallbackQuery(name: string, callback: (data: CallbackQueryData) => void | Promise<void>): void`
|
|
98
|
+
|
|
99
|
+
Регистрирует обработчик для callback query (нажатие на inline-кнопки).
|
|
100
|
+
|
|
101
|
+
**Параметры:**
|
|
102
|
+
|
|
103
|
+
- `name` (string) - Имя обработчика, должно совпадать с `data` кнопки
|
|
104
|
+
- `callback` - Функция-обработчик, вызываемая при нажатии на кнопку
|
|
105
|
+
|
|
106
|
+
**Пример:**
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Регистрация обработчика
|
|
110
|
+
tb.onCallbackQuery("button_click", async (data) => {
|
|
111
|
+
const chatId = data.from.id;
|
|
112
|
+
await tb.sendMessage(chatId, "Кнопка нажата!");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Отправка сообщения с inline-кнопкой
|
|
116
|
+
await tb.sendMessage(chatId, "Нажмите кнопку", {
|
|
117
|
+
inline_keyboard: [[{ text: "Нажми меня", callback_data: "button_click" }]],
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
#### `sendMessage(chat_id: number, text: string, reply_markup?: ReplyMarkup): Promise<any>`
|
|
124
|
+
|
|
125
|
+
Отправляет текстовое сообщение пользователю.
|
|
126
|
+
|
|
127
|
+
**Параметры:**
|
|
128
|
+
|
|
129
|
+
- `chat_id` (number) - ID чата или пользователя
|
|
130
|
+
- `text` (string) - Текст сообщения (поддерживает HTML)
|
|
131
|
+
- `reply_markup` (ReplyMarkup, опционально) - Клавиатура или inline-кнопки
|
|
132
|
+
|
|
133
|
+
**Пример:**
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// Простое сообщение
|
|
137
|
+
await tb.sendMessage(123456789, "Привет!");
|
|
138
|
+
|
|
139
|
+
// Сообщение с inline-кнопками
|
|
140
|
+
await tb.sendMessage(123456789, "Выберите действие:", {
|
|
141
|
+
inline_keyboard: [
|
|
142
|
+
[{ text: "Кнопка 1", callback_data: "action1" }],
|
|
143
|
+
[{ text: "Кнопка 2", callback_data: "action2" }],
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Сообщение с обычной клавиатурой
|
|
148
|
+
await tb.sendMessage(123456789, "Нажмите кнопку:", {
|
|
149
|
+
keyboard: [[{ text: "Отправить контакт", request_contact: true }]],
|
|
150
|
+
resize_keyboard: true,
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
#### `sendPhoto(chat_id: number, photo: string, caption?: string, reply_markup?: ReplyMarkup): Promise<any>`
|
|
157
|
+
|
|
158
|
+
Отправляет фотографию пользователю.
|
|
159
|
+
|
|
160
|
+
**Параметры:**
|
|
161
|
+
|
|
162
|
+
- `chat_id` (number) - ID чата или пользователя
|
|
163
|
+
- `photo` (string) - URL или file_id фотографии
|
|
164
|
+
- `caption` (string, опционально) - Подпись к фотографии
|
|
165
|
+
- `reply_markup` (ReplyMarkup, опционально) - Клавиатура или inline-кнопки
|
|
166
|
+
|
|
167
|
+
**Пример:**
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
await tb.sendPhoto(
|
|
171
|
+
123456789,
|
|
172
|
+
"https://example.com/image.jpg",
|
|
173
|
+
"Описание фотографии",
|
|
174
|
+
{
|
|
175
|
+
inline_keyboard: [[{ text: "Подробнее", callback_data: "photo_info" }]],
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
#### `editMessageText(chatId: number, messageId: number, text: string, replyMarkup?: ReplyMarkup): Promise<any>`
|
|
183
|
+
|
|
184
|
+
Редактирует текст существующего сообщения.
|
|
185
|
+
|
|
186
|
+
**Параметры:**
|
|
187
|
+
|
|
188
|
+
- `chatId` (number) - ID чата
|
|
189
|
+
- `messageId` (number) - ID сообщения для редактирования
|
|
190
|
+
- `text` (string) - Новый текст сообщения
|
|
191
|
+
- `replyMarkup` (ReplyMarkup, опционально) - Новая клавиатура или inline-кнопки
|
|
192
|
+
|
|
193
|
+
**Пример:**
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
tb.onCallbackQuery("edit_message", async (data) => {
|
|
197
|
+
await tb.editMessageText(
|
|
198
|
+
data.message.chat.id,
|
|
199
|
+
data.message.message_id,
|
|
200
|
+
"Сообщение обновлено!"
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
#### `answerCallbackQuery(callbackId: string, text?: string): Promise<any>`
|
|
208
|
+
|
|
209
|
+
Отвечает на callback query. Вызывается автоматически при обработке callback_query, но можно вызвать вручную.
|
|
210
|
+
|
|
211
|
+
**Параметры:**
|
|
212
|
+
|
|
213
|
+
- `callbackId` (string) - ID callback query
|
|
214
|
+
- `text` (string, опционально) - Текст уведомления (если указан, показывается alert)
|
|
215
|
+
|
|
216
|
+
**Пример:**
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
tb.onCallbackQuery("show_alert", async (data) => {
|
|
220
|
+
await tb.answerCallbackQuery(data.id, "Это уведомление!");
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
#### `setWebhook(url: string): Promise<any>`
|
|
227
|
+
|
|
228
|
+
Устанавливает webhook URL для получения обновлений от Telegram.
|
|
229
|
+
|
|
230
|
+
**Параметры:**
|
|
231
|
+
|
|
232
|
+
- `url` (string) - HTTPS URL для получения обновлений
|
|
233
|
+
|
|
234
|
+
**Пример:**
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
const result = await tb.setWebhook("https://example.com/webhook");
|
|
238
|
+
console.log(result);
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
#### `webhook(router: string): Record<string, (req: Request) => Promise<Response>>`
|
|
244
|
+
|
|
245
|
+
Создает роутер для обработки webhook запросов от Telegram.
|
|
246
|
+
|
|
247
|
+
**Параметры:**
|
|
248
|
+
|
|
249
|
+
- `router` (string) - Путь для webhook (например, 'telegram-webhook')
|
|
250
|
+
|
|
251
|
+
**Возвращает:** Объект с методом для обработки запросов
|
|
252
|
+
|
|
253
|
+
**Пример использования с Bun:**
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// Регистрация обработчиков
|
|
257
|
+
tb.onMessage(async (update) => {
|
|
258
|
+
// Обработка сообщений
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
tb.onCallbackQuery("action", async (data) => {
|
|
262
|
+
// Обработка callback query
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Создание роутера
|
|
266
|
+
const routes = tb.webhook("telegram-webhook");
|
|
267
|
+
|
|
268
|
+
// Использование в Bun server
|
|
269
|
+
Bun.serve({
|
|
270
|
+
port: 3000,
|
|
271
|
+
fetch(req) {
|
|
272
|
+
const url = new URL(req.url);
|
|
273
|
+
const handler = routes[url.pathname];
|
|
274
|
+
if (handler) {
|
|
275
|
+
return handler(req);
|
|
276
|
+
}
|
|
277
|
+
return new Response("Not found", { status: 404 });
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Примечание:**
|
|
283
|
+
|
|
284
|
+
- GET запросы автоматически устанавливают webhook на URL запроса
|
|
285
|
+
- POST запросы обрабатывают обновления от Telegram
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
#### `validatingData(body: string | URLSearchParams): boolean`
|
|
290
|
+
|
|
291
|
+
Валидирует данные от Telegram WebApp.
|
|
292
|
+
|
|
293
|
+
**Параметры:**
|
|
294
|
+
|
|
295
|
+
- `body` (string | URLSearchParams) - Данные от Telegram WebApp
|
|
296
|
+
|
|
297
|
+
**Возвращает:** `true` если данные валидны, иначе `false`
|
|
298
|
+
|
|
299
|
+
**Пример:**
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// В обработчике запроса от WebApp
|
|
303
|
+
const isValid = tb.validatingData(requestBody);
|
|
304
|
+
if (isValid) {
|
|
305
|
+
// Данные валидны, можно использовать
|
|
306
|
+
} else {
|
|
307
|
+
// Данные невалидны
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
#### `handleUpdate(update: TelegramUpdate): Promise<void>`
|
|
314
|
+
|
|
315
|
+
Обрабатывает обновление от Telegram. Обычно вызывается автоматически через webhook, но можно вызвать вручную.
|
|
316
|
+
|
|
317
|
+
**Параметры:**
|
|
318
|
+
|
|
319
|
+
- `update` (TelegramUpdate) - Объект обновления от Telegram API
|
|
320
|
+
|
|
321
|
+
**Пример:**
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
// Ручная обработка обновления
|
|
325
|
+
const update = await fetch("...").then((r) => r.json());
|
|
326
|
+
await tb.handleUpdate(update);
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Типы
|
|
332
|
+
|
|
333
|
+
### `TelegramUpdate`
|
|
334
|
+
|
|
335
|
+
Интерфейс для обновления от Telegram API.
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
interface TelegramUpdate {
|
|
339
|
+
message?: {
|
|
340
|
+
from?: {
|
|
341
|
+
id: number;
|
|
342
|
+
[key: string]: any;
|
|
343
|
+
};
|
|
344
|
+
contact?: {
|
|
345
|
+
phone_number: string;
|
|
346
|
+
user_id: number;
|
|
347
|
+
[key: string]: any;
|
|
348
|
+
};
|
|
349
|
+
text?: string;
|
|
350
|
+
[key: string]: any;
|
|
351
|
+
};
|
|
352
|
+
callback_query?: {
|
|
353
|
+
id: string;
|
|
354
|
+
data: string;
|
|
355
|
+
from: any;
|
|
356
|
+
message: any;
|
|
357
|
+
};
|
|
358
|
+
[key: string]: any;
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### `CallbackQueryData`
|
|
363
|
+
|
|
364
|
+
Данные callback query.
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
interface CallbackQueryData {
|
|
368
|
+
id: string;
|
|
369
|
+
data: string;
|
|
370
|
+
from: any;
|
|
371
|
+
message: any;
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### `ReplyMarkup`
|
|
376
|
+
|
|
377
|
+
Клавиатура или inline-кнопки.
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
interface ReplyMarkup {
|
|
381
|
+
inline_keyboard?: any[][];
|
|
382
|
+
keyboard?: any[][];
|
|
383
|
+
[key: string]: any;
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### `TelegramBot`
|
|
388
|
+
|
|
389
|
+
Интерфейс объекта бота со всеми методами.
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Полный пример
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
import { tb } from "@elcrm/tb";
|
|
397
|
+
|
|
398
|
+
// Использование напрямую (токен берется из process.env.TELEGRAM)
|
|
399
|
+
|
|
400
|
+
// Обработчик сообщений
|
|
401
|
+
tb.onMessage(async (update) => {
|
|
402
|
+
if (!update.message?.text || !update.message.from) return;
|
|
403
|
+
|
|
404
|
+
const chatId = update.message.from.id;
|
|
405
|
+
const text = update.message.text;
|
|
406
|
+
|
|
407
|
+
if (text === "/start") {
|
|
408
|
+
await tb.sendMessage(chatId, "Добро пожаловать!", {
|
|
409
|
+
inline_keyboard: [
|
|
410
|
+
[{ text: "Начать", callback_data: "start_action" }],
|
|
411
|
+
],
|
|
412
|
+
});
|
|
413
|
+
} else if (text === "/help") {
|
|
414
|
+
await tb.sendMessage(chatId, "Это справочное сообщение");
|
|
415
|
+
} else {
|
|
416
|
+
await tb.sendMessage(chatId, `Вы написали: ${text}`);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// Обработчик callback query
|
|
421
|
+
tb.onCallbackQuery("start_action", async (data) => {
|
|
422
|
+
const chatId = data.from.id;
|
|
423
|
+
await tb.sendMessage(chatId, "Действие выполнено!");
|
|
424
|
+
await tb.answerCallbackQuery(data.id, "Готово!");
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Создание webhook роутера
|
|
428
|
+
const routes = tb.webhook("telegram-webhook");
|
|
429
|
+
|
|
430
|
+
// Запуск сервера (Bun)
|
|
431
|
+
Bun.serve({
|
|
432
|
+
port: 3000,
|
|
433
|
+
fetch(req) {
|
|
434
|
+
const url = new URL(req.url);
|
|
435
|
+
const handler = routes[url.pathname];
|
|
436
|
+
if (handler) {
|
|
437
|
+
return handler(req);
|
|
438
|
+
}
|
|
439
|
+
return new Response("Not found", { status: 404 });
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Экспорты
|
|
447
|
+
|
|
448
|
+
Библиотека экспортирует следующие элементы:
|
|
449
|
+
|
|
450
|
+
- `tb` - Основной объект для работы с Telegram Bot API (синглтон)
|
|
451
|
+
- `createTelegramBot(token: string)` - Функция для создания дополнительных экземпляров бота
|
|
452
|
+
- `TelegramBot` - Интерфейс объекта бота
|
|
453
|
+
- `TelegramUpdate` - Интерфейс для обновлений от Telegram
|
|
454
|
+
- `CallbackQueryData` - Интерфейс для данных callback query
|
|
455
|
+
- `ReplyMarkup` - Интерфейс для клавиатур и кнопок
|
|
456
|
+
- `TelegramWebAppData` - Интерфейс для данных Telegram WebApp
|
|
457
|
+
|
|
458
|
+
**Пример импорта типов:**
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
import {
|
|
462
|
+
tb,
|
|
463
|
+
createTelegramBot,
|
|
464
|
+
type TelegramUpdate,
|
|
465
|
+
type CallbackQueryData,
|
|
466
|
+
} from "@elcrm/tb";
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## Требования
|
|
472
|
+
|
|
473
|
+
- Bun runtime (для работы с `Bun.CryptoHasher` в методе `validatingData`)
|
|
474
|
+
- Node.js/Bun для выполнения
|
|
475
|
+
|
|
476
|
+
## Переменные окружения
|
|
477
|
+
|
|
478
|
+
Библиотека использует переменную окружения `TELEGRAM` для получения токена бота:
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
export TELEGRAM="123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Или создайте файл `.env`:
|
|
485
|
+
|
|
486
|
+
```
|
|
487
|
+
TELEGRAM=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**Важно:** Если переменная `TELEGRAM` не установлена, при первом использовании `tb` будет выброшена ошибка. В этом случае используйте функцию `createTelegramBot(token)` для создания экземпляра с явным токеном.
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
## Ограничение скорости запросов
|
|
495
|
+
|
|
496
|
+
Библиотека автоматически ограничивает скорость запросов к Telegram API до **30 запросов в секунду** (лимит Telegram Bot API). Все запросы автоматически ставятся в очередь и обрабатываются последовательно с необходимой задержкой.
|
|
497
|
+
|
|
498
|
+
### Как это работает:
|
|
499
|
+
|
|
500
|
+
- Все запросы к API (кроме `setWebhook`) попадают в очередь перед выполнением
|
|
501
|
+
- Запросы обрабатываются последовательно с минимальной задержкой ~33.33 мс между ними
|
|
502
|
+
- Если запрос выполняется дольше задержки, дополнительная задержка не добавляется
|
|
503
|
+
- Ограничение применяется автоматически ко всем методам: `sendMessage`, `sendPhoto`, `editMessageText`, `answerCallbackQuery`
|
|
504
|
+
|
|
505
|
+
**Пример:**
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
// Все эти запросы автоматически будут обработаны с правильной скоростью
|
|
509
|
+
for (let i = 0; i < 100; i++) {
|
|
510
|
+
await tb.sendMessage(chatId, `Сообщение ${i}`);
|
|
511
|
+
// Запросы будут обработаны последовательно, не превышая лимит 30 запросов/сек
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Лицензия
|
|
518
|
+
|
|
519
|
+
MIT
|
|
520
|
+
|
|
521
|
+
## Автор
|
|
522
|
+
|
|
523
|
+
MaSkal <dev@elcrm.online>
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export interface TelegramWebAppData {
|
|
2
|
+
hash: string;
|
|
3
|
+
user: Record<string, any>;
|
|
4
|
+
[key: string]: any;
|
|
5
|
+
}
|
|
6
|
+
export interface TelegramUpdate {
|
|
7
|
+
message?: {
|
|
8
|
+
from?: {
|
|
9
|
+
id: number;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
};
|
|
12
|
+
contact?: {
|
|
13
|
+
phone_number: string;
|
|
14
|
+
user_id: number;
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
};
|
|
17
|
+
text?: string;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
};
|
|
20
|
+
callback_query?: {
|
|
21
|
+
id: string;
|
|
22
|
+
data: string;
|
|
23
|
+
from: any;
|
|
24
|
+
message: any;
|
|
25
|
+
};
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
}
|
|
28
|
+
export interface CallbackQueryData {
|
|
29
|
+
id: string;
|
|
30
|
+
data: string;
|
|
31
|
+
from: any;
|
|
32
|
+
message: any;
|
|
33
|
+
}
|
|
34
|
+
export interface ReplyMarkup {
|
|
35
|
+
inline_keyboard?: any[][];
|
|
36
|
+
keyboard?: any[][];
|
|
37
|
+
[key: string]: any;
|
|
38
|
+
}
|
|
39
|
+
export interface TelegramBot {
|
|
40
|
+
validatingData: (body: string | URLSearchParams) => boolean;
|
|
41
|
+
setWebhook: (url: string) => Promise<any>;
|
|
42
|
+
handleUpdate: (update: TelegramUpdate) => Promise<void>;
|
|
43
|
+
webhook: (
|
|
44
|
+
router: string
|
|
45
|
+
) => Record<string, (req: Request) => Promise<Response>>;
|
|
46
|
+
onCallbackQuery: (
|
|
47
|
+
name: string,
|
|
48
|
+
callback: (data: CallbackQueryData) => void | Promise<void>
|
|
49
|
+
) => void;
|
|
50
|
+
onMessage: (
|
|
51
|
+
callback: (update: TelegramUpdate) => void | Promise<void>
|
|
52
|
+
) => void;
|
|
53
|
+
answerCallbackQuery: (callbackId: string, text?: string) => Promise<any>;
|
|
54
|
+
editMessageText: (
|
|
55
|
+
chatId: number,
|
|
56
|
+
messageId: number,
|
|
57
|
+
text: string,
|
|
58
|
+
replyMarkup?: ReplyMarkup
|
|
59
|
+
) => Promise<any>;
|
|
60
|
+
sendMessage: (
|
|
61
|
+
chat_id: number,
|
|
62
|
+
text: string,
|
|
63
|
+
reply_markup?: ReplyMarkup
|
|
64
|
+
) => Promise<any>;
|
|
65
|
+
sendPhoto: (
|
|
66
|
+
chat_id: number,
|
|
67
|
+
photo: string,
|
|
68
|
+
caption?: string,
|
|
69
|
+
reply_markup?: ReplyMarkup
|
|
70
|
+
) => Promise<any>;
|
|
71
|
+
}
|
|
72
|
+
declare function createTelegramBot(token: string): TelegramBot;
|
|
73
|
+
export declare const tb: TelegramBot;
|
|
74
|
+
export { createTelegramBot };
|
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
function e(e) {
|
|
2
|
+
if (!e) throw new Error("Telegram bot token is required");
|
|
3
|
+
const a = e,
|
|
4
|
+
t = { callback_query: {} },
|
|
5
|
+
s = [];
|
|
6
|
+
let n = !1;
|
|
7
|
+
const r = 1e3 / 30,
|
|
8
|
+
o = async (e, t) =>
|
|
9
|
+
new Promise((o, c) => {
|
|
10
|
+
s.push({ method: e, body: t, resolve: o, reject: c }),
|
|
11
|
+
(async () => {
|
|
12
|
+
if (!n && 0 !== s.length) {
|
|
13
|
+
for (n = !0; s.length > 0; ) {
|
|
14
|
+
const t = s.shift();
|
|
15
|
+
if (!t) break;
|
|
16
|
+
try {
|
|
17
|
+
const e = Date.now(),
|
|
18
|
+
s = await fetch(
|
|
19
|
+
`https://api.telegram.org/bot${a}/${t.method}`,
|
|
20
|
+
{
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: {
|
|
23
|
+
"Content-Type":
|
|
24
|
+
"application/json",
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify(t.body),
|
|
27
|
+
}
|
|
28
|
+
),
|
|
29
|
+
n = await s.json();
|
|
30
|
+
t.resolve(n);
|
|
31
|
+
const o = Date.now() - e,
|
|
32
|
+
c = Math.max(0, r - o);
|
|
33
|
+
c > 0 &&
|
|
34
|
+
(await new Promise((e) =>
|
|
35
|
+
setTimeout(e, c)
|
|
36
|
+
));
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.error(
|
|
39
|
+
`Error calling Telegram API (${t.method}):`,
|
|
40
|
+
e
|
|
41
|
+
),
|
|
42
|
+
t.reject(e);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
n = !1;
|
|
46
|
+
}
|
|
47
|
+
})();
|
|
48
|
+
}),
|
|
49
|
+
c = {
|
|
50
|
+
setWebhook: async (e) => {
|
|
51
|
+
const t = e.replace("http://", "https://"),
|
|
52
|
+
s = await fetch(
|
|
53
|
+
`https://api.telegram.org/bot${a}/setWebhook?url=${t}`
|
|
54
|
+
);
|
|
55
|
+
return await s.json();
|
|
56
|
+
},
|
|
57
|
+
webhook: (e) => ({
|
|
58
|
+
[`/${e}`]: async (e) => {
|
|
59
|
+
try {
|
|
60
|
+
const a = e.method,
|
|
61
|
+
t = new URL(e.url);
|
|
62
|
+
if ("GET" === a) {
|
|
63
|
+
const e = await c.setWebhook(t.href);
|
|
64
|
+
return new Response(JSON.stringify(e), {
|
|
65
|
+
headers: { "Content-Type": "application/json" },
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if ("POST" === a) {
|
|
69
|
+
const a = await e.json();
|
|
70
|
+
return await c.handleUpdate(a), new Response("OK");
|
|
71
|
+
}
|
|
72
|
+
return new Response("Method not allowed", {
|
|
73
|
+
status: 405,
|
|
74
|
+
});
|
|
75
|
+
} catch (a) {
|
|
76
|
+
return (
|
|
77
|
+
console.error("Error handling webhook:", a),
|
|
78
|
+
new Response("Internal Server Error", {
|
|
79
|
+
status: 500,
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
handleUpdate: async (e) => {
|
|
86
|
+
if (
|
|
87
|
+
(e.message && t.message && (await t.message(e)),
|
|
88
|
+
e.callback_query)
|
|
89
|
+
) {
|
|
90
|
+
const {
|
|
91
|
+
id: a,
|
|
92
|
+
data: s,
|
|
93
|
+
from: n,
|
|
94
|
+
message: r,
|
|
95
|
+
} = e.callback_query;
|
|
96
|
+
await c.answerCallbackQuery(a),
|
|
97
|
+
t.callback_query[s] &&
|
|
98
|
+
(await t.callback_query[s]({
|
|
99
|
+
id: a,
|
|
100
|
+
data: s,
|
|
101
|
+
from: n,
|
|
102
|
+
message: r,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
sendMessage: async (e, a, t) => {
|
|
107
|
+
const s = { chat_id: e, text: a, parse_mode: "HTML" };
|
|
108
|
+
return t && (s.reply_markup = t), o("sendMessage", s);
|
|
109
|
+
},
|
|
110
|
+
sendPhoto: async (e, a, t, s) => {
|
|
111
|
+
const n = { chat_id: e, photo: a };
|
|
112
|
+
return (
|
|
113
|
+
t && (n.caption = t),
|
|
114
|
+
s && (n.reply_markup = s),
|
|
115
|
+
o("sendPhoto", n)
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
answerCallbackQuery: async (e, a) => {
|
|
119
|
+
const t = { callback_query_id: e };
|
|
120
|
+
return (
|
|
121
|
+
a && ((t.text = a), (t.show_alert = !0)),
|
|
122
|
+
o("answerCallbackQuery", t)
|
|
123
|
+
);
|
|
124
|
+
},
|
|
125
|
+
validatingData: (e) => {
|
|
126
|
+
const t = "string" == typeof e ? new URLSearchParams(e) : e,
|
|
127
|
+
s = new URLSearchParams(t);
|
|
128
|
+
s.sort();
|
|
129
|
+
const n = s.get("hash");
|
|
130
|
+
s.delete("hash");
|
|
131
|
+
const r = [...s.entries()]
|
|
132
|
+
.map(([e, a]) => `${e}=${a}`)
|
|
133
|
+
.join("\n"),
|
|
134
|
+
o = new Bun.CryptoHasher("sha256", "WebAppData")
|
|
135
|
+
.update(a)
|
|
136
|
+
.digest();
|
|
137
|
+
return (
|
|
138
|
+
new Bun.CryptoHasher("sha256", o)
|
|
139
|
+
.update(r)
|
|
140
|
+
.digest("hex") === n
|
|
141
|
+
);
|
|
142
|
+
},
|
|
143
|
+
editMessageText: async (e, a, t, s) => {
|
|
144
|
+
const n = { chat_id: e, message_id: a, text: t };
|
|
145
|
+
return s && (n.reply_markup = s), o("editMessageText", n);
|
|
146
|
+
},
|
|
147
|
+
onCallbackQuery: (e, a) => {
|
|
148
|
+
t.callback_query[e] = a;
|
|
149
|
+
},
|
|
150
|
+
onMessage: (e) => {
|
|
151
|
+
t.message = e;
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
return c;
|
|
155
|
+
}
|
|
156
|
+
let a = null;
|
|
157
|
+
function t() {
|
|
158
|
+
if (!a) {
|
|
159
|
+
const t = process.env.TELEGRAM;
|
|
160
|
+
if (!t)
|
|
161
|
+
throw new Error(
|
|
162
|
+
"TELEGRAM environment variable is required. Set TELEGRAM environment variable or use createTelegramBot(token) function"
|
|
163
|
+
);
|
|
164
|
+
a = e(t);
|
|
165
|
+
}
|
|
166
|
+
return a;
|
|
167
|
+
}
|
|
168
|
+
const s = {
|
|
169
|
+
validatingData: (...e) => t().validatingData(...e),
|
|
170
|
+
setWebhook: (...e) => t().setWebhook(...e),
|
|
171
|
+
handleUpdate: (...e) => t().handleUpdate(...e),
|
|
172
|
+
webhook: (...e) => t().webhook(...e),
|
|
173
|
+
onCallbackQuery: (...e) => t().onCallbackQuery(...e),
|
|
174
|
+
onMessage: (...e) => t().onMessage(...e),
|
|
175
|
+
answerCallbackQuery: (...e) => t().answerCallbackQuery(...e),
|
|
176
|
+
editMessageText: (...e) => t().editMessageText(...e),
|
|
177
|
+
sendMessage: (...e) => t().sendMessage(...e),
|
|
178
|
+
sendPhoto: (...e) => t().sendPhoto(...e),
|
|
179
|
+
};
|
|
180
|
+
export { e as createTelegramBot, s as tb };
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
!(function (e, t) {
|
|
2
|
+
"object" == typeof exports && "undefined" != typeof module
|
|
3
|
+
? t(exports)
|
|
4
|
+
: "function" == typeof define && define.amd
|
|
5
|
+
? define(["exports"], t)
|
|
6
|
+
: t(
|
|
7
|
+
(((e =
|
|
8
|
+
"undefined" != typeof globalThis
|
|
9
|
+
? globalThis
|
|
10
|
+
: e || self).elcrm = e.elcrm || {}),
|
|
11
|
+
(e.elcrm.tb = {}))
|
|
12
|
+
);
|
|
13
|
+
})(this, function (e) {
|
|
14
|
+
"use strict";
|
|
15
|
+
function t(e) {
|
|
16
|
+
if (!e) throw new Error("Telegram bot token is required");
|
|
17
|
+
const t = e,
|
|
18
|
+
a = { callback_query: {} },
|
|
19
|
+
n = [];
|
|
20
|
+
let s = !1;
|
|
21
|
+
const o = 1e3 / 30,
|
|
22
|
+
r = async (e, a) =>
|
|
23
|
+
new Promise((r, c) => {
|
|
24
|
+
n.push({ method: e, body: a, resolve: r, reject: c }),
|
|
25
|
+
(async () => {
|
|
26
|
+
if (!s && 0 !== n.length) {
|
|
27
|
+
for (s = !0; n.length > 0; ) {
|
|
28
|
+
const a = n.shift();
|
|
29
|
+
if (!a) break;
|
|
30
|
+
try {
|
|
31
|
+
const e = Date.now(),
|
|
32
|
+
n = await fetch(
|
|
33
|
+
`https://api.telegram.org/bot${t}/${a.method}`,
|
|
34
|
+
{
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: {
|
|
37
|
+
"Content-Type":
|
|
38
|
+
"application/json",
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify(
|
|
41
|
+
a.body
|
|
42
|
+
),
|
|
43
|
+
}
|
|
44
|
+
),
|
|
45
|
+
s = await n.json();
|
|
46
|
+
a.resolve(s);
|
|
47
|
+
const r = Date.now() - e,
|
|
48
|
+
c = Math.max(0, o - r);
|
|
49
|
+
c > 0 &&
|
|
50
|
+
(await new Promise((e) =>
|
|
51
|
+
setTimeout(e, c)
|
|
52
|
+
));
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error(
|
|
55
|
+
`Error calling Telegram API (${a.method}):`,
|
|
56
|
+
e
|
|
57
|
+
),
|
|
58
|
+
a.reject(e);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
s = !1;
|
|
62
|
+
}
|
|
63
|
+
})();
|
|
64
|
+
}),
|
|
65
|
+
c = {
|
|
66
|
+
setWebhook: async (e) => {
|
|
67
|
+
const a = e.replace("http://", "https://"),
|
|
68
|
+
n = await fetch(
|
|
69
|
+
`https://api.telegram.org/bot${t}/setWebhook?url=${a}`
|
|
70
|
+
);
|
|
71
|
+
return await n.json();
|
|
72
|
+
},
|
|
73
|
+
webhook: (e) => ({
|
|
74
|
+
[`/${e}`]: async (e) => {
|
|
75
|
+
try {
|
|
76
|
+
const t = e.method,
|
|
77
|
+
a = new URL(e.url);
|
|
78
|
+
if ("GET" === t) {
|
|
79
|
+
const e = await c.setWebhook(a.href);
|
|
80
|
+
return new Response(JSON.stringify(e), {
|
|
81
|
+
headers: {
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if ("POST" === t) {
|
|
87
|
+
const t = await e.json();
|
|
88
|
+
return (
|
|
89
|
+
await c.handleUpdate(t), new Response("OK")
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return new Response("Method not allowed", {
|
|
93
|
+
status: 405,
|
|
94
|
+
});
|
|
95
|
+
} catch (t) {
|
|
96
|
+
return (
|
|
97
|
+
console.error("Error handling webhook:", t),
|
|
98
|
+
new Response("Internal Server Error", {
|
|
99
|
+
status: 500,
|
|
100
|
+
})
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
handleUpdate: async (e) => {
|
|
106
|
+
if (
|
|
107
|
+
(e.message && a.message && (await a.message(e)),
|
|
108
|
+
e.callback_query)
|
|
109
|
+
) {
|
|
110
|
+
const {
|
|
111
|
+
id: t,
|
|
112
|
+
data: n,
|
|
113
|
+
from: s,
|
|
114
|
+
message: o,
|
|
115
|
+
} = e.callback_query;
|
|
116
|
+
await c.answerCallbackQuery(t),
|
|
117
|
+
a.callback_query[n] &&
|
|
118
|
+
(await a.callback_query[n]({
|
|
119
|
+
id: t,
|
|
120
|
+
data: n,
|
|
121
|
+
from: s,
|
|
122
|
+
message: o,
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
sendMessage: async (e, t, a) => {
|
|
127
|
+
const n = { chat_id: e, text: t, parse_mode: "HTML" };
|
|
128
|
+
return a && (n.reply_markup = a), r("sendMessage", n);
|
|
129
|
+
},
|
|
130
|
+
sendPhoto: async (e, t, a, n) => {
|
|
131
|
+
const s = { chat_id: e, photo: t };
|
|
132
|
+
return (
|
|
133
|
+
a && (s.caption = a),
|
|
134
|
+
n && (s.reply_markup = n),
|
|
135
|
+
r("sendPhoto", s)
|
|
136
|
+
);
|
|
137
|
+
},
|
|
138
|
+
answerCallbackQuery: async (e, t) => {
|
|
139
|
+
const a = { callback_query_id: e };
|
|
140
|
+
return (
|
|
141
|
+
t && ((a.text = t), (a.show_alert = !0)),
|
|
142
|
+
r("answerCallbackQuery", a)
|
|
143
|
+
);
|
|
144
|
+
},
|
|
145
|
+
validatingData: (e) => {
|
|
146
|
+
const a = "string" == typeof e ? new URLSearchParams(e) : e,
|
|
147
|
+
n = new URLSearchParams(a);
|
|
148
|
+
n.sort();
|
|
149
|
+
const s = n.get("hash");
|
|
150
|
+
n.delete("hash");
|
|
151
|
+
const o = [...n.entries()]
|
|
152
|
+
.map(([e, t]) => `${e}=${t}`)
|
|
153
|
+
.join("\n"),
|
|
154
|
+
r = new Bun.CryptoHasher("sha256", "WebAppData")
|
|
155
|
+
.update(t)
|
|
156
|
+
.digest();
|
|
157
|
+
return (
|
|
158
|
+
new Bun.CryptoHasher("sha256", r)
|
|
159
|
+
.update(o)
|
|
160
|
+
.digest("hex") === s
|
|
161
|
+
);
|
|
162
|
+
},
|
|
163
|
+
editMessageText: async (e, t, a, n) => {
|
|
164
|
+
const s = { chat_id: e, message_id: t, text: a };
|
|
165
|
+
return n && (s.reply_markup = n), r("editMessageText", s);
|
|
166
|
+
},
|
|
167
|
+
onCallbackQuery: (e, t) => {
|
|
168
|
+
a.callback_query[e] = t;
|
|
169
|
+
},
|
|
170
|
+
onMessage: (e) => {
|
|
171
|
+
a.message = e;
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
return c;
|
|
175
|
+
}
|
|
176
|
+
let a = null;
|
|
177
|
+
function n() {
|
|
178
|
+
if (!a) {
|
|
179
|
+
const e = process.env.TELEGRAM;
|
|
180
|
+
if (!e)
|
|
181
|
+
throw new Error(
|
|
182
|
+
"TELEGRAM environment variable is required. Set TELEGRAM environment variable or use createTelegramBot(token) function"
|
|
183
|
+
);
|
|
184
|
+
a = t(e);
|
|
185
|
+
}
|
|
186
|
+
return a;
|
|
187
|
+
}
|
|
188
|
+
const s = {
|
|
189
|
+
validatingData: (...e) => n().validatingData(...e),
|
|
190
|
+
setWebhook: (...e) => n().setWebhook(...e),
|
|
191
|
+
handleUpdate: (...e) => n().handleUpdate(...e),
|
|
192
|
+
webhook: (...e) => n().webhook(...e),
|
|
193
|
+
onCallbackQuery: (...e) => n().onCallbackQuery(...e),
|
|
194
|
+
onMessage: (...e) => n().onMessage(...e),
|
|
195
|
+
answerCallbackQuery: (...e) => n().answerCallbackQuery(...e),
|
|
196
|
+
editMessageText: (...e) => n().editMessageText(...e),
|
|
197
|
+
sendMessage: (...e) => n().sendMessage(...e),
|
|
198
|
+
sendPhoto: (...e) => n().sendPhoto(...e),
|
|
199
|
+
};
|
|
200
|
+
(e.createTelegramBot = t),
|
|
201
|
+
(e.tb = s),
|
|
202
|
+
Object.defineProperty(e, Symbol.toStringTag, { value: "Module" });
|
|
203
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elcrm/tb",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "plugin for elCRM",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "MaSkal <dev@elcrm.online>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"elcrm",
|
|
13
|
+
"tb",
|
|
14
|
+
"bun"
|
|
15
|
+
],
|
|
16
|
+
"main": "./dist/index.umd.js",
|
|
17
|
+
"module": "./dist/index.es.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.es.js",
|
|
23
|
+
"require": "./dist/index.umd.js"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|