From a7a86175e1cd411fa76acfe0c653682a6eca6f47 Mon Sep 17 00:00:00 2001 From: rodrigorodriguez Date: Thu, 3 Nov 2022 11:06:26 -0300 Subject: [PATCH] new(all): Vm isolated working with IPC BASIC 3.0; --- packages/basic.gblib/index.ts | 8 +- .../basic.gblib/services/DialogKeywords.ts | 185 +--------- packages/basic.gblib/services/GBVMService.ts | 168 +++++----- .../basic.gblib/services/SystemKeywords.ts | 7 +- .../services/WebAutomationKeywords.ts | 315 ++++++++++++++++++ packages/core.gbapp/services/GBMinService.ts | 10 +- 6 files changed, 419 insertions(+), 274 deletions(-) create mode 100644 packages/basic.gblib/services/WebAutomationKeywords.ts diff --git a/packages/basic.gblib/index.ts b/packages/basic.gblib/index.ts index 4944176f..1c001f7a 100644 --- a/packages/basic.gblib/index.ts +++ b/packages/basic.gblib/index.ts @@ -44,6 +44,7 @@ import { DialogKeywords } from './services/DialogKeywords'; const Koa = require('koa'); import * as koaBody from "koa-body" import { SystemKeywords } from './services/SystemKeywords'; +import { WebAutomationKeywords } from './services/WebAutomationKeywords'; const app = new Koa() /** @@ -78,10 +79,13 @@ export class GBBasicPackage implements IGBPackage { } public async loadBot(min: GBMinInstance): Promise { const dk = new DialogKeywords(min, null, null); + const wa = new WebAutomationKeywords(min, null, dk); + dk.wa = wa; const dialogRouter = createServerRouter(`/api/v2/${min.botId}/dialog`, dk); - const sysRouter = createServerRouter(`/api/v2/${min.botId}/system`, new SystemKeywords(min, null, dk)); + const waRouter = createServerRouter(`/api/v2/${min.botId}/webautomation`, wa ); + const sysRouter = createServerRouter(`/api/v2/${min.botId}/system`, new SystemKeywords(min, null, dk, wa)); app.use(dialogRouter.routes()); app.use(sysRouter.routes()); - + app.use(waRouter.routes()); } } diff --git a/packages/basic.gblib/services/DialogKeywords.ts b/packages/basic.gblib/services/DialogKeywords.ts index 6aa5b71d..a0a0e9cb 100644 --- a/packages/basic.gblib/services/DialogKeywords.ts +++ b/packages/basic.gblib/services/DialogKeywords.ts @@ -97,6 +97,7 @@ export class DialogKeywords { userId: GuaribasUser; debugWeb: boolean; lastDebugWeb: Date; + public wa; /** * SYSTEM account maxLines,when used with impersonated contexts (eg. running in SET SCHEDULE). @@ -118,7 +119,7 @@ export class DialogKeywords { constructor(min: GBMinInstance, deployer: GBDeployer, user) { this.min = min; this.user = user; - this.internalSys = new SystemKeywords(min, deployer, this); + this.internalSys = new SystemKeywords(min, deployer, this, null); this.debugWeb = this.min.core.getParam( this.min.instance, @@ -136,24 +137,6 @@ export class DialogKeywords { return this.internalSys; } - /** - * Returns the page object. - * - * @example x = GET PAGE - */ - public async getPage({url, username, password}) { - GBLog.info(`BASIC: Web Automation GET PAGE ${url}.`); - if (!this.browser) { - this.browser = await createBrowser(null); - } - const page = (await this.browser.pages())[0]; - if (username || password) { - await page.authenticate({ 'username': username, 'password': password }); - } - await page.goto(url); - return page; - } - /** * * @@ -243,170 +226,6 @@ export class DialogKeywords { return url; } - /** - * Find element on page DOM. - * - * @example GET page,"selector" - */ - public async getBySelector({page, selector}) { - GBLog.info(`BASIC: Web Automation GET element: ${selector}.`); - await page.waitForSelector(selector) - let elements = await page.$$(selector); - if (elements && elements.length > 1) { - return elements; - } - else { - const el = elements[0]; - el['originalSelector'] = selector; - el['href'] = await page.evaluate(e => e.getAttribute('href'), el); - el['value'] = await page.evaluate(e => e.getAttribute('value'), el); - el['name'] = await page.evaluate(e => e.getAttribute('name'), el); - el['class'] = await page.evaluate(e => e.getAttribute('class'), el); - return el; - } - } - - /** - * Find element on page DOM. - * - * @example GET page,"frameSelector,"elementSelector" - */ - public async getByFrame({page, frame, selector}) { - GBLog.info(`BASIC: Web Automation GET element by frame: ${selector}.`); - await page.waitForSelector(frame) - let frameHandle = await page.$(frame); - const f = await frameHandle.contentFrame(); - await f.waitForSelector(selector); - const element = await f.$(selector); - element['originalSelector'] = selector; - element['href'] = await f.evaluate(e => e.getAttribute('href'), element); - element['value'] = await f.evaluate(e => e.getAttribute('value'), element); - element['name'] = await f.evaluate(e => e.getAttribute('name'), element); - element['class'] = await f.evaluate(e => e.getAttribute('class'), element); - element['frame'] = f; - return element; - } - - /** - * Simulates a mouse hover an web page element. - */ - public async hover({page, selector}) { - GBLog.info(`BASIC: Web Automation HOVER element: ${selector}.`); - await this.getBySelector({page, selector: selector}); - await page.hover(selector); - await this.debugStepWeb(page); - } - - /** - * Clicks on an element in a web page. - * - * @example CLICK page,"#idElement" - */ - public async click({page, frameOrSelector, selector}) { - GBLog.info(`BASIC: Web Automation CLICK element: ${frameOrSelector}.`); - if (selector) { - await page.waitForSelector(frameOrSelector) - let frameHandle = await page.$(frameOrSelector); - const f = await frameHandle.contentFrame(); - await f.waitForSelector(selector); - await f.click(selector); - } - else { - await page.waitForSelector(frameOrSelector); - await page.click(frameOrSelector); - } - await this.debugStepWeb(page); - } - - private async debugStepWeb(page) { - - 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 filename = page; - if (mobile) { - await this.sendFileTo({mobile , filename, caption:"General Bots Debugger"}); - } - this.lastDebugWeb = new Date(); - } - } - - /** - * Press ENTER in a web page,useful for logins. - * - * @example PRESS ENTER ON page - */ - public async pressKey({page, char, frame}) { - GBLog.info(`BASIC: Web Automation PRESS ${char} ON element: ${frame}.`); - if (char.toLowerCase() === "enter") { - char = '\n'; - } - if (frame) { - await page.waitForSelector(frame) - let frameHandle = await page.$(frame); - const f = await frameHandle.contentFrame(); - await f.keyboard.press(char); - } - else { - await page.keyboard.press(char); - } - } - - public async linkByText({page, text, index}) { - GBLog.info(`BASIC: Web Automation CLICK LINK TEXT: ${text} ${index}.`); - if (!index) { - index = 1 - } - const els = await page.$x(`//a[contains(.,'${text}')]`); - await els[index - 1].click(); - await this.debugStepWeb(page); - } - - - - /** - * Returns the screenshot of page or element - * - * @example file = SCREENSHOT page - */ - public async screenshot({page, selector}) { - GBLog.info(`BASIC: Web Automation SCREENSHOT ${selector}.`); - - const gbaiName = `${this.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) - ); - GBLog.info(`BASIC: WebAutomation: Screenshot captured at ${url}.`); - - return url; - } - - - /** - * Types the text into the text field. - * - * @example SET page,"selector","text" - */ - public async setElementText({page, selector, text}) { - GBLog.info(`BASIC: Web Automation TYPE on ${selector}: ${text}.`); - const e = await this.getBySelector({page, selector}); - await e.click({ clickCount: 3 }); - await page.keyboard.press('Backspace'); - await e.type(text, { delay: 200 }); - await this.debugStepWeb(page); - } - /** * Returns the OCR of image file. * diff --git a/packages/basic.gblib/services/GBVMService.ts b/packages/basic.gblib/services/GBVMService.ts index a8035a02..55d4cb40 100644 --- a/packages/basic.gblib/services/GBVMService.ts +++ b/packages/basic.gblib/services/GBVMService.ts @@ -170,14 +170,15 @@ export class GBVMService extends GBService { // Processes END keyword, removing extracode, useful // for development. - let end = /(\nend\n)/gi.exec(basicCode); - if (end) { - basicCode = basicCode.substring(0, end.index); - } + // TODO: let end = /(\nend\n)/gi.exec(basicCode); + // if (end) { + // basicCode = basicCode.substring(0, end.index); + // } // Removes comments. basicCode = basicCode.replace(/((^|\W)REM.*\n)/gi, ''); + basicCode = basicCode.replace(/((^|\W)\'.*\n)/gi, ''); // Process INCLUDE keyword to include another // dialog inside the dialog. @@ -234,6 +235,7 @@ export class GBVMService extends GBService { const dk = rest.createClient('http://localhost:1111/api/v2/${min.botId}/dialog'); const sys = rest.createClient('http://localhost:1111/api/v2/${min.botId}/system'); + const wa = rest.createClient('http://localhost:1111/api/v2/${min.botId}/webautomation'); // Local variables. @@ -248,6 +250,8 @@ export class GBVMService extends GBService { const list = gb.list; const httpUsername = gb.httpUsername; const httpPs = gb.httpPs; + let page = null; + // Local functions. @@ -256,12 +260,12 @@ export class GBVMService extends GBService { // Remote functions. - const weekday = (v) => { return (async () => { return await client.getWeekFromDate(v) })(); }; - const hour = (v) => { return (async () => { return await client.getHourFromDate(v) })(); }; - const base64 = (v) => { return (async () => { return await client.getCoded(v) })(); }; - const tolist = (v) => { return (async () => { return await client.getToLst(v) })(); }; - const now = (v) => { return (async () => { return await client.getNow(v) })(); }; - const today = (v) => { return (async () => { return await client.getToday(v) })(); }; + const weekday = (v) => { return (async () => { return await dk.getWeekFromDate({v}) })(); }; + const hour = (v) => { return (async () => { return await dk.getHourFromDate({v}) })(); }; + const base64 = (v) => { return (async () => { return await dk.getCoded({v}) })(); }; + const tolist = (v) => { return (async () => { return await dk.getToLst({v}) })(); }; + const now = (v) => { return (async () => { return await dk.getNow({v}) })(); }; + const today = (v) => { return (async () => { return await dk.getToday({v}) })(); }; ${code} @@ -334,11 +338,11 @@ export class GBVMService extends GBService { let tableName = /\sFROM\s(\w+)/.exec($2)[1]; let sql = `SELECT ${$2}`.replace(tableName, '?'); - return `${$1} = await sys.executeSQL(${$1}, "${sql}", "${tableName}")\n`; + return `${$1} = await sys.executeSQL({data:${$1}, sql:"${sql}", tableName:"${tableName}"})\n`; }); code = code.replace(/(\w+)\s*\=\s*get html\s*(.*)/gi, ($0, $1, $2, $3) => { - return `${$1} = await dk.getPage(${$2})\n`; + return `page = await wa.getPage({${$2})\n`; }); code = code.replace(/(set hear on)(\s*)(.*)/gi, ($0, $1, $2, $3) => { @@ -346,55 +350,55 @@ export class GBVMService extends GBService { }); code = code.replace(/hear (\w+) as login/gi, ($0, $1) => { - return `${$1} = await dk.getHear({{"login"})`; + return `${$1} = await dk.getHear({kind:"login"})`; }); code = code.replace(/hear (\w+) as email/gi, ($0, $1) => { - return `${$1} = await dk.getHear({"email"})`; + return `${$1} = await dk.getHear({kind:"email"})`; }); code = code.replace(/hear (\w+) as integer/gi, ($0, $1, $2) => { - return `${$1} = await dk.getHear({"integer"})`; + return `${$1} = await dk.getHear({kind:"integer"})`; }); code = code.replace(/hear (\w+) as file/gi, ($0, $1, $2) => { - return `${$1} = await dk.getHear({"file"})`; + return `${$1} = await dk.getHear({kind:"file"})`; }); code = code.replace(/hear (\w+) as boolean/gi, ($0, $1, $2) => { - return `${$1} = await dk.getHear({"boolean"})`; + return `${$1} = await dk.getHear({kind:"boolean"})`; }); code = code.replace(/hear (\w+) as name/gi, ($0, $1, $2) => { - return `${$1} = await dk.getHear({"name"})`; + return `${$1} = await dk.getHear({kind:"name"})`; }); code = code.replace(/hear (\w+) as date/gi, ($0, $1, $2) => { - return `${$1} = await dk.getHear({"date"})`; + return `${$1} = await dk.getHear({kind:"date"})`; }); code = code.replace(/hear (\w+) as hour/gi, ($0, $1, $2) => { - return `${$1} = await dk.getHear({"hour"})`; + return `${$1} = await dk.getHear({kind:"hour"})`; }); code = code.replace(/hear (\w+) as phone/gi, ($0, $1, $2) => { - return `${$1} = await dk.getHear({"phone"})`; + return `${$1} = await dk.getHear({kind:"phone"})`; }); code = code.replace(/hear (\w+) as money/gi, ($0, $1, $2) => { - return `${$1} = await dk.getHear({"money")}`; + return `${$1} = await dk.getHear({kind:"money")}`; }); code = code.replace(/hear (\w+) as language/gi, ($0, $1, $2) => { - return `${$1} = await dk.getHear({"language")}`; + return `${$1} = await dk.getHear({kind:"language")}`; }); code = code.replace(/hear (\w+) as zipcode/gi, ($0, $1, $2) => { - return `${$1} = await dk.getHear({"zipcode")}`; + return `${$1} = await dk.getHear({kind:"zipcode")}`; }); code = code.replace(/hear (\w+) as (.*)/gi, ($0, $1, $2) => { - return `${$1} = await dk.getHear({"menu", [${$2}])}`; + return `${$1} = await dk.getHear({kind:"menu", [${$2}])}`; }); code = code.replace(/(hear)\s*(\w+)/gi, ($0, $1, $2) => { @@ -402,13 +406,13 @@ export class GBVMService extends GBService { }); code = code.replace(/(\w)\s*\=\s*find contact\s*(.*)/gi, ($0, $1, $2, $3) => { - return `${$1} = await dk.fndContact(${$2})\n`; + return `${$1} = await dk.fndContact({${$2})\n`; }); code = code.replace(/(\w+)\s*=\s*find\s*(.*)\s*or talk\s*(.*)/gi, ($0, $1, $2, $3) => { - return `${$1} = await await sys.find(${$2})\n + return `${$1} = await await sys.find({${$2})\n if (!${$1}) { - await dk.talk (${$3})\n; + await dk.talk ({${$3}})\n; return -1; } `; @@ -419,11 +423,11 @@ export class GBVMService extends GBService { }); code = code.replace(/(\w)\s*\=\s*find\s*(.*)/gi, ($0, $1, $2, $3) => { - return `${$1} = await sys.find(${$2})\n`; + return `${$1} = await sys.find({${$2})\n`; }); code = code.replace(/(\w)\s*\=\s*create deal(\s)(.*)/gi, ($0, $1, $2, $3) => { - return `${$1} = await dk.createDeal(${$3})\n`; + return `${$1} = await dk.createDeal({${$3}})\n`; }); code = code.replace(/(\w)\s*\=\s*active tasks/gi, ($0, $1) => { @@ -435,23 +439,23 @@ export class GBVMService extends GBService { }); code = code.replace(/(\w+)\s*\=\s*sort\s*(\w+)\s*by(.*)/gi, ($0, $1, $2, $3) => { - return `${$1} = await sys.sortBy(${$2}, "${$3}")\n`; + return `${$1} = await sys.sortBy({${$2}, "${$3}")\n`; }); code = code.replace(/see\s*text\s*of\s*(\w+)\s*as\s*(\w+)\s*/gi, ($0, $1, $2, $3) => { - return `${$2} = await sys.seeText(${$1})\n`; + return `${$2} = await sys.seeText({${$1})\n`; }); code = code.replace(/see\s*caption\s*of\s*(\w+)\s*as(.*)/gi, ($0, $1, $2, $3) => { - return `${$2} = await sys.seeCaption(${$1})\n`; + return `${$2} = await sys.seeCaption({${$1})\n`; }); code = code.replace(/(wait)\s*(\d+)/gi, ($0, $1, $2) => { - return `await sys.wait(${$2})`; + return `await sys.wait({${$2})`; }); code = code.replace(/(get stock for )(.*)/gi, ($0, $1, $2) => { - return `stock = await sys.getStock(${$2})`; + return `stock = await sys.getStock({${$2})`; }); code = code.replace(/(\w+)\s*\=\s*get\s(.*)/gi, ($0, $1, $2, $3) => { @@ -459,25 +463,25 @@ export class GBVMService extends GBService { const count = ($2.match(/\,/g) || []).length; const values = $2.split(','); - // Handles GET page, "selector". + // Handles GET "selector". if (count == 1) { - return `${$1} = await dk.getBySelector(${values[0]}, ${values[1]} )`; + return `${$1} = await wa.getBySelector({page, ${values[0]}, ${values[1]}})`; } - // Handles GET page, "frameSelector", "selector" + // Handles GET "frameSelector", "selector" else if (count == 2) { - return `${$1} = await dk.getByFrame(${values[0]}, ${values[1]}, ${values[2]} )`; + return `${$1} = await wa.getByFrame({page, ${values[0]}, ${values[1]}, ${values[2]}})`; } // Handles the GET http version. else { - return `${$1} = await sys.get (${$2}, headers, httpUsername, httpPs)`; + return `${$1} = await sys.get ({${$2}, headers, httpUsername, httpPs})`; } }); @@ -492,11 +496,11 @@ export class GBVMService extends GBService { code = code.replace(/(go to)(\s)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.gotoDialog(${$3})\n`; + return `await dk.gotoDialog({${$3}})\n`; }); code = code.replace(/(set language)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.setLanguage (${$3})\n`; + return `await dk.setLanguage ({${$3}})\n`; }); code = code.replace(/set header\s*(.*)\sas\s(.*)/gi, ($0, $1, $2) => { @@ -512,67 +516,67 @@ export class GBVMService extends GBService { }); code = code.replace(/(datediff)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.dateDiff (${$3})\n`; + return `await dk.dateDiff ({${$3}})\n`; }); code = code.replace(/(dateadd)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.dateAdd (${$3})\n`; + return `await dk.dateAdd ({${$3}})\n`; }); code = code.replace(/(set max lines)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.setMaxLines (${$3})\n`; + return `await dk.setMaxLines ({${$3}})\n`; }); code = code.replace(/(set max columns)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.setMaxColumns (${$3})\n`; + return `await dk.setMaxColumns ({${$3}})\n`; }); code = code.replace(/(set translator)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.setTranslatorOn ("${$3.toLowerCase()}")\n`; + return `await dk.setTranslatorOn ({"${$3.toLowerCase()}"})\n`; }); code = code.replace(/(set theme)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.setTheme ("${$3.toLowerCase()}")\n`; + return `await dk.setTheme ({"${$3.toLowerCase()}"})\n`; }); code = code.replace(/(set whole word)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.setWholeWord ("${$3.toLowerCase()}")\n`; + return `await dk.setWholeWord ({"${$3.toLowerCase()}"})\n`; }); code = code.replace(/(\w+)\s*\=\s*post\s*(.*),\s*(.*)/gi, ($0, $1, $2, $3) => { - return `${$1} = await sys.postByHttp (${$2}, ${$3}, headers)`; + return `${$1} = await sys.postByHttp ({${$2}, ${$3}, headers})`; }); code = code.replace(/(\w+)\s*\=\s*put\s*(.*),\s*(.*)/gi, ($0, $1, $2, $3) => { - return `${$1} = await sys.putByHttp (${$2}, ${$3}, headers)`; + return `${$1} = await sys.putByHttp ({${$2}, ${$3}, headers})`; }); code = code.replace(/(\w+)\s*\=\s*download\s*(.*),\s*(.*)/gi, ($0, $1, $2, $3) => { - return `${$1} = await sys.download (${$2}, ${$3})`; + return `${$1} = await sys.download ({${$2}, ${$3}})`; }); code = code.replace(/(\w+)\s*\=\s*CREATE FOLDER\s*(.*)/gi, ($0, $1, $2) => { - return `${$1} = await sys.createFolder (${$2})`; + return `${$1} = await sys.createFolder ({${$2}})`; }); code = code.replace(/SHARE FOLDER\s*(.*)/gi, ($0, $1) => { - return `await sys.shareFolder (${$1})`; + return `await sys.shareFolder ({${$1}})`; }); code = code.replace(/(create a bot farm using)(\s)(.*)/gi, ($0, $1, $2, $3) => { - return `await sys.createABotFarmUsing (${$3})`; + return `await sys.createABotFarmUsing ({${$3}})`; }); code = code.replace(/(chart)(\s)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.chart (${$3})\n`; + return `await dk.chart ({${$3}})\n`; }); code = code.replace(/(transfer to)(\s)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.transferTo (${$3})\n`; + return `await dk.transferTo ({${$3}})\n`; }); code = code.replace(/(\btransfer\b)(?=(?:[^"]|"[^"]*")*$)/gi, () => { - return `await dk.transferTo (step)\n`; + return `await dk.transferTo ()\n`; }); code = code.replace(/(exit)/gi, () => { @@ -580,99 +584,99 @@ export class GBVMService extends GBService { }); code = code.replace(/(show menu)/gi, () => { - return `await dk.showMenu (step)\n`; + return `await dk.showMenu ()\n`; }); code = code.replace(/(talk to)(\s)(.*)/gi, ($0, $1, $2, $3) => { - return `await sys.talkTo(${$3})\n`; + return `await sys.talkTo({${$3}})\n`; }); code = code.replace(/(talk)(\s)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.talk (${$3})\n`; + return `await dk.talk ({${$3}})\n`; }); code = code.replace(/(send sms to)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await sys.sendSmsTo (${$3})\n`; + return `await sys.sendSmsTo ({${$3}})\n`; }); code = code.replace(/(send email)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.sendEmail (${$3})\n`; + return `await dk.sendEmail ({${$3}})\n`; }); code = code.replace(/(send mail)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.sendEmail (${$3})\n`; + return `await dk.sendEmail ({${$3}})\n`; }); code = code.replace(/(send file to)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.sendFileTo (${$3})\n`; + return `await dk.sendFileTo ({${$3}})\n`; }); code = code.replace(/(hover)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.hover (${$3})\n`; + return `await wa.hover ({page, ${$3}})\n`; }); code = code.replace(/(click link text)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.linkByText (${$3})\n`; + return `await wa.linkByText ({page, ${$3}})\n`; }); code = code.replace(/(click)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.click (${$3})\n`; + return `await wa.click (page, {${$3}})\n`; }); code = code.replace(/(send file)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await dk.sendFile (${$3})\n`; + return `await dk.sendFile ({${$3}})\n`; }); code = code.replace(/(copy)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await sys.copyFile(${$3})\n`; + return `await sys.copyFile({${$3}})\n`; }); code = code.replace(/(convert)(\s*)(.*)/gi, ($0, $1, $2, $3) => { - return `await sys.convert(${$3})\n`; + return `await sys.convert({${$3}})\n`; }); // TODO: AS CHART. // code = code.replace(/(\w+)\s*\=\s*(.*)\s*as chart/gi, ($0, $1, $2) => { - // return `${$1} = await sys.asImage(${$2})\n`; + // return `${$1} = await sys.asImage({${$2})\n`; // }); code = code.replace(/MERGE\s(.*)\sWITH\s(.*)BY\s(.*)/gi, ($0, $1, $2, $3) => { - return `await sys.merge(${$1}, ${$2}, ${$3})\n`; + return `await sys.merge({${$1}, ${$2}, ${$3}})\n`; }); code = code.replace(/PRESS\s(.*)\sON\s(.*)/gi, ($0, $1, $2) => { - return `await dk.pressKey(${$2}, ${$1})\n`; + return `await wa.pressKey({page, ${$2}, ${$1})\n`; }); code = code.replace(/SCREENSHOT\s(.*)/gi, ($0, $1, $2) => { - return `await dk.screenshot(${$1})\n`; + return `await wa.screenshot({page, ${$1})\n`; }); code = code.replace(/TWEET\s(.*)/gi, ($0, $1, $2) => { - return `await sys.tweet(${$1})\n`; + return `await sys.tweet({${$1})\n`; }); code = code.replace(/(\w+)\s*\=\s*(.*)\s*as image/gi, ($0, $1, $2) => { - return `${$1} = await sys.asImage(${$2})\n`; + return `${$1} = await sys.asImage({${$2})\n`; }); code = code.replace(/(\w+)\s*\=\s*(.*)\s*as pdf/gi, ($0, $1, $2) => { - return `${$1} = await sys.asPdf(${$2})\n`; + return `${$1} = await sys.asPdf({${$2})\n`; }); code = code.replace(/(\w+)\s*\=\s*FILL\s(.*)\sWITH\s(.*)/gi, ($0, $1, $2, $3) => { - return `${1} = await sys.fill(${$2}, ${$3})\n`; + return `${1} = await sys.fill({${$2}, ${$3}})\n`; }); code = code.replace(/save\s(.*)\sas\s(.*)/gi, ($0, $1, $2, $3) => { - return `await sys.saveFile(${$2}, ${$1})\n`; + return `await sys.saveFile({${$2}, ${$1})\n`; }); code = code.replace(/(save)(\s)(.*)/gi, ($0, $1, $2, $3) => { - return `await sys.save([${$3}])\n`; + return `await sys.save({[${$3}]})\n`; }); code = code.replace(/set\s(.*)/gi, ($0, $1, $2) => { - return `await sys.set (${$1})`; + return `await sys.set ({${$1})`; }); code = `${code}\n%>`; diff --git a/packages/basic.gblib/services/SystemKeywords.ts b/packages/basic.gblib/services/SystemKeywords.ts index 89ca30e4..fa2cf75f 100644 --- a/packages/basic.gblib/services/SystemKeywords.ts +++ b/packages/basic.gblib/services/SystemKeywords.ts @@ -79,14 +79,15 @@ export class SystemKeywords { private readonly deployer: GBDeployer; dk: DialogKeywords; - + wa; /** * When creating this keyword facade, a bot instance is * specified among the deployer service. */ - constructor(min: GBMinInstance, deployer: GBDeployer, dk: DialogKeywords) { + constructor(min: GBMinInstance, deployer: GBDeployer, dk: DialogKeywords, wa) { this.min = min; + this.wa = wa; this.deployer = deployer; this.dk = dk; } @@ -472,7 +473,7 @@ export class SystemKeywords { if (file._javascriptEnabled) { const page = file; GBLog.info(`BASIC: Web automation setting ${page}' to '${value}' (SET). `); - await this.dk.setElementText({page, selector: address, text: value}); + await this.wa.setElementText({page, selector: address, text: value}); return; } diff --git a/packages/basic.gblib/services/WebAutomationKeywords.ts b/packages/basic.gblib/services/WebAutomationKeywords.ts new file mode 100644 index 00000000..47245fed --- /dev/null +++ b/packages/basic.gblib/services/WebAutomationKeywords.ts @@ -0,0 +1,315 @@ +/*****************************************************************************\ +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' v `\ /'_`\ | +| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__,\| (˅) |( (_) ) | +| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | +| | | ( )_) | | +| (_) \___/' | +| | +| General Bots Copyright (c) Pragmatismo.io. All rights reserved. | +| Licensed under the AGPL-3.0. | +| | +| According to our dual licensing model,this program can be used either | +| under the terms of the GNU Affero General Public License,version 3, | +| or under a proprietary license. | +| | +| The texts of the GNU Affero General Public License with an additional | +| permission and of our proprietary license can be found at and | +| in the LICENSE file you have received along with this program. | +| | +| This program is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY,without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| GNU Affero General Public License for more details. | +| | +| "General Bots" is a registered trademark of Pragmatismo.io. | +| The licensing of the program under the AGPLv3 does not imply a | +| trademark license. Therefore any rights,title and interest in | +| our trademarks remain entirely with us. | +| | +\*****************************************************************************/ + +'use strict'; + +import { GBLog, GBMinInstance } from 'botlib'; +import { GBServer } from '../../../src/app'; +import { GBAdminService } from '../../admin.gbapp/services/GBAdminService'; +import { createBrowser } from '../../core.gbapp/services/GBSSR'; +import { GuaribasUser } from '../../security.gbapp/models'; +import { DialogKeywords } from './DialogKeywords'; + +const urlJoin = require('url-join'); +const Path = require('path'); + +/** + * Web Automation services of conversation to be called by BASIC. + */ +export class WebAutomationKeywords { + + /** + * 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; + + /** + * 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; + + pageMap = {}; + + cyrb53 = (str, seed = 0) => { + let h1 = 0xdeadbeef ^ seed, + h2 = 0x41c6ce57 ^ seed; + for (let i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909); + + 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( + this.min.instance, + 'Debug Web Automation', + false + ); + } + + /** + * Returns the page object. + * + * @example x = GET PAGE + */ + public async getPage({url, username, password}) { + GBLog.info(`BASIC: Web Automation GET PAGE ${url}.`); + if (!this.browser) { + this.browser = await createBrowser(null); + } + const page = (await this.browser.pages())[0]; + if (username || password) { + await page.authenticate({ 'username': username, 'password': password }); + } + await page.goto(url); + + const handle = this.cyrb53(this.min.botId + url); + + this.pageMap[handle] = page; + + return handle; + } + + public getPageByHandle(hash){ + return this.pageMap[hash] ; + } + + /** + * Find element on page DOM. + * + * @example GET page,"selector" + */ + public async getBySelector({handle, selector}) { + const page = this.getPageByHandle(handle); + GBLog.info(`BASIC: Web Automation GET element: ${selector}.`); + await page.waitForSelector(selector) + let elements = await page.$$(selector); + if (elements && elements.length > 1) { + return elements; + } + else { + const el = elements[0]; + el['originalSelector'] = selector; + el['href'] = await page.evaluate(e => e.getAttribute('href'), el); + el['value'] = await page.evaluate(e => e.getAttribute('value'), el); + el['name'] = await page.evaluate(e => e.getAttribute('name'), el); + el['class'] = await page.evaluate(e => e.getAttribute('class'), el); + return el; + } + } + + /** + * Find element on page DOM. + * + * @example GET page,"frameSelector,"elementSelector" + */ + public async getByFrame({handle, frame, selector}) { + const page = this.getPageByHandle(handle); + GBLog.info(`BASIC: Web Automation GET element by frame: ${selector}.`); + await page.waitForSelector(frame) + let frameHandle = await page.$(frame); + const f = await frameHandle.contentFrame(); + await f.waitForSelector(selector); + const element = await f.$(selector); + element['originalSelector'] = selector; + element['href'] = await f.evaluate(e => e.getAttribute('href'), element); + element['value'] = await f.evaluate(e => e.getAttribute('value'), element); + element['name'] = await f.evaluate(e => e.getAttribute('name'), element); + element['class'] = await f.evaluate(e => e.getAttribute('class'), element); + element['frame'] = f; + return element; + } + + /** + * Simulates a mouse hover an web page element. + */ + public async hover({handle, selector}) { + const page = this.getPageByHandle(handle); + GBLog.info(`BASIC: Web Automation HOVER element: ${selector}.`); + await this.getBySelector({handle, selector: selector}); + await page.hover(selector); + await this.debugStepWeb(page); + } + + /** + * Clicks on an element in a web page. + * + * @example CLICK page,"#idElement" + */ + public async click({handle, frameOrSelector, selector}) { + const page = this.getPageByHandle(handle); + GBLog.info(`BASIC: Web Automation CLICK element: ${frameOrSelector}.`); + if (selector) { + await page.waitForSelector(frameOrSelector) + let frameHandle = await page.$(frameOrSelector); + const f = await frameHandle.contentFrame(); + await f.waitForSelector(selector); + await f.click(selector); + } + else { + await page.waitForSelector(frameOrSelector); + await page.click(frameOrSelector); + } + await this.debugStepWeb(page); + } + + private async debugStepWeb(page) { + + 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 filename = page; + if (mobile) { + await this.dk.sendFileTo({mobile , filename, caption:"General Bots Debugger"}); + } + this.lastDebugWeb = new Date(); + } + } + + /** + * Press ENTER in a web page,useful for logins. + * + * @example PRESS ENTER ON page + */ + public async pressKey({handle, char, frame}) { + const page = this.getPageByHandle(handle); + GBLog.info(`BASIC: Web Automation PRESS ${char} ON element: ${frame}.`); + if (char.toLowerCase() === "enter") { + char = '\n'; + } + if (frame) { + await page.waitForSelector(frame) + let frameHandle = await page.$(frame); + const f = await frameHandle.contentFrame(); + await f.keyboard.press(char); + } + else { + await page.keyboard.press(char); + } + } + + public async linkByText({handle, text, index}) { + const page = this.getPageByHandle(handle); + GBLog.info(`BASIC: Web Automation CLICK LINK TEXT: ${text} ${index}.`); + if (!index) { + index = 1 + } + const els = await page.$x(`//a[contains(.,'${text}')]`); + await els[index - 1].click(); + await this.debugStepWeb(page); + } + + + + /** + * Returns the screenshot of page or element + * + * @example file = SCREENSHOT page + */ + public async screenshot({handle, selector}) { + const page = this.getPageByHandle(handle); + GBLog.info(`BASIC: Web Automation SCREENSHOT ${selector}.`); + + const gbaiName = `${this.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) + ); + GBLog.info(`BASIC: WebAutomation: Screenshot captured at ${url}.`); + + return url; + } + + + /** + * Types the text into the text field. + * + * @example SET page,"selector","text" + */ + public async setElementText({handle, selector, text}) { + const page = this.getPageByHandle(handle); + GBLog.info(`BASIC: Web Automation TYPE on ${selector}: ${text}.`); + const e = await this.getBySelector({handle, selector}); + await e.click({ clickCount: 3 }); + await page.keyboard.press('Backspace'); + await e.type(text, { delay: 200 }); + await this.debugStepWeb(page); + } +} \ No newline at end of file diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index bb0dc840..82866172 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -807,8 +807,7 @@ export class GBMinService { public static userMobile(step) { let mobile = WhatsappDirectLine.mobiles[step.context.activity.conversation.id] - if (!mobile && step) - { + if (!mobile && step) { return step.context.activity.from.id; } @@ -927,8 +926,11 @@ export class GBMinService { const credentials = new MicrosoftAppCredentials(min.instance.marketplaceId, min.instance.marketplacePassword); const botToken = await credentials.getToken(); const headers = { Authorization: `Bearer ${botToken}` }; - const t = new SystemKeywords(null, null, null); - const data = await t.getByHttp(file.contentUrl, headers, null, null, null, true); + const t = new SystemKeywords(null, null, null, null); + const data = await t.getByHttp({ + url: file.contentUrl, headers, username: null, + ps: null, qs: null, streaming: true + }); const folder = `work/${min.instance.botId}.gbai/cache`; const filename = `${GBAdminService.generateUuid()}.png`;