# @vvlad1973/telegram-bot-client

Мощная и типобезопасная библиотека для создания Telegram ботов на TypeScript с поддержкой нескольких токенов, системой событий и расширенной фильтрацией.

## Основные возможности

- **Полная типизация TypeScript** - автоматически сгенерированные типы из OpenAPI спецификации Telegram Bot API 9.2.0
- **Event-driven архитектура** - гибкая система событий для обработки обновлений
- **Middleware система** - цепочка обработчиков для pre-processing обновлений
- **Расширенная фильтрация** - мощная система фильтров с поддержкой RegExp
- **Поддержка нескольких токенов** - управление несколькими ботами одновременно
- **Long Polling Manager** - встроенный менеджер с worker threads и автоматическими перезапусками
- **Обертки (Wrappers)** - удобные методы для работы с сообщениями и callback query
- **Построители (Builders)** - fluent API для создания клавиатур и команд
- **Обработка ошибок** - иерархия специализированных классов ошибок
- **Интеграция с логированием** - поддержка LoggerTree и SimpleLogger
- **Native Fetch API** - использует встроенный fetch без внешних зависимостей

## Установка

```bash
npm install @vvlad1973/telegram-bot-client
```

## Быстрый старт

### Создание и инициализация бота

Библиотека поддерживает три режима работы с Telegram Bot API:

1. **Inactive** - неактивный режим (только для отправки запросов)
2. **LongPolling** - получение обновлений через long polling
3. **Webhook** - получение обновлений через webhook

#### Жизненный цикл маршрута (route)

Каждый бот проходит следующие этапы:

1. **Создание** - конфигурация маршрута (route) с токеном и режимом
2. **Инициализация** - вызов `getMe` для получения информации о боте
3. **Активация** - установка режима работы (webhook/polling)
4. **Работа** - обработка обновлений
5. **Деактивация** - остановка получения обновлений
6. **Завершение** - закрытие всех ресурсов

### Режим 1: Inactive (только отправка)

Используйте этот режим, когда нужно только отправлять сообщения без получения обновлений:

```typescript
import { TelegramBotClient, RouteMode } from '@vvlad1973/telegram-bot-client';

// Создание клиента
const bot = new TelegramBotClient({
  routes: {
    token: 'YOUR_BOT_TOKEN',
    mode: RouteMode.Inactive
  }
});

// Инициализация (получает информацию о боте через getMe)
await bot.init();

// Отправка сообщения без получения обновлений
await bot.sendMessage({
  chat_id: 123456789,
  text: 'Привет! Это одностороннее сообщение.'
});

// Завершение работы
await bot.shutdown();
```

### Режим 2: Long Polling (получение обновлений)

Long polling - рекомендуемый режим для разработки и небольших ботов:

```typescript
import { TelegramBotClient, RouteMode } from '@vvlad1973/telegram-bot-client';

// Шаг 1: Создание клиента с конфигурацией
const bot = new TelegramBotClient({
  routes: {
    token: 'YOUR_BOT_TOKEN',
    mode: RouteMode.LongPolling,
    longPolling: {
      timeout: 30,           // Таймаут long polling запроса (сек)
      limit: 100,            // Максимальное количество обновлений
      allowedUpdates: [      // Типы обновлений (опционально)
        'message',
        'callback_query'
      ]
    }
  }
});

// Шаг 2: Установка обработчиков событий (до инициализации)
bot.on('/start', (params, wrapper) => {
  wrapper.replyMessage('Добро пожаловать! Я бот в режиме Long Polling.');
});

bot.on('message.text', (wrapper) => {
  wrapper.replyMessage(`Эхо: ${wrapper.text}`);
});

bot.on('callback_query', (wrapper) => {
  wrapper.answer('Кнопка нажата!');
});

// Шаг 3: Инициализация (вызывает getMe, сохраняет информацию о боте)
await bot.init();

// Шаг 4: Запуск long polling (начинает получать обновления)
await bot.startPolling();

console.log('Бот запущен в режиме Long Polling');

// Шаг 5: Graceful shutdown
process.on('SIGINT', async () => {
  console.log('Остановка бота...');
  await bot.stopPolling();
  await bot.shutdown();
  process.exit(0);
});
```

### Режим 3: Webhook (для продакшена)

Webhook режим рекомендуется для продакшен-окружения:

```typescript
import { TelegramBotClient, RouteMode } from '@vvlad1973/telegram-bot-client';
import express from 'express';

// Шаг 1: Создание клиента с webhook конфигурацией
const bot = new TelegramBotClient({
  routes: {
    token: 'YOUR_BOT_TOKEN',
    mode: RouteMode.Webhook,
    webhook: {
      path: '/webhook/bot',           // Путь для webhook
      secretToken: 'your-secret-123',  // Секретный токен для защиты
      maxConnections: 100,             // Макс. одновременных соединений
      allowedUpdates: [                // Типы обновлений
        'message',
        'callback_query'
      ]
    }
  },
  webhookBaseUrl: 'https://your-domain.com'  // Базовый URL (обязательно HTTPS!)
});

// Шаг 2: Установка обработчиков
bot.on('/start', (params, wrapper) => {
  wrapper.replyMessage('Привет! Я бот в режиме Webhook.');
});

bot.on('message.text', (wrapper) => {
  wrapper.replyMessage(`Получено: ${wrapper.text}`);
});

// Шаг 3: Инициализация
await bot.init();

// Шаг 4: Активация (устанавливает webhook через setWebhook)
await bot.activate();

// Шаг 5: Настройка Express для обработки webhook
const app = express();
app.use(express.json());

app.post('/webhook/bot', async (req, res) => {
  // Проверка секретного токена (опционально)
  const secretToken = req.headers['x-telegram-bot-api-secret-token'];
  if (secretToken !== 'your-secret-123') {
    return res.status(403).send('Forbidden');
  }

  // Обработка обновления
  try {
    await bot.processUpdate(req.body);
    res.status(200).send('OK');
  } catch (error) {
    console.error('Error processing update:', error);
    res.status(500).send('Error');
  }
});

// Шаг 6: Запуск сервера
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Webhook сервер запущен на порту ${PORT}`);
});

// Graceful shutdown
process.on('SIGINT', async () => {
  console.log('Остановка сервера...');
  await bot.deactivate();  // Удаляет webhook через deleteWebhook
  await bot.shutdown();
  process.exit(0);
});
```

### Режим 4: Переключение между режимами

Вы можете создать бота в одном режиме и переключить его в другой:

```typescript
import { TelegramBotClient, RouteMode } from '@vvlad1973/telegram-bot-client';

// Создание в неактивном режиме
const bot = new TelegramBotClient({
  routes: {
    token: 'YOUR_BOT_TOKEN',
    mode: RouteMode.Inactive
  }
});

// Инициализация
await bot.init();

// Переключение на Long Polling
await bot.activate(undefined, RouteMode.LongPolling);
await bot.startPolling();

// Работа в режиме Long Polling...
console.log('Работаем в Long Polling...');
await new Promise(resolve => setTimeout(resolve, 10000));

// Остановка polling и переключение на Webhook
await bot.stopPolling();
await bot.deactivate();

// Обновление конфигурации маршрута
bot.setRoute('default', {
  token: 'YOUR_BOT_TOKEN',
  mode: RouteMode.Webhook,
  webhook: { path: '/webhook' }
});

// Активация в режиме Webhook
await bot.activate(undefined, RouteMode.Webhook);

// Теперь бот работает через webhook
```

### Режим 5: Автоматическая активация

Для упрощения можно использовать автоматическую активацию:

```typescript
import { TelegramBotClient, RouteMode } from '@vvlad1973/telegram-bot-client';

const bot = new TelegramBotClient({
  routes: {
    token: 'YOUR_BOT_TOKEN',
    mode: RouteMode.LongPolling,
    autoActivate: true  // Автоматическая активация при инициализации
  }
});

// Обработчики
bot.on('message.text', (wrapper) => {
  wrapper.replyMessage(`Эхо: ${wrapper.text}`);
});

// Только инициализация - активация произойдет автоматически
await bot.init();

// Для Long Polling все равно нужно запустить polling
await bot.startPolling();
```

### Работа с клавиатурами

```typescript
import { InlineKeyboardBuilder } from '@vvlad1973/telegram-bot-client';

// Создание inline клавиатуры
const keyboard = new InlineKeyboardBuilder()
  .appendRows(2)
  .appendTextButton('Помощь', 'help', 0)
  .appendTextButton('Настройки', 'settings', 0)
  .appendUrlButton('Наш сайт', 'https://example.com', 1)
  .build();

bot.on('/menu', (params, wrapper) => {
  wrapper.replyMessage('Выберите действие:', {
    reply_markup: keyboard
  });
});

// Обработка callback
bot.on('callback_query', (wrapper) => {
  if (wrapper.data === 'help') {
    wrapper.editMessageText('Справка по боту...');
    wrapper.answer();
  }
});
```

### Фильтрация обновлений

```typescript
// Фильтр по приватным чатам
bot.onFilter({
  type: 'message.text',
  chat: 'private'
}, (wrapper) => {
  wrapper.replyMessage('Это приватный чат');
});

// Фильтр с RegExp
bot.onFilter({
  type: 'message.text',
  contents: /^\/admin/,
  user: /^(111111|222222)$/ // Только определенные пользователи
}, (wrapper) => {
  wrapper.replyMessage('Админ-команда');
});

// Фильтр по типу чата
bot.onFilter({
  type: 'message.text',
  chat_type: 'group'
}, (wrapper) => {
  console.log('Сообщение из группы');
});
```

### Middleware

```typescript
// Логирование всех обновлений
bot.use((update, next, client) => {
  console.log('Update ID:', update.update_id);
  next();
});

// Аутентификация
bot.use((update, next, client) => {
  const userId = update.message?.from?.id;
  if (isAuthorized(userId)) {
    next(); // Продолжить обработку
  }
  // Если не вызвать next(), обработка остановится
});

// Обработка ошибок
bot.use(async (update, next, client) => {
  try {
    next();
  } catch (error) {
    console.error('Error processing update:', error);
  }
});
```

## Документация

- [Быстрый старт](docs/QUICK-START.md) - подробное руководство для начинающих
- [Migration Guide v2.0](docs/MIGRATION-GUIDE-v2.md) - миграция с v1.x на v2.0
- [Справочник классов](docs/CLASS-REFERENCE.md) - полное описание всех классов
- API Reference - TypeDoc документация (запустите `npm run docs` для генерации)

## Архитектура

```text
TelegramBotClient (клиентский слой)
    ↓ наследует
BaseTelegramApi (сгенерированные методы API)
    ↓ использует
TelegramTransport (транспортный слой)
    ↓ использует
TelegramHttpClient (HTTP-клиент)
    ↓ использует
Native Fetch API
```

### Основные компоненты

- **TelegramBotClient** - основной клиент с системой событий и фильтрацией
- **TelegramTransport** - транспортный слой для HTTP-запросов
- **TelegramHttpClient** - HTTP-клиент с автоматическими повторами
- **TokensManager** - управление несколькими токенами ботов
- **LongPollingManager** - менеджер long polling с worker threads
- **MessageWrapper** - обертка для удобной работы с сообщениями
- **CallbackQueryWrapper** - обертка для callback query
- **InlineKeyboardBuilder** - построитель inline-клавиатур
- **ReplyKeyboardBuilder** - построитель обычных клавиатур

## Примеры использования

### Несколько токенов

```typescript
import { TelegramBotClient, RouteMode } from '@vvlad1973/telegram-bot-client';

// Создание клиента с несколькими маршрутами
const bot = new TelegramBotClient({
  routes: [
    {
      token: 'TOKEN_1',
      mode: RouteMode.LongPolling
    },
    {
      token: 'TOKEN_2',
      mode: RouteMode.Webhook,
      webhook: { path: '/webhook/support' }
    }
  ],
  defaultRouteId: 'default',
  webhookBaseUrl: 'https://example.com'
});

// Инициализация всех маршрутов
await bot.init();

// Активация конкретного маршрута
await bot.managers.lifecycle.activateRoute('default', RouteMode.LongPolling);

// Отправка от конкретного бота
await bot.sendMessage(
  { chat_id: 123, text: 'Hello from main bot!' },
  { routeId: 'default' }
);

await bot.sendMessage(
  { chat_id: 456, text: 'Hello from support!' },
  { routeId: 'route_1' }
);

// Запуск polling для всех маршрутов в режиме LongPolling
await bot.startPolling();
```

### Работа с менеджерами (Advanced)

`TelegramBotClient` использует три менеджера для управления маршрутами:

#### 1. RouteConfigManager - управление конфигурацией

```typescript
// Доступ к менеджеру
const configManager = bot.managers.config;

// Получение списка всех маршрутов
const routeIds = configManager.getRouteIds();
console.log('Доступные маршруты:', routeIds);

// Получение конфигурации маршрута
const config = configManager.getRoute('default');
console.log('Режим:', config.mode);
console.log('Токен:', config.token);

// Установка/обновление конфигурации маршрута
bot.setRoute('bot2', {
  token: 'NEW_TOKEN',
  mode: RouteMode.LongPolling,
  longPolling: {
    timeout: 30,
    limit: 100
  }
});

// Получение ID маршрута по умолчанию
const defaultRouteId = configManager.getDefaultRouteId();

// Проверка существования маршрута
if (configManager.hasRoute('bot2')) {
  console.log('Маршрут bot2 существует');
}
```

#### 2. RouteLifecycleManager - управление жизненным циклом

```typescript
// Доступ к менеджеру
const lifecycleManager = bot.managers.lifecycle;

// Инициализация конкретного маршрута (вызывает getMe)
await lifecycleManager.initRoute('bot1');

// Активация маршрута в указанном режиме
await lifecycleManager.activateRoute('bot1', RouteMode.LongPolling);

// Деактивация маршрута (удаляет webhook, останавливает polling)
await lifecycleManager.deactivateRoute('bot1');

// Получение информации о боте (после init)
const botInfo = lifecycleManager.getBotInfo('bot1');
console.log('Имя бота:', botInfo.username);
console.log('ID бота:', botInfo.id);

// Получение статуса маршрута
const status = lifecycleManager.getRouteStatus('bot1');
console.log('Статус:', status.state);        // 'initialized', 'active', 'inactive'
console.log('Режим:', status.mode);           // RouteMode
console.log('Последняя ошибка:', status.lastError);

// Переключение режима (деактивирует текущий и активирует новый)
await lifecycleManager.switchMode('bot1', RouteMode.Webhook);

// Горячая перезагрузка конфигурации (применяет новые настройки)
await lifecycleManager.hotReload('bot1');
```

#### 3. PollingIntegrationManager - управление polling

```typescript
// Доступ к менеджеру
const pollingManager = bot.managers.polling;

// Запуск polling для конкретного маршрута
await pollingManager.startPolling();

// Остановка polling
await pollingManager.stopPolling();

// Проверка активности polling для маршрута
const isActive = pollingManager.isPollingActive('bot1');
console.log('Polling активен:', isActive);

// Получение текущего offset для маршрута
const offset = pollingManager.getOffset('bot1');
console.log('Текущий offset:', offset);

// Получение статистики polling для маршрута
const stats = pollingManager.getStats('bot1');
console.log('Всего получено обновлений:', stats.totalUpdates);
console.log('Последнее обновление:', stats.lastUpdate);
console.log('Статус worker:', stats.workerStatus);
```

#### Полный пример с менеджерами

```typescript
import { TelegramBotClient, RouteMode } from '@vvlad1973/telegram-bot-client';

// Создание клиента с несколькими маршрутами
const bot = new TelegramBotClient({
  routes: new Map([
    ['main', {
      token: 'MAIN_BOT_TOKEN',
      mode: RouteMode.Inactive
    }],
    ['support', {
      token: 'SUPPORT_BOT_TOKEN',
      mode: RouteMode.Inactive
    }]
  ]),
  defaultRouteId: 'main'
});

// 1. Инициализация обоих ботов
console.log('Инициализация ботов...');
await bot.managers.lifecycle.initRoute('main');
await bot.managers.lifecycle.initRoute('support');

// Проверка информации о ботах
const mainInfo = bot.managers.lifecycle.getBotInfo('main');
const supportInfo = bot.managers.lifecycle.getBotInfo('support');
console.log('Основной бот:', mainInfo.username);
console.log('Бот поддержки:', supportInfo.username);

// 2. Активация основного бота в Long Polling
console.log('Активация основного бота...');
await bot.managers.lifecycle.activateRoute('main', RouteMode.LongPolling);
await bot.startPolling();

// 3. Активация бота поддержки в Webhook
console.log('Активация бота поддержки...');
bot.setRoute('support', {
  token: 'SUPPORT_BOT_TOKEN',
  mode: RouteMode.Webhook,
  webhook: { path: '/webhook/support' }
});
await bot.managers.lifecycle.activateRoute('support', RouteMode.Webhook);

// 4. Мониторинг состояния
setInterval(() => {
  // Статус маршрутов
  const mainStatus = bot.managers.lifecycle.getRouteStatus('main');
  const supportStatus = bot.managers.lifecycle.getRouteStatus('support');

  console.log('Основной бот - состояние:', mainStatus.state, 'режим:', mainStatus.mode);
  console.log('Бот поддержки - состояние:', supportStatus.state, 'режим:', supportStatus.mode);

  // Статистика polling для основного бота
  if (bot.managers.polling.isPollingActive('main')) {
    const stats = bot.managers.polling.getStats('main');
    console.log('Получено обновлений:', stats.totalUpdates);
  }
}, 30000);

// 5. Переключение режима основного бота (с Long Polling на Webhook)
setTimeout(async () => {
  console.log('Переключение основного бота на Webhook...');
  await bot.stopPolling();
  bot.setRoute('main', {
    token: 'MAIN_BOT_TOKEN',
    mode: RouteMode.Webhook,
    webhook: { path: '/webhook/main' }
  });
  await bot.managers.lifecycle.switchMode('main', RouteMode.Webhook);
}, 60000);

// Graceful shutdown
process.on('SIGINT', async () => {
  console.log('Остановка всех ботов...');

  // Деактивация всех маршрутов
  for (const routeId of bot.managers.config.getRouteIds()) {
    await bot.managers.lifecycle.deactivateRoute(routeId);
  }

  await bot.shutdown();
  process.exit(0);
});
```

### Обработка ошибок

```typescript
import { TelegramRateLimitError, TelegramBadRequestError } from '@vvlad1973/telegram-bot-client';

try {
  await bot.sendMessage({
    chat_id: chatId,
    text: 'Hello!'
  });
} catch (error) {
  if (error instanceof TelegramRateLimitError) {
    console.log(`Rate limited. Retry after ${error.retryAfter} seconds`);
    // Автоматическая задержка
    await new Promise(resolve => setTimeout(resolve, error.getRetryDelay()));
  } else if (error instanceof TelegramBadRequestError) {
    console.error('Bad request:', error.description);
  }
}
```

### Логирование

```typescript
import { SimpleLogger } from '@vvlad1973/simple-logger';

const logger = new SimpleLogger({
  level: 'info',
  prettyPrint: true
});

const transport = new TelegramTransport({
  tokens: { default: token },
  logger
});

const bot = new TelegramBotClient({
  transport,
  logger
});

// Логи автоматически выводятся на всех уровнях
```

### MessageWrapper методы

```typescript
bot.on('message.text', async (wrapper) => {
  // Ответ на сообщение
  await wrapper.replyMessage('Hello!');

  // Отправка в тот же чат
  await wrapper.answerMessage('Message sent!');

  // Редактирование
  await wrapper.editText('Updated text');

  // Удаление
  await wrapper.delete();

  // Пересылка
  await wrapper.forward(otherChatId);

  // Закрепление
  await wrapper.pin();

  // Chat action
  await wrapper.answerChatAction('typing');

  // Отправка фото
  await wrapper.replyPhoto('file_id_or_url', {
    caption: 'Photo caption'
  });
});
```

### ReplyKeyboardBuilder

```typescript
import { ReplyKeyboardBuilder } from '@vvlad1973/telegram-bot-client';

const keyboard = new ReplyKeyboardBuilder()
  .appendRows(3)
  .appendTextButton('Начать', 0)
  .appendTextButton('Помощь', 0)
  .appendRequestContactButton('Отправить контакт', 1)
  .appendRequestLocationButton('Отправить локацию', 2)
  .setResizeKeyboard(true)
  .setOneTimeKeyboard(true)
  .build();

await bot.sendMessage({
  chat_id: chatId,
  text: 'Выберите действие:',
  reply_markup: keyboard
});
```

## Требования

- Node.js >= 18.0.0
- npm >= 9.0.0
- TypeScript >= 5.0 (для разработки)

## Зависимости

Основные зависимости:

- `@vvlad1973/base-api` - базовый API класс
- `@vvlad1973/data-validator` - валидация данных
- `@vvlad1973/logger-tree` - система логирования
- `@vvlad1973/simple-logger` - простой логгер
- `@vvlad1973/utils` - утилиты
- `ajv` - JSON Schema валидация

## Скрипты

```bash
# Сборка проекта
npm run build

# Тестирование
npm test
npm run test:coverage

# Линтинг
npm run lint
npm run lint:fix
npm run lint:md
npm run lint:md:fix

# Генерация документации
npm run docs

# Обновление API
npm run fetch-api      # Скачать спецификацию Telegram Bot API
npm run generate-api   # Сгенерировать типы и методы
npm run update-api     # Скачать и сгенерировать
```

## Разработка

### Генерация API

Типы и методы автоматически генерируются из OpenAPI спецификации:

```bash
# Скачать актуальную спецификацию
npm run fetch-api

# Сгенерировать классы и типы
npm run generate-api

# Или всё вместе
npm run update-api
```

### Структура проекта

```text
telegram-bot-client/
├── src/
│   ├── api/                    # Сгенерированные API классы
│   │   ├── BaseTelegramApi.generated.ts
│   │   └── types/              # Сгенерированные типы
│   ├── client/                 # TelegramBotClient
│   ├── transport/              # Транспортный слой
│   ├── builders/               # Построители клавиатур
│   ├── wrappers/               # Обертки для Message и CallbackQuery
│   ├── agents/                 # Long Polling Manager
│   ├── errors/                 # Классы ошибок
│   ├── helpers/                # Вспомогательные функции
│   └── types/                  # Общие типы
├── scripts/                    # Скрипты генерации
├── docs/                       # Документация
├── examples/                   # Примеры
└── models/                     # OpenAPI спецификация
```

## Примеры

Все примеры использования представлены в разделах выше с подробными комментариями и пояснениями.

## Changelog

См. [CHANGELOG.md](CHANGELOG.md) для полной истории изменений.

## Лицензия

MIT with Commercial Use License

## Автор

Vladislav Vnukovskiy <vvlad1973@gmail.com>

## Ссылки

- [GitHub репозиторий](https://github.com/vvlad1973/telegram-bot-client)
- [npm пакет](https://www.npmjs.com/package/@vvlad1973/telegram-bot-client)
- [Telegram Bot API документация](https://core.telegram.org/bots/api)
- [Создание бота через @BotFather](https://t.me/botfather)

## Поддержка

Если у вас возникли проблемы или вопросы:

1. Проверьте [документацию](docs/QUICK-START.md)
2. Посмотрите [примеры](examples/README.md)
3. Создайте [issue на GitHub](https://github.com/vvlad1973/telegram-bot-client/issues)

## Благодарности

- Telegram Team за отличный Bot API
- Сообщество разработчиков Telegram ботов
