diff --git a/.vscode/launch.json b/.vscode/launch.json index bbee18ad4..9e3fe8b90 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,6 @@ "args": [ "--no-deprecation", "--loader ts-node/esm", - "--openssl-legacy-provider", "--require ${workspaceRoot}/suppress-node-warnings.cjs", ], "skipFiles": [ diff --git a/boot.mjs b/boot.mjs index 74c14c042..3749aa205 100644 --- a/boot.mjs +++ b/boot.mjs @@ -13,7 +13,7 @@ console.log(`██ █ ███ █ █ ██ ██ ██ console.log(`██ ███ ████ █ ██ █ ████ █████ ██████ ██ ████ █ █ █ ██ `); console.log(`██ ██ █ █ ██ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ `); console.log(` █████ █████ █ ███ █████ ██ ██ ██ ██ ██████ ████ █████ █ ███ 3.0`); -process.stdout.write(` botserver@${pjson.version}, botlib@${pjson.dependencies.botlib}, botbuilder@${pjson.dependencies.botbuilder}, node@${process.version.replace('v', '')}, ${process.platform} ${process.arch}`); +process.stdout.write(` botserver@${pjson.version}, botlib@${pjson.dependencies.botlib}, botbuilder@${pjson.dependencies.botbuilder}, node@${process.version.replace('v', '')}, ${process.platform} ${process.arch} `); var now = () => { return new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '') + ' UTC'; @@ -28,7 +28,8 @@ try { }; var processDist = () => { if (!Fs.existsSync('dist')) { - console.log(`${now()} - Compiling...`); + console.log(`\n`); + console.log(`Generall Bots: Compiling...`); exec(Path.join(__dirname, 'node_modules/.bin/tsc'), (err, stdout, stderr) => { if (err) { console.error(err); @@ -44,7 +45,8 @@ try { // Installing modules if it has not been done yet. if (!Fs.existsSync('node_modules')) { - console.log(`${now()} - Installing modules for the first time, please wait...`); + console.log(`\n`); + console.log(`Generall Bots: Installing modules for the first time, please wait...`); exec('npm install', (err, stdout, stderr) => { if (err) { console.error(err); diff --git a/gbot.cmd b/gbot.cmd index 12352570f..5dac5910a 100644 --- a/gbot.cmd +++ b/gbot.cmd @@ -3,13 +3,13 @@ ECHO General Bots Command Line IF EXIST node_modules goto COMPILE -ECHO Installing Packages for the first time use... +ECHO Installing Packages for the first time use (it may take several minutes)... CALL npm install --silent :COMPILE IF EXIST dist goto ALLSET ECHO Compiling... -CALL node_modules\.bin\tsc +npm run build :ALLSET -node boot.mjs +npm run start diff --git a/package.json b/package.json index b17f96666..be3e51ec6 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "build-gbui": "cd packages/default.gbui && echo SKIP_PREFLIGHT_CHECK=true >.env && npm install && npm run build", "build-docs": "typedoc --options typedoc.json src/", "test": "node test.js", - "start": "NODE_NO_WARNINGS=1 node ./boot.mjs --loader ts-node/esm --require ./suppress-node-warnings.cjs ", + "start": "NODE_NO_WARNINGS=1 node ./boot.mjs --loader ts-node/esm --require ./suppress-node-warnings.cjs", "reverse-proxy": "node_modules/.bin/ngrok http 4242", "watch:build": "tsc --watch", "posttypedoc": "shx cp .nojekyll docs/reference/.nojekyll", @@ -119,9 +119,11 @@ "koa": "2.13.4", "koa-body": "6.0.1", "koa-router": "12.0.0", + "line-replace": "2.0.1", "lodash": "4.17.21", "luxon": "3.1.0", "mammoth": "1.5.1", + "mime-types": "^2.1.35", "moment": "1.3.0", "ms-rest-azure": "3.0.0", "nexmo": "2.9.1", @@ -182,6 +184,7 @@ "yarn": "1.22.19" }, "devDependencies": { + "@types/qrcode": "1.5.0", "@types/url-join": "4.0.1", "ban-sensitive-files": "1.9.18", "commitizen": "4.2.2", diff --git a/packages/basic.gblib/index.ts b/packages/basic.gblib/index.ts index 7c38a978a..78bad742c 100644 --- a/packages/basic.gblib/index.ts +++ b/packages/basic.gblib/index.ts @@ -36,39 +36,31 @@ 'use strict'; -import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBInstance, IGBPackage } from 'botlib'; +import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib'; import { GuaribasSchedule } from '../core.gbapp/models/GBModel.js'; import { Sequelize } from 'sequelize-typescript'; -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 Koa from 'koa'; import cors from '@koa/cors'; -import { createRpcServer } from '@push-rpc/core'; import { createHttpKoaMiddleware } from '@push-rpc/http'; import { HttpServerOptions } from '@push-rpc/http/dist/server.js'; import { GBServer } from '../../src/app.js'; -import { } from '@push-rpc/core'; +import { SocketServer } from '@push-rpc/core'; import * as koaBody from 'koa-body'; -import { GBVMService } from './services/GBVMService.js'; -import { GBLogEx } from '../core.gbapp/services/GBLogEx.js'; -import { CollectionUtil } from 'pragmatismo-io-framework'; export function createKoaHttpServer( port: number, getRemoteId: (ctx: Koa.Context) => string, opts: Partial = {} ): SocketServer { - const { onError, onConnection, middleware } = createHttpKoaMiddleware(getRemoteId, opts); + const { onError, onConnection, middleware } = + createHttpKoaMiddleware(getRemoteId, opts); const app = new Koa(); app.use(cors({ origin: '*' })); app.use(koaBody.koaBody({ multipart: true })); app.use(middleware); const server = app.listen(port); - const SERVER_TIMEOUT = 60 * 1000; + const SERVER_TIMEOUT = 60 * 60 * 24 * 1000; // Equals to client RPC set . server.timeout = SERVER_TIMEOUT; return { diff --git a/packages/basic.gblib/services/DialogKeywords.ts b/packages/basic.gblib/services/DialogKeywords.ts index e066629c6..c11214253 100644 --- a/packages/basic.gblib/services/DialogKeywords.ts +++ b/packages/basic.gblib/services/DialogKeywords.ts @@ -46,7 +46,7 @@ import { Messages } from '../strings.js'; import * as Fs from 'fs'; import { CollectionUtil } from 'pragmatismo-io-framework'; import { GBConversationalService } from '../../core.gbapp/services/GBConversationalService.js'; -import phoneUtil from 'google-libphonenumber'; +import phoneUtil from 'google-libphonenumber'; import phone from 'phone'; import DateDiff from 'date-diff'; import tesseract from 'node-tesseract-ocr'; @@ -58,7 +58,9 @@ import { WebAutomationServices } from './WebAutomationServices.js'; import urljoin from 'url-join'; import QrScanner from 'qr-scanner'; import pkg from 'whatsapp-web.js'; +import { ActivityTypes } from 'botbuilder'; const { List, Buttons } = pkg; +import mime from 'mime-types'; /** * Default check interval for user replay @@ -204,28 +206,28 @@ export class DialogKeywords { * * @example EXIT */ - public async exit({ }) { } + public async exit({}) {} /** * Get active tasks. * * @example list = ACTIVE TASKS */ - public async getActiveTasks({ pid }) { } + public async getActiveTasks({ pid }) {} /** * Creates a new deal. * * @example CREATE DEAL dealname,contato,empresa,amount */ - public async createDeal({ pid, dealName, contact, company, amount }) { } + public async createDeal({ pid, dealName, contact, company, amount }) {} /** * Finds contacts in XRM. * * @example list = FIND CONTACT "Sandra" */ - public async fndContact({ pid, name }) { } + public async fndContact({ pid, name }) {} public getContentLocaleWithCulture(contentLocale) { switch (contentLocale) { @@ -495,7 +497,7 @@ export class DialogKeywords { */ public async sendFileTo({ pid, mobile, filename, caption }) { GBLog.info(`BASIC: SEND FILE TO '${mobile}',filename '${filename}'.`); - return await this.internalSendFile({ pid, mobile, filename, caption }); + return await this.internalSendFile({ pid, mobile, channel: null, filename, caption }); } /** @@ -505,9 +507,10 @@ export class DialogKeywords { * */ public async sendFile({ pid, filename, caption }) { + const { min, user, proc } = await DialogKeywords.getProcessInfo(pid); + GBLog.info(`BASIC: SEND FILE (to: ${user.userSystemId},filename '${filename}'.`); const mobile = await this.userMobile({ pid }); - GBLog.info(`BASIC: SEND FILE (current: ${mobile},filename '${filename}'.`); - return await this.internalSendFile({ pid, mobile, filename, caption }); + return await this.internalSendFile({ pid, channel: proc.channel, mobile, filename, caption }); } /** @@ -676,7 +679,7 @@ export class DialogKeywords { * @example MENU * */ - public async showMenu({ }) { + public async showMenu({}) { // https://github.com/GeneralBots/BotServer/issues/237 // return await beginDialog('/menu'); } @@ -1031,25 +1034,15 @@ export class DialogKeywords { static getGBAIPath(botId, packageType = null, packageName = null) { let gbai = `${botId}.gbai`; if (!packageType && !packageName) { - return GBConfigService.get('DEV_GBAI') ? - GBConfigService.get('DEV_GBAI') : - gbai; + return GBConfigService.get('DEV_GBAI') ? GBConfigService.get('DEV_GBAI') : gbai; } if (GBConfigService.get('DEV_GBAI')) { gbai = GBConfigService.get('DEV_GBAI'); - botId = gbai.replace(/\.[^/.]+$/, ""); - return urljoin(GBConfigService.get('DEV_GBAI'), - packageName ? - packageName : - `${botId}.${packageType}`); - } - else { - - return urljoin(gbai, - packageName ? - packageName : - `${botId}.${packageType}`); + botId = gbai.replace(/\.[^/.]+$/, ''); + return urljoin(GBConfigService.get('DEV_GBAI'), packageName ? packageName : `${botId}.${packageType}`); + } else { + return urljoin(gbai, packageName ? packageName : `${botId}.${packageType}`); } } @@ -1094,7 +1087,8 @@ export class DialogKeywords { return { min, user, - params + params, + proc }; } @@ -1121,21 +1115,22 @@ export class DialogKeywords { /** * Processes the sending of the file. */ - private async internalSendFile({ pid, mobile, filename, caption }) { + private async internalSendFile({ pid, channel, mobile, filename, caption }) { // Handles SEND FILE TO mobile,element in Web Automation. const { min, user } = await DialogKeywords.getProcessInfo(pid); const element = filename._page ? filename._page : filename.screenshot ? filename : null; + let url; if (element) { const gbaiName = DialogKeywords.getGBAIPath(min.botId); const localName = Path.join('work', gbaiName, 'cache', `img${GBAdminService.getRndReadableIdentifier()}.jpg`); await element.screenshot({ path: localName, fullPage: true }); - const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', Path.basename(localName)); + url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', Path.basename(localName)); GBLog.info(`BASIC: WebAutomation: Sending the file ${url} to mobile ${mobile}.`); - await min.conversationalService.sendFile(min, null, mobile, url, caption); + } // Handles Markdown. @@ -1148,25 +1143,37 @@ export class DialogKeywords { await min.conversationalService['playMarkdown'](min, md, DialogKeywords.getChannel(), mobile); } else { - const gbaiName = DialogKeywords.getGBAIPath(min.botId, `gbkb`); GBLog.info(`BASIC: Sending the file ${filename} to mobile ${mobile}.`); - let url: string; + if (!filename.startsWith('https://')) { - url = urlJoin( - GBServer.globals.publicAddress, - 'kb', - gbaiName, - 'assets', - filename - ); + url = urlJoin(GBServer.globals.publicAddress, 'kb', gbaiName, 'assets', filename); } else { url = filename; } - - await min.conversationalService.sendFile(min, null, mobile, url, caption); } + + if (url){ + const reply = { type: ActivityTypes.Message, text: caption }; + + const imageData = await (await fetch(url)).arrayBuffer(); + const base64Image = Buffer.from(imageData).toString('base64'); + const contentType = mime.lookup(url); + reply['attachments'] = []; + reply['attachments'].push({ + name: filename, + contentType: contentType, + contentUrl: `data:${contentType};base64,${base64Image}` + }); + + if (channel === 'omnichannel') { + await min.conversationalService.sendFile(min, null, mobile, url, caption); + } else { + await min.conversationalService['sendOnConversation'](min, user, reply); + } + } + } /** * Generates a new QRCode. diff --git a/packages/basic.gblib/services/GBVMService.ts b/packages/basic.gblib/services/GBVMService.ts index a4481b00c..c974b6bdc 100644 --- a/packages/basic.gblib/services/GBVMService.ts +++ b/packages/basic.gblib/services/GBVMService.ts @@ -52,6 +52,7 @@ 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'; +import lineReplace from 'line-replace'; /** * @fileoverview Decision was to priorize security(isolation) and debugging, @@ -113,7 +114,7 @@ export class GBVMService extends GBService { } // Process node_modules install. - const node_modules = urlJoin(folder, 'node_modules'); + const node_modules = urlJoin(process.env.PWD, folder, 'node_modules'); if (!Fs.existsSync(node_modules)) { const packageJson = ` { @@ -136,6 +137,34 @@ export class GBVMService extends GBService { GBLogEx.info(min, `BASIC: Installing .gbdialog node_modules for ${min.botId}...`); const npmPath = urlJoin(process.env.PWD, 'node_modules', '.bin', 'npm'); child_process.execSync(`${npmPath} install`, { cwd: folder }); + + // // Hacks push-rpc to put timeout. + + // const inject1 = ` + // const { AbortController } = require("node-abort-controller"); + // var controller_1 = new AbortController(); + // var signal = controller_1.signal; + // setTimeout(function () { controller_1.abort(); }, 24 * 60 * 60 * 1000);`; + // const inject2 = `signal: signal,`; + // const js = Path.join(process.env.PWD, folder, 'node_modules/@push-rpc/http/dist/client.js'); + + // lineReplace({ + // file: js, + // line: 75, + // text: inject1, + // addNewLine: true, + // callback: ({ file, line, text, replacedText, error }) => { + // lineReplace({ + // file: js, + // line: 82, + // text: inject2, + // addNewLine: true, + // callback: ({ file, line, text, replacedText, error }) => { + // GBLogEx.info(min, `BASIC: Patching node_modules for ${min.botId} done.`); + // } + // }); + // } + // }); } // Hot swap for .vbs files. @@ -209,16 +238,17 @@ export class GBVMService extends GBService { const createHttpClient = require("@push-rpc/http").createHttpClient; // Setups interprocess communication from .gbdialog run-time to the BotServer API. - + const optsRPC = {callTimeout: this.callTimeout}; let url; + url = 'http://localhost:${GBVMService.API_PORT}/api/v3/${min.botId}/dk'; - const dk = (await createRpcClient(0, () => createHttpClient(url))).remote; + const dk = (await createRpcClient(0, () => createHttpClient(url), optsRPC)).remote; url = 'http://localhost:${GBVMService.API_PORT}/api/v3/${min.botId}/sys'; - const sys = (await createRpcClient(0, () => createHttpClient(url))).remote; + const sys = (await createRpcClient(0, () => createHttpClient(url), optsRPC)).remote; url = 'http://localhost:${GBVMService.API_PORT}/api/v3/${min.botId}/wa'; - const wa = (await createRpcClient(0, () => createHttpClient(url))).remote; + const wa = (await createRpcClient(0, () => createHttpClient(url), optsRPC)).remote; url = 'http://localhost:${GBVMService.API_PORT}/api/v3/${min.botId}/img'; - const img = (await createRpcClient(0, () => createHttpClient(url))).remote; + const img = (await createRpcClient(0, () => createHttpClient(url), optsRPC)).remote; // Unmarshalls Local variables from server VM. @@ -227,6 +257,7 @@ export class GBVMService extends GBService { let username = this.username; let mobile = this.mobile; let from = this.from; + let channel = this.channel; let ENTER = this.ENTER; let headers = this.headers; let data = this.data; @@ -302,7 +333,7 @@ export class GBVMService extends GBService { text = text.replace(/\”/gm, '"'); text = text.replace(/\‘/gm, "'"); text = text.replace(/\’/gm, "'"); - + return text; } @@ -372,17 +403,18 @@ export class GBVMService extends GBService { } const botId = min.botId; - const path = DialogKeywords.getGBAIPath(min.botId,`gbdialog`); + const path = DialogKeywords.getGBAIPath(min.botId, `gbdialog`); const gbdialogPath = urlJoin(process.cwd(), 'work', path); const scriptPath = urlJoin(gbdialogPath, `${text}.js`); let code = min.sandBoxMap[text]; - + const channel = step? step.context.activity.channelId : 'web'; const pid = GBAdminService.getNumberIdentifier(); GBServer.globals.processes[pid] = { pid: pid, userId: user.userId, - instanceId: min.instance.instanceId + instanceId: min.instance.instanceId, + channel: channel }; const dk = new DialogKeywords(); const sys = new SystemKeywords(); @@ -400,7 +432,9 @@ export class GBVMService extends GBService { sandbox['httpPs'] = ''; sandbox['pid'] = pid; sandbox['contentLocale'] = contentLocale; - + sandbox['callTimeout'] = 60 * 60 * 24 * 1000; + sandbox['channel'] = channel; + let result; try { @@ -411,7 +445,7 @@ export class GBVMService extends GBService { console: 'inherit', wrapper: 'commonjs', require: { - builtin: ['stream', 'http', 'https', 'url', 'zlib', 'net', 'tls', 'crypto', ], + builtin: ['stream', 'http', 'https', 'url', 'zlib', 'net', 'tls', 'crypto'], root: ['./'], external: true, context: 'sandbox' diff --git a/packages/core.gbapp/services/GBConversationalService.ts b/packages/core.gbapp/services/GBConversationalService.ts index 3a6e72668..b69fd8c75 100644 --- a/packages/core.gbapp/services/GBConversationalService.ts +++ b/packages/core.gbapp/services/GBConversationalService.ts @@ -304,6 +304,17 @@ export class GBConversationalService { return GBMinService.userMobile(step); } + public async sendFile2( + min: GBMinInstance, + step: GBDialogStep, + mobile: string, + url: string, + caption: string, + channel: string + ): Promise { + return await this.sendFile(min, step, mobile, url , caption); + } + public async sendFile( min: GBMinInstance, step: GBDialogStep, diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 3ec46ead1..6cf635c74 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -462,7 +462,7 @@ export class GBMinService { this.createCheckHealthAddress(GBServer.globals.server, min, min.instance); - GBDeployer.mountGBKBAssets(`${instance.botId}.gbkb`, instance.botId, `${instance.botId}.gbkb`); + GBDeployer.mountGBKBAssets(`${botId}.gbkb`, botId, `${botId}.gbkb`); } public static isChatAPI(req: any, res: any) { diff --git a/packages/security.gbapp/dialogs/ProfileDialog.ts b/packages/security.gbapp/dialogs/ProfileDialog.ts index 5cbefd6f7..ae0ba8bac 100644 --- a/packages/security.gbapp/dialogs/ProfileDialog.ts +++ b/packages/security.gbapp/dialogs/ProfileDialog.ts @@ -39,7 +39,7 @@ import { GBLog, GBMinInstance, IGBDialog } from 'botlib'; import { GBAdminService } from '../../admin.gbapp/services/GBAdminService.js'; import { Messages } from '../strings.js'; -import * as phone from 'google-libphonenumber'; +import * as phone from 'google-libphonenumber'; /** * Dialogs for handling Menu control.