fix(all): TS Lint and minor fixes.

This commit is contained in:
Rodrigo Rodriguez 2020-12-31 15:36:19 -03:00
parent b33a8b5341
commit e7d7a1a4b2
44 changed files with 768 additions and 899 deletions

188
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "botserver",
"version": "2.0.45",
"version": "2.0.79",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -87,15 +87,10 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
},
"universal-user-agent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"uuid": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
}
}
},
@ -1325,28 +1320,6 @@
"dev": true,
"optional": true
},
"@dabh/diagnostics": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz",
"integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==",
"requires": {
"colorspace": "1.1.x",
"enabled": "2.0.x",
"kuler": "^2.0.0"
},
"dependencies": {
"enabled": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"kuler": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
}
}
},
"@derhuerst/http-basic": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.1.tgz",
@ -4569,9 +4542,9 @@
},
"dependencies": {
"@types/node": {
"version": "10.17.46",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.46.tgz",
"integrity": "sha512-Tice8a+sJtlP9C1EUo0DYyjq52T37b3LexVu3p871+kfIBIN+OQ7PKPei1oF3MgF39olEpUfxaLtD+QFc1k69Q=="
"version": "10.17.50",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.50.tgz",
"integrity": "sha512-vwX+/ija9xKc/z9VqMCdbf4WYcMTGsI0I/L/6shIF3qXURxZOhPQlPRHtjTpiNhAwn0paMJzlOQqw6mAGEQnTA=="
}
}
},
@ -4671,9 +4644,9 @@
}
},
"botlib": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/botlib/-/botlib-1.7.0.tgz",
"integrity": "sha512-U1kccXjruGNRWvAM/bvJ+r13hh/aWjrxk/Ws5jWZlOJeEmNMgO6yCNVjOp7ZaPYO7w4l/Tver2w8BGN9zZ4ibw==",
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/botlib/-/botlib-1.7.1.tgz",
"integrity": "sha512-Nu518PUt9o8ezuhOMhKSnbS0qkHt6XAR2RVzxEbyDpxyVyD7zKcBsHvnhTx8/jdRLR9O5MuHa4dD4jSxWWuSww==",
"requires": {
"async": "3.2.0",
"botbuilder": "4.11.0",
@ -4686,10 +4659,10 @@
"ms": "2.1.2",
"pragmatismo-io-framework": "1.0.20",
"reflect-metadata": "0.1.13",
"sequelize": "6.3.5",
"sequelize": "5.21.5",
"sequelize-typescript": "1.1.0",
"underscore": "1.11.0",
"winston": "3.3.3"
"winston": "3.2.1"
},
"dependencies": {
"async": {
@ -4697,14 +4670,6 @@
"resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
"integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw=="
},
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"iconv-lite": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
@ -4713,84 +4678,10 @@
"safer-buffer": ">= 2.1.2 < 3.0.0"
}
},
"is-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw=="
},
"one-time": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
"requires": {
"fn.name": "1.x.x"
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
},
"sequelize": {
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.3.5.tgz",
"integrity": "sha512-MiwiPkYSA8NWttRKAXdU9h0TxP6HAc1fl7qZmMO/VQqQOND83G4nZLXd0kWILtAoT9cxtZgFqeb/MPYgEeXwsw==",
"requires": {
"debug": "^4.1.1",
"dottie": "^2.0.0",
"inflection": "1.12.0",
"lodash": "^4.17.15",
"moment": "^2.26.0",
"moment-timezone": "^0.5.31",
"retry-as-promised": "^3.2.0",
"semver": "^7.3.2",
"sequelize-pool": "^6.0.0",
"toposort-class": "^1.0.1",
"uuid": "^8.1.0",
"validator": "^10.11.0",
"wkx": "^0.5.0"
}
},
"underscore": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.11.0.tgz",
"integrity": "sha512-xY96SsN3NA461qIRKZ/+qox37YXPtSBswMGfiNptr+wrt6ds4HaMw23TP612fEyGekRE6LNRiLYr/aqbHXNedw=="
},
"uuid": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
},
"validator": {
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz",
"integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw=="
},
"winston": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz",
"integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==",
"requires": {
"@dabh/diagnostics": "^2.0.2",
"async": "^3.1.0",
"is-stream": "^2.0.0",
"logform": "^2.2.0",
"one-time": "^1.0.0",
"readable-stream": "^3.4.0",
"stack-trace": "0.0.x",
"triple-beam": "^1.3.0",
"winston-transport": "^4.4.0"
}
}
}
},
@ -5725,8 +5616,7 @@
"colornames": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz",
"integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=",
"dev": true
"integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y="
},
"colors": {
"version": "1.4.0",
@ -7376,7 +7266,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz",
"integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==",
"dev": true,
"requires": {
"colorspace": "1.1.x",
"enabled": "1.0.x",
@ -7667,7 +7556,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz",
"integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=",
"dev": true,
"requires": {
"env-variable": "0.0.x"
}
@ -7792,8 +7680,7 @@
"env-variable": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz",
"integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==",
"dev": true
"integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg=="
},
"epub2": {
"version": "1.3.4",
@ -8820,11 +8707,6 @@
"integrity": "sha512-iEjGZ94OBMcESxnLorXNjJmtd/JtQYXUVrQpfwvtAKkuyawRmv+2LM6nqyOsOJkISEYbyY6ziudRE0u4VyPSVA==",
"dev": true
},
"fn.name": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
@ -11111,7 +10993,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz",
"integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==",
"dev": true,
"requires": {
"colornames": "^1.1.1"
}
@ -16901,8 +16782,7 @@
"one-time": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz",
"integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=",
"dev": true
"integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4="
},
"onetime": {
"version": "5.1.0",
@ -19601,11 +19481,6 @@
}
}
},
"sequelize-pool": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz",
"integrity": "sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg=="
},
"sequelize-typescript": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/sequelize-typescript/-/sequelize-typescript-1.1.0.tgz",
@ -21766,6 +21641,26 @@
}
}
},
"tslint-microsoft-contrib": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tslint-microsoft-contrib/-/tslint-microsoft-contrib-6.2.0.tgz",
"integrity": "sha512-6tfi/2tHqV/3CL77pULBcK+foty11Rr0idRDxKnteTaKm6gWF9qmaCNU17HVssOuwlYNyOmd9Jsmjd+1t3a3qw==",
"dev": true,
"requires": {
"tsutils": "^2.27.2 <2.29.0"
},
"dependencies": {
"tsutils": {
"version": "2.28.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.28.0.tgz",
"integrity": "sha512-bh5nAtW0tuhvOJnx1GLRn5ScraRLICGyJV5wJhtRWOLsxW70Kk5tZtpK3O/hW6LDnqKS9mlUMPZj9fEMJ0gxqA==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
}
}
}
},
"tsutils": {
"version": "3.17.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
@ -22087,8 +21982,7 @@
"universal-user-agent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==",
"dev": true
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"universalify": {
"version": "0.1.2",
@ -22559,7 +22453,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz",
"integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==",
"dev": true,
"requires": {
"async": "^2.6.1",
"diagnostics": "^1.1.1",
@ -22576,7 +22469,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@ -22594,14 +22486,6 @@
"triple-beam": "^1.2.0"
}
},
"wkx": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz",
"integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==",
"requires": {
"@types/node": "*"
}
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",

View file

@ -112,9 +112,6 @@
"washyourmouthoutwithsoap": "1.0.2"
},
"devDependencies": {
"ngrok": "3.3.0",
"typedoc": "0.19.2",
"simple-commit-message": "4.0.13",
"@types/url-join": "4.0.0",
"@types/winston": "2.4.4",
"ban-sensitive-files": "1.9.14",
@ -123,12 +120,16 @@
"dependency-check": "4.1.0",
"git-issues": "1.3.1",
"license-checker": "25.0.1",
"ngrok": "3.3.0",
"nsp": "3.2.1",
"prettier-standard": "16.4.1",
"semantic-release": "17.2.4",
"simple-commit-message": "4.0.13",
"travis-deploy-once": "5.0.11",
"ts-node": "9.0.0",
"tslint": "6.1.2"
"tslint": "6.1.2",
"tslint-microsoft-contrib": "^6.2.0",
"typedoc": "0.19.2"
},
"eslintConfig": {
"env": {

View file

@ -63,7 +63,6 @@ export class GBAdminPackage implements IGBPackage {
GBLog.verbose(`onExchangeData called.`);
}
public async loadPackage(core: IGBCoreService, sequelize: Sequelize): Promise<void> {
core.sequelize.addModels([GuaribasAdmin]);
}

View file

@ -37,15 +37,15 @@
'use strict';
import { AuthenticationContext, TokenResponse } from 'adal-node';
import { IGBAdminService, IGBCoreService, IGBInstance, GBMinInstance, GBLog, IGBDeployer } from 'botlib';
import { GBLog, GBMinInstance, IGBAdminService, IGBCoreService, IGBDeployer, IGBInstance } from 'botlib';
import urlJoin = require('url-join');
import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService';
import { GuaribasInstance } from '../../core.gbapp/models/GBModel';
import { GBConfigService } from '../../core.gbapp/services/GBConfigService';
import { GuaribasAdmin } from '../models/AdminModel';
import { GBSharePointService } from '../../sharepoint.gblib/services/SharePointService';
import { GBImporter } from '../../core.gbapp/services/GBImporterService';
import { GBDeployer } from '../../core.gbapp/services/GBDeployer';
import { GBImporter } from '../../core.gbapp/services/GBImporterService';
import { GBSharePointService } from '../../sharepoint.gblib/services/SharePointService';
import { GuaribasAdmin } from '../models/AdminModel';
const Path = require('path');
const msRestAzure = require('ms-rest-azure');
const PasswordGenerator = require('strict-password-generator').default;
@ -145,6 +145,57 @@ export class GBAdminService implements IGBAdminService {
return passwordGenerator.generatePassword(options);
}
public static async undeployPackageCommand(text: any, min: GBMinInstance) {
const packageName = text.split(' ')[1];
const importer = new GBImporter(min.core);
const deployer = new GBDeployer(min.core, importer);
const localFolder = Path.join('work', `${min.instance.botId}.gbai`, Path.basename(packageName));
await deployer.undeployPackageFromLocalPath(min.instance, localFolder);
}
public static isSharePointPath(path: string) {
return path.indexOf('sharepoint.com') > 0;
}
public static async deployPackageCommand(min: GBMinInstance, text: string, deployer: IGBDeployer) {
const packageName = text.split(' ')[1];
if (!this.isSharePointPath(packageName)) {
const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH');
if (additionalPath === undefined) {
throw new Error('ADDITIONAL_DEPLOY_PATH is not set and deployPackage was called.');
}
await deployer.deployPackage(min, urlJoin(additionalPath, packageName));
} else {
const siteName = text.split(' ')[1];
const folderName = text.split(' ')[2];
const s = new GBSharePointService();
const localFolder = Path.join('work', `${min.instance.botId}.gbai`, Path.basename(folderName));
GBLog.warn(`${GBConfigService.get('CLOUD_USERNAME')} must be authorized on SharePoint related site`);
await s.downloadFolder(
localFolder,
siteName,
folderName,
GBConfigService.get('CLOUD_USERNAME'),
GBConfigService.get('CLOUD_PASSWORD')
);
await deployer.deployPackage(min, localFolder);
}
}
public static async rebuildIndexPackageCommand(min: GBMinInstance, deployer: IGBDeployer) {
await deployer.rebuildIndex(
min.instance,
new AzureDeployerService(deployer).getKBSearchSchema(min.instance.searchIndex)
);
}
public static async syncBotServerCommand(min: GBMinInstance, deployer: GBDeployer) {
const serverName = `${min.instance.botId}-server`;
const service = await AzureDeployerService.createInstance(deployer);
service.syncBotServerRepository(min.instance.botId, serverName);
}
public async setValue(instanceId: number, key: string, value: string) {
const options = { where: {} };
options.where = { key: key };
@ -183,7 +234,7 @@ export class GBAdminService implements IGBAdminService {
public async acquireElevatedToken(instanceId: number): Promise<string> {
// TODO: Use boot bot as base for authentication.
let botId = GBConfigService.get('BOT_ID');
const botId = GBConfigService.get('BOT_ID');
instanceId = (await this.core.loadInstanceByBotId(botId)).instanceId;
return new Promise<string>(async (resolve, reject) => {
@ -228,56 +279,5 @@ export class GBAdminService implements IGBAdminService {
});
}
public static async undeployPackageCommand(text: any, min: GBMinInstance) {
const packageName = text.split(' ')[1];
const importer = new GBImporter(min.core);
const deployer = new GBDeployer(min.core, importer);
let localFolder = Path.join('work', `${min.instance.botId}.gbai`, Path.basename(packageName));
await deployer.undeployPackageFromLocalPath(min.instance, localFolder);
}
public static isSharePointPath(path: string) {
return path.indexOf('sharepoint.com') > 0;
}
public async publish(min: GBMinInstance, packageName: string, republish: boolean): Promise<void> { }
public static async deployPackageCommand(min: GBMinInstance, text: string, deployer: IGBDeployer) {
const packageName = text.split(' ')[1];
if (!this.isSharePointPath(packageName)) {
const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH');
if (additionalPath === undefined) {
throw new Error('ADDITIONAL_DEPLOY_PATH is not set and deployPackage was called.');
}
await deployer.deployPackage(min, urlJoin(additionalPath, packageName));
} else {
let siteName = text.split(' ')[1];
let folderName = text.split(' ')[2];
let s = new GBSharePointService();
let localFolder = Path.join('work', `${min.instance.botId}.gbai`, Path.basename(folderName));
GBLog.warn(`${GBConfigService.get('CLOUD_USERNAME')} must be authorized on SharePoint related site`);
await s.downloadFolder(
localFolder,
siteName,
folderName,
GBConfigService.get('CLOUD_USERNAME'),
GBConfigService.get('CLOUD_PASSWORD')
);
await deployer.deployPackage(min, localFolder);
}
}
public static async rebuildIndexPackageCommand(min: GBMinInstance, deployer: IGBDeployer) {
await deployer.rebuildIndex(
min.instance,
new AzureDeployerService(deployer).getKBSearchSchema(min.instance.searchIndex)
);
}
public static async syncBotServerCommand(min: GBMinInstance, deployer: GBDeployer) {
const serverName = `${min.instance.botId}-server`;
const service = await AzureDeployerService.createInstance(deployer);
service.syncBotServerRepository(min.instance.botId, serverName);
}
}

View file

@ -22,7 +22,7 @@ export const Messages = {
publish_must_be_admin: 'Seu telefone precisa estar com privilégios administrativos para realizar publicação.',
publish_success: 'Publicação realizada.',
publish_type_yes: 'Por favor, digite *Sim* para continuar com a publicação.',
publish_canceled: 'Publicação cancelada.',
publish_canceled: 'Publicação cancelada.'
},
'pt-BR': {
authenticate: 'Please, authenticate:',
@ -47,6 +47,6 @@ export const Messages = {
publish_must_be_admin: 'Seu telefone precisa estar com privilégios administrativos para realizar publicação.',
publish_success: 'Publicação realizada.',
publish_type_yes: 'Por favor, digite *Sim* para continuar com a publicação.',
publish_canceled: 'Publicação cancelada.',
publish_canceled: 'Publicação cancelada.'
}
};

View file

@ -58,7 +58,6 @@ import { GuaribasChannel, GuaribasInstance } from '../../core.gbapp/models/GBMod
import { GuaribasSubject } from '../../kb.gbapp/models';
import { GuaribasUser } from '../../security.gbapp/models';
/**
* A conversation that groups many messages.
*/
@ -74,7 +73,6 @@ export class GuaribasConversation extends Model<GuaribasConversation> {
@Column
public instanceId: number;
@ForeignKey(() => GuaribasSubject)
@Column
public startSubjectId: number;
@ -110,7 +108,6 @@ export class GuaribasConversation extends Model<GuaribasConversation> {
public startedBy: GuaribasUser;
}
/**
* A single message in a conversation.
*/

View file

@ -34,10 +34,10 @@
* @fileoverview General Bots server core.
*/
import { AzureText } from 'pragmatismo-io-framework';
import { GBServer } from '../../../src/app';
import { GuaribasUser } from '../../security.gbapp/models';
import { GuaribasConversation, GuaribasConversationMessage } from '../models';
import { GBServer } from '../../../src/app';
import { AzureText } from 'pragmatismo-io-framework';
/**
* Base services for Bot Analytics.
@ -55,16 +55,19 @@ export class AnalyticsService {
return await conversation.save();
}
public async updateConversationSuggestion(instanceId: number, conversationId: string, feedback: string, locale: string): Promise<number> {
public async updateConversationSuggestion(instanceId: number,
conversationId: string, feedback: string, locale: string): Promise<number> {
const minBoot = GBServer.globals.minBoot as any;
const rate = await AzureText.getSentiment(
minBoot.instance.textAnalyticsKey ? minBoot.instance.textAnalyticsKey : minBoot.instance.textAnalyticsKey,
minBoot.instance.textAnalyticsEndpoint ? minBoot.instance.textAnalyticsEndpoint : minBoot.instance.textAnalyticsEndpoint,
minBoot.instance.textAnalyticsKey ? minBoot.instance.textAnalyticsKey :
minBoot.instance.textAnalyticsKey,
minBoot.instance.textAnalyticsEndpoint ? minBoot.instance.textAnalyticsEndpoint :
minBoot.instance.textAnalyticsEndpoint,
locale,
feedback
);
const options = { where: { } };
options.where = { conversationId: conversationId, instanceId: instanceId };
const item = await GuaribasConversation.findOne(options);
@ -78,7 +81,6 @@ export class AnalyticsService {
}
public async createMessage(
instanceId: number,
conversation: GuaribasConversation,

View file

@ -104,7 +104,7 @@ export class StartDialog {
const instance = <IGBInstance>{};
instance.botId = botId;
instance.state ='active';
instance.state = 'active';
instance.cloudUsername = username;
instance.cloudPassword = password;
instance.cloudSubscriptionId = subscriptionId;

View file

@ -37,7 +37,7 @@
'use strict';
import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { GuaribasSchedule } from 'packages/core.gbapp/models/GBModel';
import { GuaribasSchedule } from '../core.gbapp/models/GBModel';
import { Sequelize } from 'sequelize-typescript';

View file

@ -54,7 +54,7 @@ import { GuaribasInstance } from '../../core.gbapp/models/GBModel';
@Table
//tslint:disable-next-line:max-classes-per-file
export class GuaribasSchedule extends Model<GuaribasSchedule> {
@Column
public name: string;

View file

@ -32,14 +32,14 @@
'use strict';
import { TurnContext, BotAdapter } from 'botbuilder';
import { WaterfallStepContext, WaterfallDialog } from 'botbuilder-dialogs';
import { BotAdapter, TurnContext } from 'botbuilder';
import { WaterfallDialog, WaterfallStepContext } from 'botbuilder-dialogs';
import { GBLog, GBMinInstance } from 'botlib';
import urlJoin = require('url-join');
import { GBDeployer } from '../../core.gbapp/services/GBDeployer';
import { Messages } from '../strings';
import { GBServer } from '../../../src/app';
import { GBDeployer } from '../../core.gbapp/services/GBDeployer';
import { SecService } from '../../security.gbapp/services/SecService';
import { Messages } from '../strings';
import { SystemKeywords } from './SystemKeywords';
/**
@ -48,8 +48,8 @@ import { SystemKeywords } from './SystemKeywords';
*/
export class DialogKeywords {
/**
* Reference to minimal bot instance.
/**
* Reference to minimal bot instance.
*/
public min: GBMinInstance;
@ -77,17 +77,17 @@ export class DialogKeywords {
/**
* Returns the today data filled in dd/mm/yyyy or mm/dd/yyyy.
*
*
* @example x = TODAY
*/
public async getToday(step) {
var d = new Date(),
let d = new Date(),
month = '' + (d.getMonth() + 1),
day = '' + d.getDate(),
year = d.getFullYear();
if (month.length < 2) month = '0' + month;
if (day.length < 2) day = '0' + day;
if (month.length < 2) { month = '0' + month; }
if (day.length < 2) { day = '0' + day; }
const locale = step.context.activity.locale;
switch (locale) {
@ -104,7 +104,7 @@ export class DialogKeywords {
/**
* Quits the dialog, currently required to get out of VM context.
*
*
* @example EXIT
*/
public async exit(step) {
@ -113,24 +113,24 @@ export class DialogKeywords {
/**
* Returns current time in format hh:dd.
*
*
* @example SAVE "file.xlsx", name, email, NOW
*
*
*/
public async getNow() {
const nowUTC = new Date();
const now = new Date((typeof nowUTC === "string" ?
const now = new Date((typeof nowUTC === 'string' ?
new Date(nowUTC) :
nowUTC).toLocaleString("en-US", { timeZone: process.env.DEFAULT_TIMEZONE }));
nowUTC).toLocaleString('en-US', { timeZone: process.env.DEFAULT_TIMEZONE }));
return now.getHours() + ':' + now.getMinutes();
}
/**
* Sends a file to a given mobile.
*
*
* @example SEND FILE TO "+199988887777", "image.jpg"
*
*
*/
public async sendFileTo(mobile, filename, caption) {
return await this.internalSendFile(null, mobile, filename, caption);
@ -138,47 +138,24 @@ export class DialogKeywords {
/**
* Sends a file to the current user.
*
*
* @example SEND FILE "image.jpg"
*
*
*/
public async sendFile(step, filename, caption) {
return await this.internalSendFile(step, null, filename, caption);
}
/**
* Processes the sending of the file.
*/
private async internalSendFile(step, mobile, filename, caption) {
if (filename.indexOf('.md') > -1) {
GBLog.info(`BASIC: Sending the contents of ${filename} markdown to mobile.`);
let md = await this.min.kbService.getAnswerTextByMediaName(this.min.instance.instanceId, filename);
await this.min.conversationalService.sendMarkdownToMobile(this.min, step, mobile, md);
} else {
GBLog.info(`BASIC: Sending the file ${filename} to mobile.`);
let url = urlJoin(
GBServer.globals.publicAddress,
'kb',
`${this.min.botId}.gbai`,
`${this.min.botId}.gbkb`,
'assets',
filename
);
await this.min.conversationalService.sendFile(this.min, step, mobile, url, caption);
}
}
/**
* Defines the current language of the bot conversation.
*
*
* @example SET LANGUAGE "pt"
*
*
*/
public async setLanguage(step, language) {
const user = await this.min.userProfile.get(step.context, {});
let sec = new SecService();
const sec = new SecService();
user.systemUser = await sec.updateUserLocale(user.systemUser.userId, language);
await this.min.userProfile.set(step.context, user);
@ -190,12 +167,20 @@ export class DialogKeywords {
public async userName(step) {
return step.context.activity.from.name;
}
/**
* OBSOLETE.
*/
public async getFrom(step) {
return await this.userMobile(step);
}
/**
* Returns current mobile number from user in conversation.
*
*
* @example SAVE "file.xlsx", name, email, MOBILE
*
*
*/
public async userMobile(step) {
if (isNaN(step.context.activity.from.id)) {
@ -207,9 +192,9 @@ export class DialogKeywords {
/**
* Shows the subject menu to the user
*
*
* @example MENU
*
*
*/
public async showMenu(step) {
return await step.beginDialog('/menu');
@ -217,9 +202,9 @@ export class DialogKeywords {
/**
* Performs the transfer of the conversation to a human agent.
*
*
* @example TRANSFER
*
*
*/
public async transfer(step) {
return await step.beginDialog('/t');
@ -227,9 +212,9 @@ export class DialogKeywords {
/**
* Hears something from user and put it in a variable
*
*
* @example HEAR name
*
*
*/
public async hear(step, promise, previousResolve, kind, ...args) {
function random(low, high) {
@ -253,4 +238,27 @@ export class DialogKeywords {
public async talk(step, text: string) {
return await this.min.conversationalService.sendText(this.min, step, text);
}
/**
* Processes the sending of the file.
*/
private async internalSendFile(step, mobile, filename, caption) {
if (filename.indexOf('.md') > -1) {
GBLog.info(`BASIC: Sending the contents of ${filename} markdown to mobile.`);
const md = await this.min.kbService.getAnswerTextByMediaName(this.min.instance.instanceId, filename);
await this.min.conversationalService.sendMarkdownToMobile(this.min, step, mobile, md);
} else {
GBLog.info(`BASIC: Sending the file ${filename} to mobile.`);
const url = urlJoin(
GBServer.globals.publicAddress,
'kb',
`${this.min.botId}.gbai`,
`${this.min.botId}.gbkb`,
'assets',
filename
);
await this.min.conversationalService.sendFile(this.min, step, mobile, url, caption);
}
}
}

View file

@ -38,7 +38,6 @@ import * as fs from 'fs';
import { GBDeployer } from '../../core.gbapp/services/GBDeployer';
import { TSCompiler } from './TSCompiler';
import { CollectionUtil } from 'pragmatismo-io-framework';
const walkPromise = require('walk-promise');
import urlJoin = require('url-join');
import { DialogKeywords } from './DialogKeywords';
import { Messages } from '../strings';
@ -47,7 +46,8 @@ import { GBConversationalService } from '../../core.gbapp/services/GBConversatio
const vm = require('vm');
const vb2ts = require('./vbscript-to-typescript');
const beautify = require('js-beautify').js;
var textract = require('textract');
const textract = require('textract');
const walkPromise = require('walk-promise');
const phoneUtil = require('google-libphonenumber').PhoneNumberUtil.getInstance();
const phone = require('phone');
@ -678,7 +678,7 @@ export class GBVMService extends GBService {
const promise = min.cbMap[id].promise;
delete min.cbMap[id];
try {
await promise(step, result);
if (step.activeDialog.state.options.previousResolve != undefined) {
step.activeDialog.state.options.previousResolve();

View file

@ -9,12 +9,12 @@ export const Messages = {
hi: (msg) => `Hello, ${msg}.`,
very_sorry_about_error: `I'm sorry to inform that there was an error which was recorded to be solved.`,
canceled: 'Canceled. If I can be useful, let me know how',
whats_email: "What's your E-mail address?",
which_language: "Please, type the language name you would like to talk through.",
validation_enter_valid_email: "Please enter a valid e-mail." ,
language_chosen: "Very good, so let's go..." ,
affirmative_sentences: /^(sim|s|positivo|afirmativo|claro|evidente|sem dúvida|confirmo|confirmar|confirmado|uhum|si|y|yes|sure)/i,
whats_email: 'What\'s your E-mail address?',
which_language: 'Please, type the language name you would like to talk through.',
validation_enter_valid_email: 'Please enter a valid e-mail.' ,
language_chosen: 'Very good, so let\'s go...' ,
affirmative_sentences: /^(sim|s|positivo|afirmativo|claro|evidente|sem dúvida|confirmo|confirmar|confirmado|uhum|si|y|yes|sure)/i
},
'pt-BR': {
show_video: 'Vou te mostrar um vídeo. Por favor, aguarde...',
@ -24,11 +24,11 @@ export const Messages = {
hi: (msg) => `Oi, ${msg}.`,
very_sorry_about_error: `Lamento, ocorreu um erro que já foi registrado para ser tratado.`,
canceled: 'Cancelado, avise como posso ser útil novamente.',
whats_email: "Qual seu e-mail?",
which_language: "Por favor, digite o idioma que você gostaria de usar para conversarmos.",
validation_enter_valid_email: "Por favor digite um email válido.",
language_chosen: "Muito bem, então vamos lá..." ,
affirmative_sentences: /^(sim|s|positivo|afirmativo|claro|evidente|sem dúvida|confirmo|confirmar|confirmado|uhum|si|y|yes|sure)/i,
whats_email: 'Qual seu e-mail?',
which_language: 'Por favor, digite o idioma que você gostaria de usar para conversarmos.',
validation_enter_valid_email: 'Por favor digite um email válido.',
language_chosen: 'Muito bem, então vamos lá...' ,
affirmative_sentences: /^(sim|s|positivo|afirmativo|claro|evidente|sem dúvida|confirmo|confirmar|confirmado|uhum|si|y|yes|sure)/i
}
};

View file

@ -64,8 +64,6 @@ export class GBConsolePackage implements IGBPackage {
public async onExchangeData(min: GBMinInstance, kind: string, data: any) {
GBLog.verbose(`onExchangeData called.`);
}
public async loadBot(min: GBMinInstance): Promise<void> {
this.channel = new ConsoleDirectLine(min.instance.webchatKey);
}

View file

@ -39,10 +39,10 @@
import { BotAdapter } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance, IGBDialog } from 'botlib';
import { Messages } from '../strings';
import { SecService } from '../../security.gbapp/services/SecService';
import { GBServer } from '../../../src/app';
import { SecService } from '../../security.gbapp/services/SecService';
import { GBConversationalService } from '../services/GBConversationalService';
import { Messages } from '../strings';
/**
* Dialog for the bot explains about itself.
*/
@ -59,16 +59,16 @@ export class SwitchBotDialog extends IGBDialog {
async step => {
const locale = step.context.activity.locale;
return await min.conversationalService.prompt (min, step, "Qual seria o código de ativação?");
return await min.conversationalService.prompt (min, step, 'Qual seria o código de ativação?');
},
async step => {
let sec = new SecService();
let from = step.context.activity.from.id;
const sec = new SecService();
const from = step.context.activity.from.id;
const botId = step.result;
const instance = await min.core.loadInstanceByBotId(botId);
await sec.updateUserInstance(from, instance.instanceId);
await min.conversationalService.sendText(min, step, `Opa, vamos lá!`);
return await step.next();
}
]));

View file

@ -39,9 +39,9 @@
import { BotAdapter } from 'botbuilder';
import {WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance, IGBDialog } from 'botlib';
import { Messages } from '../strings';
import { GBServer } from '../../../src/app';
import { GBConversationalService } from '../services/GBConversationalService';
import { Messages } from '../strings';
/**
* Dialog for Welcoming people.
@ -59,16 +59,15 @@ export class WelcomeDialog extends IGBDialog {
async step => {
if (GBServer.globals.entryPointDialog !== null &&
min.instance.botId === process.env.BOT_ID &&
step.context.activity.channelId === "webchat")
{
min.instance.botId === process.env.BOT_ID &&
step.context.activity.channelId === 'webchat') {
return step.replaceDialog(GBServer.globals.entryPointDialog);
}
const user = await min.userProfile.get(step.context, {});
const locale = step.context.activity.locale;
if (!user.once && step.context.activity.channelId === "webchat") {
if (!user.once && step.context.activity.channelId === 'webchat') {
user.once = true;
await min.userProfile.set(step.context, user);
const a = new Date();
@ -81,7 +80,7 @@ export class WelcomeDialog extends IGBDialog {
: Messages[locale].good_night;
await min.conversationalService.sendText(min, step, Messages[locale].hi(msg));
await step.replaceDialog('/ask', { firstTime: true });
if (

View file

@ -39,8 +39,8 @@
import { BotAdapter } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance, IGBDialog } from 'botlib';
import { Messages } from '../strings';
import { GBConversationalService } from '../services/GBConversationalService';
import { Messages } from '../strings';
/**
* Dialog for the bot explains about itself.
*/

View file

@ -50,7 +50,8 @@ import { GuaribasChannel, GuaribasException, GuaribasInstance, GuaribasPackage }
*/
export class GBCorePackage implements IGBPackage {
public sysPackages: IGBPackage[];
public CurrentEngineName = "guaribas-1.0.0";
public CurrentEngineName = 'guaribas-1.0.0';
public async loadPackage(core: IGBCoreService, sequelize: Sequelize): Promise<void> {
core.sequelize.addModels([GuaribasInstance, GuaribasPackage, GuaribasChannel, GuaribasException]);
}
@ -71,7 +72,6 @@ export class GBCorePackage implements IGBPackage {
GBLog.verbose(`onExchangeData called.`);
}
public async loadBot(min: GBMinInstance): Promise<void> {
WelcomeDialog.setup(min.bot, min);
WhoAmIDialog.setup(min.bot, min);

View file

@ -102,7 +102,6 @@ export class GuaribasInstance extends Model<GuaribasInstance>
@Column
public textAnalyticsEndpoint: string;
@Column({ type: DataType.STRING(64) })
public translatorKey: string;
@ -333,7 +332,7 @@ export class GuaribasException extends Model<GuaribasException> {
@Table
//tslint:disable-next-line:max-classes-per-file
export class GuaribasApplications extends Model<GuaribasApplications> {
@Column
public name: string;
@ -353,11 +352,10 @@ export class GuaribasApplications extends Model<GuaribasApplications> {
public updatedAt: Date;
}
@Table
//tslint:disable-next-line:max-classes-per-file
export class GuaribasSchedule extends Model<GuaribasSchedule> {
@Column
public name: string;

View file

@ -42,7 +42,7 @@ import { GBLog } from 'botlib';
* Base configuration for the server like storage.
*/
export class GBConfigService {
static getBoolean(value: string): boolean {
public static getBoolean(value: string): boolean {
return this.get(value) as unknown as boolean;
}
public static getServerPort(): string {

View file

@ -44,21 +44,21 @@ const child_process = require('child_process');
const graph = require('@microsoft/microsoft-graph-client');
const rimraf = require('rimraf');
import { GBError, GBLog, GBMinInstance, IGBCoreService, IGBInstance, IGBPackage, IGBDeployer } from 'botlib';
import { GBError, GBLog, GBMinInstance, IGBCoreService, IGBDeployer, IGBInstance, IGBPackage } from 'botlib';
import { AzureSearch } from 'pragmatismo-io-framework';
import { CollectionUtil } from 'pragmatismo-io-framework';
import { GBServer } from '../../../src/app';
import { GBVMService } from '../../basic.gblib/services/GBVMService';
import { GuaribasPackage } from '../models/GBModel';
import { GBAdminService } from './../../admin.gbapp/services/GBAdminService';
import { AzureDeployerService } from './../../azuredeployer.gbapp/services/AzureDeployerService';
import { KBService } from './../../kb.gbapp/services/KBService';
import { GBConfigService } from './GBConfigService';
import { GBImporter } from './GBImporterService';
import { GBVMService } from '../../basic.gblib/services/GBVMService';
import { CollectionUtil } from 'pragmatismo-io-framework';
const MicrosoftGraph = require('@microsoft/microsoft-graph-client');
/**
* Deployer service for bots, themes, ai and more.
* Deployer service for bots, themes, ai and more.
*/
export class GBDeployer implements IGBDeployer {
@ -77,7 +77,7 @@ export class GBDeployer implements IGBDeployer {
*/
public core: IGBCoreService;
/**
/**
* Reference to the importer service.
*/
public importer: GBImporter;
@ -112,7 +112,7 @@ export class GBDeployer implements IGBDeployer {
}
const botPackages: string[] = [];
const gbappPackages: string[] = [];
let generalPackages: string[] = [];
const generalPackages: string[] = [];
async function scanPackageDirectory(path) {
@ -126,7 +126,6 @@ export class GBDeployer implements IGBDeployer {
const dirs = getDirectories(path);
await CollectionUtil.asyncForEach(dirs, async element => {
// For each folder, checks its extensions looking for valid packages.
element = element.toLowerCase();
@ -166,7 +165,7 @@ export class GBDeployer implements IGBDeployer {
// Deploys all .gblib files first.
let list = [];
const list = [];
for (let index = 0; index < gbappPackages.length; index++) {
const element = gbappPackages[index];
if (element.endsWith('.gblib')) {
@ -187,7 +186,7 @@ export class GBDeployer implements IGBDeployer {
const instances = await core.loadInstances();
await CollectionUtil.asyncForEach(instances, async instance => {
this.mountGBKBAssets(`${instance.botId}.gbkb`,
instance.botId, `${instance.botId}.gbkb`);
instance.botId, `${instance.botId}.gbkb`);
});
GBLog.info(`Package deployment done.`);
@ -200,7 +199,7 @@ export class GBDeployer implements IGBDeployer {
// Creates a new row on the GuaribasInstance table.
let instance = await this.importer.createBotInstance(botId);
const instance = await this.importer.createBotInstance(botId);
const bootInstance = GBServer.globals.bootInstance;
// Gets the access token to perform service operations.
@ -210,7 +209,7 @@ export class GBDeployer implements IGBDeployer {
// Creates the MSFT application that will be associated to the bot.
const service = new AzureDeployerService(this);
let application = await service.createApplication(accessToken, botId);
const application = await service.createApplication(accessToken, botId);
// Fills new instance base information and get App secret.
@ -240,6 +239,7 @@ export class GBDeployer implements IGBDeployer {
*/
public async botExists(botId: string): Promise<boolean> {
const service = new AzureDeployerService(this);
return await service.botExists(botId);
}
@ -267,13 +267,9 @@ export class GBDeployer implements IGBDeployer {
instance.description,
`${publicAddress}/api/messages/${instance.botId}`
);
}
// Otherwise, perform the bot creation.
else {
let botId = GBConfigService.get('BOT_ID');
let bootInstance = await this.core.loadInstanceByBotId(botId);
} else {
const botId = GBConfigService.get('BOT_ID');
const bootInstance = await this.core.loadInstanceByBotId(botId);
instance.searchHost = bootInstance.searchHost;
instance.searchIndex = bootInstance.searchIndex;
@ -324,8 +320,8 @@ export class GBDeployer implements IGBDeployer {
public async publishNLP(instance: IGBInstance): Promise<void> {
const service = new AzureDeployerService(this);
const res = await service.publishNLP(instance.cloudLocation, instance.nlpAppId,
instance.nlpAuthoringKey);
if (res.status !== 200 && res.status !== 201) throw res.bodyAsText;
instance.nlpAuthoringKey);
if (res.status !== 200 && res.status !== 201) { throw res.bodyAsText; }
}
/**
@ -334,8 +330,8 @@ export class GBDeployer implements IGBDeployer {
public async trainNLP(instance: IGBInstance): Promise<void> {
const service = new AzureDeployerService(this);
const res = await service.trainNLP(instance.cloudLocation, instance.nlpAppId, instance.nlpAuthoringKey);
if (res.status !== 200 && res.status !== 202) throw res.bodyAsText;
let sleep = ms => {
if (res.status !== 200 && res.status !== 202) { throw res.bodyAsText; }
const sleep = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
@ -355,7 +351,7 @@ export class GBDeployer implements IGBDeployer {
instance.nlpAuthoringKey,
listData
);
if (res.status !== 200) throw res.bodyAsText;
if (res.status !== 200) { throw res.bodyAsText; }
}
/**
@ -363,7 +359,7 @@ export class GBDeployer implements IGBDeployer {
*/
public async deployBotFromLocalPath(localPath: string, publicAddress: string): Promise<void> {
const packageName = Path.basename(localPath);
let instance = await this.importer.importIfNotExistsBotPackage(undefined, packageName, localPath);
const instance = await this.importer.importIfNotExistsBotPackage(undefined, packageName, localPath);
await this.deployBotFull(instance, publicAddress);
}
@ -374,10 +370,10 @@ export class GBDeployer implements IGBDeployer {
// Connects to MSFT storage.
let token = await min.adminService.acquireElevatedToken(min.instance.instanceId);
let siteId = process.env.STORAGE_SITE_ID;
let libraryId = process.env.STORAGE_LIBRARY;
let client = MicrosoftGraph.Client.init({
const token = await min.adminService.acquireElevatedToken(min.instance.instanceId);
const siteId = process.env.STORAGE_SITE_ID;
const libraryId = process.env.STORAGE_LIBRARY;
const client = MicrosoftGraph.Client.init({
authProvider: done => {
done(null, token);
}
@ -387,24 +383,25 @@ export class GBDeployer implements IGBDeployer {
const botId = min.instance.botId;
const path = `/${botId}.gbai/${botId}.gbot`;
let res = await client
const res = await client
.api(`https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${libraryId}/drive/root:${path}:/children`)
.get();
// Finds Config.xlsx.
let document = res.value.filter(m => {
const document = res.value.filter(m => {
return m.name === 'Config.xlsx';
});
if (document === undefined || document.length === 0) {
GBLog.info(`Config.xlsx not found on .bot folder, check the package.`);
return null;
}
// Reads all rows in Config.xlsx that contains a pair of name/value
// and fills an object that is returned to be saved in params instance field.
let results = await client
const results = await client
.api(
`https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${libraryId}/drive/items/${document[0].id}/workbook/worksheets('General')/range(address='A7:B100')`
)
@ -416,6 +413,7 @@ export class GBDeployer implements IGBDeployer {
}
obj[results.text[index][0]] = results.text[index][1];
}
return obj;
}
@ -494,7 +492,7 @@ export class GBDeployer implements IGBDeployer {
return pck;
}
// Deploy platform packages here accordingly to their extension.
// Deploy platform packages here accordingly to their extension.
switch (packageType) {
case '.gbot':
@ -585,6 +583,7 @@ export class GBDeployer implements IGBDeployer {
case '.gbkb':
const service = new KBService(this.core.sequelize);
rimraf.sync(localPath);
return await service.undeployKbFromStorage(instance, this, p.packageId);
case '.gbui':
@ -712,7 +711,7 @@ export class GBDeployer implements IGBDeployer {
* Servers bot storage assets to be used by web, WhatsApp and other channels.
*/
public mountGBKBAssets(packageName: any, botId: string, filename: string) {
// Servers menu assets.
GBServer.globals.server.use(
@ -723,55 +722,18 @@ export class GBDeployer implements IGBDeployer {
// Servers all other assets in .gbkb folders.
const gbaiName = `${botId}.gbai`;
GBServer.globals.server.use(`/kb/${gbaiName}/${packageName}/assets`, express.static(urlJoin('work', gbaiName, filename, 'assets')));
GBServer.globals.server.use(`/kb/${gbaiName}/${packageName}/images`, express.static(urlJoin('work', gbaiName, filename, 'images')));
GBServer.globals.server.use(`/kb/${gbaiName}/${packageName}/audios`, express.static(urlJoin('work', gbaiName, filename, 'audios')));
GBServer.globals.server.use(`/kb/${gbaiName}/${packageName}/videos`, express.static(urlJoin('work', gbaiName, filename, 'videos')));
GBServer.globals.server.use(`/kb/${gbaiName}/${packageName}/assets`,
express.static(urlJoin('work', gbaiName, filename, 'assets')));
GBServer.globals.server.use(`/kb/${gbaiName}/${packageName}/images`,
express.static(urlJoin('work', gbaiName, filename, 'images')));
GBServer.globals.server.use(`/kb/${gbaiName}/${packageName}/audios`,
express.static(urlJoin('work', gbaiName, filename, 'audios')));
GBServer.globals.server.use(`/kb/${gbaiName}/${packageName}/videos`,
express.static(urlJoin('work', gbaiName, filename, 'videos')));
GBLog.info(`KB (.gbkb) assets accessible at: /kb/${botId}.gbai/${packageName}.`);
}
/**
* Determines if a given package is of system kind.
*/
private isSystemPackage(name: string): Boolean {
const names = [
'analytics.gblib',
'console.gblib',
'security.gbapp',
'whatsapp.gblib',
'sharepoint.gblib',
'core.gbapp',
'admin.gbapp',
'azuredeployer.gbapp',
'customer-satisfaction.gbapp',
'kb.gbapp'
];
return names.indexOf(name) > -1;
}
/**
* Performs the process of compiling all .gbapp folders.
*/
private async deployAppPackages(gbappPackages: string[], core: any, appPackages: any[]) {
// Loops through all ready to load .gbapp packages.
let appPackagesProcessed = 0;
await CollectionUtil.asyncForEach(gbappPackages, async e => {
const filenameOnly = Path.basename(e);
// Skips .gbapp inside deploy folder.
if (this.isSystemPackage(filenameOnly) === false) {
appPackagesProcessed = await this.callGBAppCompiler(e, core, appPackages, appPackagesProcessed);
}
});
return appPackagesProcessed;
}
/**
* Invokes Type Script compiler for a given .gbapp package (Node.js based).
*/
@ -792,7 +754,7 @@ export class GBDeployer implements IGBDeployer {
child_process.execSync('npm install', { cwd: gbappPath });
}
}
folder = Path.join(gbappPath, 'dist');
try {
@ -823,6 +785,48 @@ export class GBDeployer implements IGBDeployer {
}
appPackagesProcessed++;
}
return appPackagesProcessed;
}
/**
* Determines if a given package is of system kind.
*/
private isSystemPackage(name: string): Boolean {
const names = [
'analytics.gblib',
'console.gblib',
'security.gbapp',
'whatsapp.gblib',
'sharepoint.gblib',
'core.gbapp',
'admin.gbapp',
'azuredeployer.gbapp',
'customer-satisfaction.gbapp',
'kb.gbapp'
];
return names.indexOf(name) > -1;
}
/**
* Performs the process of compiling all .gbapp folders.
*/
private async deployAppPackages(gbappPackages: string[], core: any, appPackages: any[]) {
// Loops through all ready to load .gbapp packages.
let appPackagesProcessed = 0;
await CollectionUtil.asyncForEach(gbappPackages, async e => {
const filenameOnly = Path.basename(e);
// Skips .gbapp inside deploy folder.
if (this.isSystemPackage(filenameOnly) === false) {
appPackagesProcessed = await this.callGBAppCompiler(e, core, appPackages, appPackagesProcessed);
}
});
return appPackagesProcessed;
}
}

View file

@ -36,12 +36,12 @@
'use strict';
import { IGBCoreService, IGBInstance, GBMinInstance } from 'botlib';
import { GBMinInstance, IGBCoreService, IGBInstance } from 'botlib';
import fs = require('fs');
import urlJoin = require('url-join');
import { GBServer } from '../../../src/app';
import { GuaribasInstance } from '../models/GBModel';
import { GBConfigService } from './GBConfigService';
import { GBServer } from '../../../src/app';
/**
* Handles the importing of packages.
@ -53,8 +53,8 @@ export class GBImporter {
this.core = core;
}
public async importIfNotExistsBotPackage(botId: string,
packageName: string, localPath: string, additionalInstance: IGBInstance = null) {
public async importIfNotExistsBotPackage(botId: string,
packageName: string, localPath: string, additionalInstance: IGBInstance = null) {
const settingsJson = JSON.parse(fs.readFileSync(urlJoin(localPath, 'settings.json'), 'utf8'));
if (botId === undefined) {
botId = settingsJson.botId;
@ -84,10 +84,9 @@ export class GBImporter {
instance = await this.core.loadInstanceByBotId(botId);
}
if (instance != null && instance.botId === null) {
if (instance != undefined && instance.botId === null) {
console.log(`Null BotId after load instance with botId: ${botId}.`);
}
else{
} else {
instance = additionalInstance;
}
@ -95,8 +94,9 @@ export class GBImporter {
}
public async createBotInstance(botId: string) {
let fullSettingsJson = { ...GBServer.globals.bootInstance };
const fullSettingsJson = { ...GBServer.globals.bootInstance };
fullSettingsJson.botId = botId;
return await GuaribasInstance.create(fullSettingsJson);
}
@ -106,10 +106,10 @@ export class GBImporter {
localPath: string,
settingsJson: any
) {
let packageJson = JSON.parse(fs.readFileSync(urlJoin(localPath, 'package.json'), 'utf8'));
const packageJson = JSON.parse(fs.readFileSync(urlJoin(localPath, 'package.json'), 'utf8'));
const servicesJson = JSON.parse(fs.readFileSync(urlJoin(localPath, 'services.json'), 'utf8'));
let fullSettingsJson = { ...GBServer.globals.bootInstance, ...packageJson, ...settingsJson, ...servicesJson };
const fullSettingsJson = { ...GBServer.globals.bootInstance, ...packageJson, ...settingsJson, ...servicesJson };
if (botId !== undefined) {
fullSettingsJson.botId = botId;

View file

@ -51,7 +51,6 @@ import {
TurnContext,
UserState
} from 'botbuilder';
import { CollectionUtil, AzureText } from 'pragmatismo-io-framework';
import { ConfirmPrompt, OAuthPrompt, WaterfallDialog } from 'botbuilder-dialogs';
import {
GBDialogStep,
@ -63,28 +62,34 @@ import {
IGBInstance,
IGBPackage
} from 'botlib';
import { AzureText, CollectionUtil } from 'pragmatismo-io-framework';
import { MicrosoftAppCredentials } from 'botframework-connector';
import fs = require('fs');
import { GBServer } from '../../../src/app';
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService';
import { GuaribasConversationMessage } from '../../analytics.gblib/models';
import { AnalyticsService } from '../../analytics.gblib/services/AnalyticsService';
import { GBVMService } from '../../basic.gblib/services/GBVMService';
import { AskDialogArgs } from '../../kb.gbapp/dialogs/AskDialog';
import { KBService } from '../../kb.gbapp/services/KBService';
import { SecService } from '../../security.gbapp/services/SecService';
import { WhatsappDirectLine } from '../../whatsapp.gblib/services/WhatsappDirectLine';
import { Messages } from '../strings';
import { GBConfigService } from './GBConfigService';
import { GBDeployer } from './GBDeployer';
import { SecService } from '../../security.gbapp/services/SecService';
import { AnalyticsService } from '../../analytics.gblib/services/AnalyticsService';
import { WhatsappDirectLine } from '../../whatsapp.gblib/services/WhatsappDirectLine';
import fs = require('fs');
import { GuaribasConversationMessage } from '../../analytics.gblib/models';
import { GBVMService } from '../../basic.gblib/services/GBVMService';
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService';
import { GBConversationalService } from './GBConversationalService';
import { GBDeployer } from './GBDeployer';
/**
* Minimal service layer for a bot and encapsulation of BOT Framework calls.
*/
export class GBMinService {
/**
* Default General Bots User Interface package.
*/
private static uiPackage = 'default.gbui';
/**
* Main core service attached to this bot service.
*/
@ -105,11 +110,6 @@ export class GBMinService {
*/
public deployer: GBDeployer;
/**
* Default General Bots User Interface package.
*/
private static uiPackage = 'default.gbui';
/**
* Static initialization of minimal instance.
*
@ -127,118 +127,6 @@ export class GBMinService {
this.deployer = deployer;
}
private async WhatsAppCallback(req, res) {
try {
// Detects if the message is echo fro itself.
const id = req.body.messages[0].chatId.split('@')[0];
const senderName = req.body.messages[0].senderName;
const text = req.body.messages[0].body;
if (req.body.messages[0].fromMe) {
res.end();
return; // Exit here.
}
// Detects if the welcome message is enabled.
let activeMin;
if (process.env.WHATSAPP_WELCOME_DISABLED !== 'true') {
// Tries to find if user wants to switch bots.
let toSwitchMin = GBServer.globals.minInstances.filter(
p => p.instance.botId.toLowerCase() === text.toLowerCase()
)[0];
if (!toSwitchMin) {
toSwitchMin = GBServer.globals.minInstances.filter(p =>
p.instance.activationCode ? p.instance.activationCode.toLowerCase() === text.toLowerCase() : false
)[0];
}
// Find active bot instance.
activeMin = toSwitchMin ? toSwitchMin : GBServer.globals.minBoot;
// If it is the first time for the user, tries to auto-execute
// start dialog if any is specified in Config.xlsx.
let sec = new SecService();
let user = await sec.getUserFromSystemId(id);
if (user === null || user.hearOnDialog) {
user = await sec.ensureUser(activeMin.instance.instanceId, id, senderName, '', 'whatsapp', senderName);
let startDialog = user.hearOnDialog ?
user.hearOnDialog :
activeMin.core.getParam(activeMin.instance, 'Start Dialog', null);
GBLog.info(`Auto start dialog is now being called: ${startDialog}...`);
if (startDialog) {
req.body.messages[0].body = `${startDialog}`;
// Resets HEAR ON DIALOG value to none and passes
// current dialog to the direct line.
await sec.updateUserHearOnDialog(user.userId, null);
await (activeMin as any).whatsAppDirectLine.received(req, res);
}
else {
await (activeMin as any).whatsAppDirectLine.sendToDevice(
id,
`Olá! Seja bem-vinda(o)!\nMe chamo ${activeMin.instance.title}. Como posso ajudar? Pode me falar que eu te ouço, me manda um aúdio.`
);
res.end();
}
} else {
// User wants to switch bots.
if (toSwitchMin !== undefined) {
// So gets the new bot instance information and prepares to
// auto start dialog if any is specified.
const instance = await this.core.loadInstanceByBotId(activeMin.botId);
await sec.updateUserInstance(id, instance.instanceId);
await (activeMin as any).whatsAppDirectLine.resetConversationId(id);
let startDialog = activeMin.core.getParam(activeMin.instance, 'Start Dialog', null);
GBLog.info(`Auto start dialog is now being called: ${startDialog}...`);
if (startDialog) {
req.body.messages[0].body = `${startDialog}`;
await (activeMin as any).whatsAppDirectLine.received(req, res);
}
else {
await (activeMin as any).whatsAppDirectLine.sendToDevice(
id,
`Agora falando com ${activeMin.instance.title}...`
);
res.end();
}
} else {
activeMin = GBServer.globals.minInstances.filter(p => p.instance.instanceId === user.instanceId)[0];
if (activeMin === undefined) {
activeMin = GBServer.globals.minBoot;
await (activeMin as any).whatsAppDirectLine.sendToDevice(
id,
`O outro Bot que você estava falando(${user.instanceId}), não está mais disponível. Agora você está falando comigo, ${activeMin.instance.title}...`
);
}
await (activeMin as any).whatsAppDirectLine.received(req, res);
}
}
} else {
// Just pass the message to the receiver.
await (GBServer.globals.minBoot as any).whatsAppDirectLine.received(req, res);
}
} catch (error) {
GBLog.error(`Error on Whatsapp callback: ${error.data ? error.data : error}`);
}
}
/**
* Constructs a new minimal instance for each bot.
*/
@ -246,7 +134,7 @@ export class GBMinService {
// Servers default UI on root address '/' if web enabled.
if (process.env.DISABLE_WEB !== 'true') {
let url = GBServer.globals.wwwroot
const url = GBServer.globals.wwwroot
? GBServer.globals.wwwroot
: urlJoin(GBDeployer.deployFolder, GBMinService.uiPackage, 'build');
@ -257,12 +145,12 @@ export class GBMinService {
// instance information stored on server.
if (process.env.DISABLE_WEB !== 'true') {
GBServer.globals.server.get('/instances/:botId', this.handleGetInstanceForClient);
GBServer.globals.server.get('/instances/:botId', this.handleGetInstanceForClient.bind(this));
}
// Servers the WhatsApp callback.
GBServer.globals.server.post('/webhooks/whatsapp', this.WhatsAppCallback);
GBServer.globals.server.post('/webhooks/whatsapp', this.WhatsAppCallback.bind(this));
// Call mountBot event to all bots.
@ -372,6 +260,116 @@ export class GBMinService {
this.createCheckHealthAddress(GBServer.globals.server, min, min.instance);
}
private async WhatsAppCallback(req, res) {
try {
// Detects if the message is echo fro itself.
const id = req.body.messages[0].chatId.split('@')[0];
const senderName = req.body.messages[0].senderName;
const text = req.body.messages[0].body;
if (req.body.messages[0].fromMe) {
res.end();
return; // Exit here.
}
// Detects if the welcome message is enabled.
let activeMin;
if (process.env.WHATSAPP_WELCOME_DISABLED !== 'true') {
// Tries to find if user wants to switch bots.
let toSwitchMin = GBServer.globals.minInstances.filter(
p => p.instance.botId.toLowerCase() === text.toLowerCase()
)[0];
if (!toSwitchMin) {
toSwitchMin = GBServer.globals.minInstances.filter(p =>
p.instance.activationCode ? p.instance.activationCode.toLowerCase() === text.toLowerCase() : false
)[0];
}
// Find active bot instance.
activeMin = toSwitchMin ? toSwitchMin : GBServer.globals.minBoot;
// If it is the first time for the user, tries to auto-execute
// start dialog if any is specified in Config.xlsx.
const sec = new SecService();
let user = await sec.getUserFromSystemId(id);
if (user === null || user.hearOnDialog) {
user = await sec.ensureUser(activeMin.instance.instanceId, id, senderName, '', 'whatsapp', senderName);
const startDialog = user.hearOnDialog ?
user.hearOnDialog :
activeMin.core.getParam(activeMin.instance, 'Start Dialog', null);
GBLog.info(`Auto start dialog is now being called: ${startDialog}...`);
if (startDialog) {
req.body.messages[0].body = `${startDialog}`;
// Resets HEAR ON DIALOG value to none and passes
// current dialog to the direct line.
await sec.updateUserHearOnDialog(user.userId, null);
await (activeMin as any).whatsAppDirectLine.received(req, res);
} else {
await (activeMin as any).whatsAppDirectLine.sendToDevice(
id,
`Olá! Seja bem-vinda(o)!\nMe chamo ${activeMin.instance.title}. Como posso ajudar? Pode me falar que eu te ouço, me manda um aúdio.`
);
res.end();
}
} else {
// User wants to switch bots.
if (toSwitchMin !== undefined) {
// So gets the new bot instance information and prepares to
// auto start dialog if any is specified.
const instance = await this.core.loadInstanceByBotId(activeMin.botId);
await sec.updateUserInstance(id, instance.instanceId);
await (activeMin as any).whatsAppDirectLine.resetConversationId(id);
const startDialog = activeMin.core.getParam(activeMin.instance, 'Start Dialog', null);
GBLog.info(`Auto start dialog is now being called: ${startDialog}...`);
if (startDialog) {
req.body.messages[0].body = `${startDialog}`;
await (activeMin as any).whatsAppDirectLine.received(req, res);
} else {
await (activeMin as any).whatsAppDirectLine.sendToDevice(
id,
`Agora falando com ${activeMin.instance.title}...`
);
res.end();
}
} else {
activeMin = GBServer.globals.minInstances.filter(p => p.instance.instanceId === user.instanceId)[0];
if (activeMin === undefined) {
activeMin = GBServer.globals.minBoot;
await (activeMin as any).whatsAppDirectLine.sendToDevice(
id,
`O outro Bot que você estava falando(${user.instanceId}), não está mais disponível. Agora você está falando comigo, ${activeMin.instance.title}...`
);
}
await (activeMin as any).whatsAppDirectLine.received(req, res);
}
}
} else {
// Just pass the message to the receiver.
await (GBServer.globals.minBoot as any).whatsAppDirectLine.received(req, res);
}
} catch (error) {
GBLog.error(`Error on Whatsapp callback: ${error.data ? error.data : error}`);
}
}
/**
* Creates a listener that can be used by external monitors to check
* bot instance health.
@ -499,7 +497,7 @@ export class GBMinService {
// Gets the webchat token, speech token and theme.
const webchatTokenContainer = await this.getWebchatToken(instance);
const speechToken = instance.speechKey != null ? await this.getSTSToken(instance) : null;
const speechToken = instance.speechKey != undefined ? await this.getSTSToken(instance) : null;
let theme = instance.theme;
// Sends all information to the .gbui web client.
@ -540,9 +538,11 @@ export class GBMinService {
try {
const json = await request(options);
return JSON.parse(json);
} catch (error) {
const msg = `[botId:${instance.botId}] Error calling Direct Line to generate a token for Web control: ${error}.`;
return Promise.reject(new Error(msg));
}
}
@ -575,13 +575,14 @@ export class GBMinService {
// MSFT stuff.
const adapter = new BotFrameworkAdapter({ appId: instance.marketplaceId, appPassword: instance.marketplacePassword });
const adapter = new BotFrameworkAdapter(
{ appId: instance.marketplaceId, appPassword: instance.marketplacePassword });
const storage = new MemoryStorage();
const conversationState = new ConversationState(storage);
const userState = new UserState(storage);
adapter.use(new AutoSaveStateMiddleware(conversationState, userState));
MicrosoftAppCredentials.trustServiceUrl('https://directline.botframework.com',
new Date(new Date().setFullYear(new Date().getFullYear() + 10))
new Date(new Date().setFullYear(new Date().getFullYear() + 10))
);
// The minimal bot is built here.
@ -611,7 +612,7 @@ export class GBMinService {
await CollectionUtil.asyncForEach(min.appPackages, async (e: IGBPackage) => {
let services: ConcatArray<never>;
if ((services = await e.onExchangeData(min, 'getServices', null))) {
min.gbappServices = Object.assign(min.gbappServices, services);
min.gbappServices = {...min.gbappServices, ...services};
}
});
@ -736,7 +737,7 @@ export class GBMinService {
user.welcomed = false;
firstTime = true;
// Sends loadInstance event to .gbui clients.
// Sends loadInstance event to .gbui clients.
await min.conversationalService.sendEvent(min, step, 'loadInstance', {
instanceId: instance.instanceId,
@ -746,7 +747,7 @@ export class GBMinService {
});
// This same event is dispatched either to all participants
// including the bot, that is filtered bellow.
// including the bot, that is filtered bellow.
if (context.activity.from.id !== min.botId) {
@ -762,7 +763,7 @@ export class GBMinService {
member.name
);
// Required for MSTEAMS handling of persisted conversations.
// Required for MSTEAMS handling of persisted conversations.
if (step.context.activity.channelId === 'msteams') {
persistedUser.conversationReference = JSON.stringify(
@ -809,8 +810,7 @@ export class GBMinService {
user.welcomed = true;
GBLog.info(`Auto start dialog is now being called: ${startDialog}...`);
await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer);
}
else {
} else {
// Otherwise, calls / (root) to default welcome users.
@ -822,7 +822,6 @@ export class GBMinService {
GBLog.info(`Member added to conversation: ${member.name}`);
}
} else if (context.activity.type === 'message') {
// Process messages activities.
@ -896,7 +895,7 @@ export class GBMinService {
// Removes <at>Bot Id</at> from MS Teams.
context.activity.text = context.activity.text.trim();
context.activity.text = context.activity.text.replace(/\<at\>.*\<\/at\>\s/gi, '')
context.activity.text = context.activity.text.replace(/\<at\>.*\<\/at\>\s/gi, '');
const user = await min.userProfile.get(context, {});
let message: GuaribasConversationMessage;
@ -931,64 +930,38 @@ export class GBMinService {
const isVMCall = Object.keys(min.scriptMap).find(key => min.scriptMap[key] === context.activity.text) !== undefined;
if (isVMCall) {
await GBVMService.callVM(context.activity.text, min, step, this.deployer);
}
} else if (context.activity.text.charAt(0) === '/') {
// When user starts text by typing a slash, it can call either a dialog directly
// or /call <script> will invoke VM for a given .gbdialog script.
else if (context.activity.text.charAt(0) === '/') {
let text = context.activity.text;
let parts = text.split(' ');
let cmdOrDialogName = parts[0];
const text = context.activity.text;
const parts = text.split(' ');
const cmdOrDialogName = parts[0];
parts.splice(0, 1);
let args = parts.join(' ');
const args = parts.join(' ');
if (cmdOrDialogName === '/call') {
await GBVMService.callVM(args, min, step, this.deployer);
} else {
await step.beginDialog(cmdOrDialogName, { args: args });
}
}
// Processes global escape keywords like 'quit'.
else if (globalQuit(step.context.activity.locale, context.activity.text)) {
} else if (globalQuit(step.context.activity.locale, context.activity.text)) {
await step.cancelAllDialogs();
await min.conversationalService.sendText(min, step, Messages[step.context.activity.locale].canceled);
}
// Handles the admin conversational keyword.
else if (context.activity.text === 'admin') {
} else if (context.activity.text === 'admin') {
await step.beginDialog('/admin');
}
// Checks for /menu JSON signature.
else if (context.activity.text.startsWith('{"title"')) {
} else if (context.activity.text.startsWith('{"title"')) {
await step.beginDialog('/menu', JSON.parse(context.activity.text));
}
// Checks if it is the first time the bot are talking and auto-publish default packages.
else if (
} else if (
!(await this.deployer.getStoragePackageByName(min.instance.instanceId, `${min.instance.botId}.gbkb`)) &&
process.env.GBKB_ENABLE_AUTO_PUBLISH === "true"
process.env.GBKB_ENABLE_AUTO_PUBLISH === 'true'
) {
await min.conversationalService.sendText(min, step,
`Oi, ainda não possuo pacotes de conhecimento publicados. Por favor, aguarde alguns segundos enquanto eu auto-publico alguns pacotes.`
`Oi, ainda não possuo pacotes de conhecimento publicados. Por favor, aguarde alguns segundos enquanto eu auto-publico alguns pacotes.`
);
await step.beginDialog('/publish', { confirm: true, firstTime: true });
}
// Otherwise, continue to the default dialog processing of the input message.
else {
} else {
// Removes unwanted chars in input text.
@ -997,14 +970,14 @@ export class GBMinService {
text = text.replace(/<([^>]+?)([^>]*?)>(.*?)<\/\1>/gi, '');
// Saves special words (keep text) in tokens to prevent it from
// spell checking and translation.
// spell checking and translation.
const keepText: string = min.core.getParam(min.instance, 'Keep Text', '');
let keepTextList = [];
if (keepTextList) {
keepTextList = keepTextList.concat(keepText.split(';'));
}
let replacements = [];
const replacements = [];
await CollectionUtil.asyncForEach(min.appPackages, async (e: IGBPackage) => {
const result = await e.onExchangeData(min, 'getKeepText', {});
if (result) {
@ -1016,10 +989,10 @@ export class GBMinService {
keepTextList = keepTextList.filter(p => p.trim() !== '');
let i = 0;
await CollectionUtil.asyncForEach(keepTextList, item => {
let it = GBConversationalService.removeDiacritics(item);
const it = GBConversationalService.removeDiacritics(item);
if (textProcessed.toLowerCase().indexOf(it.toLowerCase()) != -1) {
const replacementToken = 'X' + GBAdminService['getNumberIdentifier']().substr(0, 4);
const replacementToken = 'X' + GBAdminService.getNumberIdentifier().substr(0, 4);
replacements[i] = { text: item, replacementToken: replacementToken };
i++;
textProcessed = textProcessed.replace(new RegExp(`\\b${it.trim()}\\b`, 'gi'), `${replacementToken}`);
@ -1035,17 +1008,17 @@ export class GBMinService {
// Detects user typed language and updates their locale profile if applies.
let locale = min.core.getParam<string>(min.instance, 'Default User Language',
GBConfigService.get('DEFAULT_USER_LANGUAGE')
GBConfigService.get('DEFAULT_USER_LANGUAGE')
);
let detectLanguage = min.core.getParam<boolean>(min.instance, 'Language Detector',
GBConfigService.getBoolean('LANGUAGE_DETECTOR')
const detectLanguage = min.core.getParam<boolean>(min.instance, 'Language Detector',
GBConfigService.getBoolean('LANGUAGE_DETECTOR')
) === 'true';
const systemUser = user.systemUser;
locale = systemUser.locale;
if (detectLanguage || !locale) {
locale = await min.conversationalService.getLanguage(min, text);
if (systemUser.locale != locale) {
let sec = new SecService();
const sec = new SecService();
user.systemUser = await sec.updateUserLocale(systemUser.userId, locale);
await min.userProfile.set(step.context, user);
}
@ -1061,7 +1034,7 @@ export class GBMinService {
// Translates text into content language, keeping
// reserved tokens specified in Config.
let contentLocale = min.core.getParam<string>(
const contentLocale = min.core.getParam<string>(
min.instance,
'Default Content Language',
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
@ -1082,17 +1055,12 @@ export class GBMinService {
context.activity.originalText = originalText;
GBLog.info(`Final text ready for NLP/Search/.gbapp: ${text}.`);
// If there is a dialog in course, continue to the next step.
if (step.activeDialog !== undefined) {
await step.continueDialog();
}
// Checks if any .gbapp will handle this answer, if not goes to standard kb.gbapp.
else {
} else {
let nextDialog = null;
await CollectionUtil.asyncForEach(min.appPackages, async (e: IGBPackage) => {
@ -1101,12 +1069,12 @@ export class GBMinService {
step: step,
notTranslatedQuery: originalText,
message: message ? message['dataValues'] : null,
user: user ? user['dataValues'] : null
user: user ? user.dataValues : null
});
});
await step.beginDialog(nextDialog ? nextDialog : '/answer', {
query: text,
user: user ? user['dataValues'] : null,
user: user ? user.dataValues : null,
message: message
});
}

View file

@ -2,9 +2,9 @@
export const Messages = {
global_quit: /^(sair|sai|chega|exit|quit|finish|end|ausfahrt|verlassen)/i,
'en-US': {
},
'pt-BR': {
}
};

View file

@ -39,10 +39,10 @@
import { BotAdapter } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance, IGBDialog } from 'botlib';
import { AnalyticsService } from '../../analytics.gblib/services/AnalyticsService';
import { SecService } from '../../security.gbapp/services/SecService';
import { CSService } from '../services/CSService';
import { Messages } from '../strings';
import { SecService } from '../../security.gbapp/services/SecService';
import { AnalyticsService } from '../../analytics.gblib/services/AnalyticsService';
/**
* Dialog for feedback collecting.
@ -74,14 +74,14 @@ export class FeedbackDialog extends IGBDialog {
const locale = step.context.activity.locale;
let sec = new SecService();
let from = step.context.activity.from.id;
const sec = new SecService();
const from = step.context.activity.from.id;
await min.conversationalService.sendText(min, step, Messages[locale].please_wait_transfering);
let agentSystemId = await sec.assignHumanAgent(from, min.instance.instanceId);
const agentSystemId = await sec.assignHumanAgent(from, min.instance.instanceId);
await min.whatsAppDirectLine.sendToDevice(agentSystemId,
Messages[locale].notify_agent(step.context.activity.from.name));
Messages[locale].notify_agent(step.context.activity.from.name));
return await step.next();
}
@ -94,8 +94,8 @@ export class FeedbackDialog extends IGBDialog {
const locale = step.context.activity.locale;
let sec = new SecService();
let from = step.context.activity.from.id;
const sec = new SecService();
const from = step.context.activity.from.id;
await sec.updateCurrentAgent(from, min.instance.instanceId, null);
await min.conversationalService.sendText(min, step, Messages[locale].notify_end_transfer(min.instance.botId));
@ -105,7 +105,6 @@ export class FeedbackDialog extends IGBDialog {
])
);
min.dialogs.add(
new WaterfallDialog('/feedbackNumber', [
async step => {
@ -144,13 +143,13 @@ export class FeedbackDialog extends IGBDialog {
const analytics = new AnalyticsService();
const rate = await analytics.updateConversationSuggestion(
min.instance.instanceId, user.conversation.conversationId, step.result, user.systemUser.locale);
if (rate > 0.5) {
await min.conversationalService.sendText(min, step, Messages[fixedLocale].glad_you_liked);
} else {
const message = min.core.getParam<string>(min.instance, "Feedback Improve Message",
Messages[fixedLocale].we_will_improve); // TODO: Improve to be multi-language.
const message = min.core.getParam<string>(min.instance, 'Feedback Improve Message',
Messages[fixedLocale].we_will_improve); // TODO: Improve to be multi-language.
await min.conversationalService.sendText(min, step, message);
}

View file

@ -40,10 +40,10 @@ import { GBMinInstance, IGBDialog } from 'botlib';
import { BotAdapter } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { CSService } from '../services/CSService';
import { Messages } from '../strings';
import { AnalyticsService } from '../../analytics.gblib/services/AnalyticsService';
import { GBConversationalService } from '../../core.gbapp/services/GBConversationalService';
import { CSService } from '../services/CSService';
import { Messages } from '../strings';
/**
* Dialog for collecting quality of answer.
@ -82,10 +82,10 @@ export class QualityDialog extends IGBDialog {
);
// Updates values to perform Bot Analytics.
const analytics = new AnalyticsService();
analytics.updateConversationSuggestion(
min.instance.instanceId, user.conversation,step.result, user.systemUser.locale);
min.instance.instanceId, user.conversation, step.result, user.systemUser.locale);
// Goes to the ask loop.

View file

@ -65,7 +65,6 @@ export class GBCustomerSatisfactionPackage implements IGBPackage {
GBLog.verbose(`onExchangeData called.`);
}
public async loadPackage(core: IGBCoreService, sequelize: Sequelize): Promise<void> {
core.sequelize.addModels([GuaribasQuestionAlternate]);
}

View file

@ -30,9 +30,9 @@
| |
\*****************************************************************************/
import { GuaribasQuestion } from '../../../packages/kb.gbapp/models';
import { GuaribasConversation } from '../../analytics.gblib/models';
import { GuaribasQuestionAlternate } from '../models';
import { GuaribasQuestion } from '../../../packages/kb.gbapp/models';
/**
* Customer Satisfaction Service Layer.
@ -43,7 +43,7 @@ export class CSService {
instanceId: number,
text: string): Promise<GuaribasQuestion> {
let questionAlternate = await GuaribasQuestionAlternate.findOne({
const questionAlternate = await GuaribasQuestionAlternate.findOne({
where: {
instanceId: instanceId,
questionTyped: text
@ -61,6 +61,7 @@ export class CSService {
}
});
}
return question;
}

View file

@ -11,7 +11,7 @@ export const Messages = {
please_no_bad_words: 'Please, no bad words.',
please_wait_transfering: 'Please, wait while I find an agent to answer you.',
notify_agent: (name) => `New call available for *${name}*, you can answer right here when you are finished, type /qt.`,
notify_end_transfer: (botName) => `Now talking to ${botName} again.`,
notify_end_transfer: (botName) => `Now talking to ${botName} again.`
},
'pt-BR': {
about_suggestions: 'Sugestões melhoram muito minha qualidade...',

View file

@ -39,9 +39,9 @@
import { BotAdapter } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance, IGBDialog } from 'botlib';
import { GBConversationalService } from '../../core.gbapp/services/GBConversationalService';
import { Messages } from '../strings';
import { KBService } from './../services/KBService';
import { GBConversationalService } from '../../core.gbapp/services/GBConversationalService';
/**
* Handle display of FAQ allowing direct access to KB.

View file

@ -41,10 +41,10 @@ import urlJoin = require('url-join');
import { BotAdapter, CardFactory, MessageFactory } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance, IGBDialog } from 'botlib';
import { GBConversationalService } from '../../core.gbapp/services/GBConversationalService';
import { GuaribasSubject } from '../models';
import { KBService } from '../services/KBService';
import { Messages } from '../strings';
import { GBConversationalService } from '../../core.gbapp/services/GBConversationalService';
/**
* Dialog arguments.
@ -134,8 +134,8 @@ export class MenuDialog extends IGBDialog {
if (attachments.length === 0) {
if (user.subjects && user.subjects.length > 0) {
await min.conversationalService.sendText(min, step,
Messages[locale].lets_search(KBService.getFormattedSubjectItems(user.subjects))
await min.conversationalService.sendText(min, step,
Messages[locale].lets_search(KBService.getFormattedSubjectItems(user.subjects))
);
}
} else {

View file

@ -64,7 +64,6 @@ export class GBKBPackage implements IGBPackage {
GBLog.verbose(`onExchangeData called.`);
}
public async loadPackage(core: IGBCoreService, sequelize: Sequelize): Promise<void> {
core.sequelize.addModels([GuaribasAnswer, GuaribasQuestion, GuaribasSubject]);
}

View file

@ -43,28 +43,28 @@ const asyncPromise = require('async-promises');
const walkPromise = require('walk-promise');
// tslint:disable-next-line:newline-per-chained-call
const { SearchService } = require('azure-search-client');
var Excel = require('exceljs');
import { GBServer } from '../../../src/app';
const Excel = require('exceljs');
import {
IGBKBService,
GBDialogStep,
GBLog,
GBMinInstance,
IGBConversationalService,
IGBCoreService,
IGBInstance,
GBMinInstance
IGBKBService
} from 'botlib';
import { CollectionUtil } from 'pragmatismo-io-framework';
import { Op } from 'sequelize';
import { Sequelize } from 'sequelize-typescript';
import { GBServer } from '../../../src/app';
import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService';
import { GuaribasPackage } from '../../core.gbapp/models/GBModel';
import { GBDeployer } from '../../core.gbapp/services/GBDeployer';
import { CSService } from '../../customer-satisfaction.gbapp/services/CSService';
import { SecService } from '../../security.gbapp/services/SecService';
import { GuaribasAnswer, GuaribasQuestion, GuaribasSubject } from '../models';
import { Messages } from '../strings';
import { GBConfigService } from './../../core.gbapp/services/GBConfigService';
import { CSService } from '../../customer-satisfaction.gbapp/services/CSService';
import { SecService } from '../../security.gbapp/services/SecService';
import { CollectionUtil } from 'pragmatismo-io-framework';
/**
* Result for quey on KB data.
@ -116,7 +116,7 @@ export class KBService implements IGBKBService {
}
});
return answer != null ? answer.content : null;
return answer != undefined ? answer.content : null;
}
public async getQuestionById(instanceId: number, questionId: number): Promise<GuaribasQuestion> {
@ -177,8 +177,6 @@ export class KBService implements IGBKBService {
): Promise<KBServiceSearchResults> {
// Builds search query.
query = query.toLowerCase();
query = query.replace('?', ' ');
query = query.replace('!', ' ');
@ -222,6 +220,7 @@ export class KBService implements IGBKBService {
`SEARCH WILL BE USED with score: ${returnedScore} > required (searchScore): ${searchScore}`
);
notSearched = false;
return { answer: value, questionId: values[0].questionId };
} else {
GBLog.info(
@ -243,6 +242,7 @@ export class KBService implements IGBKBService {
GBLog.info(
`SEARCH called but NO answer could be found (zero results).`
);
return { answer: undefined, questionId: 0 };
}
}
@ -320,8 +320,8 @@ export class KBService implements IGBKBService {
packageId: number
): Promise<GuaribasQuestion[]> {
GBLog.info(`Now reading file ${filePath}...`);
var workbook = new Excel.Workbook();
let data = await workbook.xlsx.readFile(filePath);
const workbook = new Excel.Workbook();
const data = await workbook.xlsx.readFile(filePath);
let lastQuestionId: number;
let lastAnswer: GuaribasAnswer;
@ -337,9 +337,9 @@ export class KBService implements IGBKBService {
}
}
let rows = worksheet._rows;
let answers = [];
let questions = [];
const rows = worksheet._rows;
const answers = [];
const questions = [];
GBLog.info(`Processing ${rows.length} rows from tabular file ${filePath}...`);
await asyncPromise.eachSeries(rows, async line => {
@ -454,6 +454,7 @@ export class KBService implements IGBKBService {
await CollectionUtil.asyncForEach(questions, async question => {
question.answerId = answersCreated[i++].answerId;
});
return await GuaribasQuestion.bulkCreate(questions);
}
@ -470,6 +471,123 @@ export class KBService implements IGBKBService {
}
}
public async importKbPackage(
localPath: string,
packageStorage: GuaribasPackage,
instance: IGBInstance
): Promise<any> {
// Imports subjects tree into database and return it.
const subjectFile = urlJoin(localPath, 'subjects.json');
if (Fs.existsSync(subjectFile)) {
await this.importSubjectFile(packageStorage.packageId, subjectFile, instance);
}
// Import tabular files in the tabular directory.
await this.importKbTabularDirectory(localPath, instance, packageStorage.packageId);
// Import remaining .md files in articles directory.
return await this.importRemainingArticles(localPath, instance, packageStorage.packageId);
}
/**
* Import all .md files in artcles folder that has not been referenced by tabular files.
*/
public async importRemainingArticles(localPath: string, instance: IGBInstance, packageId: number): Promise<any> {
const files = await walkPromise(urlJoin(localPath, 'articles'));
await CollectionUtil.asyncForEach(files, async file => {
if (file !== null && file.name.endsWith('.md')) {
let content = await this.getAnswerTextByMediaName(instance.instanceId, file.name);
if (content === null) {
const fullFilename = urlJoin(file.root, file.name);
content = Fs.readFileSync(fullFilename, 'utf-8');
await GuaribasAnswer.create({
instanceId: instance.instanceId,
content: content,
format: '.md',
media: file.name,
packageId: packageId,
prevId: 0 // TODO: Calculate total rows and increment.
});
}
}
});
}
public async importKbTabularDirectory(localPath: string, instance: IGBInstance, packageId: number): Promise<any> {
const files = await walkPromise(localPath);
await CollectionUtil.asyncForEach(files, async file => {
if (file !== null && file.name.endsWith('.xlsx')) {
return await this.importKbTabularFile(urlJoin(file.root, file.name), instance.instanceId, packageId);
}
});
}
public async importSubjectFile(packageId: number, filename: string, instance: IGBInstance): Promise<any> {
const subjectsLoaded = JSON.parse(Fs.readFileSync(filename, 'utf8'));
const doIt = async (subjects: GuaribasSubject[], parentSubjectId: number) => {
return asyncPromise.eachSeries(subjects, async item => {
const value = await GuaribasSubject.create({
internalId: item.id,
parentSubjectId: parentSubjectId,
instanceId: instance.instanceId,
from: item.from,
to: item.to,
title: item.title,
description: item.description,
packageId: packageId
});
if (item.children) {
return doIt(item.children, value.subjectId);
} else {
return item;
}
});
};
return doIt(subjectsLoaded.children, undefined);
}
public async undeployKbFromStorage(instance: IGBInstance, deployer: GBDeployer, packageId: number) {
await GuaribasQuestion.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
});
await GuaribasAnswer.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
});
await GuaribasSubject.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
});
await this.undeployPackageFromStorage(instance, packageId);
}
/**
* Deploys a knowledge base to the storage using the .gbkb format.
*
* @param localPath Path to the .gbkb folder.
*/
public async deployKb(core: IGBCoreService, deployer: GBDeployer, localPath: string, min: GBMinInstance) {
const packageName = Path.basename(localPath);
GBLog.info(`[GBDeployer] Opening package: ${localPath}`);
const instance = await core.loadInstanceByBotId(min.botId);
GBLog.info(`[GBDeployer] Importing: ${localPath}`);
const p = await deployer.deployPackageToStorage(instance.instanceId, packageName);
await this.importKbPackage(localPath, p, instance);
deployer.mountGBKBAssets(packageName, min.botId, localPath);
await deployer.rebuildIndex(instance, new AzureDeployerService(deployer).getKBSearchSchema(instance.searchIndex));
GBLog.info(`[GBDeployer] Finished import of ${localPath}`);
}
private async playAudio(
min: GBMinInstance,
answer: GuaribasAnswer,
@ -571,126 +689,9 @@ export class KBService implements IGBKBService {
}
}
public async importKbPackage(
localPath: string,
packageStorage: GuaribasPackage,
instance: IGBInstance
): Promise<any> {
// Imports subjects tree into database and return it.
const subjectFile = urlJoin(localPath, 'subjects.json');
if (Fs.existsSync(subjectFile)) {
await this.importSubjectFile(packageStorage.packageId, subjectFile, instance);
}
// Import tabular files in the tabular directory.
await this.importKbTabularDirectory(localPath, instance, packageStorage.packageId);
// Import remaining .md files in articles directory.
return await this.importRemainingArticles(localPath, instance, packageStorage.packageId);
}
/**
* Import all .md files in artcles folder that has not been referenced by tabular files.
*/
public async importRemainingArticles(localPath: string, instance: IGBInstance, packageId: number): Promise<any> {
const files = await walkPromise(urlJoin(localPath, 'articles'));
await CollectionUtil.asyncForEach(files, async file => {
if (file !== null && file.name.endsWith('.md')) {
let content = await this.getAnswerTextByMediaName(instance.instanceId, file.name);
if (content === null) {
const fullFilename = urlJoin(file.root, file.name);
content = Fs.readFileSync(fullFilename, 'utf-8');
await GuaribasAnswer.create({
instanceId: instance.instanceId,
content: content,
format: '.md',
media: file.name,
packageId: packageId,
prevId: 0 // TODO: Calculate total rows and increment.
});
}
}
});
}
public async importKbTabularDirectory(localPath: string, instance: IGBInstance, packageId: number): Promise<any> {
let files = await walkPromise(localPath);
await CollectionUtil.asyncForEach(files, async file => {
if (file !== null && file.name.endsWith('.xlsx')) {
return await this.importKbTabularFile(urlJoin(file.root, file.name), instance.instanceId, packageId);
}
});
}
public async importSubjectFile(packageId: number, filename: string, instance: IGBInstance): Promise<any> {
const subjectsLoaded = JSON.parse(Fs.readFileSync(filename, 'utf8'));
const doIt = async (subjects: GuaribasSubject[], parentSubjectId: number) => {
return asyncPromise.eachSeries(subjects, async item => {
const value = await GuaribasSubject.create({
internalId: item.id,
parentSubjectId: parentSubjectId,
instanceId: instance.instanceId,
from: item.from,
to: item.to,
title: item.title,
description: item.description,
packageId: packageId
});
if (item.children) {
return doIt(item.children, value.subjectId);
} else {
return item;
}
});
};
return doIt(subjectsLoaded.children, undefined);
}
public async undeployKbFromStorage(instance: IGBInstance, deployer: GBDeployer, packageId: number) {
await GuaribasQuestion.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
});
await GuaribasAnswer.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
});
await GuaribasSubject.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
});
await this.undeployPackageFromStorage(instance, packageId);
}
private async undeployPackageFromStorage(instance: any, packageId: number) {
await GuaribasPackage.destroy({
where: { instanceId: instance.instanceId, packageId: packageId }
});
}
/**
* Deploys a knowledge base to the storage using the .gbkb format.
*
* @param localPath Path to the .gbkb folder.
*/
public async deployKb(core: IGBCoreService, deployer: GBDeployer, localPath: string, min: GBMinInstance) {
const packageName = Path.basename(localPath);
GBLog.info(`[GBDeployer] Opening package: ${localPath}`);
const instance = await core.loadInstanceByBotId(min.botId);
GBLog.info(`[GBDeployer] Importing: ${localPath}`);
const p = await deployer.deployPackageToStorage(instance.instanceId, packageName);
await this.importKbPackage(localPath, p, instance);
deployer.mountGBKBAssets(packageName, min.botId, localPath);
await deployer.rebuildIndex(instance, new AzureDeployerService(deployer).getKBSearchSchema(instance.searchIndex));
GBLog.info(`[GBDeployer] Finished import of ${localPath}`);
}
}

View file

@ -37,7 +37,7 @@
'use strict';
import { TokenResponse } from 'botbuilder';
import { IGBDialog, GBLog, GBMinInstance } from 'botlib';
import { GBLog, GBMinInstance, IGBDialog } from 'botlib';
import { Messages } from '../strings';
/**
@ -50,6 +50,7 @@ export class OAuthDialog extends IGBDialog {
waterfall: [
async step => {
step.activeDialog.state.options = step.options;
return await step.beginDialog('oAuthPrompt');
},
async step => {
@ -60,6 +61,7 @@ export class OAuthDialog extends IGBDialog {
return await step.endDialog(tokenResponse);
} else {
await step.context.sendActivity('Please sign in so I can show you your profile.');
return await step.replaceDialog('/auth');
}
}

View file

@ -40,10 +40,10 @@ import urlJoin = require('url-join');
import { BotAdapter, CardFactory, MessageFactory } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance, IGBDialog, GBLog } from 'botlib';
import { Messages } from '../strings';
import { GBConversationalService } from '../../core.gbapp/services/GBConversationalService';
import { GBLog, GBMinInstance, IGBDialog } from 'botlib';
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService';
import { GBConversationalService } from '../../core.gbapp/services/GBConversationalService';
import { Messages } from '../strings';
const phoneUtil = require('google-libphonenumber').PhoneNumberUtil.getInstance();
const phone = require('phone');
@ -52,47 +52,45 @@ const phone = require('phone');
*/
export class ProfileDialog extends IGBDialog {
static getNameDialog(min: GBMinInstance) {
public static getNameDialog(min: GBMinInstance) {
return {
id: '/profile_name', waterfall: [
async step => {
step.activeDialog.state.options = step.options;
const locale = step.context.activity.locale;
await step.prompt("textPrompt", Messages[locale].whats_name);
await step.prompt('textPrompt', Messages[locale].whats_name);
},
async step => {
const locale = step.context.activity.locale;
const fullName = (text) => {
return text.match(/^[a-zA-Z]+(([',. -][a-zA-Z ])?[a-zA-Z]*)*$/gi);
}
};
const value = fullName(step.result);
if (value === null) {
await step.context.sendActivity(Messages[locale].validation_enter_name);
await step.replaceDialog('/profile_name', step.activeDialog.state.options);
}
else {
} else {
step.activeDialog.state.options.name = value[0];
return await step.replaceDialog('/profile_mobile', step.activeDialog.state.options);
}
}]
}
};
}
static getMobileDialog(min: GBMinInstance) {
public static getMobileDialog(min: GBMinInstance) {
return {
id: '/profile_mobile', waterfall: [
async step => {
step.activeDialog.state.options = step.options;
const locale = step.context.activity.locale;
await step.prompt("textPrompt", Messages[locale].whats_mobile);
await step.prompt('textPrompt', Messages[locale].whats_mobile);
},
async step => {
const locale = step.context.activity.locale;
@ -109,25 +107,24 @@ export class ProfileDialog extends IGBDialog {
await step.context.sendActivity(Messages[locale].validation_enter_valid_mobile);
return await step.replaceDialog('/profile_mobile', step.activeDialog.state.options);
}
else {
} else {
step.activeDialog.state.options.mobile = `${phoneNumber.values_['1']}${phoneNumber.values_['2']}`;
step.activeDialog.state.options.mobileCode = GBAdminService.getMobileCode();
return await step.replaceDialog('/profile_mobile_confirm', step.activeDialog.state.options);
}
}]
}
};
}
static getMobileConfirmDialog(min: GBMinInstance) {
public static getMobileConfirmDialog(min: GBMinInstance) {
return {
id: '/profile_mobile_confirm', waterfall: [
async step => {
step.activeDialog.state.options = step.options;
const locale = step.context.activity.locale;
let from = step.activeDialog.state.options.mobile;
const from = step.activeDialog.state.options.mobile;
if (min.whatsAppDirectLine) {
await min.whatsAppDirectLine.sendToDevice(from, `${step.activeDialog.state.options.mobileCode} is your General Bots creation code.`);
@ -135,7 +132,7 @@ export class ProfileDialog extends IGBDialog {
GBLog.info(`WhatsApp not configured. Here is the code: ${step.activeDialog.state.options.mobileCode}.`);
}
await step.prompt("textPrompt", Messages[locale].confirm_mobile);
await step.prompt('textPrompt', Messages[locale].confirm_mobile);
},
async step => {
const locale = step.context.activity.locale;
@ -144,40 +141,37 @@ export class ProfileDialog extends IGBDialog {
await step.context.sendActivity(Messages[locale].confirm_mobile_again);
return await step.replaceDialog('/profile_mobile_confirm', step.activeDialog.state.options);
}
else {
} else {
await step.replaceDialog('/profile_email', step.activeDialog.state.options);
}
}]
}
};
}
static getEmailDialog(min: GBMinInstance) {
public static getEmailDialog(min: GBMinInstance) {
return {
id: '/profile_email', waterfall: [
async step => {
const locale = step.context.activity.locale;
await step.prompt("textPrompt", Messages[locale].whats_email);
await step.prompt('textPrompt', Messages[locale].whats_email);
},
async step => {
const locale = step.context.activity.locale;
const extractEntity = (text) => {
return text.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)/gi);
}
};
const value = extractEntity(step.result);
if (value === null) {
await step.context.sendActivity(Messages[locale].validation_enter_valid_email);
await step.replaceDialog('/profile_email', step.activeDialog.state.options);
}
else {
} else {
step.activeDialog.state.options.email = value[0];
await step.replaceDialog(`/${step.activeDialog.state.options.nextDialog}`, step.activeDialog.state.options);
}
}]
}
};
}
}

View file

@ -39,10 +39,10 @@
import urlJoin = require('url-join');
import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import {ProfileDialog} from './dialogs/ProfileDialog'
import { Sequelize } from 'sequelize-typescript';
import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from './models';
import { OAuthDialog } from './dialogs/OAuthDialog';
import {ProfileDialog} from './dialogs/ProfileDialog';
import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from './models';
/**
* Package for the security module.
@ -55,9 +55,9 @@ export class GBSecurityPackage implements IGBPackage {
ProfileDialog.getEmailDialog(min),
ProfileDialog.getMobileDialog(min),
ProfileDialog.getMobileConfirmDialog(min),
OAuthDialog.getOAuthDialog(min),
OAuthDialog.getOAuthDialog(min)
];
}
public async unloadPackage(core: IGBCoreService): Promise<void> {
GBLog.verbose(`unloadPackage called.`);
@ -74,7 +74,7 @@ export class GBSecurityPackage implements IGBPackage {
public async onExchangeData(min: GBMinInstance, kind: string, data: any) {
GBLog.verbose(`onExchangeData called.`);
}
public async loadPackage(core: IGBCoreService, sequelize: Sequelize): Promise<void> {
core.sequelize.addModels([GuaribasGroup, GuaribasUser, GuaribasUserGroup]);
}

View file

@ -79,21 +79,21 @@ export class GuaribasUser extends Model<GuaribasUser> {
public instance: GuaribasInstance;
@Column(DataType.STRING(16))
agentSystemId: string
public agentSystemId: string;
@Column(DataType.DATE)
@Column
agentContacted: Date;
public agentContacted: Date;
@Column(DataType.STRING(16))
agentMode: string;
public agentMode: string;
@Column(DataType.TEXT)
@Column
conversationReference: string
public conversationReference: string;
@Column(DataType.STRING(64))
hearOnDialog: string;
public hearOnDialog: string;
}
/**

View file

@ -1,10 +1,10 @@
const Fs = require('fs');
import urlJoin = require('url-join');
import { GBService, IGBInstance } from 'botlib';
import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from '../models';
import { ConversationReference } from 'botbuilder';
import { GBService, IGBInstance } from 'botlib';
import { CollectionUtil } from 'pragmatismo-io-framework';
import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from '../models';
/**
* Security service layer.
@ -57,6 +57,7 @@ export class SecService extends GBService {
user.displayName = displayName;
user.email = userName;
user.defaultChannel = channelName;
return await user.save();
}
@ -82,27 +83,29 @@ export class SecService extends GBService {
}
public async updateUserLocale(userId: number, locale: any): Promise<GuaribasUser> {
let user = await GuaribasUser.findOne({
const user = await GuaribasUser.findOne({
where: {
userId: userId
}
});
user.locale = locale;
return await user.save();
}
public async updateUserHearOnDialog(userId: number, dialogName: string): Promise<GuaribasUser> {
let user = await GuaribasUser.findOne({
const user = await GuaribasUser.findOne({
where: {
userId: userId
}
});
user.hearOnDialog = dialogName;
return await user.save();
}
public async updateUserInstance(userSystemId: string, instanceId: number): Promise<GuaribasUser> {
let user = await GuaribasUser.findOne({
const user = await GuaribasUser.findOne({
where: {
userSystemId: userSystemId
}
@ -154,18 +157,19 @@ export class SecService extends GBService {
}
await user.save();
return user;
}
public async isAgentSystemId(systemId: string): Promise<Boolean> {
let user = await GuaribasUser.findOne({
const user = await GuaribasUser.findOne({
where: {
userSystemId: systemId
}
});
if (user === null) {
throw `TRANSFER_TO phones must talk first to the bot before becoming an agent.`;
throw new Error(`TRANSFER_TO phones must talk first to the bot before becoming an agent.`);
}
return user.agentMode === 'self';

View file

@ -1,21 +1,21 @@
export const Messages = {
'en-US': {
whats_name: "What's your name?",
whats_mobile: "What's your mobile number including country code (e.g. +1 222 9998888)?",
confirm_mobile: "Please type the code just sent to your mobile.",
whats_email: "What's your E-mail address?",
validation_enter_name: "Please enter your full name.",
validation_enter_valid_mobile: "Please enter a valid mobile number.",
validation_enter_valid_email: "Please enter a valid e-mail.",
whats_name: 'What\'s your name?',
whats_mobile: 'What\'s your mobile number including country code (e.g. +1 222 9998888)?',
confirm_mobile: 'Please type the code just sent to your mobile.',
whats_email: 'What\'s your E-mail address?',
validation_enter_name: 'Please enter your full name.',
validation_enter_valid_mobile: 'Please enter a valid mobile number.',
validation_enter_valid_email: 'Please enter a valid e-mail.'
},
'pt-BR': {
whats_name: "Qual o seu nome?",
whats_email: "Qual o seu e-mail?",
whats_mobile: "Qual o seu celular?",
confirm_mobile: "Por favor, digite o código enviado para seu celular.",
confirm_mobile_again: "Esse não me parece ser um código numérico válido. Por favor, digite novamente o código enviado para seu celular.",
validation_enter_valid_email: "Por favor, digite um e-mail válido no formato nome@domínio.com.br.",
validation_enter_name: "Por favor, digite seu nome completo",
validation_enter_valid_mobile: "Por favor, insira um número de celular válido (ex.: +55 21 98888-7766).",
whats_name: 'Qual o seu nome?',
whats_email: 'Qual o seu e-mail?',
whats_mobile: 'Qual o seu celular?',
confirm_mobile: 'Por favor, digite o código enviado para seu celular.',
confirm_mobile_again: 'Esse não me parece ser um código numérico válido. Por favor, digite novamente o código enviado para seu celular.',
validation_enter_valid_email: 'Por favor, digite um e-mail válido no formato nome@domínio.com.br.',
validation_enter_name: 'Por favor, digite seu nome completo',
validation_enter_valid_mobile: 'Por favor, insira um número de celular válido (ex.: +55 21 98888-7766).'
}
};

View file

@ -36,7 +36,7 @@
'use strict';
const { sppull } = require("sppull");
const { sppull } = require('sppull');
/**
* Service facade for SharePoint Online.
@ -61,5 +61,4 @@ export class GBSharePointService {
return await sppull(context, options);
}
}

View file

@ -35,18 +35,20 @@ import urlJoin = require('url-join');
const Swagger = require('swagger-client');
const rp = require('request-promise');
const fs = require('fs');
import { GBLog, GBService, GBMinInstance, IGBPackage } from 'botlib';
import { GBLog, GBMinInstance, GBService, IGBPackage } from 'botlib';
import { CollectionUtil } from 'pragmatismo-io-framework';
import * as request from 'request-promise-native';
import { GBServer } from '../../../src/app';
import { GBConversationalService } from '../../core.gbapp/services/GBConversationalService';
import { SecService } from '../../security.gbapp/services/SecService';
import { Messages } from '../strings';
import { CollectionUtil } from 'pragmatismo-io-framework';
/**
* Support for Whatsapp.
*/
export class WhatsappDirectLine extends GBService {
public static conversationIds = {};
public pollInterval = 5000;
public directLineClientName = 'DirectLineClient';
@ -55,12 +57,10 @@ export class WhatsappDirectLine extends GBService {
public whatsappServiceNumber: string;
public whatsappServiceUrl: string;
public botId: string;
public min: GBMinInstance;
private directLineSecret: string;
private locale: string = 'pt-BR';
static conversationIds = {};
min: GBMinInstance;
constructor(
min: GBMinInstance,
botId,
@ -80,13 +80,19 @@ export class WhatsappDirectLine extends GBService {
}
public static async asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
public async setup(setUrl) {
this.directLineClient =
new Swagger({
spec: JSON.parse(fs.readFileSync('directline-3.0.json', 'utf8')),
usePromise: true
});
let client = await this.directLineClient;
const client = await this.directLineClient;
client.clientAuthorizations.add(
'AuthorizationBotConnector',
@ -111,9 +117,9 @@ export class WhatsappDirectLine extends GBService {
const express = require('express');
GBServer.globals.server.use(`/audios`, express.static('work'));
if (process.env.ENDPOINT_UPDATE === "true") {
if (process.env.ENDPOINT_UPDATE === 'true') {
try {
let res = await request.post(options);
const res = await request.post(options);
GBLog.info(res);
} catch (error) {
GBLog.error(`Error initializing 3rd party Whatsapp provider(1) ${error.message}`);
@ -123,12 +129,6 @@ export class WhatsappDirectLine extends GBService {
}
public static async asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
public async resetConversationId(number) {
WhatsappDirectLine.conversationIds[number] = undefined;
}
@ -139,19 +139,21 @@ export class WhatsappDirectLine extends GBService {
const options = {
url: urlJoin(this.whatsappServiceUrl, 'status') + `?token=${this.min.instance.whatsappServiceKey}`,
method: 'GET',
method: 'GET'
};
const res = await request(options);
const json = JSON.parse(res);
return json.accountStatus === "authenticated";
return json.accountStatus === 'authenticated';
}
public async received(req, res) {
if (req.body.messages === undefined) {
res.end();
return; // Exit here.
}
@ -162,25 +164,26 @@ export class WhatsappDirectLine extends GBService {
if (req.body.messages[0].fromMe) {
res.end();
return; // Exit here.
}
GBLog.info(`GBWhatsapp: RCV ${from}(${fromName}): ${text})`);
await CollectionUtil.asyncForEach(this.min.appPackages, async (e: IGBPackage) => {
await e.onExchangeData(this.min, "whatsappMessage", message);
await e.onExchangeData(this.min, 'whatsappMessage', message);
});
const id = req.body.messages[0].chatId.split('@')[0];
const senderName = req.body.messages[0].senderName;
let sec = new SecService();
const sec = new SecService();
const user = await sec.ensureUser(this.min.instance.instanceId, id,
senderName, "", "whatsapp", senderName);
senderName, '', 'whatsapp', senderName);
const locale = user.locale ? user.locale : 'pt';
if (message.type === "ptt") {
if (message.type === 'ptt') {
if (process.env.AUDIO_DISABLED !== "true") {
if (process.env.AUDIO_DISABLED !== 'true') {
const options = {
url: message.body,
method: 'GET',
@ -188,71 +191,67 @@ export class WhatsappDirectLine extends GBService {
};
const res = await request(options);
let buf = Buffer.from(res, 'binary');
const buf = Buffer.from(res, 'binary');
text = await GBConversationalService.getTextFromAudioBuffer(
this.min.instance.speechKey,
this.min.instance.cloudLocation,
buf, locale
);
}
else {
} else {
await this.sendToDevice(user.userSystemId, `No momento estou apenas conseguindo ler mensagens de texto.`);
}
}
const conversationId = WhatsappDirectLine.conversationIds[from];
let client = await this.directLineClient;
if (user.agentMode === "self") {
let manualUser = await sec.getUserFromAgentSystemId(id);
const client = await this.directLineClient;
if (user.agentMode === 'self') {
const manualUser = await sec.getUserFromAgentSystemId(id);
if (manualUser === null) {
await sec.updateCurrentAgent(id, this.min.instance.instanceId, null);
}
else {
} else {
const cmd = '/reply ';
if (text.startsWith(cmd)) {
let filename = text.substr(cmd.length);
let message = await this.min.kbService.getAnswerTextByMediaName(this.min.instance.instanceId, filename);
const filename = text.substr(cmd.length);
const message = await this.min.kbService.getAnswerTextByMediaName(this.min.instance.instanceId, filename);
if (message === null) {
await this.sendToDeviceEx(user.userSystemId, `File ${filename} not found in any .gbkb published. Check the name or publish again the associated .gbkb.`,
locale);
locale);
} else {
await this.min.conversationalService.sendMarkdownToMobile(this.min, null, user.userSystemId, message);
}
} else if (text === '/qt') {
// TODO: Transfers only in pt-br for now.
await this.sendToDeviceEx(manualUser.userSystemId, Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
await this.sendToDeviceEx(user.agentSystemId, Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
await this.sendToDeviceEx(manualUser.userSystemId,
Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
await this.sendToDeviceEx(user.agentSystemId,
Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
await sec.updateCurrentAgent(manualUser.userSystemId, this.min.instance.instanceId, null);
}
else {
} else {
GBLog.info(`HUMAN AGENT (${id}) TO USER ${manualUser.userSystemId}: ${text}`);
this.sendToDeviceEx(manualUser.userSystemId, `${manualUser.agentSystemId}: ${text}`, locale);
}
}
}
else if (user.agentMode === "human") {
let agent = await sec.getUserFromSystemId(user.agentSystemId);
} else if (user.agentMode === 'human') {
const agent = await sec.getUserFromSystemId(user.agentSystemId);
if (text === '/t') {
await this.sendToDeviceEx(user.userSystemId, `Você já está sendo atendido por ${agent.userSystemId}.`, locale);
}
else if (text === '/qt' || text === "Sair" || text === "Fechar") {
} else if (text === '/qt' || text === 'Sair' || text === 'Fechar') {
// TODO: Transfers only in pt-br for now.
await this.sendToDeviceEx(id, Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
await this.sendToDeviceEx(id,
Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
await this.sendToDeviceEx(user.agentSystemId, Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
await sec.updateCurrentAgent(id, this.min.instance.instanceId, null);
}
else {
} else {
GBLog.info(`USER (${id}) TO AGENT ${user.userSystemId}: ${text}`);
this.sendToDeviceEx(user.agentSystemId, `Bot: ${this.min.instance.botId}\n${id}: ${text}`, locale);
}
}
else if (user.agentMode === "bot" || user.agentMode === null || user.agentMode === undefined) {
} else if (user.agentMode === 'bot' || user.agentMode === null || user.agentMode === undefined) {
if (WhatsappDirectLine.conversationIds[from] === undefined) {
GBLog.info(`GBWhatsapp: Starting new conversation on Bot.`);
@ -267,8 +266,7 @@ export class WhatsappDirectLine extends GBService {
this.inputMessage(client, conversationId, text, from, fromName);
}
}
else {
} else {
GBLog.warn(`Inconsistencty found: Invalid agentMode on User Table: ${user.agentMode}`);
}
@ -410,7 +408,7 @@ export class WhatsappDirectLine extends GBService {
public async sendTextAsAudioToDevice(to, msg) {
let url = await GBConversationalService.getAudioBufferFromText(
const url = await GBConversationalService.getAudioBufferFromText(
this.min.instance.speechKey,
this.min.instance.cloudLocation,
msg, this.locale
@ -419,18 +417,6 @@ export class WhatsappDirectLine extends GBService {
await this.sendFileToDevice(to, url, 'Audio', msg);
}
private async sendToDeviceEx(to, text, locale) {
const minBoot = GBServer.globals.minBoot as any;
text = await minBoot.conversationalService.translate(
minBoot,
text,
locale
);
await this.sendToDevice(to, text);
}
public async sendToDevice(to: string, msg: string) {
const cmd = '/audio ';
@ -464,4 +450,16 @@ export class WhatsappDirectLine extends GBService {
}
}
}
private async sendToDeviceEx(to, text, locale) {
const minBoot = GBServer.globals.minBoot as any;
text = await minBoot.conversationalService.translate(
minBoot,
text,
locale
);
await this.sendToDevice(to, text);
}
}

View file

@ -39,8 +39,8 @@
const express = require('express');
const bodyParser = require('body-parser');
import * as fs from 'fs';
let mkdirp = require('mkdirp');
let Path = require('path');
const mkdirp = require('mkdirp');
const Path = require('path');
import { GBLog, GBMinInstance, IGBCoreService, IGBInstance, IGBPackage } from 'botlib';
import { GBAdminService } from '../packages/admin.gbapp/services/GBAdminService';
@ -137,7 +137,7 @@ export class GBServer {
await core.initStorage();
} catch (error) {
GBLog.verbose(`Error initializing storage: ${error}`);
GBServer.globals.bootInstance =
GBServer.globals.bootInstance =
await core.createBootInstance(core, azureDeployer, GBServer.globals.publicAddress);
await core.initStorage();
}
@ -150,9 +150,9 @@ export class GBServer {
GBServer.globals.sysPackages = await core.loadSysPackages(core);
await core.checkStorage(azureDeployer);
await deployer.deployPackages(core, server, GBServer.globals.appPackages);
GBLog.info(`Publishing instances...`);
let instances: IGBInstance[] = await core.loadAllInstances(
const instances: IGBInstance[] = await core.loadAllInstances(
core,
azureDeployer,
GBServer.globals.publicAddress

View file

@ -1,6 +1,6 @@
{
"compilerOptions": {
"allowJs": false,
"allowJs": true,
"downlevelIteration": true,
"baseUrl": "./",
"declaration": false,

View file

@ -1,6 +1,9 @@
{
"defaultSeverity": "warning",
"extends": ["tslint:recommended", "tslint-microsoft-contrib"],
"extends": [
"tslint:recommended",
"tslint-microsoft-contrib"
],
"linterOptions": {
"exclude": [
"libraries/botframework-connector/src/generated/**/*",
@ -9,7 +12,9 @@
"./packages/**/*.gbdialog"
]
},
"rulesDirectory": ["node_modules/tslint-microsoft-contrib"],
"rulesDirectory": [
"node_modules/tslint-microsoft-contrib"
],
"jsRules": {},
"rules": {
"newline-per-chained-call": false,
@ -19,10 +24,20 @@
"typedef": false,
"variable-name": false,
"no-parameter-properties": false,
"max-line-length": [true, { "limit": 120, "ignore-pattern": "^\\s+\\*" }],
"await-promise": [true, "Bluebird"],
"max-line-length": [
true,
{
"limit": 120,
"ignore-pattern": "^\\s+\\*"
}
],
"await-promise": [
true,
"Bluebird"
],
"no-reserved-keywords": false,
"no-unnecessary-class": false,
"no-string-literal": false,
"no-require-imports": false,
"function-name": false,
"no-relative-imports": false,
@ -38,6 +53,6 @@
"switch-final-break": false,
"no-parameter-reassignment": false,
"export-name": false,
"no-backbone-get-set-outside-model": false
"no-backbone-get-set-outside-model": false
}
}
}