UNPKG

umbot

Version:

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

363 lines (362 loc) 21.3 kB
"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;