From 09715bcfc0ac49fdcb39f891657a0fb8c8dad57c Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (pragmatismo.io)" Date: Mon, 26 Nov 2018 14:09:09 -0200 Subject: [PATCH 01/13] feat(scripting): First code changes to VBA implementation. --- CONTRIBUTING.md | 2 - package-lock.json | 30 +- packages/admin.gbapp/dialogs/AdminDialog.ts | 2 +- packages/core.gbapp/services/GBAPIService.ts | 88 +++++ packages/core.gbapp/services/GBDeployer.ts | 330 +++++++++--------- .../{GBImporter.ts => GBImporterService.ts} | 0 packages/core.gbapp/services/GBVMService.ts | 99 ++++++ packages/core.gbapp/tests/core.test.ts | 2 +- packages/core.gbapp/tests/vm.test.ts | 51 +++ packages/default.gbdialog/chat.dialog.js | 43 +++ packages/default.gbdialog/chat.dialog.vbs | 43 +++ packages/default.gbkb/draft.md | 3 + packages/default.gbkb/package.json | 6 + packages/default.gbkb/pages/about.md | 16 + packages/default.gbkb/subjects.json | 58 +++ packages/default.gbkb/subjects/bots-ai.png | Bin 0 -> 3893 bytes packages/default.gbkb/subjects/cortana.png | Bin 0 -> 12717 bytes .../default.gbkb/subjects/general-bots.png | Bin 0 -> 18257 bytes packages/default.gbkb/subjects/msdynamics.png | Bin 0 -> 10200 bytes packages/default.gbkb/subjects/msproject.png | Bin 0 -> 2896 bytes packages/default.gbkb/subjects/office365.png | Bin 0 -> 9018 bytes packages/default.gbkb/subjects/powerbi.png | Bin 0 -> 9018 bytes .../default.gbkb/subjects/produtividade.png | Bin 0 -> 20230 bytes packages/default.gbkb/subjects/sharepoint.png | Bin 0 -> 3994 bytes packages/default.gbkb/subjects/skype.png | Bin 0 -> 5181 bytes packages/default.gbkb/subjects/sobre.png | Bin 0 -> 2055 bytes .../default.gbkb/tabular/common-goodbye.tsv | Bin 0 -> 2542 bytes .../default.gbkb/tabular/common-hello.tsv | Bin 0 -> 1560 bytes .../default.gbkb/tabular/common-persona.tsv | Bin 0 -> 2516 bytes packages/default.gbkb/tabular/min.tsv | Bin 0 -> 120 bytes packages/default.gbkb/videos/placeholder | 0 src/app.ts | 2 +- 32 files changed, 604 insertions(+), 171 deletions(-) create mode 100644 packages/core.gbapp/services/GBAPIService.ts rename packages/core.gbapp/services/{GBImporter.ts => GBImporterService.ts} (100%) create mode 100644 packages/core.gbapp/services/GBVMService.ts create mode 100644 packages/core.gbapp/tests/vm.test.ts create mode 100644 packages/default.gbdialog/chat.dialog.js create mode 100644 packages/default.gbdialog/chat.dialog.vbs create mode 100644 packages/default.gbkb/draft.md create mode 100644 packages/default.gbkb/package.json create mode 100644 packages/default.gbkb/pages/about.md create mode 100644 packages/default.gbkb/subjects.json create mode 100644 packages/default.gbkb/subjects/bots-ai.png create mode 100644 packages/default.gbkb/subjects/cortana.png create mode 100644 packages/default.gbkb/subjects/general-bots.png create mode 100644 packages/default.gbkb/subjects/msdynamics.png create mode 100644 packages/default.gbkb/subjects/msproject.png create mode 100644 packages/default.gbkb/subjects/office365.png create mode 100644 packages/default.gbkb/subjects/powerbi.png create mode 100644 packages/default.gbkb/subjects/produtividade.png create mode 100644 packages/default.gbkb/subjects/sharepoint.png create mode 100644 packages/default.gbkb/subjects/skype.png create mode 100644 packages/default.gbkb/subjects/sobre.png create mode 100644 packages/default.gbkb/tabular/common-goodbye.tsv create mode 100644 packages/default.gbkb/tabular/common-hello.tsv create mode 100644 packages/default.gbkb/tabular/common-persona.tsv create mode 100644 packages/default.gbkb/tabular/min.tsv create mode 100644 packages/default.gbkb/videos/placeholder diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a8725974..1014fb0f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,13 +22,11 @@ When logging a bug, please be sure to include the following: We also accept suggestions in the issue tracker. - In general, things we find useful when reviewing suggestions are: * A description of the problem you're trying to solve * An overview of the suggested solution * Examples of how the suggestion would work in various places - # Instructions for Contributing Code ## Contributing bug fixes diff --git a/package-lock.json b/package-lock.json index e5be208f..2beefb6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "botserver", - "version": "1.0.7", + "version": "1.0.8", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -7105,11 +7105,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7122,15 +7124,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -7233,7 +7238,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -7243,6 +7249,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7255,17 +7262,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -7282,6 +7292,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -7354,7 +7365,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -7364,6 +7376,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -7469,6 +7482,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/packages/admin.gbapp/dialogs/AdminDialog.ts b/packages/admin.gbapp/dialogs/AdminDialog.ts index 8b9cd419..2d0b220c 100644 --- a/packages/admin.gbapp/dialogs/AdminDialog.ts +++ b/packages/admin.gbapp/dialogs/AdminDialog.ts @@ -43,7 +43,7 @@ import { GBMinInstance } from 'botlib'; import { IGBDialog } from 'botlib'; import { GBConfigService } from '../../core.gbapp/services/GBConfigService'; import { GBDeployer } from '../../core.gbapp/services/GBDeployer'; -import { GBImporter } from '../../core.gbapp/services/GBImporter'; +import { GBImporter } from '../../core.gbapp/services/GBImporterService'; import { GBAdminService } from '../services/GBAdminService'; import { Messages } from '../strings'; diff --git a/packages/core.gbapp/services/GBAPIService.ts b/packages/core.gbapp/services/GBAPIService.ts new file mode 100644 index 00000000..aa6308b8 --- /dev/null +++ b/packages/core.gbapp/services/GBAPIService.ts @@ -0,0 +1,88 @@ +/*****************************************************************************\ +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | +| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | +| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | +| | | ( )_) | | +| (_) \___/' | +| | +| 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 { BotAdapter } from 'botbuilder'; +import { GBError } from 'botlib'; +import { IGBPackage } from 'botlib'; +import * as fs from 'fs'; +import { Messages } from '../strings'; +const logger = require('../../../src/logger'); +import { WaterfallDialog } from 'botbuilder-dialogs'; +import { IGBCoreService, IGBInstance } from 'botlib'; +import { resolve } from 'bluebird'; +const util = require('util'); +const vm = require('vm'); + +/** + * @fileoverview General Bots server core. + */ + +export class DialogClass { + public step: any; + public min: IGBInstance; + + constructor(min: IGBInstance) { + this.min = min; + } + + public async expectMessage(text: string): Promise { + return new Promise((resolve, reject) => { + this.min.dialogs.add( + new WaterfallDialog('/vmExpect', [ + async step => { + await step.prompt('textPrompt', text); + return await step.next(); + }, + async step => { + resolve(step.result); + return await step.next(); + }, + ]), + ); + }); + } + + public sendMessage(text: string) { + this.min.dialogs.add( + new WaterfallDialog('/vmSend', [ + async step => { + await step.context.sendActivity(text); + return await step.next(); + }, + ]), + ); + } + + +} diff --git a/packages/core.gbapp/services/GBDeployer.ts b/packages/core.gbapp/services/GBDeployer.ts index 5312d6f4..35581bae 100644 --- a/packages/core.gbapp/services/GBDeployer.ts +++ b/packages/core.gbapp/services/GBDeployer.ts @@ -51,7 +51,7 @@ import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDe import { GuaribasInstance, GuaribasPackage } from '../models/GBModel'; import { KBService } from './../../kb.gbapp/services/KBService'; import { GBConfigService } from './GBConfigService'; -import { GBImporter } from './GBImporter'; +import { GBImporter } from './GBImporterService'; /** Deployer service for bots, themes, ai and more. */ export class GBDeployer { @@ -83,160 +83,163 @@ export class GBDeployer { public deployPackages( core: IGBCoreService, server: any, - appPackages: IGBPackage[] + appPackages: IGBPackage[], ) { const _this = this; - return new Promise((resolve: any, reject: any): any => { - let totalPackages = 0; - const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH'); - let paths = [GBDeployer.deployFolder]; - if (additionalPath) { - paths = paths.concat(additionalPath.toLowerCase().split(';')); - } - const botPackages = new Array(); - const gbappPackages = new Array(); - let generalPackages = new Array(); + return new Promise( + (resolve: any, reject: any): any => { + let totalPackages = 0; + const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH'); + let paths = [GBDeployer.deployFolder]; + if (additionalPath) { - function doIt(path) { - const isDirectory = source => Fs.lstatSync(source).isDirectory(); - const getDirectories = source => - Fs.readdirSync(source) - .map(name => Path.join(source, name)) - .filter(isDirectory); - - const dirs = getDirectories(path); - dirs.forEach(element => { - if (element.startsWith('.')) { - logger.info(`Ignoring ${element}...`); - } else { - if (element.endsWith('.gbot')) { - botPackages.push(element); - } else if (element.endsWith('.gbapp')) { - gbappPackages.push(element); - } else { - generalPackages.push(element); - } - } - }); - } - - logger.info( - `Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...` - ); - paths.forEach(e => { - logger.info(`Looking in: ${e}...`); - doIt(e); - }); - - /** Deploys all .gbapp files first. */ - - let appPackagesProcessed = 0; - - gbappPackages.forEach(e => { - // Skips .gbapp inside deploy folder. - if (!e.startsWith('packages')) { - logger.info(`Deploying app: ${e}...`); - import(e) - .then(m => { - const p = new m.Package(); - p.loadPackage(core, core.sequelize); - appPackages.push(p); - logger.info(`App (.gbapp) deployed: ${e}.`); - appPackagesProcessed++; - }) - .catch(err => { - logger.error(`Error deploying App (.gbapp): ${e}: ${err}`); - appPackagesProcessed++; - }); - } else { - appPackagesProcessed++; + paths = paths.concat(additionalPath.toLowerCase().split(';')); } - }); + const botPackages = new Array(); + const gbappPackages = new Array(); + let generalPackages = new Array(); - WaitUntil() - .interval(1000) - .times(10) - .condition(function(cb) { - logger.info(`Waiting for app package deployment...`); - cb(appPackagesProcessed == gbappPackages.length); - }) - .done(async result => { - logger.info(`App Package deployment done.`); + function doIt(path) { + const isDirectory = source => Fs.lstatSync(source).isDirectory(); + const getDirectories = source => + Fs.readdirSync(source) + .map(name => Path.join(source, name)) + .filter(isDirectory); - try { - await core.syncDatabaseStructure(); - } catch (e) { - throw e; - } - - /** Deploys all .gbot files first. */ - - botPackages.forEach(e => { - logger.info(`Deploying bot: ${e}...`); - _this.deployBot(e); - logger.info(`Bot: ${e} deployed...`); - }); - - /** Then all remaining generalPackages are loaded. */ - - generalPackages = generalPackages.filter(p => !p.endsWith('.git')); - - generalPackages.forEach(filename => { - const filenameOnly = Path.basename(filename); - logger.info(`Deploying package: ${filename}...`); - - /** Handles apps for general bots - .gbapp must stay out of deploy folder. */ - - if ( - Path.extname(filename) === '.gbapp' || - Path.extname(filename) === '.gblib' - ) { - /** Themes for bots. */ - } else if (Path.extname(filename) === '.gbtheme') { - server.use('/themes/' + filenameOnly, express.static(filename)); - logger.info( - `Theme (.gbtheme) assets accessible at: ${'/themes/' + - filenameOnly}.` - ); - - /** Knowledge base for bots. */ - } else if (Path.extname(filename) === '.gbkb') { - server.use( - '/kb/' + filenameOnly + '/subjects', - express.static(UrlJoin(filename, 'subjects')) - ); - logger.info( - `KB (.gbkb) assets accessible at: ${'/kb/' + filenameOnly}.` - ); - } else if (Path.extname(filename) === '.gbui') { - // Already Handled + const dirs = getDirectories(path); + dirs.forEach(element => { + if (element.startsWith('.')) { + logger.info(`Ignoring ${element}...`); } else { - /** Unknown package format. */ - const err = new Error(`Package type not handled: ${filename}.`); - reject(err); - } - totalPackages++; - }); - - WaitUntil() - .interval(100) - .times(5) - .condition(function(cb) { - logger.info(`Waiting for package deployment...`); - cb(totalPackages == generalPackages.length); - }) - .done(function(result) { - if (botPackages.length === 0) { - logger.info( - 'No external packages to load, please use ADDITIONAL_DEPLOY_PATH to point to a .gbai package folder.' - ); + if (element.endsWith('.gbot')) { + botPackages.push(element); + } else if (element.endsWith('.gbapp')) { + gbappPackages.push(element); } else { - logger.info(`Package deployment done.`); + generalPackages.push(element); } - resolve(); - }); + } + }); + } + + logger.info( + `Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...`, + ); + paths.forEach(e => { + logger.info(`Looking in: ${e}...`); + doIt(e); }); - }); + + /** Deploys all .gbapp files first. */ + + let appPackagesProcessed = 0; + + gbappPackages.forEach(e => { + // Skips .gbapp inside deploy folder. + if (!e.startsWith('packages')) { + logger.info(`Deploying app: ${e}...`); + import(e) + .then(m => { + const p = new m.Package(); + p.loadPackage(core, core.sequelize); + appPackages.push(p); + logger.info(`App (.gbapp) deployed: ${e}.`); + appPackagesProcessed++; + }) + .catch(err => { + logger.error(`Error deploying App (.gbapp): ${e}: ${err}`); + appPackagesProcessed++; + }); + } else { + appPackagesProcessed++; + } + }); + + WaitUntil() + .interval(1000) + .times(10) + .condition(function(cb) { + logger.info(`Waiting for app package deployment...`); + cb(appPackagesProcessed == gbappPackages.length); + }) + .done(async result => { + logger.info(`App Package deployment done.`); + + try { + await core.syncDatabaseStructure(); + } catch (e) { + throw e; + } + + /** Deploys all .gbot files first. */ + + botPackages.forEach(e => { + logger.info(`Deploying bot: ${e}...`); + _this.deployBot(e); + logger.info(`Bot: ${e} deployed...`); + }); + + /** Then all remaining generalPackages are loaded. */ + + generalPackages = generalPackages.filter(p => !p.endsWith('.git')); + + generalPackages.forEach(filename => { + const filenameOnly = Path.basename(filename); + logger.info(`Deploying package: ${filename}...`); + + /** Handles apps for general bots - .gbapp must stay out of deploy folder. */ + + if ( + Path.extname(filename) === '.gbapp' || + Path.extname(filename) === '.gblib' + ) { + /** Themes for bots. */ + } else if (Path.extname(filename) === '.gbtheme') { + server.use('/themes/' + filenameOnly, express.static(filename)); + logger.info( + `Theme (.gbtheme) assets accessible at: ${'/themes/' + + filenameOnly}.`, + ); + + /** Knowledge base for bots. */ + } else if (Path.extname(filename) === '.gbkb') { + server.use( + '/kb/' + filenameOnly + '/subjects', + express.static(UrlJoin(filename, 'subjects')), + ); + logger.info( + `KB (.gbkb) assets accessible at: ${'/kb/' + filenameOnly}.`, + ); + } else if (Path.extname(filename) === '.gbui') { + // Already Handled + } else { + /** Unknown package format. */ + const err = new Error(`Package type not handled: ${filename}.`); + reject(err); + } + totalPackages++; + }); + + WaitUntil() + .interval(100) + .times(5) + .condition(function(cb) { + logger.info(`Waiting for package deployment...`); + cb(totalPackages == generalPackages.length); + }) + .done(function(result) { + if (botPackages.length === 0) { + logger.info( + 'No external packages to load, please use ADDITIONAL_DEPLOY_PATH to point to a .gbai package folder.', + ); + } else { + logger.info(`Package deployment done.`); + } + resolve(); + }); + }); + }, + ); } /** @@ -248,18 +251,18 @@ export class GBDeployer { const packageName = Path.basename(localPath); const instance = await this.importer.importIfNotExistsBotPackage( packageName, - localPath + localPath, ); return instance; } public async deployPackageToStorage( instanceId: number, - packageName: string + packageName: string, ): Promise { return GuaribasPackage.create({ packageName: packageName, - instanceId: instanceId + instanceId: instanceId, }); } @@ -293,16 +296,25 @@ export class GBDeployer { case '.gbui': break; + case '.gbdialog': + const vm = new VMService(this.core.sequelize); + return service.deployKb(this.core, this, localPath); + + break; + default: const err = GBError.create( - `GuaribasBusinessError: Unknow package type: ${packageType}.` + `GuaribasBusinessError: Unknow package type: ${packageType}.`, ); Promise.reject(err); break; } } - public async undeployPackageFromLocalPath(instance: IGBInstance, localPath: string) { + public async undeployPackageFromLocalPath( + instance: IGBInstance, + localPath: string, + ) { const packageType = Path.extname(localPath); const packageName = Path.basename(localPath); @@ -326,7 +338,7 @@ export class GBDeployer { default: const err = GBError.create( - `GuaribasBusinessError: Unknown package type: ${packageType}.` + `GuaribasBusinessError: Unknown package type: ${packageType}.`, ); Promise.reject(err); break; @@ -338,10 +350,12 @@ export class GBDeployer { instance.searchKey, instance.searchHost, instance.searchIndex, - instance.searchIndexer + instance.searchIndexer, ); - const connectionString = GBDeployer.getConnectionStringFromInstance(instance); + const connectionString = GBDeployer.getConnectionStringFromInstance( + instance, + ); const dsName = 'gb'; try { @@ -358,7 +372,7 @@ export class GBDeployer { dsName, 'GuaribasQuestion', 'azuresql', - connectionString + connectionString, ); try { @@ -371,17 +385,17 @@ export class GBDeployer { } await search.createIndex( AzureDeployerService.getKBSearchSchema(instance.searchIndex), - dsName + dsName, ); } public async getPackageByName( instanceId: number, - packageName: string + packageName: string, ): Promise { const where = { packageName: packageName, instanceId: instanceId }; return GuaribasPackage.findOne({ - where: where + where: where, }); } @@ -398,7 +412,7 @@ export class GBDeployer { return Promise.resolve(true); } else { return this.deployPackageFromLocalPath( - UrlJoin(deployFolder, bootPackage) + UrlJoin(deployFolder, bootPackage), ); } } diff --git a/packages/core.gbapp/services/GBImporter.ts b/packages/core.gbapp/services/GBImporterService.ts similarity index 100% rename from packages/core.gbapp/services/GBImporter.ts rename to packages/core.gbapp/services/GBImporterService.ts diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts new file mode 100644 index 00000000..6eaa4a83 --- /dev/null +++ b/packages/core.gbapp/services/GBVMService.ts @@ -0,0 +1,99 @@ +/*****************************************************************************\ +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | +| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | +| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | +| | | ( )_) | | +| (_) \___/' | +| | +| 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 { IGBCoreService, IGBInstance } from 'botlib'; +import { GBError } from 'botlib'; +import { IGBPackage } from 'botlib'; +const logger = require('../../../src/logger'); +import * as fs from 'fs'; +import { BotAdapter } from 'botbuilder'; +import { WaterfallDialog } from 'botbuilder-dialogs'; +import { Messages } from '../strings'; +import { GBDeployer } from './GBDeployer'; +const util = require('util'); +const vm = require('vm'); + +/** + * @fileoverview General Bots server core. + */ + +export class GBVMService implements IGBCoreService { + + public static setup(bot: BotAdapter, min: IGBInstance) { + + } + + public loadJS( + filename: string, + min: IGBInstance, + core: IGBCoreService, + deployer: GBDeployer, + localPath: string + ) { + + const sandbox = { + animal: 'cat', + count: 2, + }; + + const script = new vm.Script('count += 1; name = "kitty";'); + const context = vm.createContext(sandbox); + + for (let i = 0; i < 10; ++i) { + script.runInContext(context); + } + + console.log(util.inspect(sandbox)); + + // { animal: 'cat', count: 12, name: 'kitty' } + + const packageType = Path.extname(localPath); + const packageName = Path.basename(localPath); + logger.info(`[GBDeployer] Opening package: ${localPath}`); + const packageObject = JSON.parse( + Fs.readFileSync(UrlJoin(localPath, 'package.json'), 'utf8'), + ); + + const instance = await core.loadInstance(packageObject.botId); + logger.info(`[GBDeployer] Importing: ${localPath}`); + const p = await deployer.deployPackageToStorage( + instance.instanceId, + packageName, + ); + await this.importKbPackage(localPath, p, instance); + + deployer.rebuildIndex(instance); + logger.info(`[GBDeployer] Finished import of ${localPath}`); + } +} diff --git a/packages/core.gbapp/tests/core.test.ts b/packages/core.gbapp/tests/core.test.ts index 9ec963d8..838d5f2d 100644 --- a/packages/core.gbapp/tests/core.test.ts +++ b/packages/core.gbapp/tests/core.test.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import 'mocha'; -import {GBImporter} from '../services/GBImporter'; +import {GBImporter} from '../services/GBImporterService'; describe('Hello function', () => { diff --git a/packages/core.gbapp/tests/vm.test.ts b/packages/core.gbapp/tests/vm.test.ts new file mode 100644 index 00000000..4102b513 --- /dev/null +++ b/packages/core.gbapp/tests/vm.test.ts @@ -0,0 +1,51 @@ +/*****************************************************************************\ +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | +| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | +| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | +| | | ( )_) | | +| (_) \___/' | +| | +| 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'; + +/** + * @fileoverview Tests for General Bots VM. + */ + +import { expect } from 'chai'; +import { GBVMService } from '../services/GBVMService'; + +describe('Load function', () => { + it('should fail on invalid file', () => { + try { + const service = new GBVMService(); + service.loadJS('invalid.file'); + } catch (error) { + expect(error).to.equal(0); + } + }); +}); diff --git a/packages/default.gbdialog/chat.dialog.js b/packages/default.gbdialog/chat.dialog.js new file mode 100644 index 00000000..6e2c001a --- /dev/null +++ b/packages/default.gbdialog/chat.dialog.js @@ -0,0 +1,43 @@ +export function chat() { + //**************************************************************************** + // ( )_ _ + // _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ + // ( //_`\ ( //__)///_` ) ///_ `\/// _ ` _ `\ ///_` )| | | |///,__)/// _ `\ ///_`\ + // | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) + // | ,__///(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/// + // | | ( )_) | + // (_) \___/// + // + // 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. + // + //**************************************************************************** + + main = () => { + bot.addFunction(PegaEmail); + + PegaEmail = bot => { + bot.say('Qual seu e-mail?'); + email = bot.expectEmail; + bot.post('/restservice', email); + }; + }; +} diff --git a/packages/default.gbdialog/chat.dialog.vbs b/packages/default.gbdialog/chat.dialog.vbs new file mode 100644 index 00000000..62979cb3 --- /dev/null +++ b/packages/default.gbdialog/chat.dialog.vbs @@ -0,0 +1,43 @@ +<% + +'**************************************************************************** +' ( )_ _ +' _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ +' ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ +' | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) +' | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' +' | | ( )_) | +' (_) \___/' +' +' 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. +' +'**************************************************************************** + +function ICanSendEmails() + + bot.say ("Please, what's your e-mail address?") + email = bot.expectEmail() + bot.sendMail (email, "Olá", "I'm sending a General Bots VBA e-mail.") + +end function + +%> \ No newline at end of file diff --git a/packages/default.gbkb/draft.md b/packages/default.gbkb/draft.md new file mode 100644 index 00000000..3e56b0b6 --- /dev/null +++ b/packages/default.gbkb/draft.md @@ -0,0 +1,3 @@ + Últimas notícias + Contato + Ofertas diff --git a/packages/default.gbkb/package.json b/packages/default.gbkb/package.json new file mode 100644 index 00000000..6d8fb717 --- /dev/null +++ b/packages/default.gbkb/package.json @@ -0,0 +1,6 @@ +{ + "botId":"pragmatismo-ai-prd", + "version": "1.0.0", + "description": "Bot pragmatismo.", + "license": "Private" +} \ No newline at end of file diff --git a/packages/default.gbkb/pages/about.md b/packages/default.gbkb/pages/about.md new file mode 100644 index 00000000..f8f61594 --- /dev/null +++ b/packages/default.gbkb/pages/about.md @@ -0,0 +1,16 @@ +#Desenvolvimento Personalizado + +General Bots usa linguagem natural para entender o que as pessoas querem, facilitando o desenvolvimento de código. Quando alguém diz: "Preciso do relatório mensal" ou "Imprima o relatório do mês", General Bots entende o mesmo. Utilize o nosso desenvolvimento para estender a conversa com suas próprias regras e intenções. + +#Descoberta + +General Bots pode pró-ativamente sugerir suas habilidades para os usuários baseada no contexto, como solicitação de um pedido, envio de uma mensagem, agendamento de uma conferência telefônica ou qualquer ação definida na sua aplicação. + +#Torne pessoal + +Entregue experiências únicas através do conhecimento que a General Bots possui sobre seus usuários e preferências, para ajudar a tomar decisões e apresentar sempre o melhor cenário. + +#Sem downloads adicionais + +A interface da sua aplicação é automaticamente integrada à General Bots, de modo que não seja necessário realizar download ou instalações. + diff --git a/packages/default.gbkb/subjects.json b/packages/default.gbkb/subjects.json new file mode 100644 index 00000000..23471402 --- /dev/null +++ b/packages/default.gbkb/subjects.json @@ -0,0 +1,58 @@ +{ + "children": [ + { + "title": "Bots & AI", + "description": "Bots & inteligência artificial.", + "id": "bots-ai", + "children": [ + { + "title": "General Bots", + "description": "Plataforma de bots da Pragmatismo.io.", + "id": "general-bots" + }, + { + "title": "Cortana Intelligence Suite", + "description": "Suite de Big Data da Microsoft.", + "id": "cortana" + } + ] + }, + { + "title": "Produtividade", + "description": "Artigos sobre sistemas Internos.", + "id": "produtividade", + "children": [ + { + "title": "Microsoft Project Online", + "description": "Artigos sobre o Microsoft Project Online.", + "id": "msproject" + }, + { + "title": "SharePoint", + "description": "SharePoint, sites e serviços.", + "id": "sharepoint" + }, + { + "title": "Office 365", + "description": "Plataforma colaborativa moderna da Microsoft.", + "id": "office365" + }, + { + "title": "Microsoft Dynamics", + "description": "Artigos sobre plataforma de CRM da Microsoft.", + "id": "msdynamics" + }, + { + "title": "Power BI", + "description": "Dashboards modernos e intuitivos.", + "id": "powerbi" + } + ] + }, + { + "title": "Sobre", + "description": "Artigos sobre o Bot da Pragmatismo.io", + "id": "sobre" + } + ] +} \ No newline at end of file diff --git a/packages/default.gbkb/subjects/bots-ai.png b/packages/default.gbkb/subjects/bots-ai.png new file mode 100644 index 0000000000000000000000000000000000000000..5c9197f7ffee0f231daccb70daa8abf8a365ea08 GIT binary patch literal 3893 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 A6951J literal 0 HcmV?d00001 diff --git a/packages/default.gbkb/subjects/cortana.png b/packages/default.gbkb/subjects/cortana.png new file mode 100644 index 0000000000000000000000000000000000000000..964451744357c2592048257fe0a095987ff7afb2 GIT binary patch literal 12717 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 literal 0 HcmV?d00001 diff --git a/packages/default.gbkb/subjects/general-bots.png b/packages/default.gbkb/subjects/general-bots.png new file mode 100644 index 0000000000000000000000000000000000000000..1a68418ef8e8934134b0d5b1166d722c676ed2cd GIT binary patch literal 18257 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 A$p8QV literal 0 HcmV?d00001 diff --git a/packages/default.gbkb/subjects/office365.png b/packages/default.gbkb/subjects/office365.png new file mode 100644 index 0000000000000000000000000000000000000000..f9aae394c7cd1b071db034515ec7e49e6a82e827 GIT binary patch literal 9018 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 b$rUAm^2>ssGbaUd0Piw4IbmFCM2h<#eZRE8 literal 0 HcmV?d00001 diff --git a/packages/default.gbkb/subjects/skype.png b/packages/default.gbkb/subjects/skype.png new file mode 100644 index 0000000000000000000000000000000000000000..7ee1402ebd2d79707c340f46cb890be31e537aec GIT binary patch literal 5181 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 aSU|s8>lQARFAjW81B?wU^q=WsqyG<5)-Hzt literal 0 HcmV?d00001 diff --git a/packages/default.gbkb/subjects/sobre.png b/packages/default.gbkb/subjects/sobre.png new file mode 100644 index 0000000000000000000000000000000000000000..9e0f1b7620839ba372350053d309f8d5a60dfed9 GIT binary patch literal 2055 zcmbVN2~gAL694|l89=aHQEpK#X^FmyL6Cr)sd5N~;>Z;t;=v8U#6S{(B2)ww1T7#S z2*m)R5fBUzN(u;1K(3@*1_PxWlAsm>grsq%^JdkQ^XwC7vHySj@R9QWT+ z+YbxSlP~Ip8jY;3U5phu@vV2)p^ZIV+yFp223BJh%im=}o5!+V`hg%jfyvCwP$JWQ zD*d4KnjWL?aHFB@aDzqbQ{60g4D?{u+Ml-J2k*}$x`W|@%{dy@dQ;0Ycw&CRk2pF>G=<7PzCTR z0OAnrWSu@k5(?|q`vwq>3oIdIGImK`qyQiVQIW65Sgi($qnt*w!gw*R}V{a+*> z`eV%8%_opW<0ELsPZ63R=;`Yse`Tc1Uz0%zo^!WvjMdKf70+|)>9lWjpquT{n2kZ) z6w)G>wEO$^E%kgN)fM!x2=L|M`Wli*e}< zO%W#jI(u!iJhgdrKbYk%KIs&hSkxSX4D?wOLVo^}PYE0N=0=j9*DXEqHI{^&?{hdt z>rK44ALxv`@Rrxh1ZQ#Y{uwFORfIUDcIh3h>=3@?fndeyhh1i%8mXL0SMHmwx{89G znDpXS$+-Fr_fPUJ@${SF8Scz`nO}8Ma>#A4zMfKj(R47tUr@rg#*14?1732`L|N-jWZ%TN{>u$q4JdEb9e>a_~%2 zCc`!-NQ!@Dg>LH=1+tqqFh1T6K~P5SsjNo9Ms{0H%+MZhUy!zI%07ZT?YEQ=Ssb6xo&MLhvOsjl2zN~JR z9(Vg~JaOf>a(RZM$sM!mpuIaqG^dh6@GpY==TZo6ZO7CH?)PA+hwbf>LMHN zi@pq+ZTPyW9PrYL=a0uo(oF#Xzx<74lf15I?^?@5^mcBFV1*=f x3iYj>pF1pBYVE)MloZ=-BL=vQC~gY#nQ#P>gLd|CsyiN%@+ZOWM7BSJ`sdm7xz(6lshTk*Xds_+rOc-s&;fH<%iE{WrP zc~>*zyuLJ8V}d(4ln7{GFhz|eDy{{wbXHnf#^#)(4Bv_g@k<^|{IWu|t}bDsKX&w1wj zbSRUAuO r&$0ilB^&C-mL2FS)KqR)?W|s=-m0Ir`$vj5#QrZycNzhwpPcy(_np$5 literal 0 HcmV?d00001 diff --git a/packages/default.gbkb/tabular/common-hello.tsv b/packages/default.gbkb/tabular/common-hello.tsv new file mode 100644 index 0000000000000000000000000000000000000000..97062e0d9fdd97522e3c8dbac6d53b787ed946b5 GIT binary patch literal 1560 zcmd6nOK!qI5JYQBq};(~jeG?p0_$wJfB{Ew6qCd@a9)<-5Ws7H7{W(H8WzZsKkc5b z>gt-=Z3`_l(T5UERjN=&Z_4$hjJJ|!M?YjF{3(_4Jtq=#FSH_>>se2%wEV348|j_+ zC)e$@hOp3#%m5Bag*iTnM6xT;2*mFS(Xwk!ftIYDYD1)iRYsLx?vW>}7pht}$MtIk z)^z^}3}4ZAtq?Zto8KG;bQ?D+=1e4^@_8)u)U~ZR2Q6h{$0#o5i_$$XcQt!eZ-2ek zSC=mTn;nwrr?EpauhuGG=hbTEa(#6<^=sFb_w9Q^ doKNS{ez_Nl^RYv5F<+Ds`lOBWDQAkgd=KC<6X5^= literal 0 HcmV?d00001 diff --git a/packages/default.gbkb/tabular/common-persona.tsv b/packages/default.gbkb/tabular/common-persona.tsv new file mode 100644 index 0000000000000000000000000000000000000000..363d71a89998f36d59790ac30abbb223fae92e02 GIT binary patch literal 2516 zcmeH}%Zd|G5Qb|T!FM>g6v-or!&)%PGY*_fNv`SIrI zAwn-Dq%U>qUVl~fkMFG=ZE5eUveG(hZDMOX*v=X~b-GXNy*MhJbXMzqD@vhjYuBP4 z?7rQTB>at^PHpe>*58W1%1`W->u)T{LZxddtyntROwteRnV#xj^uks;OR};PU(#7f z*199T(sd>NQeFlp6!?h&uL@mLA60T{CoL<{(Ns$IR`GE!dVOdOZAhZ6633-quXKXH z11~n#isjl*DtjdU8~vB!$HLk^x%Q2o!?(y^Wha-ID&}#rS{OEn4)5wdipukPA#d8= zKKcA6UMfr#lReaL>3QFXhqtKC)~@^<&6l40$R^Z-th6@uU$R9n(Rihj^7Yvz_s-S2 zM^PW@*7kO>p6g3q@w;)arX%GfYCt_c$X2OZ2g=^)?I>#CX{oo)D;LzFuXZ4GsM=Oq z=mO$7^$`}*(b-o;Ojqobk7VcC_mbF2Bk?tTCIb0L>|H+a2EwEN@GP=cOEZ2Pgrq_d zkUelqZw}soUnqs{oRjy+XY2Vqs)A6GxI#KAb&zhn0^#(3QpL#$Js+_=_Y>?#M{$Bb z;5@248wJ5{Wsmwq_h1dwvKsEd&XYojd;#y0=f#H666f2K*GP& zYMjnp$lpL%MkqZH{T0#A{f*jClVp$Wqr4?UQG*=y(ec2nCL2$6X9|Lhwf;=VTxZZn z+*qPRzDQflwAa!;7d<@6eUHuoHQ+tt6-?koDcl6Q>1Vh@Lo59U`NcFMCiut6+l6$2 z=0CG{)1&5p-pJ1R{*L_{n}V7!HRyIK7M-;bjcvykiJiyUfK8?lvt%YJ`}(EVm92x! zZI6enlf4~#IH~${T(QTd8s0OP8eMCD%U=L7wQ?6^Ul;a5+%bEX`d+}>p=a3_!zLJV Kf6gD@4L?gBB+M literal 0 HcmV?d00001 diff --git a/packages/default.gbkb/videos/placeholder b/packages/default.gbkb/videos/placeholder new file mode 100644 index 00000000..e69de29b diff --git a/src/app.ts b/src/app.ts index 1726d160..6014b637 100644 --- a/src/app.ts +++ b/src/app.ts @@ -53,7 +53,7 @@ import { GBConfigService } from '../packages/core.gbapp/services/GBConfigService import { GBConversationalService } from '../packages/core.gbapp/services/GBConversationalService'; import { GBCoreService } from '../packages/core.gbapp/services/GBCoreService'; import { GBDeployer } from '../packages/core.gbapp/services/GBDeployer'; -import { GBImporter } from '../packages/core.gbapp/services/GBImporter'; +import { GBImporter } from '../packages/core.gbapp/services/GBImporterService'; import { GBMinService } from '../packages/core.gbapp/services/GBMinService'; import { GBCustomerSatisfactionPackage } from '../packages/customer-satisfaction.gbapp'; import { GBKBPackage } from '../packages/kb.gbapp'; -- 2.39.5 From c1db8be0c074da0ccb10eb5968e79040343f9588 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (pragmatismo.io)" Date: Mon, 26 Nov 2018 15:54:34 -0200 Subject: [PATCH 02/13] fix(core): Moved logic from app to core. --- packages/core.gbapp/services/GBAPIService.ts | 24 +-- packages/core.gbapp/services/GBCoreService.ts | 181 ++++++++++++++++-- packages/core.gbapp/services/GBDeployer.ts | 11 +- packages/core.gbapp/services/GBVMService.ts | 49 ++--- packages/core.gbapp/tests/vm.test.ts | 2 +- src/app.ts | 153 +++------------ 6 files changed, 221 insertions(+), 199 deletions(-) diff --git a/packages/core.gbapp/services/GBAPIService.ts b/packages/core.gbapp/services/GBAPIService.ts index aa6308b8..e79025c7 100644 --- a/packages/core.gbapp/services/GBAPIService.ts +++ b/packages/core.gbapp/services/GBAPIService.ts @@ -32,31 +32,21 @@ 'use strict'; -import { BotAdapter } from 'botbuilder'; -import { GBError } from 'botlib'; -import { IGBPackage } from 'botlib'; -import * as fs from 'fs'; -import { Messages } from '../strings'; -const logger = require('../../../src/logger'); import { WaterfallDialog } from 'botbuilder-dialogs'; -import { IGBCoreService, IGBInstance } from 'botlib'; -import { resolve } from 'bluebird'; -const util = require('util'); -const vm = require('vm'); +import { IGBInstance, IGBPackage } from 'botlib'; /** * @fileoverview General Bots server core. */ export class DialogClass { - public step: any; public min: IGBInstance; constructor(min: IGBInstance) { this.min = min; } - public async expectMessage(text: string): Promise { + public async expectMessage(text: string): Promise { return new Promise((resolve, reject) => { this.min.dialogs.add( new WaterfallDialog('/vmExpect', [ @@ -67,8 +57,8 @@ export class DialogClass { async step => { resolve(step.result); return await step.next(); - }, - ]), + } + ]) ); }); } @@ -79,10 +69,8 @@ export class DialogClass { async step => { await step.context.sendActivity(text); return await step.next(); - }, - ]), + } + ]) ); } - - } diff --git a/packages/core.gbapp/services/GBCoreService.ts b/packages/core.gbapp/services/GBCoreService.ts index 6043c892..88f6690f 100644 --- a/packages/core.gbapp/services/GBCoreService.ts +++ b/packages/core.gbapp/services/GBCoreService.ts @@ -36,14 +36,23 @@ 'use strict'; -import { IGBCoreService, IGBInstance } from 'botlib'; +import { IGBCoreService, IGBInstance, IGBPackage } from 'botlib'; import * as fs from 'fs'; -import processExists = require('process-exists'); import { Sequelize } from 'sequelize-typescript'; -const logger = require('../../../src/logger'); import { GBAdminService } from '../../admin.gbapp/services/GBAdminService'; import { GuaribasInstance } from '../models/GBModel'; import { GBConfigService } from './GBConfigService'; +import { AzureDeployerService } from 'packages/azuredeployer.gbapp/services/AzureDeployerService'; +import { GBAnalyticsPackage } from 'packages/analytics.gblib'; +import { GBAdminPackage } from 'packages/admin.gbapp/index'; +import { GBCorePackage } from 'packages/core.gbapp'; +import { GBCustomerSatisfactionPackage } from 'packages/customer-satisfaction.gbapp'; +import { GBKBPackage } from 'packages/kb.gbapp'; +import { GBSecurityPackage } from 'packages/security.gblib'; +import { GBWhatsappPackage } from 'packages/whatsapp.gblib/index'; + +const logger = require('../../../src/logger'); +const opn = require('opn'); /** * Core service layer. @@ -70,7 +79,7 @@ export class GBCoreService implements IGBCoreService { private createTableQuery: ( tableName: string, attributes: any, - options: any + options: any, ) => string; /** @@ -136,15 +145,15 @@ export class GBCoreService implements IGBCoreService { dialect: this.dialect, storage: storage, dialectOptions: { - encrypt: encrypt + encrypt: encrypt, }, pool: { max: 32, min: 8, idle: 40000, evict: 40000, - acquire: 40000 - } + acquire: 40000, + }, }); if (this.dialect === 'mssql') { @@ -153,7 +162,7 @@ export class GBCoreService implements IGBCoreService { this.queryGenerator.createTableQuery = ( tableName, attributes, - options + options, ) => this.createTableQueryOverride(tableName, attributes, options); this.changeColumnQuery = this.queryGenerator.changeColumnQuery; this.queryGenerator.changeColumnQuery = (tableName, attributes) => @@ -163,7 +172,7 @@ export class GBCoreService implements IGBCoreService { } catch (error) { reject(error); } - } + }, ); } @@ -174,7 +183,7 @@ export class GBCoreService implements IGBCoreService { logger.info('Syncing database...'); return this.sequelize.sync({ alter: alter, - force: force + force: force, }); } else { const msg = 'Database synchronization is disabled.'; @@ -257,7 +266,7 @@ export class GBCoreService implements IGBCoreService { let sql: string = this.createTableQuery.apply(this.queryGenerator, [ tableName, attributes, - options + options, ]); const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/; const matches = re1.exec(sql); @@ -268,7 +277,7 @@ export class GBCoreService implements IGBCoreService { re2, (match: string, ...args: any[]): string => { return 'CONSTRAINT [' + table + '_pk] ' + match; - } + }, ); const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g; const re4 = /\[([^\]]*)\]/g; @@ -283,7 +292,7 @@ export class GBCoreService implements IGBCoreService { matches = re4.exec(fkcols); } return 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')'; - } + }, ); } return sql; @@ -300,7 +309,7 @@ export class GBCoreService implements IGBCoreService { private changeColumnQueryOverride(tableName, attributes): string { let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [ tableName, - attributes + attributes, ]); const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/; const matches = re1.exec(sql); @@ -326,9 +335,151 @@ export class GBCoreService implements IGBCoreService { fkcols + ')' ); - } + }, ); } return sql; } + + /** + * Loads all bot instances from object storage, if it's formatted. + * + * @param core + * @param azureDeployer + * @param proxyAddress + */ + public async loadAllInstances( + core: GBCoreService, + azureDeployer: AzureDeployerService, + proxyAddress: string, + ) { + logger.info(`Loading instances from storage...`); + let instances: GuaribasInstance[]; + try { + instances = await core.loadInstances(); + const instance = instances[0]; + if (process.env.NODE_ENV === 'development') { + logger.info(`Updating bot endpoint to local reverse proxy (ngrok)...`); + await azureDeployer.updateBotProxy( + instance.botId, + instance.botId, + `${proxyAddress}/api/messages/${instance.botId}`, + ); + } + } catch (error) { + if (error.parent.code === 'ELOGIN') { + const group = GBConfigService.get('CLOUD_GROUP'); + const serverName = GBConfigService.get('STORAGE_SERVER').split( + '.database.windows.net', + )[0]; + await azureDeployer.openStorageFirewall(group, serverName); + } else { + // Check if storage is empty and needs formatting. + const isInvalidObject = + error.parent.number == 208 || error.parent.errno == 1; // MSSQL or SQLITE. + if (isInvalidObject) { + if (GBConfigService.get('STORAGE_SYNC') != 'true') { + throw new Error( + `Operating storage is out of sync or there is a storage connection error. Try setting STORAGE_SYNC to true in .env file. Error: ${ + error.message + }.`, + ); + } else { + logger.info( + `Storage is empty. After collecting storage structure from all .gbapps it will get synced.`, + ); + } + } else { + throw new Error( + `Cannot connect to operating storage: ${error.message}.`, + ); + } + } + } + return instances; + } + + /** + * If instances is undefined here it's because storage has been formatted. + * Load all instances from .gbot found on deploy package directory. + * @param instances + * @param bootInstance + * @param core + */ + public async ensureInstances( + instances: GuaribasInstance[], + bootInstance: any, + core: GBCoreService, + ) { + if (!instances) { + const saveInstance = new GuaribasInstance(bootInstance); + await saveInstance.save(); + instances = await core.loadInstances(); + } + return instances; + } + + public loadSysPackages(core: GBCoreService) { + // NOTE: if there is any code before this line a semicolon + // will be necessary before this line. + // Loads all system packages. + + [ + GBAdminPackage, + GBAnalyticsPackage, + GBCorePackage, + GBSecurityPackage, + GBKBPackage, + GBCustomerSatisfactionPackage, + GBWhatsappPackage, + ].forEach(e => { + logger.info(`Loading sys package: ${e.name}...`); + const p = Object.create(e.prototype) as IGBPackage; + p.loadPackage(core, core.sequelize); + }); + } + + public ensureAdminIsSecured() { + const password = GBConfigService.get('ADMIN_PASS'); + if (!GBAdminService.StrongRegex.test(password)) { + throw new Error( + 'Please, define a really strong password in ADMIN_PASS environment variable before running the server.', + ); + } + } + + public async createBootInstance( + core: GBCoreService, + azureDeployer: AzureDeployerService, + proxyAddress: string, + ) { + let bootInstance: IGBInstance; + try { + await core.initDatabase(); + } catch (error) { + logger.info( + `Deploying cognitive infrastructure (on the cloud / on premises)...`, + ); + try { + bootInstance = await azureDeployer.deployFarm(proxyAddress); + } catch (error) { + logger.warn( + 'In case of error, please cleanup any infrastructure objects created during this procedure and .env before running again.', + ); + throw error; + } + core.writeEnv(bootInstance); + logger.info(`File .env written, starting General Bots...`); + GBConfigService.init(); + await core.initDatabase(); + } + return bootInstance; + } + + public openBrowserInDevelopment() { + if (process.env.NODE_ENV === 'development') { + opn('http://localhost:4242'); + } + } + } diff --git a/packages/core.gbapp/services/GBDeployer.ts b/packages/core.gbapp/services/GBDeployer.ts index 35581bae..3eca1b85 100644 --- a/packages/core.gbapp/services/GBDeployer.ts +++ b/packages/core.gbapp/services/GBDeployer.ts @@ -52,6 +52,7 @@ import { GuaribasInstance, GuaribasPackage } from '../models/GBModel'; import { KBService } from './../../kb.gbapp/services/KBService'; import { GBConfigService } from './GBConfigService'; import { GBImporter } from './GBImporterService'; +import { GBVMService } from './GBVMService'; /** Deployer service for bots, themes, ai and more. */ export class GBDeployer { @@ -266,6 +267,10 @@ export class GBDeployer { }); } + public deployScriptToStorage(instanceId: number, localPath: string) { + + } + public deployTheme(localPath: string) { // DISABLED: Until completed, "/ui/public". // FsExtra.copy(localPath, this.workDir + packageName) @@ -297,14 +302,12 @@ export class GBDeployer { break; case '.gbdialog': - const vm = new VMService(this.core.sequelize); + const vm = new GBVMService(); return service.deployKb(this.core, this, localPath); - break; - default: const err = GBError.create( - `GuaribasBusinessError: Unknow package type: ${packageType}.`, + `GuaribasBusinessError: Unknown package type: ${packageType}.` ); Promise.reject(err); break; diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts index 6eaa4a83..42da3fc2 100644 --- a/packages/core.gbapp/services/GBVMService.ts +++ b/packages/core.gbapp/services/GBVMService.ts @@ -36,13 +36,17 @@ import { IGBCoreService, IGBInstance } from 'botlib'; import { GBError } from 'botlib'; import { IGBPackage } from 'botlib'; const logger = require('../../../src/logger'); -import * as fs from 'fs'; import { BotAdapter } from 'botbuilder'; import { WaterfallDialog } from 'botbuilder-dialogs'; +import * as fs from 'fs'; import { Messages } from '../strings'; +import { DialogClass } from './GBAPIService'; import { GBDeployer } from './GBDeployer'; const util = require('util'); const vm = require('vm'); +import processExists = require('process-exists'); +import { Sequelize } from 'sequelize-typescript'; +const UrlJoin = require('url-join'); /** * @fileoverview General Bots server core. @@ -50,50 +54,27 @@ const vm = require('vm'); export class GBVMService implements IGBCoreService { - public static setup(bot: BotAdapter, min: IGBInstance) { + private script = new vm.Script(); - } - - public loadJS( + public async loadJS( filename: string, min: IGBInstance, core: IGBCoreService, deployer: GBDeployer, localPath: string - ) { + ): Promise { - const sandbox = { - animal: 'cat', - count: 2, - }; + const code = fs.readFileSync(UrlJoin(localPath, filename), 'utf8'); + const sandbox = new DialogClass(min); - const script = new vm.Script('count += 1; name = "kitty";'); const context = vm.createContext(sandbox); - - for (let i = 0; i < 10; ++i) { - script.runInContext(context); - } - + this.script.runInContext(context); console.log(util.inspect(sandbox)); - // { animal: 'cat', count: 12, name: 'kitty' } - - const packageType = Path.extname(localPath); - const packageName = Path.basename(localPath); - logger.info(`[GBDeployer] Opening package: ${localPath}`); - const packageObject = JSON.parse( - Fs.readFileSync(UrlJoin(localPath, 'package.json'), 'utf8'), + await deployer.deployScriptToStorage( + min.instanceId, + filename ); - - const instance = await core.loadInstance(packageObject.botId); - logger.info(`[GBDeployer] Importing: ${localPath}`); - const p = await deployer.deployPackageToStorage( - instance.instanceId, - packageName, - ); - await this.importKbPackage(localPath, p, instance); - - deployer.rebuildIndex(instance); - logger.info(`[GBDeployer] Finished import of ${localPath}`); + logger.info(`[GBVMService] Finished loading of ${filename}`); } } diff --git a/packages/core.gbapp/tests/vm.test.ts b/packages/core.gbapp/tests/vm.test.ts index 4102b513..acade433 100644 --- a/packages/core.gbapp/tests/vm.test.ts +++ b/packages/core.gbapp/tests/vm.test.ts @@ -43,7 +43,7 @@ describe('Load function', () => { it('should fail on invalid file', () => { try { const service = new GBVMService(); - service.loadJS('invalid.file'); + service.loadJS('invalid.file', null, null, null, null); } catch (error) { expect(error).to.equal(0); } diff --git a/src/app.ts b/src/app.ts index 6014b637..c0d174e0 100644 --- a/src/app.ts +++ b/src/app.ts @@ -40,14 +40,9 @@ const logger = require('./logger'); const express = require('express'); const bodyParser = require('body-parser'); -const opn = require('opn'); - import { IGBInstance, IGBPackage } from 'botlib'; -import { GBAdminPackage } from '../packages/admin.gbapp/index'; import { GBAdminService } from '../packages/admin.gbapp/services/GBAdminService'; -import { GBAnalyticsPackage } from '../packages/analytics.gblib'; import { AzureDeployerService } from '../packages/azuredeployer.gbapp/services/AzureDeployerService'; -import { GBCorePackage } from '../packages/core.gbapp'; import { GuaribasInstance } from '../packages/core.gbapp/models/GBModel'; import { GBConfigService } from '../packages/core.gbapp/services/GBConfigService'; import { GBConversationalService } from '../packages/core.gbapp/services/GBConversationalService'; @@ -55,10 +50,6 @@ import { GBCoreService } from '../packages/core.gbapp/services/GBCoreService'; import { GBDeployer } from '../packages/core.gbapp/services/GBDeployer'; import { GBImporter } from '../packages/core.gbapp/services/GBImporterService'; import { GBMinService } from '../packages/core.gbapp/services/GBMinService'; -import { GBCustomerSatisfactionPackage } from '../packages/customer-satisfaction.gbapp'; -import { GBKBPackage } from '../packages/kb.gbapp'; -import { GBSecurityPackage } from '../packages/security.gblib'; -import { GBWhatsappPackage } from './../packages/whatsapp.gblib/index'; const appPackages = new Array(); @@ -66,13 +57,11 @@ const appPackages = new Array(); * General Bots open-core entry point. */ export class GBServer { - /** * Program entry-point. */ public static run() { - logger.info(`The Bot Server is in STARTING mode...`); // Creates a basic HTTP server that will serve several URL, one for each @@ -86,8 +75,8 @@ export class GBServer { server.use( bodyParser.urlencoded({ // to support URL-encoded bodies - extended: true - }) + extended: true, + }), ); let bootInstance: IGBInstance; @@ -106,133 +95,42 @@ export class GBServer { logger.info(`Establishing a development local proxy (ngrok)...`); const proxyAddress = await core.ensureProxy(port); + logger.info(`Deploying packages...`); const deployer = new GBDeployer(core, new GBImporter(core)); const azureDeployer = new AzureDeployerService(deployer); - - try { - await core.initDatabase(); - } catch (error) { - logger.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`); - try { - bootInstance = await azureDeployer.deployFarm(proxyAddress); - } catch (error) { - logger.warn( - 'In case of error, please cleanup any infrastructure objects created during this procedure and .env before running again.' - ); - throw error; - } - core.writeEnv(bootInstance); - logger.info(`File .env written, starting General Bots...`); - GBConfigService.init(); - - await core.initDatabase(); - } - - // TODO: Get .gb* templates from GitHub and download do additional deploy folder. - - // Check admin password. - - const conversationalService = new GBConversationalService(core); const adminService = new GBAdminService(core); - const password = GBConfigService.get('ADMIN_PASS'); - - if (!GBAdminService.StrongRegex.test(password)) { - throw new Error( - 'Please, define a really strong password in ADMIN_PASS environment variable before running the server.' - ); - } - - // NOTE: the semicolon is necessary before this line. - // Loads all system packages. - - [ - GBAdminPackage, - GBAnalyticsPackage, - GBCorePackage, - GBSecurityPackage, - GBKBPackage, - GBCustomerSatisfactionPackage, - GBWhatsappPackage - ].forEach(e => { - logger.info(`Loading sys package: ${e.name}...`); - const p = Object.create(e.prototype) as IGBPackage; - p.loadPackage(core, core.sequelize); - }); - - // Loads all bot instances from object storage, if it's formatted. - - logger.info(`Loading instances from storage...`); - let instances: GuaribasInstance[]; - try { - instances = await core.loadInstances(); - const instance = instances[0]; - - if (process.env.NODE_ENV === 'development') { - logger.info(`Updating bot endpoint to local reverse proxy (ngrok)...`); - - await azureDeployer.updateBotProxy( - instance.botId, - instance.botId, - `${proxyAddress}/api/messages/${instance.botId}` - ); - } - } catch (error) { - if (error.parent.code === 'ELOGIN') { - const group = GBConfigService.get('CLOUD_GROUP'); - const serverName = GBConfigService.get('STORAGE_SERVER').split( - '.database.windows.net' - )[0]; - await azureDeployer.openStorageFirewall(group, serverName); - } else { - // Check if storage is empty and needs formatting. - - const isInvalidObject = - error.parent.number == 208 || error.parent.errno == 1; // MSSQL or SQLITE. - - if (isInvalidObject) { - if (GBConfigService.get('STORAGE_SYNC') != 'true') { - throw new Error(`Operating storage is out of sync or there is a storage connection error. Try setting STORAGE_SYNC to true in .env file. Error: ${ - error.message - }.`); - } else { - logger.info( - `Storage is empty. After collecting storage structure from all .gbapps it will get synced.` - ); - } - } else { - throw new Error(`Cannot connect to operating storage: ${error.message}.`); - } - } - } - - // Deploy packages and format object store according to .gbapp storage models. - - logger.info(`Deploying packages...`); + const conversationalService = new GBConversationalService(core); + bootInstance = await core.createBootInstance( + core, + azureDeployer, + proxyAddress, + ); + core.ensureAdminIsSecured(); + core.loadSysPackages(core); await deployer.deployPackages(core, server, appPackages); - // If instances is undefined here it's because storage has been formatted. - // Load all instances from .gbot found on deploy package directory. - if (!instances) { - const saveInstance = new GuaribasInstance(bootInstance); - await saveInstance.save(); - instances = await core.loadInstances(); - } - - // Setup server dynamic (per bot instance) resources and listeners. - logger.info(`Publishing instances...`); + let instances: GuaribasInstance[] = await core.loadAllInstances( + core, + azureDeployer, + proxyAddress, + ); + instances = await core.ensureInstances( + instances, + bootInstance, + core + ); + const minService = new GBMinService( core, conversationalService, adminService, - deployer + deployer, ); await minService.buildMin(server, appPackages, instances); - logger.info(`The Bot Server is in RUNNING mode...`); - if (process.env.NODE_ENV === 'development') { - opn('http://localhost:4242'); - } + logger.info(`The Bot Server is in RUNNING mode...`); + core.openBrowserInDevelopment(); return core; } catch (err) { @@ -243,6 +141,7 @@ export class GBServer { }); } } + // First line to run. -- 2.39.5 From 1761e06061b18a0dfda6cf57de8a38ece0445ef0 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (pragmatismo.io)" Date: Tue, 27 Nov 2018 22:56:11 -0200 Subject: [PATCH 03/13] fix(core): Bot boot logic being fixed. --- .prettierrc | 5 +- package-lock.json | 34 +- package.json | 1 + packages/admin.gbapp/dialogs/AdminDialog.ts | 10 +- packages/boot.gbot/package.json | 12 + packages/boot.gbot/security.json | 3 + packages/boot.gbot/services.json | 2 + packages/boot.gbot/settings.json | 6 + packages/core.gbapp/services/GBAPIService.ts | 8 +- .../services/GBConversationalService.ts | 75 ++- packages/core.gbapp/services/GBCoreService.ts | 524 +++++++++--------- packages/core.gbapp/services/GBDeployer.ts | 68 +-- .../core.gbapp/services/GBImporterService.ts | 7 +- packages/core.gbapp/services/GBMinService.ts | 74 +-- packages/core.gbapp/services/GBVMService.ts | 16 +- .../{chat.dialog.vbs => bot.vbs} | 2 +- .../{chat.dialog.js => bot.vbs.js} | 0 packages/kb.gbapp/dialogs/AskDialog.ts | 16 +- packages/kb.gbapp/dialogs/MenuDialog.ts | 2 +- packages/kb.gbapp/services/KBService.ts | 29 +- src/app.ts | 52 +- src/logger.ts | 4 + tslint.json | 4 +- 23 files changed, 461 insertions(+), 493 deletions(-) create mode 100644 packages/boot.gbot/package.json create mode 100644 packages/boot.gbot/security.json create mode 100644 packages/boot.gbot/services.json create mode 100644 packages/boot.gbot/settings.json rename packages/default.gbdialog/{chat.dialog.vbs => bot.vbs} (97%) rename packages/default.gbdialog/{chat.dialog.js => bot.vbs.js} (100%) diff --git a/.prettierrc b/.prettierrc index 847c9dba..af8db0fa 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,8 +1,9 @@ { - "trailingComma": "all", + "trailingComma": "none", "tabWidth": 2, - "printWidth": 80, + "printWidth": 120, "arrowParens": "avoid", "semi": true, "singleQuote": true + } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2beefb6b..17f168a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2966,9 +2966,9 @@ } }, "bluebird": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", - "integrity": "sha1-G+CQjgVKdRdUVJwnBInBUF1KsVo=" + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" }, "body-parser": { "version": "1.18.3", @@ -7105,13 +7105,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7124,18 +7122,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -7238,8 +7233,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -7249,7 +7243,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7262,20 +7255,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.2.4", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -7292,7 +7282,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -7365,8 +7354,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -7376,7 +7364,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -7482,7 +7469,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/package.json b/package.json index 93cfb7ae..30542ebc 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "azure-arm-search": "^1.3.0-preview", "azure-arm-sql": "5.6.0", "azure-arm-website": "5.7.0", + "bluebird": "^3.5.3", "body-parser": "1.18.3", "botbuilder": "^4.1.5", "botbuilder-ai": "^4.1.5", diff --git a/packages/admin.gbapp/dialogs/AdminDialog.ts b/packages/admin.gbapp/dialogs/AdminDialog.ts index 2d0b220c..a12f1910 100644 --- a/packages/admin.gbapp/dialogs/AdminDialog.ts +++ b/packages/admin.gbapp/dialogs/AdminDialog.ts @@ -63,11 +63,11 @@ export class AdminDialog extends IGBDialog { ); } - public static async deployPackageCommand(text: string, deployer: GBDeployer) { + public static async deployPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) { const packageName = text.split(' ')[1]; const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH'); - await deployer.deployPackageFromLocalPath( - UrlJoin(additionalPath, packageName) + await deployer.deployPackageFromLocalPath(min, + UrlJoin(additionalPath, packageName) ); } /** @@ -119,11 +119,11 @@ export class AdminDialog extends IGBDialog { await AdminDialog.createFarmCommand(text, deployer); await step.replaceDialog('/admin', { firstRun: false }); } else if (cmdName === 'deployPackage') { - await AdminDialog.deployPackageCommand(text, deployer); + await AdminDialog.deployPackageCommand(min, text, deployer); await step.replaceDialog('/admin', { firstRun: false }); } else if (cmdName === 'redeployPackage') { await AdminDialog.undeployPackageCommand(text, min); - await AdminDialog.deployPackageCommand(text, deployer); + await AdminDialog.deployPackageCommand(min, text, deployer); await step.replaceDialog('/admin', { firstRun: false }); } else if (cmdName === 'undeployPackage') { await AdminDialog.undeployPackageCommand(text, min); diff --git a/packages/boot.gbot/package.json b/packages/boot.gbot/package.json new file mode 100644 index 00000000..ef064f8a --- /dev/null +++ b/packages/boot.gbot/package.json @@ -0,0 +1,12 @@ +{ + "version": "1.0.0", + "theme": "default.gbtheme", + "ui": "default.gbui", + "kb": "default.gbkb", + "title": "Default General Bot", + "description": "Default General Bot", + "whoAmIVideo": "TODO.mp4", + "author": "pragmatismo.io", + "license": "AGPL", + "engineName": "guaribas-1.0.0" +} \ No newline at end of file diff --git a/packages/boot.gbot/security.json b/packages/boot.gbot/security.json new file mode 100644 index 00000000..41ce2ba0 --- /dev/null +++ b/packages/boot.gbot/security.json @@ -0,0 +1,3 @@ +{ + "groups": [{}] +} diff --git a/packages/boot.gbot/services.json b/packages/boot.gbot/services.json new file mode 100644 index 00000000..2c63c085 --- /dev/null +++ b/packages/boot.gbot/services.json @@ -0,0 +1,2 @@ +{ +} diff --git a/packages/boot.gbot/settings.json b/packages/boot.gbot/settings.json new file mode 100644 index 00000000..493a65bf --- /dev/null +++ b/packages/boot.gbot/settings.json @@ -0,0 +1,6 @@ +{ + "enabledAdmin": "true", + "searchScore": ".15", + "nlpScore": ".15", + "nlpVsSearch": ".4" +} diff --git a/packages/core.gbapp/services/GBAPIService.ts b/packages/core.gbapp/services/GBAPIService.ts index e79025c7..70acdf7d 100644 --- a/packages/core.gbapp/services/GBAPIService.ts +++ b/packages/core.gbapp/services/GBAPIService.ts @@ -46,7 +46,7 @@ export class DialogClass { this.min = min; } - public async expectMessage(text: string): Promise { + public async hear(text: string): Promise { return new Promise((resolve, reject) => { this.min.dialogs.add( new WaterfallDialog('/vmExpect', [ @@ -63,7 +63,11 @@ export class DialogClass { }); } - public sendMessage(text: string) { + public post(url: string, data) { + + } + + public talk(text: string) { this.min.dialogs.add( new WaterfallDialog('/vmSend', [ async step => { diff --git a/packages/core.gbapp/services/GBConversationalService.ts b/packages/core.gbapp/services/GBConversationalService.ts index 0eda2815..d062353a 100644 --- a/packages/core.gbapp/services/GBConversationalService.ts +++ b/packages/core.gbapp/services/GBConversationalService.ts @@ -31,18 +31,15 @@ \*****************************************************************************/ /** - * @fileoverview General Bots server core. + * @fileoverview Conversation handling and external service calls. */ 'use strict'; const logger = require('../../../src/logger'); - -import { any } from 'bluebird'; import { MessageFactory } from 'botbuilder'; import { LuisRecognizer } from 'botbuilder-ai'; -import { IGBConversationalService } from 'botlib'; -import { GBMinInstance } from 'botlib'; +import { GBMinInstance, IGBConversationalService } from 'botlib'; import { AzureText } from 'pragmatismo-io-framework'; import { Messages } from '../strings'; import { GBCoreService } from './GBCoreService'; @@ -70,33 +67,27 @@ export class GBConversationalService implements IGBConversationalService { msg.value = value; msg.type = 'event'; msg.name = name; + return step.context.sendActivity(msg); } } - public async sendSms( - min: GBMinInstance, - mobile: string, - text: string - ): Promise { - return new Promise((resolve: any, reject: any): any => { - const nexmo = new Nexmo({ - apiKey: min.instance.smsKey, - apiSecret: min.instance.smsSecret - }); - nexmo.message.sendSms( - min.instance.smsServiceNumber, - mobile, - text, - (err, data) => { + public async sendSms(min: GBMinInstance, mobile: string, text: string): Promise { + return new Promise( + (resolve: any, reject: any): any => { + const nexmo = new Nexmo({ + apiKey: min.instance.smsKey, + apiSecret: min.instance.smsSecret + }); + nexmo.message.sendSms(min.instance.smsServiceNumber, mobile, text, (err, data) => { if (err) { reject(err); } else { resolve(data); } - } - ); - }); + }); + } + ); } public async routeNLP(step: any, min: GBMinInstance, text: string): Promise { @@ -112,15 +103,17 @@ export class GBConversationalService implements IGBConversationalService { try { nlp = await model.recognize(step.context); } catch (error) { - if (error.statusCode == 404) { - logger.warn ('NLP application still not publish and there are no other options for answering.'); + if (error.statusCode === 404) { + logger.warn('NLP application still not publish and there are no other options for answering.'); + return Promise.resolve(false); } else { - const msg = `Error calling NLP server, check if you have a published model and assigned keys on the service. Error: ${ - error.statusCode ? error.statusCode : '' - } ${error.message}`; - return Promise.reject(new Error(msg)); } + const msg = `Error calling NLP, check if you have a published model and assigned keys. Error: ${ + error.statusCode ? error.statusCode : '' + } ${error.message}`; + return Promise.reject(new Error(msg)); + } } // Resolves intents returned from LUIS. @@ -128,37 +121,31 @@ export class GBConversationalService implements IGBConversationalService { const topIntent = LuisRecognizer.topIntent(nlp); if (topIntent) { const intent = topIntent; - const entity = - nlp.entities && nlp.entities.length > 0 - ? nlp.entities[0].entity.toUpperCase() - : null; + const entity = nlp.entities && nlp.entities.length > 0 ? nlp.entities[0].entity.toUpperCase() : null; if (intent === 'None') { return Promise.resolve(false); } - logger.info('NLP called:' + intent + ', ' + entity); + logger.info(`NLP called: ${intent}, ${entity}`); try { - await step.replace('/' + intent, nlp.entities); + await step.replace(`/${intent}`, nlp.entities); + return Promise.resolve(true); } catch (error) { - const msg = `Error finding dialog associated to NLP event: ${intent}: ${ - error.message - }`; + const msg = `Error finding dialog associated to NLP event: ${intent}: ${error.message}`; + return Promise.reject(new Error(msg)); } } + return Promise.resolve(false); } public async checkLanguage(step, min, text) { - const locale = await AzureText.getLocale( - min.instance.textAnalyticsKey, - min.instance.textAnalyticsEndpoint, - text - ); - if (locale != step.context.activity.locale.split('-')[0]) { + const locale = await AzureText.getLocale(min.instance.textAnalyticsKey, min.instance.textAnalyticsEndpoint, text); + if (locale !== step.context.activity.locale.split('-')[0]) { switch (locale) { case 'pt': step.context.activity.locale = 'pt-BR'; diff --git a/packages/core.gbapp/services/GBCoreService.ts b/packages/core.gbapp/services/GBCoreService.ts index 88f6690f..a7f6256d 100644 --- a/packages/core.gbapp/services/GBCoreService.ts +++ b/packages/core.gbapp/services/GBCoreService.ts @@ -39,17 +39,18 @@ import { IGBCoreService, IGBInstance, IGBPackage } from 'botlib'; import * as fs from 'fs'; import { Sequelize } from 'sequelize-typescript'; +import { GBAdminPackage } from '../../admin.gbapp/index'; import { GBAdminService } from '../../admin.gbapp/services/GBAdminService'; +import { GBAnalyticsPackage } from '../../analytics.gblib'; +import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService'; +import { GBCorePackage } from '../../core.gbapp'; +import { GBCustomerSatisfactionPackage } from '../../customer-satisfaction.gbapp'; +import { GBKBPackage } from '../../kb.gbapp'; +import { GBSecurityPackage } from '../../security.gblib'; +import { GBWhatsappPackage } from '../../whatsapp.gblib/index'; import { GuaribasInstance } from '../models/GBModel'; import { GBConfigService } from './GBConfigService'; -import { AzureDeployerService } from 'packages/azuredeployer.gbapp/services/AzureDeployerService'; -import { GBAnalyticsPackage } from 'packages/analytics.gblib'; -import { GBAdminPackage } from 'packages/admin.gbapp/index'; -import { GBCorePackage } from 'packages/core.gbapp'; -import { GBCustomerSatisfactionPackage } from 'packages/customer-satisfaction.gbapp'; -import { GBKBPackage } from 'packages/kb.gbapp'; -import { GBSecurityPackage } from 'packages/security.gblib'; -import { GBWhatsappPackage } from 'packages/whatsapp.gblib/index'; +import { GBImporter } from './GBImporterService'; const logger = require('../../../src/logger'); const opn = require('opn'); @@ -76,11 +77,7 @@ export class GBCoreService implements IGBCoreService { /** * Custom create table query. */ - private createTableQuery: ( - tableName: string, - attributes: any, - options: any, - ) => string; + private createTableQuery: (tableName: string, attributes: any, options: any) => string; /** * Custom change column query. @@ -102,78 +99,79 @@ export class GBCoreService implements IGBCoreService { /** * Gets database config and connect to storage. */ - public async initDatabase(): Promise { - return new Promise( - (resolve: any, reject: any): any => { - try { - this.dialect = GBConfigService.get('STORAGE_DIALECT'); - let host: string | undefined; - let database: string | undefined; - let username: string | undefined; - let password: string | undefined; - let storage: string | undefined; + public async initStorage(): Promise { + this.dialect = GBConfigService.get('STORAGE_DIALECT'); - if (this.dialect === 'mssql') { - host = GBConfigService.get('STORAGE_SERVER'); - database = GBConfigService.get('STORAGE_NAME'); - username = GBConfigService.get('STORAGE_USERNAME'); - password = GBConfigService.get('STORAGE_PASSWORD'); - } else if (this.dialect === 'sqlite') { - storage = GBConfigService.get('STORAGE_STORAGE'); - } else { - reject(`Unknown dialect: ${this.dialect}.`); + let host: string | undefined; + let database: string | undefined; + let username: string | undefined; + let password: string | undefined; + let storage: string | undefined; + + if (this.dialect === 'mssql') { + host = GBConfigService.get('STORAGE_SERVER'); + database = GBConfigService.get('STORAGE_NAME'); + username = GBConfigService.get('STORAGE_USERNAME'); + password = GBConfigService.get('STORAGE_PASSWORD'); + } else if (this.dialect === 'sqlite') { + storage = GBConfigService.get('STORAGE_STORAGE'); + } else { + throw new Error(`Unknown dialect: ${this.dialect}.`); + } + + const logging: any = + GBConfigService.get('STORAGE_LOGGING') === 'true' + ? (str: string): void => { + logger.info(str); } + : false; - const logging: any = - GBConfigService.get('STORAGE_LOGGING') === 'true' - ? (str: string): void => { - logger.info(str); - } - : false; + const encrypt: boolean = GBConfigService.get('STORAGE_ENCRYPT') === 'true'; - const encrypt: boolean = - GBConfigService.get('STORAGE_ENCRYPT') === 'true'; - - this.sequelize = new Sequelize({ - host: host, - database: database, - username: username, - password: password, - logging: logging, - operatorsAliases: false, - dialect: this.dialect, - storage: storage, - dialectOptions: { - encrypt: encrypt, - }, - pool: { - max: 32, - min: 8, - idle: 40000, - evict: 40000, - acquire: 40000, - }, - }); - - if (this.dialect === 'mssql') { - this.queryGenerator = this.sequelize.getQueryInterface().QueryGenerator; - this.createTableQuery = this.queryGenerator.createTableQuery; - this.queryGenerator.createTableQuery = ( - tableName, - attributes, - options, - ) => this.createTableQueryOverride(tableName, attributes, options); - this.changeColumnQuery = this.queryGenerator.changeColumnQuery; - this.queryGenerator.changeColumnQuery = (tableName, attributes) => - this.changeColumnQueryOverride(tableName, attributes); - } - resolve(); - } catch (error) { - reject(error); - } + this.sequelize = new Sequelize({ + host: host, + database: database, + username: username, + password: password, + logging: logging, + operatorsAliases: false, + dialect: this.dialect, + storage: storage, + dialectOptions: { + encrypt: encrypt }, - ); + pool: { + max: 32, + min: 8, + idle: 40000, + evict: 40000, + acquire: 40000 + } + }); + + if (this.dialect === 'mssql') { + this.queryGenerator = this.sequelize.getQueryInterface().QueryGenerator; + this.createTableQuery = this.queryGenerator.createTableQuery; + this.queryGenerator.createTableQuery = (tableName, attributes, options) => + this.createTableQueryOverride(tableName, attributes, options); + this.changeColumnQuery = this.queryGenerator.changeColumnQuery; + this.queryGenerator.changeColumnQuery = (tableName, attributes) => + this.changeColumnQueryOverride(tableName, attributes); + } + } + + public async checkStorage(azureDeployer: AzureDeployerService) { + try { + await this.sequelize.authenticate(); + } catch (error) { + logger.info('Opening storage firewall on infrastructure...'); + if (error.parent.code === 'ELOGIN') { + await this.openStorageFrontier(azureDeployer); + } else { + throw error; + } + } } public async syncDatabaseStructure() { @@ -181,9 +179,10 @@ export class GBCoreService implements IGBCoreService { const alter = GBConfigService.get('STORAGE_SYNC_ALTER') === 'true'; const force = GBConfigService.get('STORAGE_SYNC_FORCE') === 'true'; logger.info('Syncing database...'); + return this.sequelize.sync({ alter: alter, - force: force, + force: force }); } else { const msg = 'Database synchronization is disabled.'; @@ -203,6 +202,7 @@ export class GBCoreService implements IGBCoreService { */ public async loadInstanceById(instanceId: string): Promise { const options = { where: { instanceId: instanceId } }; + return GuaribasInstance.findOne(options); } @@ -211,63 +211,182 @@ export class GBCoreService implements IGBCoreService { */ public async loadInstance(botId: string): Promise { const options = { where: {} }; + options.where = { botId: botId }; - if (botId !== '[default]') { - options.where = { botId: botId }; - } - - return GuaribasInstance.findOne(options); + return await GuaribasInstance.findOne(options); } public async writeEnv(instance: IGBInstance) { - const env = - `ADDITIONAL_DEPLOY_PATH=\n` + - `ADMIN_PASS=${instance.adminPass}\n` + - `CLOUD_SUBSCRIPTIONID=${instance.cloudSubscriptionId}\n` + - `CLOUD_LOCATION=${instance.cloudLocation}\n` + - `CLOUD_GROUP=${instance.botId}\n` + - `CLOUD_USERNAME=${instance.cloudUsername}\n` + - `CLOUD_PASSWORD=${instance.cloudPassword}\n` + - `MARKETPLACE_ID=${instance.marketplaceId}\n` + - `MARKETPLACE_SECRET=${instance.marketplacePassword}\n` + - `NLP_AUTHORING_KEY=${instance.nlpAuthoringKey}\n` + - `STORAGE_DIALECT=${instance.storageDialect}\n` + - `STORAGE_SERVER=${instance.storageServer}.database.windows.net\n` + - `STORAGE_NAME=${instance.storageName}\n` + - `STORAGE_USERNAME=${instance.storageUsername}\n` + - `STORAGE_PASSWORD=${instance.storagePassword}\n` + - `STORAGE_SYNC=true\n`; + const env = `ADDITIONAL_DEPLOY_PATH= + ADMIN_PASS=${instance.adminPass} + CLOUD_SUBSCRIPTIONID=${instance.cloudSubscriptionId} + CLOUD_LOCATION=${instance.cloudLocation} + CLOUD_GROUP=${instance.botId} + CLOUD_USERNAME=${instance.cloudUsername} + CLOUD_PASSWORD=${instance.cloudPassword} + MARKETPLACE_ID=${instance.marketplaceId} + MARKETPLACE_SECRET=${instance.marketplacePassword} + NLP_AUTHORING_KEY=${instance.nlpAuthoringKey} + STORAGE_DIALECT=${instance.storageDialect} + STORAGE_SERVER=${instance.storageServer}.database.windows.net + STORAGE_NAME=${instance.storageName} + STORAGE_USERNAME=${instance.storageUsername} + STORAGE_PASSWORD=${instance.storagePassword} + STORAGE_SYNC=true`; fs.writeFileSync('.env', env); } public async ensureProxy(port): Promise { - let proxyAddress: string; const ngrok = require('ngrok'); + return await ngrok.connect({ port: port }); } + public async saveInstance(fullInstance: any) { + const options = { where: {} }; + options.where = { botId: fullInstance.botId }; + let instance = await GuaribasInstance.findOne(options); + // tslint:disable-next-line:prefer-object-spread + instance = Object.assign(instance, fullInstance); + + return await instance.save(); + } + + /** + * Loads all bot instances from object storage, if it's formatted. + * + * @param core + * @param azureDeployer + * @param proxyAddress + */ + public async loadAllInstances(core: GBCoreService, azureDeployer: AzureDeployerService, proxyAddress: string) { + logger.info(`Loading instances from storage...`); + let instances: GuaribasInstance[]; + try { + instances = await core.loadInstances(); + const instance = instances[0]; + if (process.env.NODE_ENV === 'development') { + logger.info(`Updating bot endpoint to local reverse proxy (ngrok)...`); + await azureDeployer.updateBotProxy( + instance.botId, + instance.botId, + `${proxyAddress}/api/messages/${instance.botId}` + ); + } + } catch (error) { + // Check if storage is empty and needs formatting. + const isInvalidObject = error.parent.number == 208 || error.parent.errno == 1; // MSSQL or SQLITE. + if (isInvalidObject) { + if (GBConfigService.get('STORAGE_SYNC') != 'true') { + throw new Error( + `Operating storage is out of sync or there is a storage connection error. + Try setting STORAGE_SYNC to true in .env file. Error: ${error.message}.` + ); + } else { + logger.info(`Storage is empty. After collecting storage structure from all .gbapps it will get synced.`); + } + } else { + throw new Error(`Cannot connect to operating storage: ${error.message}.`); + } + } + + return instances; + } + + /** + * If instances is undefined here it's because storage has been formatted. + * Load all instances from .gbot found on deploy package directory. + * @param instances + * @param bootInstance + * @param core + */ + public async ensureInstances(instances: GuaribasInstance[], bootInstance: any, core: GBCoreService) { + if (!instances) { + const saveInstance = new GuaribasInstance(bootInstance); + await saveInstance.save(); + instances = await core.loadInstances(); + } + + return instances; + } + + public loadSysPackages(core: GBCoreService) { + // NOTE: if there is any code before this line a semicolon + // will be necessary before this line. + // Loads all system packages. + + [ + GBAdminPackage, + GBAnalyticsPackage, + GBCorePackage, + GBSecurityPackage, + GBKBPackage, + GBCustomerSatisfactionPackage, + GBWhatsappPackage + ].forEach(e => { + logger.info(`Loading sys package: ${e.name}...`); + const p = Object.create(e.prototype) as IGBPackage; + p.loadPackage(core, core.sequelize); + }); + } + + public ensureAdminIsSecured() { + const password = GBConfigService.get('ADMIN_PASS'); + if (!GBAdminService.StrongRegex.test(password)) { + throw new Error( + 'Please, define a really strong password in ADMIN_PASS environment variable before running the server.' + ); + } + } + + public async createBootInstance(core: GBCoreService, azureDeployer: AzureDeployerService, proxyAddress: string) { + let instance: IGBInstance; + try { + await core.initStorage(); + } catch (error) { + logger.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`); + try { + instance = await azureDeployer.deployFarm(proxyAddress); + } catch (error) { + logger.warn( + 'In case of error, please cleanup any infrastructure objects ' + + 'created during this procedure and .env before running again.' + ); + throw error; + } + core.writeEnv(instance); + logger.info(`File .env written, starting General Bots...`); + GBConfigService.init(); + await core.initStorage(); + + return instance; + } + } + + public openBrowserInDevelopment() { + if (process.env.NODE_ENV === 'development') { + opn('http://localhost:4242'); + } + } + /** * SQL: * * // let sql: string = '' + - * // 'IF OBJECT_ID(\'[UserGroup]\', \'U\') IS NULL\n' + - * // 'CREATE TABLE [UserGroup] (\n' + - * // ' [id] INTEGER NOT NULL IDENTITY(1,1),\n' + - * // ' [userId] INTEGER NULL,\n' + - * // ' [groupId] INTEGER NULL,\n' + - * // ' [instanceId] INTEGER NULL,\n' + - * // ' PRIMARY KEY ([id1], [id2]),\n' + - * // ' FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,\n' + - * // ' FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId1]) ON DELETE NO ACTION,\n' + + * // 'IF OBJECT_ID(\'[UserGroup]\', \'U\') IS NULL' + + * // 'CREATE TABLE [UserGroup] (' + + * // ' [id] INTEGER NOT NULL IDENTITY(1,1),' + + * // ' [userId] INTEGER NULL,' + + * // ' [groupId] INTEGER NULL,' + + * // ' [instanceId] INTEGER NULL,' + + * // ' PRIMARY KEY ([id1], [id2]),' + + * // ' FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,' + + * // ' FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId1]) ON DELETE NO ACTION,' + * // ' FOREIGN KEY ([instanceId]) REFERENCES [Instance] ([instanceId]) ON DELETE NO ACTION)' */ private createTableQueryOverride(tableName, attributes, options): string { - let sql: string = this.createTableQuery.apply(this.queryGenerator, [ - tableName, - attributes, - options, - ]); + let sql: string = this.createTableQuery.apply(this.queryGenerator, [tableName, attributes, options]); const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/; const matches = re1.exec(sql); if (matches) { @@ -277,7 +396,7 @@ export class GBCoreService implements IGBCoreService { re2, (match: string, ...args: any[]): string => { return 'CONSTRAINT [' + table + '_pk] ' + match; - }, + } ); const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g; const re4 = /\[([^\]]*)\]/g; @@ -292,7 +411,7 @@ export class GBCoreService implements IGBCoreService { matches = re4.exec(fkcols); } return 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')'; - }, + } ); } return sql; @@ -301,16 +420,13 @@ export class GBCoreService implements IGBCoreService { /** * SQL: * let sql = '' + - * 'ALTER TABLE [UserGroup]\n' + - * ' ADD CONSTRAINT [invalid1] FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,\n' + - * ' CONSTRAINT [invalid2] FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId2]) ON DELETE NO ACTION, \n' + - * ' CONSTRAINT [invalid3] FOREIGN KEY ([instanceId1]) REFERENCES [Instance] ([instanceId1]) ON DELETE NO ACTION\n' + * 'ALTER TABLE [UserGroup]' + + * ' ADD CONSTRAINT [invalid1] FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,' + + * ' CONSTRAINT [invalid2] FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId2]) ON DELETE NO ACTION, ' + + * ' CONSTRAINT [invalid3] FOREIGN KEY ([instanceId1]) REFERENCES [Instance] ([instanceId1]) ON DELETE NO ACTION' */ private changeColumnQueryOverride(tableName, attributes): string { - let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [ - tableName, - attributes, - ]); + let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [tableName, attributes]); const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/; const matches = re1.exec(sql); if (matches) { @@ -327,159 +443,21 @@ export class GBCoreService implements IGBCoreService { fkname += '_' + matches[1]; matches = re3.exec(fkcols); } - return ( - (args[0] ? args[0] : '') + - 'CONSTRAINT [' + - fkname + - '_fk] FOREIGN KEY (' + - fkcols + - ')' - ); - }, + return (args[0] ? args[0] : '') + 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')'; + } ); } return sql; } /** - * Loads all bot instances from object storage, if it's formatted. + * Opens storage firewall. * - * @param core - * @param azureDeployer - * @param proxyAddress + * @param azureDeployer Infrastructure Deployer instance. */ - public async loadAllInstances( - core: GBCoreService, - azureDeployer: AzureDeployerService, - proxyAddress: string, - ) { - logger.info(`Loading instances from storage...`); - let instances: GuaribasInstance[]; - try { - instances = await core.loadInstances(); - const instance = instances[0]; - if (process.env.NODE_ENV === 'development') { - logger.info(`Updating bot endpoint to local reverse proxy (ngrok)...`); - await azureDeployer.updateBotProxy( - instance.botId, - instance.botId, - `${proxyAddress}/api/messages/${instance.botId}`, - ); - } - } catch (error) { - if (error.parent.code === 'ELOGIN') { - const group = GBConfigService.get('CLOUD_GROUP'); - const serverName = GBConfigService.get('STORAGE_SERVER').split( - '.database.windows.net', - )[0]; - await azureDeployer.openStorageFirewall(group, serverName); - } else { - // Check if storage is empty and needs formatting. - const isInvalidObject = - error.parent.number == 208 || error.parent.errno == 1; // MSSQL or SQLITE. - if (isInvalidObject) { - if (GBConfigService.get('STORAGE_SYNC') != 'true') { - throw new Error( - `Operating storage is out of sync or there is a storage connection error. Try setting STORAGE_SYNC to true in .env file. Error: ${ - error.message - }.`, - ); - } else { - logger.info( - `Storage is empty. After collecting storage structure from all .gbapps it will get synced.`, - ); - } - } else { - throw new Error( - `Cannot connect to operating storage: ${error.message}.`, - ); - } - } - } - return instances; + private async openStorageFrontier(deployer: AzureDeployerService) { + const group = GBConfigService.get('CLOUD_GROUP'); + const serverName = GBConfigService.get('STORAGE_SERVER').split('.database.windows.net')[0]; + await deployer.openStorageFirewall(group, serverName); } - - /** - * If instances is undefined here it's because storage has been formatted. - * Load all instances from .gbot found on deploy package directory. - * @param instances - * @param bootInstance - * @param core - */ - public async ensureInstances( - instances: GuaribasInstance[], - bootInstance: any, - core: GBCoreService, - ) { - if (!instances) { - const saveInstance = new GuaribasInstance(bootInstance); - await saveInstance.save(); - instances = await core.loadInstances(); - } - return instances; - } - - public loadSysPackages(core: GBCoreService) { - // NOTE: if there is any code before this line a semicolon - // will be necessary before this line. - // Loads all system packages. - - [ - GBAdminPackage, - GBAnalyticsPackage, - GBCorePackage, - GBSecurityPackage, - GBKBPackage, - GBCustomerSatisfactionPackage, - GBWhatsappPackage, - ].forEach(e => { - logger.info(`Loading sys package: ${e.name}...`); - const p = Object.create(e.prototype) as IGBPackage; - p.loadPackage(core, core.sequelize); - }); - } - - public ensureAdminIsSecured() { - const password = GBConfigService.get('ADMIN_PASS'); - if (!GBAdminService.StrongRegex.test(password)) { - throw new Error( - 'Please, define a really strong password in ADMIN_PASS environment variable before running the server.', - ); - } - } - - public async createBootInstance( - core: GBCoreService, - azureDeployer: AzureDeployerService, - proxyAddress: string, - ) { - let bootInstance: IGBInstance; - try { - await core.initDatabase(); - } catch (error) { - logger.info( - `Deploying cognitive infrastructure (on the cloud / on premises)...`, - ); - try { - bootInstance = await azureDeployer.deployFarm(proxyAddress); - } catch (error) { - logger.warn( - 'In case of error, please cleanup any infrastructure objects created during this procedure and .env before running again.', - ); - throw error; - } - core.writeEnv(bootInstance); - logger.info(`File .env written, starting General Bots...`); - GBConfigService.init(); - await core.initDatabase(); - } - return bootInstance; - } - - public openBrowserInDevelopment() { - if (process.env.NODE_ENV === 'development') { - opn('http://localhost:4242'); - } - } - } diff --git a/packages/core.gbapp/services/GBDeployer.ts b/packages/core.gbapp/services/GBDeployer.ts index 3eca1b85..1d0b15de 100644 --- a/packages/core.gbapp/services/GBDeployer.ts +++ b/packages/core.gbapp/services/GBDeployer.ts @@ -84,7 +84,7 @@ export class GBDeployer { public deployPackages( core: IGBCoreService, server: any, - appPackages: IGBPackage[], + appPackages: IGBPackage[] ) { const _this = this; return new Promise( @@ -93,7 +93,6 @@ export class GBDeployer { const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH'); let paths = [GBDeployer.deployFolder]; if (additionalPath) { - paths = paths.concat(additionalPath.toLowerCase().split(';')); } const botPackages = new Array(); @@ -124,7 +123,7 @@ export class GBDeployer { } logger.info( - `Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...`, + `Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...` ); paths.forEach(e => { logger.info(`Looking in: ${e}...`); @@ -199,20 +198,22 @@ export class GBDeployer { server.use('/themes/' + filenameOnly, express.static(filename)); logger.info( `Theme (.gbtheme) assets accessible at: ${'/themes/' + - filenameOnly}.`, + filenameOnly}.` ); /** Knowledge base for bots. */ } else if (Path.extname(filename) === '.gbkb') { server.use( '/kb/' + filenameOnly + '/subjects', - express.static(UrlJoin(filename, 'subjects')), + express.static(UrlJoin(filename, 'subjects')) ); logger.info( - `KB (.gbkb) assets accessible at: ${'/kb/' + filenameOnly}.`, + `KB (.gbkb) assets accessible at: ${'/kb/' + filenameOnly}.` ); } else if (Path.extname(filename) === '.gbui') { // Already Handled + } else if (Path.extname(filename) === '.gbdialog') { + // Already Handled } else { /** Unknown package format. */ const err = new Error(`Package type not handled: ${filename}.`); @@ -231,7 +232,7 @@ export class GBDeployer { .done(function(result) { if (botPackages.length === 0) { logger.info( - 'No external packages to load, please use ADDITIONAL_DEPLOY_PATH to point to a .gbai package folder.', + 'No external packages to load, please use ADDITIONAL_DEPLOY_PATH to point to a .gbai package folder.' ); } else { logger.info(`Package deployment done.`); @@ -239,7 +240,7 @@ export class GBDeployer { resolve(); }); }); - }, + } ); } @@ -252,24 +253,22 @@ export class GBDeployer { const packageName = Path.basename(localPath); const instance = await this.importer.importIfNotExistsBotPackage( packageName, - localPath, + localPath ); return instance; } public async deployPackageToStorage( instanceId: number, - packageName: string, + packageName: string ): Promise { return GuaribasPackage.create({ packageName: packageName, - instanceId: instanceId, + instanceId: instanceId }); } - public deployScriptToStorage(instanceId: number, localPath: string) { - - } + public deployScriptToStorage(instanceId: number, localPath: string) {} public deployTheme(localPath: string) { // DISABLED: Until completed, "/ui/public". @@ -283,7 +282,7 @@ export class GBDeployer { // }) } - public async deployPackageFromLocalPath(localPath: string) { + public async deployPackageFromLocalPath(min: IGBInstance, localPath: string) { const packageType = Path.extname(localPath); switch (packageType) { @@ -303,7 +302,7 @@ export class GBDeployer { case '.gbdialog': const vm = new GBVMService(); - return service.deployKb(this.core, this, localPath); + return vm.loadJS(localPath, min, this.core, this, localPath); default: const err = GBError.create( @@ -316,7 +315,7 @@ export class GBDeployer { public async undeployPackageFromLocalPath( instance: IGBInstance, - localPath: string, + localPath: string ) { const packageType = Path.extname(localPath); const packageName = Path.basename(localPath); @@ -339,9 +338,12 @@ export class GBDeployer { case '.gbui': break; + case '.gbdialog': + break; + default: const err = GBError.create( - `GuaribasBusinessError: Unknown package type: ${packageType}.`, + `GuaribasBusinessError: Unknown package type: ${packageType}.` ); Promise.reject(err); break; @@ -353,11 +355,11 @@ export class GBDeployer { instance.searchKey, instance.searchHost, instance.searchIndex, - instance.searchIndexer, + instance.searchIndexer ); const connectionString = GBDeployer.getConnectionStringFromInstance( - instance, + instance ); const dsName = 'gb'; @@ -375,7 +377,7 @@ export class GBDeployer { dsName, 'GuaribasQuestion', 'azuresql', - connectionString, + connectionString ); try { @@ -388,35 +390,17 @@ export class GBDeployer { } await search.createIndex( AzureDeployerService.getKBSearchSchema(instance.searchIndex), - dsName, + dsName ); } public async getPackageByName( instanceId: number, - packageName: string, + packageName: string ): Promise { const where = { packageName: packageName, instanceId: instanceId }; return GuaribasPackage.findOne({ - where: where, + where: where }); } - - /** - * - * Hot deploy processing. - * - */ - public async scanBootPackage() { - const deployFolder = 'packages'; - const bootPackage = GBConfigService.get('BOOT_PACKAGE'); - - if (bootPackage === 'none') { - return Promise.resolve(true); - } else { - return this.deployPackageFromLocalPath( - UrlJoin(deployFolder, bootPackage), - ); - } - } } diff --git a/packages/core.gbapp/services/GBImporterService.ts b/packages/core.gbapp/services/GBImporterService.ts index 1ad9b0e6..04bf1208 100644 --- a/packages/core.gbapp/services/GBImporterService.ts +++ b/packages/core.gbapp/services/GBImporterService.ts @@ -57,14 +57,12 @@ export class GBImporter { const packageJson = JSON.parse( fs.readFileSync(UrlJoin(localPath, 'package.json'), 'utf8') ); - const botId = packageJson.botId; - const instance = await this.core.loadInstance(botId); if (instance) { - return Promise.resolve(instance); + return instance; } else { - return this.createInstanceInternal(packageName, localPath, packageJson); + return await this.createInstanceInternal(packageName, localPath, packageJson); } } @@ -83,7 +81,6 @@ export class GBImporter { packageJson = {...packageJson, ...settings, ...servicesJson}; GuaribasInstance.create(packageJson).then((instance: IGBInstance) => { - const service = new SecService(); // TODO: service.importSecurityFile(localPath, instance) diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index d99361af..fded27ab 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -88,7 +88,7 @@ export class GBMinService { core: IGBCoreService, conversationalService: IGBConversationalService, adminService: IGBAdminService, - deployer: GBDeployer, + deployer: GBDeployer ) { this.core = core; this.conversationalService = conversationalService; @@ -110,14 +110,14 @@ export class GBMinService { public async buildMin( server: any, appPackages: IGBPackage[], - instances: GuaribasInstance[], + instances: GuaribasInstance[] ): Promise { // Serves default UI on root address '/'. const uiPackage = 'default.gbui'; server.use( '/', - express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')), + express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')) ); Promise.all( @@ -152,8 +152,8 @@ export class GBMinService { speechToken: speechToken, conversationId: webchatToken.conversationId, authenticatorTenant: instance.authenticatorTenant, - authenticatorClientId: instance.authenticatorClientId, - }), + authenticatorClientId: instance.authenticatorClientId + }) ); } else { const error = `Instance not found: ${botId}.`; @@ -166,7 +166,7 @@ export class GBMinService { // Build bot adapter. const { min, adapter, conversationState } = await this.buildBotAdapter( - instance, + instance ); // Call the loadBot context.activity for all packages. @@ -184,11 +184,11 @@ export class GBMinService { conversationState, min, instance, - appPackages, + appPackages ); }); logger.info( - `GeneralBots(${instance.engineName}) listening on: ${url}.`, + `GeneralBots(${instance.engineName}) listening on: ${url}.` ); // Serves individual URL for each bot user interface. @@ -196,12 +196,12 @@ export class GBMinService { const uiUrl = `/${instance.botId}`; server.use( uiUrl, - express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')), + express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')) ); logger.info(`Bot UI ${uiPackage} accessible at: ${uiUrl}.`); const state = `${instance.instanceId}${Math.floor( - Math.random() * 1000000000, + Math.random() * 1000000000 )}`; // Clients get redirected here in order to create an OAuth authorize url and redirect them to AAD. @@ -211,7 +211,7 @@ export class GBMinService { let authorizationUrl = UrlJoin( min.instance.authenticatorAuthorityHostUrl, min.instance.authenticatorTenant, - '/oauth2/authorize', + '/oauth2/authorize' ); authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${ min.instance.authenticatorClientId @@ -229,7 +229,7 @@ export class GBMinService { server.get(`/${min.instance.botId}/token`, async (req, res) => { const state = await min.adminService.getValue( min.instance.instanceId, - 'AntiCSRFAttackState', + 'AntiCSRFAttackState' ); if (req.query.state !== state) { @@ -242,8 +242,8 @@ export class GBMinService { const authenticationContext = new AuthenticationContext( UrlJoin( min.instance.authenticatorAuthorityHostUrl, - min.instance.authenticatorTenant, - ), + min.instance.authenticatorTenant + ) ); const resource = 'https://graph.microsoft.com'; @@ -263,27 +263,27 @@ export class GBMinService { await this.adminService.setValue( instance.instanceId, 'refreshToken', - token.refreshToken, + token.refreshToken ); await this.adminService.setValue( instance.instanceId, 'accessToken', - token.accessToken, + token.accessToken ); await this.adminService.setValue( instance.instanceId, 'expiresOn', - token.expiresOn.toString(), + token.expiresOn.toString() ); await this.adminService.setValue( instance.instanceId, 'AntiCSRFAttackState', - null, + null ); res.redirect(min.instance.botEndpoint); } - }, + } ); }); @@ -301,7 +301,7 @@ export class GBMinService { // } // ) // next() - }), + }) ); } @@ -316,8 +316,8 @@ export class GBMinService { url: 'https://directline.botframework.com/v3/directline/tokens/generate', method: 'POST', headers: { - Authorization: `Bearer ${instance.webchatKey}`, - }, + Authorization: `Bearer ${instance.webchatKey}` + } }; try { @@ -344,8 +344,8 @@ export class GBMinService { url: 'https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken', method: 'POST', headers: { - 'Ocp-Apim-Subscription-Key': instance.speechKey, - }, + 'Ocp-Apim-Subscription-Key': instance.speechKey + } }; try { @@ -359,7 +359,7 @@ export class GBMinService { private async buildBotAdapter(instance: any) { const adapter = new BotFrameworkAdapter({ appId: instance.marketplaceId, - appPassword: instance.marketplacePassword, + appPassword: instance.marketplacePassword }); const storage = new MemoryStorage(); @@ -395,7 +395,7 @@ export class GBMinService { GBKBPackage, GBAnalyticsPackage, GBCustomerSatisfactionPackage, - GBWhatsappPackage, + GBWhatsappPackage ].forEach(sysPackage => { const p = Object.create(sysPackage.prototype) as IGBPackage; p.loadBot(min); @@ -406,12 +406,12 @@ export class GBMinService { p.channel.received(req, res); }); } - }, this); + }, this); appPackages.forEach(e => { e.sysPackages = sysPackages; e.loadBot(min); - }, this); + }, this); } /** @@ -424,7 +424,7 @@ export class GBMinService { conversationState: ConversationState, min: any, instance: any, - appPackages: any[], + appPackages: any[] ) { return adapter.processActivity(req, res, async context => { const state = conversationState.get(context); @@ -439,7 +439,7 @@ export class GBMinService { instanceId: instance.instanceId, botId: instance.botId, theme: instance.theme ? instance.theme : 'default.gbtheme', - secret: instance.webchatKey, + secret: instance.webchatKey }); user.loaded = true; user.subjects = []; @@ -449,7 +449,7 @@ export class GBMinService { logger.info( `User>: ${context.activity.text} (${context.activity.type}, ${ context.activity.name - }, ${context.activity.channelId}, {context.activity.value})`, + }, ${context.activity.channelId}, {context.activity.value})` ); if ( context.activity.type === 'conversationUpdate' && @@ -478,7 +478,7 @@ export class GBMinService { // Checks for /menu JSON signature. } else if (context.activity.text.startsWith('{"title"')) { await step.beginDialog('/menu', { - data: JSON.parse(context.activity.text), + data: JSON.parse(context.activity.text) }); // Otherwise, continue to the active dialog in the stack. @@ -487,7 +487,7 @@ export class GBMinService { await step.continueDialog(); } else { await step.beginDialog('/answer', { - query: context.activity.text, + query: context.activity.text }); } } @@ -504,18 +504,18 @@ export class GBMinService { await step.beginDialog('/menu'); } else if (context.activity.name === 'giveFeedback') { await step.beginDialog('/feedback', { - fromMenu: true, + fromMenu: true }); } else if (context.activity.name === 'showFAQ') { await step.beginDialog('/faq'); } else if (context.activity.name === 'answerEvent') { await step.beginDialog('/answerEvent', { questionId: (context.activity as any).data, - fromFaq: true, + fromFaq: true }); } else if (context.activity.name === 'quality') { await step.beginDialog('/quality', { - score: (context.activity as any).data, + score: (context.activity as any).data }); } else if (context.activity.name === 'updateToken') { const token = (context.activity as any).data; @@ -529,7 +529,7 @@ export class GBMinService { logger.error(msg); await step.context.sendActivity( - Messages[step.context.activity.locale].very_sorry_about_error, + Messages[step.context.activity.locale].very_sorry_about_error ); await step.beginDialog('/ask', { isReturning: true }); } diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts index 42da3fc2..9e81174f 100644 --- a/packages/core.gbapp/services/GBVMService.ts +++ b/packages/core.gbapp/services/GBVMService.ts @@ -53,7 +53,6 @@ const UrlJoin = require('url-join'); */ export class GBVMService implements IGBCoreService { - private script = new vm.Script(); public async loadJS( @@ -63,18 +62,15 @@ export class GBVMService implements IGBCoreService { deployer: GBDeployer, localPath: string ): Promise { - - const code = fs.readFileSync(UrlJoin(localPath, filename), 'utf8'); - const sandbox = new DialogClass(min); - + localPath = UrlJoin(localPath, 'chat.dialog.js'); + const code: string = fs.readFileSync(UrlJoin(localPath, filename), 'utf8'); + const sandbox: DialogClass = new DialogClass(min); const context = vm.createContext(sandbox); - this.script.runInContext(context); + + this.script.runInContext(code, context); console.log(util.inspect(sandbox)); - await deployer.deployScriptToStorage( - min.instanceId, - filename - ); + await deployer.deployScriptToStorage(min.instanceId, filename); logger.info(`[GBVMService] Finished loading of ${filename}`); } } diff --git a/packages/default.gbdialog/chat.dialog.vbs b/packages/default.gbdialog/bot.vbs similarity index 97% rename from packages/default.gbdialog/chat.dialog.vbs rename to packages/default.gbdialog/bot.vbs index 62979cb3..226e1358 100644 --- a/packages/default.gbdialog/chat.dialog.vbs +++ b/packages/default.gbdialog/bot.vbs @@ -36,7 +36,7 @@ function ICanSendEmails() bot.say ("Please, what's your e-mail address?") email = bot.expectEmail() - bot.sendMail (email, "Olá", "I'm sending a General Bots VBA e-mail.") + bot.sendMail (email, "Hello", "I'm sending a General Bots VBA e-mail.") end function diff --git a/packages/default.gbdialog/chat.dialog.js b/packages/default.gbdialog/bot.vbs.js similarity index 100% rename from packages/default.gbdialog/chat.dialog.js rename to packages/default.gbdialog/bot.vbs.js diff --git a/packages/kb.gbapp/dialogs/AskDialog.ts b/packages/kb.gbapp/dialogs/AskDialog.ts index 3b2f1774..6a804bbf 100644 --- a/packages/kb.gbapp/dialogs/AskDialog.ts +++ b/packages/kb.gbapp/dialogs/AskDialog.ts @@ -59,10 +59,10 @@ export class AskDialog extends IGBDialog { min.dialogs.add( new WaterfallDialog('/answerEvent', [ async step => { - if (step.options && step.options['questionId']) { + if (step.options && step.options.questionId) { const question = await service.getQuestionById( min.instance.instanceId, - step.options['questionId'] + step.options.questionId ); const answer = await service.getAnswerById( min.instance.instanceId, @@ -84,7 +84,7 @@ export class AskDialog extends IGBDialog { new WaterfallDialog('/answer', [ async step => { const user = await min.userProfile.get(step.context, {}); - let text = step.options['query']; + let text = step.options.query; if (!text) { throw new Error(`/answer being called with no args query text.`); } @@ -97,9 +97,9 @@ export class AskDialog extends IGBDialog { // Handle extra text from FAQ. - if (step.options && step.options['query']) { - text = step.options['query']; - } else if (step.options && step.options['fromFaq']) { + if (step.options && step.options.query) { + text = step.options.query; + } else if (step.options && step.options.fromFaq) { await step.context.sendActivity(Messages[locale].going_answer); } @@ -212,9 +212,9 @@ export class AskDialog extends IGBDialog { // Three forms of asking. - if (step.options && step.options['firstTime'] ) { + if (step.options && step.options.firstTime) { text = Messages[locale].ask_first_time; - } else if (step.options && step.options['isReturning'] ) { + } else if (step.options && step.options.isReturning) { text = Messages[locale].anything_else; } else if (user.subjects.length > 0) { text = Messages[locale].which_question; diff --git a/packages/kb.gbapp/dialogs/MenuDialog.ts b/packages/kb.gbapp/dialogs/MenuDialog.ts index fece9fc0..5ae6441f 100644 --- a/packages/kb.gbapp/dialogs/MenuDialog.ts +++ b/packages/kb.gbapp/dialogs/MenuDialog.ts @@ -63,7 +63,7 @@ export class MenuDialog extends IGBDialog { const locale = step.context.activity.locale; let rootSubjectId = null; - if (step.options && step.options['data']) { + if (step.options && step.options.data) { const subject = step.result.data; // If there is a shortcut specified as subject destination, go there. diff --git a/packages/kb.gbapp/services/KBService.ts b/packages/kb.gbapp/services/KBService.ts index e94b9634..614d1876 100644 --- a/packages/kb.gbapp/services/KBService.ts +++ b/packages/kb.gbapp/services/KBService.ts @@ -30,11 +30,15 @@ | | \*****************************************************************************/ +/** + * @fileoverview Knowledge base services and logic. + */ + const logger = require('../../../src/logger'); const Path = require('path'); const Fs = require('fs'); -const promise = require('bluebird'); -const parse = promise.promisify(require('csv-parse')); + +const parse = require('bluebird').promisify(require('csv-parse')); const UrlJoin = require('url-join'); const marked = require('marked'); const path = require('path'); @@ -69,6 +73,7 @@ export class KBService { subjects.forEach(subject => { out.push(subject.title); }); + return out.join(', '); } @@ -77,6 +82,7 @@ export class KBService { subjects.forEach(subject => { out.push(subject.internalId); }); + return out.join(' '); } @@ -125,8 +131,10 @@ export class KBService { answerId: question.answerId } }); + return Promise.resolve({ question: question, answer: answer }); } + return Promise.resolve(null); } @@ -163,9 +171,9 @@ export class KBService { query = `${query} ${text}`; } } - // TODO: Filter by instance. what = `${what}&$filter=instanceId eq ${instanceId}` + query = `${query}&$filter=instanceId eq ${instance.instanceId}`; try { - if (instance.searchKey && GBConfigService.get('STORAGE_DIALECT') == 'mssql') { + if (instance.searchKey && GBConfigService.get('STORAGE_DIALECT') === 'mssql') { const service = new AzureSearch( instance.searchKey, instance.searchHost, @@ -203,6 +211,7 @@ export class KBService { parentId: number ): Promise { const where = { parentSubjectId: parentId, instanceId: instanceId }; + return GuaribasSubject.findAll({ where: where }); @@ -210,7 +219,7 @@ export class KBService { public async getFaqBySubjectArray(from: string, subjects: any): Promise { const where = { - from: from, subject1: null, subject2: null, subject3: null, subject4:null + from: from, subject1: null, subject2: null, subject3: null, subject4: null }; if (subjects) { @@ -230,6 +239,7 @@ export class KBService { where.subject4 = subjects[3].internalId; } } + return await GuaribasQuestion.findAll({ where: where }); @@ -250,6 +260,7 @@ export class KBService { let lastAnswer: GuaribasAnswer; const data = await parse(file, opts); + return asyncPromise.eachSeries(data, async line => { // Extracts values from columns in the current line. @@ -262,7 +273,7 @@ export class KBService { // Skips the first line. - if (!(subjectsText === 'subjects' && from == 'from')) { + if (!(subjectsText === 'subjects' && from === 'from')) { let format = '.txt'; // Extracts answer from external media if any. @@ -281,8 +292,10 @@ export class KBService { // Processes subjects hierarchy splitting by dots. const subjectArray = subjectsText.split('.'); - let subject1: string, subject2: string, subject3: string, - subject4: string; + let subject1: string; + let subject2: string; + let subject3: string; + let subject4: string; let indexer = 0; subjectArray.forEach(element => { diff --git a/src/app.ts b/src/app.ts index c0d174e0..1f87ab6a 100644 --- a/src/app.ts +++ b/src/app.ts @@ -75,8 +75,8 @@ export class GBServer { server.use( bodyParser.urlencoded({ // to support URL-encoded bodies - extended: true, - }), + extended: true + }) ); let bootInstance: IGBInstance; @@ -93,40 +93,33 @@ export class GBServer { // Ensures cloud / on-premises infrastructure is setup. logger.info(`Establishing a development local proxy (ngrok)...`); - const proxyAddress = await core.ensureProxy(port); + const proxyAddress: string = await core.ensureProxy(port); logger.info(`Deploying packages...`); - const deployer = new GBDeployer(core, new GBImporter(core)); - const azureDeployer = new AzureDeployerService(deployer); - const adminService = new GBAdminService(core); - const conversationalService = new GBConversationalService(core); - bootInstance = await core.createBootInstance( - core, - azureDeployer, - proxyAddress, - ); + const importer: GBImporter = new GBImporter(core); + const deployer: GBDeployer = new GBDeployer(core, importer); + const azureDeployer: AzureDeployerService = new AzureDeployerService(deployer); + const adminService: GBAdminService = new GBAdminService(core); + const conversationalService: GBConversationalService = new GBConversationalService(core); core.ensureAdminIsSecured(); - core.loadSysPackages(core); + + const bootInstance = await core.createBootInstance(core, azureDeployer, proxyAddress); + await core.checkStorage(azureDeployer); + await core.loadSysPackages(core); await deployer.deployPackages(core, server, appPackages); logger.info(`Publishing instances...`); - let instances: GuaribasInstance[] = await core.loadAllInstances( - core, - azureDeployer, - proxyAddress, - ); - instances = await core.ensureInstances( - instances, - bootInstance, - core - ); + const packageInstance = await importer.importIfNotExistsBotPackage('boot.gbot', 'packages/boot.gbot'); + const fullInstance = Object.assign(packageInstance, bootInstance); + await core.saveInstance(fullInstance); + let instances: GuaribasInstance[] = await core.loadAllInstances(core, azureDeployer, proxyAddress); + instances = await core.ensureInstances(instances, bootInstance, core); - const minService = new GBMinService( - core, - conversationalService, - adminService, - deployer, - ); + // Install default VBA module. + + deployer.deployPackageFromLocalPath(instances[0], 'packages/default.gbdialog'); + + const minService: GBMinService = new GBMinService(core, conversationalService, adminService, deployer); await minService.buildMin(server, appPackages, instances); logger.info(`The Bot Server is in RUNNING mode...`); @@ -141,7 +134,6 @@ export class GBServer { }); } } - // First line to run. diff --git a/src/logger.ts b/src/logger.ts index 6a77c89a..ee201a78 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -30,6 +30,10 @@ | | \*****************************************************************************/ +/** + * @fileoverview Logging support. + */ + const { createLogger, format, transports } = require('winston'); const config = { diff --git a/tslint.json b/tslint.json index 99e6879a..1b26d002 100644 --- a/tslint.json +++ b/tslint.json @@ -15,6 +15,8 @@ ], "jsRules": {}, "rules": { + "no-var-requires":false, + "typedef":false, "variable-name": false, "no-parameter-properties": false, "no-reserved-keywords": false, @@ -34,6 +36,6 @@ "export-name":false, "no-relative-imports": false, "no-backbone-get-set-outside-model": false, - "max-line-length": [true,{"limit":80,"ignore-pattern":"^\\s+\\*"}] + "max-line-length": [true,{"limit":120,"ignore-pattern":"^\\s+\\*"}] } } -- 2.39.5 From 3f32e48fad3635b3e29365cbd019a16fea36fd39 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (pragmatismo.io)" Date: Wed, 28 Nov 2018 17:08:06 -0200 Subject: [PATCH 04/13] fix(core): Loaded dynamically a .js file containing converted VBA dialogs. --- packages/core.gbapp/services/GBAPIService.ts | 7 +-- packages/core.gbapp/services/GBCoreService.ts | 45 +++++++++---------- packages/core.gbapp/services/GBDeployer.ts | 9 ++-- .../core.gbapp/services/GBImporterService.ts | 40 ++++++----------- packages/core.gbapp/services/GBMinService.ts | 8 +++- packages/core.gbapp/services/GBVMService.ts | 13 +++--- packages/default.gbdialog/bot.vbs.js | 8 +++- packages/kb.gbapp/dialogs/AskDialog.ts | 16 +++---- packages/kb.gbapp/dialogs/MenuDialog.ts | 2 +- src/app.ts | 22 ++++++--- 10 files changed, 89 insertions(+), 81 deletions(-) diff --git a/packages/core.gbapp/services/GBAPIService.ts b/packages/core.gbapp/services/GBAPIService.ts index 70acdf7d..f489fcb0 100644 --- a/packages/core.gbapp/services/GBAPIService.ts +++ b/packages/core.gbapp/services/GBAPIService.ts @@ -33,16 +33,16 @@ 'use strict'; import { WaterfallDialog } from 'botbuilder-dialogs'; -import { IGBInstance, IGBPackage } from 'botlib'; +import { IGBInstance, IGBPackage ,GBMinInstance } from 'botlib'; /** * @fileoverview General Bots server core. */ export class DialogClass { - public min: IGBInstance; + public min: GBMinInstance; - constructor(min: IGBInstance) { + constructor(min: GBMinInstance) { this.min = min; } @@ -72,6 +72,7 @@ export class DialogClass { new WaterfallDialog('/vmSend', [ async step => { await step.context.sendActivity(text); + return await step.next(); } ]) diff --git a/packages/core.gbapp/services/GBCoreService.ts b/packages/core.gbapp/services/GBCoreService.ts index a7f6256d..4d0f4ca3 100644 --- a/packages/core.gbapp/services/GBCoreService.ts +++ b/packages/core.gbapp/services/GBCoreService.ts @@ -184,8 +184,9 @@ export class GBCoreService implements IGBCoreService { alter: alter, force: force }); + } else { - const msg = 'Database synchronization is disabled.'; + const msg = `Database synchronization is disabled.`; logger.info(msg); } } @@ -218,21 +219,21 @@ export class GBCoreService implements IGBCoreService { public async writeEnv(instance: IGBInstance) { const env = `ADDITIONAL_DEPLOY_PATH= - ADMIN_PASS=${instance.adminPass} - CLOUD_SUBSCRIPTIONID=${instance.cloudSubscriptionId} - CLOUD_LOCATION=${instance.cloudLocation} - CLOUD_GROUP=${instance.botId} - CLOUD_USERNAME=${instance.cloudUsername} - CLOUD_PASSWORD=${instance.cloudPassword} - MARKETPLACE_ID=${instance.marketplaceId} - MARKETPLACE_SECRET=${instance.marketplacePassword} - NLP_AUTHORING_KEY=${instance.nlpAuthoringKey} - STORAGE_DIALECT=${instance.storageDialect} - STORAGE_SERVER=${instance.storageServer}.database.windows.net - STORAGE_NAME=${instance.storageName} - STORAGE_USERNAME=${instance.storageUsername} - STORAGE_PASSWORD=${instance.storagePassword} - STORAGE_SYNC=true`; + ADMIN_PASS=${instance.adminPass} + CLOUD_SUBSCRIPTIONID=${instance.cloudSubscriptionId} + CLOUD_LOCATION=${instance.cloudLocation} + CLOUD_GROUP=${instance.botId} + CLOUD_USERNAME=${instance.cloudUsername} + CLOUD_PASSWORD=${instance.cloudPassword} + MARKETPLACE_ID=${instance.marketplaceId} + MARKETPLACE_SECRET=${instance.marketplacePassword} + NLP_AUTHORING_KEY=${instance.nlpAuthoringKey} + STORAGE_DIALECT=${instance.storageDialect} + STORAGE_SERVER=${instance.storageServer}.database.windows.net + STORAGE_NAME=${instance.storageName} + STORAGE_USERNAME=${instance.storageUsername} + STORAGE_PASSWORD=${instance.storagePassword} + STORAGE_SYNC=true`; fs.writeFileSync('.env', env); } @@ -312,6 +313,7 @@ export class GBCoreService implements IGBCoreService { } public loadSysPackages(core: GBCoreService) { + // NOTE: if there is any code before this line a semicolon // will be necessary before this line. // Loads all system packages. @@ -342,26 +344,21 @@ export class GBCoreService implements IGBCoreService { public async createBootInstance(core: GBCoreService, azureDeployer: AzureDeployerService, proxyAddress: string) { let instance: IGBInstance; - try { - await core.initStorage(); - } catch (error) { logger.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`); try { instance = await azureDeployer.deployFarm(proxyAddress); } catch (error) { logger.warn( - 'In case of error, please cleanup any infrastructure objects ' + - 'created during this procedure and .env before running again.' + `In case of error, please cleanup any infrastructure objects + created during this procedure and .env before running again.` ); throw error; } core.writeEnv(instance); logger.info(`File .env written, starting General Bots...`); GBConfigService.init(); - await core.initStorage(); - return instance; - } + return instance; } public openBrowserInDevelopment() { diff --git a/packages/core.gbapp/services/GBDeployer.ts b/packages/core.gbapp/services/GBDeployer.ts index 1d0b15de..b79eff07 100644 --- a/packages/core.gbapp/services/GBDeployer.ts +++ b/packages/core.gbapp/services/GBDeployer.ts @@ -43,7 +43,7 @@ const Fs = require('fs'); const WaitUntil = require('wait-until'); const express = require('express'); -import { IGBCoreService, IGBInstance } from 'botlib'; +import { GBMinInstance, IGBCoreService, IGBInstance } from 'botlib'; import { GBError } from 'botlib'; import { IGBPackage } from 'botlib'; import { AzureSearch } from 'pragmatismo-io-framework'; @@ -174,9 +174,11 @@ export class GBDeployer { /** Deploys all .gbot files first. */ botPackages.forEach(e => { + if (e!=='packages\\boot.gbot'){ logger.info(`Deploying bot: ${e}...`); _this.deployBot(e); logger.info(`Bot: ${e} deployed...`); + } }); /** Then all remaining generalPackages are loaded. */ @@ -251,10 +253,11 @@ export class GBDeployer { public async deployBot(localPath: string): Promise { const packageType = Path.extname(localPath); const packageName = Path.basename(localPath); - const instance = await this.importer.importIfNotExistsBotPackage( + const instance = await this.importer.importIfNotExistsBotPackage(null, packageName, localPath ); + return instance; } @@ -282,7 +285,7 @@ export class GBDeployer { // }) } - public async deployPackageFromLocalPath(min: IGBInstance, localPath: string) { + public async deployPackageFromLocalPath(min: GBMinInstance, localPath: string) { const packageType = Path.extname(localPath); switch (packageType) { diff --git a/packages/core.gbapp/services/GBImporterService.ts b/packages/core.gbapp/services/GBImporterService.ts index 04bf1208..65745089 100644 --- a/packages/core.gbapp/services/GBImporterService.ts +++ b/packages/core.gbapp/services/GBImporterService.ts @@ -50,41 +50,29 @@ export class GBImporter { this.core = core; } - public async importIfNotExistsBotPackage( - packageName: string, - localPath: string) { - - const packageJson = JSON.parse( - fs.readFileSync(UrlJoin(localPath, 'package.json'), 'utf8') - ); - const botId = packageJson.botId; + public async importIfNotExistsBotPackage(botId: string, packageName: string, localPath: string) { + const packageJson = JSON.parse(fs.readFileSync(UrlJoin(localPath, 'package.json'), 'utf8')); + if (!botId) { + botId = packageJson.botId; + } const instance = await this.core.loadInstance(botId); if (instance) { return instance; } else { - return await this.createInstanceInternal(packageName, localPath, packageJson); + return await this.createInstanceInternal(botId, packageName, localPath, packageJson); } } - private async createInstanceInternal( - packageName: string, - localPath: string, - packageJson: any - ) { - const settings = JSON.parse( - fs.readFileSync(UrlJoin(localPath, 'settings.json'), 'utf8') - ); - const servicesJson = JSON.parse( - fs.readFileSync(UrlJoin(localPath, 'services.json'), 'utf8') - ); + private async createInstanceInternal(botId: string, packageName: string, localPath: string, packageJson: any) { + const settings = JSON.parse(fs.readFileSync(UrlJoin(localPath, 'settings.json'), 'utf8')); + const servicesJson = JSON.parse(fs.readFileSync(UrlJoin(localPath, 'services.json'), 'utf8')); - packageJson = {...packageJson, ...settings, ...servicesJson}; + packageJson = { ...packageJson, ...settings, ...servicesJson }; - GuaribasInstance.create(packageJson).then((instance: IGBInstance) => { - const service = new SecService(); - // TODO: service.importSecurityFile(localPath, instance) + if (botId){ + packageJson.botId = botId; + } - Promise.resolve(instance); - }); + return GuaribasInstance.create(packageJson); } } diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index fded27ab..eb974773 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -110,7 +110,8 @@ export class GBMinService { public async buildMin( server: any, appPackages: IGBPackage[], - instances: GuaribasInstance[] + instances: GuaribasInstance[], + deployer: GBDeployer ): Promise { // Serves default UI on root address '/'. @@ -169,6 +170,11 @@ export class GBMinService { instance ); + // Install default VBA module. + + deployer.deployPackageFromLocalPath(min, 'packages/default.gbdialog'); + + // Call the loadBot context.activity for all packages. this.invokeLoadBot(appPackages, min, server); diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts index 9e81174f..4249f6b9 100644 --- a/packages/core.gbapp/services/GBVMService.ts +++ b/packages/core.gbapp/services/GBVMService.ts @@ -33,8 +33,7 @@ 'use strict'; import { IGBCoreService, IGBInstance } from 'botlib'; -import { GBError } from 'botlib'; -import { IGBPackage } from 'botlib'; +import { GBMinInstance } from 'botlib'; const logger = require('../../../src/logger'); import { BotAdapter } from 'botbuilder'; import { WaterfallDialog } from 'botbuilder-dialogs'; @@ -57,18 +56,20 @@ export class GBVMService implements IGBCoreService { public async loadJS( filename: string, - min: IGBInstance, + min: GBMinInstance, core: IGBCoreService, deployer: GBDeployer, localPath: string ): Promise { - localPath = UrlJoin(localPath, 'chat.dialog.js'); - const code: string = fs.readFileSync(UrlJoin(localPath, filename), 'utf8'); + + localPath = UrlJoin(localPath, 'bot.vbs.js'); + const code: string = fs.readFileSync(localPath, 'utf8'); const sandbox: DialogClass = new DialogClass(min); const context = vm.createContext(sandbox); - this.script.runInContext(code, context); + vm.runInContext(code, context); console.log(util.inspect(sandbox)); + sandbox['chat'](sandbox); await deployer.deployScriptToStorage(min.instanceId, filename); logger.info(`[GBVMService] Finished loading of ${filename}`); diff --git a/packages/default.gbdialog/bot.vbs.js b/packages/default.gbdialog/bot.vbs.js index 6e2c001a..c18aa941 100644 --- a/packages/default.gbdialog/bot.vbs.js +++ b/packages/default.gbdialog/bot.vbs.js @@ -1,4 +1,6 @@ -export function chat() { + +function chat(bot) { + //**************************************************************************** // ( )_ _ // _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ @@ -31,11 +33,13 @@ export function chat() { // //**************************************************************************** + bot.talk('Qual seu e-mail?'); + main = () => { bot.addFunction(PegaEmail); PegaEmail = bot => { - bot.say('Qual seu e-mail?'); + bot.talk('Qual seu e-mail?'); email = bot.expectEmail; bot.post('/restservice', email); }; diff --git a/packages/kb.gbapp/dialogs/AskDialog.ts b/packages/kb.gbapp/dialogs/AskDialog.ts index 6a804bbf..076f965b 100644 --- a/packages/kb.gbapp/dialogs/AskDialog.ts +++ b/packages/kb.gbapp/dialogs/AskDialog.ts @@ -59,10 +59,10 @@ export class AskDialog extends IGBDialog { min.dialogs.add( new WaterfallDialog('/answerEvent', [ async step => { - if (step.options && step.options.questionId) { + if (step.options && step.options['questionId']) { const question = await service.getQuestionById( min.instance.instanceId, - step.options.questionId + step.options['questionId'] ); const answer = await service.getAnswerById( min.instance.instanceId, @@ -84,7 +84,7 @@ export class AskDialog extends IGBDialog { new WaterfallDialog('/answer', [ async step => { const user = await min.userProfile.get(step.context, {}); - let text = step.options.query; + let text = step.options['query']; if (!text) { throw new Error(`/answer being called with no args query text.`); } @@ -97,9 +97,9 @@ export class AskDialog extends IGBDialog { // Handle extra text from FAQ. - if (step.options && step.options.query) { - text = step.options.query; - } else if (step.options && step.options.fromFaq) { + if (step.options && step.options['query']) { + text = step.options['query']; + } else if (step.options && step.options['fromFaq']) { await step.context.sendActivity(Messages[locale].going_answer); } @@ -212,9 +212,9 @@ export class AskDialog extends IGBDialog { // Three forms of asking. - if (step.options && step.options.firstTime) { + if (step.options && step.options['firstTime']) { text = Messages[locale].ask_first_time; - } else if (step.options && step.options.isReturning) { + } else if (step.options && step.options['isReturning']) { text = Messages[locale].anything_else; } else if (user.subjects.length > 0) { text = Messages[locale].which_question; diff --git a/packages/kb.gbapp/dialogs/MenuDialog.ts b/packages/kb.gbapp/dialogs/MenuDialog.ts index 5ae6441f..fece9fc0 100644 --- a/packages/kb.gbapp/dialogs/MenuDialog.ts +++ b/packages/kb.gbapp/dialogs/MenuDialog.ts @@ -63,7 +63,7 @@ export class MenuDialog extends IGBDialog { const locale = step.context.activity.locale; let rootSubjectId = null; - if (step.options && step.options.data) { + if (step.options && step.options['data']) { const subject = step.result.data; // If there is a shortcut specified as subject destination, go there. diff --git a/src/app.ts b/src/app.ts index 1f87ab6a..1c1b897a 100644 --- a/src/app.ts +++ b/src/app.ts @@ -103,24 +103,32 @@ export class GBServer { const conversationalService: GBConversationalService = new GBConversationalService(core); core.ensureAdminIsSecured(); - const bootInstance = await core.createBootInstance(core, azureDeployer, proxyAddress); - await core.checkStorage(azureDeployer); + let bootInstance: IGBInstance = null; + try { + await core.initStorage(); + } catch (error) { + bootInstance = await core.createBootInstance(core, azureDeployer, proxyAddress); + await core.initStorage(); + } + await core.loadSysPackages(core); + await core.checkStorage(azureDeployer); await deployer.deployPackages(core, server, appPackages); logger.info(`Publishing instances...`); - const packageInstance = await importer.importIfNotExistsBotPackage('boot.gbot', 'packages/boot.gbot'); + const packageInstance = await importer.importIfNotExistsBotPackage( + GBConfigService.get('CLOUD_GROUP'), + 'boot.gbot', + 'packages/boot.gbot' + ); const fullInstance = Object.assign(packageInstance, bootInstance); await core.saveInstance(fullInstance); let instances: GuaribasInstance[] = await core.loadAllInstances(core, azureDeployer, proxyAddress); instances = await core.ensureInstances(instances, bootInstance, core); - // Install default VBA module. - - deployer.deployPackageFromLocalPath(instances[0], 'packages/default.gbdialog'); const minService: GBMinService = new GBMinService(core, conversationalService, adminService, deployer); - await minService.buildMin(server, appPackages, instances); + await minService.buildMin(server, appPackages, instances, deployer); logger.info(`The Bot Server is in RUNNING mode...`); core.openBrowserInDevelopment(); -- 2.39.5 From 9379dec1b0e11db9e245f9322dd68af66d983edf Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (pragmatismo.io)" Date: Fri, 30 Nov 2018 11:55:44 -0200 Subject: [PATCH 05/13] fix(core): Bot Server is runnable again after refactory. --- package-lock.json | 5 + package.json | 1 + packages/core.gbapp/dialogs/WelcomeDialog.ts | 1 + packages/core.gbapp/services/GBAPIService.ts | 49 +- packages/core.gbapp/services/GBDeployer.ts | 100 ++-- packages/core.gbapp/services/GBMinService.ts | 14 +- packages/core.gbapp/services/GBVMService.ts | 17 +- packages/default.gbdialog/bot.vbs.js | 15 +- packages/default.gbui/package-lock.json | 516 ++++--------------- packages/default.gbui/package.json | 15 +- packages/default.gbui/src/GBUIApp.js | 3 - packages/kb.gbapp/dialogs/AskDialog.ts | 3 +- packages/whatsapp.gblib/index.ts | 2 +- src/app.ts | 9 +- 14 files changed, 187 insertions(+), 563 deletions(-) diff --git a/package-lock.json b/package-lock.json index 17f168a0..10f5ac14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19158,6 +19158,11 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "vbscript-to-typescript": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/vbscript-to-typescript/-/vbscript-to-typescript-1.0.8.tgz", + "integrity": "sha512-vjElTpd4EVxUoxeGrRId4hetHdlyxIRicxkZxdErFfLJ9X7xyw0HEboXZO+s8prIC7s1eh0ulkuVbencrv6fAQ==" + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", diff --git a/package.json b/package.json index 30542ebc..c1069077 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "typedoc-plugin-markdown": "^1.1.18", "typescript": "3.1.6", "url-join": "4.0.0", + "vbscript-to-typescript": "^1.0.8", "wait-until": "0.0.2", "walk-promise": "0.2.0", "winston": "3.1.0" diff --git a/packages/core.gbapp/dialogs/WelcomeDialog.ts b/packages/core.gbapp/dialogs/WelcomeDialog.ts index b4dcfe75..ced210bb 100644 --- a/packages/core.gbapp/dialogs/WelcomeDialog.ts +++ b/packages/core.gbapp/dialogs/WelcomeDialog.ts @@ -80,6 +80,7 @@ export class WelcomeDialog extends IGBDialog { await step.replaceDialog('/answer', { query: step.context.activity.text }); } } + return await step.next(); } ])); diff --git a/packages/core.gbapp/services/GBAPIService.ts b/packages/core.gbapp/services/GBAPIService.ts index f489fcb0..e2d37ba6 100644 --- a/packages/core.gbapp/services/GBAPIService.ts +++ b/packages/core.gbapp/services/GBAPIService.ts @@ -32,8 +32,8 @@ 'use strict'; -import { WaterfallDialog } from 'botbuilder-dialogs'; -import { IGBInstance, IGBPackage ,GBMinInstance } from 'botlib'; +import { TurnContext } from 'botbuilder'; +import { GBMinInstance } from 'botlib'; /** * @fileoverview General Bots server core. @@ -41,41 +41,30 @@ import { IGBInstance, IGBPackage ,GBMinInstance } from 'botlib'; export class DialogClass { public min: GBMinInstance; + public context: TurnContext; constructor(min: GBMinInstance) { this.min = min; - } + } public async hear(text: string): Promise { - return new Promise((resolve, reject) => { - this.min.dialogs.add( - new WaterfallDialog('/vmExpect', [ - async step => { - await step.prompt('textPrompt', text); - return await step.next(); - }, - async step => { - resolve(step.result); - return await step.next(); - } - ]) - ); - }); - } - - public post(url: string, data) { - + // await this.context.beginDialog('textPrompt', text); } public talk(text: string) { - this.min.dialogs.add( - new WaterfallDialog('/vmSend', [ - async step => { - await step.context.sendActivity(text); - - return await step.next(); - } - ]) - ); + this.context.sendActivity(text); } + + /** + * Generic function to call any REST API. + */ + public sendEmail(to, subject, body) { + // tslint:disable-next-line:no-console + console.log(`[E-mail]: to:${to}, subject: ${subject}, body: ${body}.`); + } + + /** + * Generic function to call any REST API. + */ + public post(url: string, data) {} } diff --git a/packages/core.gbapp/services/GBDeployer.ts b/packages/core.gbapp/services/GBDeployer.ts index b79eff07..da535e02 100644 --- a/packages/core.gbapp/services/GBDeployer.ts +++ b/packages/core.gbapp/services/GBDeployer.ts @@ -42,6 +42,7 @@ const UrlJoin = require('url-join'); const Fs = require('fs'); const WaitUntil = require('wait-until'); const express = require('express'); +const child_process = require('child_process'); import { GBMinInstance, IGBCoreService, IGBInstance } from 'botlib'; import { GBError } from 'botlib'; @@ -67,13 +68,9 @@ export class GBDeployer { } public static getConnectionStringFromInstance(instance: GuaribasInstance) { - return `Server=tcp:${ - instance.storageServer - }.database.windows.net,1433;Database=${instance.storageName};User ID=${ + return `Server=tcp:${instance.storageServer}.database.windows.net,1433;Database=${instance.storageName};User ID=${ instance.storageUsername - };Password=${ - instance.storagePassword - };Trusted_Connection=False;Encrypt=True;Connection Timeout=30;`; + };Password=${instance.storagePassword};Trusted_Connection=False;Encrypt=True;Connection Timeout=30;`; } /** @@ -81,11 +78,7 @@ export class GBDeployer { * Performs package deployment in all .gbai or default. * * */ - public deployPackages( - core: IGBCoreService, - server: any, - appPackages: IGBPackage[] - ) { + public deployPackages(core: IGBCoreService, server: any, appPackages: IGBPackage[]) { const _this = this; return new Promise( (resolve: any, reject: any): any => { @@ -122,9 +115,7 @@ export class GBDeployer { }); } - logger.info( - `Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...` - ); + logger.info(`Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...`); paths.forEach(e => { logger.info(`Looking in: ${e}...`); doIt(e); @@ -174,10 +165,10 @@ export class GBDeployer { /** Deploys all .gbot files first. */ botPackages.forEach(e => { - if (e!=='packages\\boot.gbot'){ - logger.info(`Deploying bot: ${e}...`); - _this.deployBot(e); - logger.info(`Bot: ${e} deployed...`); + if (e !== 'packages\\boot.gbot') { + logger.info(`Deploying bot: ${e}...`); + _this.deployBot(e); + logger.info(`Bot: ${e} deployed...`); } }); @@ -191,27 +182,16 @@ export class GBDeployer { /** Handles apps for general bots - .gbapp must stay out of deploy folder. */ - if ( - Path.extname(filename) === '.gbapp' || - Path.extname(filename) === '.gblib' - ) { + if (Path.extname(filename) === '.gbapp' || Path.extname(filename) === '.gblib') { /** Themes for bots. */ } else if (Path.extname(filename) === '.gbtheme') { server.use('/themes/' + filenameOnly, express.static(filename)); - logger.info( - `Theme (.gbtheme) assets accessible at: ${'/themes/' + - filenameOnly}.` - ); + logger.info(`Theme (.gbtheme) assets accessible at: ${'/themes/' + filenameOnly}.`); /** Knowledge base for bots. */ } else if (Path.extname(filename) === '.gbkb') { - server.use( - '/kb/' + filenameOnly + '/subjects', - express.static(UrlJoin(filename, 'subjects')) - ); - logger.info( - `KB (.gbkb) assets accessible at: ${'/kb/' + filenameOnly}.` - ); + server.use('/kb/' + filenameOnly + '/subjects', express.static(UrlJoin(filename, 'subjects'))); + logger.info(`KB (.gbkb) assets accessible at: ${'/kb/' + filenameOnly}.`); } else if (Path.extname(filename) === '.gbui') { // Already Handled } else if (Path.extname(filename) === '.gbdialog') { @@ -253,18 +233,12 @@ export class GBDeployer { public async deployBot(localPath: string): Promise { const packageType = Path.extname(localPath); const packageName = Path.basename(localPath); - const instance = await this.importer.importIfNotExistsBotPackage(null, - packageName, - localPath - ); + const instance = await this.importer.importIfNotExistsBotPackage(null, packageName, localPath); return instance; } - public async deployPackageToStorage( - instanceId: number, - packageName: string - ): Promise { + public async deployPackageToStorage(instanceId: number, packageName: string): Promise { return GuaribasPackage.create({ packageName: packageName, instanceId: instanceId @@ -308,18 +282,13 @@ export class GBDeployer { return vm.loadJS(localPath, min, this.core, this, localPath); default: - const err = GBError.create( - `GuaribasBusinessError: Unknown package type: ${packageType}.` - ); + const err = GBError.create(`GuaribasBusinessError: Unknown package type: ${packageType}.`); Promise.reject(err); break; } } - public async undeployPackageFromLocalPath( - instance: IGBInstance, - localPath: string - ) { + public async undeployPackageFromLocalPath(instance: IGBInstance, localPath: string) { const packageType = Path.extname(localPath); const packageName = Path.basename(localPath); @@ -345,9 +314,7 @@ export class GBDeployer { break; default: - const err = GBError.create( - `GuaribasBusinessError: Unknown package type: ${packageType}.` - ); + const err = GBError.create(`GuaribasBusinessError: Unknown package type: ${packageType}.`); Promise.reject(err); break; } @@ -361,9 +328,7 @@ export class GBDeployer { instance.searchIndexer ); - const connectionString = GBDeployer.getConnectionStringFromInstance( - instance - ); + const connectionString = GBDeployer.getConnectionStringFromInstance(instance); const dsName = 'gb'; try { @@ -375,13 +340,7 @@ export class GBDeployer { } } - await search.createDataSource( - dsName, - dsName, - 'GuaribasQuestion', - 'azuresql', - connectionString - ); + await search.createDataSource(dsName, dsName, 'GuaribasQuestion', 'azuresql', connectionString); try { await search.deleteIndex(); @@ -391,19 +350,22 @@ export class GBDeployer { throw err; } } - await search.createIndex( - AzureDeployerService.getKBSearchSchema(instance.searchIndex), - dsName - ); + await search.createIndex(AzureDeployerService.getKBSearchSchema(instance.searchIndex), dsName); } - public async getPackageByName( - instanceId: number, - packageName: string - ): Promise { + public async getPackageByName(instanceId: number, packageName: string): Promise { const where = { packageName: packageName, instanceId: instanceId }; return GuaribasPackage.findOne({ where: where }); } + + public installDefaultGBUI() { + const root = 'packages/default.gbui'; + if (!Fs.existsSync(`${root}/build`)) { + Fs.writeFileSync(`${root}/.env`, 'SKIP_PREFLIGHT_CHECK=true'); + child_process.execSync('npm install', { cwd: root }); + child_process.execSync('npm run build', { cwd: root }); + } + } } diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index eb974773..9d646020 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -108,6 +108,7 @@ export class GBMinService { * */ public async buildMin( + bootInstance: GuaribasInstance, server: any, appPackages: IGBPackage[], instances: GuaribasInstance[], @@ -134,7 +135,11 @@ export class GBMinService { (async () => { // Returns the instance object to clients requesting bot info. - const botId = req.params.botId; + let botId = req.params.botId; + if (botId === '[default]'){ + botId = bootInstance.botId; + } + const instance = await this.core.loadInstance(botId); if (instance) { @@ -477,8 +482,10 @@ export class GBMinService { // Processes messages. } else if (context.activity.type === 'message') { // Checks for /admin request. - - if (context.activity.text === 'admin') { + if (context.activity.text === 'vba') { + min.sandbox.context = context; + min.sandbox['chat'](min.sandbox); + } else if (context.activity.text === 'admin') { await step.beginDialog('/admin'); // Checks for /menu JSON signature. @@ -500,6 +507,7 @@ export class GBMinService { // Processes events. } else if (context.activity.type === 'event') { + // Empties dialog stack before going to the target. await step.endAll(); diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts index 4249f6b9..4a6e480a 100644 --- a/packages/core.gbapp/services/GBVMService.ts +++ b/packages/core.gbapp/services/GBVMService.ts @@ -32,23 +32,17 @@ 'use strict'; -import { IGBCoreService, IGBInstance } from 'botlib'; -import { GBMinInstance } from 'botlib'; -const logger = require('../../../src/logger'); -import { BotAdapter } from 'botbuilder'; -import { WaterfallDialog } from 'botbuilder-dialogs'; +import { GBMinInstance, IGBCoreService } from 'botlib'; import * as fs from 'fs'; -import { Messages } from '../strings'; import { DialogClass } from './GBAPIService'; import { GBDeployer } from './GBDeployer'; const util = require('util'); +const logger = require('../../../src/logger'); const vm = require('vm'); -import processExists = require('process-exists'); -import { Sequelize } from 'sequelize-typescript'; const UrlJoin = require('url-join'); /** - * @fileoverview General Bots server core. + * @fileoverview Virtualization services for emulation of BASIC. */ export class GBVMService implements IGBCoreService { @@ -66,12 +60,11 @@ export class GBVMService implements IGBCoreService { const code: string = fs.readFileSync(localPath, 'utf8'); const sandbox: DialogClass = new DialogClass(min); const context = vm.createContext(sandbox); - vm.runInContext(code, context); - console.log(util.inspect(sandbox)); - sandbox['chat'](sandbox); await deployer.deployScriptToStorage(min.instanceId, filename); logger.info(`[GBVMService] Finished loading of ${filename}`); + + min.sandbox = sandbox; } } diff --git a/packages/default.gbdialog/bot.vbs.js b/packages/default.gbdialog/bot.vbs.js index c18aa941..22f66cb4 100644 --- a/packages/default.gbdialog/bot.vbs.js +++ b/packages/default.gbdialog/bot.vbs.js @@ -33,15 +33,8 @@ function chat(bot) { // //**************************************************************************** - bot.talk('Qual seu e-mail?'); - - main = () => { - bot.addFunction(PegaEmail); - - PegaEmail = bot => { - bot.talk('Qual seu e-mail?'); - email = bot.expectEmail; - bot.post('/restservice', email); - }; - }; + bot.talk('Please, what is your e-mail?'); + //email = bot.expectEmail(); + //bot.talk('Thanks, sending e-mail to: ' + email); + //bot.sendEmail(to, 'Message from VBA Bot', 'Yes, I can send e-mails.'); } diff --git a/packages/default.gbui/package-lock.json b/packages/default.gbui/package-lock.json index c787988b..a586ba92 100644 --- a/packages/default.gbui/package-lock.json +++ b/packages/default.gbui/package-lock.json @@ -1134,9 +1134,9 @@ "integrity": "sha512-ugTb7Lq7u4GfWSqqpwE0bGyoBZNMTok/zDBXxfEG0QM50jNlGhIWjRC1pPN7bvV1anhF+bs+/gNcRw+o55Evbg==" }, "adaptivecards": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/adaptivecards/-/adaptivecards-1.1.0.tgz", - "integrity": "sha512-n4vRIjo0vKvjMMnTKqRZm3TXhq+8saN1s17Z0pQEtDpBWjrGbfn8e+Qx/xvgNjqFYzWIme7uZAX3DBDhSOLu/A==" + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/adaptivecards/-/adaptivecards-1.0.0.tgz", + "integrity": "sha1-96HxdpJRYmirQMpU8Z/aP6WJdjQ=" }, "address": { "version": "1.0.3", @@ -2043,15 +2043,6 @@ } } }, - "babel-runtime": { - "version": "7.0.0-beta.3", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-7.0.0-beta.3.tgz", - "integrity": "sha512-jlzZ8RACjt0QGxq+wqsw5bCQE9RcUyWpw987mDY3GYxTpOQT2xoyNoG++oVCHzr/nACLBIprfVBNvv/If1ZYcg==", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, "babel-template": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", @@ -2338,86 +2329,48 @@ } }, "botframework-webchat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/botframework-webchat/-/botframework-webchat-4.1.0.tgz", - "integrity": "sha512-ZvmY2iYN+VPp/myuqVRHGehgx+xtHXnv7cwn1c2nCz+/ak0WAR4D2sakxEhX4vB09t5MNUbx8GYq9GPxTI+lCA==", + "version": "0.14.3-master.72bfef9", + "resolved": "https://registry.npmjs.org/botframework-webchat/-/botframework-webchat-0.14.3-master.72bfef9.tgz", + "integrity": "sha512-rgppVWeVd43FT5O2HpoPeVkowPtGN74cAhC+ZSGGd7HiDq5UyHtR4+PISLvR9XtRy/dLTF2afubOOThPvW+44w==", "requires": { - "@babel/runtime": "^7.0.0", - "adaptivecards": "^1.0.0", - "babel-runtime": "^7.0.0-beta.3", - "botframework-directlinejs": "^0.10.0", - "botframework-webchat-component": "4.1.0", - "botframework-webchat-core": "4.1.0", - "core-js": "^2.5.7", - "markdown-it": "^8.4.2", - "markdown-it-for-inline": "^0.1.1", - "memoize-one": "^4.0.2", - "microsoft-speech-browser-sdk": "^0.0.12", - "react": "^16.5.0", - "react-dom": "^16.5.0", - "react-redux": "^5.0.7", - "redux": "^4.0.0", - "sanitize-html": "^1.19.0", - "store": "^2.0.12", - "url-search-params-polyfill": "^5.0.0", - "web-speech-cognitive-services": "^3.0.0", - "whatwg-fetch": "^3.0.0" + "adaptivecards": "1.0.0", + "bluebird": "^3.5.1", + "botframework-directlinejs": "0.9.17", + "core-js": "2.4.1", + "jspeech": "^0.1.1", + "markdown-it": "8.3.1", + "microsoft-speech-browser-sdk": "0.0.12", + "react-redux": "5.0.5", + "redux": "3.7.2", + "redux-observable": "0.13.0", + "rxjs": "5.4.3", + "simple-update-in": "^1.2.0", + "tslib": "1.7.1" }, "dependencies": { - "whatwg-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", - "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + "botframework-directlinejs": { + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/botframework-directlinejs/-/botframework-directlinejs-0.9.17.tgz", + "integrity": "sha512-Ib2BhalcxbKi5rtCO1OX5VhWkfb/rN/voq5823hCIvd/p+GCI/G9jEnxzRcfFWmLUk6hWYjwUnKarIeg93YiMA==", + "requires": { + "rxjs": "^5.0.3" + } + }, + "core-js": { + "version": "2.4.1", + "resolved": "http://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=" + }, + "rxjs": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz", + "integrity": "sha512-fSNi+y+P9ss+EZuV0GcIIqPUK07DEaMRUtLJvdcvMyFjc9dizuDjere+A4V7JrLGnm9iCc+nagV/4QdMTkqC4A==", + "requires": { + "symbol-observable": "^1.0.1" + } } } }, - "botframework-webchat-component": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/botframework-webchat-component/-/botframework-webchat-component-4.1.0.tgz", - "integrity": "sha512-kTgAxA1zMwblg/3/cE2GjWX9ff+NodCnVMcPm7Daas5iik74orqoPUJKARcKNH+iRihlfE/2oeoj06Gl0CB1Gw==", - "requires": { - "adaptivecards": "^1.0.0", - "botframework-webchat-core": "4.1.0", - "bytes": "^3.0.0", - "classnames": "^2.2.6", - "glamor": "^2.20.40", - "memoize-one": "^3.1.1", - "react-dictate-button": "^1.1.3", - "react-film": "^1.1.1", - "react-redux": "^5.0.7", - "react-say": "^1.1.1", - "react-scroll-to-bottom": "^1.2.0", - "redux": "^4.0.0", - "sanitize-html": "^1.18.2", - "simple-update-in": "^1.3.0" - }, - "dependencies": { - "memoize-one": { - "version": "3.1.1", - "resolved": "http://registry.npmjs.org/memoize-one/-/memoize-one-3.1.1.tgz", - "integrity": "sha512-YqVh744GsMlZu6xkhGslPSqSurOv6P+kLN2J3ysBZfagLcL5FdRK/0UpgLoL8hwjjEvvAVkjJZyFP+1T6p1vgA==" - } - } - }, - "botframework-webchat-core": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/botframework-webchat-core/-/botframework-webchat-core-4.1.0.tgz", - "integrity": "sha512-mPw92k0Pq2ATsx1rE3oi73oy7lqrCfzV/XFK+XjXYeiSp6PMi1Mn6B4eIrnGKZe8vLQ8Y92jt0Zgq7HUl2jqLg==", - "requires": { - "@babel/runtime": "^7.0.0-rc.2", - "jsonwebtoken": "^8.3.0", - "mime": "^2.3.1", - "redux": "^4.0.0", - "redux-promise-middleware": "^5.1.1", - "redux-saga": "^0.16.0", - "simple-update-in": "^1.3.0" - } - }, - "bowser": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz", - "integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==" - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2555,11 +2508,6 @@ "isarray": "^1.0.0" } }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -2883,11 +2831,6 @@ } } }, - "classnames": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", - "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" - }, "clean-css": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", @@ -3234,6 +3177,16 @@ "sha.js": "^2.4.8" } }, + "create-react-class": { + "version": "15.6.3", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", + "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", + "requires": { + "fbjs": "^0.8.9", + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -3290,15 +3243,6 @@ } } }, - "css-in-js-utils": { - "version": "2.0.1", - "resolved": "http://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz", - "integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==", - "requires": { - "hyphenate-style-name": "^1.0.2", - "isobject": "^3.0.1" - } - }, "css-loader": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-1.0.0.tgz", @@ -3912,14 +3856,6 @@ "webidl-conversions": "^4.0.2" } }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "requires": { - "domelementtype": "1" - } - }, "domutils": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", @@ -3988,14 +3924,6 @@ "safer-buffer": "^2.1.0" } }, - "ecdsa-sig-formatter": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", - "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4503,21 +4431,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, - "event-as-promise": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/event-as-promise/-/event-as-promise-1.0.5.tgz", - "integrity": "sha512-z/WIlyou7oTvXBjm5YYjfklr2d8gUWtx8b5GAcrIs1n1D35f7NIK0CrcYSXbY3VYikG9bUan+wScPyGXL/NH4A==" - }, "eventemitter3": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", "integrity": "sha1-CQtNbNvWRe0Qv3UNS1QHlC17oWM=" }, - "events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", - "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==" - }, "eventsource": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz", @@ -6168,18 +6086,6 @@ "assert-plus": "^1.0.0" } }, - "glamor": { - "version": "2.20.40", - "resolved": "https://registry.npmjs.org/glamor/-/glamor-2.20.40.tgz", - "integrity": "sha512-DNXCd+c14N9QF8aAKrfl4xakPk5FdcFwmH7sD0qnC0Pr7xoZ5W9yovhUrY/dJc3psfGGXC58vqQyRtuskyUJxA==", - "requires": { - "fbjs": "^0.8.12", - "inline-style-prefixer": "^3.0.6", - "object-assign": "^4.1.1", - "prop-types": "^15.5.10", - "through": "^2.3.8" - } - }, "glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", @@ -6504,12 +6410,9 @@ "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" }, "hoist-non-react-statics": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.1.0.tgz", - "integrity": "sha512-MYcYuROh7SBM69xHGqXEwQqDux34s9tz+sCnxJmN18kgWh6JFdTw/5YdZtqsOdZJXddE/wUpCzfEdDrJj8p0Iw==", - "requires": { - "react-is": "^16.3.2" - } + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", + "integrity": "sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs=" }, "home-or-tmp": { "version": "2.0.0", @@ -6628,19 +6531,6 @@ "util.promisify": "1.0.0" } }, - "htmlparser2": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz", - "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==", - "requires": { - "domelementtype": "^1.3.0", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.0.6" - } - }, "http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -6977,11 +6867,6 @@ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, - "hyphenate-style-name": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz", - "integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es=" - }, "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", @@ -7097,15 +6982,6 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=" }, - "inline-style-prefixer": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz", - "integrity": "sha1-hVG45bTVcyROZqNLBPfTIHaitTQ=", - "requires": { - "bowser": "^1.7.3", - "css-in-js-utils": "^2.0.0" - } - }, "inquirer": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", @@ -8280,21 +8156,10 @@ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" }, - "jsonwebtoken": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz", - "integrity": "sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag==", - "requires": { - "jws": "^3.1.5", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1" - } + "jspeech": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jspeech/-/jspeech-0.1.1.tgz", + "integrity": "sha1-n+wcnRGeFJBajeqCpQWvBs+Sg1k=" }, "jsprim": { "version": "1.4.1", @@ -8315,25 +8180,6 @@ "array-includes": "^3.0.3" } }, - "jwa": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", - "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.10", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", - "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", - "requires": { - "jwa": "^1.1.5", - "safe-buffer": "^5.0.1" - } - }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -8394,9 +8240,9 @@ } }, "linkify-it": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz", - "integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.1.0.tgz", + "integrity": "sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg==", "requires": { "uc.micro": "^1.0.1" } @@ -8503,6 +8349,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "integrity": "sha1-s56mIp72B+zYniyN8SU2iRysm40=" }, + "lodash-es": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz", + "integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==" + }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", @@ -8513,66 +8364,16 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" - }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, - "lodash.escaperegexp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=" - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, - "lodash.mergewith": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", - "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -8682,22 +8483,17 @@ } }, "markdown-it": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz", - "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.3.1.tgz", + "integrity": "sha1-L0tiKUjM3Bk9ZvPKLUMSWsSscyM=", "requires": { "argparse": "^1.0.7", "entities": "~1.1.1", "linkify-it": "^2.0.0", "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "uc.micro": "^1.0.3" } }, - "markdown-it-for-inline": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/markdown-it-for-inline/-/markdown-it-for-inline-0.1.1.tgz", - "integrity": "sha1-Q18jFvW15o4UUM+iJC8rjVmtx18=" - }, "math-random": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", @@ -8736,11 +8532,6 @@ "mimic-fn": "^1.0.0" } }, - "memoize-one": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.3.tgz", - "integrity": "sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw==" - }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -11569,16 +11360,6 @@ } } }, - "react-dictate-button": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/react-dictate-button/-/react-dictate-button-1.1.3.tgz", - "integrity": "sha512-4Od7sTAEIvPKpZbpy1tCv7qqfRNwLnZom9hrLnNvr1FIWjSOYC94ekAP8S5kevqnVaFvSqwFisCmjd3uLmvsrA==", - "requires": { - "classnames": "^2.2.6", - "glamor": "^2.20.40", - "memoize-one": "^4.0.0" - } - }, "react-dom": { "version": "16.6.3", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", @@ -11606,15 +11387,6 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.0.tgz", "integrity": "sha512-akMy/BQT5m1J3iJIHkSb4qycq2wzllWsmmolaaFVnb+LPV9cIJ/nTud40ZsiiT0H3P+/wXIdbjx2fzF61OaeOQ==" }, - "react-film": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/react-film/-/react-film-1.1.1.tgz", - "integrity": "sha512-el6Uw1yDeiu7cKyyuL0eupAKO6vmmTX8un+5btvyYlzD15J1JWLp+Q4rKR3JfcJ2ZM8LKPhAHMfCUGV8t9lVMQ==", - "requires": { - "classnames": "^2.2.6", - "glamor": "^2.20.40" - } - }, "react-helmet": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-5.2.0.tgz", @@ -11626,11 +11398,6 @@ "react-side-effect": "^1.1.0" } }, - "react-is": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.1.tgz", - "integrity": "sha512-wOKsGtvTMYs7WAscmwwdM8sfRRvE17Ym30zFj3n37Qx5tHRfhenPKEPILHaHob6WoLFADmQm1ZNrE5xMCM6sCw==" - }, "react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -11655,28 +11422,17 @@ } }, "react-redux": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.1.tgz", - "integrity": "sha512-LE7Ned+cv5qe7tMV5BPYkGQ5Lpg8gzgItK07c67yHvJ8t0iaD9kPFPAli/mYkiyJYrs2pJgExR2ZgsGqlrOApg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.5.tgz", + "integrity": "sha1-+OjHsjlCJXblLWt9sGQ5RpvphGo=", "requires": { - "@babel/runtime": "^7.1.2", - "hoist-non-react-statics": "^3.1.0", - "invariant": "^2.2.4", + "create-react-class": "^15.5.3", + "hoist-non-react-statics": "^1.0.3", + "invariant": "^2.0.0", + "lodash": "^4.2.0", + "lodash-es": "^4.2.0", "loose-envify": "^1.1.0", - "prop-types": "^15.6.1", - "react-is": "^16.6.0", - "react-lifecycles-compat": "^3.0.0" - } - }, - "react-say": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/react-say/-/react-say-1.1.1.tgz", - "integrity": "sha512-R+XfFQjpwlD48miC0AAb6DZDq4h5DkKFnqQFi/83wpO73isfYx+wuPU6PodzRGP4rF+/6nibnKc2V328ifiALA==", - "requires": { - "classnames": "^2.2.6", - "event-as-promise": "^1.0.3", - "glamor": "^2.20.40", - "memoize-one": "^4.0.0" + "prop-types": "^15.5.10" } }, "react-scripts": { @@ -11749,17 +11505,6 @@ } } }, - "react-scroll-to-bottom": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/react-scroll-to-bottom/-/react-scroll-to-bottom-1.2.0.tgz", - "integrity": "sha512-/tAaVVnSpnMtv4PVIxmxOFh45g6PcUihIK6zH17WTPkqreRFRrbCBnC7ieVu2isV68Zjee98cmBMGgKapI9fCw==", - "requires": { - "classnames": "^2.2.6", - "glamor": "^2.20.40", - "memoize-one": "^4.0.2", - "simple-update-in": "^1.4.0" - } - }, "react-side-effect": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-1.1.5.tgz", @@ -11837,16 +11582,6 @@ } } }, - "readable-stream": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz", - "integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -12146,22 +11881,16 @@ } }, "redux": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz", - "integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", + "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", "requires": { - "loose-envify": "^1.4.0", - "symbol-observable": "^1.2.0" + "lodash": "^4.2.1", + "lodash-es": "^4.2.1", + "loose-envify": "^1.1.0", + "symbol-observable": "^1.0.3" }, "dependencies": { - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -12169,15 +11898,10 @@ } } }, - "redux-promise-middleware": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/redux-promise-middleware/-/redux-promise-middleware-5.1.1.tgz", - "integrity": "sha512-YC1tiheU28Hgmtu5HHMLiuveLgjL1aCJWsSnwquMiZBcj5i/J9qVLt6vgOnb0Gz37y4deJ/rjiNt7l6Dh+Z8lA==" - }, - "redux-saga": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-0.16.2.tgz", - "integrity": "sha512-iIjKnRThI5sKPEASpUvySemjzwqwI13e3qP7oLub+FycCRDysLSAOwt958niZW6LhxfmS6Qm1BzbU70w/Koc4w==" + "redux-observable": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/redux-observable/-/redux-observable-0.13.0.tgz", + "integrity": "sha1-NbJsLNu3HkmbMcqZYdoFgcKXOQk=" }, "regenerate": { "version": "1.4.0", @@ -12555,20 +12279,10 @@ "aproba": "^1.1.1" } }, - "rxjs": { + "rxjs-compat": { "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", - "requires": { - "tslib": "^1.9.0" - }, - "dependencies": { - "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" - } - } + "resolved": "https://registry.npmjs.org/rxjs-compat/-/rxjs-compat-6.3.3.tgz", + "integrity": "sha512-caGN7ixiabHpOofginKEquuHk7GgaCrC7UpUQ9ZqGp80tMc68msadOeP/2AKy2R4YJsT1+TX5GZCtxO82qWkyA==" }, "safe-buffer": { "version": "5.1.2", @@ -12867,23 +12581,6 @@ } } }, - "sanitize-html": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.19.1.tgz", - "integrity": "sha512-zNYr6FvBn4bZukr9x2uny6od/9YdjCLwF+FqxivqI0YOt/m9GIxfX+tWhm52tBAPUXiTTb4bJTGVagRz5b06bw==", - "requires": { - "chalk": "^2.3.0", - "htmlparser2": "^3.9.0", - "lodash.clonedeep": "^4.5.0", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.mergewith": "^4.6.0", - "postcss": "^6.0.14", - "srcset": "^1.0.0", - "xtend": "^4.0.0" - } - }, "sass-loader": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", @@ -13539,15 +13236,6 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, - "srcset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz", - "integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=", - "requires": { - "array-uniq": "^1.0.2", - "number-is-nan": "^1.0.0" - } - }, "sshpk": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", @@ -13611,11 +13299,6 @@ "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, - "store": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/store/-/store-2.0.12.tgz", - "integrity": "sha1-jFNOKguDH3K3X8XxEZhXxE711ZM=" - }, "stream-browserify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", @@ -14531,11 +14214,6 @@ "requires-port": "^1.0.0" } }, - "url-search-params-polyfill": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/url-search-params-polyfill/-/url-search-params-polyfill-5.0.0.tgz", - "integrity": "sha512-+SCD22QJp4UnqPOI5UTTR0Ljuh8cHbjEf1lIiZrZ8nHTlTixqwVsVQTSfk5vrmDz7N09/Y+ka5jQr0ff35FnQQ==" - }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -14675,18 +14353,6 @@ "minimalistic-assert": "^1.0.0" } }, - "web-speech-cognitive-services": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/web-speech-cognitive-services/-/web-speech-cognitive-services-3.0.0.tgz", - "integrity": "sha512-mkAsnL1pzGTn4TXKxFNwcgYL5hhh/NyvZtUghsFk6cGuP2OHebG8K04uJlcwpZnzp18HSWyi3YcYXXGU1tAZJg==", - "requires": { - "@babel/runtime": "^7.1.2", - "event-as-promise": "^1.0.5", - "events": "^3.0.0", - "memoize-one": "^4.0.0", - "simple-update-in": "^1.2.0" - } - }, "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", diff --git a/packages/default.gbui/package.json b/packages/default.gbui/package.json index 1366ff36..80ae13d3 100644 --- a/packages/default.gbui/package.json +++ b/packages/default.gbui/package.json @@ -2,24 +2,27 @@ "name": "default.gbui", "version": "0.0.12", "private": false, + "repository": "https://github.com/pragmatismo-io/BotServer", + "description": "Default web interface for General Bots open-core", "license": "AGPL-3.0", "homepage": ".", "dependencies": { - "ajv": "^6.5.5", + "ajv": "^6.5.4", "botframework-directlinejs": "^0.10.0", - "botframework-webchat": "4.1.0", + "botframework-webchat": "0.14.3-master.72bfef9", "deep-extend": "^0.6.0", "fetch": "^1.1.0", "msal": "^0.2.3", "powerbi-client": "^2.6.5", - "react": "^16.6.3", - "react-dom": "^16.6.3", + "react": "^16.6.0", + "react-dom": "^16.6.0", "react-helmet": "^5.2.0", - "react-player": "^1.7.0", + "react-player": "^1.6.6", "react-powerbi": "^0.3.1", "react-scripts": "^2.1.1", "react-transition-group": "^2.5.0", - "rxjs": "^6.3.3", + "rxjs": "^5.5.12", + "rxjs-compat": "^6.3.3", "url-join": "^4.0.0" }, "scripts": { diff --git a/packages/default.gbui/src/GBUIApp.js b/packages/default.gbui/src/GBUIApp.js index 191989c7..a5044e64 100644 --- a/packages/default.gbui/src/GBUIApp.js +++ b/packages/default.gbui/src/GBUIApp.js @@ -318,9 +318,6 @@ class GBUIApp extends React.Component { gbCss = ; function getToken() { - return new Promise((resolve: any, reject: any): any => { - resolve(token); - }); } // speechOptions = { diff --git a/packages/kb.gbapp/dialogs/AskDialog.ts b/packages/kb.gbapp/dialogs/AskDialog.ts index 076f965b..e911b7f8 100644 --- a/packages/kb.gbapp/dialogs/AskDialog.ts +++ b/packages/kb.gbapp/dialogs/AskDialog.ts @@ -149,6 +149,7 @@ export class AskDialog extends IGBDialog { return await step.replaceDialog('/ask', { isReturning: true }); } else { + // Second time running Search, now with no filter. const resultsB = await service.ask( @@ -161,10 +162,10 @@ export class AskDialog extends IGBDialog { // If there is some result, answer immediately. if (resultsB && resultsB.answer) { + // Saves some context info. const user = await min.userProfile.get(step.context, {}); - user.isAsking = false; user.lastQuestionId = resultsB.questionId; await min.userProfile.set(step.context, user); diff --git a/packages/whatsapp.gblib/index.ts b/packages/whatsapp.gblib/index.ts index 41829f90..5bc37b89 100644 --- a/packages/whatsapp.gblib/index.ts +++ b/packages/whatsapp.gblib/index.ts @@ -59,7 +59,7 @@ export class GBWhatsappPackage implements IGBPackage { // Only loads engine if it is defined on services.json. - if (min.instance.whatsappBotKey != '') { + if (min.instance.whatsappBotKey) { this.channel = new WhatsappDirectLine(min.botId, min.instance.whatsappBotKey, min.instance.whatsappServiceKey, min.instance.whatsappServiceNumber, min.instance.whatsappServiceUrl, min.instance.whatsappServiceWebhookUrl); } diff --git a/src/app.ts b/src/app.ts index 1c1b897a..fb2b1afb 100644 --- a/src/app.ts +++ b/src/app.ts @@ -125,10 +125,15 @@ export class GBServer { await core.saveInstance(fullInstance); let instances: GuaribasInstance[] = await core.loadAllInstances(core, azureDeployer, proxyAddress); instances = await core.ensureInstances(instances, bootInstance, core); - + if(!bootInstance) { + bootInstance = instances[0]; + } const minService: GBMinService = new GBMinService(core, conversationalService, adminService, deployer); - await minService.buildMin(server, appPackages, instances, deployer); + await minService.buildMin(bootInstance, server, appPackages, instances, deployer); + + logger.info(`Preparing default.gbui (it may take some additional time for the first time)...`); + deployer.installDefaultGBUI(); logger.info(`The Bot Server is in RUNNING mode...`); core.openBrowserInDevelopment(); -- 2.39.5 From f0a0cd36be69c36b0295065caad3d43cb7620f6f Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (pragmatismo.io)" Date: Fri, 30 Nov 2018 17:30:48 -0200 Subject: [PATCH 06/13] feat(gbdialog): The first VBA code is run. --- packages/core.gbapp/services/GBAPIService.ts | 9 ++- packages/core.gbapp/services/GBMinService.ts | 3 +- packages/core.gbapp/services/GBVMService.ts | 40 ++++++++--- packages/core.gbapp/services/TSCompiler.ts | 74 ++++++++++++++++++++ packages/default.gbdialog/bot.vbs | 12 ++-- packages/default.gbdialog/bot.vbs.js | 40 ----------- 6 files changed, 118 insertions(+), 60 deletions(-) create mode 100644 packages/core.gbapp/services/TSCompiler.ts delete mode 100644 packages/default.gbdialog/bot.vbs.js diff --git a/packages/core.gbapp/services/GBAPIService.ts b/packages/core.gbapp/services/GBAPIService.ts index e2d37ba6..67637937 100644 --- a/packages/core.gbapp/services/GBAPIService.ts +++ b/packages/core.gbapp/services/GBAPIService.ts @@ -47,14 +47,15 @@ export class DialogClass { this.min = min; } - public async hear(text: string): Promise { - // await this.context.beginDialog('textPrompt', text); + public hear(text: string) { + // TODO: await this.context.beginDialog('textPrompt', text); } public talk(text: string) { this.context.sendActivity(text); } + /** * Generic function to call any REST API. */ @@ -66,5 +67,7 @@ export class DialogClass { /** * Generic function to call any REST API. */ - public post(url: string, data) {} + public post(url: string, data) { + + } } diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 9d646020..9266d40b 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -484,7 +484,8 @@ export class GBMinService { // Checks for /admin request. if (context.activity.text === 'vba') { min.sandbox.context = context; - min.sandbox['chat'](min.sandbox); + min.sandbox['bot'].bind(min.sandbox); + min.sandbox['bot'](); } else if (context.activity.text === 'admin') { await step.beginDialog('/admin'); diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts index 4a6e480a..bc8e096b 100644 --- a/packages/core.gbapp/services/GBVMService.ts +++ b/packages/core.gbapp/services/GBVMService.ts @@ -36,10 +36,12 @@ import { GBMinInstance, IGBCoreService } from 'botlib'; import * as fs from 'fs'; import { DialogClass } from './GBAPIService'; import { GBDeployer } from './GBDeployer'; +import { TSCompiler } from './TSCompiler'; const util = require('util'); const logger = require('../../../src/logger'); const vm = require('vm'); const UrlJoin = require('url-join'); +const vb2ts = require('vbscript-to-typescript/dist/converter'); /** * @fileoverview Virtualization services for emulation of BASIC. @@ -55,16 +57,38 @@ export class GBVMService implements IGBCoreService { deployer: GBDeployer, localPath: string ): Promise { + const path = 'packages/default.gbdialog'; + const file = 'bot.vbs'; + const source = UrlJoin(path, file); - localPath = UrlJoin(localPath, 'bot.vbs.js'); - const code: string = fs.readFileSync(localPath, 'utf8'); - const sandbox: DialogClass = new DialogClass(min); - const context = vm.createContext(sandbox); - vm.runInContext(code, context); + // Example when handled through fs.watch() listener + fs.watchFile(source, async (curr, prev) => { + await this.run(source, path, localPath, min, deployer, filename); + }); + await this.run(source, path, localPath, min, deployer, filename); + } - await deployer.deployScriptToStorage(min.instanceId, filename); - logger.info(`[GBVMService] Finished loading of ${filename}`); + private async run(source: any, path: string, localPath: string, min: any, deployer: GBDeployer, filename: string) { + // Converts VBS into TS. - min.sandbox = sandbox; + vb2ts.convertFile(source); + + // Convert TS into JS. + const tsfile = `bot.ts`; + const tsc = new TSCompiler(); + tsc.compile([UrlJoin(path, tsfile)]); + // Run JS into the GB context. + const jsfile = `bot.js`; + localPath = UrlJoin(path, jsfile); + if (fs.existsSync(localPath)) { + let code: string = fs.readFileSync(localPath, 'utf8'); + code = code.replace(/^.*exports.*$/gm, ''); + const sandbox: DialogClass = new DialogClass(min); + const context = vm.createContext(sandbox); + vm.runInContext(code, context); + min.sandbox = sandbox; + await deployer.deployScriptToStorage(min.instanceId, filename); + logger.info(`[GBVMService] Finished loading of ${filename}`); + } } } diff --git a/packages/core.gbapp/services/TSCompiler.ts b/packages/core.gbapp/services/TSCompiler.ts new file mode 100644 index 00000000..60bbe85b --- /dev/null +++ b/packages/core.gbapp/services/TSCompiler.ts @@ -0,0 +1,74 @@ +/*****************************************************************************\ +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | +| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | +| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | +| | | ( )_) | | +| (_) \___/' | +| | +| 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. | +| | +\*****************************************************************************/ + +/** + * @fileoverview General Bots server core. + */ + +'use strict'; + +import * as ts from 'typescript'; +const logger = require('../../../src/logger'); + +export class TSCompiler { + public compile( + fileNames: string[], + options: ts.CompilerOptions = { + noEmitOnError: false, + noImplicitAny: true, + target: ts.ScriptTarget.ES5, + module: ts.ModuleKind.None, + moduleResolution: ts.ModuleResolutionKind.Classic, + noEmitHelpers: true, + maxNodeModuleJsDepth: 0, + esModuleInterop: false + + } + ) { + const program = ts.createProgram(fileNames, options); + const emitResult = program.emit(); + + const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); + + allDiagnostics.forEach(diagnostic => { + if (diagnostic.file) { + const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); + const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); + logger.error(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`); + } else { + logger.error(`${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`); + } + }); + + return emitResult; + } +} diff --git a/packages/default.gbdialog/bot.vbs b/packages/default.gbdialog/bot.vbs index 226e1358..c902c27b 100644 --- a/packages/default.gbdialog/bot.vbs +++ b/packages/default.gbdialog/bot.vbs @@ -1,5 +1,4 @@ <% - '**************************************************************************** ' ( )_ _ ' _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ @@ -31,13 +30,10 @@ ' our trademarks remain entirely with us. ' '**************************************************************************** - -function ICanSendEmails() - bot.say ("Please, what's your e-mail address?") - email = bot.expectEmail() - bot.sendMail (email, "Hello", "I'm sending a General Bots VBA e-mail.") - -end function +this.talk ("Please, what's your e-mail address?") +let email = this.hear() +this.talk("Thanks, sending e-mail to: " + email); +this.sendEmail(email, "Message from VBA Bot", "Yes, I can send e-mails."); %> \ No newline at end of file diff --git a/packages/default.gbdialog/bot.vbs.js b/packages/default.gbdialog/bot.vbs.js deleted file mode 100644 index 22f66cb4..00000000 --- a/packages/default.gbdialog/bot.vbs.js +++ /dev/null @@ -1,40 +0,0 @@ - -function chat(bot) { - - //**************************************************************************** - // ( )_ _ - // _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ - // ( //_`\ ( //__)///_` ) ///_ `\/// _ ` _ `\ ///_` )| | | |///,__)/// _ `\ ///_`\ - // | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) - // | ,__///(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/// - // | | ( )_) | - // (_) \___/// - // - // 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. - // - //**************************************************************************** - - bot.talk('Please, what is your e-mail?'); - //email = bot.expectEmail(); - //bot.talk('Thanks, sending e-mail to: ' + email); - //bot.sendEmail(to, 'Message from VBA Bot', 'Yes, I can send e-mails.'); -} -- 2.39.5 From ce04290fcd95d25413ea69d90ae53b48392deb23 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (pragmatismo.io)" Date: Sat, 1 Dec 2018 14:38:08 -0200 Subject: [PATCH 07/13] fix(gbdialog): Trying to save context. --- .gitignore | 2 + package-lock.json | 18 +-- package.json | 2 +- packages/core.gbapp/services/GBAPIService.ts | 20 +-- packages/core.gbapp/services/GBCoreService.ts | 38 +++-- packages/core.gbapp/services/GBMinService.ts | 147 +++++------------- packages/core.gbapp/services/GBVMService.ts | 4 +- .../dialogs/FeedbackDialog.ts | 69 ++++---- packages/default.gbdialog/bot.vbs | 1 - src/app.ts | 1 + 10 files changed, 128 insertions(+), 174 deletions(-) diff --git a/.gitignore b/.gitignore index d8b6f209..e9430015 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ /packages/default.gbui/node_modules /tmp /work +/packages/default.gbdialog/bot.js +/packages/default.gbdialog/bot.ts diff --git a/package-lock.json b/package-lock.json index 10f5ac14..b7d49f64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3299,9 +3299,9 @@ } }, "@types/node": { - "version": "9.6.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.36.tgz", - "integrity": "sha512-Fbw+AdRLL01vv7Rk7bYaNPecqmKoinJHGbpKnDpbUZmUj/0vj3nLqPQ4CNBzr3q2zso6Cq/4jHoCAdH78fvJrw==" + "version": "9.6.40", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.40.tgz", + "integrity": "sha512-M3HHoXXndsho/sTbQML2BJr7/uwNhMg8P0D4lb+UsM65JQZx268faiz9hKpY4FpocWqpwlLwa8vevw8hLtKjOw==" }, "async": { "version": "1.5.2", @@ -3548,9 +3548,9 @@ } }, "botlib": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/botlib/-/botlib-0.1.6.tgz", - "integrity": "sha512-NG/F7Yxhx/duehDzjI78mYMonZ03d+Gx+WtRmqj7TimGcc4xK1y4m7s+n9jt0XR+GYhj0jcA5uKA2LqDRtq22A==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/botlib/-/botlib-0.1.7.tgz", + "integrity": "sha512-vp8htUT/AL+pYXdiy9s13HFLbygCUorELw1dg1FEqHsfXQOoTlUvr52rNEeKikHvNYaXEEHqhv2F4pLRvEHIYw==", "requires": { "async": "2.6.1", "botbuilder": "4.1.3", @@ -3607,9 +3607,9 @@ } }, "@types/node": { - "version": "9.6.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.36.tgz", - "integrity": "sha512-Fbw+AdRLL01vv7Rk7bYaNPecqmKoinJHGbpKnDpbUZmUj/0vj3nLqPQ4CNBzr3q2zso6Cq/4jHoCAdH78fvJrw==" + "version": "9.6.40", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.40.tgz", + "integrity": "sha512-M3HHoXXndsho/sTbQML2BJr7/uwNhMg8P0D4lb+UsM65JQZx268faiz9hKpY4FpocWqpwlLwa8vevw8hLtKjOw==" }, "botbuilder": { "version": "4.1.3", diff --git a/package.json b/package.json index c1069077..6e49b5cf 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "botbuilder-choices": "^4.0.0-preview1.2", "botbuilder-dialogs": "^4.1.5", "botbuilder-prompts": "^4.0.0-preview1.2", - "botlib": "0.1.6", + "botlib": "^0.1.7", "chai": "4.2.0", "child_process": "^1.0.2", "chokidar": "2.0.4", diff --git a/packages/core.gbapp/services/GBAPIService.ts b/packages/core.gbapp/services/GBAPIService.ts index 67637937..b494ea19 100644 --- a/packages/core.gbapp/services/GBAPIService.ts +++ b/packages/core.gbapp/services/GBAPIService.ts @@ -33,7 +33,9 @@ 'use strict'; import { TurnContext } from 'botbuilder'; +import { WaterfallStepContext } from 'botbuilder-dialogs'; import { GBMinInstance } from 'botlib'; +const WaitUntil = require('wait-until'); /** * @fileoverview General Bots server core. @@ -42,19 +44,21 @@ import { GBMinInstance } from 'botlib'; export class DialogClass { public min: GBMinInstance; public context: TurnContext; + public step: WaterfallStepContext; constructor(min: GBMinInstance) { this.min = min; - } - - public hear(text: string) { - // TODO: await this.context.beginDialog('textPrompt', text); } - public talk(text: string) { - this.context.sendActivity(text); + public async hear(cb) { + const id = Math.floor(Math.random() * 1000000000000); + this.min.cbMap[id] = cb; + await this.step.beginDialog('/feedback', { id: id }); } + public async talk(text: string) { + return await this.context.sendActivity(text); + } /** * Generic function to call any REST API. @@ -67,7 +71,5 @@ export class DialogClass { /** * Generic function to call any REST API. */ - public post(url: string, data) { - - } + public post(url: string, data) {} } diff --git a/packages/core.gbapp/services/GBCoreService.ts b/packages/core.gbapp/services/GBCoreService.ts index 4d0f4ca3..95b4cf86 100644 --- a/packages/core.gbapp/services/GBCoreService.ts +++ b/packages/core.gbapp/services/GBCoreService.ts @@ -184,7 +184,6 @@ export class GBCoreService implements IGBCoreService { alter: alter, force: force }); - } else { const msg = `Database synchronization is disabled.`; logger.info(msg); @@ -239,9 +238,15 @@ export class GBCoreService implements IGBCoreService { } public async ensureProxy(port): Promise { - const ngrok = require('ngrok'); - - return await ngrok.connect({ port: port }); + try { + const ngrok = require('ngrok'); + return await ngrok.connect({ port: port }); + } catch (error) { + // There are false positive from ngrok regarding to no memory, but it's just + // lack of connection. + logger.verbose(error); + throw new Error('Error connecting to remote ngrok server, please check network connection.'); + } } public async saveInstance(fullInstance: any) { @@ -313,7 +318,6 @@ export class GBCoreService implements IGBCoreService { } public loadSysPackages(core: GBCoreService) { - // NOTE: if there is any code before this line a semicolon // will be necessary before this line. // Loads all system packages. @@ -344,19 +348,19 @@ export class GBCoreService implements IGBCoreService { public async createBootInstance(core: GBCoreService, azureDeployer: AzureDeployerService, proxyAddress: string) { let instance: IGBInstance; - logger.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`); - try { - instance = await azureDeployer.deployFarm(proxyAddress); - } catch (error) { - logger.warn( - `In case of error, please cleanup any infrastructure objects + logger.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`); + try { + instance = await azureDeployer.deployFarm(proxyAddress); + } catch (error) { + logger.warn( + `In case of error, please cleanup any infrastructure objects created during this procedure and .env before running again.` - ); - throw error; - } - core.writeEnv(instance); - logger.info(`File .env written, starting General Bots...`); - GBConfigService.init(); + ); + throw error; + } + core.writeEnv(instance); + logger.info(`File .env written, starting General Bots...`); + GBConfigService.init(); return instance; } diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 9266d40b..9e3ea2cf 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -43,21 +43,9 @@ const logger = require('../../../src/logger'); const request = require('request-promise-native'); const AuthenticationContext = require('adal-node').AuthenticationContext; -import { - AutoSaveStateMiddleware, - BotFrameworkAdapter, - ConversationState, - MemoryStorage, - UserState -} from 'botbuilder'; +import { AutoSaveStateMiddleware, BotFrameworkAdapter, ConversationState, MemoryStorage, UserState } from 'botbuilder'; -import { - GBMinInstance, - IGBAdminService, - IGBConversationalService, - IGBCoreService, - IGBPackage -} from 'botlib'; +import { GBMinInstance, IGBAdminService, IGBConversationalService, IGBCoreService, IGBPackage } from 'botlib'; import { GBAnalyticsPackage } from '../../analytics.gblib'; import { GBCorePackage } from '../../core.gbapp'; import { GBCustomerSatisfactionPackage } from '../../customer-satisfaction.gbapp'; @@ -117,10 +105,7 @@ export class GBMinService { // Serves default UI on root address '/'. const uiPackage = 'default.gbui'; - server.use( - '/', - express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')) - ); + server.use('/', express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build'))); Promise.all( instances.map(async instance => { @@ -136,7 +121,7 @@ export class GBMinService { // Returns the instance object to clients requesting bot info. let botId = req.params.botId; - if (botId === '[default]'){ + if (botId === '[default]') { botId = bootInstance.botId; } @@ -171,15 +156,12 @@ export class GBMinService { // Build bot adapter. - const { min, adapter, conversationState } = await this.buildBotAdapter( - instance - ); + const { min, adapter, conversationState } = await this.buildBotAdapter(instance); // Install default VBA module. deployer.deployPackageFromLocalPath(min, 'packages/default.gbdialog'); - // Call the loadBot context.activity for all packages. this.invokeLoadBot(appPackages, min, server); @@ -188,32 +170,17 @@ export class GBMinService { const url = `/api/messages/${instance.botId}`; server.post(url, async (req, res) => { - return this.receiver( - adapter, - req, - res, - conversationState, - min, - instance, - appPackages - ); + return await this.receiver(adapter, req, res, conversationState, min, instance, appPackages); }); - logger.info( - `GeneralBots(${instance.engineName}) listening on: ${url}.` - ); + logger.info(`GeneralBots(${instance.engineName}) listening on: ${url}.`); // Serves individual URL for each bot user interface. const uiUrl = `/${instance.botId}`; - server.use( - uiUrl, - express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')) - ); + server.use(uiUrl, express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build'))); logger.info(`Bot UI ${uiPackage} accessible at: ${uiUrl}.`); - const state = `${instance.instanceId}${Math.floor( - Math.random() * 1000000000 - )}`; + const state = `${instance.instanceId}${Math.floor(Math.random() * 1000000000)}`; // Clients get redirected here in order to create an OAuth authorize url and redirect them to AAD. // There they will authenticate and give their consent to allow this app access to @@ -226,9 +193,7 @@ export class GBMinService { ); authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${ min.instance.authenticatorClientId - }&redirect_uri=${min.instance.botEndpoint}/${ - min.instance.botId - }/token`; + }&redirect_uri=${min.instance.botEndpoint}/${min.instance.botId}/token`; res.redirect(authorizationUrl); }); @@ -238,23 +203,16 @@ export class GBMinService { // access token that can be used to access the user owned resource. server.get(`/${min.instance.botId}/token`, async (req, res) => { - const state = await min.adminService.getValue( - min.instance.instanceId, - 'AntiCSRFAttackState' - ); + const state = await min.adminService.getValue(min.instance.instanceId, 'AntiCSRFAttackState'); if (req.query.state !== state) { - const msg = - 'WARNING: state field was not provided as anti-CSRF token'; + const msg = 'WARNING: state field was not provided as anti-CSRF token'; logger.error(msg); throw new Error(msg); } const authenticationContext = new AuthenticationContext( - UrlJoin( - min.instance.authenticatorAuthorityHostUrl, - min.instance.authenticatorTenant - ) + UrlJoin(min.instance.authenticatorAuthorityHostUrl, min.instance.authenticatorTenant) ); const resource = 'https://graph.microsoft.com'; @@ -271,47 +229,16 @@ export class GBMinService { logger.error(msg); res.send(msg); } else { - await this.adminService.setValue( - instance.instanceId, - 'refreshToken', - token.refreshToken - ); - await this.adminService.setValue( - instance.instanceId, - 'accessToken', - token.accessToken - ); - await this.adminService.setValue( - instance.instanceId, - 'expiresOn', - token.expiresOn.toString() - ); - await this.adminService.setValue( - instance.instanceId, - 'AntiCSRFAttackState', - null - ); + await this.adminService.setValue(instance.instanceId, 'refreshToken', token.refreshToken); + await this.adminService.setValue(instance.instanceId, 'accessToken', token.accessToken); + await this.adminService.setValue(instance.instanceId, 'expiresOn', token.expiresOn.toString()); + await this.adminService.setValue(instance.instanceId, 'AntiCSRFAttackState', null); res.redirect(min.instance.botEndpoint); } } ); }); - - // Setups handlers. - // send: function (context.activity, next) { - // logger.info( - // `[SND]: ChannelID: ${context.activity.address.channelId}, ConversationID: ${context.activity.address.conversation}, - // Type: ${context.activity.type} `) - // this.core.createMessage( - // this.min.conversation, - // this.min.conversation.startedBy, - // context.activity.source, - // (data, err) => { - // logger.info(context.activity.source) - // } - // ) - // next() }) ); } @@ -388,8 +315,10 @@ export class GBMinService { min.conversationalService = this.conversationalService; min.adminService = this.adminService; min.instance = await this.core.loadInstance(min.botId); + min.userProfile = conversationState.createProperty('userProfile'); const dialogState = conversationState.createProperty('dialogState'); + min.dialogs = new DialogSet(dialogState); min.dialogs.add(new TextPrompt('textPrompt')); @@ -417,18 +346,18 @@ export class GBMinService { p.channel.received(req, res); }); } - }, this); + }, this); appPackages.forEach(e => { e.sysPackages = sysPackages; e.loadBot(min); - }, this); + }, this); } /** * Bot Service hook method. */ - private receiver( + private async receiver( adapter: BotFrameworkAdapter, req: any, res: any, @@ -437,8 +366,9 @@ export class GBMinService { instance: any, appPackages: any[] ) { - return adapter.processActivity(req, res, async context => { - const state = conversationState.get(context); + return await adapter.processActivity(req, res, async context => { + // Get loaded user state + const state = await conversationState.get(context); const step = await min.dialogs.createContext(context, state); step.context.activity.locale = 'en-US'; // TODO: Make dynamic. @@ -454,18 +384,16 @@ export class GBMinService { }); user.loaded = true; user.subjects = []; + user.cb = null; await min.userProfile.set(step.context, user); } logger.info( - `User>: ${context.activity.text} (${context.activity.type}, ${ - context.activity.name - }, ${context.activity.channelId}, {context.activity.value})` + `User>: ${context.activity.text} (${context.activity.type}, ${context.activity.name}, ${ + context.activity.channelId + }, {context.activity.value})` ); - if ( - context.activity.type === 'conversationUpdate' && - context.activity.membersAdded.length > 0 - ) { + if (context.activity.type === 'conversationUpdate' && context.activity.membersAdded.length > 0) { const member = context.activity.membersAdded[0]; if (member.name === 'GeneralBots') { logger.info(`Bot added to conversation, starting chat...`); @@ -480,12 +408,15 @@ export class GBMinService { } // Processes messages. + } else if (context.activity.type === 'message') { // Checks for /admin request. if (context.activity.text === 'vba') { min.sandbox.context = context; + min.sandbox.step = step; min.sandbox['bot'].bind(min.sandbox); - min.sandbox['bot'](); + + await min.sandbox['bot'](); } else if (context.activity.text === 'admin') { await step.beginDialog('/admin'); @@ -497,7 +428,9 @@ export class GBMinService { // Otherwise, continue to the active dialog in the stack. } else { - if (step.activeDialog) { + const user = await min.userProfile.get(context, {}); + + if (step.activeDialog || user.dialog) { await step.continueDialog(); } else { await step.beginDialog('/answer', { @@ -508,7 +441,6 @@ export class GBMinService { // Processes events. } else if (context.activity.type === 'event') { - // Empties dialog stack before going to the target. await step.endAll(); @@ -539,13 +471,12 @@ export class GBMinService { await step.continueDialog(); } } + await conversationState.saveChanges(context, true); } catch (error) { const msg = `ERROR: ${error.message} ${error.stack ? error.stack : ''}`; logger.error(msg); - await step.context.sendActivity( - Messages[step.context.activity.locale].very_sorry_about_error - ); + await step.context.sendActivity(Messages[step.context.activity.locale].very_sorry_about_error); await step.beginDialog('/ask', { isReturning: true }); } }); diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts index bc8e096b..d4e45bae 100644 --- a/packages/core.gbapp/services/GBVMService.ts +++ b/packages/core.gbapp/services/GBVMService.ts @@ -76,13 +76,15 @@ export class GBVMService implements IGBCoreService { // Convert TS into JS. const tsfile = `bot.ts`; const tsc = new TSCompiler(); - tsc.compile([UrlJoin(path, tsfile)]); + // TODO: tsc.compile([UrlJoin(path, tsfile)]); // Run JS into the GB context. const jsfile = `bot.js`; localPath = UrlJoin(path, jsfile); if (fs.existsSync(localPath)) { let code: string = fs.readFileSync(localPath, 'utf8'); code = code.replace(/^.*exports.*$/gm, ''); + code = code.replace(/this\./gm, 'await this.'); + code = code.replace(/function/gm, 'async function'); const sandbox: DialogClass = new DialogClass(min); const context = vm.createContext(sandbox); vm.runInContext(code, context); diff --git a/packages/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts b/packages/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts index ca62c7fc..7074ed04 100644 --- a/packages/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts +++ b/packages/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts @@ -78,35 +78,48 @@ export class FeedbackDialog extends IGBDialog { ]) ); - min.dialogs.add(new WaterfallDialog('/feedback', [ - async step => { - const locale = step.context.activity.locale; - if (step.result.fromMenu) { + min.dialogs.add( + new WaterfallDialog('/feedback', [ + async step => { + const locale = step.context.activity.locale; + await step.context.sendActivity(Messages[locale].about_suggestions); + step.activeDialog.state.cbId = step.options['id']; + + return await step.prompt('textPrompt', Messages[locale].what_about_service); + }, + async step => { + + console.log(step.result); + + // min.sandbox.context = step.context; + // min.sandbox.step = step; + + let cbId = step.activeDialog.state.cbId; + let cb = min.cbMap[cbId]; + cb.bind({ step: step, context: step.context }); + await cb(); + + // const locale = step.context.activity.locale; + // const rate = await AzureText.getSentiment( + // min.instance.textAnalyticsKey, + // min.instance.textAnalyticsEndpoint, + // min.conversationalService.getCurrentLanguage(step), + // step.result + // ); + + // if (rate > 0.5) { + // await step.context.sendActivity(Messages[locale].glad_you_liked); + // } else { + // await step.context.sendActivity(Messages[locale].we_will_improve); + + // // TODO: Record. + // } + // await step.replaceDialog('/ask', { isReturning: true }); + + return await step.next(); } - - await step.prompt('textPrompt', Messages[locale].what_about_service); - return await step.next(); - }, - async step => { - const locale = step.context.activity.locale; - const rate = await AzureText.getSentiment( - min.instance.textAnalyticsKey, - min.instance.textAnalyticsEndpoint, - min.conversationalService.getCurrentLanguage(step), - step.result - ); - - if (rate > 0.5) { - await step.context.sendActivity(Messages[locale].glad_you_liked); - } else { - await step.context.sendActivity(Messages[locale].we_will_improve); - - // TODO: Record. - } - await step.replaceDialog('/ask', { isReturning: true }); - return await step.next(); - } - ])); + ]) + ); } } diff --git a/packages/default.gbdialog/bot.vbs b/packages/default.gbdialog/bot.vbs index c902c27b..ffb1425a 100644 --- a/packages/default.gbdialog/bot.vbs +++ b/packages/default.gbdialog/bot.vbs @@ -35,5 +35,4 @@ this.talk ("Please, what's your e-mail address?") let email = this.hear() this.talk("Thanks, sending e-mail to: " + email); this.sendEmail(email, "Message from VBA Bot", "Yes, I can send e-mails."); - %> \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index fb2b1afb..01a438f7 100644 --- a/src/app.ts +++ b/src/app.ts @@ -93,6 +93,7 @@ export class GBServer { // Ensures cloud / on-premises infrastructure is setup. logger.info(`Establishing a development local proxy (ngrok)...`); + const proxyAddress: string = await core.ensureProxy(port); logger.info(`Deploying packages...`); -- 2.39.5 From 776fe035033733ea47507c831430526c85549039 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (pragmatismo.io)" Date: Sat, 1 Dec 2018 17:31:57 -0200 Subject: [PATCH 08/13] fix(gbdialog): VBA loop done - one thing left to automate: Hear wrapper. --- package-lock.json | 6 +-- package.json | 2 +- packages/core.gbapp/services/GBAPIService.ts | 6 +-- packages/core.gbapp/services/GBMinService.ts | 4 +- packages/core.gbapp/services/GBVMService.ts | 29 ++++++++++++++ .../dialogs/FeedbackDialog.ts | 39 +++++++------------ 6 files changed, 51 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index b7d49f64..29da2ceb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3548,9 +3548,9 @@ } }, "botlib": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/botlib/-/botlib-0.1.7.tgz", - "integrity": "sha512-vp8htUT/AL+pYXdiy9s13HFLbygCUorELw1dg1FEqHsfXQOoTlUvr52rNEeKikHvNYaXEEHqhv2F4pLRvEHIYw==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/botlib/-/botlib-0.1.8.tgz", + "integrity": "sha512-66v6koaZnEZBMtnFlMs6wGyD6lu+CUg0YXuPPhTEMROrJtWu25aTlCXgkxlhhuGjQvxRpkqwvP8Ta5fsAGqvPg==", "requires": { "async": "2.6.1", "botbuilder": "4.1.3", diff --git a/package.json b/package.json index 6e49b5cf..b5b86b8b 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "botbuilder-choices": "^4.0.0-preview1.2", "botbuilder-dialogs": "^4.1.5", "botbuilder-prompts": "^4.0.0-preview1.2", - "botlib": "^0.1.7", + "botlib": "0.1.8", "chai": "4.2.0", "child_process": "^1.0.2", "chokidar": "2.0.4", diff --git a/packages/core.gbapp/services/GBAPIService.ts b/packages/core.gbapp/services/GBAPIService.ts index b494ea19..118724e8 100644 --- a/packages/core.gbapp/services/GBAPIService.ts +++ b/packages/core.gbapp/services/GBAPIService.ts @@ -51,9 +51,9 @@ export class DialogClass { } public async hear(cb) { - const id = Math.floor(Math.random() * 1000000000000); - this.min.cbMap[id] = cb; - await this.step.beginDialog('/feedback', { id: id }); + let idCallback = Math.floor(Math.random() * 1000000000000); + this.min.cbMap[idCallback] = cb; + await this.step.beginDialog('/hear', { id: idCallback}); } public async talk(text: string) { diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 9e3ea2cf..5dc7a75c 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -315,7 +315,7 @@ export class GBMinService { min.conversationalService = this.conversationalService; min.adminService = this.adminService; min.instance = await this.core.loadInstance(min.botId); - + min.cbMap = {}; min.userProfile = conversationState.createProperty('userProfile'); const dialogState = conversationState.createProperty('dialogState'); @@ -408,14 +408,12 @@ export class GBMinService { } // Processes messages. - } else if (context.activity.type === 'message') { // Checks for /admin request. if (context.activity.text === 'vba') { min.sandbox.context = context; min.sandbox.step = step; min.sandbox['bot'].bind(min.sandbox); - await min.sandbox['bot'](); } else if (context.activity.text === 'admin') { await step.beginDialog('/admin'); diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts index d4e45bae..0e5e4b3a 100644 --- a/packages/core.gbapp/services/GBVMService.ts +++ b/packages/core.gbapp/services/GBVMService.ts @@ -37,6 +37,7 @@ import * as fs from 'fs'; import { DialogClass } from './GBAPIService'; import { GBDeployer } from './GBDeployer'; import { TSCompiler } from './TSCompiler'; +import { WaterfallDialog } from 'botbuilder-dialogs'; const util = require('util'); const logger = require('../../../src/logger'); const vm = require('vm'); @@ -66,6 +67,31 @@ export class GBVMService implements IGBCoreService { await this.run(source, path, localPath, min, deployer, filename); }); await this.run(source, path, localPath, min, deployer, filename); + this.addHearDialog(min); + } + + private addHearDialog(min) { + min.dialogs.add( + new WaterfallDialog('/hear', [ + async step => { + step.activeDialog.state.cbId = step.options['id']; + step.activeDialog.state.idResolve = step.options['idResolve']; + + return await step.prompt('textPrompt', {}); + }, + async step => { + min.sandbox.context = step.context; + min.sandbox.step = step; + + const cbId = step.activeDialog.state.cbId; + const cb = min.cbMap[cbId]; + cb.bind({ step: step, context: step.context }); // TODO: Necessary or min.sandbox + await cb(); + + return await step.next(); + } + ]) + ); } private async run(source: any, path: string, localPath: string, min: any, deployer: GBDeployer, filename: string) { @@ -80,11 +106,14 @@ export class GBVMService implements IGBCoreService { // Run JS into the GB context. const jsfile = `bot.js`; localPath = UrlJoin(path, jsfile); + if (fs.existsSync(localPath)) { let code: string = fs.readFileSync(localPath, 'utf8'); code = code.replace(/^.*exports.*$/gm, ''); code = code.replace(/this\./gm, 'await this.'); code = code.replace(/function/gm, 'async function'); + //code = code.replace(/this\.hear\(\){/gm, 'this.hear(async () => { '); + const sandbox: DialogClass = new DialogClass(min); const context = vm.createContext(sandbox); vm.runInContext(code, context); diff --git a/packages/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts b/packages/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts index 7074ed04..d0ee93c3 100644 --- a/packages/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts +++ b/packages/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts @@ -89,35 +89,24 @@ export class FeedbackDialog extends IGBDialog { return await step.prompt('textPrompt', Messages[locale].what_about_service); }, async step => { - - console.log(step.result); - // min.sandbox.context = step.context; - // min.sandbox.step = step; + const locale = step.context.activity.locale; + const rate = await AzureText.getSentiment( + min.instance.textAnalyticsKey, + min.instance.textAnalyticsEndpoint, + min.conversationalService.getCurrentLanguage(step), + step.result + ); - let cbId = step.activeDialog.state.cbId; - let cb = min.cbMap[cbId]; - cb.bind({ step: step, context: step.context }); - await cb(); + if (rate > 0.5) { + await step.context.sendActivity(Messages[locale].glad_you_liked); + } else { + await step.context.sendActivity(Messages[locale].we_will_improve); - // const locale = step.context.activity.locale; - // const rate = await AzureText.getSentiment( - // min.instance.textAnalyticsKey, - // min.instance.textAnalyticsEndpoint, - // min.conversationalService.getCurrentLanguage(step), - // step.result - // ); + // TODO: Record. + } + return await step.replaceDialog('/ask', { isReturning: true }); - // if (rate > 0.5) { - // await step.context.sendActivity(Messages[locale].glad_you_liked); - // } else { - // await step.context.sendActivity(Messages[locale].we_will_improve); - - // // TODO: Record. - // } - // await step.replaceDialog('/ask', { isReturning: true }); - - return await step.next(); } ]) ); -- 2.39.5 From 6915d58db178133ae514fd4d9fbdb5dce8caed5d Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (pragmatismo.io)" Date: Sat, 1 Dec 2018 20:48:08 -0200 Subject: [PATCH 09/13] fix(gbdialog): VBA hear must be a wrapper call. --- packages/core.gbapp/services/GBVMService.ts | 76 ++++++++++++++++++++- packages/default.gbdialog/bot.vbs | 25 +++++-- src/app.ts | 8 ++- 3 files changed, 100 insertions(+), 9 deletions(-) diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts index 0e5e4b3a..e17f6dad 100644 --- a/packages/core.gbapp/services/GBVMService.ts +++ b/packages/core.gbapp/services/GBVMService.ts @@ -102,7 +102,7 @@ export class GBVMService implements IGBCoreService { // Convert TS into JS. const tsfile = `bot.ts`; const tsc = new TSCompiler(); - // TODO: tsc.compile([UrlJoin(path, tsfile)]); + tsc.compile([UrlJoin(path, tsfile)]); // Run JS into the GB context. const jsfile = `bot.js`; localPath = UrlJoin(path, jsfile); @@ -112,8 +112,24 @@ export class GBVMService implements IGBCoreService { code = code.replace(/^.*exports.*$/gm, ''); code = code.replace(/this\./gm, 'await this.'); code = code.replace(/function/gm, 'async function'); + var match; + let finalCode: string; + let pos = 0; + while ((match = /hear.*\(\)/.exec(code)) != null) { + pos = match.index; + console.log(pos); + finalCode += code.substring(0, pos); + let nextCode = code.substring(pos); + + // Find last } + + while ((match = /\{|\}/g.exec(nextCode)) != null) { + console.log(match.index); + } + } + //code = code.replace(/this\.hear\(\){/gm, 'this.hear(async () => { '); - + const sandbox: DialogClass = new DialogClass(min); const context = vm.createContext(sandbox); vm.runInContext(code, context); @@ -122,4 +138,60 @@ export class GBVMService implements IGBCoreService { logger.info(`[GBVMService] Finished loading of ${filename}`); } } + + public static async run2(source: any, path: string) { + // Converts VBS into TS. + + //vb2ts.convertFile(source); + + // Convert TS into JS. + const tsfile = `bot.ts`; + const tsc = new TSCompiler(); + //tsc.compile([UrlJoin(path, tsfile)]); + // Run JS into the GB context. + const jsfile = `bot.js`; + let localPath = UrlJoin(path, jsfile); + + if (fs.existsSync(localPath)) { + let code: string = fs.readFileSync(localPath, 'utf8'); + code = code.replace(/^.*exports.*$/gm, ''); + code = code.replace(/this\./gm, 'await this.'); + code = code.replace(/function/gm, 'async function'); + var match1; + var match2; + let finalCode: string; + let pos = 0; + while ((match1 = /hear.*\(\)/.exec(code))) { + pos = match1.index; + console.log(pos); + finalCode += code.substring(0, pos); + let nextCode = code.substring(pos); + + // Find last } + let right = 0; + let left = 1; + + while ((match2 = /\{|\}/.exec(nextCode))) { + let c = nextCode.substring(match2.index, match2.index+1); + if (c === '}') { + right++; + } else if (c === '{') { + left++; + } + + nextCode = nextCode.substring(match2.index + 1); + + if (left == right) + { + console.log('end '+match2.index); + } + console.log(match2.index); + } + } + + console.log(finalCode); + + //code = code.replace(/this\.hear\(\){/gm, 'this.hear(async () => { '); + } + } } diff --git a/packages/default.gbdialog/bot.vbs b/packages/default.gbdialog/bot.vbs index ffb1425a..a0fa9b17 100644 --- a/packages/default.gbdialog/bot.vbs +++ b/packages/default.gbdialog/bot.vbs @@ -31,8 +31,23 @@ ' '**************************************************************************** -this.talk ("Please, what's your e-mail address?") -let email = this.hear() -this.talk("Thanks, sending e-mail to: " + email); -this.sendEmail(email, "Message from VBA Bot", "Yes, I can send e-mails."); -%> \ No newline at end of file +talk ("Please, what's your e-mail address?") +email = hear () +talk("Thanks, sending e-mail to: " + email ) +sendEmail(email, "Message from VBA Bot", "Yes, I can send e-mails.") +if email = "" then + +end if + +select case email + case 1: + + case 2: + +end select + +if i > 10 then + +end if + +%> \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 01a438f7..1dece0c6 100644 --- a/src/app.ts +++ b/src/app.ts @@ -50,6 +50,7 @@ import { GBCoreService } from '../packages/core.gbapp/services/GBCoreService'; import { GBDeployer } from '../packages/core.gbapp/services/GBDeployer'; import { GBImporter } from '../packages/core.gbapp/services/GBImporterService'; import { GBMinService } from '../packages/core.gbapp/services/GBMinService'; +import { GBVMService } from '../packages/core.gbapp/services/GBVMService'; const appPackages = new Array(); @@ -150,5 +151,8 @@ export class GBServer { } // First line to run. - -GBServer.run(); +const path = 'packages/default.gbdialog'; +const file = 'bot.vbs'; +const source =(path + '/' + file); +GBVMService.run2(source, path) +//GBServer.run(); -- 2.39.5 From 3bb9d652fd732f2f57314901c4e7c491bd9e7062 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (pragmatismo.io)" Date: Sat, 1 Dec 2018 23:01:42 -0200 Subject: [PATCH 10/13] fix(gbdialog): Support for multiples hear blocks. --- packages/core.gbapp/services/GBVMService.ts | 204 ++++++++++---------- packages/default.gbdialog/bot.vbs | 47 +++-- src/app.ts | 11 +- 3 files changed, 136 insertions(+), 126 deletions(-) diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts index e17f6dad..a281aeb0 100644 --- a/packages/core.gbapp/services/GBVMService.ts +++ b/packages/core.gbapp/services/GBVMService.ts @@ -64,12 +64,111 @@ export class GBVMService implements IGBCoreService { // Example when handled through fs.watch() listener fs.watchFile(source, async (curr, prev) => { - await this.run(source, path, localPath, min, deployer, filename); + await this.run(source, path, min, deployer, filename); }); - await this.run(source, path, localPath, min, deployer, filename); + await this.run(source, path, min, deployer, filename); this.addHearDialog(min); } + public async run(source: any, path: string, min: any, deployer: GBDeployer, filename: string) { + // Converts VBS into TS. + + //vb2ts.convertFile(source); + + // Convert TS into JS. + const tsfile = `bot.ts`; + const tsc = new TSCompiler(); + //tsc.compile([UrlJoin(path, tsfile)]); + // Run JS into the GB context. + const jsfile = `bot.js`; + let localPath = UrlJoin(path, jsfile); + + if (fs.existsSync(localPath)) { + let code: string = fs.readFileSync(localPath, 'utf8'); + code = code.replace(/^.*exports.*$/gm, ''); + let match1; + let match2; + let finalCode = ''; + let pos = 0; + let nextCode = code; + let hearExp = /(\w+).*hear.*\(\)/; + + while (match1 = hearExp.exec(nextCode)) { + + // Write async body. + + const variable = match1[1]; // variable = hear(); + + finalCode += code.substring(pos, pos + match1.index); + finalCode += `hear (async (${variable}) => {\n`; + + // Skip old construction and point to the async block. + + pos = pos + match1.index; + nextCode = code.substring(pos + match1[0].length + 1); + let start = pos; + + // Find last } + + let right = 0; + let left = 1; + while ((match2 = /\{|\}/.exec(nextCode))) { + const c = nextCode.substring(match2.index, match2.index + 1); + + if (c === '}') { + right++; + } else if (c === '{') { + left++; + } + + let match3 + if (match3 = hearExp.exec(nextCode)) + { + nextCode = nextCode.substring(match3.index + 1); + pos += match3.index; + break; + } + + nextCode = nextCode.substring(match2.index + 1); + pos += match2.index + 1; + + if (left === right) { + break; + } + + } + + finalCode += code.substring(start + match1[0].length + 1, pos + match1[0].length); + finalCode += '});\n'; + + nextCode = code.substring(pos + match1[0].length); + } + + finalCode = finalCode.replace(/("[^"]*"|'[^']*')|\btalk\b/g, function($0, $1) { + return $1 == undefined ? 'this.talk' : $1; + }); + + finalCode = finalCode.replace(/("[^"]*"|'[^']*')|\bhear\b/g, function($0, $1) { + return $1 == undefined ? 'this.hear' : $1; + }); + + finalCode = finalCode.replace(/("[^"]*"|'[^']*')|\bsendEmail\b/g, function($0, $1) { + return $1 == undefined ? 'this.sendEmail' : $1; + }); + + finalCode = finalCode.replace(/this\./gm, 'await this.'); + finalCode = finalCode.replace(/function/gm, 'async function'); + console.log(finalCode); + + const sandbox: DialogClass = new DialogClass(min); + const context = vm.createContext(sandbox); + vm.runInContext(finalCode, context); + min.sandbox = sandbox; + await deployer.deployScriptToStorage(min.instanceId, filename); + logger.info(`[GBVMService] Finished loading of ${filename}`); + } + } + private addHearDialog(min) { min.dialogs.add( new WaterfallDialog('/hear', [ @@ -93,105 +192,4 @@ export class GBVMService implements IGBCoreService { ]) ); } - - private async run(source: any, path: string, localPath: string, min: any, deployer: GBDeployer, filename: string) { - // Converts VBS into TS. - - vb2ts.convertFile(source); - - // Convert TS into JS. - const tsfile = `bot.ts`; - const tsc = new TSCompiler(); - tsc.compile([UrlJoin(path, tsfile)]); - // Run JS into the GB context. - const jsfile = `bot.js`; - localPath = UrlJoin(path, jsfile); - - if (fs.existsSync(localPath)) { - let code: string = fs.readFileSync(localPath, 'utf8'); - code = code.replace(/^.*exports.*$/gm, ''); - code = code.replace(/this\./gm, 'await this.'); - code = code.replace(/function/gm, 'async function'); - var match; - let finalCode: string; - let pos = 0; - while ((match = /hear.*\(\)/.exec(code)) != null) { - pos = match.index; - console.log(pos); - finalCode += code.substring(0, pos); - let nextCode = code.substring(pos); - - // Find last } - - while ((match = /\{|\}/g.exec(nextCode)) != null) { - console.log(match.index); - } - } - - //code = code.replace(/this\.hear\(\){/gm, 'this.hear(async () => { '); - - const sandbox: DialogClass = new DialogClass(min); - const context = vm.createContext(sandbox); - vm.runInContext(code, context); - min.sandbox = sandbox; - await deployer.deployScriptToStorage(min.instanceId, filename); - logger.info(`[GBVMService] Finished loading of ${filename}`); - } - } - - public static async run2(source: any, path: string) { - // Converts VBS into TS. - - //vb2ts.convertFile(source); - - // Convert TS into JS. - const tsfile = `bot.ts`; - const tsc = new TSCompiler(); - //tsc.compile([UrlJoin(path, tsfile)]); - // Run JS into the GB context. - const jsfile = `bot.js`; - let localPath = UrlJoin(path, jsfile); - - if (fs.existsSync(localPath)) { - let code: string = fs.readFileSync(localPath, 'utf8'); - code = code.replace(/^.*exports.*$/gm, ''); - code = code.replace(/this\./gm, 'await this.'); - code = code.replace(/function/gm, 'async function'); - var match1; - var match2; - let finalCode: string; - let pos = 0; - while ((match1 = /hear.*\(\)/.exec(code))) { - pos = match1.index; - console.log(pos); - finalCode += code.substring(0, pos); - let nextCode = code.substring(pos); - - // Find last } - let right = 0; - let left = 1; - - while ((match2 = /\{|\}/.exec(nextCode))) { - let c = nextCode.substring(match2.index, match2.index+1); - if (c === '}') { - right++; - } else if (c === '{') { - left++; - } - - nextCode = nextCode.substring(match2.index + 1); - - if (left == right) - { - console.log('end '+match2.index); - } - console.log(match2.index); - } - } - - console.log(finalCode); - - //code = code.replace(/this\.hear\(\){/gm, 'this.hear(async () => { '); - } - } } diff --git a/packages/default.gbdialog/bot.vbs b/packages/default.gbdialog/bot.vbs index a0fa9b17..e065b770 100644 --- a/packages/default.gbdialog/bot.vbs +++ b/packages/default.gbdialog/bot.vbs @@ -31,23 +31,34 @@ ' '**************************************************************************** -talk ("Please, what's your e-mail address?") -email = hear () -talk("Thanks, sending e-mail to: " + email ) -sendEmail(email, "Message from VBA Bot", "Yes, I can send e-mails.") -if email = "" then + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + talk ("Please, what's your e-mail address?") + email = hear () + talk("Thanks, sending e-mail to: " + email ) + sendEmail(email, "Message from VBA Bot", "Yes, I can send e-mails.") -end if + name = hear () + talk("Hey " + name + "!") + -select case email - case 1: - - case 2: - -end select - -if i > 10 then - -end if - -%> \ No newline at end of file +%> \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 1dece0c6..86da8a7f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -150,9 +150,10 @@ export class GBServer { } } -// First line to run. -const path = 'packages/default.gbdialog'; -const file = 'bot.vbs'; -const source =(path + '/' + file); -GBVMService.run2(source, path) +// // First line to run. + const path = 'packages/default.gbdialog'; + const file = 'bot.vbs'; + const source =(path + '/' + file); + let s = new GBVMService(); + s.run(source, path, null, null, null) //GBServer.run(); -- 2.39.5 From 2dd359a34441f40b2dff98517e48f918c64f844b Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (pragmatismo.io)" Date: Sun, 2 Dec 2018 19:59:27 -0200 Subject: [PATCH 11/13] fix(gbdialog): VBA is running. --- .prettierrc | 1 - packages/core.gbapp/services/GBVMService.ts | 72 +++++----- packages/core.gbapp/services/TSCompiler.ts | 3 +- packages/default.gbdialog/bot.vbs | 139 ++++++++++++++++---- src/app.ts | 16 ++- 5 files changed, 158 insertions(+), 73 deletions(-) diff --git a/.prettierrc b/.prettierrc index af8db0fa..c1e1114c 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,5 +5,4 @@ "arrowParens": "avoid", "semi": true, "singleQuote": true - } \ No newline at end of file diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts index a281aeb0..42c9292e 100644 --- a/packages/core.gbapp/services/GBVMService.ts +++ b/packages/core.gbapp/services/GBVMService.ts @@ -73,12 +73,13 @@ export class GBVMService implements IGBCoreService { public async run(source: any, path: string, min: any, deployer: GBDeployer, filename: string) { // Converts VBS into TS. - //vb2ts.convertFile(source); + vb2ts.convertFile(source); // Convert TS into JS. const tsfile = `bot.ts`; const tsc = new TSCompiler(); - //tsc.compile([UrlJoin(path, tsfile)]); + tsc.compile([UrlJoin(path, tsfile)]); + // Run JS into the GB context. const jsfile = `bot.js`; let localPath = UrlJoin(path, jsfile); @@ -86,34 +87,38 @@ export class GBVMService implements IGBCoreService { if (fs.existsSync(localPath)) { let code: string = fs.readFileSync(localPath, 'utf8'); code = code.replace(/^.*exports.*$/gm, ''); - let match1; - let match2; - let finalCode = ''; - let pos = 0; - let nextCode = code; + + // Finds all hear calls. + + let parsedCode = code; let hearExp = /(\w+).*hear.*\(\)/; - while (match1 = hearExp.exec(nextCode)) { + let match1; - // Write async body. + while ((match1 = hearExp.exec(code))) { + + let pos = 0; + + // Writes async body. const variable = match1[1]; // variable = hear(); - finalCode += code.substring(pos, pos + match1.index); - finalCode += `hear (async (${variable}) => {\n`; + parsedCode = code.substring(pos, pos + match1.index); + parsedCode += `hear (async (${variable}) => {\n`; - // Skip old construction and point to the async block. + // Skips old construction and point to the async block. pos = pos + match1.index; - nextCode = code.substring(pos + match1[0].length + 1); + let tempCode = code.substring(pos + match1[0].length + 1); let start = pos; - // Find last } + // Balances code blocks and checks for exits. let right = 0; let left = 1; - while ((match2 = /\{|\}/.exec(nextCode))) { - const c = nextCode.substring(match2.index, match2.index + 1); + let match2; + while ((match2 = /\{|\}/.exec(tempCode))) { + const c = tempCode.substring(match2.index, match2.index + 1); if (c === '}') { right++; @@ -121,48 +126,43 @@ export class GBVMService implements IGBCoreService { left++; } - let match3 - if (match3 = hearExp.exec(nextCode)) - { - nextCode = nextCode.substring(match3.index + 1); - pos += match3.index; - break; - } - - nextCode = nextCode.substring(match2.index + 1); + tempCode = tempCode.substring(match2.index + 1); pos += match2.index + 1; if (left === right) { break; } - } - finalCode += code.substring(start + match1[0].length + 1, pos + match1[0].length); - finalCode += '});\n'; + parsedCode += code.substring(start + match1[0].length + 1, pos + match1[0].length); + parsedCode += '});\n'; + parsedCode += code.substring(pos + match1[0].length); - nextCode = code.substring(pos + match1[0].length); + // A interaction will be made for each hear. + + code = parsedCode; } - finalCode = finalCode.replace(/("[^"]*"|'[^']*')|\btalk\b/g, function($0, $1) { + parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\btalk\b/g, function($0, $1) { return $1 == undefined ? 'this.talk' : $1; }); - finalCode = finalCode.replace(/("[^"]*"|'[^']*')|\bhear\b/g, function($0, $1) { + parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\bhear\b/g, function($0, $1) { return $1 == undefined ? 'this.hear' : $1; }); - finalCode = finalCode.replace(/("[^"]*"|'[^']*')|\bsendEmail\b/g, function($0, $1) { + parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\bsendEmail\b/g, function($0, $1) { return $1 == undefined ? 'this.sendEmail' : $1; }); - finalCode = finalCode.replace(/this\./gm, 'await this.'); - finalCode = finalCode.replace(/function/gm, 'async function'); - console.log(finalCode); + parsedCode = parsedCode.replace(/this\./gm, 'await this.'); + parsedCode = parsedCode.replace(/function/gm, 'async function'); + + fs.writeFileSync(localPath, parsedCode); const sandbox: DialogClass = new DialogClass(min); const context = vm.createContext(sandbox); - vm.runInContext(finalCode, context); + vm.runInContext(parsedCode, context); min.sandbox = sandbox; await deployer.deployScriptToStorage(min.instanceId, filename); logger.info(`[GBVMService] Finished loading of ${filename}`); diff --git a/packages/core.gbapp/services/TSCompiler.ts b/packages/core.gbapp/services/TSCompiler.ts index 60bbe85b..4cb7e63a 100644 --- a/packages/core.gbapp/services/TSCompiler.ts +++ b/packages/core.gbapp/services/TSCompiler.ts @@ -43,6 +43,8 @@ export class TSCompiler { public compile( fileNames: string[], options: ts.CompilerOptions = { + noStrictGenericChecks: true, + noImplicitUseStrict: true, noEmitOnError: false, noImplicitAny: true, target: ts.ScriptTarget.ES5, @@ -51,7 +53,6 @@ export class TSCompiler { noEmitHelpers: true, maxNodeModuleJsDepth: 0, esModuleInterop: false - } ) { const program = ts.createProgram(fileNames, options); diff --git a/packages/default.gbdialog/bot.vbs b/packages/default.gbdialog/bot.vbs index e065b770..e6fe6d00 100644 --- a/packages/default.gbdialog/bot.vbs +++ b/packages/default.gbdialog/bot.vbs @@ -31,34 +31,117 @@ ' '**************************************************************************** - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - talk ("Please, what's your e-mail address?") - email = hear () - talk("Thanks, sending e-mail to: " + email ) - sendEmail(email, "Message from VBA Bot", "Yes, I can send e-mails.") +talk ("How many installments do you want to pay your Credit?") +installments = hear () - name = hear () - talk("Hey " + name + "!") - +talk ("What is the amount requested?") +ammount = hear () + +talk ("What is the initial payment value?") +valorEntrada = hear () + + +talk ("What is the best due date?") +dueDate = hear () + +juros =0 +coeficiente1=0 + +if installments <12 then + juros = 1.60 + coeficiente = 0.09748 +end if + +if installments > 12 and installments< 18 then + juros = 1.66 + coeficiente = 0.06869 +end if + +if installments > 18 and installments< 36 then + juros = 1.64 + coeficiente = 0.05397 +end if + +if installments > 36 and installments< 48 then + juros = 1.62 + coeficiente = 0.03931 +end if + +if installments > 48 and installments< 60 then + juros = 1.70 + coeficiente = 0.03270 +end if + +if installments =60 then + juros = 1.79 + coeficiente = 0.02916 +end if + +if installments > 60 then + talk ("The maximum number of payments is 60") +end if + + +nInstallments = parseInt(installments) +vAmmount = parseFloat(ammount) +first = parseFloat(vAmmount) * 0.3 ' 30% of the value +tac = 800 +coeficiente = 1.3 + +taxaJuros = parseFloat(juros) +valorFinanciado = ammount - valorEntrada + tac +valorParcela = valorFinanciado * coeficiente +valorTotalDoBem = valorParcela * nInstallments + valorEntrada + +talk("Your credit analysis is done.") + +talk("First payment" + valorEntrada) +talk("valorParcela" + valor) +talk("taxaJuros" + taxaJuros) +talk("valorFinanciado" + valorFinanciado) +talk("valorTotalDoBem" + valorTotalDoBem) + + +text = hear() + +if email = "" then + + text = hear() + + + if email = "" then + talk () + text = hear() + + i1 = 10 + end if +else + text = hear() + if email = "" then + talk () + text = hear() + + i2 = 10 + + talk () + text = hear() + + talk () + text = hear() + end if +end if + +talk () +text = hear() + +i = 10 + +if i > 10 then + text = hear() + text = hear() +else + text = hear() + text = hear() +end if %> \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 86da8a7f..6a60a044 100644 --- a/src/app.ts +++ b/src/app.ts @@ -150,10 +150,12 @@ export class GBServer { } } -// // First line to run. - const path = 'packages/default.gbdialog'; - const file = 'bot.vbs'; - const source =(path + '/' + file); - let s = new GBVMService(); - s.run(source, path, null, null, null) -//GBServer.run(); +// First line to run. + +// const path = 'packages/default.gbdialog'; +// const file = 'bot.vbs'; +// const source =(path + '/' + file); +// let s = new GBVMService(); +// s.run(source, path, null, null, null) + +GBServer.run(); -- 2.39.5 From 9fb431ce2f1b6586855db275222b3f3b2371a4cd Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (pragmatismo.io)" Date: Sun, 2 Dec 2018 21:39:36 -0200 Subject: [PATCH 12/13] fix(gbdialog): VBA is running financial simulations. --- packages/core.gbapp/dialogs/WhoAmIDialog.ts | 1 + packages/core.gbapp/services/GBMinService.ts | 2 +- packages/core.gbapp/services/GBVMService.ts | 6 +- packages/default.gbdialog/bot.vbs | 164 +++++++------------ 4 files changed, 67 insertions(+), 106 deletions(-) diff --git a/packages/core.gbapp/dialogs/WhoAmIDialog.ts b/packages/core.gbapp/dialogs/WhoAmIDialog.ts index d9e599b2..20d7162e 100644 --- a/packages/core.gbapp/dialogs/WhoAmIDialog.ts +++ b/packages/core.gbapp/dialogs/WhoAmIDialog.ts @@ -64,6 +64,7 @@ export class WhoAmIDialog extends IGBDialog { } await step.replaceDialog('/ask', { isReturning: true }); + return await step.next(); } ])); diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 5dc7a75c..7dd5204b 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -428,7 +428,7 @@ export class GBMinService { } else { const user = await min.userProfile.get(context, {}); - if (step.activeDialog || user.dialog) { + if (step.activeDialog) { await step.continueDialog(); } else { await step.beginDialog('/answer', { diff --git a/packages/core.gbapp/services/GBVMService.ts b/packages/core.gbapp/services/GBVMService.ts index 42c9292e..30cd2c8d 100644 --- a/packages/core.gbapp/services/GBVMService.ts +++ b/packages/core.gbapp/services/GBVMService.ts @@ -174,7 +174,6 @@ export class GBVMService implements IGBCoreService { new WaterfallDialog('/hear', [ async step => { step.activeDialog.state.cbId = step.options['id']; - step.activeDialog.state.idResolve = step.options['idResolve']; return await step.prompt('textPrompt', {}); }, @@ -185,9 +184,10 @@ export class GBVMService implements IGBCoreService { const cbId = step.activeDialog.state.cbId; const cb = min.cbMap[cbId]; cb.bind({ step: step, context: step.context }); // TODO: Necessary or min.sandbox - await cb(); + + await step.endDialog(); - return await step.next(); + return await cb(step.result); } ]) ); diff --git a/packages/default.gbdialog/bot.vbs b/packages/default.gbdialog/bot.vbs index e6fe6d00..737f6ba3 100644 --- a/packages/default.gbdialog/bot.vbs +++ b/packages/default.gbdialog/bot.vbs @@ -34,114 +34,74 @@ talk ("How many installments do you want to pay your Credit?") installments = hear () -talk ("What is the amount requested?") -ammount = hear () - -talk ("What is the initial payment value?") -valorEntrada = hear () - - -talk ("What is the best due date?") -dueDate = hear () - -juros =0 -coeficiente1=0 - -if installments <12 then - juros = 1.60 - coeficiente = 0.09748 -end if - -if installments > 12 and installments< 18 then - juros = 1.66 - coeficiente = 0.06869 -end if - -if installments > 18 and installments< 36 then - juros = 1.64 - coeficiente = 0.05397 -end if - -if installments > 36 and installments< 48 then - juros = 1.62 - coeficiente = 0.03931 -end if - -if installments > 48 and installments< 60 then - juros = 1.70 - coeficiente = 0.03270 -end if - -if installments =60 then - juros = 1.79 - coeficiente = 0.02916 -end if - if installments > 60 then talk ("The maximum number of payments is 60") -end if - - -nInstallments = parseInt(installments) -vAmmount = parseFloat(ammount) -first = parseFloat(vAmmount) * 0.3 ' 30% of the value -tac = 800 -coeficiente = 1.3 - -taxaJuros = parseFloat(juros) -valorFinanciado = ammount - valorEntrada + tac -valorParcela = valorFinanciado * coeficiente -valorTotalDoBem = valorParcela * nInstallments + valorEntrada - -talk("Your credit analysis is done.") - -talk("First payment" + valorEntrada) -talk("valorParcela" + valor) -talk("taxaJuros" + taxaJuros) -talk("valorFinanciado" + valorFinanciado) -talk("valorTotalDoBem" + valorTotalDoBem) - - -text = hear() - -if email = "" then - - text = hear() - - - if email = "" then - talk () - text = hear() - - i1 = 10 - end if else - text = hear() - if email = "" then - talk () - text = hear() + talk ("What is the amount requested?") + ammount = hear () - i2 = 10 + if ammount >100000 then + talk ("We are sorry, we can only accept proposals bellow 100k") + else - talk () - text = hear() + talk ("What is the best due date?") + dueDate = hear () - talk () - text = hear() + interestRate = 0 + adjustment = 0 + + if installments < 12 then + interestRate = 1.60 + adjustment = 0.09748 + end if + + if installments > 12 and installments < 18 then + interestRate = 1.66 + adjustment = 0.06869 + end if + + if installments > 18 and installments < 36 then + interestRate = 1.64 + adjustment = 0.05397 + end if + + if installments > 36 and installments < 48 then + interestRate = 1.62 + adjustment = 0.03931 + end if + + if installments > 48 and installments < 60 then + interestRate = 1.70 + adjustment = 0.03270 + end if + + if installments = 60 then + interestRate = 1.79 + adjustment = 0.02916 + end if + + if installments > 60 then + talk ("The maximum number of payments is 60") + end if + + + nInstallments = parseInt(installments) + vAmmount = parseFloat(ammount) + initialPayment = parseFloat(vAmmount) * 0.3 ' 30% of the value + tac = 800 + adjustment = 1.3 + + totalValue = ammount - initialPayment + tac + paymentValue = totalValue * adjustment + finalValue = paymentValue * nInstallments + initialPayment + + talk("Congratulations! Your credit analysis is **done**:") + talk("First payment: **" + initialPayment + "**") + talk("Payment value: **" + paymentValue + "**") + talk("Interest Rate: **" + interestRate + "%**") + talk("Total Value: **" + totalValue + "**") + talk("Final Value: **" + finalValue + "**") + end if end if - -talk () -text = hear() - -i = 10 - -if i > 10 then - text = hear() - text = hear() -else - text = hear() - text = hear() -end if - %> \ No newline at end of file -- 2.39.5 From 9cd66b8fac343d3e70a818a020bf43101e2c51da Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (pragmatismo.io)" Date: Thu, 6 Dec 2018 10:16:28 -0200 Subject: [PATCH 13/13] fix(gbdialog): Renamed alpha command to alpha-VBA added documentation files. --- LICENSE.txt => LICENSE | 0 LOCALIZATION.md | 7 + SAMPLES.md | 0 package-lock.json | 465 +++++++++++++------ package.json | 32 +- packages/admin.gbapp/dialogs/AdminDialog.ts | 85 ++-- packages/analytics.gblib/index.ts | 21 +- packages/core.gbapp/services/GBMinService.ts | 2 +- packages/default.gbdialog/bot.vbs | 7 + packages/default.gbui/package-lock.json | 36 +- 10 files changed, 438 insertions(+), 217 deletions(-) rename LICENSE.txt => LICENSE (100%) create mode 100644 LOCALIZATION.md create mode 100644 SAMPLES.md diff --git a/LICENSE.txt b/LICENSE similarity index 100% rename from LICENSE.txt rename to LICENSE diff --git a/LOCALIZATION.md b/LOCALIZATION.md new file mode 100644 index 00000000..4ab1f387 --- /dev/null +++ b/LOCALIZATION.md @@ -0,0 +1,7 @@ + +# Localization in General Bots + +## .gbapp + +The localization is done by adding a strings.ts file to the root of the .gbapp package. + diff --git a/SAMPLES.md b/SAMPLES.md new file mode 100644 index 00000000..e69de29b diff --git a/package-lock.json b/package-lock.json index 29da2ceb..74d00b24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1059,40 +1059,56 @@ "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "dev": true }, - "@octokit/rest": { - "version": "15.17.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-15.17.0.tgz", - "integrity": "sha512-tN16FJOGBPxt9QtPfl8yVbbuik3bQ7EI66zcX2XDh05Wcs8t+7mVEE3SWtCeK/Qm0RTLCeFQgGzuvkbD2J6cEg==", + "@octokit/endpoint": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-3.1.1.tgz", + "integrity": "sha512-KPkoTvKwCTetu/UqonLs1pfwFO5HAqTv/Ksp9y4NAg//ZgUCpvJsT4Hrst85uEzJvkB8+LxKyR4Bfv2X8O4cmQ==", "dev": true, "requires": { - "before-after-hook": "^1.1.0", - "btoa-lite": "^1.0.0", - "debug": "^3.1.0", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.0", - "lodash": "^4.17.4", - "node-fetch": "^2.1.1", - "universal-user-agent": "^2.0.0", + "deepmerge": "3.0.0", + "is-plain-object": "^2.0.4", + "universal-user-agent": "^2.0.1", "url-template": "^2.0.8" + } + }, + "@octokit/request": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-2.2.0.tgz", + "integrity": "sha512-4P9EbwKZ4xfyupVMb3KVuHmM+aO2fye3nufjGKz/qDssvdJj9Rlx44O0FdFvUp4kIzToy3AHLTOulEIDAL+dpg==", + "dev": true, + "requires": { + "@octokit/endpoint": "^3.0.0", + "is-plain-object": "^2.0.4", + "node-fetch": "^2.3.0", + "universal-user-agent": "^2.0.1" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, "node-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.1.tgz", - "integrity": "sha512-ObXBpNCD3A/vYQiQtEWl7DuqjAXjfptYFuGHLdPl5U19/6kJuZV+8uMHLrkj3wJrJoyfg4nhgyFixZdaZoAiEQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", + "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==", "dev": true } } }, + "@octokit/rest": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.1.0.tgz", + "integrity": "sha512-/D1XokSycOE+prxxI2r9cxssiLMqcr+BsEUjdruC67puEEjNJjJoRIkuA1b20jOkX5Ue3Rz99Mu9rTnNmjetUA==", + "dev": true, + "requires": { + "@octokit/request": "2.2.0", + "before-after-hook": "^1.2.0", + "btoa-lite": "^1.0.0", + "lodash.get": "^4.4.2", + "lodash.pick": "^4.4.0", + "lodash.set": "^4.3.2", + "lodash.uniq": "^4.5.0", + "octokit-pagination-methods": "^1.1.0", + "universal-user-agent": "^2.0.0", + "url-template": "^2.0.8" + } + }, "@semantic-release/changelog": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-3.0.1.tgz", @@ -1243,12 +1259,12 @@ } }, "@semantic-release/github": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-5.2.1.tgz", - "integrity": "sha512-EVh5MCMOSl5WfOIum+k7fb7ZaDBcZAepPvtMrJOn8HKa9MERK6PgT76OKro+tReWjT1PnGiaKjofjyRC4BhN6Q==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-5.2.5.tgz", + "integrity": "sha512-myO00q84CyfyzaEZ4OdA7GOMCQKd+juZd5g2Cloh4jV6CyiMyWflZ629RH99wjAVUiwMKnvX2SQ5XPFvO1+FCw==", "dev": true, "requires": { - "@octokit/rest": "^15.13.1", + "@octokit/rest": "^16.0.1", "@semantic-release/error": "^2.2.0", "aggregate-error": "^1.0.0", "bottleneck": "^2.0.1", @@ -1277,9 +1293,9 @@ } }, "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", "dev": true } } @@ -1638,9 +1654,9 @@ } }, "@types/sequelize": { - "version": "4.27.30", - "resolved": "https://registry.npmjs.org/@types/sequelize/-/sequelize-4.27.30.tgz", - "integrity": "sha512-5DZCTnlLizi7Dk/sZ9ZWI3WxL1eUSH4Rpy5rie394GOIAYSYPBamexl1citj6ZgdbkaNXvJ+RuU1HmipUPA47w==", + "version": "4.27.32", + "resolved": "https://registry.npmjs.org/@types/sequelize/-/sequelize-4.27.32.tgz", + "integrity": "sha512-M4iFjZLXDdjqnyKaxz9I5XlVFr4/WBvFYyd8oLU5YsKcXGeppMl6nNo9EBsdDgOOirJcyH2JkJEUhbhegZ6MqA==", "dev": true, "requires": { "@types/bluebird": "*", @@ -2219,9 +2235,9 @@ } }, "azure-arm-resource": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/azure-arm-resource/-/azure-arm-resource-7.2.0.tgz", - "integrity": "sha512-1WYsma7061MNc3MnRuS8s+HdWfLEJkrozdj4qJpRh1KQkYUk2pC76ypqNKBL9DdaWgYkqpys7j6juuITRW/pnQ==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/azure-arm-resource/-/azure-arm-resource-7.2.1.tgz", + "integrity": "sha512-dtji3Eia9/TABcKzxWklTLh9zj24BtOkBObzxH4BAr/ZSFmyAtJA4lYXkjizEkPCS6XVsCiqsUJZtNrqQ8iTWA==", "requires": { "ms-rest": "^2.3.3", "ms-rest-azure": "^2.5.5" @@ -2996,37 +3012,107 @@ } }, "botbuilder": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/botbuilder/-/botbuilder-4.1.5.tgz", - "integrity": "sha512-7OZqUqS3jhse2xq9wdG4jaE1mpPcsgQid+wPE2gv17/KwChHAZ5ZyXEmjoc6LP3Twmsapj6h0R4ni36bcst/xg==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/botbuilder/-/botbuilder-4.1.7.tgz", + "integrity": "sha512-AlZhvjeqiCpeWGN1TkqBi09l6f0spYIh0Xzc4rJYF8feCFi4k2FEYC1IpiiOAtYhEBeQ9SOGFcUUwPaLmsI3Xg==", "requires": { "@types/filenamify": "^2.0.1", "@types/node": "^9.3.0", "async-file": "^2.0.2", - "botbuilder-core": "^4.1.5", - "botframework-connector": "^4.1.5", + "botbuilder-core": "^4.1.7", + "botframework-connector": "^4.1.7", "filenamify": "^2.0.0", "rimraf": "^2.6.2" }, "dependencies": { + "@types/jsonwebtoken": { + "version": "7.2.8", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-7.2.8.tgz", + "integrity": "sha512-XENN3YzEB8D6TiUww0O8SRznzy1v+77lH7UmuN54xq/IHIsyWjWOzZuFFTtoiRuaE782uAoRwBe/wwow+vQXZw==", + "requires": { + "@types/node": "*" + } + }, "@types/node": { - "version": "9.6.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.36.tgz", - "integrity": "sha512-Fbw+AdRLL01vv7Rk7bYaNPecqmKoinJHGbpKnDpbUZmUj/0vj3nLqPQ4CNBzr3q2zso6Cq/4jHoCAdH78fvJrw==" + "version": "9.6.40", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.40.tgz", + "integrity": "sha512-M3HHoXXndsho/sTbQML2BJr7/uwNhMg8P0D4lb+UsM65JQZx268faiz9hKpY4FpocWqpwlLwa8vevw8hLtKjOw==" + }, + "base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" + }, + "botbuilder-core": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/botbuilder-core/-/botbuilder-core-4.1.7.tgz", + "integrity": "sha512-kfNOOpHVDLNdpYVMAefWjETXI4VsnDHgucEfKgANcCUrXmsYETlioHOCngUWLrFcaeVMJodeZvafIYl5NTgy0A==", + "requires": { + "assert": "^1.4.1", + "botframework-schema": "^4.1.7" + } + }, + "botframework-connector": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/botframework-connector/-/botframework-connector-4.1.7.tgz", + "integrity": "sha512-aIGY0acc/cMxmg1o+06HqReOjNC8qxbmPJDg+wDgaAwr85bSrDZE1CbyITcj2OqPG/QQ7VM3YzpPAZPlKkDWoQ==", + "requires": { + "@types/jsonwebtoken": "7.2.8", + "@types/node": "^9.3.0", + "@types/request": "^2.47.0", + "base64url": "^3.0.0", + "botframework-schema": "^4.1.7", + "jsonwebtoken": "8.0.1", + "ms-rest-azure-js": "1.0.176", + "ms-rest-js": "1.0.455", + "request": "^2.87.0", + "rsa-pem-from-mod-exp": "^0.8.4" + } + }, + "botframework-schema": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/botframework-schema/-/botframework-schema-4.1.7.tgz", + "integrity": "sha512-vPb5gHldmTIpUFx5uCdv/4XEsouMkXvSfQS2zsAC3VqAo29YESHYzNbr5HecRaUveb48NZ27+Djm0U0mLFxe9Q==", + "requires": { + "@types/node": "^9.3.0", + "ms-rest-js": "1.0.455" + } + }, + "ms-rest-azure-js": { + "version": "1.0.176", + "resolved": "https://registry.npmjs.org/ms-rest-azure-js/-/ms-rest-azure-js-1.0.176.tgz", + "integrity": "sha512-qtEBpSf/1nJ0/m1jGLkHISRnpOeHUp5n4SvzZRdFeYnGF4SQx9v/fl8a8ZwEmyujmgbUwyLNM9qKpH5PmW7pZg==", + "requires": { + "ms-rest-js": "^1.0.443", + "tslib": "^1.9.2" + } + }, + "ms-rest-js": { + "version": "1.0.455", + "resolved": "https://registry.npmjs.org/ms-rest-js/-/ms-rest-js-1.0.455.tgz", + "integrity": "sha512-RUDnFFNhk4ZdvOACg0yfaxmp5OzNwUcTIwgh/rVBeuNzgA7hOoVh5zFW06XmOtaBHXL2Bu/vWoQtzloEUlv9tw==", + "requires": { + "axios": "^0.18.0", + "form-data": "^2.3.2", + "tough-cookie": "^2.4.3", + "tslib": "^1.9.2", + "uuid": "^3.2.1", + "xml2js": "^0.4.19" + } } } }, "botbuilder-ai": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/botbuilder-ai/-/botbuilder-ai-4.1.5.tgz", - "integrity": "sha512-m9NGPsJ12dHey+/Or7516I4tj8EID1U8W2rnVwn3T9OpRMrL4XM9TloItNBqHhpAy1q7YfglxdsZP5721TMwvg==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/botbuilder-ai/-/botbuilder-ai-4.1.7.tgz", + "integrity": "sha512-TYYe7GrR9nZ55gJ+UBkbJnHea/LkJSwCjDuBncmRMsARGwxhYsBhLkkigY1Hzbl6+OuuPwZP/6OR0Xk9JzwgHA==", "requires": { "@microsoft/recognizers-text-date-time": "1.1.2", "@types/html-entities": "^1.2.16", "@types/node": "^9.3.0", "@types/request-promise-native": "^1.0.10", "azure-cognitiveservices-luis-runtime": "^1.0.0", - "botbuilder": "^4.1.5", + "botbuilder": "^4.1.7", "html-entities": "^1.2.1", "moment": "^2.20.1", "ms-rest": "^2.3.6", @@ -3036,29 +3122,29 @@ }, "dependencies": { "@types/node": { - "version": "9.6.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.36.tgz", - "integrity": "sha512-Fbw+AdRLL01vv7Rk7bYaNPecqmKoinJHGbpKnDpbUZmUj/0vj3nLqPQ4CNBzr3q2zso6Cq/4jHoCAdH78fvJrw==" + "version": "9.6.40", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.40.tgz", + "integrity": "sha512-M3HHoXXndsho/sTbQML2BJr7/uwNhMg8P0D4lb+UsM65JQZx268faiz9hKpY4FpocWqpwlLwa8vevw8hLtKjOw==" } } }, "botbuilder-azure": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/botbuilder-azure/-/botbuilder-azure-4.1.5.tgz", - "integrity": "sha512-3iyapL4wTm3D6QtkkQl4NbV8CtGzZjCetrWPLIrB04QBd6MM1AWIigAyo66KRl52O5/7k0KRpz0ZtIqwK4nRwA==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/botbuilder-azure/-/botbuilder-azure-4.1.7.tgz", + "integrity": "sha512-wdq+8Fgm8EscZqsg2lB+JJ4mtukQHOVm+mRpsgjFrYB84/SB0k3Z2wT4PHVNdNdvnSHAWOAXzigIJhPSIv9Hcw==", "requires": { "@types/node": "^9.3.0", "azure-storage": "^2.10.2", - "botbuilder": "^4.1.5", + "botbuilder": "^4.1.7", "documentdb": "1.14.5", "flat": "^4.0.0", "semaphore": "^1.1.0" }, "dependencies": { "@types/node": { - "version": "9.6.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.36.tgz", - "integrity": "sha512-Fbw+AdRLL01vv7Rk7bYaNPecqmKoinJHGbpKnDpbUZmUj/0vj3nLqPQ4CNBzr3q2zso6Cq/4jHoCAdH78fvJrw==" + "version": "9.6.40", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.40.tgz", + "integrity": "sha512-M3HHoXXndsho/sTbQML2BJr7/uwNhMg8P0D4lb+UsM65JQZx268faiz9hKpY4FpocWqpwlLwa8vevw8hLtKjOw==" } } }, @@ -3227,16 +3313,16 @@ } }, "botbuilder-dialogs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/botbuilder-dialogs/-/botbuilder-dialogs-4.1.5.tgz", - "integrity": "sha512-tLBe+clYg6VWsarR3a6NOzK8RqjJ3mXA4IBsQNSiqxIbEu/jjQTZ8OoZceye5rWfa67cbm2aihShqFB5WZX3nA==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/botbuilder-dialogs/-/botbuilder-dialogs-4.1.7.tgz", + "integrity": "sha512-XDR6HT5xJFMrepT5TOjuvDIz0/jpuemi4hGNjW7a0klucfXroSB3yFvg6aVinbjVE5uAejnEwoRAXevFZRg+RQ==", "requires": { "@microsoft/recognizers-text-choice": "1.1.2", "@microsoft/recognizers-text-date-time": "1.1.2", "@microsoft/recognizers-text-number": "1.1.2", "@microsoft/recognizers-text-suite": "1.1.2", "@types/node": "^9.3.0", - "botbuilder-core": "^4.1.5" + "botbuilder-core": "^4.1.7" }, "dependencies": { "@microsoft/recognizers-text-choice": { @@ -3274,9 +3360,40 @@ } }, "@types/node": { - "version": "9.6.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.36.tgz", - "integrity": "sha512-Fbw+AdRLL01vv7Rk7bYaNPecqmKoinJHGbpKnDpbUZmUj/0vj3nLqPQ4CNBzr3q2zso6Cq/4jHoCAdH78fvJrw==" + "version": "9.6.40", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.40.tgz", + "integrity": "sha512-M3HHoXXndsho/sTbQML2BJr7/uwNhMg8P0D4lb+UsM65JQZx268faiz9hKpY4FpocWqpwlLwa8vevw8hLtKjOw==" + }, + "botbuilder-core": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/botbuilder-core/-/botbuilder-core-4.1.7.tgz", + "integrity": "sha512-kfNOOpHVDLNdpYVMAefWjETXI4VsnDHgucEfKgANcCUrXmsYETlioHOCngUWLrFcaeVMJodeZvafIYl5NTgy0A==", + "requires": { + "assert": "^1.4.1", + "botframework-schema": "^4.1.7" + } + }, + "botframework-schema": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/botframework-schema/-/botframework-schema-4.1.7.tgz", + "integrity": "sha512-vPb5gHldmTIpUFx5uCdv/4XEsouMkXvSfQS2zsAC3VqAo29YESHYzNbr5HecRaUveb48NZ27+Djm0U0mLFxe9Q==", + "requires": { + "@types/node": "^9.3.0", + "ms-rest-js": "1.0.455" + } + }, + "ms-rest-js": { + "version": "1.0.455", + "resolved": "https://registry.npmjs.org/ms-rest-js/-/ms-rest-js-1.0.455.tgz", + "integrity": "sha512-RUDnFFNhk4ZdvOACg0yfaxmp5OzNwUcTIwgh/rVBeuNzgA7hOoVh5zFW06XmOtaBHXL2Bu/vWoQtzloEUlv9tw==", + "requires": { + "axios": "^0.18.0", + "form-data": "^2.3.2", + "tough-cookie": "^2.4.3", + "tslib": "^1.9.2", + "uuid": "^3.2.1", + "xml2js": "^0.4.19" + } } } }, @@ -3687,6 +3804,17 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "pragmatismo-io-framework": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/pragmatismo-io-framework/-/pragmatismo-io-framework-1.0.18.tgz", + "integrity": "sha512-oHzUxNbxXJ9DieUj9DbhMpryPWLdmrnzsTHxG9/a8/UUMFpHal5TR4hzh6JPkQqjhPjktYyvy2R2fdQiz114WQ==", + "requires": { + "azure-cognitiveservices-textanalytics": "^2.0.0", + "azure-search": "^0.0.21", + "ms-rest-azure": "^2.5.9", + "url-join": "^4.0.0-m2.0" + } + }, "sequelize": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.41.0.tgz", @@ -3719,9 +3847,9 @@ } }, "bottleneck": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.13.0.tgz", - "integrity": "sha512-9YmZ0aiKta2OAxTujKCS/INjGWCIGWK4Ff1nQpgHnR4CTjlk9jcnpaHOjPnMZPtqRXkqwKdtxZgvJ9udsXylaw==", + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.13.2.tgz", + "integrity": "sha512-DVS4Uv7xr4Ql0w9valPBaueLRnEtBepeoevDhWO0LBhyihICJ7RySyzPfyvPswanrXAAbWaF8Zx4QpxmIxHa/g==", "dev": true }, "boxen": { @@ -4001,7 +4129,7 @@ "dependencies": { "callsites": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true } @@ -4530,9 +4658,9 @@ "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" }, "commitizen": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-3.0.4.tgz", - "integrity": "sha512-djR5F7RBsGALyUEm/B1H/85nsN4L1F5DhWN+9/efSwqHDSyhw2MK6MF2VRuD26PUqGkQbcUlYO61btkTWjcjVw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-3.0.5.tgz", + "integrity": "sha512-WB9sz7qudArOsW1ninU8YGLNoXLQ5lJBZf538iQ7i96SXAkqVMZdmPtSyN4WFPM5PjQR7rWxDa+hzfGIJfrXUg==", "dev": true, "requires": { "cachedir": "2.1.0", @@ -4646,7 +4774,7 @@ }, "shelljs": { "version": "0.7.6", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.6.tgz", + "resolved": "http://registry.npmjs.org/shelljs/-/shelljs-0.7.6.tgz", "integrity": "sha1-N5zM+1a5HIYB5HkzVutTgpJN6a0=", "dev": true, "requires": { @@ -5050,9 +5178,9 @@ } }, "csv-parse": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-3.2.0.tgz", - "integrity": "sha512-1IBQ7PYHc5h6AXS4nyGsmicttbpUbNvlsgKxhvouh+kRaVug8xBWSiIeWdbPUQgjKG88hEGYjdU29fX73mo+6Q==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.0.1.tgz", + "integrity": "sha512-ehkwejEj05wwO7Q9JD+YSI6dNMIauHIroNU1RALrmRrqPoZIwRnfBtgq5GkU6i2RxZOJqjo3dtI1NrVSXvaimA==" }, "csv-parser": { "version": "1.12.1", @@ -5321,6 +5449,12 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, + "deepmerge": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.0.0.tgz", + "integrity": "sha512-a8z8bkgHsAML+uHLqmMS83HHlpy3PvZOOuiTQqaa3wu8ZVg3h0hqHk6aCsGdOnZV2XMM/FRimNGjUh0KCcmHBw==", + "dev": true + }, "defer-to-connect": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.0.1.tgz", @@ -5830,9 +5964,9 @@ } }, "env-ci": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-3.1.0.tgz", - "integrity": "sha512-+yFT8QX8W9bee0/fuzKvqt2vEhWvU3FXZ1P5xbveDq/EqcYLh4e0QNE16okUS3pawALDGXE9eCJPVeWY0/ilQA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-3.1.2.tgz", + "integrity": "sha512-qJ+ug5OEHEK6HyjhEB0z2tPJCmdvemQE3WUUYEe7qj7teZIJGjZK9elWB4kxE8qRdVHWl4aBvyVmX0Y5xlMbBw==", "dev": true, "requires": { "execa": "^1.0.0", @@ -6351,7 +6485,7 @@ }, "expand-range": { "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "resolved": "http://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { @@ -7233,7 +7367,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -8153,9 +8288,9 @@ } }, "hook-std": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-1.1.0.tgz", - "integrity": "sha512-aIyBZbZl3NS8XoSwIDQ+ZaiBuPOhhPWoBFA3QX0Q8hOMO8Tx4xGRTDnn/nl/LAtZWdieXzFC9ohAtTSnWrlHCQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-1.2.0.tgz", + "integrity": "sha512-yntre2dbOAjgQ5yoRykyON0D9T96BfshR8IuiL/r3celeHD8I/76w4qo8m3z99houR4Z678jakV3uXrQdSvW/w==", "dev": true }, "hosted-git-info": { @@ -8998,9 +9133,9 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "issue-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-3.0.0.tgz", - "integrity": "sha512-VWIhBdy0eOhlvpxOOMecBCHMpjx7lWVZcYpSzjD4dSdxptzI9TBR/cQEh057HL8+7jQKTLs+uCtezY/9VoveCA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-3.0.1.tgz", + "integrity": "sha512-5wdT3EE8Kq38x/hJD8QZCJ9scGoOZ5QnzwXyClkviSWTS+xOCE6hJ0qco3H5n5jCsFqpbofZCcMWqlXJzF72VQ==", "dev": true, "requires": { "lodash.capitalize": "^4.2.1", @@ -9542,6 +9677,12 @@ "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -9630,6 +9771,18 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", + "dev": true + }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", + "dev": true + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -9657,6 +9810,12 @@ "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=", "dev": true }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, "lodash.uniqby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", @@ -9770,9 +9929,9 @@ } }, "macos-release": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-1.1.0.tgz", - "integrity": "sha512-mmLbumEYMi5nXReB9js3WGsB8UE6cDBWyIO62Z4DNx6GbRhDxHNjA1MlzSpJ2S2KM1wyiPRA0d19uHWYYvMHjA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.0.0.tgz", + "integrity": "sha512-iCM3ZGeqIzlrH7KxYK+fphlJpCCczyHXc+HhRVbEu9uNTCrzYJjvvtefzeKTCVHd5AP/aD/fzC80JZ4ZP+dQ/A==", "dev": true }, "make-dir": { @@ -9836,9 +9995,9 @@ } }, "marked": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.5.1.tgz", - "integrity": "sha512-iUkBZegCZou4AdwbKTwSW/lNDcz5OuRSl3qdcl31Ia0B2QPG0Jn+tKblh/9/eP9/6+4h27vpoh8wel/vQOV0vw==" + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.5.2.tgz", + "integrity": "sha512-fdZvBa7/vSQIZCi4uuwo2N3q+7jJURpMVCcbaX0S1Mg65WZ5ilXvC67MviJAsdjqqgD+CEq4RKo5AYGgINkVAA==" }, "marked-terminal": { "version": "3.1.1", @@ -14917,6 +15076,12 @@ "isobject": "^3.0.1" } }, + "octokit-pagination-methods": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz", + "integrity": "sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==", + "dev": true + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -15009,13 +15174,13 @@ } }, "os-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/os-name/-/os-name-2.0.1.tgz", - "integrity": "sha1-uaOGNhwXrjohc27wWZQFyajF3F4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.0.0.tgz", + "integrity": "sha512-7c74tib2FsdFbQ3W+qj8Tyd1R3Z6tuVRNNxXjJcZ4NgjIEQU9N/prVMqcW29XZPXGACqaXN3jq58/6hoaoXH6g==", "dev": true, "requires": { - "macos-release": "^1.0.0", - "win-release": "^1.0.0" + "macos-release": "^2.0.0", + "windows-release": "^3.1.0" } }, "os-tmpdir": { @@ -15460,9 +15625,9 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "pragmatismo-io-framework": { - "version": "1.0.18", - "resolved": "https://registry.npmjs.org/pragmatismo-io-framework/-/pragmatismo-io-framework-1.0.18.tgz", - "integrity": "sha512-oHzUxNbxXJ9DieUj9DbhMpryPWLdmrnzsTHxG9/a8/UUMFpHal5TR4hzh6JPkQqjhPjktYyvy2R2fdQiz114WQ==", + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/pragmatismo-io-framework/-/pragmatismo-io-framework-1.0.19.tgz", + "integrity": "sha512-70RpF84fddSrMHPwWDQZLKjZoEwTyoqBAoGywVaXOaz7qi11bEWIxQgbDt26cclkQMkK1RfMBJvkH38t5l9lWA==", "requires": { "azure-cognitiveservices-textanalytics": "^2.0.0", "azure-search": "^0.0.21", @@ -16396,9 +16561,9 @@ "integrity": "sha1-Az1go60g7PLgCUDRT5eCNGV3QzU=" }, "semantic-release": { - "version": "15.12.0", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-15.12.0.tgz", - "integrity": "sha512-s8JQ0twxSTat4aIKy8AVPm6gP+2OlkMyyTGeeWFopOXQVa3Bg0LPNjdhsUL90MHvh9aVy9k0/ym4DWmOcGxOMQ==", + "version": "15.12.4", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-15.12.4.tgz", + "integrity": "sha512-po30Te9E26v3Qb/G9pXFO6lCTFO07zvliqH00vmfuCoAjl1Wpg9SKb9dXVFM7BTdg5Fr/KqbdOsqHkT7kR4FeQ==", "dev": true, "requires": { "@semantic-release/commit-analyzer": "^6.1.0", @@ -16646,9 +16811,9 @@ } }, "yargs": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.4.tgz", - "integrity": "sha512-f5esswlPO351AnejaO2A1ZZr0zesz19RehQKwiRDqWtrraWrJy16tsUIKgDXFMVytvNOHPVmTiaTh3wO67I0fQ==", + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "dev": true, "requires": { "cliui": "^4.0.0", @@ -16662,13 +16827,13 @@ "string-width": "^2.0.0", "which-module": "^2.0.0", "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.0" + "yargs-parser": "^11.1.1" } }, "yargs-parser": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.0.tgz", - "integrity": "sha512-lGA5HsbjkpCfekDBHAhgE5OE8xEoqiUDylowr+BvhRCwG1xVYTsd8hx2CYC0NY4k9RIgJeybFTG2EZW4P2aN1w==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -17995,9 +18160,9 @@ } }, "tapable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.0.tgz", - "integrity": "sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz", + "integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==", "dev": true }, "tar": { @@ -18578,9 +18743,9 @@ "integrity": "sha1-pZUhTHKY24M57u7gg+TRC9jLjdk=" }, "ts-loader": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.3.0.tgz", - "integrity": "sha512-lGSNs7szRFj/rK9T1EQuayE3QNLg6izDUxt5jpmq0RG1rU2bapAt7E7uLckLCUPeO1jwxCiet2oRaWovc53UAg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.3.1.tgz", + "integrity": "sha512-fDDgpBH3SR8xlt2MasLdz3Yy611PQ/UY/KGyo7TgXhTRU/6sS8uGG0nJYnU1OdFBNKcoYbId1UTNaAOUn+i41g==", "dev": true, "requires": { "chalk": "^2.3.0", @@ -18638,9 +18803,9 @@ } }, "tslint-microsoft-contrib": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.2.1.tgz", - "integrity": "sha512-PDYjvpo0gN9IfMULwKk0KpVOPMhU6cNoT9VwCOLeDl/QS8v8W2yspRpFFuUS7/c5EIH/n8ApMi8TxJAz1tfFUA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tslint-microsoft-contrib/-/tslint-microsoft-contrib-6.0.0.tgz", + "integrity": "sha512-R//efwn+34IUjTJeYgNDAJdzG0jyLWIehygPt/PHuZAieTolFVS56FgeFW7DOLap9ghXzMiFPTmDgm54qaL7QA==", "dev": true, "requires": { "tsutils": "^2.27.2 <2.29.0" @@ -18749,6 +18914,11 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/marked/-/marked-0.4.0.tgz", "integrity": "sha1-mtLCp6F5HxCoUuARL3e1cdzhDGY=" + }, + "typescript": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz", + "integrity": "sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==" } } }, @@ -18763,17 +18933,17 @@ "integrity": "sha512-/VMawTW4NnUUsgq0o8O37y9MmXFaOCDrH1dvDg7SZUS5ZSpUPSILVWwGJP+7g4I8vKZ5bBKZKHfPIEA4xUC+PQ==" }, "typedoc-plugin-markdown": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-1.1.18.tgz", - "integrity": "sha512-hE/2wM3NzMY/6K2wLtW7koVALLvVYm7w0mx+GFH3Er5gTioEMrUtd70Yf6Mb88fG6IvUWx1mX10ZD/ZR0AXS2w==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-1.1.19.tgz", + "integrity": "sha512-dk1J9NHW3c1cMmcmTrw55U7ke6zadB3hZL7y7tkLQ6FeJGkz6SEMuexjVOq5rltuW7JeLqt1ngNKAwG9dXiMgQ==", "requires": { "turndown": "^5.0.1" } }, "typescript": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz", - "integrity": "sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.1.tgz", + "integrity": "sha512-jw7P2z/h6aPT4AENXDGjcfHTu5CSqzsbZc6YlUIebTyBAq8XaKp78x7VcSh30xwSCcsu5irZkYZUSFP1MrAMbg==" }, "typescript-eslint-parser": { "version": "16.0.1", @@ -18922,12 +19092,12 @@ } }, "universal-user-agent": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-2.0.1.tgz", - "integrity": "sha512-vz+heWVydO0iyYAa65VHD7WZkYzhl7BeNVy4i54p4TF8OMiLSXdbuQe4hm+fmWAsL+rVibaQHXfhvkw3c1Ws2w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-2.0.2.tgz", + "integrity": "sha512-nOwvHWLH3dBazyuzbECPA5uVFNd7AlgviXRHgR4yf48QqitIvpdncRrxMbZNMpPPEfgz30I9ubd1XmiJiqsTrg==", "dev": true, "requires": { - "os-name": "^2.0.1" + "os-name": "^3.0.0" } }, "universalify": { @@ -19255,9 +19425,9 @@ "integrity": "sha1-/IBORYzEYACbGiuWa8iBfSV4rvs=" }, "whatwg-mimetype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz", - "integrity": "sha512-5YSO1nMd5D1hY3WzAQV3PzZL83W3YeyR1yW9PcH26Weh1t+Vzh9B6XkDh7aXm83HBZ4nSMvkjvN2H2ySWIvBgw==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" }, "whatwg-url": { "version": "6.5.0", @@ -19332,13 +19502,30 @@ } } }, - "win-release": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/win-release/-/win-release-1.1.1.tgz", - "integrity": "sha1-X6VeAr58qTTt/BJmVjLoSbcuUgk=", + "windows-release": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.1.0.tgz", + "integrity": "sha512-hBb7m7acFgQPQc222uEQTmdcGLeBmQLNLFIh0rDk3CwFOBrfjefLzEfEfmpMq8Af/n/GnFf3eYf203FY1PmudA==", "dev": true, "requires": { - "semver": "^5.0.1" + "execa": "^0.10.0" + }, + "dependencies": { + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + } } }, "winston": { diff --git a/package.json b/package.json index b5b86b8b..2a3ab2e7 100644 --- a/package.json +++ b/package.json @@ -54,24 +54,24 @@ "async": "2.6.1", "async-promises": "0.2.1", "azure-arm-cognitiveservices": "2.4.0", - "azure-arm-resource": "7.2.0", + "azure-arm-resource": "7.2.1", "azure-arm-search": "^1.3.0-preview", "azure-arm-sql": "5.6.0", "azure-arm-website": "5.7.0", "bluebird": "^3.5.3", "body-parser": "1.18.3", - "botbuilder": "^4.1.5", - "botbuilder-ai": "^4.1.5", - "botbuilder-azure": "^4.1.5", + "botbuilder": "^4.1.7", + "botbuilder-ai": "^4.1.7", + "botbuilder-azure": "^4.1.7", "botbuilder-choices": "^4.0.0-preview1.2", - "botbuilder-dialogs": "^4.1.5", + "botbuilder-dialogs": "^4.1.7", "botbuilder-prompts": "^4.0.0-preview1.2", "botlib": "0.1.8", "chai": "4.2.0", "child_process": "^1.0.2", "chokidar": "2.0.4", "cli-spinner": "^0.2.8", - "csv-parse": "3.2.0", + "csv-parse": "4.0.1", "dotenv-extended": "2.3.0", "express": "4.16.4", "express-promise-router": "3.0.3", @@ -79,7 +79,7 @@ "fs-walk": "0.0.2", "ip": "^1.1.5", "localize": "0.4.7", - "marked": "0.5.1", + "marked": "0.5.2", "mocha": "5.2.0", "mocha-typescript": "1.1.17", "ms": "2.1.1", @@ -88,7 +88,7 @@ "ngrok": "^3.1.0", "nyc": "^13.1.0", "opn": "^5.4.0", - "pragmatismo-io-framework": "1.0.18", + "pragmatismo-io-framework": "1.0.19", "process-exists": "^3.1.0", "public-ip": "^2.4.0", "reflect-metadata": "0.1.12", @@ -105,8 +105,8 @@ "ts-node": "7.0.1", "typedoc": "0.13.0", "typedoc-plugin-external-module-name": "^1.1.3", - "typedoc-plugin-markdown": "^1.1.18", - "typescript": "3.1.6", + "typedoc-plugin-markdown": "^1.1.19", + "typescript": "3.2.1", "url-join": "4.0.0", "vbscript-to-typescript": "^1.0.8", "wait-until": "0.0.2", @@ -117,17 +117,17 @@ "@semantic-release/changelog": "^3.0.1", "@semantic-release/commit-analyzer": "^6.1.0", "@semantic-release/git": "^7.0.5", - "@semantic-release/github": "^5.2.1", + "@semantic-release/github": "^5.2.5", "@semantic-release/npm": "^5.1.1", "@semantic-release/release-notes-generator": "^7.1.4", "@types/chai": "4.1.7", "@types/mocha": "5.2.5", - "@types/sequelize": "4.27.30", + "@types/sequelize": "4.27.32", "@types/url-join": "0.8.2", "@types/winston": "2.4.4", "ban-sensitive-files": "1.9.2", "chai": "^4.2.0", - "commitizen": "^3.0.4", + "commitizen": "^3.0.5", "coveralls": "^3.0.2", "cz-conventional-changelog": "^2.1.0", "dependency-check": "3.2.1", @@ -138,12 +138,12 @@ "nsp": "3.2.1", "pre-git": "3.17.1", "prettier-standard": "8.0.1", - "semantic-release": "^15.12.0", + "semantic-release": "^15.12.4", "standard": "12.0.1", "travis-deploy-once": "5.0.9", - "ts-loader": "^5.3.0", + "ts-loader": "^5.3.1", "tslint": "^5.11.0", - "tslint-microsoft-contrib": "^5.2.1" + "tslint-microsoft-contrib": "^6.0.0" }, "eslintConfig": { "env": { diff --git a/packages/admin.gbapp/dialogs/AdminDialog.ts b/packages/admin.gbapp/dialogs/AdminDialog.ts index a12f1910..12557e96 100644 --- a/packages/admin.gbapp/dialogs/AdminDialog.ts +++ b/packages/admin.gbapp/dialogs/AdminDialog.ts @@ -57,19 +57,27 @@ export class AdminDialog extends IGBDialog { const packageName = text.split(' ')[1]; const importer = new GBImporter(min.core); const deployer = new GBDeployer(min.core, importer); - await deployer.undeployPackageFromLocalPath( - min.instance, - UrlJoin('packages', packageName) - ); + await deployer.undeployPackageFromLocalPath(min.instance, UrlJoin('packages', packageName)); } public static async deployPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) { const packageName = text.split(' ')[1]; const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH'); - await deployer.deployPackageFromLocalPath(min, - UrlJoin(additionalPath, packageName) - ); + await deployer.deployPackageFromLocalPath(min, UrlJoin(additionalPath, packageName)); } + + public static async rebuildIndexPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) { + await deployer.rebuildIndex(min.instance); + } + + public static async addConnectionCommand(text: any, min: GBMinInstance) { + const packageName = text.split(' ')[1]; + const importer = new GBImporter(min.core); + const admin = new GBAdminService(min.core); + // TODO: await admin.addConnection + } + + /** * Setup dialogs flows and define services call. * @@ -87,23 +95,22 @@ export class AdminDialog extends IGBDialog { async step => { const locale = step.context.activity.locale; const prompt = Messages[locale].authenticate; - await step.prompt('textPrompt', prompt); - return await step.next(); + + return await step.prompt('textPrompt', prompt); }, async step => { const locale = step.context.activity.locale; const password = step.result; - if ( - password === GBConfigService.get('ADMIN_PASS') && - GBAdminService.StrongRegex.test(password) - ) { + + if (password === GBConfigService.get('ADMIN_PASS')) { await step.context.sendActivity(Messages[locale].welcome); - await step.prompt('textPrompt', Messages[locale].which_task); + + return await step.prompt('textPrompt', Messages[locale].which_task); } else { - await step.prompt('textPrompt', Messages[locale].wrong_password); - await step.endDialog(); + await step.context.sendActivity(Messages[locale].wrong_password); + + return await step.endDialog(); } - return await step.next(); }, async step => { const locale = step.context.activity.locale; @@ -114,20 +121,32 @@ export class AdminDialog extends IGBDialog { let unknownCommand = false; if (text === 'quit') { - await step.replaceDialog('/'); + return await step.replaceDialog('/'); } else if (cmdName === 'createFarm') { await AdminDialog.createFarmCommand(text, deployer); - await step.replaceDialog('/admin', { firstRun: false }); + + return await step.replaceDialog('/admin', { firstRun: false }); } else if (cmdName === 'deployPackage') { await AdminDialog.deployPackageCommand(min, text, deployer); - await step.replaceDialog('/admin', { firstRun: false }); + + return await step.replaceDialog('/admin', { firstRun: false }); } else if (cmdName === 'redeployPackage') { await AdminDialog.undeployPackageCommand(text, min); await AdminDialog.deployPackageCommand(min, text, deployer); - await step.replaceDialog('/admin', { firstRun: false }); + + return await step.replaceDialog('/admin', { firstRun: false }); + } else if (cmdName === 'rebuildIndex') { + await AdminDialog.rebuildIndexPackageCommand(min, text, deployer); + + return await step.replaceDialog('/admin', { firstRun: false }); + } else if (cmdName === 'addConnection') { + await AdminDialog.addConnectionCommand(min, text, deployer); + + return await step.replaceDialog('/admin', { firstRun: false }); } else if (cmdName === 'undeployPackage') { await AdminDialog.undeployPackageCommand(text, min); - await step.replaceDialog('/admin', { firstRun: false }); + + return await step.replaceDialog('/admin', { firstRun: false }); } else if (cmdName === 'setupSecurity') { await AdminDialog.setupSecurity(min, step); } else { @@ -137,13 +156,11 @@ export class AdminDialog extends IGBDialog { if (unknownCommand) { await step.context.sendActivity(Messages[locale].unknown_command); } else { - await step.context.sendActivity( - Messages[locale].finshed_working(cmdName) - ); + await step.context.sendActivity(Messages[locale].finshed_working(cmdName)); } await step.endDialog(); - await step.replaceDialog('/answer', { query: text }); - return await step.next(); + + return await step.replaceDialog('/answer', { query: text }); } ]) ); @@ -151,17 +168,9 @@ export class AdminDialog extends IGBDialog { private static async setupSecurity(min: any, step: any) { const locale = step.activity.locale; - const state = `${min.instance.instanceId}${Math.floor( - Math.random() * 1000000000 - )}`; - await min.adminService.setValue( - min.instance.instanceId, - 'AntiCSRFAttackState', - state - ); - const url = `https://login.microsoftonline.com/${ - min.instance.authenticatorTenant - }/oauth2/authorize?client_id=${ + const state = `${min.instance.instanceId}${Math.floor(Math.random() * 1000000000)}`; + await min.adminService.setValue(min.instance.instanceId, 'AntiCSRFAttackState', state); + const url = `https://login.microsoftonline.com/${min.instance.authenticatorTenant}/oauth2/authorize?client_id=${ min.instance.authenticatorClientId }&response_type=code&redirect_uri=${min.instance.botEndpoint}/${ min.instance.botId diff --git a/packages/analytics.gblib/index.ts b/packages/analytics.gblib/index.ts index 63ba890b..1c29bb77 100644 --- a/packages/analytics.gblib/index.ts +++ b/packages/analytics.gblib/index.ts @@ -43,22 +43,11 @@ import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib'; import { Sequelize } from 'sequelize-typescript'; export class GBAnalyticsPackage implements IGBPackage { - public sysPackages: IGBPackage[] = null; - public loadPackage(core: IGBCoreService, sequelize: Sequelize): void { - - } - public unloadPackage(core: IGBCoreService): void { - - } - public loadBot(min: GBMinInstance): void { - - } - public unloadBot(min: GBMinInstance): void { - - } - public onNewSession(min: GBMinInstance, step: any): void { - - } + public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {} + public unloadPackage(core: IGBCoreService): void {} + public loadBot(min: GBMinInstance): void {} + public unloadBot(min: GBMinInstance): void {} + public onNewSession(min: GBMinInstance, step: any): void {} } diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 7dd5204b..6f520fdd 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -410,7 +410,7 @@ export class GBMinService { // Processes messages. } else if (context.activity.type === 'message') { // Checks for /admin request. - if (context.activity.text === 'vba') { + if (context.activity.text === 'alpha-vba') { min.sandbox.context = context; min.sandbox.step = step; min.sandbox['bot'].bind(min.sandbox); diff --git a/packages/default.gbdialog/bot.vbs b/packages/default.gbdialog/bot.vbs index 737f6ba3..d5ddca66 100644 --- a/packages/default.gbdialog/bot.vbs +++ b/packages/default.gbdialog/bot.vbs @@ -10,6 +10,9 @@ ' ' General Bots Copyright (c) Pragmatismo.io. All rights reserved. ' Licensed under the AGPL-3.0. +' +' This BASIC file is based on this JavaScript file by Rodrigo Ruotolo: +' -> http://jsfiddle.net/roderick/dym05hsy ' ' According to our dual licensing model, this program can be used either ' under the terms of the GNU Affero General Public License, version 3, @@ -31,6 +34,8 @@ ' '**************************************************************************** + + talk ("How many installments do you want to pay your Credit?") installments = hear () @@ -85,6 +90,8 @@ else end if + ' TODO: This must be reviewed in terms of financing logic. + nInstallments = parseInt(installments) vAmmount = parseFloat(ammount) initialPayment = parseFloat(vAmmount) * 0.3 ' 30% of the value diff --git a/packages/default.gbui/package-lock.json b/packages/default.gbui/package-lock.json index a586ba92..3dec0118 100644 --- a/packages/default.gbui/package-lock.json +++ b/packages/default.gbui/package-lock.json @@ -5621,11 +5621,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5638,15 +5640,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5749,7 +5754,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5759,6 +5765,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5771,17 +5778,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5798,6 +5808,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5870,7 +5881,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5880,6 +5892,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5985,6 +5998,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -12279,6 +12293,14 @@ "aproba": "^1.1.1" } }, + "rxjs": { + "version": "5.5.12", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", + "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", + "requires": { + "symbol-observable": "1.0.1" + } + }, "rxjs-compat": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/rxjs-compat/-/rxjs-compat-6.3.3.tgz", -- 2.39.5