umbot
Version:
Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф
363 lines (362 loc) • 21.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Preload = void 0;
/**
* Модуль для предварительной загрузки медиаресурсов (изображений, звуков) на серверы платформ.
*
* Позволяет заранее получить и закэшировать токены медиафайлов, чтобы при ответе пользователю
* не тратить время на загрузку. Особенно полезно для платформ с жёсткими тайм-аутами
* (Алиса, Маруся, Сбер Салют), где время ответа ограничено 2–3 секундами.
*
* **Важно:** Для Telegram токен можно получить только отправив файл конкретному пользователю.
* Используйте опцию `telegramUseId` в методах `loadImages` / `loadSounds`.
*
* @packageDocumentation
* @module preload
*/
const models_1 = require("./models");
const plugins_1 = require("./plugins");
const controller_1 = require("./controller");
const IMAGE_MAP = {
[plugins_1.T_ALISA]: plugins_1.AlisaCard,
[plugins_1.T_MARUSIA]: plugins_1.MarusiaCard,
[plugins_1.T_VK]: plugins_1.VkCard,
[plugins_1.T_MAX_APP]: plugins_1.MaxCard,
[plugins_1.T_TELEGRAM]: plugins_1.TelegramCard,
};
const SOUND_MAP = {
[plugins_1.T_ALISA]: plugins_1.AlisaSound,
[plugins_1.T_MARUSIA]: plugins_1.MarusiaSound,
[plugins_1.T_VK]: plugins_1.VkSound,
[plugins_1.T_TELEGRAM]: plugins_1.TelegramSound,
};
const PLATFORMS = {
[plugins_1.T_ALISA]: 'alisa',
[plugins_1.T_MARUSIA]: 'marusia',
[plugins_1.T_VK]: 'vk',
[plugins_1.T_TELEGRAM]: 'telegram',
[plugins_1.T_VIBER]: 'viber',
[plugins_1.T_MAX_APP]: 'max',
};
/**
* Класс, предназначенный для предварительной загрузки медиаресурсов (изображений, звуков) для различных платформ.
*
* Этот класс позволяет загрузить файлы на серверы платформ и закэшировать их токены *до* начала обработки
* пользовательских запросов, что помогает сократить время обработки пользовательского запроса при
* первичной отправке медиафайлов в ответе.
*
* @example
* // В точке входа приложения (например, index.ts)
* import { Preload } from 'umbot/preload';
*
* // Предположим, appContext уже инициализирован
* const preload = new Preload(appContext);
*
* // Загрузить изображения для Алисы и ВК
* const imagePromises = preload.loadImages(['/path/to/img1.jpg', '/path/to/img2.png'], [T_ALISA, T_VK]);
* // Загрузить звуки только для Маруси
* const soundPromises = preload.loadSounds(['/path/to/sound1.mp3'], [T_MARUSIA]);
*
* // Запустить загрузку (все промисы выполняются параллельно)
* try {
* await Promise.all(imagePromises);
* console.log('Изображения предзагружены');
* await Promise.all(soundPromises);
* console.log('Звуки предзагружены');
* } catch (error) {
* console.error('Ошибка при предзагрузке:', error);
* }
*
* // Теперь приложение готово к работе, и первые вызовы с медиа будут быстрыми
* bot.start('localhost', 3000);
*/
class Preload {
_appContext;
_controller;
/**
* Создает экземпляр класса Preload.
*
* @param {AppContext} [appContext] - Контекст приложения, необходимый для доступа к БД и конфигурации.
* Может быть установлен позже через `setAppContext`.
*/
constructor(appContext) {
this.setAppContext(appContext);
this._controller = new controller_1.BaseBotController(appContext);
}
/**
* Устанавливает контекст приложения.
*
* @param {AppContext} [appContext] - Контекст приложения.
*/
setAppContext(appContext) {
this._appContext = appContext;
this._controller.setAppContext(appContext);
}
/**
* Возвращает список платформ, для которых в конфигурации `AppContext` указаны токены.
* Если передан массив `platforms`, результат фильтруется по этому списку.
*
* @param {TAppType[]} [platforms] - Массив типов платформ для фильтрации.
* @returns {TAppType[]} Массив доступных платформ.
*/
_getPlatforms(platforms) {
const result = [];
if (this._appContext) {
const platformParams = this._appContext.appConfig.tokens;
// Проверяем наличие токенов для каждой платформы
Object.keys(PLATFORMS).forEach((key) => {
if (platformParams[PLATFORMS[key]]
?.token) {
if (!platforms || platforms.includes(key)) {
result.push(key);
}
}
});
}
return result;
}
/**
* Возвращает внутренний тип изображения, используемый в `ImageTokens`, для указанной платформы.
*
* @param {TAppType} platform - Тип платформы.
* @returns {number | undefined} Тип изображения для `ImageTokens` или `undefined`, если платформа не поддерживается
* или не требует предзагрузки (например, Telegram).
*/
_getImageType(platform) {
switch (platform) {
case plugins_1.T_ALISA:
return plugins_1.T_ALISA;
case plugins_1.T_MARUSIA:
return plugins_1.T_MARUSIA;
case plugins_1.T_VK:
return plugins_1.T_VK;
case plugins_1.T_MAX_APP:
return plugins_1.T_MAX_APP;
case plugins_1.T_TELEGRAM:
return plugins_1.T_TELEGRAM;
}
return undefined;
}
/**
* Возвращает внутренний тип звука, используемый в `SoundTokens`, для указанной платформы.
*
* @param {TAppType} platform - Тип платформы.
* @returns {number | undefined} Тип звука для `SoundTokens` или `undefined`, если платформа не поддерживается
* или не требует предзагрузки (например, Telegram).
*/
_getSoundType(platform) {
switch (platform) {
case plugins_1.T_ALISA:
return plugins_1.T_ALISA;
case plugins_1.T_MARUSIA:
return plugins_1.T_MARUSIA;
case plugins_1.T_VK:
return plugins_1.T_VK;
case plugins_1.T_TELEGRAM:
return plugins_1.T_TELEGRAM;
}
return undefined;
}
/**
* Подготавливает промисы для удаления изображений с серверов платформ и из базы данных.
*
* Этот метод *не запускает* выполнение промисов. Для выполнения удаления необходимо
* использовать `Promise.all()` или `Promise.allSettled()` с возвращаемым массивом.
*
* @param {string[]} images - Массив путей к файлам изображений для удаления.
* @param {TAppType[]} [platforms] - Массив типов платформ для фильтрации. Если не указан, обрабатываются все доступные.
* @returns {Promise<boolean>[]} Массив промисов, каждый из которых разрешается `true` при успешном удалении
* или `false` при ошибке.
*/
removeImages(images, platforms) {
const allowedPlatforms = this._getPlatforms(platforms);
const promises = [];
if (allowedPlatforms.length && this._appContext) {
const imageTokensModel = new models_1.ImageTokens(this._appContext);
images.forEach((image) => {
allowedPlatforms.forEach((platform) => {
const type = this._getImageType(platform);
if (type !== undefined) {
const removePromise = (async () => {
try {
const tokenRecord = await imageTokensModel.where({
path: image,
type,
});
if (tokenRecord && imageTokensModel.imageToken) {
let apiRequest;
let req;
if (platform === plugins_1.T_ALISA || platform === plugins_1.T_MARUSIA) {
if (platform === plugins_1.T_ALISA) {
apiRequest = new plugins_1.YandexImageRequest(null, null, this._appContext);
req = await apiRequest.deleteImage(imageTokensModel.imageToken);
}
else {
apiRequest = new plugins_1.MarusiaRequest(this._appContext);
req = await apiRequest.marusiaDeletePicture(imageTokensModel.imageToken);
}
if (req) {
await imageTokensModel.remove();
}
// eslint-disable-next-line require-atomic-updates
imageTokensModel.imageToken = null;
}
}
return true;
}
catch (error) {
this._appContext?.logError(`Ошибка при удалении изображения "${image}" на платформе ${platform}:`, error);
return false;
}
})();
promises.push(removePromise);
}
});
});
}
return promises;
}
/**
* Подготавливает промисы для удаления звуков с серверов платформ и из базы данных.
*
* Этот метод *не запускает* выполнение промисов. Для выполнения удаления необходимо
* использовать `Promise.all()` или `Promise.allSettled()` с возвращаемым массивом.
*
* @param {string[]} sounds - Массив путей к файлам звуков для удаления.
* @param {TAppType[]} [platforms] - Массив типов платформ для фильтрации. Если не указан, обрабатываются все доступные.
* @returns {Promise<boolean>[]} Массив промисов, каждый из которых разрешается `true` при успешном удалении
* или `false` при ошибке.
*/
removeSounds(sounds, platforms) {
const allowedPlatforms = this._getPlatforms(platforms);
const promises = [];
if (allowedPlatforms.length && this._appContext) {
const soundTokensModel = new models_1.SoundTokens(this._appContext);
sounds.forEach((sound) => {
allowedPlatforms.forEach((platform) => {
const type = this._getSoundType(platform);
if (type !== undefined) {
const removePromise = (async () => {
try {
// Аналогично removeImages, но для SoundTokens
const tokenRecord = await soundTokensModel.where({
path: sound,
type,
}); // Предполагаем метод getOneByPathAndType
if (tokenRecord && soundTokensModel.soundToken) {
let apiRequest;
let req;
if (platform === plugins_1.T_ALISA || platform === plugins_1.T_MARUSIA) {
if (platform === plugins_1.T_ALISA) {
apiRequest = new plugins_1.YandexSoundRequest(null, null, this._appContext);
req = await apiRequest.deleteSound(soundTokensModel.soundToken);
}
else {
apiRequest = new plugins_1.MarusiaRequest(this._appContext);
req = await apiRequest.marusiaDeleteAudio(soundTokensModel.soundToken);
}
if (req) {
await soundTokensModel.remove();
}
// eslint-disable-next-line require-atomic-updates
soundTokensModel.soundToken = null;
}
}
return true;
}
catch (error) {
this._appContext?.logError(`Ошибка при удалении звука "${sound}" на платформе ${platform}:`, error);
return false;
}
})();
promises.push(removePromise);
}
});
});
}
return promises;
}
/**
* Подготавливает промисы для загрузки и кэширования изображений для указанных платформ.
*
* Этот метод *не запускает* выполнение промисов. Для выполнения загрузки необходимо
* использовать `Promise.all()` или `Promise.allSettled()` с возвращаемым массивом.
*
* @param {string[]} images - Массив путей к файлам изображений для загрузки.
* @param {TAppType[]} [platforms] - Массив типов платформ для фильтрации. Если не указан, обрабатываются все доступные.
* @param opts - Дополнительные опции для загрузки. Так как в Telegram не получить токен без отправки файла пользователю, можно отправить файл произвольному пользователю, который будет передан в свойстве.
* @returns {Promise<(string | null)[]>[]} Массив промисов, каждый из которых разрешается токеном изображения
* или `null` в случае ошибки или если платформа не поддерживается.
*/
loadImages(images, platforms, opts) {
const allowedPlatforms = this._getPlatforms(platforms);
const promises = [];
if (allowedPlatforms.length && this._appContext) {
// Создаем один экземпляр ImageTokens и переиспользуем его
const imageToken = new models_1.ImageTokens(this._appContext);
images.forEach((image) => {
allowedPlatforms.forEach((platform) => {
const type = this._getImageType(platform);
// Проверяем, что тип определен перед установкой
if (type !== undefined) {
imageToken.path = image;
imageToken.platform = type;
if (type === plugins_1.T_TELEGRAM) {
if (opts?.telegramUseId) {
this._controller.userId = opts.telegramUseId;
promises.push(plugins_1.TelegramCard.getImageInDB(this._controller, image, ''));
}
}
else {
// Добавляем промис в массив
promises.push(IMAGE_MAP[type].getImageInDB(this._controller, image, ''));
}
}
});
});
}
return promises;
}
/**
* Подготавливает промисы для загрузки и кэширования звуков для указанных платформ.
*
* Этот метод *не запускает* выполнение промисов. Для выполнения загрузки необходимо
* использовать `Promise.all()` или `Promise.allSettled()` с возвращаемым массивом.
*
* @param {string[]} sounds - Массив путей к файлам звуков для загрузки.
* @param {TAppType[]} [platforms] - Массив типов платформ для фильтрации. Если не указан, обрабатываются все доступные.
* @param opts - Дополнительные опции для загрузки. Так как в телеграм не получить токен без отправки файла пользователю, можно отправить файл произвольному пользователю, который будет передан в свойстве.
* @returns {Promise<(string | null)[]>[]} Массив промисов, каждый из которых разрешается токеном звука
* или `null` в случае ошибки или если платформа не поддерживается.
*/
loadSounds(sounds, platforms, opts) {
const allowedPlatforms = this._getPlatforms(platforms);
const promises = [];
if (allowedPlatforms.length && this._appContext) {
// Создаем один экземпляр SoundTokens и переиспользуем его
const soundToken = new models_1.SoundTokens(this._appContext);
sounds.forEach((sound) => {
allowedPlatforms.forEach((platform) => {
const type = this._getSoundType(platform);
// Проверяем, что тип определен перед установкой
if (type !== undefined) {
soundToken.path = sound;
soundToken.platform = type;
if (type === plugins_1.T_TELEGRAM) {
if (opts?.telegramUseId) {
this._controller.userId = opts.telegramUseId;
promises.push(plugins_1.TelegramSound.getSoundInDB(this._controller, sound));
}
}
else {
// Добавляем промис в массив
promises.push(SOUND_MAP[type].getSoundInDB(this._controller, sound));
}
}
});
});
}
return promises;
}
}
exports.Preload = Preload;