fix(config): TSLint parsed on all files.

new(config): Several CI integrations.
This commit is contained in:
Rodrigo Rodriguez (pragmatismo.io) 2018-11-12 12:20:44 -02:00
parent 6f868c5178
commit ea978f7d65
47 changed files with 11509 additions and 1839 deletions

View file

@ -6,4 +6,4 @@ tmp
.env .env
.coveralls.yml .coveralls.yml
coverage coverage
{YOUR_LIB}-*.tgz BotServer-*.tgz

View file

@ -7,7 +7,7 @@ notifications:
email: false email: false
before_install: before_install:
- npm install --global nyc mocha typescript rimraf shx - npm install --global nyc mocha typescript shx
install: install:
- npm install - npm install
@ -17,7 +17,6 @@ before_script:
script: script:
#- npm run build-docs #- npm run build-docs
#- npm run tslint
#- npm run coveralls #- npm run coveralls
branches: branches:
@ -29,10 +28,17 @@ after_success:
- npm pack - npm pack
deploy: deploy:
- provider: pages
skip_cleanup: true
local_dir: docs/reference
github_token: $GITHUB_TOKEN
on:
tags: false
- provider: releases - provider: releases
api_key: $GITHUB_TOKEN api_key: $GITHUB_TOKEN
file_glob: true file_glob: true
file: "{YOURLIB}-*.tgz" file: "BotServer-*.tgz"
skip_cleanup: true skip_cleanup: true
on: on:
tags: false tags: false

9654
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -18,15 +18,29 @@
"url": "https://github.com/pragmatismo-io/BotServer.git" "url": "https://github.com/pragmatismo-io/BotServer.git"
}, },
"scripts": { "scripts": {
"clean": "rimraf dist", "clean": "shx rm -rf node_modules/ dist/ docs/reference",
"tslint": "tslint ./src ./packages/**/*.ts -t verbose", "tslint": "tslint --fix ./src/*.ts ./packages/**/*.ts -t verbose",
"build": "tsc", "build": "tsc",
"build-docs": "typedoc --options typedoc.json src/", "build-docs": "typedoc --options typedoc.json src/",
"test": "nyc --reporter=html --reporter=text mocha -r ts-node/register packages/**/*.test.ts ", "test": "nyc --reporter=html --reporter=text mocha -r ts-node/register packages/**/*.test.ts ",
"pretest": "npm run build",
"coveralls": "npm run test && nyc report --reporter=text-lcov | coveralls", "coveralls": "npm run test && nyc report --reporter=text-lcov | coveralls",
"start": "node ./dist/src/app.js", "start": "node ./dist/src/app.js",
"watch:build": "tsc --watch", "watch:build": "tsc --watch",
"watch:server": "nodemon './dist/index.js' --watch './dist'" "watch:server": "nodemon './dist/index.js' --watch './dist'",
"posttypedoc": "shx cp .nojekyll docs/reference/.nojekyll",
"ban": "ban",
"issues": "git-issues",
"license": "license-checker --production --onlyunknown --csv",
"lint": "standard --verbose --fix 'src/*.js' 'cypress/**/*.js'",
"prelint": "npm run pretty",
"pretty": "prettier-standard 'src/*.ts' 'packages/**/*.ts'",
"secure": "nsp check",
"size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";",
"unused-deps": "dependency-check --unused --no-dev ./package.json",
"travis-deploy-once": "travis-deploy-once",
"semantic-release": "semantic-release",
"commit": "commit-wizard"
}, },
"engines": { "engines": {
"node": "=10.13.0" "node": "=10.13.0"
@ -81,7 +95,7 @@
"reflect-metadata": "0.1.12", "reflect-metadata": "0.1.12",
"request-promise-native": "1.0.5", "request-promise-native": "1.0.5",
"scanf": "^1.0.2", "scanf": "^1.0.2",
"sequelize": "4.41.1", "sequelize": "4.41.2",
"sequelize-typescript": "0.6.6", "sequelize-typescript": "0.6.6",
"simple-git": "^1.107.0", "simple-git": "^1.107.0",
"sqlite3": "4.0.4", "sqlite3": "4.0.4",
@ -99,14 +113,25 @@
"winston": "3.1.0" "winston": "3.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/mocha": "^2.2.47", "@types/mocha": "^5.2.5",
"chai": "^4.2.0", "chai": "^4.2.0",
"coveralls": "^3.0.2", "coveralls": "^3.0.2",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"shx": "^0.3.2", "shx": "^0.3.2",
"ts-loader": "^5.3.0", "ts-loader": "^5.3.0",
"tslint": "^5.11.0", "tslint": "^5.11.0",
"tslint-microsoft-contrib": "^5.2.1" "tslint-microsoft-contrib": "^5.2.1",
"ban-sensitive-files": "1.9.2",
"dependency-check": "3.2.1",
"deps-ok": "1.4.1",
"git-issues": "1.3.1",
"license-checker": "24.0.1",
"nsp": "3.2.1",
"pre-git": "3.17.1",
"prettier-standard": "8.0.1",
"standard": "12.0.1",
"travis-deploy-once": "5.0.9",
"semantic-release": "15.10.8"
}, },
"eslintConfig": { "eslintConfig": {
"env": { "env": {
@ -152,5 +177,18 @@
"warn" "warn"
] ]
} }
},
"release": {
"analyzeCommits": "simple-commit-message"
},
"config": {
"pre-git": {
"commit-msg": "simple",
"pre-commit": [],
"pre-push": [],
"post-commit": [],
"post-checkout": [],
"post-merge": []
}
} }
} }

View file

@ -36,36 +36,36 @@
'use strict'; 'use strict';
const UrlJoin = require("url-join"); const UrlJoin = require('url-join');
import { GBMinInstance } from "botlib"; import { BotAdapter } from 'botbuilder';
import { IGBDialog } from "botlib"; import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBDeployer } from "../../core.gbapp/services/GBDeployer"; import { GBMinInstance } from 'botlib';
import { GBImporter } from "../../core.gbapp/services/GBImporter"; import { IGBDialog } from 'botlib';
import { GBConfigService } from "../../core.gbapp/services/GBConfigService"; import { GBConfigService } from '../../core.gbapp/services/GBConfigService';
import { BotAdapter } from "botbuilder"; import { GBDeployer } from '../../core.gbapp/services/GBDeployer';
import { GBAdminService } from "../services/GBAdminService"; import { GBImporter } from '../../core.gbapp/services/GBImporter';
import { Messages } from "../strings"; import { GBAdminService } from '../services/GBAdminService';
import { WaterfallDialog } from "botbuilder-dialogs"; import { Messages } from '../strings';
/** /**
* Dialogs for administration tasks. * Dialogs for administration tasks.
*/ */
export class AdminDialog extends IGBDialog { export class AdminDialog extends IGBDialog {
static async createFarmCommand(text: any, min: GBMinInstance) {} public static async createFarmCommand(text: any, min: GBMinInstance) {}
static async undeployPackageCommand(text: any, min: GBMinInstance) { public static async undeployPackageCommand(text: any, min: GBMinInstance) {
let packageName = text.split(" ")[1]; const packageName = text.split(' ')[1];
let importer = new GBImporter(min.core); const importer = new GBImporter(min.core);
let deployer = new GBDeployer(min.core, importer); const deployer = new GBDeployer(min.core, importer);
await deployer.undeployPackageFromLocalPath( await deployer.undeployPackageFromLocalPath(
min.instance, min.instance,
UrlJoin("packages", packageName) UrlJoin('packages', packageName)
); );
} }
static async deployPackageCommand(text: string, deployer: GBDeployer) { public static async deployPackageCommand(text: string, deployer: GBDeployer) {
let packageName = text.split(" ")[1]; const packageName = text.split(' ')[1];
let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH"); const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH');
await deployer.deployPackageFromLocalPath( await deployer.deployPackageFromLocalPath(
UrlJoin(additionalPath, packageName) UrlJoin(additionalPath, packageName)
); );
@ -76,59 +76,59 @@ export class AdminDialog extends IGBDialog {
* @param bot The bot adapter. * @param bot The bot adapter.
* @param min The minimal bot instance data. * @param min The minimal bot instance data.
*/ */
static setup(bot: BotAdapter, min: GBMinInstance) { public static setup(bot: BotAdapter, min: GBMinInstance) {
// Setup services. // Setup services.
let importer = new GBImporter(min.core); const importer = new GBImporter(min.core);
let deployer = new GBDeployer(min.core, importer); const deployer = new GBDeployer(min.core, importer);
min.dialogs.add( min.dialogs.add(
new WaterfallDialog("/admin", [ new WaterfallDialog('/admin', [
async step => { async step => {
const locale = step.context.activity.locale; const locale = step.context.activity.locale;
const prompt = Messages[locale].authenticate; const prompt = Messages[locale].authenticate;
await step.prompt("textPrompt", prompt); await step.prompt('textPrompt', prompt);
return await step.next(); return await step.next();
}, },
async step => { async step => {
const locale = step.context.activity.locale; const locale = step.context.activity.locale;
let password = step.result; const password = step.result;
if ( if (
password === GBConfigService.get("ADMIN_PASS") && password === GBConfigService.get('ADMIN_PASS') &&
GBAdminService.StrongRegex.test(password) GBAdminService.StrongRegex.test(password)
) { ) {
await step.context.sendActivity(Messages[locale].welcome); await step.context.sendActivity(Messages[locale].welcome);
await step.prompt("textPrompt", Messages[locale].which_task); await step.prompt('textPrompt', Messages[locale].which_task);
} else { } else {
await step.prompt("textPrompt", Messages[locale].wrong_password); await step.prompt('textPrompt', Messages[locale].wrong_password);
await step.endDialog(); await step.endDialog();
} }
return await step.next(); return await step.next();
}, },
async step => { async step => {
const locale = step.context.activity.locale; const locale = step.context.activity.locale;
var text = step.result; const text = step.result;
let cmdName = text.split(" ")[0]; const cmdName = text.split(' ')[0];
step.context.sendActivity(Messages[locale].working(cmdName)); step.context.sendActivity(Messages[locale].working(cmdName));
let unknownCommand = false; let unknownCommand = false;
if (text === "quit") { if (text === 'quit') {
await step.replaceDialog("/"); await step.replaceDialog('/');
} else if (cmdName === "createFarm") { } else if (cmdName === 'createFarm') {
await AdminDialog.createFarmCommand(text, deployer); await AdminDialog.createFarmCommand(text, deployer);
await step.replaceDialog("/admin", { firstRun: false }); await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === "deployPackage") { } else if (cmdName === 'deployPackage') {
await AdminDialog.deployPackageCommand(text, deployer); await AdminDialog.deployPackageCommand(text, deployer);
await step.replaceDialog("/admin", { firstRun: false }); await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === "redeployPackage") { } else if (cmdName === 'redeployPackage') {
await AdminDialog.undeployPackageCommand(text, min); await AdminDialog.undeployPackageCommand(text, min);
await AdminDialog.deployPackageCommand(text, deployer); await AdminDialog.deployPackageCommand(text, deployer);
await step.replaceDialog("/admin", { firstRun: false }); await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === "undeployPackage") { } else if (cmdName === 'undeployPackage') {
await AdminDialog.undeployPackageCommand(text, min); await AdminDialog.undeployPackageCommand(text, min);
await step.replaceDialog("/admin", { firstRun: false }); await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === "setupSecurity") { } else if (cmdName === 'setupSecurity') {
await AdminDialog.setupSecurity(min, step); await AdminDialog.setupSecurity(min, step);
} else { } else {
unknownCommand = true; unknownCommand = true;
@ -142,7 +142,7 @@ export class AdminDialog extends IGBDialog {
); );
} }
await step.endDialog(); await step.endDialog();
await step.replaceDialog("/answer", { query: text }); await step.replaceDialog('/answer', { query: text });
return await step.next(); return await step.next();
} }
]) ])
@ -151,15 +151,15 @@ export class AdminDialog extends IGBDialog {
private static async setupSecurity(min: any, step: any) { private static async setupSecurity(min: any, step: any) {
const locale = step.activity.locale; const locale = step.activity.locale;
let state = `${min.instance.instanceId}${Math.floor( const state = `${min.instance.instanceId}${Math.floor(
Math.random() * 1000000000 Math.random() * 1000000000
)}`; )}`;
await min.adminService.setValue( await min.adminService.setValue(
min.instance.instanceId, min.instance.instanceId,
"AntiCSRFAttackState", 'AntiCSRFAttackState',
state state
); );
let url = `https://login.microsoftonline.com/${ const url = `https://login.microsoftonline.com/${
min.instance.authenticatorTenant min.instance.authenticatorTenant
}/oauth2/authorize?client_id=${ }/oauth2/authorize?client_id=${
min.instance.authenticatorClientId min.instance.authenticatorClientId

View file

@ -37,32 +37,30 @@
'use strict'; 'use strict';
import { import {
Table,
Column, Column,
Model,
CreatedAt, CreatedAt,
UpdatedAt, Model,
} from "sequelize-typescript"; Table,
UpdatedAt
} from 'sequelize-typescript';
@Table @Table
export class GuaribasAdmin extends Model<GuaribasAdmin> export class GuaribasAdmin extends Model<GuaribasAdmin> {
{
@Column @Column
instanceId: number; public instanceId: number;
@Column @Column
key: string; public key: string;
@Column @Column
value: string; public value: string;
@Column @Column
@CreatedAt @CreatedAt
createdAt: Date; public createdAt: Date;
@Column @Column
@UpdatedAt @UpdatedAt
updatedAt: Date; public updatedAt: Date;
} }

View file

@ -36,116 +36,41 @@
'use strict'; 'use strict';
import { GuaribasAdmin } from "../models/AdminModel"; import { AuthenticationContext, TokenResponse } from 'adal-node';
import { IGBCoreService } from "botlib"; import { IGBCoreService } from 'botlib';
import { AuthenticationContext, TokenResponse } from "adal-node"; import { GuaribasAdmin } from '../models/AdminModel';
const UrlJoin = require("url-join"); const UrlJoin = require('url-join');
const msRestAzure = require("ms-rest-azure"); const msRestAzure = require('ms-rest-azure');
const PasswordGenerator = require("strict-password-generator").default; const PasswordGenerator = require('strict-password-generator').default;
export class GBAdminService { export class GBAdminService {
static GB_PROMPT: string = "GeneralBots: " public static GB_PROMPT: string = 'GeneralBots: ';
public static masterBotInstanceId = 0;
static generateUuid(): string {
return msRestAzure.generateUuid();
}
static masterBotInstanceId = 0;
public static StrongRegex = new RegExp( public static StrongRegex = new RegExp(
"^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*+_-])(?=.{8,})" '^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*+_-])(?=.{8,})'
); );
core: IGBCoreService; public core: IGBCoreService;
constructor(core: IGBCoreService) { constructor(core: IGBCoreService) {
this.core = core; this.core = core;
} }
public async setValue( public static generateUuid(): string {
instanceId: number, return msRestAzure.generateUuid();
key: string,
value: string
): Promise<GuaribasAdmin> {
let options = { where: {} };
options.where = { key: key };
let admin = await GuaribasAdmin.findOne(options);
if (admin == null) {
admin = new GuaribasAdmin();
admin.key = key;
}
admin.value = value;
admin.instanceId = instanceId;
return admin.save();
}
public async getValue(instanceId: number, key: string) {
let options = { where: {} };
options.where = { key: key, instanceId: instanceId };
let obj = await GuaribasAdmin.findOne(options);
return Promise.resolve(obj.value);
}
public async acquireElevatedToken(instanceId): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
let instance = await this.core.loadInstanceById(instanceId);
let expiresOn = new Date(await this.getValue(instanceId, "expiresOn"));
if (expiresOn.getTime() > new Date().getTime()) {
let accessToken = await this.getValue(instanceId, "accessToken");
resolve(accessToken);
} else {
let authorizationUrl = UrlJoin(
instance.authenticatorAuthorityHostUrl,
instance.authenticatorTenant,
"/oauth2/authorize"
);
let refreshToken = await this.getValue(instanceId, "refreshToken");
let resource = "https://graph.microsoft.com";
var authenticationContext = new AuthenticationContext(authorizationUrl);
authenticationContext.acquireTokenWithRefreshToken(
refreshToken,
instance.authenticatorClientId,
instance.authenticatorClientSecret,
resource,
async (err, res) => {
if (err) {
reject(err);
} else {
let token = res as TokenResponse;
await this.setValue(
instanceId,
"accessToken",
token.accessToken
);
await this.setValue(
instanceId,
"refreshToken",
token.refreshToken
);
await this.setValue(
instanceId,
"expiresOn",
token.expiresOn.toString()
);
resolve(token.accessToken);
}
}
);
}
});
} }
public static async getADALTokenFromUsername( public static async getADALTokenFromUsername(
username: string, username: string,
password: string password: string
) { ) {
let credentials = await GBAdminService.getADALCredentialsFromUsername( const credentials = await GBAdminService.getADALCredentialsFromUsername(
username, username,
password password
); );
let accessToken = credentials.tokenCache._entries[0].accessToken; const accessToken = credentials.tokenCache._entries[0].accessToken;
return accessToken; return accessToken;
} }
@ -153,7 +78,7 @@ export class GBAdminService {
username: string, username: string,
password: string password: string
) { ) {
let credentials = await msRestAzure.loginWithUsernamePassword( const credentials = await msRestAzure.loginWithUsernamePassword(
username, username,
password password
); );
@ -171,7 +96,7 @@ export class GBAdminService {
maximumLength: 14 maximumLength: 14
}; };
let password = passwordGenerator.generatePassword(options); let password = passwordGenerator.generatePassword(options);
password = password.replace(/@[=:;\?]/g, "#"); password = password.replace(/@[=:;\?]/g, '#');
return password; return password;
} }
@ -185,8 +110,83 @@ export class GBAdminService {
minimumLength: 12, minimumLength: 12,
maximumLength: 14 maximumLength: 14
}; };
let name = passwordGenerator.generatePassword(options); const name = passwordGenerator.generatePassword(options);
return name; return name;
} }
public async setValue(
instanceId: number,
key: string,
value: string
): Promise<GuaribasAdmin> {
const options = { where: {} };
options.where = { key: key };
let admin = await GuaribasAdmin.findOne(options);
if (admin == null) {
admin = new GuaribasAdmin();
admin.key = key;
}
admin.value = value;
admin.instanceId = instanceId;
return admin.save();
}
public async getValue(instanceId: number, key: string) {
const options = { where: {} };
options.where = { key: key, instanceId: instanceId };
const obj = await GuaribasAdmin.findOne(options);
return Promise.resolve(obj.value);
}
public async acquireElevatedToken(instanceId): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
const instance = await this.core.loadInstanceById(instanceId);
const expiresOn = new Date(await this.getValue(instanceId, 'expiresOn'));
if (expiresOn.getTime() > new Date().getTime()) {
const accessToken = await this.getValue(instanceId, 'accessToken');
resolve(accessToken);
} else {
const authorizationUrl = UrlJoin(
instance.authenticatorAuthorityHostUrl,
instance.authenticatorTenant,
'/oauth2/authorize'
);
const refreshToken = await this.getValue(instanceId, 'refreshToken');
const resource = 'https://graph.microsoft.com';
const authenticationContext = new AuthenticationContext(authorizationUrl);
authenticationContext.acquireTokenWithRefreshToken(
refreshToken,
instance.authenticatorClientId,
instance.authenticatorClientSecret,
resource,
async (err, res) => {
if (err) {
reject(err);
} else {
const token = res as TokenResponse;
await this.setValue(
instanceId,
'accessToken',
token.accessToken
);
await this.setValue(
instanceId,
'refreshToken',
token.refreshToken
);
await this.setValue(
instanceId,
'expiresOn',
token.expiresOn.toString()
);
resolve(token.accessToken);
}
}
);
}
});
}
} }

View file

@ -1,10 +1,10 @@
export const Messages = { export const Messages = {
"en-US": { 'en-US': {
authenticate: "Please, authenticate:", authenticate: 'Please, authenticate:',
welcome: "Welcome to Pragmatismo.io GeneralBots Administration.", welcome: 'Welcome to Pragmatismo.io GeneralBots Administration.',
which_task: "Which task do you wanna run now?", which_task: 'Which task do you wanna run now?',
working:(command)=> `I'm working on ${command}...`, working: (command) => `I'm working on ${command}...`,
finshed_working:"Done.", finshed_working: 'Done.',
unknown_command: text => unknown_command: text =>
`Well, but ${text} is not a administrative General Bots command, I will try to search for it.`, `Well, but ${text} is not a administrative General Bots command, I will try to search for it.`,
hi: text => `Hello, ${text}.`, hi: text => `Hello, ${text}.`,
@ -12,11 +12,11 @@ export const Messages = {
deployPackage: text => `Deploying package ${text}...`, deployPackage: text => `Deploying package ${text}...`,
redeployPackage: text => `Redeploying package ${text}...`, redeployPackage: text => `Redeploying package ${text}...`,
packageUndeployed: text => `Package ${text} undeployed...`, packageUndeployed: text => `Package ${text} undeployed...`,
consent: (url)=>`Please, consent access to this app at: [Microsoft Online](${url}).`, consent: (url) => `Please, consent access to this app at: [Microsoft Online](${url}).`,
wrong_password: "Sorry, wrong password. Please, try again." wrong_password: 'Sorry, wrong password. Please, try again.'
}, },
"pt-BR": { 'pt-BR': {
show_video: "Vou te mostrar um vídeo. Por favor, aguarde...", show_video: 'Vou te mostrar um vídeo. Por favor, aguarde...',
hi: msg => `Oi, ${msg}.` hi: msg => `Oi, ${msg}.`
} }
}; };

View file

@ -34,33 +34,31 @@
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
*/ */
'use strict' 'use strict';
const UrlJoin = require("url-join") const UrlJoin = require('url-join');
import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { Sequelize } from 'sequelize-typescript';
import { GBMinInstance, IGBPackage, IGBCoreService } from "botlib"
import { Sequelize } from "sequelize-typescript"
export class GBAnalyticsPackage implements IGBPackage { export class GBAnalyticsPackage implements IGBPackage {
sysPackages: IGBPackage[] = null public sysPackages: IGBPackage[] = null;
loadPackage(core: IGBCoreService, sequelize: Sequelize): void { public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
} }
unloadPackage(core: IGBCoreService): void { public unloadPackage(core: IGBCoreService): void {
} }
loadBot(min: GBMinInstance): void { public loadBot(min: GBMinInstance): void {
} }
unloadBot(min: GBMinInstance): void { public unloadBot(min: GBMinInstance): void {
} }
onNewSession(min: GBMinInstance, step: any): void { public onNewSession(min: GBMinInstance, step: any): void {
} }
} }

View file

@ -34,36 +34,36 @@
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
*/ */
'use strict' 'use strict';
import { import {
DataTypes,
DataTypeUUIDv4,
DataTypeDate, DataTypeDate,
DataTypeDecimal DataTypeDecimal,
} from "sequelize" DataTypes,
DataTypeUUIDv4
} from 'sequelize';
import { import {
Sequelize, AutoIncrement,
Table,
Column,
Model,
HasMany,
BelongsTo, BelongsTo,
BelongsToMany, BelongsToMany,
Length, Column,
ForeignKey,
CreatedAt, CreatedAt,
UpdatedAt,
DataType, DataType,
ForeignKey,
HasMany,
IsUUID, IsUUID,
Length,
Model,
PrimaryKey, PrimaryKey,
AutoIncrement Sequelize,
} from "sequelize-typescript" Table,
UpdatedAt
} from 'sequelize-typescript';
import { GuaribasSubject } from "../../kb.gbapp/models" import { GuaribasChannel, GuaribasInstance } from '../../core.gbapp/models/GBModel';
import { GuaribasUser } from "../../security.gblib/models" import { GuaribasSubject } from '../../kb.gbapp/models';
import { GuaribasChannel, GuaribasInstance } from "../../core.gbapp/models/GBModel" import { GuaribasUser } from '../../security.gblib/models';
@Table @Table
export class GuaribasConversation extends Model<GuaribasConversation> { export class GuaribasConversation extends Model<GuaribasConversation> {
@ -71,40 +71,40 @@ export class GuaribasConversation extends Model<GuaribasConversation> {
@PrimaryKey @PrimaryKey
@AutoIncrement @AutoIncrement
@Column @Column
conversationId: number public conversationId: number;
@ForeignKey(() => GuaribasSubject) @ForeignKey(() => GuaribasSubject)
@Column @Column
startSubjectId: number public startSubjectId: number;
@BelongsTo(() => GuaribasSubject) @BelongsTo(() => GuaribasSubject)
startSubject: GuaribasSubject public startSubject: GuaribasSubject;
@ForeignKey(() => GuaribasChannel) @ForeignKey(() => GuaribasChannel)
@Column @Column
channelId: string public channelId: string;
@Column rateDate: Date @Column public rateDate: Date;
@Column(DataType.FLOAT) @Column(DataType.FLOAT)
@Column @Column
rate: number public rate: number;
@Column @Column
@CreatedAt @CreatedAt
createdAt: Date public createdAt: Date;
@Column text: string @Column public text: string;
@HasMany(() => GuaribasConversationMessage) @HasMany(() => GuaribasConversationMessage)
conversationMessage: GuaribasConversationMessage[] public conversationMessage: GuaribasConversationMessage[];
@ForeignKey(() => GuaribasUser) @ForeignKey(() => GuaribasUser)
@Column @Column
startedByUserId: number public startedByUserId: number;
@BelongsTo(() => GuaribasUser) @BelongsTo(() => GuaribasUser)
startedBy: GuaribasUser public startedBy: GuaribasUser;
} }
@Table @Table
@ -113,38 +113,38 @@ export class GuaribasConversationMessage extends Model<GuaribasConversationMessa
@PrimaryKey @PrimaryKey
@AutoIncrement @AutoIncrement
@Column @Column
conversationMessageId: number public conversationMessageId: number;
@ForeignKey(() => GuaribasSubject) @ForeignKey(() => GuaribasSubject)
@Column @Column
subjectId: number public subjectId: number;
@Column(DataType.TEXT) @Column(DataType.TEXT)
content: string public content: string;
@Column @Column
@CreatedAt @CreatedAt
createdAt: Date public createdAt: Date;
@Column @Column
@UpdatedAt @UpdatedAt
updatedAt: Date public updatedAt: Date;
@ForeignKey(() => GuaribasConversation) @ForeignKey(() => GuaribasConversation)
@Column @Column
conversationId: number public conversationId: number;
@BelongsTo(() => GuaribasConversation) @BelongsTo(() => GuaribasConversation)
conversation: GuaribasConversation public conversation: GuaribasConversation;
@ForeignKey(() => GuaribasInstance) @ForeignKey(() => GuaribasInstance)
@Column @Column
instanceId: number public instanceId: number;
@ForeignKey(() => GuaribasUser) @ForeignKey(() => GuaribasUser)
@Column @Column
userId: number public userId: number;
@BelongsTo(() => GuaribasUser) @BelongsTo(() => GuaribasUser)
user: GuaribasUser public user: GuaribasUser;
} }

View file

@ -34,38 +34,38 @@
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
*/ */
import { GuaribasUser } from "../../security.gblib/models" import { GuaribasUser } from '../../security.gblib/models';
import { GuaribasConversation, GuaribasConversationMessage } from "../models" import { GuaribasConversation, GuaribasConversationMessage } from '../models';
export class AnalyticsService { export class AnalyticsService {
async createConversation( public async createConversation(
user: GuaribasUser user: GuaribasUser
): Promise<GuaribasConversation> { ): Promise<GuaribasConversation> {
return new Promise<GuaribasConversation>( return new Promise<GuaribasConversation>(
(resolve, reject) => { (resolve, reject) => {
let conversation = new GuaribasConversation() const conversation = new GuaribasConversation();
conversation.startedBy = user conversation.startedBy = user;
conversation.startedByUserId = user.userId conversation.startedByUserId = user.userId;
conversation.save().then((value: GuaribasConversation) => { conversation.save().then((value: GuaribasConversation) => {
resolve(value) resolve(value);
}) });
}) });
} }
createMessage( public createMessage(
conversation: GuaribasConversation, conversation: GuaribasConversation,
user: GuaribasUser, user: GuaribasUser,
content: string content: string
): Promise<GuaribasConversationMessage> { ): Promise<GuaribasConversationMessage> {
return new Promise<GuaribasConversationMessage>( return new Promise<GuaribasConversationMessage>(
(resolve, reject) => { (resolve, reject) => {
let message = GuaribasConversationMessage.build() const message = GuaribasConversationMessage.build();
message.conversation = conversation message.conversation = conversation;
message.user = user message.user = user;
message.content = content message.content = content;
message.save().then((value: GuaribasConversationMessage) => { message.save().then((value: GuaribasConversationMessage) => {
resolve(value) resolve(value);
}) });
}) });
} }
} }

View file

@ -57,13 +57,13 @@ export class BotFarmDialog extends IGBDialog {
'2', '2',
'3', '3',
'4', '4',
'5', '5'
]); ]);
}, },
async step => { async step => {
const locale = step.context.activity.locale; const locale = step.context.activity.locale;
await step.context.sendActivity(Messages[locale].thanks); await step.context.sendActivity(Messages[locale].thanks);
}, }
]); ]);
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
export const Messages = { export const Messages = {
"en-US": { 'en-US': {
about_suggestions: "Suggestions are welcomed and improve my quality...", about_suggestions: 'Suggestions are welcomed and improve my quality...'
}, },
"pt-BR": { 'pt-BR': {
about_suggestions: "Sugestões melhoram muito minha qualidade...", about_suggestions: 'Sugestões melhoram muito minha qualidade...'
} }
}; };

View file

@ -34,32 +34,32 @@
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
*/ */
'use strict' 'use strict';
const UrlJoin = require("url-join") const UrlJoin = require('url-join');
import { GBMinInstance, IGBPackage, IGBCoreService } from "botlib" import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { Sequelize } from 'sequelize-typescript' import { Sequelize } from 'sequelize-typescript';
import { ConsoleDirectLine } from "./services/ConsoleDirectLine" import { ConsoleDirectLine } from './services/ConsoleDirectLine';
export class GBConsolePackage implements IGBPackage { export class GBConsolePackage implements IGBPackage {
sysPackages: IGBPackage[] = null public sysPackages: IGBPackage[] = null;
channel: ConsoleDirectLine public channel: ConsoleDirectLine;
loadPackage(core: IGBCoreService, sequelize: Sequelize): void { public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
} }
unloadPackage(core: IGBCoreService): void { public unloadPackage(core: IGBCoreService): void {
} }
loadBot(min: GBMinInstance): void { public loadBot(min: GBMinInstance): void {
this.channel = new ConsoleDirectLine(min.instance.webchatKey) this.channel = new ConsoleDirectLine(min.instance.webchatKey);
} }
unloadBot(min: GBMinInstance): void { public unloadBot(min: GBMinInstance): void {
} }
onNewSession(min: GBMinInstance, step: any): void { public onNewSession(min: GBMinInstance, step: any): void {
} }
} }

View file

@ -30,76 +30,75 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
const Path = require("path") const Path = require('path');
const Fs = require("fs") const Fs = require('fs');
const _ = require("lodash") const _ = require('lodash');
const Parse = require("csv-parse") const Parse = require('csv-parse');
const Async = require("async") const Async = require('async');
const UrlJoin = require("url-join") const UrlJoin = require('url-join');
const Walk = require("fs-walk") const Walk = require('fs-walk');
const logger = require("../../../src/logger") const logger = require('../../../src/logger');
const Swagger = require('swagger-client') const Swagger = require('swagger-client');
const rp = require('request-promise') const rp = require('request-promise');
import { GBService } from "botlib" import { GBService } from 'botlib';
export class ConsoleDirectLine extends GBService { export class ConsoleDirectLine extends GBService {
pollInterval = 1000 public pollInterval = 1000;
directLineSecret = '' public directLineSecret = '';
directLineClientName = 'DirectLineClient' public directLineClientName = 'DirectLineClient';
directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json' public directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json';
constructor(directLineSecret) { constructor(directLineSecret) {
super() super();
this.directLineSecret = directLineSecret
this.directLineSecret = directLineSecret;
// TODO: Migrate to Swagger 3. // TODO: Migrate to Swagger 3.
let directLineClient = rp(this.directLineSpecUrl) const directLineClient = rp(this.directLineSpecUrl)
.then(function (spec) { .then(function (spec) {
return new Swagger({ return new Swagger({
spec: JSON.parse(spec.trim()), spec: JSON.parse(spec.trim()),
usePromise: true usePromise: true
}) });
}) })
.then(function (client) { .then(function (client) {
client.clientAuthorizations.add('AuthorizationBotConnector', client.clientAuthorizations.add('AuthorizationBotConnector',
new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + directLineSecret, 'header')) new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + directLineSecret, 'header'));
return client return client;
}) })
.catch(function (err) { .catch(function (err) {
console.error('Error initializing DirectLine client', err) console.error('Error initializing DirectLine client', err);
}) });
// TODO: Remove *this* issue. // TODO: Remove *this* issue.
let _this_ = this const _this_ = this;
directLineClient.then((client)=> { directLineClient.then((client) => {
client.Conversations.Conversations_StartConversation() client.Conversations.Conversations_StartConversation()
.then(function (response) { .then(function (response) {
return response.obj.conversationId return response.obj.conversationId;
}) })
.then(function (conversationId) { .then(function (conversationId) {
_this_.sendMessagesFromConsole(client, conversationId) _this_.sendMessagesFromConsole(client, conversationId);
_this_.pollMessages(client, conversationId) _this_.pollMessages(client, conversationId);
}) })
.catch(function (err) { .catch(function (err) {
console.error('Error starting conversation', err) console.error('Error starting conversation', err);
}) });
}) });
} }
sendMessagesFromConsole(client, conversationId) { public sendMessagesFromConsole(client, conversationId) {
let _this_ = this const _this_ = this;
process.stdin.resume() process.stdin.resume();
var stdin = process.stdin const stdin = process.stdin;
process.stdout.write('Command> ') process.stdout.write('Command> ');
stdin.addListener('data', function (e) { stdin.addListener('data', function (e) {
var input = e.toString().trim() const input = e.toString().trim();
if (input) { if (input) {
// exit // exit
if (input.toLowerCase() === 'exit') { if (input.toLowerCase() === 'exit') {
return process.exit() return process.exit();
} }
client.Conversations.Conversations_PostActivity( client.Conversations.Conversations_PostActivity(
@ -115,80 +114,80 @@ export class ConsoleDirectLine extends GBService {
} }
} }
}).catch(function (err) { }).catch(function (err) {
console.error('Error sending message:', err) console.error('Error sending message:', err);
}) });
process.stdout.write('Command> ') process.stdout.write('Command> ');
} }
}) });
} }
/** TBD: Poll Messages from conversation using DirectLine client */ /** TBD: Poll Messages from conversation using DirectLine client */
pollMessages(client, conversationId) { public pollMessages(client, conversationId) {
let _this_ = this const _this_ = this;
console.log('Starting polling message for conversationId: ' + conversationId) console.log('Starting polling message for conversationId: ' + conversationId);
var watermark = null let watermark = null;
setInterval(function () { setInterval(function () {
client.Conversations.Conversations_GetActivities({ conversationId: conversationId, watermark: watermark }) client.Conversations.Conversations_GetActivities({ conversationId: conversationId, watermark: watermark })
.then(function (response) { .then(function (response) {
watermark = response.obj.watermark // use watermark so subsequent requests skip old messages watermark = response.obj.watermark; // use watermark so subsequent requests skip old messages
return response.obj.activities return response.obj.activities;
}) })
.then(_this_.printMessages, _this_.directLineClientName) .then(_this_.printMessages, _this_.directLineClientName);
}, this.pollInterval) }, this.pollInterval);
} }
printMessages(activities, directLineClientName) { public printMessages(activities, directLineClientName) {
if (activities && activities.length) { if (activities && activities.length) {
// ignore own messages // ignore own messages
activities = activities.filter(function (m) { return m.from.id !== directLineClientName }) activities = activities.filter(function (m) { return m.from.id !== directLineClientName; });
if (activities.length) { if (activities.length) {
// print other messages // print other messages
activities.forEach(activity => { activities.forEach(activity => {
console.log(activity.text) console.log(activity.text);
}, this) }, this);
process.stdout.write('Command> ') process.stdout.write('Command> ');
} }
} }
} }
printMessage(activity) { public printMessage(activity) {
if (activity.text) { if (activity.text) {
console.log(activity.text) console.log(activity.text);
} }
if (activity.attachments) { if (activity.attachments) {
activity.attachments.forEach(function (attachment) { activity.attachments.forEach(function (attachment) {
switch (attachment.contentType) { switch (attachment.contentType) {
case "application/vnd.microsoft.card.hero": case 'application/vnd.microsoft.card.hero':
this.renderHeroCard(attachment) this.renderHeroCard(attachment);
break break;
case "image/png": case 'image/png':
console.log('Opening the requested image ' + attachment.contentUrl) console.log('Opening the requested image ' + attachment.contentUrl);
open(attachment.contentUrl) open(attachment.contentUrl);
break break;
} }
}) });
} }
} }
renderHeroCard(attachment) { public renderHeroCard(attachment) {
var width = 70 const width = 70;
var contentLine = function (content) { const contentLine = function (content) {
return ' '.repeat((width - content.length) / 2) + return ' '.repeat((width - content.length) / 2) +
content + content +
' '.repeat((width - content.length) / 2) ' '.repeat((width - content.length) / 2);
} };
console.log('/' + '*'.repeat(width + 1)) console.log('/' + '*'.repeat(width + 1));
console.log('*' + contentLine(attachment.content.title) + '*') console.log('*' + contentLine(attachment.content.title) + '*');
console.log('*' + ' '.repeat(width) + '*') console.log('*' + ' '.repeat(width) + '*');
console.log('*' + contentLine(attachment.content.text) + '*') console.log('*' + contentLine(attachment.content.text) + '*');
console.log('*'.repeat(width + 1) + '/') console.log('*'.repeat(width + 1) + '/');
} }
} }

View file

@ -36,11 +36,11 @@
'use strict'; 'use strict';
import { IGBDialog } from "botlib"; import { BotAdapter } from 'botbuilder';
import { GBMinInstance } from "botlib"; import {WaterfallDialog } from 'botbuilder-dialogs';
import { BotAdapter } from "botbuilder"; import { IGBDialog } from 'botlib';
import {WaterfallDialog } from "botbuilder-dialogs"; import { GBMinInstance } from 'botlib';
import { Messages } from "../strings"; import { Messages } from '../strings';
export class WelcomeDialog extends IGBDialog { export class WelcomeDialog extends IGBDialog {
/** /**
@ -49,9 +49,9 @@ export class WelcomeDialog extends IGBDialog {
* @param bot The bot adapter. * @param bot The bot adapter.
* @param min The minimal bot instance data. * @param min The minimal bot instance data.
*/ */
static setup(bot: BotAdapter, min: GBMinInstance) { public static setup(bot: BotAdapter, min: GBMinInstance) {
min.dialogs.add(new WaterfallDialog("/", [ min.dialogs.add(new WaterfallDialog('/', [
async step => { async step => {
const user = await min.userProfile.get(context, {}); const user = await min.userProfile.get(context, {});
@ -60,9 +60,9 @@ export class WelcomeDialog extends IGBDialog {
if (!user.once) { if (!user.once) {
user.once = true; user.once = true;
await min.userProfile.set(step.context, user); await min.userProfile.set(step.context, user);
var a = new Date(); const a = new Date();
const date = a.getHours(); const date = a.getHours();
var msg = const msg =
date < 12 date < 12
? Messages[locale].good_morning ? Messages[locale].good_morning
: date < 18 : date < 18
@ -70,18 +70,18 @@ export class WelcomeDialog extends IGBDialog {
: Messages[locale].good_night; : Messages[locale].good_night;
await step.context.sendActivity(Messages[locale].hi(msg)); await step.context.sendActivity(Messages[locale].hi(msg));
await step.replaceDialog("/ask", { firstTime: true }); await step.replaceDialog('/ask', { firstTime: true });
if ( if (
step.context.activity && step.context.activity &&
step.context.activity.type == "message" && step.context.activity.type == 'message' &&
step.context.activity.text != "" step.context.activity.text != ''
) { ) {
await step.replaceDialog("/answer", { query: step.context.activity.text }); await step.replaceDialog('/answer', { query: step.context.activity.text });
} }
} }
return await step.next(); return await step.next();
} }
])) ]));
} }
} }

View file

@ -36,11 +36,11 @@
'use strict'; 'use strict';
import { IGBDialog } from "botlib"; import { BotAdapter } from 'botbuilder';
import { GBMinInstance } from "botlib"; import { WaterfallDialog } from 'botbuilder-dialogs';
import { BotAdapter } from "botbuilder"; import { IGBDialog } from 'botlib';
import { Messages } from "../strings"; import { GBMinInstance } from 'botlib';
import { WaterfallDialog } from "botbuilder-dialogs"; import { Messages } from '../strings';
export class WhoAmIDialog extends IGBDialog { export class WhoAmIDialog extends IGBDialog {
/** /**
@ -49,21 +49,21 @@ export class WhoAmIDialog extends IGBDialog {
* @param bot The bot adapter. * @param bot The bot adapter.
* @param min The minimal bot instance data. * @param min The minimal bot instance data.
*/ */
static setup(bot: BotAdapter, min: GBMinInstance) { public static setup(bot: BotAdapter, min: GBMinInstance) {
min.dialogs.add(new WaterfallDialog("/whoAmI", [ min.dialogs.add(new WaterfallDialog('/whoAmI', [
async step => { async step => {
let locale = step.context.activity.locale; const locale = step.context.activity.locale;
await step.context.sendActivity(`${min.instance.description}`); await step.context.sendActivity(`${min.instance.description}`);
if (min.instance.whoAmIVideo) { if (min.instance.whoAmIVideo) {
await step.context.sendActivity(Messages[locale].show_video); await step.context.sendActivity(Messages[locale].show_video);
await min.conversationalService.sendEvent(step, "play", { await min.conversationalService.sendEvent(step, 'play', {
playerType: "video", playerType: 'video',
data: min.instance.whoAmIVideo.trim() data: min.instance.whoAmIVideo.trim()
}); });
} }
await step.replaceDialog("/ask", { isReturning: true }); await step.replaceDialog('/ask', { isReturning: true });
return await step.next(); return await step.next();
} }
])); ]));

View file

@ -34,44 +34,44 @@
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
*/ */
'use strict' 'use strict';
const UrlJoin = require("url-join") const UrlJoin = require('url-join');
import { GBMinInstance, IGBPackage } from "botlib" import { GBMinInstance, IGBPackage } from 'botlib';
import { WelcomeDialog } from "./dialogs/WelcomeDialog" import { IGBCoreService} from 'botlib';
import { WhoAmIDialog } from "./dialogs/WhoAmIDialog" import { Sequelize } from 'sequelize-typescript';
import { IGBCoreService} from "botlib" import { WelcomeDialog } from './dialogs/WelcomeDialog';
import { Sequelize } from "sequelize-typescript" import { WhoAmIDialog } from './dialogs/WhoAmIDialog';
import { GuaribasInstance, GuaribasException, GuaribasPackage, GuaribasChannel } from "./models/GBModel" import { GuaribasChannel, GuaribasException, GuaribasInstance, GuaribasPackage } from './models/GBModel';
export class GBCorePackage implements IGBPackage { export class GBCorePackage implements IGBPackage {
sysPackages: IGBPackage[] = null public static CurrentEngineName = 'guaribas-1.0.0';
public static CurrentEngineName = "guaribas-1.0.0"; public sysPackages: IGBPackage[] = null;
loadPackage(core: IGBCoreService, sequelize: Sequelize): void { public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
core.sequelize.addModels([ core.sequelize.addModels([
GuaribasInstance, GuaribasInstance,
GuaribasPackage, GuaribasPackage,
GuaribasChannel, GuaribasChannel,
GuaribasException, GuaribasException
]) ]);
} }
unloadPackage(core: IGBCoreService): void { public unloadPackage(core: IGBCoreService): void {
} }
loadBot(min: GBMinInstance): void { public loadBot(min: GBMinInstance): void {
WelcomeDialog.setup(min.bot, min) WelcomeDialog.setup(min.bot, min);
WhoAmIDialog.setup(min.bot, min) WhoAmIDialog.setup(min.bot, min);
} }
unloadBot(min: GBMinInstance): void { public unloadBot(min: GBMinInstance): void {
} }
onNewSession(min: GBMinInstance, step: any): void { public onNewSession(min: GBMinInstance, step: any): void {
} }
} }

View file

@ -36,21 +36,20 @@
'use strict'; 'use strict';
import { import {
Table, AutoIncrement,
Column,
Model,
BelongsTo, BelongsTo,
ForeignKey, Column,
CreatedAt, CreatedAt,
UpdatedAt,
DataType, DataType,
ForeignKey,
Model,
PrimaryKey, PrimaryKey,
AutoIncrement Table,
} from "sequelize-typescript"; UpdatedAt
} from 'sequelize-typescript';
import { IGBInstance } from "botlib"; import { IGBInstance } from 'botlib';
@Table @Table
export class GuaribasInstance extends Model<GuaribasInstance> export class GuaribasInstance extends Model<GuaribasInstance>
@ -58,185 +57,185 @@ export class GuaribasInstance extends Model<GuaribasInstance>
@PrimaryKey @PrimaryKey
@AutoIncrement @AutoIncrement
@Column @Column
instanceId: number; public instanceId: number;
@Column @Column
botEndpoint: string; public botEndpoint: string;
@Column @Column
whoAmIVideo: string; public whoAmIVideo: string;
@Column @Column
botId: string; public botId: string;
@Column @Column
title: string; public title: string;
@Column @Column
description: string; public description: string;
@Column @Column
version: string; public version: string;
@Column @Column
enabledAdmin: boolean; public enabledAdmin: boolean;
/* Services section on bot.json */ /* Services section on bot.json */
@Column @Column
engineName: string; public engineName: string;
@Column @Column
marketplaceId: string; public marketplaceId: string;
@Column @Column
textAnalyticsKey: string; public textAnalyticsKey: string;
@Column @Column
textAnalyticsEndpoint: string; public textAnalyticsEndpoint: string;
@Column @Column
marketplacePassword: string; public marketplacePassword: string;
@Column @Column
webchatKey: string; public webchatKey: string;
@Column @Column
authenticatorTenant: string; public authenticatorTenant: string;
@Column @Column
authenticatorAuthorityHostUrl: string; public authenticatorAuthorityHostUrl: string;
@Column @Column
authenticatorClientId: string; public authenticatorClientId: string;
@Column @Column
authenticatorClientSecret: string; public authenticatorClientSecret: string;
@Column @Column
cloudSubscriptionId: string; public cloudSubscriptionId: string;
@Column @Column
cloudUsername: string; public cloudUsername: string;
@Column @Column
cloudPassword: string; public cloudPassword: string;
@Column @Column
cloudLocation: string; public cloudLocation: string;
@Column @Column
whatsappBotKey: string; public whatsappBotKey: string;
@Column @Column
whatsappServiceKey: string; public whatsappServiceKey: string;
@Column @Column
whatsappServiceNumber: string; public whatsappServiceNumber: string;
@Column @Column
whatsappServiceUrl: string; public whatsappServiceUrl: string;
@Column @Column
whatsappServiceWebhookUrl: string; public whatsappServiceWebhookUrl: string;
@Column @Column
smsKey: string; public smsKey: string;
@Column @Column
smsSecret: string; public smsSecret: string;
@Column @Column
smsServiceNumber: string; public smsServiceNumber: string;
@Column @Column
speechKey: string; public speechKey: string;
@Column @Column
speechKeyEndpoint: string; public speechKeyEndpoint: string;
@Column @Column
spellcheckerKey: string; public spellcheckerKey: string;
@Column @Column
spellcheckerEndpoint: string; public spellcheckerEndpoint: string;
@Column @Column
theme: string; public theme: string;
@Column @Column
ui: string; public ui: string;
@Column @Column
kb: string; public kb: string;
@Column @Column
nlpAppId: string; public nlpAppId: string;
@Column @Column
nlpKey: string; public nlpKey: string;
@Column @Column
@Column({ type: DataType.STRING(512) }) @Column({ type: DataType.STRING(512) })
nlpEndpoint: string; public nlpEndpoint: string;
@Column @Column
nlpAuthoringKey: string; public nlpAuthoringKey: string;
@Column @Column
deploymentPaths: string; public deploymentPaths: string;
@Column @Column
searchHost: string; public searchHost: string;
@Column @Column
searchKey: string; public searchKey: string;
@Column @Column
searchIndex: string; public searchIndex: string;
@Column @Column
searchIndexer: string; public searchIndexer: string;
@Column @Column
storageUsername: string; public storageUsername: string;
@Column @Column
storagePassword: string; public storagePassword: string;
@Column @Column
storageName: string; public storageName: string;
@Column @Column
storageServer: string; public storageServer: string;
@Column @Column
storageDialect: string; public storageDialect: string;
@Column @Column
storagePath: string; public storagePath: string;
@Column @Column
adminPass: string; public adminPass: string;
/* Settings section of bot.json */ /* Settings section of bot.json */
@Column(DataType.FLOAT) @Column(DataType.FLOAT)
nlpVsSearch: number; public nlpVsSearch: number;
@Column(DataType.FLOAT) @Column(DataType.FLOAT)
searchScore: number; public searchScore: number;
@Column(DataType.FLOAT) @Column(DataType.FLOAT)
nlpScore: number; public nlpScore: number;
@Column @Column
@CreatedAt @CreatedAt
createdAt: Date; public createdAt: Date;
@Column @Column
@UpdatedAt @UpdatedAt
updatedAt: Date; public updatedAt: Date;
} }
@Table @Table
@ -244,25 +243,25 @@ export class GuaribasPackage extends Model<GuaribasPackage> {
@PrimaryKey @PrimaryKey
@AutoIncrement @AutoIncrement
@Column @Column
packageId: number; public packageId: number;
@Column @Column
packageName: string; public packageName: string;
@ForeignKey(() => GuaribasInstance) @ForeignKey(() => GuaribasInstance)
@Column @Column
instanceId: number; public instanceId: number;
@BelongsTo(() => GuaribasInstance) @BelongsTo(() => GuaribasInstance)
instance: GuaribasInstance; public instance: GuaribasInstance;
@Column @Column
@CreatedAt @CreatedAt
createdAt: Date; public createdAt: Date;
@Column @Column
@UpdatedAt @UpdatedAt
updatedAt: Date; public updatedAt: Date;
} }
@Table @Table
@ -270,18 +269,18 @@ export class GuaribasChannel extends Model<GuaribasChannel> {
@PrimaryKey @PrimaryKey
@AutoIncrement @AutoIncrement
@Column @Column
channelId: number; public channelId: number;
@Column @Column
title: string; public title: string;
@Column @Column
@CreatedAt @CreatedAt
createdAt: Date; public createdAt: Date;
@Column @Column
@UpdatedAt @UpdatedAt
updatedAt: Date; public updatedAt: Date;
} }
@Table @Table
@ -289,23 +288,23 @@ export class GuaribasException extends Model<GuaribasException> {
@PrimaryKey @PrimaryKey
@AutoIncrement @AutoIncrement
@Column @Column
exceptionId: number; public exceptionId: number;
@Column @Column
message: string; public message: string;
@ForeignKey(() => GuaribasInstance) @ForeignKey(() => GuaribasInstance)
@Column @Column
instanceId: number; public instanceId: number;
@BelongsTo(() => GuaribasInstance) @BelongsTo(() => GuaribasInstance)
instance: GuaribasInstance; public instance: GuaribasInstance;
@Column @Column
@CreatedAt @CreatedAt
createdAt: Date; public createdAt: Date;
@Column @Column
@UpdatedAt @UpdatedAt
updatedAt: Date; public updatedAt: Date;
} }

View file

@ -30,20 +30,20 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
const logger = require("../../../src/logger"); const logger = require('../../../src/logger');
import * as fs from "fs"; import * as fs from 'fs';
/** /**
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
*/ */
"use strict"; 'use strict';
export class GBConfigService { export class GBConfigService {
static init(): any { public static init(): any {
try { try {
require("dotenv-extended").load({ require('dotenv-extended').load({
path: ".env", path: '.env',
errorOnMissing: true, errorOnMissing: true,
errorOnExtra: false, errorOnExtra: false,
overrideProcessEnv: true overrideProcessEnv: true
@ -54,52 +54,52 @@ export class GBConfigService {
} }
} }
static get(key: string): string | undefined { public static get(key: string): string | undefined {
let value = GBConfigService.tryGet(key); let value = GBConfigService.tryGet(key);
if (!value) { if (!value) {
switch (key) { switch (key) {
case "CLOUD_USERNAME": case 'CLOUD_USERNAME':
value = undefined; value = undefined;
break; break;
case "BOT_ID": case 'BOT_ID':
value = undefined; value = undefined;
break; break;
case "CLOUD_PASSWORD": case 'CLOUD_PASSWORD':
value = undefined; value = undefined;
break; break;
case "CLOUD_SUBSCRIPTIONID": case 'CLOUD_SUBSCRIPTIONID':
value = undefined; value = undefined;
break; break;
case "CLOUD_LOCATION": case 'CLOUD_LOCATION':
value = undefined; value = undefined;
break; break;
case "NLP_AUTHORING_KEY": case 'NLP_AUTHORING_KEY':
value = undefined; value = undefined;
break; break;
case "STORAGE_DIALECT": case 'STORAGE_DIALECT':
value = undefined; value = undefined;
break; break;
case "STORAGE_STORAGE": case 'STORAGE_STORAGE':
value = "./guaribas.sqlite"; value = './guaribas.sqlite';
break; break;
case "ADDITIONAL_DEPLOY_PATH": case 'ADDITIONAL_DEPLOY_PATH':
value = undefined; value = undefined;
break; break;
case "STORAGE_SYNC": case 'STORAGE_SYNC':
value = "false"; value = 'false';
break; break;
case "STORAGE_SYNC_ALTER": case 'STORAGE_SYNC_ALTER':
value = "false"; value = 'false';
break; break;
case "STORAGE_SYNC_FORCE": case 'STORAGE_SYNC_FORCE':
value = "false"; value = 'false';
break; break;
case "STORAGE_LOGGING": case 'STORAGE_LOGGING':
value = "false"; value = 'false';
break; break;
case "STORAGE_ENCRYPT": case 'STORAGE_ENCRYPT':
value = "true"; value = 'true';
break; break;
default: default:
logger.warn(`Invalid key on .env file: '${key}'`); logger.warn(`Invalid key on .env file: '${key}'`);
@ -110,7 +110,7 @@ export class GBConfigService {
} }
public static tryGet(key: string) { public static tryGet(key: string) {
let value = process.env["container:" + key]; let value = process.env['container:' + key];
if (!value) { if (!value) {
value = process.env[key]; value = process.env[key];
} }

View file

@ -36,17 +36,17 @@
'use strict'; 'use strict';
const logger = require("../../../src/logger"); const logger = require('../../../src/logger');
import { GBCoreService } from "./GBCoreService"; import { any } from 'bluebird';
import { IGBConversationalService } from "botlib"; import { MessageFactory } from 'botbuilder';
import { GBMinInstance } from "botlib"; import { LuisRecognizer } from 'botbuilder-ai';
import { LuisRecognizer } from "botbuilder-ai"; import { IGBConversationalService } from 'botlib';
import { MessageFactory } from "botbuilder"; import { GBMinInstance } from 'botlib';
import { Messages } from "../strings"; import { AzureText } from 'pragmatismo-io-framework';
import { AzureText } from "pragmatismo-io-framework"; import { Messages } from '../strings';
import { any } from "bluebird"; import { GBCoreService } from './GBCoreService';
const Nexmo = require("nexmo"); const Nexmo = require('nexmo');
export interface LanguagePickerSettings { export interface LanguagePickerSettings {
defaultLocale?: string; defaultLocale?: string;
@ -54,27 +54,27 @@ export interface LanguagePickerSettings {
} }
export class GBConversationalService implements IGBConversationalService { export class GBConversationalService implements IGBConversationalService {
coreService: GBCoreService; public coreService: GBCoreService;
constructor(coreService: GBCoreService) { constructor(coreService: GBCoreService) {
this.coreService = coreService; this.coreService = coreService;
} }
getCurrentLanguage(step: any) { public getCurrentLanguage(step: any) {
return step.context.activity.locale; return step.context.activity.locale;
} }
async sendEvent(step: any, name: string, value: any): Promise<any> { public async sendEvent(step: any, name: string, value: any): Promise<any> {
if (step.context.activity.channelId === "webchat") { if (step.context.activity.channelId === 'webchat') {
const msg = MessageFactory.text(""); const msg = MessageFactory.text('');
msg.value = value; msg.value = value;
msg.type = "event"; msg.type = 'event';
msg.name = name; msg.name = name;
return step.context.sendActivity(msg); return step.context.sendActivity(msg);
} }
} }
async sendSms( public async sendSms(
min: GBMinInstance, min: GBMinInstance,
mobile: string, mobile: string,
text: string text: string
@ -99,7 +99,7 @@ export class GBConversationalService implements IGBConversationalService {
}); });
} }
async routeNLP(step: any, min: GBMinInstance, text: string): Promise<boolean> { public async routeNLP(step: any, min: GBMinInstance, text: string): Promise<boolean> {
// Invokes LUIS. // Invokes LUIS.
const model = new LuisRecognizer({ const model = new LuisRecognizer({
@ -112,39 +112,38 @@ export class GBConversationalService implements IGBConversationalService {
try { try {
nlp = await model.recognize(step.context); nlp = await model.recognize(step.context);
} catch (error) { } catch (error) {
if (error.statusCode == 404){ if (error.statusCode == 404) {
logger.warn ('NLP application still not publish and there are no other options for answering.') logger.warn ('NLP application still not publish and there are no other options for answering.');
return Promise.resolve(false); return Promise.resolve(false);
} } else {
else{ const msg = `Error calling NLP server, check if you have a published model and assigned keys on the service. Error: ${
let msg = `Error calling NLP server, check if you have a published model and assigned keys on the service. Error: ${ error.statusCode ? error.statusCode : ''
error.statusCode ? error.statusCode : ""
} ${error.message}`; } ${error.message}`;
return Promise.reject(new Error(msg));} return Promise.reject(new Error(msg)); }
} }
// Resolves intents returned from LUIS. // Resolves intents returned from LUIS.
let topIntent = LuisRecognizer.topIntent(nlp); const topIntent = LuisRecognizer.topIntent(nlp);
if (topIntent) { if (topIntent) {
var intent = topIntent; const intent = topIntent;
var entity = const entity =
nlp.entities && nlp.entities.length > 0 nlp.entities && nlp.entities.length > 0
? nlp.entities[0].entity.toUpperCase() ? nlp.entities[0].entity.toUpperCase()
: null; : null;
if (intent === "None") { if (intent === 'None') {
return Promise.resolve(false); return Promise.resolve(false);
} }
logger.info("NLP called:" + intent + ", " + entity); logger.info('NLP called:' + intent + ', ' + entity);
try { try {
await step.replace("/" + intent, nlp.entities); await step.replace('/' + intent, nlp.entities);
return Promise.resolve(true); return Promise.resolve(true);
} catch (error) { } catch (error) {
let msg = `Error finding dialog associated to NLP event: ${intent}: ${ const msg = `Error finding dialog associated to NLP event: ${intent}: ${
error.message error.message
}`; }`;
return Promise.reject(new Error(msg)); return Promise.reject(new Error(msg));
@ -153,20 +152,20 @@ export class GBConversationalService implements IGBConversationalService {
return Promise.resolve(false); return Promise.resolve(false);
} }
async checkLanguage(step, min, text) { public async checkLanguage(step, min, text) {
let locale = await AzureText.getLocale( const locale = await AzureText.getLocale(
min.instance.textAnalyticsKey, min.instance.textAnalyticsKey,
min.instance.textAnalyticsEndpoint, min.instance.textAnalyticsEndpoint,
text text
); );
if (locale != step.context.activity.locale.split("-")[0]) { if (locale != step.context.activity.locale.split('-')[0]) {
switch (locale) { switch (locale) {
case "pt": case 'pt':
step.context.activity.locale = "pt-BR"; step.context.activity.locale = 'pt-BR';
await step.context.sendActivity(Messages[locale].changing_language); await step.context.sendActivity(Messages[locale].changing_language);
break; break;
case "en": case 'en':
step.context.activity.locale = "en-US"; step.context.activity.locale = 'en-US';
await step.context.sendActivity(Messages[locale].changing_language); await step.context.sendActivity(Messages[locale].changing_language);
break; break;
default: default:

View file

@ -70,7 +70,7 @@ export class GBCoreService implements IGBCoreService {
private createTableQuery: ( private createTableQuery: (
tableName: string, tableName: string,
attributes: any, attributes: any,
options: any, options: any
) => string; ) => string;
/** /**
@ -136,15 +136,15 @@ export class GBCoreService implements IGBCoreService {
dialect: this.dialect, dialect: this.dialect,
storage: storage, storage: storage,
dialectOptions: { dialectOptions: {
encrypt: encrypt, encrypt: encrypt
}, },
pool: { pool: {
max: 32, max: 32,
min: 8, min: 8,
idle: 40000, idle: 40000,
evict: 40000, evict: 40000,
acquire: 40000, acquire: 40000
}, }
}); });
if (this.dialect === 'mssql') { if (this.dialect === 'mssql') {
@ -153,7 +153,7 @@ export class GBCoreService implements IGBCoreService {
this.queryGenerator.createTableQuery = ( this.queryGenerator.createTableQuery = (
tableName, tableName,
attributes, attributes,
options, options
) => this.createTableQueryOverride(tableName, attributes, options); ) => this.createTableQueryOverride(tableName, attributes, options);
this.changeColumnQuery = this.queryGenerator.changeColumnQuery; this.changeColumnQuery = this.queryGenerator.changeColumnQuery;
this.queryGenerator.changeColumnQuery = (tableName, attributes) => this.queryGenerator.changeColumnQuery = (tableName, attributes) =>
@ -163,7 +163,7 @@ export class GBCoreService implements IGBCoreService {
} catch (error) { } catch (error) {
reject(error); reject(error);
} }
}, }
); );
} }
@ -174,7 +174,7 @@ export class GBCoreService implements IGBCoreService {
logger.info('Syncing database...'); logger.info('Syncing database...');
return this.sequelize.sync({ return this.sequelize.sync({
alter: alter, alter: alter,
force: force, force: force
}); });
} else { } else {
const msg = 'Database synchronization is disabled.'; const msg = 'Database synchronization is disabled.';
@ -257,7 +257,7 @@ export class GBCoreService implements IGBCoreService {
let sql: string = this.createTableQuery.apply(this.queryGenerator, [ let sql: string = this.createTableQuery.apply(this.queryGenerator, [
tableName, tableName,
attributes, attributes,
options, options
]); ]);
const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/; const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/;
const matches = re1.exec(sql); const matches = re1.exec(sql);
@ -268,7 +268,7 @@ export class GBCoreService implements IGBCoreService {
re2, re2,
(match: string, ...args: any[]): string => { (match: string, ...args: any[]): string => {
return 'CONSTRAINT [' + table + '_pk] ' + match; return 'CONSTRAINT [' + table + '_pk] ' + match;
}, }
); );
const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g; const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g;
const re4 = /\[([^\]]*)\]/g; const re4 = /\[([^\]]*)\]/g;
@ -283,7 +283,7 @@ export class GBCoreService implements IGBCoreService {
matches = re4.exec(fkcols); matches = re4.exec(fkcols);
} }
return 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')'; return 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')';
}, }
); );
} }
return sql; return sql;
@ -300,7 +300,7 @@ export class GBCoreService implements IGBCoreService {
private changeColumnQueryOverride(tableName, attributes): string { private changeColumnQueryOverride(tableName, attributes): string {
let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [ let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [
tableName, tableName,
attributes, attributes
]); ]);
const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/; const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/;
const matches = re1.exec(sql); const matches = re1.exec(sql);
@ -326,7 +326,7 @@ export class GBCoreService implements IGBCoreService {
fkcols + fkcols +
')' ')'
); );
}, }
); );
} }
return sql; return sql;

View file

@ -36,35 +36,45 @@
'use strict'; 'use strict';
const logger = require("../../../src/logger"); const logger = require('../../../src/logger');
const Path = require("path"); const Path = require('path');
const UrlJoin = require("url-join"); const UrlJoin = require('url-join');
const Fs = require("fs"); const Fs = require('fs');
const WaitUntil = require("wait-until"); const WaitUntil = require('wait-until');
const express = require("express"); const express = require('express');
import { KBService } from "./../../kb.gbapp/services/KBService"; import { IGBCoreService, IGBInstance } from 'botlib';
import { GBImporter } from "./GBImporter"; import { GBError } from 'botlib';
import { IGBCoreService, IGBInstance } from "botlib"; import { IGBPackage } from 'botlib';
import { GBConfigService } from "./GBConfigService"; import { AzureSearch } from 'pragmatismo-io-framework';
import { GBError } from "botlib"; import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService';
import { GuaribasPackage, GuaribasInstance } from "../models/GBModel"; import { GuaribasInstance, GuaribasPackage } from '../models/GBModel';
import { IGBPackage } from "botlib"; import { KBService } from './../../kb.gbapp/services/KBService';
import { AzureSearch } from "pragmatismo-io-framework"; import { GBConfigService } from './GBConfigService';
import { AzureDeployerService } from "../../azuredeployer.gbapp/services/AzureDeployerService"; import { GBImporter } from './GBImporter';
/** Deployer service for bots, themes, ai and more. */ /** Deployer service for bots, themes, ai and more. */
export class GBDeployer { export class GBDeployer {
core: IGBCoreService; public static deployFolder = 'packages';
importer: GBImporter; public core: IGBCoreService;
workDir: string = "./work"; public importer: GBImporter;
static deployFolder = "packages"; public workDir: string = './work';
constructor(core: IGBCoreService, importer: GBImporter) { constructor(core: IGBCoreService, importer: GBImporter) {
this.core = core; this.core = core;
this.importer = importer; this.importer = importer;
} }
public static getConnectionStringFromInstance(instance: GuaribasInstance) {
return `Server=tcp:${
instance.storageServer
}.database.windows.net,1433;Database=${instance.storageName};User ID=${
instance.storageUsername
};Password=${
instance.storagePassword
};Trusted_Connection=False;Encrypt=True;Connection Timeout=30;`;
}
/** /**
* *
* Performs package deployment in all .gbai or default. * Performs package deployment in all .gbai or default.
@ -73,18 +83,18 @@ export class GBDeployer {
public deployPackages( public deployPackages(
core: IGBCoreService, core: IGBCoreService,
server: any, server: any,
appPackages: Array<IGBPackage> appPackages: IGBPackage[]
) { ) {
let _this = this; const _this = this;
return new Promise((resolve: any, reject: any): any => { return new Promise((resolve: any, reject: any): any => {
let totalPackages = 0; let totalPackages = 0;
let additionalPath = GBConfigService.get("ADDITIONAL_DEPLOY_PATH"); const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH');
let paths = [GBDeployer.deployFolder]; let paths = [GBDeployer.deployFolder];
if (additionalPath) { if (additionalPath) {
paths = paths.concat(additionalPath.toLowerCase().split(";")); paths = paths.concat(additionalPath.toLowerCase().split(';'));
} }
let botPackages = new Array<string>(); const botPackages = new Array<string>();
let gbappPackages = new Array<string>(); const gbappPackages = new Array<string>();
let generalPackages = new Array<string>(); let generalPackages = new Array<string>();
function doIt(path) { function doIt(path) {
@ -94,14 +104,14 @@ export class GBDeployer {
.map(name => Path.join(source, name)) .map(name => Path.join(source, name))
.filter(isDirectory); .filter(isDirectory);
let dirs = getDirectories(path); const dirs = getDirectories(path);
dirs.forEach(element => { dirs.forEach(element => {
if (element.startsWith(".")) { if (element.startsWith('.')) {
logger.info(`Ignoring ${element}...`); logger.info(`Ignoring ${element}...`);
} else { } else {
if (element.endsWith(".gbot")) { if (element.endsWith('.gbot')) {
botPackages.push(element); botPackages.push(element);
} else if (element.endsWith(".gbapp")) { } else if (element.endsWith('.gbapp')) {
gbappPackages.push(element); gbappPackages.push(element);
} else { } else {
generalPackages.push(element); generalPackages.push(element);
@ -124,11 +134,11 @@ export class GBDeployer {
gbappPackages.forEach(e => { gbappPackages.forEach(e => {
// Skips .gbapp inside deploy folder. // Skips .gbapp inside deploy folder.
if (!e.startsWith("packages")) { if (!e.startsWith('packages')) {
logger.info(`Deploying app: ${e}...`); logger.info(`Deploying app: ${e}...`);
import(e) import(e)
.then(m => { .then(m => {
let p = new m.Package(); const p = new m.Package();
p.loadPackage(core, core.sequelize); p.loadPackage(core, core.sequelize);
appPackages.push(p); appPackages.push(p);
logger.info(`App (.gbapp) deployed: ${e}.`); logger.info(`App (.gbapp) deployed: ${e}.`);
@ -169,40 +179,40 @@ export class GBDeployer {
/** Then all remaining generalPackages are loaded. */ /** Then all remaining generalPackages are loaded. */
generalPackages = generalPackages.filter(p => !p.endsWith(".git")); generalPackages = generalPackages.filter(p => !p.endsWith('.git'));
generalPackages.forEach(filename => { generalPackages.forEach(filename => {
let filenameOnly = Path.basename(filename); const filenameOnly = Path.basename(filename);
logger.info(`Deploying package: ${filename}...`); logger.info(`Deploying package: ${filename}...`);
/** Handles apps for general bots - .gbapp must stay out of deploy folder. */ /** Handles apps for general bots - .gbapp must stay out of deploy folder. */
if ( if (
Path.extname(filename) === ".gbapp" || Path.extname(filename) === '.gbapp' ||
Path.extname(filename) === ".gblib" Path.extname(filename) === '.gblib'
) { ) {
/** Themes for bots. */ /** Themes for bots. */
} else if (Path.extname(filename) === ".gbtheme") { } else if (Path.extname(filename) === '.gbtheme') {
server.use("/themes/" + filenameOnly, express.static(filename)); server.use('/themes/' + filenameOnly, express.static(filename));
logger.info( logger.info(
`Theme (.gbtheme) assets accessible at: ${"/themes/" + `Theme (.gbtheme) assets accessible at: ${'/themes/' +
filenameOnly}.` filenameOnly}.`
); );
/** Knowledge base for bots. */ /** Knowledge base for bots. */
} else if (Path.extname(filename) === ".gbkb") { } else if (Path.extname(filename) === '.gbkb') {
server.use( server.use(
"/kb/" + filenameOnly + "/subjects", '/kb/' + filenameOnly + '/subjects',
express.static(UrlJoin(filename, "subjects")) express.static(UrlJoin(filename, 'subjects'))
); );
logger.info( logger.info(
`KB (.gbkb) assets accessible at: ${"/kb/" + filenameOnly}.` `KB (.gbkb) assets accessible at: ${'/kb/' + filenameOnly}.`
); );
} else if (Path.extname(filename) === ".gbui") { } else if (Path.extname(filename) === '.gbui') {
// Already Handled // Already Handled
} else { } else {
/** Unknown package format. */ /** Unknown package format. */
let err = new Error(`Package type not handled: ${filename}.`); const err = new Error(`Package type not handled: ${filename}.`);
reject(err); reject(err);
} }
totalPackages++; totalPackages++;
@ -218,7 +228,7 @@ export class GBDeployer {
.done(function(result) { .done(function(result) {
if (botPackages.length === 0) { if (botPackages.length === 0) {
logger.warn( logger.warn(
"No external packages to load, please use ADDITIONAL_DEPLOY_PATH to point to a .gbai package folder." 'No external packages to load, please use ADDITIONAL_DEPLOY_PATH to point to a .gbai package folder.'
); );
} else { } else {
logger.info(`Package deployment done.`); logger.info(`Package deployment done.`);
@ -233,17 +243,17 @@ export class GBDeployer {
* Deploys a bot to the storage. * Deploys a bot to the storage.
*/ */
async deployBot(localPath: string): Promise<IGBInstance> { public async deployBot(localPath: string): Promise<IGBInstance> {
let packageType = Path.extname(localPath); const packageType = Path.extname(localPath);
let packageName = Path.basename(localPath); const packageName = Path.basename(localPath);
let instance = await this.importer.importIfNotExistsBotPackage( const instance = await this.importer.importIfNotExistsBotPackage(
packageName, packageName,
localPath localPath
); );
return instance; return instance;
} }
async deployPackageToStorage( public async deployPackageToStorage(
instanceId: number, instanceId: number,
packageName: string packageName: string
): Promise<GuaribasPackage> { ): Promise<GuaribasPackage> {
@ -253,7 +263,7 @@ export class GBDeployer {
}); });
} }
deployTheme(localPath: string) { public deployTheme(localPath: string) {
// DISABLED: Until completed, "/ui/public". // DISABLED: Until completed, "/ui/public".
// FsExtra.copy(localPath, this.workDir + packageName) // FsExtra.copy(localPath, this.workDir + packageName)
// .then(() => { // .then(() => {
@ -265,26 +275,26 @@ export class GBDeployer {
// }) // })
} }
async deployPackageFromLocalPath(localPath: string) { public async deployPackageFromLocalPath(localPath: string) {
let packageType = Path.extname(localPath); const packageType = Path.extname(localPath);
switch (packageType) { switch (packageType) {
case ".gbot": case '.gbot':
return this.deployBot(localPath); return this.deployBot(localPath);
case ".gbtheme": case '.gbtheme':
return this.deployTheme(localPath); return this.deployTheme(localPath);
// PACKAGE: Put in package logic. // PACKAGE: Put in package logic.
case ".gbkb": case '.gbkb':
let service = new KBService(this.core.sequelize); const service = new KBService(this.core.sequelize);
return service.deployKb(this.core, this, localPath); return service.deployKb(this.core, this, localPath);
case ".gbui": case '.gbui':
break; break;
default: default:
var err = GBError.create( const err = GBError.create(
`GuaribasBusinessError: Unknow package type: ${packageType}.` `GuaribasBusinessError: Unknow package type: ${packageType}.`
); );
Promise.reject(err); Promise.reject(err);
@ -292,30 +302,30 @@ export class GBDeployer {
} }
} }
async undeployPackageFromLocalPath(instance: IGBInstance, localPath: string) { public async undeployPackageFromLocalPath(instance: IGBInstance, localPath: string) {
let packageType = Path.extname(localPath); const packageType = Path.extname(localPath);
let packageName = Path.basename(localPath); const packageName = Path.basename(localPath);
let p = await this.getPackageByName(instance.instanceId, packageName); const p = await this.getPackageByName(instance.instanceId, packageName);
switch (packageType) { switch (packageType) {
case ".gbot": case '.gbot':
// TODO: this.undeployBot(packageName, localPath) // TODO: this.undeployBot(packageName, localPath)
break; break;
case ".gbtheme": case '.gbtheme':
// TODO: this.undeployTheme(packageName, localPath) // TODO: this.undeployTheme(packageName, localPath)
break; break;
case ".gbkb": case '.gbkb':
let service = new KBService(this.core.sequelize); const service = new KBService(this.core.sequelize);
return service.undeployKbFromStorage(instance, this, p.packageId); return service.undeployKbFromStorage(instance, this, p.packageId);
case ".gbui": case '.gbui':
break; break;
default: default:
var err = GBError.create( const err = GBError.create(
`GuaribasBusinessError: Unknown package type: ${packageType}.` `GuaribasBusinessError: Unknown package type: ${packageType}.`
); );
Promise.reject(err); Promise.reject(err);
@ -323,27 +333,17 @@ export class GBDeployer {
} }
} }
public static getConnectionStringFromInstance(instance: GuaribasInstance) {
return `Server=tcp:${
instance.storageServer
}.database.windows.net,1433;Database=${instance.storageName};User ID=${
instance.storageUsername
};Password=${
instance.storagePassword
};Trusted_Connection=False;Encrypt=True;Connection Timeout=30;`;
}
public async rebuildIndex(instance: GuaribasInstance) { public async rebuildIndex(instance: GuaribasInstance) {
let search = new AzureSearch( const search = new AzureSearch(
instance.searchKey, instance.searchKey,
instance.searchHost, instance.searchHost,
instance.searchIndex, instance.searchIndex,
instance.searchIndexer instance.searchIndexer
); );
let connectionString = GBDeployer.getConnectionStringFromInstance(instance); const connectionString = GBDeployer.getConnectionStringFromInstance(instance);
const dsName = "gb"; const dsName = 'gb';
try { try {
await search.deleteDataSource(dsName); await search.deleteDataSource(dsName);
} catch (err) { } catch (err) {
@ -356,8 +356,8 @@ export class GBDeployer {
await search.createDataSource( await search.createDataSource(
dsName, dsName,
dsName, dsName,
"GuaribasQuestion", 'GuaribasQuestion',
"azuresql", 'azuresql',
connectionString connectionString
); );
@ -375,11 +375,11 @@ export class GBDeployer {
); );
} }
async getPackageByName( public async getPackageByName(
instanceId: number, instanceId: number,
packageName: string packageName: string
): Promise<GuaribasPackage> { ): Promise<GuaribasPackage> {
var where = { packageName: packageName, instanceId: instanceId }; const where = { packageName: packageName, instanceId: instanceId };
return GuaribasPackage.findOne({ return GuaribasPackage.findOne({
where: where where: where
}); });
@ -390,11 +390,11 @@ export class GBDeployer {
* Hot deploy processing. * Hot deploy processing.
* *
*/ */
async scanBootPackage() { public async scanBootPackage() {
const deployFolder = "packages"; const deployFolder = 'packages';
let bootPackage = GBConfigService.get("BOOT_PACKAGE"); const bootPackage = GBConfigService.get('BOOT_PACKAGE');
if (bootPackage === "none") { if (bootPackage === 'none') {
return Promise.resolve(true); return Promise.resolve(true);
} else { } else {
return this.deployPackageFromLocalPath( return this.deployPackageFromLocalPath(

View file

@ -30,42 +30,41 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
/** /**
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
*/ */
'use strict' 'use strict';
const UrlJoin = require("url-join") const UrlJoin = require('url-join');
import Fs = require("fs") import { IGBCoreService, IGBInstance } from 'botlib';
import Path = require("path") import fs = require('fs');
import { IGBCoreService, IGBInstance } from "botlib" import path = require('path');
import { SecService } from "../../security.gblib/services/SecService" import { SecService } from '../../security.gblib/services/SecService';
import { GuaribasInstance } from "../models/GBModel" import { GuaribasInstance } from '../models/GBModel';
export class GBImporter { export class GBImporter {
core: IGBCoreService public core: IGBCoreService;
constructor(core: IGBCoreService) { constructor(core: IGBCoreService) {
this.core = core this.core = core;
} }
async importIfNotExistsBotPackage( public async importIfNotExistsBotPackage(
packageName: string, packageName: string,
localPath: string) { localPath: string) {
let packageJson = JSON.parse( const packageJson = JSON.parse(
Fs.readFileSync(UrlJoin(localPath, "package.json"), "utf8") Fs.readFileSync(UrlJoin(localPath, 'package.json'), 'utf8')
) );
let botId = packageJson.botId const botId = packageJson.botId;
let instance = await this.core.loadInstance(botId) const instance = await this.core.loadInstance(botId);
if (instance) { if (instance) {
return Promise.resolve(instance) return Promise.resolve(instance);
} else { } else {
return this.createInstanceInternal(packageName, localPath, packageJson) return this.createInstanceInternal(packageName, localPath, packageJson);
} }
} }
@ -75,20 +74,20 @@ export class GBImporter {
packageJson: any packageJson: any
) { ) {
const settings = JSON.parse( const settings = JSON.parse(
Fs.readFileSync(UrlJoin(localPath, "settings.json"), "utf8") Fs.readFileSync(UrlJoin(localPath, 'settings.json'), 'utf8')
) );
const servicesJson = JSON.parse( const servicesJson = JSON.parse(
Fs.readFileSync(UrlJoin(localPath, "services.json"), "utf8") Fs.readFileSync(UrlJoin(localPath, 'services.json'), 'utf8')
) );
packageJson = Object.assign(packageJson, settings, servicesJson) packageJson = {...packageJson, ...settings, ...servicesJson};
GuaribasInstance.create(packageJson).then((instance: IGBInstance) => { GuaribasInstance.create(packageJson).then((instance: IGBInstance) => {
let service = new SecService() const service = new SecService();
// TODO: service.importSecurityFile(localPath, instance) // TODO: service.importSecurityFile(localPath, instance)
Promise.resolve(instance) Promise.resolve(instance);
}) });
} }
} }

View file

@ -36,48 +36,48 @@
'use strict'; 'use strict';
const { DialogSet, TextPrompt } = require("botbuilder-dialogs"); const { DialogSet, TextPrompt } = require('botbuilder-dialogs');
const UrlJoin = require("url-join"); const UrlJoin = require('url-join');
const express = require("express"); const express = require('express');
const logger = require("../../../src/logger"); const logger = require('../../../src/logger');
const request = require("request-promise-native"); const request = require('request-promise-native');
var AuthenticationContext = require("adal-node").AuthenticationContext; const AuthenticationContext = require('adal-node').AuthenticationContext;
import { import {
AutoSaveStateMiddleware,
BotFrameworkAdapter, BotFrameworkAdapter,
BotStateSet, BotStateSet,
ConversationState, ConversationState,
MemoryStorage, MemoryStorage,
UserState, UserState
AutoSaveStateMiddleware } from 'botbuilder';
} from "botbuilder";
import { GBMinInstance, IGBPackage } from "botlib"; import { GBMinInstance, IGBPackage } from 'botlib';
import { GBAnalyticsPackage } from "../../analytics.gblib";
import { GBCorePackage } from "../../core.gbapp";
import { GBKBPackage } from "../../kb.gbapp";
import { GBDeployer } from "./GBDeployer";
import { GBSecurityPackage } from "../../security.gblib";
import { GBAdminPackage } from "./../../admin.gbapp/index";
import { GBCustomerSatisfactionPackage } from "../../customer-satisfaction.gbapp";
import { GBWhatsappPackage } from "../../whatsapp.gblib";
import { import {
IGBAdminService, IGBAdminService,
IGBCoreService, IGBConversationalService,
IGBConversationalService IGBCoreService
} from "botlib"; } from 'botlib';
import { GuaribasInstance } from "../models/GBModel"; import { GBAnalyticsPackage } from '../../analytics.gblib';
import { Messages } from "../strings"; import { GBCorePackage } from '../../core.gbapp';
import { GBCustomerSatisfactionPackage } from '../../customer-satisfaction.gbapp';
import { GBKBPackage } from '../../kb.gbapp';
import { GBSecurityPackage } from '../../security.gblib';
import { GBWhatsappPackage } from '../../whatsapp.gblib';
import { GuaribasInstance } from '../models/GBModel';
import { Messages } from '../strings';
import { GBAdminPackage } from './../../admin.gbapp/index';
import { GBDeployer } from './GBDeployer';
/** Minimal service layer for a bot. */ /** Minimal service layer for a bot. */
export class GBMinService { export class GBMinService {
core: IGBCoreService; public core: IGBCoreService;
conversationalService: IGBConversationalService; public conversationalService: IGBConversationalService;
adminService: IGBAdminService; public adminService: IGBAdminService;
deployer: GBDeployer; public deployer: GBDeployer;
corePackage = "core.gbai"; public corePackage = 'core.gbai';
/** /**
* Static initialization of minimal instance. * Static initialization of minimal instance.
@ -107,40 +107,40 @@ export class GBMinService {
* *
* */ * */
async buildMin( public async buildMin(
server: any, server: any,
appPackages: Array<IGBPackage>, appPackages: IGBPackage[],
instances: GuaribasInstance[] instances: GuaribasInstance[]
): Promise<GBMinInstance> { ): Promise<GBMinInstance> {
// Serves default UI on root address '/'. // Serves default UI on root address '/'.
let uiPackage = "default.gbui"; const uiPackage = 'default.gbui';
server.use( server.use(
"/", '/',
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, "build")) express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build'))
); );
Promise.all( Promise.all(
instances.map(async instance => { instances.map(async instance => {
// Gets the authorization key for each instance from Bot Service. // Gets the authorization key for each instance from Bot Service.
let webchatToken = await this.getWebchatToken(instance); const webchatToken = await this.getWebchatToken(instance);
// Serves the bot information object via HTTP so clients can get // Serves the bot information object via HTTP so clients can get
// instance information stored on server. // instance information stored on server.
server.get("/instances/:botId", (req, res) => { server.get('/instances/:botId', (req, res) => {
(async () => { (async () => {
// Returns the instance object to clients requesting bot info. // Returns the instance object to clients requesting bot info.
let botId = req.params.botId; const botId = req.params.botId;
let instance = await this.core.loadInstance(botId); const instance = await this.core.loadInstance(botId);
if (instance) { if (instance) {
let speechToken = await this.getSTSToken(instance); const speechToken = await this.getSTSToken(instance);
let theme = instance.theme; let theme = instance.theme;
if (!theme) { if (!theme) {
theme = "default.gbtheme"; theme = 'default.gbtheme';
} }
res.send( res.send(
@ -156,7 +156,7 @@ export class GBMinService {
}) })
); );
} else { } else {
let error = `Instance not found: ${botId}.`; const error = `Instance not found: ${botId}.`;
res.sendStatus(error); res.sendStatus(error);
logger.error(error); logger.error(error);
} }
@ -165,7 +165,7 @@ export class GBMinService {
// Build bot adapter. // Build bot adapter.
var { min, adapter, conversationState } = await this.buildBotAdapter( const { min, adapter, conversationState } = await this.buildBotAdapter(
instance instance
); );
@ -175,7 +175,7 @@ export class GBMinService {
// Serves individual URL for each bot conversational interface... // Serves individual URL for each bot conversational interface...
let url = `/api/messages/${instance.botId}`; const url = `/api/messages/${instance.botId}`;
server.post(url, async (req, res) => { server.post(url, async (req, res) => {
return this.receiver( return this.receiver(
adapter, adapter,
@ -193,14 +193,14 @@ export class GBMinService {
// Serves individual URL for each bot user interface. // Serves individual URL for each bot user interface.
let uiUrl = `/${instance.botId}`; const uiUrl = `/${instance.botId}`;
server.use( server.use(
uiUrl, uiUrl,
express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, "build")) express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build'))
); );
logger.info(`Bot UI ${uiPackage} accessible at: ${uiUrl}.`); logger.info(`Bot UI ${uiPackage} accessible at: ${uiUrl}.`);
let state = `${instance.instanceId}${Math.floor( const state = `${instance.instanceId}${Math.floor(
Math.random() * 1000000000 Math.random() * 1000000000
)}`; )}`;
@ -211,7 +211,7 @@ export class GBMinService {
let authorizationUrl = UrlJoin( let authorizationUrl = UrlJoin(
min.instance.authenticatorAuthorityHostUrl, min.instance.authenticatorAuthorityHostUrl,
min.instance.authenticatorTenant, min.instance.authenticatorTenant,
"/oauth2/authorize" '/oauth2/authorize'
); );
authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${ authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${
min.instance.authenticatorClientId min.instance.authenticatorClientId
@ -227,57 +227,57 @@ export class GBMinService {
// access token that can be used to access the user owned resource. // access token that can be used to access the user owned resource.
server.get(`/${min.instance.botId}/token`, async (req, res) => { server.get(`/${min.instance.botId}/token`, async (req, res) => {
let state = await min.adminService.getValue( const state = await min.adminService.getValue(
min.instance.instanceId, min.instance.instanceId,
"AntiCSRFAttackState" 'AntiCSRFAttackState'
); );
if (req.query.state != state) { if (req.query.state != state) {
let msg = const msg =
"WARNING: state field was not provided as anti-CSRF token"; 'WARNING: state field was not provided as anti-CSRF token';
logger.error(msg); logger.error(msg);
throw new Error(msg); throw new Error(msg);
} }
var authenticationContext = new AuthenticationContext( const authenticationContext = new AuthenticationContext(
UrlJoin( UrlJoin(
min.instance.authenticatorAuthorityHostUrl, min.instance.authenticatorAuthorityHostUrl,
min.instance.authenticatorTenant min.instance.authenticatorTenant
) )
); );
let resource = "https://graph.microsoft.com"; const resource = 'https://graph.microsoft.com';
authenticationContext.acquireTokenWithAuthorizationCode( authenticationContext.acquireTokenWithAuthorizationCode(
req.query.code, req.query.code,
UrlJoin(instance.botEndpoint, min.instance.botId, "/token"), UrlJoin(instance.botEndpoint, min.instance.botId, '/token'),
resource, resource,
instance.authenticatorClientId, instance.authenticatorClientId,
instance.authenticatorClientSecret, instance.authenticatorClientSecret,
async (err, token) => { async (err, token) => {
if (err) { if (err) {
let msg = `Error acquiring token: ${err}`; const msg = `Error acquiring token: ${err}`;
logger.error(msg); logger.error(msg);
res.send(msg); res.send(msg);
} else { } else {
await this.adminService.setValue( await this.adminService.setValue(
instance.instanceId, instance.instanceId,
"refreshToken", 'refreshToken',
token.refreshToken token.refreshToken
); );
await this.adminService.setValue( await this.adminService.setValue(
instance.instanceId, instance.instanceId,
"accessToken", 'accessToken',
token.accessToken token.accessToken
); );
await this.adminService.setValue( await this.adminService.setValue(
instance.instanceId, instance.instanceId,
"expiresOn", 'expiresOn',
token.expiresOn.toString() token.expiresOn.toString()
); );
await this.adminService.setValue( await this.adminService.setValue(
instance.instanceId, instance.instanceId,
"AntiCSRFAttackState", 'AntiCSRFAttackState',
null null
); );
@ -305,8 +305,57 @@ export class GBMinService {
); );
} }
/**
* Get Webchat key from Bot Service.
*
* @param instance The Bot instance.
*
*/
public async getWebchatToken(instance: any) {
const options = {
url: 'https://directline.botframework.com/v3/directline/tokens/generate',
method: 'POST',
headers: {
Authorization: `Bearer ${instance.webchatKey}`
}
};
try {
const json = await request(options);
return Promise.resolve(JSON.parse(json));
} catch (error) {
const msg = `[botId:${instance.botId}] Error calling Direct Line client, verify Bot endpoint on the cloud. Error is: ${error}.`;
return Promise.reject(new Error(msg));
}
}
/**
* Gets a Speech to Text / Text to Speech token from the provider.
*
* @param instance The general bot instance.
*
*/
public async getSTSToken(instance: any) {
// TODO: Make dynamic: https://CHANGE.api.cognitive.microsoft.com/sts/v1.0
const options = {
url: 'https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken',
method: 'POST',
headers: {
'Ocp-Apim-Subscription-Key': instance.speechKey
}
};
try {
return await request(options);
} catch (error) {
const msg = `Error calling Speech to Text client. Error is: ${error}.`;
return Promise.reject(new Error(msg));
}
}
private async buildBotAdapter(instance: any) { private async buildBotAdapter(instance: any) {
let adapter = new BotFrameworkAdapter({ const adapter = new BotFrameworkAdapter({
appId: instance.marketplaceId, appId: instance.marketplaceId,
appPassword: instance.marketplacePassword appPassword: instance.marketplacePassword
}); });
@ -318,7 +367,7 @@ export class GBMinService {
// The minimal bot is built here. // The minimal bot is built here.
let min = new GBMinInstance(); const min = new GBMinInstance();
min.botId = instance.botId; min.botId = instance.botId;
min.bot = adapter; min.bot = adapter;
min.userState = userState; min.userState = userState;
@ -326,16 +375,16 @@ export class GBMinService {
min.conversationalService = this.conversationalService; min.conversationalService = this.conversationalService;
min.adminService = this.adminService; min.adminService = this.adminService;
min.instance = await this.core.loadInstance(min.botId); min.instance = await this.core.loadInstance(min.botId);
min.userProfile = conversationState.createProperty("userProfile"); min.userProfile = conversationState.createProperty('userProfile');
const dialogState = conversationState.createProperty("dialogState"); const dialogState = conversationState.createProperty('dialogState');
min.dialogs = new DialogSet(dialogState); min.dialogs = new DialogSet(dialogState);
min.dialogs.add(new TextPrompt("textPrompt")); min.dialogs.add(new TextPrompt('textPrompt'));
return { min, adapter, conversationState }; return { min, adapter, conversationState };
} }
private invokeLoadBot(appPackages: any[], min: any, server: any) { private invokeLoadBot(appPackages: any[], min: any, server: any) {
let sysPackages = new Array<IGBPackage>(); const sysPackages = new Array<IGBPackage>();
// NOTE: A semicolon is necessary before this line. // NOTE: A semicolon is necessary before this line.
[ [
GBCorePackage, GBCorePackage,
@ -346,13 +395,13 @@ export class GBMinService {
GBCustomerSatisfactionPackage, GBCustomerSatisfactionPackage,
GBWhatsappPackage GBWhatsappPackage
].forEach(sysPackage => { ].forEach(sysPackage => {
let p = Object.create(sysPackage.prototype) as IGBPackage; const p = Object.create(sysPackage.prototype) as IGBPackage;
p.loadBot(min); p.loadBot(min);
sysPackages.push(p); sysPackages.push(p);
if (sysPackage.name === "GBWhatsappPackage") { if (sysPackage.name === 'GBWhatsappPackage') {
let url = "/instances/:botId/whatsapp"; const url = '/instances/:botId/whatsapp';
server.post(url, (req, res) => { server.post(url, (req, res) => {
p["channel"].received(req, res); p.channel.received(req, res);
}); });
} }
}, this); }, this);
@ -378,16 +427,16 @@ export class GBMinService {
return adapter.processActivity(req, res, async context => { return adapter.processActivity(req, res, async context => {
const state = conversationState.get(context); const state = conversationState.get(context);
const step = await min.dialogs.createContext(context, state); const step = await min.dialogs.createContext(context, state);
step.context.activity.locale = "en-US"; // TODO: Make dynamic. step.context.activity.locale = 'en-US'; // TODO: Make dynamic.
try { try {
const user = await min.userProfile.get(context, {}); const user = await min.userProfile.get(context, {});
if (!user.loaded) { if (!user.loaded) {
await min.conversationalService.sendEvent(step, "loadInstance", { await min.conversationalService.sendEvent(step, 'loadInstance', {
instanceId: instance.instanceId, instanceId: instance.instanceId,
botId: instance.botId, botId: instance.botId,
theme: instance.theme?instance.theme:"default.gbtheme" , theme: instance.theme ? instance.theme : 'default.gbtheme' ,
secret: instance.webchatKey secret: instance.webchatKey
}); });
user.loaded = true; user.loaded = true;
@ -401,32 +450,32 @@ export class GBMinService {
}, ${context.activity.channelId}, {context.activity.value})` }, ${context.activity.channelId}, {context.activity.value})`
); );
if ( if (
context.activity.type === "conversationUpdate" && context.activity.type === 'conversationUpdate' &&
context.activity.membersAdded.length > 0 context.activity.membersAdded.length > 0
) { ) {
let member = context.activity.membersAdded[0]; const member = context.activity.membersAdded[0];
if (member.name === "GeneralBots") { if (member.name === 'GeneralBots') {
logger.info(`Bot added to conversation, starting chat...`); logger.info(`Bot added to conversation, starting chat...`);
appPackages.forEach(e => { appPackages.forEach(e => {
e.onNewSession(min, step); e.onNewSession(min, step);
}); });
// Processes the root dialog. // Processes the root dialog.
await step.beginDialog("/"); await step.beginDialog('/');
} else { } else {
logger.info(`Member added to conversation: ${member.name}`); logger.info(`Member added to conversation: ${member.name}`);
} }
// Processes messages. // Processes messages.
} else if (context.activity.type === "message") { } else if (context.activity.type === 'message') {
// Checks for /admin request. // Checks for /admin request.
if (context.activity.text === "admin") { if (context.activity.text === 'admin') {
await step.beginDialog("/admin"); await step.beginDialog('/admin');
// Checks for /menu JSON signature. // Checks for /menu JSON signature.
} else if (context.activity.text.startsWith('{"title"')) { } else if (context.activity.text.startsWith('{"title"')) {
await step.beginDialog("/menu", { await step.beginDialog('/menu', {
data: JSON.parse(context.activity.text) data: JSON.parse(context.activity.text)
}); });
@ -435,102 +484,53 @@ export class GBMinService {
if (step.activeDialog) { if (step.activeDialog) {
await step.continueDialog(); await step.continueDialog();
} else { } else {
await step.beginDialog("/answer", { await step.beginDialog('/answer', {
query: context.activity.text query: context.activity.text
}); });
} }
} }
// Processes events. // Processes events.
} else if (context.activity.type === "event") { } else if (context.activity.type === 'event') {
// Empties dialog stack before going to the target. // Empties dialog stack before going to the target.
await step.endAll(); await step.endAll();
if (context.activity.name === "whoAmI") { if (context.activity.name === 'whoAmI') {
await step.beginDialog("/whoAmI"); await step.beginDialog('/whoAmI');
} else if (context.activity.name === "showSubjects") { } else if (context.activity.name === 'showSubjects') {
await step.beginDialog("/menu"); await step.beginDialog('/menu');
} else if (context.activity.name === "giveFeedback") { } else if (context.activity.name === 'giveFeedback') {
await step.beginDialog("/feedback", { await step.beginDialog('/feedback', {
fromMenu: true fromMenu: true
}); });
} else if (context.activity.name === "showFAQ") { } else if (context.activity.name === 'showFAQ') {
await step.beginDialog("/faq"); await step.beginDialog('/faq');
} else if (context.activity.name === "answerEvent") { } else if (context.activity.name === 'answerEvent') {
await step.beginDialog("/answerEvent", { await step.beginDialog('/answerEvent', {
questionId: (context.activity as any).data, questionId: (context.activity as any).data,
fromFaq: true fromFaq: true
}); });
} else if (context.activity.name === "quality") { } else if (context.activity.name === 'quality') {
await step.beginDialog("/quality", { await step.beginDialog('/quality', {
score: (context.activity as any).data score: (context.activity as any).data
}); });
} else if (context.activity.name === "updateToken") { } else if (context.activity.name === 'updateToken') {
let token = (context.activity as any).data; const token = (context.activity as any).data;
await step.beginDialog("/adminUpdateToken", { token: token }); await step.beginDialog('/adminUpdateToken', { token: token });
} else { } else {
await step.continueDialog(); await step.continueDialog();
} }
} }
} catch (error) { } catch (error) {
let msg = `ERROR: ${error.message} ${error.stack ? error.stack : ""}`; const msg = `ERROR: ${error.message} ${error.stack ? error.stack : ''}`;
logger.error(msg); logger.error(msg);
await step.context.sendActivity( await step.context.sendActivity(
Messages[step.context.activity.locale].very_sorry_about_error Messages[step.context.activity.locale].very_sorry_about_error
); );
await step.beginDialog("/ask", { isReturning: true }); await step.beginDialog('/ask', { isReturning: true });
} }
}); });
} }
/**
* Get Webchat key from Bot Service.
*
* @param instance The Bot instance.
*
*/
async getWebchatToken(instance: any) {
let options = {
url: "https://directline.botframework.com/v3/directline/tokens/generate",
method: "POST",
headers: {
Authorization: `Bearer ${instance.webchatKey}`
}
};
try {
let json = await request(options);
return Promise.resolve(JSON.parse(json));
} catch (error) {
let msg = `[botId:${instance.botId}] Error calling Direct Line client, verify Bot endpoint on the cloud. Error is: ${error}.`;
return Promise.reject(new Error(msg));
}
}
/**
* Gets a Speech to Text / Text to Speech token from the provider.
*
* @param instance The general bot instance.
*
*/
async getSTSToken(instance: any) {
// TODO: Make dynamic: https://CHANGE.api.cognitive.microsoft.com/sts/v1.0
let options = {
url: "https://westus.api.cognitive.microsoft.com/sts/v1.0/issueToken",
method: "POST",
headers: {
"Ocp-Apim-Subscription-Key": instance.speechKey
}
};
try {
return await request(options);
} catch (error) {
let msg = `Error calling Speech to Text client. Error is: ${error}.`;
return Promise.reject(new Error(msg));
}
}
} }

View file

@ -1,20 +1,20 @@
export const Messages = { export const Messages = {
"en-US": { 'en-US': {
show_video: "I will show you a video, please wait...", show_video: 'I will show you a video, please wait...',
good_morning: "good morning", good_morning: 'good morning',
good_evening: "good evening", good_evening: 'good evening',
good_night: "good night", good_night: 'good night',
hi: (msg ) => `Hello, ${msg}.`, hi: (msg) => `Hello, ${msg}.`,
very_sorry_about_error: `I'm sorry to inform that there was an error which was recorded to be solved.` very_sorry_about_error: `I'm sorry to inform that there was an error which was recorded to be solved.`
}, },
"pt-BR": { 'pt-BR': {
show_video: "Vou te mostrar um vídeo. Por favor, aguarde...", show_video: 'Vou te mostrar um vídeo. Por favor, aguarde...',
good_morning: "bom dia", good_morning: 'bom dia',
good_evening: "boa tarde", good_evening: 'boa tarde',
good_night: "boa noite", good_night: 'boa noite',
hi: (msg ) => `Oi, ${msg}.`, hi: (msg) => `Oi, ${msg}.`,
very_sorry_about_error: `Lamento, ocorreu um erro que já foi registrado para ser tratado.` very_sorry_about_error: `Lamento, ocorreu um erro que já foi registrado para ser tratado.`
} }
}; };

View file

@ -1,12 +1,12 @@
import { expect } from 'chai' import { expect } from 'chai';
import 'mocha' import 'mocha';
import {GBImporter} from '../services/GBImporter' import {GBImporter} from '../services/GBImporter';
describe('Hello function', () => { describe('Hello function', () => {
it('should return empty test', () => { it('should return empty test', () => {
let service = new GBImporter(null); const service = new GBImporter(null);
//service.importIfNotExistsBotPackage(null, null); //service.importIfNotExistsBotPackage(null, null);
const result = 0; const result = 0;
expect(result).to.equal(0); expect(result).to.equal(0);

View file

@ -36,13 +36,13 @@
'use strict'; 'use strict';
import { CSService } from "../services/CSService"; import { BotAdapter } from 'botbuilder';
import { AzureText } from "pragmatismo-io-framework"; import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance } from "botlib"; import { GBMinInstance } from 'botlib';
import { IGBDialog } from "botlib"; import { IGBDialog } from 'botlib';
import { BotAdapter } from "botbuilder"; import { AzureText } from 'pragmatismo-io-framework';
import { Messages } from "../strings"; import { CSService } from '../services/CSService';
import { WaterfallDialog } from "botbuilder-dialogs"; import { Messages } from '../strings';
export class FeedbackDialog extends IGBDialog { export class FeedbackDialog extends IGBDialog {
/** /**
@ -51,13 +51,13 @@ export class FeedbackDialog extends IGBDialog {
* @param bot The bot adapter. * @param bot The bot adapter.
* @param min The minimal bot instance data. * @param min The minimal bot instance data.
*/ */
static setup(bot: BotAdapter, min: GBMinInstance) { public static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new CSService(); const service = new CSService();
min.dialogs.add( min.dialogs.add(
new WaterfallDialog("/feedbackNumber", [ new WaterfallDialog('/feedbackNumber', [
async step => { async step => {
let locale = step.context.activity.locale; const locale = step.context.activity.locale;
// TODO: Migrate to 4.*+ await step.prompt("choicePrompt", Messages[locale].what_about_me, [ // TODO: Migrate to 4.*+ await step.prompt("choicePrompt", Messages[locale].what_about_me, [
// "1", // "1",
// "2", // "2",
@ -68,8 +68,8 @@ export class FeedbackDialog extends IGBDialog {
return await step.next(); return await step.next();
}, },
async step => { async step => {
let locale = step.context.activity.locale; const locale = step.context.activity.locale;
let rate = step.result.entity; const rate = step.result.entity;
const user = await min.userProfile.get(context, {}); const user = await min.userProfile.get(context, {});
await service.updateConversationRate(user.conversation, rate); await service.updateConversationRate(user.conversation, rate);
await step.context.sendActivity(Messages[locale].thanks); await step.context.sendActivity(Messages[locale].thanks);
@ -78,19 +78,19 @@ export class FeedbackDialog extends IGBDialog {
]) ])
); );
min.dialogs.add(new WaterfallDialog("/feedback", [ min.dialogs.add(new WaterfallDialog('/feedback', [
async step => { async step => {
let locale = step.context.activity.locale; const locale = step.context.activity.locale;
if (step.result.fromMenu) { if (step.result.fromMenu) {
await step.context.sendActivity(Messages[locale].about_suggestions); await step.context.sendActivity(Messages[locale].about_suggestions);
} }
await step.prompt("textPrompt", Messages[locale].what_about_service); await step.prompt('textPrompt', Messages[locale].what_about_service);
return await step.next(); return await step.next();
}, },
async step => { async step => {
let locale = step.context.activity.locale; const locale = step.context.activity.locale;
let rate = await AzureText.getSentiment( const rate = await AzureText.getSentiment(
min.instance.textAnalyticsKey, min.instance.textAnalyticsKey,
min.instance.textAnalyticsEndpoint, min.instance.textAnalyticsEndpoint,
min.conversationalService.getCurrentLanguage(step), min.conversationalService.getCurrentLanguage(step),
@ -104,7 +104,7 @@ export class FeedbackDialog extends IGBDialog {
// TODO: Record. // TODO: Record.
} }
await step.replaceDialog("/ask", { isReturning: true }); await step.replaceDialog('/ask', { isReturning: true });
return await step.next(); return await step.next();
} }
])); ]));

View file

@ -36,14 +36,14 @@
'use strict'; 'use strict';
import { IGBDialog } from "botlib"; import { IGBDialog } from 'botlib';
import { GBMinInstance } from "botlib"; import { BotAdapter } from 'botbuilder';
import { CSService } from "../services/CSService"; import { WaterfallDialog } from 'botbuilder-dialogs';
import { BotAdapter } from "botbuilder"; import { GBMinInstance } from 'botlib';
import { Messages } from "../strings"; import { CSService } from '../services/CSService';
import { WaterfallDialog } from "botbuilder-dialogs"; import { Messages } from '../strings';
const logger = require("../../../src/logger"); const logger = require('../../../src/logger');
export class QualityDialog extends IGBDialog { export class QualityDialog extends IGBDialog {
/** /**
@ -52,18 +52,18 @@ export class QualityDialog extends IGBDialog {
* @param bot The bot adapter. * @param bot The bot adapter.
* @param min The minimal bot instance data. * @param min The minimal bot instance data.
*/ */
static setup(bot: BotAdapter, min: GBMinInstance) { public static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new CSService(); const service = new CSService();
min.dialogs.add( new WaterfallDialog("/quality", [ min.dialogs.add(new WaterfallDialog('/quality', [
async step => { async step => {
const locale = step.context.activity.locale; const locale = step.context.activity.locale;
const user = await min.userProfile.get(context, {}); const user = await min.userProfile.get(context, {});
var score = step.result; const score = step.result;
setTimeout( setTimeout(
() => min.conversationalService.sendEvent(step, "stop", null), () => min.conversationalService.sendEvent(step, 'stop', null),
400 400
); );
@ -77,7 +77,7 @@ export class QualityDialog extends IGBDialog {
user.lastQuestion, user.lastQuestion,
user.lastQuestionId user.lastQuestionId
); );
await step.replaceDialog("/ask", { isReturning: true }); await step.replaceDialog('/ask', { isReturning: true });
} }
return await step.next(); return await step.next();
} }

View file

@ -34,34 +34,34 @@
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
*/ */
'use strict' 'use strict';
const UrlJoin = require("url-join") const UrlJoin = require('url-join');
import { GuaribasQuestionAlternate } from './models/index' import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { QualityDialog } from './dialogs/QualityDialog' import { FeedbackDialog } from './dialogs/FeedbackDialog';
import { FeedbackDialog } from './dialogs/FeedbackDialog' import { QualityDialog } from './dialogs/QualityDialog';
import { GBMinInstance, IGBPackage, IGBCoreService } from "botlib" import { GuaribasQuestionAlternate } from './models/index';
import { Sequelize } from 'sequelize-typescript' import { Sequelize } from 'sequelize-typescript';
export class GBCustomerSatisfactionPackage implements IGBPackage { export class GBCustomerSatisfactionPackage implements IGBPackage {
sysPackages: IGBPackage[] = null public sysPackages: IGBPackage[] = null;
loadPackage(core: IGBCoreService, sequelize: Sequelize): void { public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
core.sequelize.addModels([ core.sequelize.addModels([
GuaribasQuestionAlternate GuaribasQuestionAlternate
]) ]);
} }
unloadPackage(core: IGBCoreService): void { public unloadPackage(core: IGBCoreService): void {
} }
loadBot(min: GBMinInstance): void { public loadBot(min: GBMinInstance): void {
FeedbackDialog.setup(min.bot, min) FeedbackDialog.setup(min.bot, min);
QualityDialog.setup(min.bot, min) QualityDialog.setup(min.bot, min);
} }
unloadBot(min: GBMinInstance): void { public unloadBot(min: GBMinInstance): void {
} }
onNewSession(min: GBMinInstance, step: any): void { public onNewSession(min: GBMinInstance, step: any): void {
} }
} }

View file

@ -34,34 +34,34 @@
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
*/ */
'use strict' 'use strict';
import { import {
DataTypes,
DataTypeUUIDv4,
DataTypeDate, DataTypeDate,
DataTypeDecimal DataTypeDecimal,
} from "sequelize" DataTypes,
DataTypeUUIDv4
} from 'sequelize';
import { import {
Sequelize, AutoIncrement,
Table,
Column,
Model,
HasMany,
BelongsTo, BelongsTo,
BelongsToMany, BelongsToMany,
Length, Column,
ForeignKey,
CreatedAt, CreatedAt,
UpdatedAt,
DataType, DataType,
ForeignKey,
HasMany,
IsUUID, IsUUID,
Length,
Model,
PrimaryKey, PrimaryKey,
AutoIncrement Sequelize,
} from "sequelize-typescript" Table,
UpdatedAt
} from 'sequelize-typescript';
import { GuaribasInstance } from "../../core.gbapp/models/GBModel" import { GuaribasInstance } from '../../core.gbapp/models/GBModel';
@Table @Table
export class GuaribasQuestionAlternate extends Model<GuaribasQuestionAlternate> { export class GuaribasQuestionAlternate extends Model<GuaribasQuestionAlternate> {
@ -69,16 +69,16 @@ export class GuaribasQuestionAlternate extends Model<GuaribasQuestionAlternate>
@PrimaryKey @PrimaryKey
@AutoIncrement @AutoIncrement
@Column @Column
quickAnswerId: number public quickAnswerId: number;
@Column questionTyped: string @Column public questionTyped: string;
@Column questionText: string @Column public questionText: string;
@ForeignKey(() => GuaribasInstance) @ForeignKey(() => GuaribasInstance)
@Column @Column
instanceId: number public instanceId: number;
@BelongsTo(() => GuaribasInstance) @BelongsTo(() => GuaribasInstance)
instance: GuaribasInstance public instance: GuaribasInstance;
} }

View file

@ -30,12 +30,12 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
import { GuaribasQuestionAlternate } from '../models' import { GuaribasConversation } from '../../analytics.gblib/models';
import { GuaribasConversation } from '../../analytics.gblib/models' import { GuaribasQuestionAlternate } from '../models';
export class CSService { export class CSService {
async resolveQuestionAlternate( public async resolveQuestionAlternate(
instanceId: number, instanceId: number,
questionTyped: string): Promise<GuaribasQuestionAlternate> { questionTyped: string): Promise<GuaribasQuestionAlternate> {
@ -44,24 +44,24 @@ export class CSService {
instanceId: instanceId, instanceId: instanceId,
questionTyped: questionTyped questionTyped: questionTyped
} }
}) });
} }
async insertQuestionAlternate( public async insertQuestionAlternate(
instanceId: number, instanceId: number,
questionTyped: string, questionTyped: string,
questionText: string): Promise<GuaribasQuestionAlternate> { questionText: string): Promise<GuaribasQuestionAlternate> {
return GuaribasQuestionAlternate.create({ return GuaribasQuestionAlternate.create({
questionTyped: questionTyped, questionTyped: questionTyped,
questionText: questionText questionText: questionText
}) });
} }
async updateConversationRate( public async updateConversationRate(
conversation: GuaribasConversation, conversation: GuaribasConversation,
rate: number rate: number
): Promise<GuaribasConversation> { ): Promise<GuaribasConversation> {
conversation.rate = rate conversation.rate = rate;
return conversation.save() return conversation.save();
} }
} }

View file

@ -1,22 +1,22 @@
export const Messages = { export const Messages = {
"en-US": { 'en-US': {
about_suggestions: "Suggestions are welcomed and improve my quality...", about_suggestions: 'Suggestions are welcomed and improve my quality...',
what_about_service: "What about my service?", what_about_service: 'What about my service?',
glad_you_liked: "I'm glad you liked. I'm here for you.", glad_you_liked: 'I\'m glad you liked. I\'m here for you.',
we_will_improve: "Let's take note of that, thanks for sharing.", we_will_improve: 'Let\'s take note of that, thanks for sharing.',
what_about_me: "What about the service, please rate between 1 and 5.", what_about_me: 'What about the service, please rate between 1 and 5.',
thanks: "Thanks!", thanks: 'Thanks!',
im_sorry_lets_try: "I'm sorry. Let's try again...", im_sorry_lets_try: 'I\'m sorry. Let\'s try again...',
great_thanks: "Great, thanks for sharing your thoughts." great_thanks: 'Great, thanks for sharing your thoughts.'
}, },
"pt-BR": { 'pt-BR': {
about_suggestions: "Sugestões melhoram muito minha qualidade...", about_suggestions: 'Sugestões melhoram muito minha qualidade...',
what_about_service:"O que achou do meu atendimento?", what_about_service: 'O que achou do meu atendimento?',
glad_you_liked: "Bom saber que você gostou. Conte comigo.", glad_you_liked: 'Bom saber que você gostou. Conte comigo.',
we_will_improve: "Vamos registrar sua questão, obrigado pela sinceridade.", we_will_improve: 'Vamos registrar sua questão, obrigado pela sinceridade.',
what_about_me: "O que achou do meu atendimento, de 1 a 5?", what_about_me: 'O que achou do meu atendimento, de 1 a 5?',
thanks: "Obrigado!", thanks: 'Obrigado!',
im_sorry_lets_try: "Desculpe-me, vamos tentar novamente.", im_sorry_lets_try: 'Desculpe-me, vamos tentar novamente.',
great_thanks: "Ótimo, obrigado por contribuir com sua resposta." great_thanks: 'Ótimo, obrigado por contribuir com sua resposta.'
} }
}; };

View file

@ -36,15 +36,15 @@
'use strict'; 'use strict';
import { IGBDialog } from "botlib"; import { BotAdapter } from 'botbuilder';
import { AzureText } from "pragmatismo-io-framework"; import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance } from "botlib"; import { IGBDialog } from 'botlib';
import { KBService } from "./../services/KBService"; import { GBMinInstance } from 'botlib';
import { BotAdapter } from "botbuilder"; import { AzureText } from 'pragmatismo-io-framework';
import { Messages } from "../strings"; import { Messages } from '../strings';
import { WaterfallDialog } from "botbuilder-dialogs"; import { KBService } from './../services/KBService';
const logger = require("../../../src/logger"); const logger = require('../../../src/logger');
export class AskDialog extends IGBDialog { export class AskDialog extends IGBDialog {
/** /**
@ -53,18 +53,18 @@ export class AskDialog extends IGBDialog {
* @param bot The bot adapter. * @param bot The bot adapter.
* @param min The minimal bot instance data. * @param min The minimal bot instance data.
*/ */
static setup(bot: BotAdapter, min: GBMinInstance) { public static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new KBService(min.core.sequelize); const service = new KBService(min.core.sequelize);
min.dialogs.add( min.dialogs.add(
new WaterfallDialog("/answerEvent", [ new WaterfallDialog('/answerEvent', [
async step => { async step => {
if (step.options && step.options["questionId"]) { if (step.options && step.options.questionId) {
let question = await service.getQuestionById( const question = await service.getQuestionById(
min.instance.instanceId, min.instance.instanceId,
step.options["questionId"] step.options.questionId
); );
let answer = await service.getAnswerById( const answer = await service.getAnswerById(
min.instance.instanceId, min.instance.instanceId,
question.answerId question.answerId
); );
@ -73,7 +73,7 @@ export class AskDialog extends IGBDialog {
await service.sendAnswer(min.conversationalService, step, answer); await service.sendAnswer(min.conversationalService, step, answer);
await step.replaceDialog("/ask", { isReturning: true }); await step.replaceDialog('/ask', { isReturning: true });
} }
return await step.next(); return await step.next();
} }
@ -81,32 +81,32 @@ export class AskDialog extends IGBDialog {
); );
min.dialogs.add( min.dialogs.add(
new WaterfallDialog("/answer", [ new WaterfallDialog('/answer', [
async step => { async step => {
const user = await min.userProfile.get(step.context, {}); const user = await min.userProfile.get(step.context, {});
let text = step.options["query"]; let text = step.options.query;
if (!text) { if (!text) {
throw new Error(`/answer being called with no args.query text.`); throw new Error(`/answer being called with no args.query text.`);
} }
let locale = step.context.activity.locale; const locale = step.context.activity.locale;
// Stops any content on projector. // Stops any content on projector.
await min.conversationalService.sendEvent(step, "stop", null); await min.conversationalService.sendEvent(step, 'stop', null);
// Handle extra text from FAQ. // Handle extra text from FAQ.
if (step.options && step.options["query"]) { if (step.options && step.options.query) {
text = step.options["query"]; text = step.options.query;
} else if (step.options && step.options["fromFaq"]) { } else if (step.options && step.options.fromFaq) {
await step.context.sendActivity(Messages[locale].going_answer); await step.context.sendActivity(Messages[locale].going_answer);
} }
// Spells check the input text before sending Search or NLP. // Spells check the input text before sending Search or NLP.
if (min.instance.spellcheckerKey) { if (min.instance.spellcheckerKey) {
let data = await AzureText.getSpelledText( const data = await AzureText.getSpelledText(
min.instance.spellcheckerKey, min.instance.spellcheckerKey,
text text
); );
@ -121,7 +121,7 @@ export class AskDialog extends IGBDialog {
user.lastQuestion = text; user.lastQuestion = text;
await min.userProfile.set(step.context, user); await min.userProfile.set(step.context, user);
let resultsA = await service.ask( const resultsA = await service.ask(
min.instance, min.instance,
text, text,
min.instance.searchScore, min.instance.searchScore,
@ -147,11 +147,11 @@ export class AskDialog extends IGBDialog {
// Goes to ask loop, again. // Goes to ask loop, again.
return await step.replaceDialog("/ask", { isReturning: true }); return await step.replaceDialog('/ask', { isReturning: true });
} else { } else {
// Second time running Search, now with no filter. // Second time running Search, now with no filter.
let resultsB = await service.ask( const resultsB = await service.ask(
min.instance, min.instance,
text, text,
min.instance.searchScore, min.instance.searchScore,
@ -172,7 +172,7 @@ export class AskDialog extends IGBDialog {
// Informs user that a broader search will be used. // Informs user that a broader search will be used.
if (user.subjects.length > 0) { if (user.subjects.length > 0) {
let subjectText = `${KBService.getSubjectItemsSeparatedBySpaces( const subjectText = `${KBService.getSubjectItemsSeparatedBySpaces(
user.subjects user.subjects
)}`; )}`;
await step.context.sendActivity(Messages[locale].wider_answer); await step.context.sendActivity(Messages[locale].wider_answer);
@ -185,13 +185,13 @@ export class AskDialog extends IGBDialog {
step, step,
resultsB.answer resultsB.answer
); );
return await step.replaceDialog("/ask", { isReturning: true }); return await step.replaceDialog('/ask', { isReturning: true });
} else { } else {
if ( if (
!(await min.conversationalService.routeNLP(step, min, text)) !(await min.conversationalService.routeNLP(step, min, text))
) { ) {
await step.context.sendActivity(Messages[locale].did_not_find); await step.context.sendActivity(Messages[locale].did_not_find);
return await step.replaceDialog("/ask", { isReturning: true }); return await step.replaceDialog('/ask', { isReturning: true });
} }
} }
} }
@ -200,7 +200,7 @@ export class AskDialog extends IGBDialog {
); );
min.dialogs.add( min.dialogs.add(
new WaterfallDialog("/ask", [ new WaterfallDialog('/ask', [
async step => { async step => {
const locale = step.context.activity.locale; const locale = step.context.activity.locale;
const user = await min.userProfile.get(step.context, {}); const user = await min.userProfile.get(step.context, {});
@ -212,23 +212,23 @@ export class AskDialog extends IGBDialog {
// Three forms of asking. // Three forms of asking.
if (step.options && step.options["firstTime"]) { if (step.options && step.options.firstTime) {
text = Messages[locale].ask_first_time; text = Messages[locale].ask_first_time;
} else if (step.options && step.options["isReturning"]) { } else if (step.options && step.options.isReturning) {
text = Messages[locale].anything_else; text = Messages[locale].anything_else;
} else if (user.subjects.length > 0) { } else if (user.subjects.length > 0) {
text = Messages[locale].which_question; text = Messages[locale].which_question;
} else { } else {
throw new Error("Invalid use of /ask"); throw new Error('Invalid use of /ask');
} }
if (text.length > 0) { if (text.length > 0) {
return await step.prompt("textPrompt", text); return await step.prompt('textPrompt', text);
} }
return await step.next(); return await step.next();
}, },
async step => { async step => {
return await step.replaceDialog("/answer", { query: step.result }); return await step.replaceDialog('/answer', { query: step.result });
} }
]) ])
); );

View file

@ -34,14 +34,14 @@
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
*/ */
'use strict' 'use strict';
import { KBService } from './../services/KBService' import { BotAdapter } from 'botbuilder';
import { IGBDialog } from "botlib"
import { BotAdapter } from "botbuilder"
import { Messages } from "../strings";
import { GBMinInstance } from "botlib"
import { WaterfallDialog } from 'botbuilder-dialogs'; import { WaterfallDialog } from 'botbuilder-dialogs';
import { IGBDialog } from 'botlib';
import { GBMinInstance } from 'botlib';
import { Messages } from '../strings';
import { KBService } from './../services/KBService';
export class FaqDialog extends IGBDialog { export class FaqDialog extends IGBDialog {
/** /**
@ -50,25 +50,25 @@ export class FaqDialog extends IGBDialog {
* @param bot The bot adapter. * @param bot The bot adapter.
* @param min The minimal bot instance data. * @param min The minimal bot instance data.
*/ */
static setup(bot: BotAdapter, min: GBMinInstance) { public static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new KBService(min.core.sequelize) const service = new KBService(min.core.sequelize);
min.dialogs.add(new WaterfallDialog("/faq", [ min.dialogs.add(new WaterfallDialog('/faq', [
async step => { async step => {
let data = await service.getFaqBySubjectArray("faq", null) const data = await service.getFaqBySubjectArray('faq', null);
const locale = step.context.activity.locale; const locale = step.context.activity.locale;
if (data) { if (data) {
await min.conversationalService.sendEvent(step, "play", { await min.conversationalService.sendEvent(step, 'play', {
playerType: "bullet", playerType: 'bullet',
data: data.slice(0, 10) data: data.slice(0, 10)
}) });
await step.context.sendActivity(Messages[locale].see_faq) // TODO: RND messages. await step.context.sendActivity(Messages[locale].see_faq); // TODO: RND messages.
await step.endDialog() await step.endDialog();
return await step.next(); return await step.next();
} }
} }
])) ]));
} }
} }

View file

@ -63,7 +63,7 @@ export class MenuDialog extends IGBDialog {
const locale = step.context.activity.locale; const locale = step.context.activity.locale;
let rootSubjectId = null; let rootSubjectId = null;
if (step.options && step.options['data']) { if (step.options && step.options.data) {
const subject = step.result.data; const subject = step.result.data;
// If there is a shortcut specified as subject destination, go there. // If there is a shortcut specified as subject destination, go there.

View file

@ -34,45 +34,45 @@
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
*/ */
'use strict' 'use strict';
const UrlJoin = require("url-join") const UrlJoin = require('url-join');
import { GuaribasAnswer, GuaribasQuestion, GuaribasSubject } from './models/index' import { GBMinInstance, IGBPackage } from 'botlib';
import { GBMinInstance, IGBPackage } from "botlib" import { GuaribasAnswer, GuaribasQuestion, GuaribasSubject } from './models/index';
import { AskDialog } from "./dialogs/AskDialog" import { IGBCoreService } from 'botlib';
import { FaqDialog } from "./dialogs/FaqDialog" import { Sequelize } from 'sequelize-typescript';
import { MenuDialog } from "./dialogs/MenuDialog" import { AskDialog } from './dialogs/AskDialog';
import { Sequelize } from 'sequelize-typescript' import { FaqDialog } from './dialogs/FaqDialog';
import { IGBCoreService } from 'botlib' import { MenuDialog } from './dialogs/MenuDialog';
export class GBKBPackage implements IGBPackage { export class GBKBPackage implements IGBPackage {
sysPackages: IGBPackage[] = null public sysPackages: IGBPackage[] = null;
loadPackage(core: IGBCoreService, sequelize: Sequelize): void { public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
core.sequelize.addModels([ core.sequelize.addModels([
GuaribasAnswer, GuaribasAnswer,
GuaribasQuestion, GuaribasQuestion,
GuaribasSubject GuaribasSubject
]) ]);
} }
unloadPackage(core: IGBCoreService): void { public unloadPackage(core: IGBCoreService): void {
} }
loadBot(min: GBMinInstance): void { public loadBot(min: GBMinInstance): void {
AskDialog.setup(min.bot, min) AskDialog.setup(min.bot, min);
FaqDialog.setup(min.bot, min) FaqDialog.setup(min.bot, min);
MenuDialog.setup(min.bot, min) MenuDialog.setup(min.bot, min);
} }
unloadBot(min: GBMinInstance): void { public unloadBot(min: GBMinInstance): void {
} }
onNewSession(min: GBMinInstance, step: any): void { public onNewSession(min: GBMinInstance, step: any): void {
} }
} }

View file

@ -30,40 +30,57 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
const logger = require("../../../src/logger") const logger = require('../../../src/logger');
const Path = require("path") const Path = require('path');
const Fs = require("fs") const Fs = require('fs');
const promise = require('bluebird') const promise = require('bluebird');
const parse = promise.promisify(require('csv-parse')) const parse = promise.promisify(require('csv-parse'));
const UrlJoin = require("url-join") const UrlJoin = require('url-join');
const marked = require("marked") const marked = require('marked');
const path = require("path") const path = require('path');
const asyncPromise = require('async-promises') const asyncPromise = require('async-promises');
const walkPromise = require('walk-promise') const walkPromise = require('walk-promise');
import { Messages } from "../strings"; import { Messages } from '../strings';
import { Sequelize } from 'sequelize-typescript' import { IGBConversationalService, IGBCoreService, IGBInstance } from 'botlib';
import { GBConfigService } from './../../core.gbapp/services/GBConfigService' import { AzureSearch } from 'pragmatismo-io-framework';
import { GuaribasQuestion, GuaribasAnswer, GuaribasSubject } from "../models" import { Sequelize } from 'sequelize-typescript';
import { IGBCoreService, IGBConversationalService, IGBInstance } from "botlib" import { GuaribasPackage } from '../../core.gbapp/models/GBModel';
import { AzureSearch } from "pragmatismo-io-framework" import { GBDeployer } from '../../core.gbapp/services/GBDeployer';
import { GBDeployer } from "../../core.gbapp/services/GBDeployer" import { GuaribasAnswer, GuaribasQuestion, GuaribasSubject } from '../models';
import { GuaribasPackage } from "../../core.gbapp/models/GBModel" import { GBConfigService } from './../../core.gbapp/services/GBConfigService';
export class KBServiceSearchResults { export class KBServiceSearchResults {
answer: GuaribasAnswer public answer: GuaribasAnswer;
questionId: number public questionId: number;
} }
export class KBService { export class KBService {
sequelize: Sequelize public sequelize: Sequelize;
constructor(sequelize: Sequelize) { constructor(sequelize: Sequelize) {
this.sequelize = sequelize this.sequelize = sequelize;
} }
async getQuestionById( public static getFormattedSubjectItems(subjects: GuaribasSubject[]) {
if (!subjects) { return ''; }
const out = [];
subjects.forEach(subject => {
out.push(subject.title);
});
return out.join(', ');
}
public static getSubjectItemsSeparatedBySpaces(subjects: GuaribasSubject[]) {
const out = [];
subjects.forEach(subject => {
out.push(subject.internalId);
});
return out.join(' ');
}
public async getQuestionById(
instanceId: number, instanceId: number,
questionId: number questionId: number
): Promise<GuaribasQuestion> { ): Promise<GuaribasQuestion> {
@ -72,10 +89,10 @@ export class KBService {
instanceId: instanceId, instanceId: instanceId,
questionId: questionId questionId: questionId
} }
}) });
} }
async getAnswerById( public async getAnswerById(
instanceId: number, instanceId: number,
answerId: number answerId: number
): Promise<GuaribasAnswer> { ): Promise<GuaribasAnswer> {
@ -84,47 +101,47 @@ export class KBService {
instanceId: instanceId, instanceId: instanceId,
answerId: answerId answerId: answerId
} }
}) });
} }
async getAnswerByText( public async getAnswerByText(
instanceId: number, instanceId: number,
text: string text: string
): Promise<any> { ): Promise<any> {
const Op = Sequelize.Op const Op = Sequelize.Op;
let question = await GuaribasQuestion.findOne({ const question = await GuaribasQuestion.findOne({
where: { where: {
instanceId: instanceId, instanceId: instanceId,
content: { [Op.like]: `%${text.trim()}%` } content: { [Op.like]: `%${text.trim()}%` }
} }
}) });
if (question) { if (question) {
let answer = await GuaribasAnswer.findOne({ const answer = await GuaribasAnswer.findOne({
where: { where: {
instanceId: instanceId, instanceId: instanceId,
answerId: question.answerId answerId: question.answerId
} }
}) });
return Promise.resolve({ question: question, answer: answer }) return Promise.resolve({ question: question, answer: answer });
} }
return Promise.resolve(null) return Promise.resolve(null);
} }
async addAnswer(obj: GuaribasAnswer): Promise<GuaribasAnswer> { public async addAnswer(obj: GuaribasAnswer): Promise<GuaribasAnswer> {
return new Promise<GuaribasAnswer>( return new Promise<GuaribasAnswer>(
(resolve, reject) => { (resolve, reject) => {
GuaribasAnswer.create(obj).then(item => { GuaribasAnswer.create(obj).then(item => {
resolve(item) resolve(item);
}).error((reason) => { }).error((reason) => {
reject(reason) reject(reason);
}) });
}) });
} }
async ask( public async ask(
instance: IGBInstance, instance: IGBInstance,
query: string, query: string,
searchScore: number, searchScore: number,
@ -133,185 +150,165 @@ export class KBService {
// Builds search query. // Builds search query.
query = query.toLowerCase() query = query.toLowerCase();
query = query.replace("?", " ") query = query.replace('?', ' ');
query = query.replace("!", " ") query = query.replace('!', ' ');
query = query.replace(".", " ") query = query.replace('.', ' ');
query = query.replace("/", " ") query = query.replace('/', ' ');
query = query.replace("\\", " ") query = query.replace('\\', ' ');
if (subjects) { if (subjects) {
let text = KBService.getSubjectItemsSeparatedBySpaces(subjects) const text = KBService.getSubjectItemsSeparatedBySpaces(subjects);
if (text) { if (text) {
query = `${query} ${text}` query = `${query} ${text}`;
} }
} }
// TODO: Filter by instance. what = `${what}&$filter=instanceId eq ${instanceId}` // TODO: Filter by instance. what = `${what}&$filter=instanceId eq ${instanceId}`
try { try {
if (instance.searchKey && GBConfigService.get("STORAGE_DIALECT") == "mssql") { if (instance.searchKey && GBConfigService.get('STORAGE_DIALECT') == 'mssql') {
let service = new AzureSearch( const service = new AzureSearch(
instance.searchKey, instance.searchKey,
instance.searchHost, instance.searchHost,
instance.searchIndex, instance.searchIndex,
instance.searchIndexer instance.searchIndexer
) );
let results = await service.search(query) const results = await service.search(query);
if (results && results.length > 0 && if (results && results.length > 0 &&
results[0]["@search.score"] >= searchScore) { results[0]['@search.score'] >= searchScore) {
let value = await this.getAnswerById( const value = await this.getAnswerById(
instance.instanceId, instance.instanceId,
results[0].answerId) results[0].answerId);
if (value) { if (value) {
return Promise.resolve({ answer: value, questionId: results[0].questionId }) return Promise.resolve({ answer: value, questionId: results[0].questionId });
} } else {
else { return Promise.resolve({ answer: null, questionId: 0 });
return Promise.resolve({ answer: null, questionId: 0 })
} }
} }
} else { } else {
let data = await this.getAnswerByText(instance.instanceId, query) const data = await this.getAnswerByText(instance.instanceId, query);
if (data) { if (data) {
return Promise.resolve( return Promise.resolve(
{ answer: data.answer, questionId: data.question.questionId }) { answer: data.answer, questionId: data.question.questionId });
} else { } else {
return Promise.resolve({ answer: null, questionId: 0 }) return Promise.resolve({ answer: null, questionId: 0 });
} }
} }
} } catch (reason) {
catch (reason) {
return Promise.reject(new Error(reason)); return Promise.reject(new Error(reason));
} }
} }
public async getSubjectItems(
static getFormattedSubjectItems(subjects: GuaribasSubject[]) {
if (!subjects) return ""
let out = []
subjects.forEach(subject => {
out.push(subject.title)
})
return out.join(", ")
}
static getSubjectItemsSeparatedBySpaces(subjects: GuaribasSubject[]) {
let out = []
subjects.forEach(subject => {
out.push(subject.internalId)
})
return out.join(" ")
}
async getSubjectItems(
instanceId: number, instanceId: number,
parentId: number parentId: number
): Promise<GuaribasSubject[]> { ): Promise<GuaribasSubject[]> {
var where = { parentSubjectId: parentId, instanceId: instanceId } const where = { parentSubjectId: parentId, instanceId: instanceId };
return GuaribasSubject.findAll({ return GuaribasSubject.findAll({
where: where where: where
}) });
} }
async getFaqBySubjectArray(from: string, subjects: any): Promise<GuaribasQuestion[]> { public async getFaqBySubjectArray(from: string, subjects: any): Promise<GuaribasQuestion[]> {
let where = { const where = {
from: from from: from
} };
if (subjects) { if (subjects) {
if (subjects[0]) { if (subjects[0]) {
where["subject1"] = subjects[0].internalId where.subject1 = subjects[0].internalId;
} }
if (subjects[1]) { if (subjects[1]) {
where["subject2"] = subjects[1].internalId where.subject2 = subjects[1].internalId;
} }
if (subjects[2]) { if (subjects[2]) {
where["subject3"] = subjects[2].internalId where.subject3 = subjects[2].internalId;
} }
if (subjects[3]) { if (subjects[3]) {
where["subject4"] = subjects[3].internalId where.subject4 = subjects[3].internalId;
} }
} }
return await GuaribasQuestion.findAll({ return await GuaribasQuestion.findAll({
where: where where: where
}) });
} }
async importKbTabularFile( public async importKbTabularFile(
filePath: string, filePath: string,
instanceId: number, instanceId: number,
packageId: number packageId: number
): Promise<GuaribasQuestion[]> { ): Promise<GuaribasQuestion[]> {
let file = Fs.readFileSync(filePath, "UCS-2") const file = Fs.readFileSync(filePath, 'UCS-2');
let opts = { const opts = {
delimiter: "\t" delimiter: '\t'
} };
let lastQuestion: GuaribasQuestion; let lastQuestion: GuaribasQuestion;
let lastAnswer: GuaribasAnswer; let lastAnswer: GuaribasAnswer;
let data = await parse(file, opts) const data = await parse(file, opts);
return asyncPromise.eachSeries(data, async line => { return asyncPromise.eachSeries(data, async line => {
// Extracts values from columns in the current line. // Extracts values from columns in the current line.
let subjectsText = line[0] const subjectsText = line[0];
var from = line[1] const from = line[1];
var to = line[2] const to = line[2];
var question = line[3] const question = line[3];
var answer = line[4] let answer = line[4];
// Skips the first line. // Skips the first line.
if (!(subjectsText === "subjects" && from == "from")) { if (!(subjectsText === 'subjects' && from == 'from')) {
let format = ".txt" let format = '.txt';
// Extracts answer from external media if any. // Extracts answer from external media if any.
if (answer.indexOf(".md") > -1) { if (answer.indexOf('.md') > -1) {
let mediaFilename = UrlJoin(path.dirname(filePath), "..", "articles", answer) const mediaFilename = UrlJoin(path.dirname(filePath), '..', 'articles', answer);
if (Fs.existsSync(mediaFilename)) { if (Fs.existsSync(mediaFilename)) {
answer = Fs.readFileSync(mediaFilename, "utf8") answer = Fs.readFileSync(mediaFilename, 'utf8');
format = ".md" format = '.md';
} else { } else {
logger.info(`[GBImporter] File not found: ${mediaFilename}.`) logger.info(`[GBImporter] File not found: ${mediaFilename}.`);
answer = "" answer = '';
} }
} }
// Processes subjects hierarchy splitting by dots. // Processes subjects hierarchy splitting by dots.
let subjectArray = subjectsText.split(".") const subjectArray = subjectsText.split('.');
let subject1: string, subject2: string, subject3: string, let subject1: string, subject2: string, subject3: string,
subject4: string subject4: string;
var indexer = 0 let indexer = 0;
subjectArray.forEach(element => { subjectArray.forEach(element => {
if (indexer == 0) { if (indexer == 0) {
subject1 = subjectArray[indexer].substring(0, 63) subject1 = subjectArray[indexer].substring(0, 63);
} else if (indexer == 1) { } else if (indexer == 1) {
subject2 = subjectArray[indexer].substring(0, 63) subject2 = subjectArray[indexer].substring(0, 63);
} else if (indexer == 2) { } else if (indexer == 2) {
subject3 = subjectArray[indexer].substring(0, 63) subject3 = subjectArray[indexer].substring(0, 63);
} else if (indexer == 3) { } else if (indexer == 3) {
subject4 = subjectArray[indexer].substring(0, 63) subject4 = subjectArray[indexer].substring(0, 63);
} }
indexer++ indexer++;
}) });
// Now with all the data ready, creates entities in the store. // Now with all the data ready, creates entities in the store.
let answer1 = await GuaribasAnswer.create({ const answer1 = await GuaribasAnswer.create({
instanceId: instanceId, instanceId: instanceId,
content: answer, content: answer,
format: format, format: format,
packageId: packageId, packageId: packageId,
prevId: lastQuestion ? lastQuestion.questionId : 0, prevId: lastQuestion ? lastQuestion.questionId : 0
}) });
let question1 = await GuaribasQuestion.create({ const question1 = await GuaribasQuestion.create({
from: from, from: from,
to: to, to: to,
subject1: subject1, subject1: subject1,
@ -322,41 +319,41 @@ export class KBService {
instanceId: instanceId, instanceId: instanceId,
answerId: answer1.answerId, answerId: answer1.answerId,
packageId: packageId packageId: packageId
}) });
if (lastAnswer && lastQuestion) { if (lastAnswer && lastQuestion) {
await lastAnswer.updateAttributes({ nextId: lastQuestion.questionId }) await lastAnswer.updateAttributes({ nextId: lastQuestion.questionId });
} }
lastAnswer = answer1 lastAnswer = answer1;
lastQuestion = question1 lastQuestion = question1;
return Promise.resolve(lastQuestion) return Promise.resolve(lastQuestion);
} else { } else {
// Skips the header. // Skips the header.
return Promise.resolve(null) return Promise.resolve(null);
} }
}) });
} }
async sendAnswer(conversationalService: IGBConversationalService, public async sendAnswer(conversationalService: IGBConversationalService,
step: any, answer: GuaribasAnswer) { step: any, answer: GuaribasAnswer) {
if (answer.content.endsWith('.mp4')) { if (answer.content.endsWith('.mp4')) {
await conversationalService.sendEvent(step, "play", { await conversationalService.sendEvent(step, 'play', {
playerType: "video", playerType: 'video',
data: answer.content data: answer.content
}) });
} else if (answer.content.length > 140 && } else if (answer.content.length > 140 &&
step.context._activity.channelId === "webchat") { step.context._activity.channelId === 'webchat') {
const locale = step.context.activity.locale; const locale = step.context.activity.locale;
await step.context.sendActivity(Messages[locale].will_answer_projector) // TODO: Handle rnd. await step.context.sendActivity(Messages[locale].will_answer_projector); // TODO: Handle rnd.
var html = answer.content let html = answer.content;
if (answer.format === ".md") { if (answer.format === '.md') {
marked.setOptions({ marked.setOptions({
renderer: new marked.Renderer(), renderer: new marked.Renderer(),
gfm: true, gfm: true,
@ -367,23 +364,23 @@ export class KBService {
smartLists: true, smartLists: true,
smartypants: false, smartypants: false,
xhtml: false xhtml: false
}) });
html = marked(answer.content) html = marked(answer.content);
} }
await conversationalService.sendEvent(step, "play", await conversationalService.sendEvent(step, 'play',
{ {
playerType: "markdown", data: { playerType: 'markdown', data: {
content: html, answer: answer, content: html, answer: answer,
prevId: answer.prevId, nextId: answer.nextId prevId: answer.prevId, nextId: answer.nextId
} }
}) });
} else { } else {
await step.context.sendActivity(answer.content) await step.context.sendActivity(answer.content);
await conversationalService.sendEvent(step, "stop", null) await conversationalService.sendEvent(step, 'stop', null);
} }
} }
async importKbPackage( public async importKbPackage(
localPath: string, localPath: string,
packageStorage: GuaribasPackage, packageStorage: GuaribasPackage,
instance: IGBInstance instance: IGBInstance
@ -393,8 +390,8 @@ export class KBService {
await this.importSubjectFile( await this.importSubjectFile(
packageStorage.packageId, packageStorage.packageId,
UrlJoin(localPath, "subjects.json"), UrlJoin(localPath, 'subjects.json'),
instance) instance);
// Import all .tsv files in the tabular directory. // Import all .tsv files in the tabular directory.
@ -402,41 +399,40 @@ export class KBService {
localPath, localPath,
instance, instance,
packageStorage.packageId packageStorage.packageId
) );
} }
public async importKbTabularDirectory(
async importKbTabularDirectory(
localPath: string, localPath: string,
instance: IGBInstance, instance: IGBInstance,
packageId: number packageId: number
): Promise<any> { ): Promise<any> {
let files = await walkPromise(UrlJoin(localPath, "tabular")) const files = await walkPromise(UrlJoin(localPath, 'tabular'));
return Promise.all(files.map(async file => { return Promise.all(files.map(async file => {
if (file.name.endsWith(".tsv")) { if (file.name.endsWith('.tsv')) {
return this.importKbTabularFile( return this.importKbTabularFile(
UrlJoin(file.root, file.name), UrlJoin(file.root, file.name),
instance.instanceId, instance.instanceId,
packageId) packageId);
} }
})) }));
} }
async importSubjectFile( public async importSubjectFile(
packageId: number, packageId: number,
filename: string, filename: string,
instance: IGBInstance instance: IGBInstance
): Promise<any> { ): Promise<any> {
var subjects = JSON.parse(Fs.readFileSync(filename, "utf8")) const subjects = JSON.parse(Fs.readFileSync(filename, 'utf8'));
const doIt = async (subjects: GuaribasSubject[], parentSubjectId: number) => { const doIt = async (subjects: GuaribasSubject[], parentSubjectId: number) => {
return asyncPromise.eachSeries(subjects, async item => { return asyncPromise.eachSeries(subjects, async item => {
let mediaFilename = item.id + ".png" const mediaFilename = item.id + '.png';
let value = await GuaribasSubject.create({ const value = await GuaribasSubject.create({
internalId: item.id, internalId: item.id,
parentSubjectId: parentSubjectId, parentSubjectId: parentSubjectId,
instanceId: instance.instanceId, instanceId: instance.instanceId,
@ -445,20 +441,19 @@ export class KBService {
title: item.title, title: item.title,
description: item.description, description: item.description,
packageId: packageId packageId: packageId
}) });
if (item.children) { if (item.children) {
return Promise.resolve(doIt(item.children, value.subjectId)) return Promise.resolve(doIt(item.children, value.subjectId));
} else {
return Promise.resolve(item);
} }
else { });
return Promise.resolve(item) };
} return doIt(subjects.children, null);
})
}
return doIt(subjects.children, null)
} }
async undeployKbFromStorage( public async undeployKbFromStorage(
instance: IGBInstance, instance: IGBInstance,
deployer: GBDeployer, deployer: GBDeployer,
packageId: number packageId: number
@ -466,18 +461,18 @@ export class KBService {
await GuaribasQuestion.destroy({ await GuaribasQuestion.destroy({
where: { instanceId: instance.instanceId, packageId: packageId } where: { instanceId: instance.instanceId, packageId: packageId }
}) });
await GuaribasAnswer.destroy({ await GuaribasAnswer.destroy({
where: { instanceId: instance.instanceId, packageId: packageId } where: { instanceId: instance.instanceId, packageId: packageId }
}) });
await GuaribasSubject.destroy({ await GuaribasSubject.destroy({
where: { instanceId: instance.instanceId, packageId: packageId } where: { instanceId: instance.instanceId, packageId: packageId }
}) });
await GuaribasPackage.destroy({ await GuaribasPackage.destroy({
where: { instanceId: instance.instanceId, packageId: packageId } where: { instanceId: instance.instanceId, packageId: packageId }
}) });
await deployer.rebuildIndex(instance) await deployer.rebuildIndex(instance);
} }
/** /**
@ -485,22 +480,22 @@ export class KBService {
* *
* @param localPath Path to the .gbkb folder. * @param localPath Path to the .gbkb folder.
*/ */
async deployKb(core: IGBCoreService, deployer: GBDeployer, localPath: string) { public async deployKb(core: IGBCoreService, deployer: GBDeployer, localPath: string) {
let packageType = Path.extname(localPath) const packageType = Path.extname(localPath);
let packageName = Path.basename(localPath) const packageName = Path.basename(localPath);
logger.info(`[GBDeployer] Opening package: ${localPath}`) logger.info(`[GBDeployer] Opening package: ${localPath}`);
let packageObject = JSON.parse( const packageObject = JSON.parse(
Fs.readFileSync(UrlJoin(localPath, "package.json"), "utf8") Fs.readFileSync(UrlJoin(localPath, 'package.json'), 'utf8')
) );
let instance = await core.loadInstance(packageObject.botId) const instance = await core.loadInstance(packageObject.botId);
logger.info(`[GBDeployer] Importing: ${localPath}`) logger.info(`[GBDeployer] Importing: ${localPath}`);
let p = await deployer.deployPackageToStorage( const p = await deployer.deployPackageToStorage(
instance.instanceId, instance.instanceId,
packageName) packageName);
await this.importKbPackage(localPath, p, instance) await this.importKbPackage(localPath, p, instance);
deployer.rebuildIndex(instance) deployer.rebuildIndex(instance);
logger.info(`[GBDeployer] Finished import of ${localPath}`) logger.info(`[GBDeployer] Finished import of ${localPath}`);
} }
} }

View file

@ -1,38 +1,38 @@
export const Messages = { export const Messages = {
"en-US": { 'en-US': {
did_not_find: "I'm sorry I didn't find anything.", did_not_find: 'I\'m sorry I didn\'t find anything.',
changing_language: "OK, changing language to English...", changing_language: 'OK, changing language to English...',
going_answer: "Great choice, now looking for your answer...", going_answer: 'Great choice, now looking for your answer...',
wider_answer: subjectText => wider_answer: subjectText =>
`Answering to you in a broader way... Not just about ${subjectText}.`, `Answering to you in a broader way... Not just about ${subjectText}.`,
which_question: "What's your question?", which_question: 'What\'s your question?',
anything_else: "So, may I help with anything else?", anything_else: 'So, may I help with anything else?',
here_is_subjects: "Here are some subjects to choose from...", here_is_subjects: 'Here are some subjects to choose from...',
menu_select: "Select", menu_select: 'Select',
lets_search: query => lets_search: query =>
`Vamos pesquisar sobre ${query}... O que deseja saber?`, `Vamos pesquisar sobre ${query}... O que deseja saber?`,
see_faq: see_faq:
"Please take a look at the FAQ I've prepared for you. You can click on them to get the answer.", 'Please take a look at the FAQ I\'ve prepared for you. You can click on them to get the answer.',
will_answer_projector: will_answer_projector:
"I'll answer on the projector to a better experience...", 'I\'ll answer on the projector to a better experience...',
ask_first_time: "What are you looking for?" ask_first_time: 'What are you looking for?'
}, },
"pt-BR": { 'pt-BR': {
did_not_find: "Desculpe-me, não encontrei nada a respeito.", did_not_find: 'Desculpe-me, não encontrei nada a respeito.',
changing_language: "OK, mundando de idioma para o Português...", changing_language: 'OK, mundando de idioma para o Português...',
going_answer: "Ótima escolha, procurando resposta para sua questão...", going_answer: 'Ótima escolha, procurando resposta para sua questão...',
wider_answer: subjectText => wider_answer: subjectText =>
`Vou te responder de modo mais abrangente... Não apenas sobre ${subjectText}`, `Vou te responder de modo mais abrangente... Não apenas sobre ${subjectText}`,
which_question: "Qual a pergunta?", which_question: 'Qual a pergunta?',
anything_else: "Então, posso ajudar em algo a mais?", anything_else: 'Então, posso ajudar em algo a mais?',
here_is_subjects: "Aqui estão algumas categorias de assuntos...", here_is_subjects: 'Aqui estão algumas categorias de assuntos...',
menu_select: "Selecionar", menu_select: 'Selecionar',
lets_search: query => lets_search: query =>
`Let's search about ${query}... What do you want to know?`, `Let's search about ${query}... What do you want to know?`,
see_faq: see_faq:
"Veja algumas perguntas mais frequentes logo na tela. Clique numa delas para eu responder.", 'Veja algumas perguntas mais frequentes logo na tela. Clique numa delas para eu responder.',
will_answer_projector: will_answer_projector:
"Vou te responder na tela para melhor visualização...", 'Vou te responder na tela para melhor visualização...',
ask_first_time: "Sobre como eu poderia ajudar?" ask_first_time: 'Sobre como eu poderia ajudar?'
} }
}; };

View file

@ -34,40 +34,39 @@
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
*/ */
'use strict' 'use strict';
const UrlJoin = require("url-join") const UrlJoin = require('url-join');
import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { GBMinInstance, IGBPackage, IGBCoreService } from "botlib" import { Sequelize } from 'sequelize-typescript';
import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from './models';
import { Sequelize } from "sequelize-typescript"
import { GuaribasUser, GuaribasGroup, GuaribasUserGroup } from "./models"
export class GBSecurityPackage implements IGBPackage { export class GBSecurityPackage implements IGBPackage {
sysPackages: IGBPackage[] = null public sysPackages: IGBPackage[] = null;
loadPackage(core: IGBCoreService, sequelize: Sequelize): void { public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
core.sequelize.addModels([ core.sequelize.addModels([
GuaribasGroup, GuaribasGroup,
GuaribasUser, GuaribasUser,
GuaribasUserGroup GuaribasUserGroup
]) ]);
core core;
} }
unloadPackage(core: IGBCoreService): void { public unloadPackage(core: IGBCoreService): void {
} }
loadBot(min: GBMinInstance): void { public loadBot(min: GBMinInstance): void {
} }
unloadBot(min: GBMinInstance): void { public unloadBot(min: GBMinInstance): void {
} }
onNewSession(min: GBMinInstance, step: any): void { public onNewSession(min: GBMinInstance, step: any): void {
} }
} }

View file

@ -34,60 +34,60 @@
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
*/ */
'use strict' 'use strict';
import { import {
DataTypes,
DataTypeUUIDv4,
DataTypeDate, DataTypeDate,
DataTypeDecimal DataTypeDecimal,
} from "sequelize" DataTypes,
DataTypeUUIDv4
} from 'sequelize';
import { import {
Sequelize, AutoIncrement,
Table,
Column,
Model,
HasMany,
BelongsTo, BelongsTo,
BelongsToMany, BelongsToMany,
Length, Column,
ForeignKey,
CreatedAt, CreatedAt,
UpdatedAt,
DataType, DataType,
ForeignKey,
HasMany,
IsUUID, IsUUID,
Length,
Model,
PrimaryKey, PrimaryKey,
AutoIncrement Sequelize,
} from "sequelize-typescript" Table,
UpdatedAt
} from 'sequelize-typescript';
import { GuaribasInstance } from "../../core.gbapp/models/GBModel" import { GuaribasInstance } from '../../core.gbapp/models/GBModel';
@Table @Table
export class GuaribasUser extends Model<GuaribasUser> { export class GuaribasUser extends Model<GuaribasUser> {
@PrimaryKey @PrimaryKey
@AutoIncrement @AutoIncrement
@Column @Column
userId: number public userId: number;
@Column displayName: string @Column public displayName: string;
@Column userSystemId: string @Column public userSystemId: string;
@Column userName: string @Column public userName: string;
@Column defaultChannel: string @Column public defaultChannel: string;
@Column email: string @Column public email: string;
@Column(DataType.STRING(512)) @Column(DataType.STRING(512))
internalAddress: string public internalAddress: string;
@ForeignKey(() => GuaribasInstance) @ForeignKey(() => GuaribasInstance)
@Column @Column
instanceId: number public instanceId: number;
@BelongsTo(() => GuaribasInstance) @BelongsTo(() => GuaribasInstance)
instance: GuaribasInstance public instance: GuaribasInstance;
} }
@Table @Table
@ -95,40 +95,40 @@ export class GuaribasGroup extends Model<GuaribasGroup> {
@PrimaryKey @PrimaryKey
@AutoIncrement @AutoIncrement
@Column @Column
groupId: number public groupId: number;
@Length({ min: 0, max: 512 }) @Length({ min: 0, max: 512 })
@Column @Column
displayName: string public displayName: string;
@ForeignKey(() => GuaribasInstance) @ForeignKey(() => GuaribasInstance)
@Column @Column
instanceId: number public instanceId: number;
@BelongsTo(() => GuaribasInstance) @BelongsTo(() => GuaribasInstance)
instance: GuaribasInstance public instance: GuaribasInstance;
} }
@Table @Table
export class GuaribasUserGroup extends Model<GuaribasUserGroup> { export class GuaribasUserGroup extends Model<GuaribasUserGroup> {
@ForeignKey(() => GuaribasUser) @ForeignKey(() => GuaribasUser)
@Column @Column
userId: number public userId: number;
@ForeignKey(() => GuaribasGroup) @ForeignKey(() => GuaribasGroup)
@Column @Column
groupId: number public groupId: number;
@ForeignKey(() => GuaribasInstance) @ForeignKey(() => GuaribasInstance)
@Column @Column
instanceId: number public instanceId: number;
@BelongsTo(() => GuaribasInstance) @BelongsTo(() => GuaribasInstance)
instance: GuaribasInstance public instance: GuaribasInstance;
@BelongsTo(() => GuaribasGroup) @BelongsTo(() => GuaribasGroup)
group: GuaribasGroup public group: GuaribasGroup;
@BelongsTo(() => GuaribasUser) @BelongsTo(() => GuaribasUser)
user: GuaribasUser public user: GuaribasUser;
} }

View file

@ -30,48 +30,48 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
const Path = require("path") const Path = require('path');
const Fs = require("fs") const Fs = require('fs');
const _ = require("lodash") const _ = require('lodash');
const Parse = require("csv-parse") const Parse = require('csv-parse');
const Async = require("async") const Async = require('async');
const UrlJoin = require("url-join") const UrlJoin = require('url-join');
const Walk = require("fs-walk") const Walk = require('fs-walk');
const logger = require("../../../src/logger") const logger = require('../../../src/logger');
import { GBServiceCallback, GBService, IGBInstance } from "botlib" import { GBService, GBServiceCallback, IGBInstance } from 'botlib';
import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from "../models" import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from '../models';
export class SecService extends GBService { export class SecService extends GBService {
async importSecurityFile(localPath: string, instance: IGBInstance) { public async importSecurityFile(localPath: string, instance: IGBInstance) {
let security = JSON.parse( const security = JSON.parse(
Fs.readFileSync(UrlJoin(localPath, "security.json"), "utf8") Fs.readFileSync(UrlJoin(localPath, 'security.json'), 'utf8')
) );
security.groups.forEach(group => { security.groups.forEach(group => {
let groupDb = GuaribasGroup.build({ const groupDb = GuaribasGroup.build({
instanceId: instance.instanceId, instanceId: instance.instanceId,
displayName: group.displayName displayName: group.displayName
}) });
groupDb.save().then(groupDb => { groupDb.save().then(groupDb => {
group.users.forEach(user => { group.users.forEach(user => {
let userDb = GuaribasUser.build({ const userDb = GuaribasUser.build({
instanceId: instance.instanceId, instanceId: instance.instanceId,
groupId: groupDb.groupId, groupId: groupDb.groupId,
userName: user.userName userName: user.userName
}) });
userDb.save().then(userDb => { userDb.save().then(userDb => {
let userGroup = GuaribasUserGroup.build() const userGroup = GuaribasUserGroup.build();
userGroup.groupId = groupDb.groupId userGroup.groupId = groupDb.groupId;
userGroup.userId = userDb.userId userGroup.userId = userDb.userId;
userGroup.save() userGroup.save();
}) });
}) });
}) });
}) });
} }
async ensureUser( public async ensureUser(
instanceId: number, instanceId: number,
userSystemId: string, userSystemId: string,
userName: string, userName: string,
@ -83,24 +83,24 @@ export class SecService extends GBService {
(resolve, reject) => { (resolve, reject) => {
GuaribasUser.findOne({ GuaribasUser.findOne({
attributes: ["instanceId", "internalAddress"], attributes: ['instanceId', 'internalAddress'],
where: { where: {
instanceId: instanceId, instanceId: instanceId,
userSystemId: userSystemId userSystemId: userSystemId
} }
}).then(user => { }).then(user => {
if (!user) { if (!user) {
user = GuaribasUser.build() user = GuaribasUser.build();
} }
user.userSystemId = userSystemId user.userSystemId = userSystemId;
user.userName = userName user.userName = userName;
user.displayName = displayName user.displayName = displayName;
user.internalAddress = address user.internalAddress = address;
user.email = userName user.email = userName;
user.defaultChannel = channelName user.defaultChannel = channelName;
user.save() user.save();
resolve(user) resolve(user);
}).error(reason => reject(reason)) }).error(reject);
}) });
} }
} }

View file

@ -34,43 +34,41 @@
* @fileoverview General Bots server core. * @fileoverview General Bots server core.
*/ */
'use strict' 'use strict';
const UrlJoin = require("url-join") const UrlJoin = require('url-join');
import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { GBMinInstance, IGBPackage, IGBCoreService } from "botlib" import { Sequelize } from 'sequelize-typescript';
import { WhatsappDirectLine } from './services/WhatsappDirectLine';
import { Sequelize } from "sequelize-typescript"
import { WhatsappDirectLine } from "./services/WhatsappDirectLine"
export class GBWhatsappPackage implements IGBPackage { export class GBWhatsappPackage implements IGBPackage {
sysPackages: IGBPackage[] = null public sysPackages: IGBPackage[] = null;
channel: WhatsappDirectLine public channel: WhatsappDirectLine;
loadPackage(core: IGBCoreService, sequelize: Sequelize): void { public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
} }
unloadPackage(core: IGBCoreService): void { public unloadPackage(core: IGBCoreService): void {
} }
loadBot(min: GBMinInstance): void { public loadBot(min: GBMinInstance): void {
// Only loads engine if it is defined on services.json. // Only loads engine if it is defined on services.json.
if (min.instance.whatsappBotKey != "") { if (min.instance.whatsappBotKey != '') {
this.channel = new WhatsappDirectLine(min.botId, min.instance.whatsappBotKey, min.instance.whatsappServiceKey, this.channel = new WhatsappDirectLine(min.botId, min.instance.whatsappBotKey, min.instance.whatsappServiceKey,
min.instance.whatsappServiceNumber, min.instance.whatsappServiceUrl, min.instance.whatsappServiceWebhookUrl) min.instance.whatsappServiceNumber, min.instance.whatsappServiceUrl, min.instance.whatsappServiceWebhookUrl);
} }
} }
unloadBot(min: GBMinInstance): void { public unloadBot(min: GBMinInstance): void {
} }
onNewSession(min: GBMinInstance, step: any): void { public onNewSession(min: GBMinInstance, step: any): void {
} }
} }

View file

@ -30,45 +30,45 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
const Path = require("path") const Path = require('path');
const Fs = require("fs") const Fs = require('fs');
const _ = require("lodash") const _ = require('lodash');
const Parse = require("csv-parse") const Parse = require('csv-parse');
const Async = require("async") const Async = require('async');
const UrlJoin = require("url-join") const UrlJoin = require('url-join');
const Walk = require("fs-walk") const Walk = require('fs-walk');
const logger = require("../../../src/logger") const logger = require('../../../src/logger');
const Swagger = require('swagger-client') const Swagger = require('swagger-client');
const rp = require('request-promise') const rp = require('request-promise');
import * as request from "request-promise-native" import * as request from 'request-promise-native';
import { GBServiceCallback, GBService, IGBInstance } from "botlib" import { GBService, GBServiceCallback, IGBInstance } from 'botlib';
export class WhatsappDirectLine extends GBService { export class WhatsappDirectLine extends GBService {
pollInterval = 1000 public pollInterval = 1000;
directLineClientName = 'DirectLineClient' public directLineClientName = 'DirectLineClient';
directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json' public directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json';
directLineClient: any public directLineClient: any;
whatsappServiceKey: string public whatsappServiceKey: string;
whatsappServiceNumber: string public whatsappServiceNumber: string;
whatsappServiceUrl: string public whatsappServiceUrl: string;
whatsappServiceWebhookUrl: string public whatsappServiceWebhookUrl: string;
botId: string public botId: string;
watermark: string = null public watermark: string = null;
conversationIds = {} public conversationIds = {};
constructor(botId, directLineSecret, whatsappServiceKey, whatsappServiceNumber, whatsappServiceUrl, whatsappServiceWebhookUrl) { constructor(botId, directLineSecret, whatsappServiceKey, whatsappServiceNumber, whatsappServiceUrl, whatsappServiceWebhookUrl) {
super() super();
this.botId = botId this.botId = botId;
this.whatsappServiceKey = whatsappServiceKey this.whatsappServiceKey = whatsappServiceKey;
this.whatsappServiceNumber = whatsappServiceNumber this.whatsappServiceNumber = whatsappServiceNumber;
this.whatsappServiceUrl = whatsappServiceUrl this.whatsappServiceUrl = whatsappServiceUrl;
this.whatsappServiceWebhookUrl = whatsappServiceWebhookUrl this.whatsappServiceWebhookUrl = whatsappServiceWebhookUrl;
// TODO: Migrate to Swagger 3. // TODO: Migrate to Swagger 3.
this.directLineClient = rp(this.directLineSpecUrl) this.directLineClient = rp(this.directLineSpecUrl)
@ -76,16 +76,16 @@ export class WhatsappDirectLine extends GBService {
return new Swagger({ return new Swagger({
spec: JSON.parse(spec.trim()), spec: JSON.parse(spec.trim()),
usePromise: true usePromise: true
}) });
}) })
.then(async (client) => { .then(async (client) => {
client.clientAuthorizations.add('AuthorizationBotConnector', client.clientAuthorizations.add('AuthorizationBotConnector',
new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' +
directLineSecret, 'header')) directLineSecret, 'header'));
var options = { const options = {
method: 'POST', method: 'POST',
url: UrlJoin(this.whatsappServiceUrl, "webhook"), url: UrlJoin(this.whatsappServiceUrl, 'webhook'),
qs: qs:
{ {
token: this.whatsappServiceKey, token: this.whatsappServiceKey,
@ -96,67 +96,66 @@ export class WhatsappDirectLine extends GBService {
{ {
'cache-control': 'no-cache' 'cache-control': 'no-cache'
} }
} };
try { try {
const result = await request.post(options) const result = await request.post(options);
logger.info(result) logger.info(result);
} catch (error) { } catch (error) {
logger.error('Error initializing 3rd party Whatsapp provider.', error) logger.error('Error initializing 3rd party Whatsapp provider.', error);
} }
return client return client;
}) })
.catch((err) => { .catch((err) => {
logger.error('Error initializing DirectLine client', err) logger.error('Error initializing DirectLine client', err);
}) });
} }
received(req, res) { public received(req, res) {
let text = req.body.messages[0].body const text = req.body.messages[0].body;
let from = req.body.messages[0].author.split('@')[0] const from = req.body.messages[0].author.split('@')[0];
let fromName = req.body.messages[0].senderName const fromName = req.body.messages[0].senderName;
if (req.body.messages[0].fromMe) { if (req.body.messages[0].fromMe) {
return // Exit here. return; // Exit here.
} }
logger.info(`GBWhatsapp: Hook called. from: ${from}(${fromName}), text: ${text})`) logger.info(`GBWhatsapp: Hook called. from: ${from}(${fromName}), text: ${text})`);
let conversationId = this.conversationIds[from] const conversationId = this.conversationIds[from];
this.directLineClient.then((client) => { this.directLineClient.then((client) => {
if (this.conversationIds[from] == null) { if (this.conversationIds[from] == null) {
logger.info(`GBWhatsapp: Starting new conversation on Bot.`) logger.info(`GBWhatsapp: Starting new conversation on Bot.`);
client.Conversations.Conversations_StartConversation() client.Conversations.Conversations_StartConversation()
.then((response) => { .then((response) => {
return response.obj.conversationId return response.obj.conversationId;
}) })
.then((conversationId) => { .then((conversationId) => {
this.conversationIds[from] = conversationId this.conversationIds[from] = conversationId;
this.inputMessage(client, conversationId, text, this.inputMessage(client, conversationId, text,
from, fromName) from, fromName);
this.pollMessages(client, conversationId, from, fromName) this.pollMessages(client, conversationId, from, fromName);
}) })
.catch((err) => { .catch((err) => {
console.error('Error starting conversation', err) console.error('Error starting conversation', err);
}) });
} else { } else {
this.inputMessage(client, conversationId, text, this.inputMessage(client, conversationId, text,
from, fromName) from, fromName);
} }
res.end() res.end();
}) });
} }
public inputMessage(client, conversationId, text, from, fromName) {
inputMessage(client, conversationId, text, from, fromName) {
client.Conversations.Conversations_PostActivity( client.Conversations.Conversations_PostActivity(
{ {
@ -172,15 +171,15 @@ export class WhatsappDirectLine extends GBService {
replyToId: from replyToId: from
} }
}).catch((err) => { }).catch((err) => {
logger.error(`GBWhatsapp: Error receiving message: ${err}.`) logger.error(`GBWhatsapp: Error receiving message: ${err}.`);
}) });
} }
pollMessages(client, conversationId, from, fromName) { public pollMessages(client, conversationId, from, fromName) {
logger.info(`GBWhatsapp: Starting polling message for conversationId: logger.info(`GBWhatsapp: Starting polling message for conversationId:
${conversationId}.`) ${conversationId}.`);
setInterval(() => { setInterval(() => {
client.Conversations.Conversations_GetActivities({ client.Conversations.Conversations_GetActivities({
@ -188,67 +187,67 @@ export class WhatsappDirectLine extends GBService {
conversationId, watermark: this.watermark conversationId, watermark: this.watermark
}) })
.then((response) => { .then((response) => {
this.watermark = response.obj.watermark this.watermark = response.obj.watermark;
return response.obj.activities return response.obj.activities;
}) })
.then((activities) => { .then((activities) => {
this.printMessages(activities, conversationId, from, fromName) this.printMessages(activities, conversationId, from, fromName);
}) });
}, this.pollInterval) }, this.pollInterval);
} }
printMessages(activities, conversationId, from, fromName) { public printMessages(activities, conversationId, from, fromName) {
if (activities && activities.length) { if (activities && activities.length) {
// Ignore own messages. // Ignore own messages.
activities = activities.filter((m) => { return (m.from.id === "GeneralBots") && m.type === "message" }) activities = activities.filter((m) => (m.from.id === 'GeneralBots') && m.type === 'message');
if (activities.length) { if (activities.length) {
// Print other messages. // Print other messages.
activities.forEach(activity => { activities.forEach(activity => {
this.printMessage(activity, conversationId, from, fromName) this.printMessage(activity, conversationId, from, fromName);
}) });
} }
} }
} }
printMessage(activity, conversationId, from, fromName) { public printMessage(activity, conversationId, from, fromName) {
let output = "" let output = '';
if (activity.text) { if (activity.text) {
logger.info(`GBWhatsapp: MSG: ${activity.text}`) logger.info(`GBWhatsapp: MSG: ${activity.text}`);
output = activity.text output = activity.text;
} }
if (activity.attachments) { if (activity.attachments) {
activity.attachments.forEach((attachment) => { activity.attachments.forEach((attachment) => {
switch (attachment.contentType) { switch (attachment.contentType) {
case "application/vnd.microsoft.card.hero": case 'application/vnd.microsoft.card.hero':
output += `\n${this.renderHeroCard(attachment)}` output += `\n${this.renderHeroCard(attachment)}`;
break break;
case "image/png": case 'image/png':
logger.info('Opening the requested image ' + attachment.contentUrl) logger.info('Opening the requested image ' + attachment.contentUrl);
output += `\n${attachment.contentUrl}` output += `\n${attachment.contentUrl}`;
break break;
} }
}) });
} }
this.sendToDevice(conversationId, from, fromName, output) this.sendToDevice(conversationId, from, fromName, output);
} }
renderHeroCard(attachment) { public renderHeroCard(attachment) {
return `${attachment.content.title} - ${attachment.content.text}` return `${attachment.content.title} - ${attachment.content.text}`;
} }
async sendToDevice(conversationId, to, toName, msg) { public async sendToDevice(conversationId, to, toName, msg) {
var options = { const options = {
method: 'POST', method: 'POST',
url: UrlJoin(this.whatsappServiceUrl, 'message'), url: UrlJoin(this.whatsappServiceUrl, 'message'),
qs: qs:
@ -261,8 +260,8 @@ export class WhatsappDirectLine extends GBService {
{ {
'cache-control': 'no-cache' 'cache-control': 'no-cache'
} }
} };
const result = await request.get(options) const result = await request.get(options);
} }
} }

View file

@ -37,30 +37,30 @@
'use strict'; 'use strict';
const logger = require("./logger"); const logger = require('./logger');
const express = require("express"); const express = require('express');
const bodyParser = require("body-parser"); const bodyParser = require('body-parser');
const opn = require('opn'); const opn = require('opn');
import { GBConfigService } from "../packages/core.gbapp/services/GBConfigService"; import { IGBInstance, IGBPackage } from 'botlib';
import { GBConversationalService } from "../packages/core.gbapp/services/GBConversationalService"; import { GBAdminPackage } from '../packages/admin.gbapp/index';
import { GBMinService } from "../packages/core.gbapp/services/GBMinService"; import { GBAdminService } from '../packages/admin.gbapp/services/GBAdminService';
import { GBDeployer } from "../packages/core.gbapp/services/GBDeployer"; import { GBAnalyticsPackage } from '../packages/analytics.gblib';
import { GBWhatsappPackage } from "./../packages/whatsapp.gblib/index"; import { AzureDeployerService } from '../packages/azuredeployer.gbapp/services/AzureDeployerService';
import { GBCoreService } from "../packages/core.gbapp/services/GBCoreService"; import { GBCorePackage } from '../packages/core.gbapp';
import { GBImporter } from "../packages/core.gbapp/services/GBImporter"; import { GuaribasInstance } from '../packages/core.gbapp/models/GBModel';
import { GBAnalyticsPackage } from "../packages/analytics.gblib"; import { GBConfigService } from '../packages/core.gbapp/services/GBConfigService';
import { GBCorePackage } from "../packages/core.gbapp"; import { GBConversationalService } from '../packages/core.gbapp/services/GBConversationalService';
import { GBKBPackage } from "../packages/kb.gbapp"; import { GBCoreService } from '../packages/core.gbapp/services/GBCoreService';
import { GBSecurityPackage } from "../packages/security.gblib"; import { GBDeployer } from '../packages/core.gbapp/services/GBDeployer';
import { GBAdminPackage } from "../packages/admin.gbapp/index"; import { GBImporter } from '../packages/core.gbapp/services/GBImporter';
import { GBCustomerSatisfactionPackage } from "../packages/customer-satisfaction.gbapp"; import { GBMinService } from '../packages/core.gbapp/services/GBMinService';
import { GBAdminService } from "../packages/admin.gbapp/services/GBAdminService"; import { GBCustomerSatisfactionPackage } from '../packages/customer-satisfaction.gbapp';
import { GuaribasInstance } from "../packages/core.gbapp/models/GBModel"; import { GBKBPackage } from '../packages/kb.gbapp';
import { AzureDeployerService } from "../packages/azuredeployer.gbapp/services/AzureDeployerService"; import { GBSecurityPackage } from '../packages/security.gblib';
import { IGBPackage, IGBInstance } from "botlib"; import { GBWhatsappPackage } from './../packages/whatsapp.gblib/index';
let appPackages = new Array<IGBPackage>(); const appPackages = new Array<IGBPackage>();
/** /**
* General Bots open-core entry point. * General Bots open-core entry point.
@ -71,7 +71,7 @@ export class GBServer {
* Program entry-point. * Program entry-point.
*/ */
static run() { public static run() {
logger.info(`The Bot Server is in STARTING mode...`); logger.info(`The Bot Server is in STARTING mode...`);
@ -79,8 +79,8 @@ export class GBServer {
// bot instance. This allows the same server to attend multiple Bot on // bot instance. This allows the same server to attend multiple Bot on
// the Marketplace until GB get serverless. // the Marketplace until GB get serverless.
let port = process.env.port || process.env.PORT || 4242; const port = process.env.port || process.env.PORT || 4242;
let server = express(); const server = express();
server.use(bodyParser.json()); // to support JSON-encoded bodies server.use(bodyParser.json()); // to support JSON-encoded bodies
server.use( server.use(
@ -99,15 +99,15 @@ export class GBServer {
// Reads basic configuration, initialize minimal services. // Reads basic configuration, initialize minimal services.
GBConfigService.init(); GBConfigService.init();
let core = new GBCoreService(); const core = new GBCoreService();
// Ensures cloud / on-premises infrastructure is setup. // Ensures cloud / on-premises infrastructure is setup.
logger.info(`Establishing a development local proxy (ngrok)...`); logger.info(`Establishing a development local proxy (ngrok)...`);
let proxyAddress = await core.ensureProxy(port); const proxyAddress = await core.ensureProxy(port);
let deployer = new GBDeployer(core, new GBImporter(core)); const deployer = new GBDeployer(core, new GBImporter(core));
let azureDeployer = new AzureDeployerService(deployer); const azureDeployer = new AzureDeployerService(deployer);
try { try {
await core.initDatabase(); await core.initDatabase();
@ -117,7 +117,7 @@ export class GBServer {
bootInstance = await azureDeployer.deployFarm(proxyAddress); bootInstance = await azureDeployer.deployFarm(proxyAddress);
} catch (error) { } catch (error) {
logger.warn( logger.warn(
"In case of error, please cleanup any infrastructure objects created during this procedure and .env before running again." 'In case of error, please cleanup any infrastructure objects created during this procedure and .env before running again.'
); );
throw error; throw error;
} }
@ -132,13 +132,13 @@ export class GBServer {
// Check admin password. // Check admin password.
let conversationalService = new GBConversationalService(core); const conversationalService = new GBConversationalService(core);
let adminService = new GBAdminService(core); const adminService = new GBAdminService(core);
let password = GBConfigService.get("ADMIN_PASS"); const password = GBConfigService.get('ADMIN_PASS');
if (!GBAdminService.StrongRegex.test(password)) { if (!GBAdminService.StrongRegex.test(password)) {
throw new Error( throw new Error(
"Please, define a really strong password in ADMIN_PASS environment variable before running the server." 'Please, define a really strong password in ADMIN_PASS environment variable before running the server.'
); );
} }
@ -155,7 +155,7 @@ export class GBServer {
GBWhatsappPackage GBWhatsappPackage
].forEach(e => { ].forEach(e => {
logger.info(`Loading sys package: ${e.name}...`); logger.info(`Loading sys package: ${e.name}...`);
let p = Object.create(e.prototype) as IGBPackage; const p = Object.create(e.prototype) as IGBPackage;
p.loadPackage(core, core.sequelize); p.loadPackage(core, core.sequelize);
}); });
@ -165,9 +165,9 @@ export class GBServer {
let instances: GuaribasInstance[]; let instances: GuaribasInstance[];
try { try {
instances = await core.loadInstances(); instances = await core.loadInstances();
let instance = instances[0]; const instance = instances[0];
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === 'development') {
logger.info(`Updating bot endpoint to local reverse proxy (ngrok)...`); logger.info(`Updating bot endpoint to local reverse proxy (ngrok)...`);
await azureDeployer.updateBotProxy( await azureDeployer.updateBotProxy(
@ -177,30 +177,30 @@ export class GBServer {
); );
} }
} catch (error) { } catch (error) {
if (error.parent.code === "ELOGIN") { if (error.parent.code === 'ELOGIN') {
let group = GBConfigService.get("CLOUD_GROUP"); const group = GBConfigService.get('CLOUD_GROUP');
let serverName = GBConfigService.get("STORAGE_SERVER").split( const serverName = GBConfigService.get('STORAGE_SERVER').split(
".database.windows.net" '.database.windows.net'
)[0]; )[0];
await azureDeployer.openStorageFirewall(group, serverName); await azureDeployer.openStorageFirewall(group, serverName);
} else { } else {
// Check if storage is empty and needs formatting. // Check if storage is empty and needs formatting.
let isInvalidObject = const isInvalidObject =
error.parent.number == 208 || error.parent.errno == 1; // MSSQL or SQLITE. error.parent.number == 208 || error.parent.errno == 1; // MSSQL or SQLITE.
if (isInvalidObject) { if (isInvalidObject) {
if (GBConfigService.get("STORAGE_SYNC") != "true") { if (GBConfigService.get('STORAGE_SYNC') != 'true') {
throw `Operating storage is out of sync or there is a storage connection error. Try setting STORAGE_SYNC to true in .env file. Error: ${ throw new Error(`Operating storage is out of sync or there is a storage connection error. Try setting STORAGE_SYNC to true in .env file. Error: ${
error.message error.message
}.`; }.`);
} else { } else {
logger.info( logger.info(
`Storage is empty. After collecting storage structure from all .gbapps it will get synced.` `Storage is empty. After collecting storage structure from all .gbapps it will get synced.`
); );
} }
} else { } else {
throw `Cannot connect to operating storage: ${error.message}.`; throw new Error(`Cannot connect to operating storage: ${error.message}.`);
} }
} }
} }
@ -213,7 +213,7 @@ export class GBServer {
// If instances is undefined here it's because storage has been formatted. // If instances is undefined here it's because storage has been formatted.
// Load all instances from .gbot found on deploy package directory. // Load all instances from .gbot found on deploy package directory.
if (!instances) { if (!instances) {
let saveInstance = new GuaribasInstance(bootInstance); const saveInstance = new GuaribasInstance(bootInstance);
await saveInstance.save(); await saveInstance.save();
instances = await core.loadInstances(); instances = await core.loadInstances();
} }
@ -221,7 +221,7 @@ export class GBServer {
// Setup server dynamic (per bot instance) resources and listeners. // Setup server dynamic (per bot instance) resources and listeners.
logger.info(`Publishing instances...`); logger.info(`Publishing instances...`);
let minService = new GBMinService( const minService = new GBMinService(
core, core,
conversationalService, conversationalService,
adminService, adminService,
@ -230,13 +230,13 @@ export class GBServer {
await minService.buildMin(server, appPackages, instances); await minService.buildMin(server, appPackages, instances);
logger.info(`The Bot Server is in RUNNING mode...`); logger.info(`The Bot Server is in RUNNING mode...`);
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === 'development') {
opn('http://localhost:4242'); opn('http://localhost:4242');
} }
return core; return core;
} catch (err) { } catch (err) {
logger.error(`STOP: ${err} ${err.stack ? err.stack : ""}`); logger.error(`STOP: ${err} ${err.stack ? err.stack : ''}`);
process.exit(1); process.exit(1);
} }
})(); })();

View file

@ -30,7 +30,7 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
const { createLogger, format, transports } = require('winston') const { createLogger, format, transports } = require('winston');
const config = { const config = {
levels: { levels: {
@ -53,7 +53,7 @@ const config = {
silly: 'magenta', silly: 'magenta',
custom: 'yellow' custom: 'yellow'
} }
} };
const logger = createLogger({ const logger = createLogger({
format: format.combine( format: format.combine(
@ -62,11 +62,11 @@ const logger = createLogger({
format.label({ label: 'GeneralBots' }), format.label({ label: 'GeneralBots' }),
format.timestamp(), format.timestamp(),
format.printf(nfo => { format.printf(nfo => {
return `${nfo.timestamp} [${nfo.label}] ${nfo.level}: ${nfo.message}` return `${nfo.timestamp} [${nfo.label}] ${nfo.level}: ${nfo.message}`;
}) })
), ),
levels: config.levels, levels: config.levels,
transports: [new transports.Console()] transports: [new transports.Console()]
}) });
module.exports=logger module.exports = logger;