umbot
Version:
Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф
492 lines (491 loc) • 21.3 kB
TypeScript
/**
* Ядро конфигурации и типов для 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;
}