umbot
Version:
Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф
320 lines (319 loc) • 13.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SmartAppAdapter = void 0;
const index_1 = require("../../../index");
const Base_1 = require("../Base/Base");
const Button_1 = require("./Button");
const Card_1 = require("./Card");
const constants_1 = require("./constants");
/**
* Адаптер, обеспечивающий полную поддержку платформы Салют от Сбер(SmartApp). Позволяет разрабатывать навыки для "Салют"(SmartApp) на TypeScript с использованием всего функционала платформы: от обработки голосовых запросов до работы с карточками и кнопками.
*
* Этот адаптер автоматически обрабатывает входящие webhook`и от SmartApp,
* преобразует их в унифицированный формат фреймворка и формирует ответ,
* совместимый с требованиями платформы. Подключается одной строкой и
* не мешает работе других адаптеров (например, для Telegram или VK).
*
* Поддерживает:
* - голосовые и текстовые запросы;
* - карточки, кнопки, TTS-эффекты;
*
* Подключается как любой другой адаптер: `bot.use(new SmartAppAdapter(token))`.
* Несколько адаптеров могут работать одновременно — система сама выберет подходящий
* на основе заголовков и структуры входящего запроса.
* @example
* // Простейший навык, который отвечает на приветствие
* import { Bot } from 'umbot';
* import { SmartAppAdapter } from 'umbot/plugins';
*
* const bot = new Bot()
* .use(new SmartAppAdapter('YOUR_SMARTAPP_TOKEN'))
* .addCommand('start', ['привет'], (_text, ctx) => {
* ctx.text = 'Привет! Я твой первый навык для SmartApp';
* });
*
* bot.start('localhost', 3000);
*
* @see Bot
* @see BotController
* @see BasePlatform
*/
class SmartAppAdapter extends Base_1.BasePlatform {
platformName = constants_1.T_SMART_APP;
isPlatformOnQuery(query, headers) {
if (headers?.['x-sber-smartapp-webhook-token'] || headers?.['x-sber-token']) {
return true;
}
if (!query) {
this.appContext?.logWarn(`SmartAppAdapter.isPlatformOnQuery(): ${Base_1.EMPTY_QUERY_ERROR}`);
return false;
}
return !!(query.messageName &&
query.uuid &&
query.payload?.character &&
query.payload?.app_info);
}
/**
* Инициализирует команду пользователя.
* Обрабатывает различные типы сообщений и событий
* @param content Объект webhook-запроса SmartApp
* @param controller Экземпляр контроллера приложения
*
* Поддерживаемые типы сообщений:
* - MESSAGE_TO_SKILL: сообщение пользователя
* - CLOSE_APP: закрытие приложения
* - SERVER_ACTION: действие сервера
* - RUN_APP: запуск приложения
* - RATING_RESULT: результат оценки
*/
#initUserCommand(content, controller) {
controller.requestObject = content;
controller.messageId = content.messageId;
switch (content.messageName) {
case 'MESSAGE_TO_SKILL':
case 'CLOSE_APP': {
const msg = content.payload?.message;
controller.userCommand = msg?.normalized_text ?? '';
controller.originalUserCommand = msg?.original_text ?? '';
break;
}
case 'SERVER_ACTION':
case 'RUN_APP':
controller.payload = content.payload?.server_action?.parameters;
/*if (typeof controller.payload === 'string') {
controller.userCommand = controller.originalUserCommand = controller.payload;
}*/
if (content.messageName === 'RUN_APP') {
controller.messageId = 0;
controller.originalUserCommand = controller.userCommand;
controller.userCommand = '';
}
break;
case 'RATING_RESULT':
controller.payload = content.payload;
controller.messageId = 0;
controller.userEvents = {
rating: {
status: content.payload.status_code?.code === 1,
value: content.payload.rating?.estimation,
},
};
break;
}
if (!controller.userCommand) {
controller.userCommand = controller.originalUserCommand;
}
}
setQueryData(query, controller) {
if (this.appContext) {
if (query) {
this.#initUserCommand(query, controller);
controller.platformOptions.session = {
device: query.payload.device,
meta: query.payload.meta,
sessionId: query.sessionId,
messageId: query.messageId,
uuid: query.uuid,
projectName: query.payload.projectName,
};
controller.oldIntentName = query.payload.intent;
controller.appeal = query.payload.character?.appeal ?? null;
controller.userId = query.uuid.userId;
const msg = query.payload.message;
const nlu = {
entities: msg?.entities,
tokens: msg?.tokenized_elements_list,
};
controller.nlu.setNlu(nlu);
controller.userMeta = query.payload.meta || {};
controller.platformOptions.appId = query.payload.app_info.applicationId;
if (query.payload.device?.capabilities?.screen) {
controller.isScreen = query.payload.device.capabilities.screen.available;
}
else {
controller.isScreen = true;
}
return true;
}
else {
controller.platformOptions.error = `SmartAppAdapter.isPlatformOnQuery(): ${Base_1.EMPTY_QUERY_ERROR}`;
}
}
else {
console.error(`SmartAppAdapter.isPlatformOnQuery(): ${Base_1.EMPTY_CONTEXT_ERROR}`);
}
return false;
}
/**
* Формирует ответ для пользователя.
* Собирает текст, TTS, карточки и кнопки в единый объект ответа
* @returns {Promise<ISberSmartAppResponsePayload>} Объект ответа для SmartApp
*/
#getPayload(controller) {
const payload = {
pronounceText: controller.text,
pronounceTextType: 'application/text',
device: controller.platformOptions.session.device,
intent: controller.thisIntentName,
projectName: controller.platformOptions.session.projectName,
auto_listening: !controller.isEnd,
finished: controller.isEnd,
};
if (controller.emotion) {
payload.emotion = {
emotionId: controller.emotion,
};
}
if (controller.text) {
payload.items = [
{
bubble: {
text: index_1.Text.resize(controller.text, 250),
markdown: true,
expand_policy: 'auto_expand',
},
},
];
}
if (controller.tts) {
payload.pronounceText = controller.tts;
payload.pronounceTextType = 'application/ssml';
}
if (controller.isScreen) {
if (controller.isCardInit() && controller.card.images.length) {
payload.items ??= [];
const cards = controller.card.getCards(Card_1.cardProcessing, controller);
if (cards) {
payload.items.push(cards);
}
}
payload.suggestions = {
buttons: controller.isButtonsInit()
? controller.buttons.getButtons(Button_1.buttonProcessing)
: [],
};
}
if (controller.isEnd) {
payload.items ??= [];
payload.items.push({
command: {
type: 'close_app',
},
});
}
return payload;
}
getContent(controller) {
const result = {
messageName: 'ANSWER_TO_USER',
sessionId: controller.platformOptions.session.sessionId,
messageId: controller.platformOptions.session.messageId,
uuid: controller.platformOptions.session.uuid,
};
if (controller.sound.sounds.length) {
controller.tts ??= controller.text;
}
result.payload = this.#getPayload(controller);
this._timeLimitLog(controller);
return result;
}
/**
* Формирует ответ с оценкой навыка
* @returns {Promise<ISberSmartAppWebhookResponse>} Объект ответа для webhook`а
*/
getRatingContext(controller) {
return {
messageName: 'CALL_RATING',
sessionId: controller.platformOptions.session.sessionId,
messageId: controller.platformOptions.session.messageId,
uuid: controller.platformOptions.session.uuid,
payload: {},
};
}
/**
* Получает данные пользователя из хранилища
* @returns {Promise<unknown | string>} Данные пользователя или строка с ошибкой
* @protected
*/
async _getUserData(controller) {
const request = new index_1.Request(controller.appContext);
request.url = `https://smartapp-code.sberdevices.ru/tools/api/data/${controller.userId}`;
const result = await request.send();
if (result.status && result.data) {
return result.data;
}
return {};
}
/**
* Сохраняет данные пользователя в хранилище
*/
async _setUserData(data, controller) {
const request = new index_1.Request(controller.appContext);
request.header = index_1.Request.HEADER_JSON;
request.url = `https://smartapp-code.sberdevices.ru/tools/api/data/${controller.userId}`;
request.post = data;
return await request.send();
}
async setLocalStorage(data, controller) {
await this._setUserData(data, controller);
}
/**
* Получает данные из локального хранилища
* @returns Данные из хранилища или строка с ошибкой
*/
getLocalStorage(controller) {
return this._getUserData(controller);
}
isLocalStorage() {
return true;
}
send() {
return false;
}
getQueryExample(query, userId, count) {
return {
messageName: 'MESSAGE_TO_SKILL',
uuid: { userId: `${+userId}`, userChannel: '', sub: '' },
messageId: count,
sessionId: `${userId}`,
payload: {
device: constants_1.DEVICE,
app_info: {
projectId: '',
applicationId: '',
appversionId: '',
},
character: {
id: 'sber',
name: 'Сбер',
gender: 'male',
appeal: 'official',
},
intent: '',
original_intent: '',
intent_meta: {},
meta: {
time: {
timezone_id: '',
timezone_offset_sec: 0,
timestamp: Date.now(),
},
},
projectName: 'test',
annotations: constants_1.ANNOTATIONS,
strategies: {
happy_birthday: false,
last_call: 0,
},
new_session: count === 0,
message: {
normalized_text: query,
original_text: query,
asr_normalized_message: '',
tokenized_elements_list: [],
},
},
};
}
}
exports.SmartAppAdapter = SmartAppAdapter;