This commit is contained in:
rodrigorodriguez 2023-03-04 16:27:25 -03:00
parent 4314a37916
commit f3b7c1d77e
11 changed files with 235 additions and 340 deletions

View file

@ -67,6 +67,9 @@
"@microsoft/microsoft-graph-client": "3.0.4",
"@nlpjs/basic": "4.26.1",
"@nosferatu500/textract": "3.1.2",
"@push-rpc/core": "^1.5.7",
"@push-rpc/http": "^1.5.7",
"@push-rpc/websocket": "^1.5.7",
"@semantic-release/changelog": "5.0.1",
"@semantic-release/exec": "5.0.0",
"@semantic-release/git": "9.0.0",
@ -171,6 +174,7 @@
"whatsapp-web.js": "github:pedroslopez/whatsapp-web.js#fix-buttons-list",
"winston": "3.8.2",
"winston-logs-display": "1.0.0",
"ws": "^8.12.1",
"yarn": "1.22.19"
},
"devDependencies": {

View file

@ -39,16 +39,43 @@
import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { GuaribasSchedule } from '../core.gbapp/models/GBModel.js';
import { Sequelize } from 'sequelize-typescript';
import { createServerRouter } from 'typescript-rest-rpc/lib/server.js';
import { DialogKeywords } from './services/DialogKeywords.js';
import { SystemKeywords } from './services/SystemKeywords.js';
import { WebAutomationServices } from './services/WebAutomationServices.js';
import { ImageProcessingServices } from './services/ImageProcessingServices.js';
import { DebuggerService } from './services/DebuggerService.js';
import * as koaBody from 'koa-body';
import Koa from 'koa';
import {createRpcServer, createRpcClient} from "@push-rpc/core"
import {createKoaHttpMiddleware, createExpressHttpMiddleware, createHttpClient} from "@push-rpc/http"
import { GBServer } from '../../src/app.js';
const app = new Koa();
import {SocketServer} from "@push-rpc/core"
import * as koaBody from "koa-body"
export function createKoaHttpServer(
port: number,
getRemoteId: (ctx: Koa.Context) => string
): SocketServer {
const {onError, onConnection, middleware} = createKoaHttpMiddleware(getRemoteId)
const app = new Koa()
app.use(koaBody.koaBody({multipart: true}))
app.use(middleware)
const server = app.listen(port)
return {
onError,
onConnection,
close(cb) {
server.close(cb)
},
}
}
/**
* Package for core.gbapp.
@ -60,9 +87,49 @@ export class GBBasicPackage implements IGBPackage {
public async loadPackage (core: IGBCoreService, sequelize: Sequelize): Promise<void> {
core.sequelize.addModels([GuaribasSchedule]);
//app.use(koaBody.koaBody({ multipart: true, }));
app.listen(1111);
const dk = new DialogKeywords();
const wa = new WebAutomationServices();
const sys = new SystemKeywords();
const dbg = new DebuggerService();
const img = new ImageProcessingServices();
// remote id is required for assigning separate HTTP requests to a single session
function getRemoteId(ctx: Koa.Context) {
return "1" // share a single session for now, real impl could use cookies or some other meaning for HTTP sessions
}
GBServer.globals.server.dk = createRpcServer(dk, createKoaHttpServer(5555, getRemoteId),{
listeners: {
unsubscribed(subscriptions: number): void {},
subscribed(subscriptions: number): void {},
disconnected(remoteId: string, connections: number): void {
console.log(`Client ${remoteId} disconnected`)
},
connected(remoteId: string, connections: number): void {
console.log(`New client ${remoteId} connected`)
},
messageIn(...params): void {
console.log("IN ", params)
},
messageOut(...params): void {
console.log("OUT ", params)
},
},
pingSendTimeout: null,
keepAliveTimeout: null,
})
console.log("RPC Server started at ws://localhost:5555")
// GBServer.globals.wa = createRpcServer(wa, createWebsocketServer({port: 1112}));
// GBServer.globals.sys = createRpcServer(sys, createWebsocketServer({port: 1113}));
// GBServer.globals.dbg = createRpcServer(dbg, createWebsocketServer({port: 1114}));
// GBServer.globals.img = createRpcServer(img, createWebsocketServer({port: 1115}));
}
public async getDialogs (min: GBMinInstance) {
@ -81,28 +148,16 @@ export class GBBasicPackage implements IGBPackage {
GBLog.verbose(`onExchangeData called.`);
}
public async loadBot (min: GBMinInstance): Promise<void> {
const dk = new DialogKeywords(min, null, null);
const wa = new WebAutomationServices(min, null, dk);
const sys = new SystemKeywords(min, null, dk, wa);
const dbg = new DebuggerService(min, null, dk);
const img = new ImageProcessingServices(min, null, dk);
dk.wa = wa;
wa.sys = sys;
const dialogRouter = createServerRouter(`/api/v2/${min.botId}/dialog`, dk);
const waRouter = createServerRouter(`/api/v2/${min.botId}/webautomation`, wa);
const sysRouter = createServerRouter(`/api/v2/${min.botId}/system`, sys);
const dbgRouter = createServerRouter(`/api/v2/${min.botId}/debugger`, dbg);
const imgRouter = createServerRouter(`/api/v2/${min.botId}/imageprocessing`, dbg);
app.use(dialogRouter.routes());
app.use(sysRouter.routes());
app.use(waRouter.routes());
app.use(dbgRouter.routes());
app.use(imgRouter.routes());
app.use(async (ctx, next) => {
if(ctx['status'] === 404){
ctx.status = 404
ctx.body = {msg:'emmmmmmm, seems 404'};
}
});
const botId = min.botId;
GBServer.globals.debuggers[botId] = {};
GBServer.globals.debuggers[botId].state = 0;
GBServer.globals.debuggers[botId].breaks = [];
GBServer.globals.debuggers[botId].stateInfo = 'Stopped';
GBServer.globals.debuggers[botId].childProcess = null;
GBServer.globals.debuggers[botId].client = null;
GBServer.globals.debuggers[botId].conversationsMap = {};
GBServer.globals.debuggers[botId].watermarkMap = {};
}
}

View file

@ -44,44 +44,6 @@ import { spawn } from 'child_process';
* Web Automation services of conversation to be called by BASIC.
*/
export class DebuggerService {
/**
* Reference to minimal bot instance.
*/
public min: GBMinInstance;
/**
* Reference to the base system keywords functions to be called.
*/
public dk: DialogKeywords;
/**
* Current user object to get BASIC properties read.
*/
public user;
/**
* HTML browser for conversation over page interaction.
*/
browser: any;
sys: any;
/**
* The number used in this execution for HEAR calls (useful for SET SCHEDULE).
*/
hrOn: string;
userId: GuaribasUser;
debugWeb: boolean;
lastDebugWeb: Date;
/**
* SYSTEM account maxLines,when used with impersonated contexts (eg. running in SET SCHEDULE).
*/
maxLines: number = 2000;
conversationsMap = {};
watermarkMap = {};
static systemVariables = [
'AggregateError',
'Array',
@ -173,28 +135,6 @@ export class DebuggerService {
'valueOf'
];
/**
* When creating this keyword facade,a bot instance is
* specified among the deployer service.
*/
constructor (min: GBMinInstance, user, dk) {
this.min = min;
this.user = user;
this.dk = dk;
this.debugWeb = this.min.core.getParam<boolean>(this.min.instance, 'Debug Web Automation', false);
const botId = min.botId;
GBServer.globals.debuggers[botId] = {};
GBServer.globals.debuggers[botId].state = 0;
GBServer.globals.debuggers[botId].breaks = [];
GBServer.globals.debuggers[botId].stateInfo = 'Stopped';
GBServer.globals.debuggers[botId].childProcess = null;
}
private client;
public async breakpoint ({ botId, line }) {
GBLog.info(`BASIC: Enabled breakpoint for ${botId} on ${line}.`);
GBServer.globals.debuggers[botId].breaks.push(Number.parseInt(line));
@ -239,14 +179,18 @@ export class DebuggerService {
}
public async context ({ botId }) {
const conversationId = this.conversationsMap[botId];
const conversationsMap = GBServer.globals.debuggers[botId].conversationsMap;
const watermarkMap = GBServer.globals.debuggers[botId].watermarkMap;
const conversationId = conversationsMap[botId];
let messages = [];
if (this.client) {
const response = await this.client.Conversations.Conversations_GetActivities({
const client = GBServer.globals.debuggers[botId].client;
if (client) {
const response = await client.Conversations.Conversations_GetActivities({
conversationId: conversationId,
watermark: this.watermarkMap[botId]
watermark: watermarkMap[botId]
});
this.watermarkMap[botId] = response.obj.watermark;
watermarkMap[botId] = response.obj.watermark;
let activities = response.obj.activites;
if (activities && activities.length) {
@ -272,6 +216,8 @@ export class DebuggerService {
}
public async getRunning ({ botId, botApiKey, scriptName }) {
const conversationsMap = GBServer.globals.debuggers[botId].conversationsMap;
let error;
botId = botId[0];
if (!GBServer.globals.debuggers[botId]) {
@ -296,20 +242,21 @@ export class DebuggerService {
let min: GBMinInstance = GBServer.globals.minInstances.filter(p => p.instance.botId === botId)[0];
this.client = await new Swagger({
GBServer.globals.debuggers[botId].client = await new Swagger({
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
usePromise: true
});
this.client.clientAuthorizations.add(
const client = GBServer.globals.debuggers[botId].client;
client.clientAuthorizations.add(
'AuthorizationBotConnector',
new Swagger.ApiKeyAuthorization('Authorization', `Bearer ${min.instance.webchatKey}`, 'header')
);
const response = await this.client.Conversations.Conversations_StartConversation();
const response = await client.Conversations.Conversations_StartConversation();
const conversationId = response.obj.conversationId;
this.conversationsMap[botId] = conversationId;
conversationsMap[botId] = conversationId;
GBServer.globals.debugConversationId = conversationId;
this.client.Conversations.Conversations_PostActivity({
client.Conversations.Conversations_PostActivity({
conversationId: conversationId,
activity: {
textFormat: 'plain',

View file

@ -69,67 +69,6 @@ const DEFAULT_HEAR_POLL_INTERVAL = 500;
* Base services of conversation to be called by BASIC.
*/
export class DialogKeywords {
/**
* Reference to minimal bot instance.
*/
public min: GBMinInstance;
/**
* Reference to the base system keywords functions to be called.
*/
public internalSys: SystemKeywords;
/**
* Current user object to get BASIC properties read.
*/
public user;
/**
* HTML browser for conversation over page interaction.
*/
browser: any;
/**
* The number used in this execution for HEAR calls (useful for SET SCHEDULE).
*/
hrOn: string;
userId: GuaribasUser;
debugWeb: boolean;
lastDebugWeb: Date;
public wa;
/**
* SYSTEM account maxLines,when used with impersonated contexts (eg. running in SET SCHEDULE).
*/
maxLines: number = 2000;
public async getDeployer() {
return this.min.deployService;
}
public async getMin() {
return this.min;
}
/**
* When creating this keyword facade,a bot instance is
* specified among the deployer service.
*/
constructor(min: GBMinInstance, deployer: GBDeployer, user) {
this.min = min;
this.user = user;
this.internalSys = new SystemKeywords(min, deployer, this, null);
this.debugWeb = this.min.core.getParam<boolean>(this.min.instance, 'Debug Web Automation', false);
}
/**
* Base reference of system keyword facade,called directly
* by the script.
*/
public sys(): SystemKeywords {
return this.internalSys;
}
/**
*
@ -144,7 +83,8 @@ export class DialogKeywords {
* @param legends
* @see https://www.npmjs.com/package/plot
*/
public async chart({ type, data, legends, transpose }) {
public async chart({pid, type, data, legends, transpose }) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
let table = [[]];
if (legends) {
@ -195,12 +135,12 @@ export class DialogKeywords {
};
}
const gbaiName = `${this.min.botId}.gbai`;
const gbaiName = `${min.botId}.gbai`;
const localName = Path.join('work', gbaiName, 'cache', `img${GBAdminService.getRndReadableIdentifier()}.jpg`);
await ChartServices.screenshot(definition, localName);
const url = urlJoin(GBServer.globals.publicAddress, this.min.botId, 'cache', Path.basename(localName));
const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', Path.basename(localName));
GBLog.info(`BASIC: Visualization: Chart generated at ${url}.`);
@ -228,7 +168,8 @@ export class DialogKeywords {
*
* @example x = TODAY
*/
public async getToday({}) {
public async getToday({pid}) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
let d = new Date(),
month = '' + (d.getMonth() + 1),
day = '' + d.getDate(),
@ -241,8 +182,8 @@ export class DialogKeywords {
day = '0' + day;
}
const contentLocale = this.min.core.getParam<string>(
this.min.instance,
const contentLocale = min.core.getParam(
min.instance,
'Default Content Language',
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
);
@ -316,9 +257,10 @@ export class DialogKeywords {
* @example day = WEEKDAY (date)
*
*/
public getWeekFromDate(pid, date) {
const contentLocale = this.min.core.getParam<string>(
this.min.instance,
public async getWeekFromDate({pid, date}) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
const contentLocale = min.core.getParam(
min.instance,
'Default Content Language',
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
);
@ -465,7 +407,7 @@ export class DialogKeywords {
return i;
}
const contentLocale = this.min.core.getParam<string>(
const contentLocale = min.core.getParam(
min.instance,
'Default Content Language',
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
@ -488,9 +430,10 @@ export class DialogKeywords {
* @example SAVE "contacts.xlsx", name, email, NOW
*
*/
public async getNow({}) {
const contentLocale = this.min.core.getParam<string>(
this.min.instance,
public async getNow({pid}) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
const contentLocale = min.core.getParam(
min.instance,
'Default Content Language',
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
);
@ -574,9 +517,10 @@ export class DialogKeywords {
* @example SET LANGUAGE "pt"
*
*/
public async setLanguage({ language }) {
public async setLanguage({ pid, language }) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
const sec = new SecService();
await sec.updateUserLocale(this.user.userId, language);
await sec.updateUserLocale(user.userId, language);
}
/**
@ -587,7 +531,7 @@ export class DialogKeywords {
*/
public async setIdGeneration({ mode }) {
this['idGeneration'] = mode;
this['id'] = this.sys().getRandomId();
this['id'] = new SystemKeywords().getRandomId();
}
private isUserSystemParam(name: string): Boolean {
@ -751,8 +695,7 @@ export class DialogKeywords {
*
*/
public async getHear({ pid, kind, arg }) {
const process = GBServer.globals.processes[pid];
let { min, user } = await DialogKeywords.getProcessInfo(pid);
let { min, user, params } = await DialogKeywords.getProcessInfo(pid);
// Handles first arg as an array of args.
@ -772,8 +715,8 @@ export class DialogKeywords {
// containing the specified user other than the actual user
// TODO: Store hrOn in processInfo.
if (this.hrOn) {
user = await sec.getUserFromAgentSystemId(this.hrOn);
if (params.hrOn) {
user = await sec.getUserFromAgentSystemId(params.hrOn);
}
const userId = user.userId;
@ -831,7 +774,7 @@ export class DialogKeywords {
// Retrieves the .xlsx file associated with the HEAR var AS file.xlsx.
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min);
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
const botId = min.instance.botId;
const path = urljoin(`${botId}.gbai`, `${botId}.gbdata`);
let url = `${baseUrl}/drive/root:/${path}:/children`;
@ -884,13 +827,13 @@ export class DialogKeywords {
// In case of unmatch, asks the person to try again.
if (result === null) {
await this.getTalk({ pid, text: `Escolha por favor um dos itens sugeridos.` });
await this.talk({ pid, text: `Escolha por favor um dos itens sugeridos.` });
return await this.getHear({ pid, kind, arg });
}
} else if (kind === 'file') {
GBLog.info(`BASIC (${min.botId}): Upload done for ${answer.filename}.`);
const handle = WebAutomationServices.cyrb53(this.min.botId + answer.filename);
const handle = WebAutomationServices.cyrb53(min.botId + answer.filename);
GBServer.globals.files[handle] = answer;
result = handle;
} else if (kind === 'boolean') {
@ -907,7 +850,7 @@ export class DialogKeywords {
const value = extractEntity(answer);
if (value === null) {
await this.getTalk({ pid, text: 'Por favor, digite um e-mail válido.' });
await this.talk({ pid, text: 'Por favor, digite um e-mail válido.' });
return await this.getHear({ pid, kind, arg });
}
@ -920,7 +863,7 @@ export class DialogKeywords {
const value = extractEntity(answer);
if (value === null || value.length != 1) {
await this.getTalk({ pid, text: 'Por favor, digite um nome válido.' });
await this.talk({ pid, text: 'Por favor, digite um nome válido.' });
return await this.getHear({ pid, kind, arg });
}
@ -933,7 +876,7 @@ export class DialogKeywords {
const value = extractEntity(answer);
if (value === null || value.length != 1) {
await this.getTalk({ pid, text: 'Por favor, digite um número válido.' });
await this.talk({ pid, text: 'Por favor, digite um número válido.' });
return await this.getHear({ pid, kind, arg });
}
@ -948,7 +891,7 @@ export class DialogKeywords {
const value = extractEntity(answer);
if (value === null || value.length != 1) {
await this.getTalk({ 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.' });
return await this.getHear({ pid, kind, arg });
}
@ -961,7 +904,7 @@ export class DialogKeywords {
const value = extractEntity(answer);
if (value === null || value.length != 1) {
await this.getTalk({ 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.' });
return await this.getHear({ pid, kind, arg });
}
@ -980,7 +923,7 @@ export class DialogKeywords {
const value = extractEntity(answer);
if (value === null || value.length != 1) {
await this.getTalk({ pid, text: 'Por favor, digite um valor monetário.' });
await this.talk({ pid, text: 'Por favor, digite um valor monetário.' });
return await this.getHear({ pid, kind, arg });
}
@ -992,12 +935,12 @@ export class DialogKeywords {
phoneNumber = phone(answer, { country: 'BRA' })[0];
phoneNumber = phoneUtil.parse(phoneNumber);
} catch (error) {
await this.getTalk({ pid, text: Messages[locale].validation_enter_valid_mobile });
await this.talk({ pid, text: Messages[locale].validation_enter_valid_mobile });
return await this.getHear({ pid, kind, arg });
}
if (!phoneUtil.isPossibleNumber(phoneNumber)) {
await this.getTalk({ pid, text: 'Por favor, digite um número de telefone válido.' });
await this.talk({ pid, text: 'Por favor, digite um número de telefone válido.' });
return await this.getHear({ pid, kind, arg });
}
@ -1017,7 +960,7 @@ export class DialogKeywords {
const value = extractEntity(answer);
if (value === null || value.length != 1) {
await this.getTalk({ pid, text: 'Por favor, digite um CEP válido.' });
await this.talk({ pid, text: 'Por favor, digite um CEP válido.' });
return await this.getHear({ pid, kind, arg });
}
@ -1032,7 +975,7 @@ export class DialogKeywords {
});
if (result === null) {
await this.getTalk({ pid, text: `Escolha por favor um dos itens sugeridos.` });
await this.talk({ pid, text: `Escolha por favor um dos itens sugeridos.` });
return await this.getHear({ pid, kind, arg });
}
} else if (kind === 'language') {
@ -1064,7 +1007,7 @@ export class DialogKeywords {
});
if (result === null) {
await this.getTalk({ pid, text: `Escolha por favor um dos itens sugeridos.` });
await this.talk({ pid, text: `Escolha por favor um dos itens sugeridos.` });
return await this.getHear({ pid, kind, arg });
}
}
@ -1077,7 +1020,8 @@ export class DialogKeywords {
/**
* Prepares the next dialog to be shown to the specified user.
*/
public async gotoDialog({ fromOrDialogName, dialogName }) {
public async gotoDialog({ pid, fromOrDialogName, dialogName }) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
if (dialogName) {
if (dialogName.charAt(0) === '/') {
// https://github.com/GeneralBots/BotServer/issues/308
@ -1087,7 +1031,7 @@ export class DialogKeywords {
let user = await sec.getUserFromSystemId(fromOrDialogName);
if (!user) {
user = await sec.ensureUser(
this.min.instance.instanceId,
min.instance.instanceId,
fromOrDialogName,
fromOrDialogName,
null,
@ -1121,7 +1065,7 @@ export class DialogKeywords {
/**
* Talks to the user by using the specified text.
*/
public async getTalk({ pid, text }) {
public async talk({ pid, text }) {
GBLog.info(`BASIC: TALK '${text}'.`);
const { min, user } = await DialogKeywords.getProcessInfo(pid);

View file

@ -51,6 +51,7 @@ import { DialogKeywords } from './DialogKeywords.js';
import { KeywordsExpressions } from './KeywordsExpressions.js';
import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js';
import { GuaribasUser } from '../../security.gbapp/models/index.js';
import { SystemKeywords } from './SystemKeywords.js';
/**
* @fileoverview Decision was to priorize security(isolation) and debugging,
@ -340,7 +341,6 @@ export class GBVMService extends GBService {
// Creates a class DialogKeywords which is the *this* pointer
// in BASIC.
const dk = new DialogKeywords(min, deployer, null);
const sandbox = {};
const contentLocale = min.core.getParam<string>(
min.instance,
@ -373,9 +373,11 @@ export class GBVMService extends GBService {
userId: user.userId,
instanceId: min.instance.instanceId
};
const dk = new DialogKeywords();
const sys = new SystemKeywords();
sandbox['variables'] = variables;
sandbox['id'] = dk.sys().getRandomId();
sandbox['id'] = sys.getRandomId();
sandbox['username'] = await dk.userName({ pid });
sandbox['mobile'] = await dk.userMobile({ pid });
sandbox['from'] = await dk.userMobile({ pid });
@ -398,7 +400,7 @@ export class GBVMService extends GBService {
console: 'inherit',
wrapper: 'commonjs',
require: {
builtin: ['stream', 'http', 'https', 'url', 'zlib'],
builtin: ['stream', 'http', 'https', 'url', 'zlib', 'net', 'tls', 'crypto', ],
root: ['./'],
external: true,
context: 'sandbox'

View file

@ -46,33 +46,6 @@ import { GBServer } from '../../../src/app.js';
* Image processing services of conversation to be called by BASIC.
*/
export class ImageProcessingServices {
/**
* Reference to minimal bot instance.
*/
public min: GBMinInstance;
/**
* Reference to the base system keywords functions to be called.
*/
public dk: DialogKeywords;
/**
* Current user object to get BASIC properties read.
*/
public user;
sys: any;
/**
* When creating this keyword facade,a bot instance is
* specified among the deployer service.
*/
constructor(min: GBMinInstance, user, dk) {
this.min = min;
this.user = user;
this.dk = dk;
}
/**
* Sharpen the image.
*
@ -116,7 +89,7 @@ export class ImageProcessingServices {
paths.push(gbfile.path);
});
const botId = this.min.instance.botId;
const botId = min.instance.botId;
const gbaiName = `${botId}.gbai`;
const img = await joinImages(paths);
const localName = Path.join('work', gbaiName, 'cache', `img-mrg${GBAdminService.getRndReadableIdentifier()}.png`);

View file

@ -57,6 +57,7 @@ import ImageModule from 'open-docxtemplater-image-module';
import DynamicsWebApi from 'dynamics-web-api';
import * as MSAL from '@azure/msal-node';
import { GBConversationalService } from '../../core.gbapp/services/GBConversationalService.js';
import { WebAutomationServices } from './WebAutomationServices.js';
/**
@ -67,23 +68,6 @@ import { GBConversationalService } from '../../core.gbapp/services/GBConversatio
* BASIC system class for extra manipulation of bot behaviour.
*/
export class SystemKeywords {
/**
* Reference to minimal bot instance.
*/
public min: GBMinInstance;
dk: DialogKeywords;
wa;
/**
* When creating this keyword facade, a bot instance is
* specified among the deployer service.
*/
constructor(min: GBMinInstance, deployer: GBDeployer, dk: DialogKeywords, wa) {
this.min = min;
this.wa = wa;
this.dk = dk;
}
public async callVM({ pid, text }) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
@ -114,7 +98,7 @@ export class SystemKeywords {
let caption = (await computerVisionClient.describeImage(url)).captions[0];
const contentLocale = this.min.core.getParam<string>(
const contentLocale = min.core.getParam(
min.instance,
'Default Content Language',
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
@ -158,7 +142,7 @@ export class SystemKeywords {
public async sortBy({ pid, array, memberName }) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
memberName = memberName.trim();
const contentLocale = this.min.core.getParam<string>(
const contentLocale = min.core.getParam(
min.instance,
'Default Content Language',
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
@ -257,7 +241,7 @@ export class SystemKeywords {
// Includes the associated CSS related to current theme.
const theme = this.dk.user.basicOptions.theme;
const theme: string = 'white'; // TODO: params.theme;
switch (theme) {
case 'white':
await page.addStyleTag({ path: 'node_modules/tabulator-tables/dist/css/tabulator_simple.min.css' });
@ -342,9 +326,9 @@ export class SystemKeywords {
if (data.data) {
const gbfile = data.data;
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min);
const botId = this.min.instance.botId;
const gbaiName = `${this.min.botId}.gbai`;
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
const botId = min.instance.botId;
const gbaiName = `${min.botId}.gbai`;
const tmpDocx = urlJoin(gbaiName, `${botId}.gbdrive`, `tmp${GBAdminService.getRndReadableIdentifier()}.docx`);
// Performs the conversion operation.
@ -425,7 +409,7 @@ export class SystemKeywords {
* Retrives a random id with a length of five, every time it is called.
*/
public getRandomId() {
const idGeneration = this.dk['idGeneration'];
const idGeneration = '1v'; // TODO: this.dk['idGeneration'];
if (idGeneration && idGeneration.trim().toLowerCase() === 'number') {
return GBAdminService.getNumberIdentifier();
} else {
@ -488,15 +472,15 @@ export class SystemKeywords {
* @example SET page, "elementHTMLSelector", "text"
*
*/
public async set({ pid, file, address, value }): Promise<any> {
public async set({ pid, handle, file, address, value }): Promise<any> {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
// Handles calls for HTML stuff
if (file._javascriptEnabled) {
const page = file;
GBLog.info(`BASIC: Web automation setting ${page}' to '${value}' (SET). `);
await this.wa.setElementText({ page, selector: address, text: value });
GBLog.info(`BASIC: Web automation setting ${file}' to '${value}' (SET). `);
await new WebAutomationServices().setElementText({ pid, handle, selector: address, text: value });
return;
}
@ -550,9 +534,10 @@ export class SystemKeywords {
*
*/
public async saveFile({ pid, file, data }): Promise<any> {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
GBLog.info(`BASIC: Saving '${file}' (SAVE file).`);
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min);
const botId = this.min.instance.botId;
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
const botId = min.instance.botId;
const path = `/${botId}.gbai/${botId}.gbdrive`;
// Checks if it is a GB FILE object.
@ -581,11 +566,12 @@ export class SystemKeywords {
*
*/
public async save({ pid, args }): Promise<any> {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
const file = args[0];
args.shift();
GBLog.info(`BASIC: Saving '${file}' (SAVE). Args: ${args.join(',')}.`);
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min);
const botId = this.min.instance.botId;
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
const botId = min.instance.botId;
const path = `/${botId}.gbai/${botId}.gbdata`;
let document = await this.internalGetDocument(client, baseUrl, path, file);
@ -626,6 +612,7 @@ export class SystemKeywords {
*
*/
public async get({ pid, file, addressOrHeaders, httpUsername, httpPs, qs, streaming }): Promise<any> {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
if (file.startsWith('http')) {
return await this.getByHttp({
pid,
@ -637,8 +624,8 @@ export class SystemKeywords {
});
} else {
GBLog.info(`BASIC: GET '${addressOrHeaders}' in '${file}'.`);
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min);
const botId = this.min.instance.botId;
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
const botId = min.instance.botId;
const path = `/${botId}.gbai/${botId}.gbdata`;
let document = await this.internalGetDocument(client, baseUrl, path, file);
@ -659,9 +646,10 @@ export class SystemKeywords {
}
}
public isValidDate({ pid, dt }) {
const contentLocale = this.min.core.getParam<string>(
this.min.instance,
public async isValidDate({ pid, dt }) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
const contentLocale = min.core.getParam(
min.instance,
'Default Content Language',
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
);
@ -678,7 +666,8 @@ export class SystemKeywords {
return !isNaN(date.valueOf());
}
public isValidNumber({ pid, number }) {
public async isValidNumber({ pid, number }) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
if (number === '') {
return false;
}
@ -704,21 +693,22 @@ export class SystemKeywords {
*
*/
public async getFind({ pid, args }): Promise<any> {
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
const file = args[0];
args.shift();
const botId = this.min.instance.botId;
const botId = min.instance.botId;
const path = `/${botId}.gbai/${botId}.gbdata`;
// MAX LINES property.
let maxLines;
if (this.dk.user && this.dk.user.basicOptions && this.dk.user.basicOptions.maxLines) {
if (this.dk.user.basicOptions.maxLines.toString().toLowerCase() !== 'default') {
maxLines = Number.parseInt(this.dk.user.basicOptions.maxLines).valueOf();
if (user && params && params.maxLines) {
if (params.maxLines.toString().toLowerCase() !== 'default') {
maxLines = Number.parseInt(params.maxLines).valueOf();
}
} else {
maxLines = this.dk.maxLines;
maxLines = maxLines;
}
GBLog.info(`BASIC: FIND running on ${file} (maxLines: ${maxLines}) and args: ${JSON.stringify(args)}...`);
@ -760,7 +750,7 @@ export class SystemKeywords {
rows[i] = result[i];
}
} else if (file['cTag']) {
const gbaiName = `${this.min.botId}.gbai`;
const gbaiName = `${min.botId}.gbai`;
const localName = Path.join('work', gbaiName, 'cache', `csv${GBAdminService.getRndReadableIdentifier()}.csv`);
const url = file['@microsoft.graph.downloadUrl'];
const response = await fetch(url);
@ -785,7 +775,7 @@ export class SystemKeywords {
}
}
} else {
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min);
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
let document;
document = await this.internalGetDocument(client, baseUrl, path, file);
@ -834,8 +824,8 @@ export class SystemKeywords {
return filter;
};
const contentLocale = this.min.core.getParam<string>(
this.min.instance,
const contentLocale = min.core.getParam(
min.instance,
'Default Content Language',
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
);
@ -888,8 +878,8 @@ export class SystemKeywords {
await CollectionUtil.asyncForEach(filters, async filter => {
let result = rows[foundIndex][filter.columnIndex];
let wholeWord = true;
if (this.dk.user && this.dk.user.basicOptions && this.dk.user.basicOptions.wholeWord) {
wholeWord = this.dk.user.basicOptions.wholeWord;
if (user && params && params.wholeWord) {
wholeWord = params.wholeWord;
}
switch (filter.dataType) {
@ -1101,8 +1091,9 @@ export class SystemKeywords {
*
*/
public async createFolder({ pid, name }) {
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min);
const botId = this.min.instance.botId;
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
const botId = min.instance.botId;
let path = `/${botId}.gbai/${botId}.gbdrive`;
// Extracts each part of path to call create folder to each
@ -1150,8 +1141,9 @@ export class SystemKeywords {
*
*/
public async shareFolder({ pid, folder, email, message }) {
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min);
const root = urlJoin(`/${this.min.botId}.gbai/${this.min.botId}.gbdrive`, folder);
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
const root = urlJoin(`/${min.botId}.gbai/${min.botId}.gbdrive`, folder);
const src = await client.api(`${baseUrl}/drive/root:/${root}`).get();
@ -1177,9 +1169,10 @@ export class SystemKeywords {
*
*/
public async copyFile({ pid, src, dest }) {
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
GBLog.info(`BASIC: BEGINING COPY '${src}' to '${dest}'`);
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min);
const botId = this.min.instance.botId;
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
const botId = min.instance.botId;
// Normalizes all slashes.
@ -1237,9 +1230,10 @@ export class SystemKeywords {
*
*/
public async convert({ pid, src, dest }) {
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
GBLog.info(`BASIC: CONVERT '${src}' to '${dest}'`);
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min);
const botId = this.min.instance.botId;
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
const botId = min.instance.botId;
// Normalizes all slashes.
@ -1339,7 +1333,7 @@ export class SystemKeywords {
* @example
*
* user = put "http://server/path", "data"
* talk "The updated user area is" + user.area
* talk "The updated user area is" + area
*
*/
public async putByHttp({ pid, url, data, headers }) {
@ -1359,7 +1353,7 @@ export class SystemKeywords {
* @example
*
* user = post "http://server/path", "data"
* talk "The updated user area is" + user.area
* talk "The updated user area is" + area
*
*/
public async postByHttp({ pid, url, data, headers }) {
@ -1435,13 +1429,13 @@ export class SystemKeywords {
*/
public async fill({ pid, templateName, data }) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
const botId = this.min.instance.botId;
const botId = min.instance.botId;
const gbaiName = `${botId}.gbai`;
let localName;
// Downloads template from .gbdrive.
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min);
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
let path = '/' + urlJoin(gbaiName, `${botId}.gbdrive`);
let template = await this.internalGetDocument(client, baseUrl, path, templateName);
let url = template['@microsoft.graph.downloadUrl'];
@ -1473,7 +1467,7 @@ export class SystemKeywords {
for (const kind of ['png', 'jpg', 'jpeg']) {
if (value.endsWith && value.endsWith(`.${kind}`)) {
const { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min);
const { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
path = urlJoin(gbaiName, `${botId}.gbdrive`);
if (value.indexOf('/') !== -1) {
@ -1596,16 +1590,16 @@ export class SystemKeywords {
public async merge({ pid, file, data, key1, key2 }): Promise<any> {
GBLog.info(`BASIC: MERGE running on ${file} and key1: ${key1}, key2: ${key2}...`);
const { min, user } = await DialogKeywords.getProcessInfo(pid);
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
const botId = min.instance.botId;
const path = `/${botId}.gbai/${botId}.gbdata`;
// MAX LINES property.
let maxLines = 1000;
if (this.dk.user && this.dk.user.basicOptions && this.dk.user.basicOptions.maxLines) {
if (this.dk.user.basicOptions.maxLines.toString().toLowerCase() !== 'default') {
maxLines = Number.parseInt(this.dk.user.basicOptions.maxLines).valueOf();
if (user && params && params.maxLines) {
if (params.maxLines.toString().toLowerCase() !== 'default') {
maxLines = Number.parseInt(params.maxLines).valueOf();
}
}
@ -1694,7 +1688,7 @@ export class SystemKeywords {
const address = `${cell}:${cell}`;
if (value !== found[columnName]) {
await this.set({ pid, file, address, value });
await this.set({ pid, handle:null, file, address, value });
merges++;
}
}

View file

@ -46,39 +46,18 @@ import { DialogKeywords } from './DialogKeywords.js';
import { GBDeployer } from '../../core.gbapp/services/GBDeployer.js';
import { Mutex } from 'async-mutex';
import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js';
import { SystemKeywords } from './SystemKeywords.js';
/**
* Web Automation services of conversation to be called by BASIC.
*/
export class WebAutomationServices {
/**
* Reference to minimal bot instance.
*/
public min: GBMinInstance;
/**
* Reference to the base system keywords functions to be called.
*/
public dk: DialogKeywords;
/**
* Current user object to get BASIC properties read.
*/
public user;
/**
* HTML browser for conversation over page interaction.
*/
browser: any;
sys: any;
/**
* The number used in this execution for HEAR calls (useful for SET SCHEDULE).
*/
hrOn: string;
userId: GuaribasUser;
debugWeb: boolean;
lastDebugWeb: Date;
@ -102,17 +81,6 @@ export class WebAutomationServices {
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
};
/**
* When creating this keyword facade,a bot instance is
* specified among the deployer service.
*/
constructor(min: GBMinInstance, user, dk) {
this.min = min;
this.user = user;
this.dk = dk;
this.debugWeb = this.min.core.getParam<boolean>(this.min.instance, 'Debug Web Automation', false);
}
public async getCloseHandles({ pid }) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
@ -185,7 +153,7 @@ export class WebAutomationServices {
// A new web session is being created.
handle = WebAutomationServices.cyrb53(this.min.botId + url);
handle = WebAutomationServices.cyrb53(min.botId + url);
GBServer.globals.webSessions[handle] = session = {};
session.sessionName = sessionName;
@ -294,16 +262,17 @@ export class WebAutomationServices {
}
private async debugStepWeb(pid, page) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
let refresh = true;
if (this.lastDebugWeb) {
refresh = new Date().getTime() - this.lastDebugWeb.getTime() > 5000;
}
if (this.debugWeb && refresh) {
const mobile = this.min.core.getParam(this.min.instance, 'Bot Admin Number', null);
const mobile = min.core.getParam(min.instance, 'Bot Admin Number', null);
const filename = page;
if (mobile) {
await this.dk.sendFileTo({ pid: pid, mobile, filename, caption: 'General Bots Debugger' });
await new DialogKeywords().sendFileTo({ pid: pid, mobile, filename, caption: 'General Bots Debugger' });
}
this.lastDebugWeb = new Date();
}
@ -346,16 +315,17 @@ export class WebAutomationServices {
*
* @example file = SCREENSHOT page
*/
public async screenshot({ handle, selector }) {
public async screenshot({pid, handle, selector }) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
const page = this.getPageByHandle(handle);
GBLog.info(`BASIC: Web Automation SCREENSHOT ${selector}.`);
const gbaiName = `${this.min.botId}.gbai`;
const gbaiName = `${min.botId}.gbai`;
const localName = Path.join('work', gbaiName, 'cache', `screen-${GBAdminService.getRndReadableIdentifier()}.jpg`);
await page.screenshot({ path: localName });
const url = urlJoin(GBServer.globals.publicAddress, this.min.botId, 'cache', Path.basename(localName));
const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', Path.basename(localName));
GBLog.info(`BASIC: WebAutomation: Screenshot captured at ${url}.`);
return url;
@ -381,7 +351,8 @@ export class WebAutomationServices {
*
* @example file = DOWNLOAD element, folder
*/
public async download({ handle, selector, folder }) {
public async download({ pid, handle, selector, folder }) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
const page = this.getPageByHandle(handle);
const element = await this.getBySelector({ handle, selector });
@ -429,8 +400,8 @@ export class WebAutomationServices {
const res = await fetch(options.uri, options);
result = Buffer.from(await res.arrayBuffer());
}
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(this.min);
const botId = this.min.instance.botId;
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
const botId = min.instance.botId;
// Normalizes all slashes.
@ -444,7 +415,7 @@ export class WebAutomationServices {
// Checks if the destination contains subfolders that
// need to be created.
folder = await this.sys.createFolder(folder);
folder = await new SystemKeywords().createFolder(folder);
// Performs the conversion operation getting a reference
// to the source and calling /content on drive API.

View file

@ -9,7 +9,7 @@ const evaluate = async (script, scope) => {
console: 'inherit',
wrapper: 'none',
require: {
builtin: ['stream', 'http', 'https', 'url', 'buffer', 'zlib', 'isomorphic-fetch', 'punycode', 'encoding'],
builtin: ['stream', 'http', 'https', 'url', 'buffer', 'zlib', 'isomorphic-fetch', 'punycode', 'encoding', 'net'],
root: ['./'],
external: true,
context: 'sandbox'

View file

@ -911,7 +911,7 @@ export class GBMinService {
);
const botToken = await credentials.getToken();
const headers = { Authorization: `Bearer ${botToken}` };
const t = new SystemKeywords(null, null, null, null);
const t = new SystemKeywords( );
const data = await t.getByHttp({
pid: 0,
url: file.contentUrl,

View file

@ -42,7 +42,7 @@ import { GBMinService } from '../packages/core.gbapp/services/GBMinService.js';
*/
export class RootData {
public webSessions: {} // List of Web Automation sessions.
public webSessions: {}; // List of Web Automation sessions.
public processes: {}; // List of .gbdialog active executions.
public files: {}; // List of uploaded files handled.
public publicAddress: string; // URI for BotServer.
@ -59,5 +59,10 @@ export class RootData {
public debugConversationId: any; // Used to self-message during debug.
public debuggers: any[]; // Client of attached Debugger instances by botId.
public chatGPT: any; // ChatGPT API handle (shared Browser).
public dk;
public wa;
public sys;
public dbg;
public img;
indexSemaphore: any;
}