fix(basic.gblib): #227 - HEAR AS FILE and GET/SET PARAM.

This commit is contained in:
rodrigorodriguez 2023-02-01 11:15:58 -03:00
parent 82a9ba983e
commit 2521117a80
6 changed files with 156 additions and 75 deletions

View file

@ -75,6 +75,7 @@
"adm-zip": "0.5.9", "adm-zip": "0.5.9",
"alasql": "2.1.6", "alasql": "2.1.6",
"any-shell-escape": "0.1.1", "any-shell-escape": "0.1.1",
"arraybuffer-to-buffer": "^0.0.7",
"async-promises": "0.2.3", "async-promises": "0.2.3",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"billboard.js": "3.6.3", "billboard.js": "3.6.3",

View file

@ -83,15 +83,16 @@ export class AnalyticsService {
public async createMessage ( public async createMessage (
instanceId: number, instanceId: number,
conversation: GuaribasConversation, conversationId: number,
userId: number, userId: number,
content: string content: string
): Promise<GuaribasConversationMessage> { ): Promise<GuaribasConversationMessage> {
const message = GuaribasConversationMessage.build(); const message = GuaribasConversationMessage.build();
message.content = typeof content === 'object' ? JSON.stringify(content) : content; message.content = typeof content === 'object' ? JSON.stringify(content) : content;
message.instanceId = instanceId; message.instanceId = instanceId;
message.userId = userId; message.userId = userId;
message.conversationId = conversation.conversationId; message.conversationId = conversationId;
return await message.save(); return await message.save();
} }

View file

@ -588,8 +588,29 @@ export class DialogKeywords {
this['id'] = this.sys().getRandomId(); this['id'] = this.sys().getRandomId();
} }
private isUserSystemParam(name: string): Boolean {
const names = [
'welcomed',
'loaded',
'subjects',
'cb',
'welcomed',
'maxLines',
'translatorOn',
'wholeWord',
'theme',
'maxColumns'
];
return names.indexOf(name) > -1;
}
private async setOption({pid, name, value}) private async setOption({pid, name, value})
{ {
if (this.isUserSystemParam(name)){
throw new Error(`Not possible to define ${name} as it is a reserved system param name.`);
}
const process = GBServer.globals.processes[pid]; const process = GBServer.globals.processes[pid];
let { min, user, params } = await DialogKeywords.getProcessInfo(pid); let { min, user, params } = await DialogKeywords.getProcessInfo(pid);
const sec = new SecService(); const sec = new SecService();
@ -598,6 +619,17 @@ export class DialogKeywords {
return { min, user, params }; return { min, user, params };
} }
private async getOption({pid, name})
{
if (this.isUserSystemParam(name)){
throw new Error(`Not possible to retrieve ${name} system param.`);
}
const process = GBServer.globals.processes[pid];
let { min, user, params } = await DialogKeywords.getProcessInfo(pid);
const sec = new SecService();
return await sec.getParam(user, name);
}
/** /**
* Defines the maximum lines to scan in spreedsheets. * Defines the maximum lines to scan in spreedsheets.
* *
@ -608,6 +640,27 @@ export class DialogKeywords {
await this.setOption({pid, name: "maxLines", value: count}); await this.setOption({pid, name: "maxLines", value: count});
} }
/**
* Defines a custom user param to be persisted to storage.
*
* @example SET PARAM name AS value
*
*/
public async setUserParam({ pid, name, value }) {
await this.setOption({pid, name, value});
}
/**
* Returns a custom user param persisted on storage.
*
* @example GET PARAM name
*
*/
public async getUserParam({ pid, name }) {
await this.getOption({pid, name});
}
/** /**
* Defines the maximum lines to scan in spreedsheets. * Defines the maximum lines to scan in spreedsheets.
* *
@ -772,12 +825,14 @@ export class DialogKeywords {
await sleep(DEFAULT_HEAR_POLL_INTERVAL); await sleep(DEFAULT_HEAR_POLL_INTERVAL);
} }
const text = min.cbMap[userId].promise; const answer = min.cbMap[userId].promise;
if (kind === 'file') { if (kind === 'file') {
GBLog.info(`BASIC (${min.botId}): Upload done for ${answer.filename}.`);
// TODO: answer.filename, answer.data.
} else if (kind === 'boolean') { } else if (kind === 'boolean') {
if (isIntentYes('pt-BR', text)) { if (isIntentYes('pt-BR', answer)) {
result = true; result = true;
} else { } else {
result = false; result = false;
@ -787,7 +842,7 @@ export class DialogKeywords {
return text.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)/gi); return text.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)/gi);
}; };
const value = extractEntity(text); const value = extractEntity(answer);
if (value === null) { if (value === null) {
await this.talk({ pid, text: 'Por favor, digite um e-mail válido.' }); await this.talk({ pid, text: 'Por favor, digite um e-mail válido.' });
@ -800,7 +855,7 @@ export class DialogKeywords {
return text.match(/[_a-zA-Z][_a-zA-Z0-9]{0,16}/gi); return text.match(/[_a-zA-Z][_a-zA-Z0-9]{0,16}/gi);
}; };
const value = extractEntity(text); const value = extractEntity(answer);
if (value === null || value.length != 1) { if (value === null || value.length != 1) {
await this.talk({ pid, text: 'Por favor, digite um nome válido.' }); await this.talk({ pid, text: 'Por favor, digite um nome válido.' });
@ -813,7 +868,7 @@ export class DialogKeywords {
return text.match(/\d+/gi); return text.match(/\d+/gi);
}; };
const value = extractEntity(text); const value = extractEntity(answer);
if (value === null || value.length != 1) { if (value === null || value.length != 1) {
await this.talk({ pid, text: 'Por favor, digite um número válido.' }); await this.talk({ pid, text: 'Por favor, digite um número válido.' });
@ -828,7 +883,7 @@ export class DialogKeywords {
); );
}; };
const value = extractEntity(text); const value = extractEntity(answer);
if (value === null || value.length != 1) { if (value === null || value.length != 1) {
await this.talk({ pid, text: 'Por favor, digite uma data no formato 12/12/2020.' }); await this.talk({ pid, text: 'Por favor, digite uma data no formato 12/12/2020.' });
@ -841,7 +896,7 @@ export class DialogKeywords {
return text.match(/^([0-1]?[0-9]|2[0-4]):([0-5][0-9])(:[0-5][0-9])?$/gi); return text.match(/^([0-1]?[0-9]|2[0-4]):([0-5][0-9])(:[0-5][0-9])?$/gi);
}; };
const value = extractEntity(text); const value = extractEntity(answer);
if (value === null || value.length != 1) { if (value === null || value.length != 1) {
await this.talk({ pid, text: 'Por favor, digite um horário no formato hh:ss.' }); await this.talk({ pid, text: 'Por favor, digite um horário no formato hh:ss.' });
@ -860,7 +915,7 @@ export class DialogKeywords {
return []; return [];
}; };
const value = extractEntity(text); const value = extractEntity(answer);
if (value === null || value.length != 1) { if (value === null || value.length != 1) {
await this.talk({ pid, text: 'Por favor, digite um valor monetário.' }); await this.talk({ pid, text: 'Por favor, digite um valor monetário.' });
@ -872,7 +927,7 @@ export class DialogKeywords {
let phoneNumber; let phoneNumber;
try { try {
// https://github.com/GeneralBots/BotServer/issues/307 // https://github.com/GeneralBots/BotServer/issues/307
phoneNumber = phone(text, { country: 'BRA' })[0]; phoneNumber = phone(answer, { country: 'BRA' })[0];
phoneNumber = phoneUtil.parse(phoneNumber); phoneNumber = phoneUtil.parse(phoneNumber);
} catch (error) { } catch (error) {
await this.talk({ pid, text: Messages[locale].validation_enter_valid_mobile }); await this.talk({ pid, text: Messages[locale].validation_enter_valid_mobile });
@ -897,7 +952,7 @@ export class DialogKeywords {
} }
}; };
const value = extractEntity(text); const value = extractEntity(answer);
if (value === null || value.length != 1) { if (value === null || value.length != 1) {
await this.talk({ pid, text: 'Por favor, digite um CEP válido.' }); await this.talk({ pid, text: 'Por favor, digite um CEP válido.' });
@ -909,7 +964,7 @@ export class DialogKeywords {
const list = args; const list = args;
result = null; result = null;
await CollectionUtil.asyncForEach(list, async item => { await CollectionUtil.asyncForEach(list, async item => {
if (GBConversationalService.kmpSearch(text, item) != -1) { if (GBConversationalService.kmpSearch(answer, item) != -1) {
result = item; result = item;
} }
}); });
@ -939,8 +994,8 @@ export class DialogKeywords {
await CollectionUtil.asyncForEach(list, async item => { await CollectionUtil.asyncForEach(list, async item => {
if ( if (
GBConversationalService.kmpSearch(text.toLowerCase(), item.name.toLowerCase()) != -1 || GBConversationalService.kmpSearch(answer.toLowerCase(), item.name.toLowerCase()) != -1 ||
GBConversationalService.kmpSearch(text.toLowerCase(), item.code.toLowerCase()) != -1 GBConversationalService.kmpSearch(answer.toLowerCase(), item.code.toLowerCase()) != -1
) { ) {
result = item.code; result = item.code;
} }

View file

@ -659,6 +659,20 @@ export class GBVMService extends GBService {
} }
]; ];
keywords[i++] = [
/^\s*set param \s*(.*)\s*as\s*(.*)/gim,
($0, $1, $2) => {
return `await dk.setUserParam ({pid: pid, ${$1}}, ${$2})`;
}
];
keywords[i++] = [
/^\s*get param \s*(.*)/gim,
($0, $1, $2) => {
return `await dk.getUserParam ({pid: pid, ${$1}})`;
}
];
keywords[i++] = [ keywords[i++] = [
/^\s*set header\s*(.*)\s*as\s*(.*)/gim, /^\s*set header\s*(.*)\s*as\s*(.*)/gim,
($0, $1, $2) => { ($0, $1, $2) => {

View file

@ -46,6 +46,8 @@ import { FacebookAdapter } from 'botbuilder-adapter-facebook';
import path from 'path'; import path from 'path';
import mkdirp from 'mkdirp'; import mkdirp from 'mkdirp';
import Fs from 'fs'; import Fs from 'fs';
import arrayBufferToBuffer from 'arraybuffer-to-buffer';
import { import {
AutoSaveStateMiddleware, AutoSaveStateMiddleware,
BotFrameworkAdapter, BotFrameworkAdapter,
@ -826,11 +828,11 @@ export class GBMinService {
// Get loaded user state // Get loaded user state
const member = context.activity.from;
const step = await min.dialogs.createContext(context); const step = await min.dialogs.createContext(context);
step.context.activity.locale = 'pt-BR'; step.context.activity.locale = 'pt-BR';
let firstTime = false; let firstTime = false;
const member = context.activity.from;
const sec = new SecService(); const sec = new SecService();
const user = await sec.ensureUser(instance.instanceId, member.id, member.name, '', 'web', member.name, null); const user = await sec.ensureUser(instance.instanceId, member.id, member.name, '', 'web', member.name, null);
const userId = user.userId; const userId = user.userId;
@ -1061,36 +1063,28 @@ export class GBMinService {
private static async downloadAttachmentAndWrite(attachment) { private static async downloadAttachmentAndWrite(attachment) {
const url = attachment.contentUrl; const url = attachment.contentUrl;
// https://github.com/GeneralBots/BotServer/issues/195 - '${botId}','uploads'); // TODO: https://github.com/GeneralBots/BotServer/issues/195 - '${botId}','uploads');
const localFolder = Path.join('work'); const localFolder = Path.join('work');
const localFileName = Path.join(localFolder, this['botId'],'uploads', attachment.name); const localFileName = Path.join(localFolder, `${this['min'].botId}.gbai`, 'uploads', attachment.name);
try { let res;
let response; if (url.startsWith('data:')) {
if (url.startsWith('data:')) { var regex = /^data:.+\/(.+);base64,(.*)$/;
var regex = /^data:.+\/(.+);base64,(.*)$/; var matches = url.match(regex);
var matches = url.match(regex); var ext = matches[1];
var ext = matches[1]; var data = matches[2];
var data = matches[2]; res = Buffer.from(data, 'base64');
response = Buffer.from(data, 'base64'); } else {
} else { // arraybuffer is necessary for images
// arraybuffer is necessary for images const options = {
const options = { method: 'GET',
method: 'GET', encoding: 'binary'
encoding: 'binary' };
}; res = await fetch(url, options);
response = await fetch(url, options); const buffer = arrayBufferToBuffer(await res.arrayBuffer());
} Fs.writeFileSync(localFileName, buffer);
Fs.writeFile(localFileName, response, fsError => {
if (fsError) {
throw fsError;
}
});
} catch (error) {
console.error(error);
return undefined;
} }
// If no error was thrown while writing to disk,return the attachment's name // If no error was thrown while writing to disk,return the attachment's name
// and localFilePath for the response back to the user. // and localFilePath for the response back to the user.
return { return {
@ -1133,48 +1127,62 @@ export class GBMinService {
context.activity.text = context.activity.text.trim(); context.activity.text = context.activity.text.trim();
const user = await min.userProfile.get(context, {}); const member = context.activity.from;
let user = await sec.ensureUser(min.instance.instanceId, member.id, member.name, '', 'web', member.name, null);
const userId = user.userId;
const params = user.params ? JSON.parse(user.params) : {};
let message: GuaribasConversationMessage; let message: GuaribasConversationMessage;
if (process.env.PRIVACY_STORE_MESSAGES === 'true') { if (process.env.PRIVACY_STORE_MESSAGES === 'true') {
// Adds message to the analytics layer. // Adds message to the analytics layer.
const analytics = new AnalyticsService(); const analytics = new AnalyticsService();
if (user) { if (user) {
if (!user.conversation) { let conversation;
user.conversation = await analytics.createConversation(user.systemUser); if (!user.conversationId) {
conversation = await analytics.createConversation(user);
user.conversationId = conversation.Id;
} }
message = await analytics.createMessage( message = await analytics.createMessage(
min.instance.instanceId, min.instance.instanceId,
user.conversation, user.conversationId,
user.systemUser.userId, userId,
context.activity.text context.activity.text
); );
} }
} }
if (process.env.ENABLE_DOWNLOAD) { // Prepare Promises to download each attachment and then execute each Promise.
// Prepare Promises to download each attachment and then execute each Promise.
const promises = step.context.activity.attachments.map(GBMinService.downloadAttachmentAndWrite.bind(min)); const promises = step.context.activity.attachments.map(
const successfulSaves = await Promise.all(promises); GBMinService.downloadAttachmentAndWrite.bind({ min, user, params })
async function replyForReceivedAttachments(localAttachmentData) { );
if (localAttachmentData) { const successfulSaves = await Promise.all(promises);
// Because the TurnContext was bound to this function,the bot can call async function replyForReceivedAttachments(localAttachmentData) {
// `TurnContext.sendActivity` via `this.sendActivity`; if (localAttachmentData) {
await this.sendActivity(`Upload OK.`); // Because the TurnContext was bound to this function,the bot can call
} else { // `TurnContext.sendActivity` via `this.sendActivity`;
await this.sendActivity('Error uploading file. Please,start again.'); await this.sendActivity(`Upload OK.`);
} } else {
await this.sendActivity('Error uploading file. Please,start again.');
} }
// Prepare Promises to reply to the user with information about saved attachments. }
// The current TurnContext is bound so `replyForReceivedAttachments` can also send replies. // Prepare Promises to reply to the user with information about saved attachments.
const replyPromises = successfulSaves.map(replyForReceivedAttachments.bind(step.context)); // The current TurnContext is bound so `replyForReceivedAttachments` can also send replies.
await Promise.all(replyPromises); const replyPromises = successfulSaves.map(replyForReceivedAttachments.bind(step.context));
await Promise.all(replyPromises);
if (successfulSaves.length > 0) {
const result = { const result = {
data: Fs.readFileSync(successfulSaves[0]['localPath']), data: Fs.readFileSync(successfulSaves[0]['localPath']),
filename: successfulSaves[0]['fileName'] filename: successfulSaves[0]['fileName']
}; };
if (min.cbMap[userId] && min.cbMap[userId].promise == '!GBHEAR') {
min.cbMap[userId].promise = result;
}
} }
// Files in .gbdialog can be called directly by typing its name normalized into JS . // Files in .gbdialog can be called directly by typing its name normalized into JS .
@ -1298,13 +1306,11 @@ export class GBMinService {
'Language Detector', 'Language Detector',
GBConfigService.getBoolean('LANGUAGE_DETECTOR') GBConfigService.getBoolean('LANGUAGE_DETECTOR')
) === 'true'; ) === 'true';
const systemUser = user.systemUser; locale = user.locale;
locale = systemUser.locale;
if (text != '' && detectLanguage && !locale) { if (text != '' && detectLanguage && !locale) {
locale = await min.conversationalService.getLanguage(min, text); locale = await min.conversationalService.getLanguage(min, text);
if (systemUser.locale != locale) { if (user.locale != locale) {
user.systemUser = await sec.updateUserLocale(systemUser.userId, locale); user = await sec.updateUserLocale(user.userId, locale);
await min.userProfile.set(step.context, user);
} }
} }
@ -1340,10 +1346,10 @@ export class GBMinService {
GBLog.info(`Text>: ${text}.`); GBLog.info(`Text>: ${text}.`);
if (user.systemUser.agentMode === 'self') { if (user.agentMode === 'self') {
const manualUser = await sec.getUserFromAgentSystemId(user.systemUser.userSystemId); const manualUser = await sec.getUserFromAgentSystemId(user.userSystemId);
GBLog.info(`HUMAN AGENT (${user.systemUser.userSystemId}) TO USER ${manualUser.userSystemId}: ${text}`); GBLog.info(`HUMAN AGENT (${user.userId}) TO USER ${manualUser.userSystemId}: ${text}`);
const cmd = 'SEND FILE '; const cmd = 'SEND FILE ';
if (text.startsWith(cmd)) { if (text.startsWith(cmd)) {
@ -1366,8 +1372,8 @@ export class GBMinService {
); );
} }
} else { } else {
if (min.cbMap[user.systemUser.userId] && min.cbMap[user.systemUser.userId].promise == '!GBHEAR') { if (min.cbMap[userId] && min.cbMap[userId].promise == '!GBHEAR') {
min.cbMap[user.systemUser.userId].promise = text; min.cbMap[userId].promise = text;
} }
// If there is a dialog in course, continue to the next step. // If there is a dialog in course, continue to the next step.

View file

@ -36,6 +36,7 @@
'use strict'; 'use strict';
import { GuaribasConversation } from '../../analytics.gblib/models/index.js';
import { import {
AutoIncrement, AutoIncrement,
BelongsTo, BelongsTo,
@ -97,11 +98,14 @@ export class GuaribasUser extends Model<GuaribasUser> {
@Column(DataType.TEXT) @Column(DataType.TEXT)
declare conversationReference: string; declare conversationReference: string;
@Column(DataType.INTEGER)
declare conversationId: number;
@Column(DataType.STRING(64)) @Column(DataType.STRING(64))
declare hearOnDialog: string; declare hearOnDialog: string;
@Column(DataType.STRING(4000)) @Column(DataType.STRING(4000))
declare params: string; declare params: string;
} }
/** /**