diff --git a/package.json b/package.json index cd516c2b..bfc8aaad 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "core-js": "3.26.1", "data-forge": "1.9.6", "date-diff": "1.0.2", + "docximager": "^0.0.4", "docxtemplater": "3.32.4", "dotenv-extended": "2.9.0", "exceljs": "4.3.0", @@ -120,6 +121,7 @@ "npm": "9.1.2", "open": "8.4.0", "pdf-extraction": "1.0.2", + "pdf-to-png-converter": "^2.7.1", "pdfkit": "0.13.0", "phone": "3.1.30", "pizzip": "3.1.3", diff --git a/packages/basic.gblib/services/GBVMService.ts b/packages/basic.gblib/services/GBVMService.ts index fa5f9d01..ab40d197 100644 --- a/packages/basic.gblib/services/GBVMService.ts +++ b/packages/basic.gblib/services/GBVMService.ts @@ -49,8 +49,11 @@ import Path from 'path'; import { GBAdminService } from '../../admin.gbapp/services/GBAdminService.js'; import pkg from 'swagger-client'; import { DialogKeywords } from './DialogKeywords.js'; +import { KeywordsExpressions } from './KeywordsExpressions.js'; const { Swagger } = pkg; + + /** * @fileoverview Decision was to priorize security(isolation) and debugging, * over a beautiful BASIC transpiler (to be done). @@ -60,6 +63,10 @@ const { Swagger } = pkg; * Basic services for BASIC manipulation. */ export class GBVMService extends GBService { + + private static DEBUGGER_PORT = 9222; + + public async loadDialogPackage(folder: string, min: GBMinInstance, core: IGBCoreService, deployer: GBDeployer) { const files = await walkPromise(folder); @@ -271,38 +278,6 @@ export class GBVMService extends GBService { }); } - private getParams = (text: string, names) => { - let ret = {}; - const splitParamsButIgnoreCommasInDoublequotes = (str: string) => { - return str.split(',').reduce( - (accum, curr) => { - if (accum.isConcatting) { - accum.soFar[accum.soFar.length - 1] += ',' + curr; - } else { - accum.soFar.push(curr); - } - if (curr.split('"').length % 2 == 0) { - accum.isConcatting = !accum.isConcatting; - } - return accum; - }, - { soFar: [], isConcatting: false } - ).soFar; - }; - - const items = splitParamsButIgnoreCommasInDoublequotes(text); - - let i = 0; - let json = ''; - names.forEach(name => { - let value = items[i]; - i++; - json = `${json} "${name}": ${value} ${names.length == i ? '' : ','}`; - }); - json = `${json}`; - - return json; - }; /** * Converts General Bots BASIC @@ -315,7 +290,7 @@ export class GBVMService extends GBService { code = process.env.ENABLE_AUTH ? `hear gbLogin as login\n${code}` : code; var lines = code.split('\n'); - const keywords = this.getKeywords(); + const keywords = KeywordsExpressions.getKeywords(); let current = 41; const map = {}; @@ -335,697 +310,17 @@ export class GBVMService extends GBService { return { code, jsonMap: map }; } - private getKeywords() { - // Keywords from General Bots BASIC. - - let keywords = []; - let i = 0; - - const convertConditions = input => { - var result = input.replace(/ +and +/gim, ' && '); - result = result.replace(/ +or +/gim, ' || '); - result = result.replace(/ +<> +/gim, ' !== '); - result = result.replace(/ += +/gim, ' === '); - return result; - }; - - keywords[i++] = [/^\s*REM.*/gim, '']; - - keywords[i++] = [/^\s*\'.*/gim, '']; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*SELECT\s*(.*)/gim, - ($0, $1, $2) => { - let tableName = /\s*FROM\s*(\w+)/.exec($2)[1]; - let sql = `SELECT ${$2}`.replace(tableName, '?'); - return `${$1} = await sys.executeSQL({pid: pid, data:${$1}, sql:"${sql}", tableName:"${tableName}"})\n`; - } - ]; - - keywords[i++] = [/^\s*end if/gim, '}']; - - keywords[i++] = [ - /^\s*if +(.*?) +then/gim, - (input, group1) => { - var condition = convertConditions(group1); - return 'if (' + condition + ') {'; - } - ]; - - // Based on https://github.com/uweg/vbscript-to-typescript. - - keywords[i++] = [/^\s*else(?!{)/gim, '}\nelse {']; - - keywords[i++] = [/^\s*select case +(.*)/gim, 'switch ($1) {']; - - keywords[i++] = [/^\s*end select/gim, '}']; - - keywords[i++] = [/^\s*end function/gim, '}']; - - keywords[i++] = [/^\s*function +(.*)\((.*)\)/gim, '$1 = ($2) => {\n']; - - keywords[i++] = [/^\s*for +(.*to.*)/gim, 'for ($1) {']; - - keywords[i++] = [/^\s*next *$/gim, '}']; - - keywords[i++] = [ - /^\s*do while +(.*)/gim, - function (input, group1) { - var condition = convertConditions(group1); - return 'while (' + condition + ') {'; - } - ]; - - keywords[i++] = [/^\s*loop *$/gim, '}']; - - keywords[i++] = [ - /^\s*open\s*(.*)/gim, - ($0, $1, $2) => { - if (!$1.startsWith('"') && !$1.startsWith("'")) { - $1 = `"${$1}"`; - } - const params = this.getParams($1, ['url', 'username', 'password']); - - return `page = await wa.getPage({pid: pid,${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(set hear on)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - return `hrOn = ${$3}`; - } - ]; - - keywords[i++] = [ - /^\s*hear (\w+) as login/gim, - ($0, $1) => { - return `${$1} = await dk.getHear({pid: pid, kind:"login"})`; - } - ]; - - keywords[i++] = [ - /^\s*hear (\w+) as email/gim, - ($0, $1) => { - return `${$1} = await dk.getHear({pid: pid, kind:"email"})`; - } - ]; - - keywords[i++] = [ - /^\s*hear (\w+) as integer/gim, - ($0, $1) => { - return `${$1} = await dk.getHear({pid: pid, kind:"integer"})`; - } - ]; - - keywords[i++] = [ - /^\s*hear (\w+) as file/gim, - ($0, $1) => { - return `${$1} = await dk.getHear({pid: pid, kind:"file"})`; - } - ]; - - keywords[i++] = [ - /^\s*hear (\w+) as boolean/gim, - ($0, $1) => { - return `${$1} = await dk.getHear({pid: pid, kind:"boolean"})`; - } - ]; - - keywords[i++] = [ - /^\s*hear (\w+) as name/gim, - ($0, $1) => { - return `${$1} = await dk.getHear({pid: pid, kind:"name"})`; - } - ]; - - keywords[i++] = [ - /^\s*hear (\w+) as date/gim, - ($0, $1) => { - return `${$1} = await dk.getHear({pid: pid, kind:"date"})`; - } - ]; - - keywords[i++] = [ - /^\s*hear (\w+) as hour/gim, - ($0, $1) => { - return `${$1} = await dk.getHear({pid: pid, kind:"hour"})`; - } - ]; - - keywords[i++] = [ - /^\s*hear (\w+) as phone/gim, - ($0, $1) => { - return `${$1} = await dk.getHear({pid: pid, kind:"phone"})`; - } - ]; - - keywords[i++] = [ - /^\s*hear (\w+) as money/gim, - ($0, $1) => { - return `${$1} = await dk.getHear({pid: pid, kind:"money")}`; - } - ]; - - keywords[i++] = [ - /^\s*hear (\w+) as language/gim, - ($0, $1) => { - return `${$1} = await dk.getHear({pid: pid, kind:"language")}`; - } - ]; - - keywords[i++] = [ - /^\s*hear (\w+) as zipcode/gim, - ($0, $1) => { - return `${$1} = await dk.getHear({pid: pid, kind:"zipcode")}`; - } - ]; - - keywords[i++] = [ - /^\s*hear (\w+) as (.*)/gim, - ($0, $1, $2) => { - return `${$1} = await dk.getHear({pid: pid, kind:"menu", args: [${$2}]})`; - } - ]; - - keywords[i++] = [ - /^\s*(hear)\s*(\w+)/gim, - ($0, $1, $2) => { - return `${$2} = await dk.getHear({pid: pid})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*find contact\s*(.*)/gim, - ($0, $1, $2, $3) => { - return `${$1} = await dk.fndContact({pid: pid, ${$2}})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*=\s*find\s*(.*)\s*or talk\s*(.*)/gim, - ($0, $1, $2, $3) => { - return `${$1} = await sys.find({pid: pid, args:[${$2}])\n - if (!${$1}) { - await dk.talk ({pid: pid, ${$3}})\n; - return -1; - } - `; - } - ]; - - keywords[i++] = [ - /^\s*CALL\s*(.*)/gim, - ($0, $1) => { - return `await ${$1}`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*find\s*(.*)/gim, - ($0, $1, $2, $3) => { - return ` - ${$1} = await sys.find({pid: pid, args: [${$2}]})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*create deal(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['dealName', 'contact', 'company', 'amount']); - - return `${$1} = await dk.createDeal({pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*active tasks/gim, - ($0, $1) => { - return `${$1} = await dk.getActiveTasks({pid: pid})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*append\s*(.*)/gim, - ($0, $1, $2, $3) => { - return `${$1} = await sys.append({pid: pid, args:[${$2}]})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*sort\s*(\w+)\s*by(.*)/gim, - ($0, $1, $2, $3) => { - return `${$1} = await sys.sortBy({pid: pid, array: ${$2}, memberName: "${$3}"})`; - } - ]; - - keywords[i++] = [ - /^\s*see\s*text\s*of\s*(\w+)\s*as\s*(\w+)\s*/gim, - ($0, $1, $2, $3) => { - return `${$2} = await sys.seeText({pid: pid, url: ${$1})`; - } - ]; - - keywords[i++] = [ - /^\s*see\s*caption\s*of\s*(\w+)\s*as(.*)/gim, - ($0, $1, $2, $3) => { - return `${$2} = await sys.seeCaption({pid: pid, url: ${$1})`; - } - ]; - - keywords[i++] = [ - /^\s*(wait)\s*(\d+)/gim, - ($0, $1, $2) => { - return `await sys.wait({pid: pid, seconds:${$2}})`; - } - ]; - - keywords[i++] = [ - /^\s*(get stock for )(.*)/gim, - ($0, $1, $2) => { - return `stock = await sys.getStock({pid: pid, symbol: ${$2})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*get\s*(.*)/gim, - ($0, $1, $2, $3) => { - const count = ($2.match(/\,/g) || []).length; - const values = $2.split(','); - - // Handles GET "selector". - - if (count == 1) { - return `${$1} = await wa.getBySelector({pid: pid, handle:page, selector: ${values[0]}})`; - } - - // Handles GET "frameSelector", "selector" - else if (count == 2) { - return `${$1} = await wa.getByFrame({pid: pid, handle: page, ${values[0]}, frameOrSelector: ${values[1]}, selector: ${values[2]}})`; - } - - // Handles the GET http version. - else { - return `${$1} = await sys.get ({pid: pid, file: ${$2}, addressOrHeaders: headers, httpUsername, httpPs})`; - } - } - ]; - - keywords[i++] = [ - /\= NEW OBJECT/gim, - ($0, $1, $2, $3) => { - return ` = {pid: pid}`; - } - ]; - - keywords[i++] = [ - /\= NEW ARRAY/gim, - ($0, $1, $2, $3) => { - return ` = []`; - } - ]; - - keywords[i++] = [ - /^\s*(go to)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['fromOrDialogName', 'dialogName']); - return `await dk.gotoDialog({pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(set language)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - return `await dk.setLanguage ({pid: pid, ${$3}})`; - } - ]; - - 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++] = [ - /^\s*set header\s*(.*)\s*as\s*(.*)/gim, - ($0, $1, $2) => { - return `headers[${$1}]=${$2})`; - } - ]; - - keywords[i++] = [ - /^\s*set http username\s*\=\s*(.*)/gim, - ($0, $1) => { - return `httpUsername = ${$1}`; - } - ]; - - keywords[i++] = [ - /^\s*set http password\s*\=\s*(.*)/gim, - ($0, $1) => { - return `httpPs = ${$1}`; - } - ]; - - keywords[i++] = [ - /^\s*(datediff)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['date1', 'date2', 'mode']); - return `await dk.dateDiff (pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(dateadd)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['date', 'mode', 'units']); - return `await dk.dateAdd (pid: pid, ${$3})`; - } - ]; - - keywords[i++] = [ - /^\s*(set max lines)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - return `await dk.setMaxLines ({pid: pid, count: ${$3}})`; - } - ]; - - keywords[i++] = [ - /^\s*(set max columns)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - return `await dk.setMaxColumns ({pid: pid, count: ${$3}})`; - } - ]; - - keywords[i++] = [ - /^\s*(set translator)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - return `await dk.setTranslatorOn ({pid: pid, on: "${$3.toLowerCase()}"})`; - } - ]; - - keywords[i++] = [ - /^\s*(set theme)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - return `await dk.setTheme ({pid: pid, theme: "${$3.toLowerCase()}"})`; - } - ]; - - keywords[i++] = [ - /^\s*(set whole word)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - return `await dk.setWholeWord ({pid: pid, on: "${$3.toLowerCase()}"})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*post\s*(.*),\s*(.*)/gim, - ($0, $1, $2, $3) => { - return `${$1} = await sys.postByHttp ({pid: pid, url:${$2}, data:${$3}, headers})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*put\s*(.*),\s*(.*)/gim, - ($0, $1, $2, $3) => { - return `${$1} = await sys.putByHttp ({pid: pid, url:${$2}, data:${$3}, headers})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*download\s*(.*),\s*(.*)/gim, - ($0, $1, $2, $3) => { - return `${$1} = await sys.download ({pid: pid, handle:page, selector: ${$2}, folder:${$3}})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*CREATE FOLDER\s*(.*)/gim, - ($0, $1, $2) => { - return `${$1} = await sys.createFolder ({pid: pid, name:${$2}})`; - } - ]; - - keywords[i++] = [ - /^\s*SHARE FOLDER\s*(.*)/gim, - ($0, $1) => { - return `await sys.shareFolder ({pid: pid, name: ${$1}})`; - } - ]; - - keywords[i++] = [ - /^\s*(create a bot farm using)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - return `await sys.createABotFarmUsing ({pid: pid, ${$3}})`; - } - ]; - - keywords[i++] = [ - /^\s*(transfer to)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - return `await dk.transferTo ({pid: pid, to:${$3}})`; - } - ]; - - keywords[i++] = [ - /^\s*(\btransfer\b)(?=(?:[^"]|"[^"]*")*$)/gim, - () => { - return `await dk.transferTo ({pid: pid})`; - } - ]; - - keywords[i++] = [ - /^\s*(exit)/gim, - () => { - return `return;`; - } - ]; - - keywords[i++] = [ - /^\s*(END)/gim, - () => { - return `return;`; - } - ]; - - keywords[i++] = [ - /^\s*(show menu)/gim, - () => { - return `await dk.showMenu ({pid: pid, })`; - } - ]; - - keywords[i++] = [ - /^\s*(talk to)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['mobile', 'message']); - return `await sys.talkTo({pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(talk)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - if ($3.substr(0, 1) !== '"') { - $3 = `"${$3}"`; - } - return `await dk.talk ({pid: pid, text: ${$3}})`; - } - ]; - - keywords[i++] = [ - /^\s*(send sms to)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['mobile', 'message']); - return `await sys.sendSmsTo({pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(send email)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['to', 'subject', 'body']); - return `await dk.sendEmail({pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(send mail)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['to', 'subject', 'body']); - return `await dk.sendEmail({pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(send file to)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['mobile', 'filename', 'caption']); - return `await dk.sendFileTo({pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(hover)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['handle', 'selector']); - return `await wa.hover ({pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(click link text)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams('page,' + $3, ['handle', 'text', 'index']); - return `await wa.linkByText ({pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(click)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - // page is not string. - // https://github.com/GeneralBots/BotServer/issues/310 - const params = this.getParams('page,' + $3, ['handle', 'frameOrSelector', 'selector']); - return `await wa.click ({pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(send file)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['filename', 'caption']); - return `await dk.sendFile({pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(copy)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['src', 'dst']); - return `await sys.copyFile ({pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(convert)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['src', 'dst']); - return `await sys.convert ({pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*chart(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - const params = this.getParams($3, ['type', 'data', 'legends', 'transpose']); - return `${$1} = await dk.chart ({pid: pid, ${params}})`; - } - ]; - - keywords[i++] = [ - /^\s*MERGE\s*(.*)\s*WITH\s*(.*)BY\s*(.*)/gim, - ($0, $1, $2, $3) => { - return `await sys.merge({pid: pid, file: ${$1}, data: ${$2}, key1: ${$3}})`; - } - ]; - - keywords[i++] = [ - /^\s*PRESS\s*(.*)/gim, - ($0, $1, $2) => { - return `await wa.pressKey({pid: pid, handle: page, char: ${$1}})`; - } - ]; - - keywords[i++] = [ - /^\s*SCREENSHOT\s*(.*)/gim, - ($0, $1, $2) => { - return `await wa.screenshot({pid: pid, handle: page, selector: ${$1}})`; - } - ]; - - keywords[i++] = [ - /^\s*TWEET\s*(.*)/gim, - ($0, $1, $2) => { - return `await sys.tweet({pid: pid, text: ${$1}})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*(.*)\s*as image/gim, - ($0, $1, $2) => { - return `${$1} = await sys.asImage({pid: pid, data: ${$2}})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*(.*)\s*as pdf/gim, - ($0, $1, $2) => { - return `${$1} = await sys.asPdf({pid: pid, data: ${$2})`; - } - ]; - - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*FILL\s*(.*)\s*WITH\s*(.*)/gim, - ($0, $1, $2, $3) => { - return `${$1} = await sys.fill({pid: pid, templateName: ${$2}, data: ${$3}})`; - } - ]; - - keywords[i++] = [ - /^\s*save\s*(.*)\s*as\s*(.*)/gim, - ($0, $1, $2, $3) => { - return `await sys.saveFile({pid: pid, file: ${$2}, data: ${$1}})`; - } - ]; - keywords[i++] = [ - /^\s*(save)(\s*)(.*)/gim, - ($0, $1, $2, $3) => { - return `await sys.save({pid: pid, args: [${$3}]})`; - } - ]; - - keywords[i++] = [ - /^\s*set\s*(.*)/gim, - ($0, $1, $2) => { - const params = this.getParams($1, ['file', 'address', 'value']); - return `await sys.set ({pid: pid, ${params}})`; - } - ]; - keywords[i++] = [ - /^\s*(\w+)\s*\=\s*SHARPEN\s*(.*)/gim, - ($0, $1, $2, $3) => { - return ` - ${$1} = await img.sharpen({pid: pid, args: [${$2}]})`; - } - ]; - - return keywords; - } - /** * Executes the converted JavaScript from BASIC code inside execution context. */ public static async callVM(text: string, min: GBMinInstance, step, deployer: GBDeployer, debug: boolean) { - const debuggerPort = 9222; // Creates a class DialogKeywords which is the *this* pointer // in BASIC. const user = step ? await min.userProfile.get(step.context, {}) : null; - const dk = new DialogKeywords(min, deployer, user); - const sandbox = {}; - const contentLocale = min.core.getParam( min.instance, 'Default Content Language', @@ -1069,7 +364,7 @@ export class GBVMService extends GBService { sandbox['httpPs'] = ''; sandbox['pid'] = pid; - if (GBConfigService.get('VM3') === 'true') { + if (GBConfigService.get('VM3') === 'false') { try { const vm1 = new NodeVM({ allowAsync: true, @@ -1105,7 +400,7 @@ export class GBVMService extends GBService { min: 0, max: 0, debug: debug, - debuggerPort: debuggerPort, + debuggerPort: GBVMService.DEBUGGER_PORT, botId: botId, cpu: 100, memory: 50000, diff --git a/packages/basic.gblib/services/KeywordsExpressions.ts b/packages/basic.gblib/services/KeywordsExpressions.ts new file mode 100644 index 00000000..7ce48114 --- /dev/null +++ b/packages/basic.gblib/services/KeywordsExpressions.ts @@ -0,0 +1,754 @@ +/*****************************************************************************\ +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' 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'; + +/** + * Image processing services of conversation to be called by BASIC. + */ +export class KeywordsExpressions { + + private static getParams = (text: string, names) => { + let ret = {}; + const splitParamsButIgnoreCommasInDoublequotes = (str: string) => { + return str.split(',').reduce( + (accum, curr) => { + if (accum.isConcatting) { + accum.soFar[accum.soFar.length - 1] += ',' + curr; + } else { + accum.soFar.push(curr); + } + if (curr.split('"').length % 2 == 0) { + accum.isConcatting = !accum.isConcatting; + } + return accum; + }, + { soFar: [], isConcatting: false } + ).soFar; + }; + + const items = splitParamsButIgnoreCommasInDoublequotes(text); + + let i = 0; + let json = ''; + names.forEach(name => { + let value = items[i]; + i++; + json = `${json} "${name}": ${value} ${names.length == i ? '' : ','}`; + }); + json = `${json}`; + + return json; + }; + + /** + * Returns the page object. + * + * @example OPEN "https://wikipedia.org" + */ + public static getKeywords() { + + // Keywords from General Bots BASIC. + + let keywords = []; + let i = 0; + + const convertConditions = input => { + var result = input.replace(/ +and +/gim, ' && '); + result = result.replace(/ +or +/gim, ' || '); + result = result.replace(/ +<> +/gim, ' !== '); + result = result.replace(/ += +/gim, ' === '); + return result; + }; + + keywords[i++] = [/^\s*REM.*/gim, '']; + + keywords[i++] = [/^\s*\'.*/gim, '']; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*SELECT\s*(.*)/gim, + ($0, $1, $2) => { + let tableName = /\s*FROM\s*(\w+)/.exec($2)[1]; + let sql = `SELECT ${$2}`.replace(tableName, '?'); + return `${$1} = await sys.executeSQL({pid: pid, data:${$1}, sql:"${sql}", tableName:"${tableName}"})\n`; + } + ]; + + keywords[i++] = [/^\s*end if/gim, '}']; + + keywords[i++] = [ + /^\s*if +(.*?) +then/gim, + (input, group1) => { + var condition = convertConditions(group1); + return 'if (' + condition + ') {'; + } + ]; + + // Based on https://github.com/uweg/vbscript-to-typescript. + + keywords[i++] = [/^\s*else(?!{)/gim, '}\nelse {']; + + keywords[i++] = [/^\s*select case +(.*)/gim, 'switch ($1) {']; + + keywords[i++] = [/^\s*end select/gim, '}']; + + keywords[i++] = [/^\s*end function/gim, '}']; + + keywords[i++] = [/^\s*function +(.*)\((.*)\)/gim, '$1 = ($2) => {\n']; + + keywords[i++] = [/^\s*for +(.*to.*)/gim, 'for ($1) {']; + + keywords[i++] = [/^\s*next *$/gim, '}']; + + keywords[i++] = [ + /^\s*do while +(.*)/gim, + function (input, group1) { + var condition = convertConditions(group1); + return 'while (' + condition + ') {'; + } + ]; + + keywords[i++] = [/^\s*loop *$/gim, '}']; + + keywords[i++] = [ + /^\s*open\s*(.*)/gim, + ($0, $1, $2) => { + if (!$1.startsWith('"') && !$1.startsWith("'")) { + $1 = `"${$1}"`; + } + const params = this.getParams($1, ['url', 'username', 'password']); + + return `page = await wa.getPage({pid: pid,${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(set hear on)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + return `hrOn = ${$3}`; + } + ]; + + keywords[i++] = [ + /^\s*hear (\w+) as login/gim, + ($0, $1) => { + return `${$1} = await dk.getHear({pid: pid, kind:"login"})`; + } + ]; + + keywords[i++] = [ + /^\s*hear (\w+) as email/gim, + ($0, $1) => { + return `${$1} = await dk.getHear({pid: pid, kind:"email"})`; + } + ]; + + keywords[i++] = [ + /^\s*hear (\w+) as integer/gim, + ($0, $1) => { + return `${$1} = await dk.getHear({pid: pid, kind:"integer"})`; + } + ]; + + keywords[i++] = [ + /^\s*hear (\w+) as file/gim, + ($0, $1) => { + return `${$1} = await dk.getHear({pid: pid, kind:"file"})`; + } + ]; + + keywords[i++] = [ + /^\s*hear (\w+) as boolean/gim, + ($0, $1) => { + return `${$1} = await dk.getHear({pid: pid, kind:"boolean"})`; + } + ]; + + keywords[i++] = [ + /^\s*hear (\w+) as name/gim, + ($0, $1) => { + return `${$1} = await dk.getHear({pid: pid, kind:"name"})`; + } + ]; + + keywords[i++] = [ + /^\s*hear (\w+) as date/gim, + ($0, $1) => { + return `${$1} = await dk.getHear({pid: pid, kind:"date"})`; + } + ]; + + keywords[i++] = [ + /^\s*hear (\w+) as hour/gim, + ($0, $1) => { + return `${$1} = await dk.getHear({pid: pid, kind:"hour"})`; + } + ]; + + keywords[i++] = [ + /^\s*hear (\w+) as phone/gim, + ($0, $1) => { + return `${$1} = await dk.getHear({pid: pid, kind:"phone"})`; + } + ]; + + keywords[i++] = [ + /^\s*hear (\w+) as money/gim, + ($0, $1) => { + return `${$1} = await dk.getHear({pid: pid, kind:"money")}`; + } + ]; + + keywords[i++] = [ + /^\s*hear (\w+) as language/gim, + ($0, $1) => { + return `${$1} = await dk.getHear({pid: pid, kind:"language")}`; + } + ]; + + keywords[i++] = [ + /^\s*hear (\w+) as zipcode/gim, + ($0, $1) => { + return `${$1} = await dk.getHear({pid: pid, kind:"zipcode")}`; + } + ]; + + keywords[i++] = [ + /^\s*hear (\w+) as (.*)/gim, + ($0, $1, $2) => { + return `${$1} = await dk.getHear({pid: pid, kind:"menu", args: [${$2}]})`; + } + ]; + + keywords[i++] = [ + /^\s*(hear)\s*(\w+)/gim, + ($0, $1, $2) => { + return `${$2} = await dk.getHear({pid: pid})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*find contact\s*(.*)/gim, + ($0, $1, $2, $3) => { + return `${$1} = await dk.fndContact({pid: pid, ${$2}})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*=\s*find\s*(.*)\s*or talk\s*(.*)/gim, + ($0, $1, $2, $3) => { + return `${$1} = await sys.find({pid: pid, args:[${$2}])\n + if (!${$1}) { + await dk.talk ({pid: pid, ${$3}})\n; + return -1; + } + `; + } + ]; + + keywords[i++] = [ + /^\s*CALL\s*(.*)/gim, + ($0, $1) => { + return `await ${$1}`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*find\s*(.*)/gim, + ($0, $1, $2, $3) => { + return ` + ${$1} = await sys.find({pid: pid, args: [${$2}]})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*create deal(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams($3, ['dealName', 'contact', 'company', 'amount']); + + return `${$1} = await dk.createDeal({pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*active tasks/gim, + ($0, $1) => { + return `${$1} = await dk.getActiveTasks({pid: pid})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*append\s*(.*)/gim, + ($0, $1, $2, $3) => { + return `${$1} = await sys.append({pid: pid, args:[${$2}]})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*sort\s*(\w+)\s*by(.*)/gim, + ($0, $1, $2, $3) => { + return `${$1} = await sys.sortBy({pid: pid, array: ${$2}, memberName: "${$3}"})`; + } + ]; + + keywords[i++] = [ + /^\s*see\s*text\s*of\s*(\w+)\s*as\s*(\w+)\s*/gim, + ($0, $1, $2, $3) => { + return `${$2} = await sys.seeText({pid: pid, url: ${$1})`; + } + ]; + + keywords[i++] = [ + /^\s*see\s*caption\s*of\s*(\w+)\s*as(.*)/gim, + ($0, $1, $2, $3) => { + return `${$2} = await sys.seeCaption({pid: pid, url: ${$1})`; + } + ]; + + keywords[i++] = [ + /^\s*(wait)\s*(\d+)/gim, + ($0, $1, $2) => { + return `await sys.wait({pid: pid, seconds:${$2}})`; + } + ]; + + keywords[i++] = [ + /^\s*(get stock for )(.*)/gim, + ($0, $1, $2) => { + return `stock = await sys.getStock({pid: pid, symbol: ${$2})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*get\s*(.*)/gim, + ($0, $1, $2, $3) => { + const count = ($2.match(/\,/g) || []).length; + const values = $2.split(','); + + // Handles GET "selector". + + if (count == 1) { + return `${$1} = await wa.getBySelector({pid: pid, handle:page, selector: ${values[0]}})`; + } + + // Handles GET "frameSelector", "selector" + else if (count == 2) { + return `${$1} = await wa.getByFrame({pid: pid, handle: page, ${values[0]}, frameOrSelector: ${values[1]}, selector: ${values[2]}})`; + } + + // Handles the GET http version. + else { + return `${$1} = await sys.get ({pid: pid, file: ${$2}, addressOrHeaders: headers, httpUsername, httpPs})`; + } + } + ]; + + keywords[i++] = [ + /\= NEW OBJECT/gim, + ($0, $1, $2, $3) => { + return ` = {pid: pid}`; + } + ]; + + keywords[i++] = [ + /\= NEW ARRAY/gim, + ($0, $1, $2, $3) => { + return ` = []`; + } + ]; + + keywords[i++] = [ + /^\s*(go to)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams($3, ['fromOrDialogName', 'dialogName']); + return `await dk.gotoDialog({pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(set language)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + return `await dk.setLanguage ({pid: pid, ${$3}})`; + } + ]; + + 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++] = [ + /^\s*set header\s*(.*)\s*as\s*(.*)/gim, + ($0, $1, $2) => { + return `headers[${$1}]=${$2})`; + } + ]; + + keywords[i++] = [ + /^\s*set http username\s*\=\s*(.*)/gim, + ($0, $1) => { + return `httpUsername = ${$1}`; + } + ]; + + keywords[i++] = [ + /^\s*set http password\s*\=\s*(.*)/gim, + ($0, $1) => { + return `httpPs = ${$1}`; + } + ]; + + keywords[i++] = [ + /^\s*(datediff)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams($3, ['date1', 'date2', 'mode']); + return `await dk.dateDiff (pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(dateadd)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams($3, ['date', 'mode', 'units']); + return `await dk.dateAdd (pid: pid, ${$3})`; + } + ]; + + keywords[i++] = [ + /^\s*(set max lines)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + return `await dk.setMaxLines ({pid: pid, count: ${$3}})`; + } + ]; + + keywords[i++] = [ + /^\s*(set max columns)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + return `await dk.setMaxColumns ({pid: pid, count: ${$3}})`; + } + ]; + + keywords[i++] = [ + /^\s*(set translator)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + return `await dk.setTranslatorOn ({pid: pid, on: "${$3.toLowerCase()}"})`; + } + ]; + + keywords[i++] = [ + /^\s*(set theme)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + return `await dk.setTheme ({pid: pid, theme: "${$3.toLowerCase()}"})`; + } + ]; + + keywords[i++] = [ + /^\s*(set whole word)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + return `await dk.setWholeWord ({pid: pid, on: "${$3.toLowerCase()}"})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*post\s*(.*),\s*(.*)/gim, + ($0, $1, $2, $3) => { + return `${$1} = await sys.postByHttp ({pid: pid, url:${$2}, data:${$3}, headers})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*put\s*(.*),\s*(.*)/gim, + ($0, $1, $2, $3) => { + return `${$1} = await sys.putByHttp ({pid: pid, url:${$2}, data:${$3}, headers})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*download\s*(.*),\s*(.*)/gim, + ($0, $1, $2, $3) => { + return `${$1} = await sys.download ({pid: pid, handle:page, selector: ${$2}, folder:${$3}})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*CREATE FOLDER\s*(.*)/gim, + ($0, $1, $2) => { + return `${$1} = await sys.createFolder ({pid: pid, name:${$2}})`; + } + ]; + + keywords[i++] = [ + /^\s*SHARE FOLDER\s*(.*)/gim, + ($0, $1) => { + return `await sys.shareFolder ({pid: pid, name: ${$1}})`; + } + ]; + + keywords[i++] = [ + /^\s*(create a bot farm using)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + return `await sys.createABotFarmUsing ({pid: pid, ${$3}})`; + } + ]; + + keywords[i++] = [ + /^\s*(transfer to)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + return `await dk.transferTo ({pid: pid, to:${$3}})`; + } + ]; + + keywords[i++] = [ + /^\s*(\btransfer\b)(?=(?:[^"]|"[^"]*")*$)/gim, + () => { + return `await dk.transferTo ({pid: pid})`; + } + ]; + + keywords[i++] = [ + /^\s*(exit)/gim, + () => { + return `return;`; + } + ]; + + keywords[i++] = [ + /^\s*(END)/gim, + () => { + return `return;`; + } + ]; + + keywords[i++] = [ + /^\s*(show menu)/gim, + () => { + return `await dk.showMenu ({pid: pid, })`; + } + ]; + + keywords[i++] = [ + /^\s*(talk to)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams($3, ['mobile', 'message']); + return `await sys.talkTo({pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(talk)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + if ($3.substr(0, 1) !== '"') { + $3 = `"${$3}"`; + } + return `await dk.talk ({pid: pid, text: ${$3}})`; + } + ]; + + keywords[i++] = [ + /^\s*(send sms to)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams($3, ['mobile', 'message']); + return `await sys.sendSmsTo({pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(send email)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams($3, ['to', 'subject', 'body']); + return `await dk.sendEmail({pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(send mail)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams($3, ['to', 'subject', 'body']); + return `await dk.sendEmail({pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(send file to)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams($3, ['mobile', 'filename', 'caption']); + return `await dk.sendFileTo({pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(hover)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams($3, ['handle', 'selector']); + return `await wa.hover ({pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(click link text)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams('page,' + $3, ['handle', 'text', 'index']); + return `await wa.linkByText ({pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(click)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + // page is not string. + // https://github.com/GeneralBots/BotServer/issues/310 + const params = this.getParams('page,' + $3, ['handle', 'frameOrSelector', 'selector']); + return `await wa.click ({pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(send file)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams($3, ['filename', 'caption']); + return `await dk.sendFile({pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(copy)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams($3, ['src', 'dst']); + return `await sys.copyFile ({pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(convert)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams($3, ['src', 'dst']); + return `await sys.convert ({pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*chart(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + const params = this.getParams($3, ['type', 'data', 'legends', 'transpose']); + return `${$1} = await dk.chart ({pid: pid, ${params}})`; + } + ]; + + keywords[i++] = [ + /^\s*MERGE\s*(.*)\s*WITH\s*(.*)BY\s*(.*)/gim, + ($0, $1, $2, $3) => { + return `await sys.merge({pid: pid, file: ${$1}, data: ${$2}, key1: ${$3}})`; + } + ]; + + keywords[i++] = [ + /^\s*PRESS\s*(.*)/gim, + ($0, $1, $2) => { + return `await wa.pressKey({pid: pid, handle: page, char: ${$1}})`; + } + ]; + + keywords[i++] = [ + /^\s*SCREENSHOT\s*(.*)/gim, + ($0, $1, $2) => { + return `await wa.screenshot({pid: pid, handle: page, selector: ${$1}})`; + } + ]; + + keywords[i++] = [ + /^\s*TWEET\s*(.*)/gim, + ($0, $1, $2) => { + return `await sys.tweet({pid: pid, text: ${$1}})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*(.*)\s*as image/gim, + ($0, $1, $2) => { + return `${$1} = await sys.asImage({pid: pid, data: ${$2}})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*(.*)\s*as pdf/gim, + ($0, $1, $2) => { + return `${$1} = await sys.asPdf({pid: pid, data: ${$2})`; + } + ]; + + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*FILL\s*(.*)\s*WITH\s*(.*)/gim, + ($0, $1, $2, $3) => { + return `${$1} = await sys.fill({pid: pid, templateName: ${$2}, data: ${$3}})`; + } + ]; + + keywords[i++] = [ + /^\s*save\s*(.*)\s*as\s*(.*)/gim, + ($0, $1, $2, $3) => { + return `await sys.saveFile({pid: pid, file: ${$2}, data: ${$1}})`; + } + ]; + keywords[i++] = [ + /^\s*(save)(\s*)(.*)/gim, + ($0, $1, $2, $3) => { + return `await sys.save({pid: pid, args: [${$3}]})`; + } + ]; + + keywords[i++] = [ + /^\s*set\s*(.*)/gim, + ($0, $1, $2) => { + const params = this.getParams($1, ['file', 'address', 'value']); + return `await sys.set ({pid: pid, ${params}})`; + } + ]; + keywords[i++] = [ + /^\s*(\w+)\s*\=\s*SHARPEN\s*(.*)/gim, + ($0, $1, $2, $3) => { + return ` + ${$1} = await img.sharpen({pid: pid, args: [${$2}]})`; + } + ]; + + return keywords; + } +}