UNPKG

umbot

Version:

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

492 lines (491 loc) 21.3 kB
/** * Ядро конфигурации и типов для umbot. * * Этот файл определяет: * - Режимы работы приложения (dev / prod) * - Структуру конфигурации (пути, БД, токены) * - Формат команд (интентов) * - Систему плагинов (i18n, NLU, RegExp) * * Все типы предназначены для строгой типизации и автодополнения в IDE. */ import { INlu } from '../../components'; /** * Режим работы приложения. * - `dev` — разработка: включены логи, отладка, нет строгих проверок. * - `prod` — продакшн: минимальные логи, валидация включена. * - `strict_prod` — строгий продакшн: запрещены любые отклонения от спецификации платформ, включена полная валидация. */ export type TAppMode = 'dev' | 'prod' | 'strict_prod'; /** * Тип для HTTP-клиента, используемого в приложении. * * Должен соответствовать fetch-совместимому API. * Может быть заменён на кастомную реализацию (например, с таймаутами, retry). * * @example * ```ts * const customFetch: THttpClient = async (url, init) => { * const controller = new AbortController(); * const timeout = setTimeout(() => controller.abort(), 5000); * try { * return await fetch(url, { ...init, signal: controller.signal }); * } finally { * clearTimeout(timeout); * } * }; * ``` */ export type THttpClient = (url: URL | RequestInfo, init?: RequestInit) => Promise<Response>; /** * @interface IDir * Интерфейс для работы с директориями * * Используется для указания пути к файлу и его имени * при сохранении данных. * * @example * ```ts * const dir: IDir = { * path: './data', * fileName: 'config.json' * }; * ``` */ export interface IDir { /** * Путь к директории * * Может быть абсолютным или относительным путем * к директории, где будет сохранен файл. */ path: string; /** * Имя файла * * Имя файла, который будет создан в указанной директории. */ fileName: string; } /** * Поддерживаемые платформы приложения. * * Допустимые значения: * - `T_ALISA` — Яндекс.Алиса * - `T_VK` — ВКонтакте * - `T_TELEGRAM` — Telegram * - `T_VIBER` — Viber * - `T_MARUSIA` — Маруся (Mail.ru) * - `T_SMART_APP` — Сбер SmartApp * - `T_MAX_APP` — MAX * - `string` — любое произвольное имя платформы, для которой реализован адаптер * * @remarks * Использование неизвестного значения может привести к отсутствию адаптера и ошибкам выполнения. */ export type TAppType = string; /** * Константы для метрик */ export declare enum EMetric { REQUEST = "umbot_http_request_duration_ms", GET_INTENT = "umbot_get-intent_duration_ms", GET_COMMAND = "umbot_get-command_duration_ms", ACTION = "umbot_action_duration_ms", MIDDLEWARE = "umbot_middleware_duration_ms", START_WEBHOOK = "umbot_request_start", END_WEBHOOK = "umbot_request_duration_ms", DB_UPDATE = "umbot_db_update_ms", DB_INSERT = "umbot_db_insert_ms", DB_REMOVE = "umbot_db_remove_ms", DB_SELECT = "umbot_db_select_ms" } /** * @interface IAppDB * Параметры подключения к базе данных * * Определяет параметры для подключения к базе данных, * включая хост, учетные данные и дополнительные опции. * * @example * ```ts * const dbConfig: IAppDB = { * host: 'localhost', * user: 'admin', * pass: 'password', * database: 'bot_db', * options: { * port: 3306, * charset: 'utf8mb4' * } * }; * ``` */ export interface IAppDB { /** * Адрес сервера базы данных * * Может быть указан как localhost или IP-адрес сервера. */ host: string; /** * Имя пользователя для подключения */ user?: string; /** * Пароль для подключения */ pass?: string; /** * Название базы данных */ database: string; /** * Дополнительные параметры подключения * * Могут включать специфичные для БД параметры, * такие как порт, кодировка и т.д. */ options?: Record<string, unknown>; } /** * @interface IAppIntent * Конфигурация интента (команды) * * Определяет структуру команды, включая ее имя, * триггеры активации и флаг использования регулярных выражений. * * @example * ```ts * // Простая команда * const intent: IAppIntent = { * name: 'greeting', * slots: ['привет', 'здравствуй'], * is_pattern: false * }; * * // Команда с регулярным выражением * const patternIntent: IAppIntent = { * name: 'numbers', * slots: ['\\b\\d{3}\\b'], * is_pattern: true * }; * ``` */ export interface IAppIntent { /** * Уникальный идентификатор команды */ name: string; /** * Триггеры активации команды. * * Может содержать строки (поиск подстроки) и/или регулярные выражения. * При наличии хотя бы одного `RegExp` параметр `is_pattern` игнорируется — * каждый элемент обрабатывается по своему типу. * * @example * ```ts * // Простые слова * slots: ['привет', 'здравствуй'] * * // Регулярное выражение для чисел * slots: ['\\b\\d{3}\\b'] * * // Строки + RegExp в одном слоте * slots: ['привет', /\b\+7\s?\d{3}\b/] * ``` */ slots: (string | RegExp)[]; /** * Флаг использования регулярных выражений * * Если true, строки в slots интерпретируются как регулярные выражения. * * @defaultValue false */ is_pattern?: boolean; } /** * Интерфейс для сохранения токенов */ export interface ITokenPlatform { /** * Токены, и дополнительная информация(например секреты), которые необходимы для корректной работы указанной платформы. */ [platformName: string]: { token?: string; [name: string]: string | number | undefined; }; } /** * Основная конфигурация приложения * * Определяет основные настройки приложения, включая пути к директориям, * конфигурацию базы данных, токены платформ и флаги режимов работы. * * @example * ```ts * const config: IAppConfig = { * error_log: './logs', * json: './data', * db: { * host: 'localhost', * database: 'bot_db' * }, * isLocalStorage: true, * env: '.env' * }; * ``` */ export interface IAppConfig { /** * Путь к директории для логов ошибок */ error_log?: string; /** * Путь к директории для JSON файлов */ json?: string; /** * Конфигурация базы данных */ db?: IAppDB; /** * Флаг использования локального хранилища платформы. * Перед включением флага, убедитесь что платформа поддерживает хранение данные в локальном хранилище. * Также некоторые платформы требуют, чтобы была установлена соответствующая настройка у приложения. * * При указании опции в значение true, фреймворк автоматически сохранит все данные из userData в платформу, * Также все данные от платформы записываются в userData. * Рекомендуется использовать в том случае, если вам не нужна база данных. * * В случае, если платформа не поддерживает работу с локальным хранилищем, то работа с данными осуществляется через базу данных. * Рекомендуется подключать адаптер для работы с базой данных, иначе данные для некоторых платформ могут потеряться. */ isLocalStorage?: boolean; /** * Путь к файлу с переменными окружения(.env). * Если указан путь к .env-файлу, фреймворк попытается загрузить переменные из него. Если файл не найден — используются переменные из process.env. * Также если передать значение `local`, то данные будут взяты из `process.env` */ env?: string; /** * Список различных токенов. Стоит использовать в ситуациях, когда для корректной работы платформы, необходимо указывать токен. * НЕ рекомендуется его как-либо заполнять автоматически находясь вне адаптера платформы */ tokens?: ITokenPlatform; } /** * @interface IAppParam * Параметры приложения для платформ * * Содержит дополнительные параметры для платформ, а также тексты приветствия и помощи. * * @example * ```ts * const params: IAppParam = { * welcome_text: 'Привет! Чем могу помочь?', * help_text: 'Список доступных команд: ...', * intents: [ * { * name: 'greeting', * slots: ['привет', 'здравствуй'], * is_pattern: false * } * ] * }; * ``` */ export interface IAppParam { /** * Флаг говорящий о том, что для работы приложения необходима авторизация. * Сам способ авторизации обрабатывается на стороне платформы. * В случае если платформа не поддерживает механизм авторизации, саму авторизацию может реализовать разработчик приложения */ isAuthUser?: boolean; /** * Текст приветствия * * Может быть строкой или массивом вариантов. */ welcome_text?: string | string[]; /** * Текст помощи * * Может быть строкой или массивом вариантов. */ help_text?: string | string[]; /** * Текст, который будет показан, если нет подходящих команд не найдено * * Может быть строкой или массивом вариантов. */ empty_text?: string | string[]; /** * Массив интентов (команд) приложения * * * @example * ```ts * intents: [ * { * name: 'greeting', * slots: [ * '\\b{привет}\\b', // Точное совпадение слова * '\\b{привет}[^\\s]+\\b', // Начало слова (например, "привет" найдет "приветствие") * '(\\b{привет}(|[^\\s]+)\\b)', // Точное совпадение или начало слова * '\\b(\\d{3})\\b', // Числа от 100 до 999 * 'привет \\d друг', // Шаблон с числом между словами * '{привет}' // Любое вхождение слова * /\d{0, 3}/i // Поиск числа от 0 до 999 * ], * is_pattern: true * } * ] * ``` * * Где _value_ - это значение, которое необходимо найти. * Например, если _value_ = "привет", то регулярное выражение '\\b{привет}\\b' будет искать точное совпадение слова "привет". * * @remarks * **Важно о безопасности регулярных выражений:** * Фреймворк автоматически проверяет все регулярные выражения (как переданные через `RegExp`, так и строки с `is_pattern: true`) * на уязвимости типа ReDoS. В зависимости от режима работы приложения: * - `strict_prod` — опасные выражения будут отклонены с ошибкой. * - `prod` — опасные выражения будут отфильтрованы (исключены) с предупреждением в логах. * - `dev` — проверка выполняется, но выражение не удаляется (только предупреждение). * * Рекомендуется тестировать регулярные выражения в режиме `dev` перед выкаткой в продакшн. * * @see {@link Bot.setAppMode} для настройки режима. */ intents: IAppIntent[] | null; /** * UTM-метка для ссылок * * @defaultValue utm_source=Yandex_Alisa&utm_medium=cpc&utm_campaign=phone */ utm_text?: string | null; } /** * Общий интерфейс для плагина-объекта с методом `getData`. * `TArgs` — кортеж типов аргументов, `TResult` — тип возвращаемого значения. */ export interface IAppPlugin<TArgs extends unknown[] = unknown[], TResult = unknown> { /** * Основной метод плагина. Вызывается с аргументами, зависящими от типа плагина. */ getData: (...args: TArgs) => TResult; } /** * Альтернативный способ задать плагин — через функцию. */ export type IAppPluginFn<TArgs extends unknown[] = unknown[], TResult = unknown> = (...args: TArgs) => TResult; /** * Общий тип, который объединяет IAppPlugin и IAppPluginFn */ export type TAppPluginData<TArgs extends unknown[] = unknown[], TResult = unknown> = IAppPlugin<TArgs, TResult> | IAppPluginFn<TArgs, TResult>; /** * Тип для подключения любого пользовательского плагина. */ export type AnyPluginData = TAppPluginData<[key: string, ...params: unknown[]], string> | TAppPluginData<[text: string, platformNlu: INlu, platform: string, request: unknown], INlu> | TAppPluginData<[], RegExpConstructor> | TAppPluginData; /** * Реестр плагинов приложения. * * ⚠️ ВАЖНО: по умолчанию плагины `i18n`, `nlu` и `regExp` **НЕ подключены**. * Если вы хотите использовать один из них — вы **обязаны** зарегистрировать его самостоятельно. * * === Как зарегистрировать плагин? === * * 1. Создайте реализацию как объект с методом `init` или как функцию: * * // Вариант 1: объект * class MyI18nPlugin implements IPlugin { * init(appContext: AppContext<IDatabaseInfo>) { * appContext.plugins['i18n'] = { * getData(key: string, ...params: unknown[]): string { * return `Translated: ${key}`; * } * }; * } * } * * // Вариант 2: функция * const myNluPlugin: IPluginFn = (appContext: AppContext) => { * appContext.plugins['nlu'] = (text: string, ctx?: unknown) => ({ * intent: 'default', * entities: {} * }); * }; * myNluPlugin.isPlugin = true; // маркер обязательного наличия * * 2. Подключите плагин к приложению: * bot.use(new MyI18nPlugin()); * // или * bot.use(myNluPlugin); * * === Требования к реализации === * * - Плагин должен устанавливать значение в `appContext.plugins['имя']`. * - Имя может быть: * • `i18n` — должен соответствовать сигнатуре `TAppPluginData<[key: string, ...params: unknown[]], string>` * • `nlu` — должен соответствовать `TAppPluginData<[text: string, platformNlu: INlu, platform: string, request: unknown], INlu>` * • `regExp` — должен соответствовать `TAppPluginData<[], RegExpConstructor>` * • любое другое имя — произвольная сигнатура `AnyPluginData` * * === Примеры корректных реализаций === * * appContext.plugins['i18n'] = (key: string, ...params: unknown[]) => `Hello, ${params[0]}`; * appContext.plugins['nlu'] = (text, platformNlu, platform, request) => ({ intent: 'greet', entities: {} }); * appContext.plugins['regExp'] = () => RegExp; * appContext.plugins['custom'] = (a, b, c) => a + b + c; */ export interface TAppPlugin { /** * Плагин интернационализации. * Вызывается как: `plugins.i18n?.getData('greeting', user)` * или, если функция: `plugins.i18n?.('greeting', user)` */ i18n?: TAppPluginData<[key: string, ...params: unknown[]], string>; /** * Плагин NLU. * * Принимает: * - `text` — исходный текст запроса; * - `platformNlu` — предварительно заполненная платформой структура INlu; * - `platform` — имя платформы (например, 'alisa', 'telegram'); * - `request` — оригинальный объект запроса от платформы. * * Второй аргумент — предварительно заполненная платформой структура INlu * (например, в Алисе — с original_utterance, session, user и т.д.). * Плагин должен обогатить её intent'ом и entities. * * @example * ```ts * nlu: (text, platformNlu, platform, request) => { * return { * ...platformNlu, * intent: detectIntent(text), * entities: extractEntities(text) * }; * } * ``` */ nlu?: TAppPluginData<[ text: string, platformNlu: INlu, platform: string, request: unknown ], INlu>; /** * Плагин для получения конструктора регулярных выражений. * Вызывается **без аргументов**: `plugins.regExp?.getData()` или `plugins.regExp?.()` */ regExp?: TAppPluginData<[], RegExpConstructor>; /** * Произвольные пользовательские плагины. * Каждый — либо объект с `getData`, либо функция. */ [name: string]: AnyPluginData | undefined; }