UNPKG

umbot

Version:

Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф

944 lines (943 loc) 52.3 kB
import { IAppConfig, IAppParam, TAppType, TAppMode } from './interfaces/IAppContext'; import { TBotContent, TBotResponseCb, TCommandGroupMode, TPlugin } from './interfaces/IBot'; import { ICommandParam, TSlots, TCommandResolver, IStepParam } from './utils/CommandReg'; import { IncomingMessage, ServerResponse, Server } from 'node:http'; import { BotController, IUserData } from '../controller'; import { AppContext } from './AppContext'; import { ILogger } from './interfaces/ILogger'; /** * Тип для класса контроллера приложения */ export type TBotControllerClass<T extends IUserData = IUserData> = new (appContext: AppContext) => BotController<T>; /** * Результат выполнения запроса от платформы - ответ, который будет отправлен пользователю * Может быть ответом для Алисы, Маруси или текстовым сообщением * * @example * ```ts * // Ответ для Алисы * const alisaResponse: TRunResult = { * response: { * text: 'Привет!', * end_session: false * }, * version: '1.0' * }; * * // Ответ для Маруси * const marusiaResponse: TRunResult = { * response: { * text: 'Привет!', * end_session: false * }, * version: '1.0' * }; * * // Простой текстовый ответ * const textResponse: TRunResult = 'Привет!'; * ``` */ export type TRunResult = object | string; export * from './interfaces/IBot'; /** * Функция для обработки следующего шага в цепочке промежуточных функций */ export type MiddlewareNext = () => Promise<void>; /** * Функция промежуточной обработки */ export type MiddlewareFn = (ctx: BotController, next: MiddlewareNext) => void | Promise<void>; /** * Тип для кастомного обработчика определения платформы. * * Позволяет разработчику взять на себя определение типа платформы для входящего запроса. * Обработчик выполняется **до** встроенного автоопределения через адаптеры. * * @param query - Тело запроса (обычно объект, может быть строкой, если запрос не распарсен). * @param headers - HTTP-заголовки запроса (если доступны). * @param detect - Функция, вызывающая стандартный механизм определения платформы * (перебор зарегистрированных адаптеров). Принимает те же параметры, * что и резолвер, и возвращает имя платформы или `null`. * @returns Имя платформы (строка) или `null`, если платформа не определена. * * @example * ```ts * bot.setPlatformResolver((query, headers, detect) => { * // Сначала пробуем стандартное определение * const platform = detect(query, headers); * if (platform === 'telegram' && headers?.['x-force-vk']) { * return 'vk'; * } * return platform; * }); * ``` */ export type TPlatformResolver = (query: unknown, headers?: Record<string, unknown>, detect?: (uBody: unknown, headers?: Record<string, unknown>) => TAppType | null) => TAppType | null; /** * Мультиплатформенный фреймворк для разработки голосовых навыков и чат-ботов. Он даёт единую бизнес-логику для все платформ — но одинаково эффективен, даже если вы работаете только с одной. * * **`Bot` — главный класс**, управляющий всем жизненным циклом приложения: * - регистрацией платформ (Алиса, Telegram, VK, Маруся, Max и др.); * - обработкой входящих запросов; * - маршрутизацией команд; * - middleware; * - работой с базой данных; * - логированием * - метриками. * * Фреймворк построен на **адаптерах** — каждый адаптер отвечает за преобразование * специфичного для платформы запроса в унифицированный `BotController`, который * содержит всю информацию о пользователе, текст команды, NLU, состояние и методы ответа * (кнопки, карточки, TTS). Вы пишете **один код**, а фреймворк доставляет его * на все поддерживаемые платформы. * * ## 📖 КРАТКОЕ РУКОВОДСТВО * * 1. **Создайте приложение:** `const bot = new Bot();` * 2. **Настройте токены:** `bot.setAppConfig({ tokens: { telegram: {token: '...'}} });` * 3. **Добавьте логику при необходимости:** `bot.initBotController(MyController);` * 4. **Добавьте команды:** `bot.addCommand('start', ['старт'], handler);` * 5. **Запустите:** `bot.start();` * * ## 🎯 Ключевые возможности * * - ✅ **Поддержка множества платформ** через подключаемые адаптеры (Алиса, Telegram, VK, Маруся и др.) * * - ✅ **Единая логика** для голосовых навыков и ботов * * - ✅ **Мощная система команд и интентов** с поддержкой регулярных выражений * * - ✅ **Управление состоянием диалога** (шаги) и пользовательскими данными * * - ✅ **Встроенная работа с БД** (MongoDB через плагины) * * - ✅ **Middleware и плагины** для расширения функциональности * * - ✅ **Гибкая настройка** (режимы разработки/продакшена, защита от ReDoS, кастомные резолверы команд) * * - ✅ **Логирование и метрики** для отладки и мониторинга * * ## 🚀 БЫСТРЫЙ СТАРТ * Создание простого Telegram бота * ```ts * import { Bot } from 'umbot'; * import { botPlatforms } from 'umbot/plugins'; * * // 1. Создаем бота для Telegram * const bot = new Bot(); * * // 2. Настраиваем токен (рекомендуется через .env файл) * bot.setAppConfig({ * env: 'local' * }); * * // 3. Говорим приложению, что нужно поддерживать только платформы для чат-ботов * bot.use(botPlatforms); * * // 4. Добавляем команды * bot.addCommand('help', ['помощь', 'справка'], (cmd, controller) => { * controller.text = 'Я могу:\n• Приветствовать\n• Помогать\n• И многое другое!'; * }); * * // 5. Запускаем сервер * bot.start('localhost', 3000); * * // 6. Настройте webhook в Telegram: https://api.telegram.org/bot{YOUR_TOKEN}/setWebhook?url=https://ваш-домен/webhook * ``` * * Создание простого приложения со своим контроллером: * ```ts * const bot = new Bot(); * bot.setPlatformParams({ * intents: [{ * name: 'greeting', * slots: ['привет', 'здравствуй'] * }] * }); * * class MyController extends BotController { * public action(intentName: string | null): void { * if (intentName === 'greeting') { * this.text = 'Привет! Я ваш помощник 🤖'; * this.buttons * .addBtn('Помощь') * .addBtn('Настройки'); * } * } * } * * bot.initBotController(MyController); * ``` * Использование с базой данных: * ```ts * import { Bot } from 'umbot' * import { MongoAdapter } from 'umbot/plugins' * const bot = new Bot(); * bot.use(new MongoAdapter({ * host: 'localhost', * database: 'bot_db', * user: 'user', * pass: 'password' * })); * ``` * * @template TUserData Тип пользовательских данных, по умолчанию {@link IUserData} * @see BotController */ export declare class Bot<TUserData extends IUserData = IUserData> { #private; /** * Полученный запрос от пользователя. * Может быть JSON-строкой, текстом или null */ protected _content: TBotContent; /** * Создает новый экземпляр приложения * * @param {TAppType} [type] - Тип платформы (по умолчанию автоопределение) * @param {TBotControllerClass} [botController] - Контроллер с логикой * * @throws {Error} Если не удалось инициализировать тип платформы * * @example * ```ts * // Создание навыка для Алисы * const bot = new Bot(T_ALISA, MyController); * * // Создание бота для Telegram * const bot = new Bot(T_TELEGRAM, MyController); * * // Создание бота для VK * const bot = new Bot(T_VK, MyController); * * // Создание приложения по умолчанию * const bot = new Bot(); * ``` */ constructor(type?: TAppType, botController?: TBotControllerClass<TUserData>); /** * Явно устанавливает тип платформы для всего приложения. Стоит использовать в крайнем случае * @param appType */ set appType(appType: TAppType | 'auto'); /** * Возвращает установленный тип приложения. */ get appType(): string; /** * Задает режим работы с регулярным выражениями. * При значении auto, регулярные выражения будут группироваться в группу, благодаря чему уменьшается время обработки. Логика начинает отрабатывать после того, как добавили более 300 команд. * При значении no-group, группировка регулярных выражений производиться не будет, из-за чего каждое регулярное выражение будет обрабатываться отдельно. Указывать данное значение стоит в том случае, если вы получаете сильную деградацию при обработке групп. * При значении group, все регулярные выражения будут добавляться в группу. Перед использованием данного значения, перепроверьте производительность, так как при группировке определенных регулярных выражений, производительность может быть ниже. * @param mode - Определяет режим работы с регулярными выражениями. */ setCommandGroupMode(mode: TCommandGroupMode): this; /** * Устанавливает кастомный обработчик для определения типа платформы. * * По умолчанию фреймворк определяет платформу автоматически, перебирая зарегистрированные адаптеры * и вызывая их метод `isPlatformOnQuery`. Этот метод позволяет переопределить логику определения, * что полезно в следующих случаях: * - Нужно добавить поддержку кастомной платформы, не создавая адаптер. * - Требуется изменить стандартное поведение для конкретного набора запросов * (например, по заголовкам или по содержимому запроса). * - Необходимо выполнить какую-то логику до того, как запрос попадёт в адаптер. * * **Важно:** резолвер выполняется **до** вызова `isPlatformOnQuery` любого адаптера. * Если резолвер возвращает строку (имя платформы), фреймворк использует это значение * и не выполняет стандартное автоопределение. Если возвращает `null`, то запускается * стандартный перебор адаптеров. * * В резолвер передаётся опциональная функция `detect`, которая вызывает встроенное * автоопределение. Это позволяет, например, получить результат стандартного определения * и затем скорректировать его. * * @param resolver - Функция, принимающая запрос, заголовки и опционально `detect`. * Должна вернуть имя платформы (строка) или `null`. * @returns Тот же экземпляр приложения для цепочечных вызовов. * * @example * // Простейший резолвер, который для всех запросов использует платформу 'alisa' * bot.setPlatformResolver(() => 'alisa'); * * @example * // Резолвер, который вызывает стандартное определение и при необходимости * // заменяет результат для конкретного заголовка. * bot.setPlatformResolver((query, headers, detect) => { * const platform = detect?.(query, headers); * if (platform === 'telegram' && headers?.['x-force-vk']) { * return 'vk'; * } * return platform; * }); * * @example * // Полное переопределение: обработка запросов от собственного сервиса. * bot.setPlatformResolver((query) => { * if (typeof query === 'object' && query?.source === 'my_service') { * return 'my_platform'; * } * return null; * }); * * @remarks * - Резолвер может быть асинхронным, если это необходимо. Для этого просто объявите * функцию как `async` и возвращайте `Promise<string | null>`. Фреймворк дождётся * результата перед продолжением. * - Внутри резолвера можно модифицировать `query` или `headers`, если нужно повлиять * на дальнейшую обработку (например, добавить недостающие поля). * - Если резолвер возвращает имя платформы, для которой нет зарегистрированного * адаптера, фреймворк выбросит ошибку. Убедитесь, что нужный адаптер подключен. */ setPlatformResolver(resolver: TPlatformResolver): this; /** * Позволяет установить свою реализацию для логирования * @param logger */ setLogger(logger: ILogger | null): this; /** * Регистрирует команду — обработчик, срабатывающий при совпадении входящего текста с одним из шаблонов. * * Поиск команд оптимизирован: * 1. Сначала проверяется точное совпадение * 2. Если точного совпадения нет — выполняется последовательный перебор в порядке регистрации * * Первая совпавшая команда выполняется. * * @param {string} commandName - Уникальное имя команды (например, `'greeting'`). Используется для логирования и отладки. * @param {TSlots} slots - Массив шаблонов для сопоставления: * - Если элемент — строка → ищется как подстрока (`text.includes(...)`). * - Если элемент — RegExp → проверяется как регулярное выражение (`.test(text)`). * - Параметр `isPattern` учитывается **только если в `slots` нет RegExp**. * - При наличии хотя бы одного `RegExp`, `isPattern = false` игнорируется, и каждый элемент * обрабатывается согласно своему типу. * @param {ICommandParam['cb']} cb - Обработчик команды. Принимает: * - `text` — исходный текст от пользователя; * - `controller` — экземпляр `BotController` для формирования ответа (кнопки, текст, шаги, данные и т.д.); * * Поддерживает `async`. * @param {boolean} isPattern - Если `true` и в `slots` **нет RegExp**, все строки преобразуются в регулярные выражения. * ⚠️ Используйте с осторожностью: возможен ReDoS. Все RegExp проверяются на уязвимости. * * @example * Простая текстовая команда: * ```ts * bot.addCommand( * 'greeting', * ['привет', 'здравствуй'], * (cmd, ctrl) => { * ctrl.text = 'Здравствуйте!'; * } * ); * ``` * * @example * Команда с регулярными выражениями: * ```ts * // Обработка чисел от 0 до 999 * bot.addCommand( * 'number', * ['\\b(\\d{0,3})\\b'], * (cmd, ctrl) => { * ctrl.text = `Вы ввели число: ${cmd}`; * }, * true // включаем поддержку регулярных выражений * ); * ``` * * @example * Команда с доступом к состоянию: * ```ts * bot.addCommand( * 'stats', * ['статистика'], * async (cmd, ctrl) => { * if (ctrl) { * // Доступ к пользовательским данным * const visits = ctrl.userData?.visits || 0; * ctrl.text = `Вы использовали приложение ${visits} раз`; * * // Доступ к кнопкам и другим UI элементам * ctrl.buttons * .addBtn('Сбросить статистику') * .addBtn('Закрыть'); * } * } * ); * ``` * * @example * // Асинхронная команда (работа с API): * ```ts * bot.addCommand('weather', ['погода'], async (text, controller) => { * const weather = await fetch('Какой-то сервис для получения погоды'); * controller.text = `Погода: ${await weather.text()}`; * }); * ``` * * @example * // Fallback: срабатывает, если ни одна команда не подошла: * ```ts * bot.addCommand('*', [], (text, controller) => { * controller.text = `Извините, я не понял "${text}". Скажите "помощь" для списка команд.`; * }); * ``` * * @remarks * Поиск команд оптимизирован: * 1. Сначала проверяется точное совпадение * 2. Если точного совпадения нет — выполняется последовательный перебор * * При регистрации более 300 команд с регулярными выражениями * фреймворк автоматически объединяет их в группы для повышения производительности. * * При isPattern=true используются регулярные выражения JavaScript * В callback доступен весь функционал BotController * Можно использовать async функции в callback */ addCommand<TBotController extends BotController = BotController>(commandName: string, slots: TSlots, cb: ICommandParam<TBotController>['cb'], isPattern?: boolean): this; /** * Удаляет зарегистрированную команду по имени * @param commandName - Имя команды */ removeCommand(commandName: string): this; /** * Удаляет **все** зарегистрированные команды * * > ⚠️ Это **глобальная операция**: все сценарии станут недоступны. * > Используйте с осторожностью (например, при перезагрузке логики приложения). */ clearCommands(): this; /** * Регистрирует обработчик для именованного шага диалога. * * Шаг — это часть **многошагового сценария** (например: "регистрация", "оформление заказа"). * После вызова `ctx.thisIntentName = 'myStep'` в команде или другом шаге, * следующее сообщение пользователя будет обработано этим обработчиком. * * > 💡 Обработчик получает полный `BotController`, как и в командах: * > доступны `this.text`, `this.userData`, `this.buttons` и т.д. * * @param stepName — Уникальное имя шага (например, `'enter_email'`). * @param handler — Функция, вызываемая при получении сообщения в этом шаге. * @returns Текущий экземпляр `Bot` (для цепочки вызовов). * * @example * ```ts * bot.addCommand('start', ['начать'], (_, ctx) => { * ctx.text = 'Готовы начать приключение?'; * ctx.buttons.addBtn('Да').addBtn('Нет'); * ctx.thisIntentName = 'confirm'; * }); * bot.addStep('confirm', (ctx) => { * if (ctx.userCommand === 'да') { * ctx.text = 'Отлично! Добро пожаловать.'; * } else { * ctx.text = 'Извините, вход запрещён.'; * ctx.thisIntentName = 'goodbye'; // переходим к другому шагу * } * }); * ``` * @example * ```ts * // Пример многошаговой формы * bot.addCommand('order', ['заказать'], (_, ctx) => { * ctx.text = 'Введите ваше имя:'; * ctx.thisIntentName = 'step_name'; * }); * * bot.addStep('step_name', (ctx) => { * ctx.userData.name = ctx.userCommand; * ctx.text = `Приятно познакомиться, ${ctx.userData.name}! Теперь введите email:`; * ctx.thisIntentName = 'step_email'; * }); * * bot.addStep('step_email', (ctx) => { * ctx.userData.email = ctx.userCommand; * ctx.text = `Заказ оформлен! Имя: ${ctx.userData.name}, Email: ${ctx.userData.email}`; * ctx.thisIntentName = null; // сбрасываем шаг * }); * ``` */ addStep<TBotController extends BotController = BotController>(stepName: string, handler: IStepParam<TBotController>['cb']): this; /** * Удаляет зарегистрированный шаг по имени. * * После удаления шаг больше не будет обрабатываться, даже если активен у пользователя. * (Рекомендуется завершать активные сценарии через `ctx.clearStep()` перед удалением.) * * @param stepName — Имя шага для удаления. * @returns Текущий экземпляр `Bot`. */ removeStep(stepName: string): this; /** * Удаляет **все** зарегистрированные шаги. * * > ⚠️ Это **глобальная операция**: все сценарии станут недоступны. * > Используйте с осторожностью (например, при перезагрузке логики приложения). * * @returns Текущий экземпляр `Bot`. */ clearSteps(): this; /** * Удаляет **все** зарегистрированные платформы, плагины и middleware службы. * * > ⚠️ Это **глобальная операция**: все сценарии станут недоступны. * > Используйте с осторожностью (например, при перезагрузке логики приложения). * * @returns Текущий экземпляр `Bot`. */ clearUse(): this; /** * Устанавливает режим работы приложения * * @param {'dev' | 'prod' | 'strict_prod'} appMode - Режим работы: * - 'dev': подробные логи, отладочная информация, отключена проверка ReDoS * - 'prod': минимальные логи, производительность, НО небезопасные RegExp всё равно регистрируются * - 'strict_prod': строгая проверка безопасности — любая RegExp с потенциальным ReDoS отклоняется с ошибкой * * ⚠️ ВАЖНО: В продакшене всегда используйте 'strict_prod' для защиты от атак через регулярные выражения. * Режим 'prod' оставлен для обратной совместимости, но небезопасен. * * @example * // Для продакшена (обязательно!) * bot.setAppMode('strict_prod'); * * // Для разработки * bot.setAppMode('dev'); * @param appMode */ setAppMode(appMode: TAppMode): this; /** * Позволяет заменить встроенный механизм сопоставления команд на **кастомный алгоритм поиска**. * * По умолчанию `umbot` использует **оптимизированный поиск**: * 1. Сначала проверяется точное совпадение * 2. Если точного совпадения нет — выполняется последовательный перебор с поддержкой: * - подстрок (`includes`), * - простых регулярных выражений. * * Это обеспечивает **предсказуемость**, **простоту отладки** и **соответствие поведению большинства платформ**: * первая совпавшая команда (в порядке регистрации) — выигрывает. * * Однако при: * - количестве команд >1000, * - высокой нагрузке (>1000 RPS), * - необходимости в fuzzy-поиске или сложной маршрутизации, * вы можете подключить оптимизированный resolver. * * @param resolver - Функция вида `(userText: string, commands: Map<string, ICommand>) => string | null`. * Должна вернуть имя команды или `null`, если совпадений нет. * * @example * ```ts * // Пример: кэширование частых запросов * const cache = new Map<string, string | null>(); * bot.setCustomCommandResolver((text, commands) => { * if (cache.has(text)) return cache.get(text)!; * * for (const [name, cmd] of commands) { * if (cmd.slots && cmd.slots.some(slot => typeof slot === 'string' && text.includes(slot))) { * cache.set(text, name); * return name; * } * } * cache.set(text, null); * return null; * }); * ``` * * @remarks * **Рекомендации при реализации resolver'а:** * - Сохраняйте **порядок регистрации команд**, если логика зависит от приоритета. * - Используйте **кэширование** для часто встречающихся фраз (но учитывайте потребление памяти). * - Для fuzzy-поиска — рассмотрите `fuse.js`, `natural` или trie-структуры. * - При работе с регулярными выражениями **обязательно проверяйте их на ReDoS**. * - Избегайте тяжёлых синхронных операций — они блокируют event loop. */ setCustomCommandResolver(resolver: TCommandResolver): this; /** * Задаёт **инфраструктурную конфигурацию** приложения: подключение к БД, загрузку `.env`, и другие * настройки, связанные с окружением выполнения (а не с бизнес-логикой приложения). * * > 🔒 **Безопасность**: никогда не храните секреты (пароли, токены, API-ключи) прямо в коде. * > Всегда используйте `.env`-файлы или переменные окружения. * * @param {IAppConfig} config - Конфигурация приложения * * @example * ```ts * // Конфигурация с базой данных * bot.setAppConfig({ * db: { * host: 'localhost', * database: 'bot_db', * user: 'user', * pass: 'password' * } * }); * * * @remarks * Важно! Чувствительные данные рекомендуется сохранять в .env файл, передав путь к нему: * ```ts * bot.setAppConfig({ * env: './.env', // путь до файла * }); * ``` */ setAppConfig(config: Partial<IAppConfig>): this; /** * Возвращает контекст приложения — центральный объект для расширенной настройки фреймворка. * * **Когда использовать:** * - Замена HTTP-клиента: `bot.getAppContext().httpClient = customFetch` * - Доступ к зарегистрированным адаптерам: `bot.getAppContext().platforms` * - Настройка метрик и логирования * * **Пример:** * ```ts * // Добавление таймаутов к запросам * const customFetch: THttpClient = async (url, init) => { * const controller = new AbortController(); * const timeout = setTimeout(() => controller.abort(), 5000); * return fetch(url, { ...init, signal: controller.signal }); * }; * * bot.getAppContext().httpClient = customFetch; * * ⚠️ Важно: Не модифицируйте внутренние поля контекста напрямую (например, commands, steps). Используйте публичные методы addCommand(), addStep(). */ getAppContext(): AppContext; /** * Задаёт параметры, управляющие **логикой приложения** на всех платформах. * * Сюда входят: * - список интентов по умолчанию (`help`, `welcome` и др.), * - тексты ответов по умолчанию (`welcome_text`, `help_text`, `empty_text`), * - другие настройки, не зависящие от конкретной платформы * * @param {IAppParam} params - Параметры платформы * * @example * ```ts * // Базовая настройка * bot.setPlatformParams({ * intents: [{ * name: 'help', * slots: ['помощь', 'справка'] * }], * welcome_text: 'Привет! Я ваш помощник.', * help_text: 'Скажите "помощь", чтобы увидеть команды.', * empty_text: 'Извините, я не понял.' * }); * ``` */ setPlatformParams(params: IAppParam): this; /** * Устанавливает контроллер, метод `action()` которого вызывается **после обработки каждого запроса** — * независимо от того, была ли распознана команда, активен ли шаг сценария, или обработан базовый интент. * * Метод `action()` получает флаги `isCommand` и `isStep`, чтобы вы могли: * - добавить общую логику (аналитика, логирование, трассировка); * - модифицировать ответ глобально (например, добавить рекламу, кнопку "Оценить"); * - применить кросс-функциональные правила (rate limiting, мутации текста и т.д.); * * > 💡 **Рекомендация**: основную бизнес-логику размещайте в командах (`addCommand`) и шагах (`addStep`), * > а в `action()` — только **сквозную** логику, которую не хочется дублировать. * * Контроллер по умолчанию уже обрабатывает интенты `welcome`, `help` и `fallback`, * но вы можете заменить его, если нужно кастомное поведение. * * @param {BotController<TUserData>} fn - Экземпляр контроллера, наследующий `BotController<TUserData>` * * @example * ```ts * class MyController extends BotController { * public action(intentName: string): void { * switch (intentName) { * case 'greeting': * this.text = 'Привет!'; * break; * case 'help': * this.text = 'Чем могу помочь?'; * break; * } * } * } * * bot.initBotController(MyController); * ``` * * @example * ```ts * // Добавление рекламы ко всем ответам * class MyController extends BotController { * public action( * intentName: string | null, * isCommand: boolean = false, * isStep: boolean = false * ): void { * // Добавим кнопку "Подписаться" везде, кроме шагов * if (!isStep) { * this.buttons.addBtn('Подписаться на рассылку', 'http://localhost'); * } * * // Логирование * console.log(`Обработано: ${isCommand ? 'команда' : isStep ? 'шаг' : 'интент'} – ${intentName}`); * } * } * * bot.initBotController(MyController); * ``` */ initBotController(fn: TBotControllerClass<TUserData>): this; /** * Устанавливает контент запроса. * Используется для передачи данных от пользователя в платформу. * Не рекомендуется использовать напрямую, использовать только в крайнем случае, либо для тестов * * @param {TBotContent} content - Контент запроса * * @example * ```ts * // Установка текстового сообщения * bot.setContent('Привет!'); * * // Установка JSON-данных * bot.setContent({ * request: { * command: 'привет', * original_utterance: 'Привет, мир!' * } * }); * ``` */ setContent(content: TBotContent): void; /** * Очищает состояние пользователя */ protected _clearState(botController: BotController): void; /** * Регистрирует middleware, вызываемый **до** выполнения `BotController.action()`. * * Middleware получает доступ к полному `BotController` (включая `text`, `isEnd`, `userData`, `buttons` и т.д.) * и может: * - Модифицировать контекст * - Прервать выполнение (если не вызвать `next()`) * - Выполнить логирование, tracing, rate limiting и др. * * @example * // Глобальный middleware (для всех платформ) * bot.use(async (ctx, next) => { * console.log('Запрос от:', ctx.appType); * await next(); * }); * * @param fn - Middleware-функция * @returns Текущий экземпляр `Bot` для цепочки вызовов */ use(fn: MiddlewareFn): this; /** * Регистрирует middleware, вызываемый только для указанной платформы. * * @example * // Только для Алисы * bot.use(T_ALISA, async (ctx, next) => { * if (!ctx.appContext.requestObject?.session?.user_id) { * ctx.text = 'Некорректный запрос'; * ctx.isEnd = true; * // next() не вызывается → action() не запускается * return; * } * await next(); * }); * * @param platform - Идентификатор платформы (`alisa`, `telegram`, `vk`, и т.д.) * @param fn - Middleware-функция * @returns Текущий экземпляр `Bot` */ use(platform: TAppType, fn: MiddlewareFn): this; /** * Регистрирует плагин — объект, расширяющий функциональность приложения. * * Плагин может быть как функцией, так и классом. * Если он реализован как метод, то должен быть указан флаг isPlugin. * В случае когда используется класс, то должен быть реализован метод `init(appContext: AppContext)` * * @param plugin — Объект плагина, совместимый с `TPlugin`. * @returns Текущий экземпляр `Bot`. * * @example * import {AlisaAdapter} from 'umbot/plugins' * bot.use(new AlisaAdapter()); */ use(plugin: TPlugin): this; /** * Установка контроллера. Используется только для тестирования * @param botController * @protected */ protected _setBotController(botController: BotController<TUserData>): void; /** * Получение контроллера приложения * Не использовать! * @private */ getBotController(): BotController<TUserData> | null; /** * Выполняет непосредственную обработку входящего запроса от платформы. * Этот метод **не запускает HTTP-сервер** и **не обрабатывает HTTP-запросы напрямую** — * он принимает уже распарсенные данные и возвращает результат обработки. * * Обычно вызывается **внутри {@link webhookHandle}**, но может использоваться напрямую, * если вы реализуете собственный обработчик запросов, тестируете логику приложения * или запускаете его вне HTTP-контекста (например, из консоли или очереди сообщений). * * @param {TAppType | null} [appType] - Тип приложения. Если не указан, будет определен автоматически в зависимости от запроса. * @param {string | object} [content] - Входные данные для обработки (например, текст сообщения или объект запроса). * @returns {Promise<TRunResult>} Результат обработки запроса * @throws {Error} Если не удаётся определить платформу или отсутствуют данные для обработки. * * @example * ```ts * // Обработка запроса * const result = await bot.run(); * console.log(result); * ``` */ run(appType?: TAppType | null, content?: string | object | null): Promise<TRunResult>; /** * Обрабатывает входящий webhook-запрос от поддерживаемой платформы (Telegram, VK, Алиса и др.). * Метод автоматически распознаёт платформу по заголовкам или телу запроса и делегирует обработку * соответствующему адаптеру. Ответ отправляется автоматически через переданный объект `res`. * * @param req - Объект входящего запроса (IncomingMessage или совместимый) * @param res - Объект ответа (ServerResponse или совместимый) * @param responseCb - Callback, для пользовательской обработки ответа пользователю. Стоит использовать в том случае, если есть необходимость переопределить стандартный ответ фреймворка. * Если передан, ВЫ ДОЛЖНЫ вызвать res.end() самостоятельно. * Без колбэка фреймворк автоматически завершит ответ через res.end(). * * @example * ```ts * // Использование с Express * import express from 'express'; * const app = express(); * app.use(express.json()); * * const bot = new Bot(); * * app.post('/webhook', (req, res) => bot.webhookHandle(req, res)); * ``` * @example * // Использование с встроенным HTTP-сервером Node.js * import { createServer } from 'http'; * * const bot = new Bot(); * const server = createServer((req, res) => { * if (req.method === 'POST' && req.url === '/webhook') { * bot.webhookHandle(req, res); * } else { * res.statusCode = 404; * res.end(); * } * }); * server.listen(3000); * @example * ```ts * // Пример с переопределением ответа * import { createServer } from 'http'; * * const bot = new Bot(); * const server = createServer((req, res) => { * if (req.method === 'POST' && req.url === '/webhook') { * // В случае если вернулся статус отличный от 200, вернет содержимое какой-то страницы. * bot.webhookHandle(req, res, (_reg: IncomingMessage, _res: ServerResponse, state: IBotResponseState) => { * if (state.statusCode === 200) { * return state.defaultSend(_res, state); * } * res.statusCode = 200; * res.end(...);// Какое-то содержимое страницы * }); * } else { * res.statusCode = 404; * res.end(); * } * }); * server.listen(3000); * * ``` */ webhookHandle(req: IncomingMessage, res: ServerResponse, responseCb?: TBotResponseCb): Promise<void>; /** * Запускает встроенный HTTP-сервер на указанном хосте и порту для приёма webhook-запросов * от поддерживаемых платформ. Сервер использует нативный `http.createServer`. * * Метод возвращает экземпляр `http.Server`, что позволяет, например, корректно * остановить сервер или добавить обработчики событий (`'listening'`, `'error'` и т.д.). * * Если требуется интеграция с фреймворком (например, Express, Fastify и др.), * рекомендуется использовать {@link webhookHandle} как middleware-обработчик. * * @see webhookHandle * * @param {string} hostname - Имя хоста * @param {number} port - Порт * @param responseCb - Callback, для пользовательской обработки ответа пользователю. Стоит использовать в том случае, если есть необходимость переопределить стандартный ответ фреймворка. * * @example * ```ts * // Запуск встроенного сервера * const bot = new Bot(); * bot.start('0.0.0.0', 8080); * * // Интеграция с Express (вместо встроенного сервера) * import express from 'express'; * import { Bot } from 'umbot'; * * const bot = new Bot(); * const app = express(); * app.use(express.json()); * app.post('/webhook', (req, res) => bot.webhookHandle(req, res)); * * app.listen(3000, () => { * console.log('Bot listening on port 3000'); * }); * ``` * ```ts * // Пример с переопределением ответа * const bot = new Bot(); * // В случае если вернулся статус отличный от 200, вернет содержимое какой-то страницы. * bot.start('0.0.0.0', 8080, (reg: IncomingMessage, _res: ServerResponse, state: IBotResponseState) => { * if (state.statusCode === 200) { * return state.defaultSend(_res, state); * } * res.statusCode = 200; * res.end(...);// Какое-то содержимое страницы * }); * ``` */ start(hostname?: string, port?: number, responseCb?: TBotResponseCb): Server; /** * Корректно завершает работу встроенного HTTP-сервера (если он был запущен через {@link start}). * Ожидает завершения всех текущих запросов, освобождает сетевые ресурсы и отменяет * все активные асинхронные операции, связанные с жизненным циклом приложения. * * Метод безопасен для повторного вызова. * * @returns {Promise<void>} Завершается, когда сервер остановлен и все ресурсы освобождены. * * @example * ```ts * const bot = new Bot(); * bot.start('localhost', 3000); * * // ... позже, при завершении приложения * await bot.close(); * ``` */ close(): Promise<void>; /** * Отправка текста пользователю * Этот метод используется для активных рассылок — когда голосовой навык или чат-бот инициирует диалог первым (например, уведомление). * В методе реализована механика преобразования текстового значения `controllerOrText` в контроллер, а также базовый механизм для отправки ответа. * * Если платформа не поддерживает возможность начать диалог самостоятельно, то вернется false * @param userId Ид пользователя, которому нужно отправить сообщение * @param controllerOrText Контроллер приложения или текст. Если необходимо отправить просто текст, можно передать строку, в случае, если необходимо передать картинку звук и тд, то необходимо корректно заполнить контроллер. * @param platform Платформа, на которую необходимо отправить запрос */ send(userId: string | number, controllerOrText: BotController | string, platform: TAppType): Promise<unknown | boolean>; }