Merge pull request #90 from rodrigorodriguez/master

fix(general): tslint being applied in all sources.
This commit is contained in:
Rodrigo Rodriguez 2019-04-08 12:26:56 -03:00 committed by GitHub
commit 528e0a90eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 2046 additions and 2227 deletions

View file

@ -5,4 +5,5 @@
"arrowParens": "avoid",
"semi": true,
"singleQuote": true
}

8
FEATURES.md Normal file
View file

@ -0,0 +1,8 @@
# General Bots Features
| Feature | BF | GB |
|----------------------------------------------------------------------------|----|----|
| Use of conversational administration to manage bot packages (Talk to admin | - | X |
| F5 to run on VSCode | - | X |
| Isolated code on packages | - | X |
| Breaking changes protected | - | X |

6
ROADMAP.md Normal file
View file

@ -0,0 +1,6 @@
# Roadmap
| Title | Priority | Release | Status |
|-------------------------------|------------------------------------------------------------------------------------------------------------|---------|--------|
| Isolation of .gbapp per .gbot | Today .gbapp loaded is shared across all bot instances and must be associated to one or more individually. | Medium | 2019Q4 |
| Python based .gbapps | Write conversational login in Python | Low | - |

800
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -23,7 +23,7 @@
},
"scripts": {
"clean": "shx rm -rf node_modules/ dist/ docs/reference",
"tslint": "tslint --fix ./src/*.ts ./packages/**/*.ts -t verbose",
"tslint": "tslint --fix ./src/*.ts ./packages/**/*.ts -t verbose -e ./packages/default.gbui/**/* -e ./packages/**/*.gbdialog/**/*",
"build": "npm install && npm run build-server && npm run build-gbui && npm run build-docs",
"build-server": "tsc",
"build-gbui": "cd packages/default.gbui && echo SKIP_PREFLIGHT_CHECK=true >.env && npm install && npm run build",
@ -38,7 +38,6 @@
"ban": "ban",
"issues": "git-issues",
"license": "license-checker --production --onlyunknown --csv",
"pretslint": "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}\";",
@ -48,7 +47,7 @@
"commit": "git-cz"
},
"dependencies": {
"@microsoft/microsoft-graph-client": "1.4.0",
"@microsoft/microsoft-graph-client": "1.5.2",
"@semantic-release/exec": "^3.3.2",
"adal-node": "0.1.28",
"async": "2.6.2",
@ -58,36 +57,37 @@
"azure-arm-search": "^1.3.0-preview",
"azure-arm-sql": "5.6.0",
"azure-arm-website": "5.7.0",
"bluebird": "^3.5.3",
"bluebird": "^3.5.4",
"body-parser": "1.18.3",
"botbuilder": "4.1.7",
"botbuilder-ai": "4.2.0",
"botbuilder-azure": "4.2.0",
"botbuilder": "4.3.4",
"botbuilder-ai": "4.3.4",
"botbuilder-azure": "4.3.4",
"botbuilder-choices": "4.0.0-preview1.2",
"botbuilder-dialogs": "4.2.0",
"botbuilder-dialogs": "4.3.4",
"botbuilder-prompts": "4.0.0-preview1.2",
"botlib": "0.1.19",
"botlib": "0.1.24",
"chai": "4.2.0",
"child_process": "^1.0.2",
"chokidar": "2.1.2",
"cli-spinner": "^0.2.8",
"csv-parse": "4.3.3",
"dotenv-extended": "2.3.0",
"chokidar": "2.1.5",
"cli-spinner": "^0.2.10",
"csv-parse": "4.3.4",
"dotenv-extended": "2.4.0",
"express": "4.16.4",
"express-promise-router": "3.0.3",
"fs-extra": "7.0.1",
"ip": "^1.1.5",
"js-beautify": "^1.8.9",
"js-beautify": "^1.9.1",
"localize": "0.4.7",
"marked": "0.6.1",
"marked": "0.6.2",
"mocha": "6.0.2",
"mocha-typescript": "1.1.17",
"ms": "2.1.1",
"ms-rest-azure": "2.6.0",
"ms-rest-js": "^1.0.1",
"nexmo": "2.4.1",
"ngrok": "3.1.1",
"nyc": "13.3.0",
"opn": "5.4.0",
"opn": "6.0.0",
"pragmatismo-io-framework": "1.0.19",
"process-exists": "3.1.0",
"public-ip": "^3.0.0",
@ -95,20 +95,20 @@
"request-promise": "4.2.4",
"request-promise-native": "1.0.7",
"scanf": "^1.0.2",
"sequelize": "4.42.1",
"sequelize-typescript": "0.6.7",
"sequelize": "^5.2.12",
"sequelize-typescript": "0.6.9",
"shx": "0.3.2",
"simple-git": "1.107.0",
"simple-git": "1.110.0",
"sqlite3": "4.0.6",
"strict-password-generator": "^1.1.2",
"swagger-client": "3.8.25",
"tedious": "5.0.3",
"tedious": "6.1.0",
"temperature-js": "^0.1.0",
"ts-node": "8.0.2",
"ts-node": "8.0.3",
"typedoc": "0.14.2",
"typedoc-plugin-external-module-name": "^2.0.0",
"typedoc-plugin-markdown": "^1.1.27",
"typescript": "3.3.3333",
"typescript": "3.4.1",
"url-join": "4.0.0",
"vbscript-to-typescript": "^1.0.8",
"wait-until": "0.0.2",
@ -124,7 +124,7 @@
"@semantic-release/release-notes-generator": "^7.1.4",
"@types/chai": "4.1.7",
"@types/mocha": "5.2.6",
"@types/sequelize": "4.27.38",
"@types/sequelize": "4.27.46",
"@types/url-join": "4.0.0",
"@types/winston": "2.4.4",
"ban-sensitive-files": "1.9.2",
@ -142,8 +142,8 @@
"standard": "12.0.1",
"travis-deploy-once": "5.0.11",
"ts-loader": "^5.3.3",
"tslint": "^5.13.0",
"tslint-microsoft-contrib": "^6.0.0"
"tslint": "^5.15.0",
"tslint-microsoft-contrib": "^6.1.0"
},
"eslintConfig": {
"env": {

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,28 +36,24 @@
'use strict';
const UrlJoin = require('url-join');
import { BotAdapter } from 'botbuilder';
import { WaterfallDialog, WaterfallStep, WaterfallStepContext } from 'botbuilder-dialogs';
import { GBMinInstance } from 'botlib';
import { IGBDialog } from 'botlib';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance, IGBDialog } from 'botlib';
import urlJoin = require('url-join');
import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService';
import { GBConfigService } from '../../core.gbapp/services/GBConfigService';
import { GBDeployer } from '../../core.gbapp/services/GBDeployer';
import { GBImporter } from '../../core.gbapp/services/GBImporterService';
import { GBAdminService } from '../services/GBAdminService';
import { Messages } from '../strings';
/**
* Dialogs for administration tasks.
*/
export class AdminDialog extends IGBDialog {
public static async createFarmCommand(text: any, min: GBMinInstance) {}
public static async undeployPackageCommand(text: any, min: GBMinInstance) {
public static async undeployPamand(text: any, min: GBMinInstance) {
const packageName = text.split(' ')[1];
const importer = new GBImporter(min.core);
const deployer = new GBDeployer(min.core, importer);
await deployer.undeployPackageFromLocalPath(min.instance, UrlJoin('packages', packageName));
await deployer.undeployPackageFromLocalPath(min.instance, urlJoin('packages', packageName));
}
public static isSharePointPath(path: string) {
@ -67,26 +63,20 @@ export class AdminDialog extends IGBDialog {
public static async deployPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) {
const packageName = text.split(' ')[1];
if (AdminDialog.isSharePointPath(packageName)) {
await deployer.deployFromSharePoint(min.instance.instanceId, packageName);
} else {
if (!AdminDialog.isSharePointPath(packageName)) {
const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH');
if (!additionalPath) {
if (additionalPath === undefined) {
throw new Error('ADDITIONAL_DEPLOY_PATH is not set and deployPackage was called.');
}
await deployer.deployPackageFromLocalPath(min, UrlJoin(additionalPath, packageName));
await deployer.deployPackage(min, urlJoin(additionalPath, packageName));
}
}
public static async rebuildIndexPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) {
await deployer.rebuildIndex(min.instance);
}
public static async addConnectionCommand(min: GBMinInstance, text: any) {
const packageName = text.split(' ')[1];
const importer = new GBImporter(min.core);
const admin = new GBAdminService(min.core);
// TODO: await admin.addConnection
public static async rebuildIndexPackageCommand(min: GBMinInstance, deployer: GBDeployer) {
await deployer.rebuildIndex(
min.instance,
new AzureDeployerService(deployer).getKBSearchSchema(min.instance.searchIndex)
);
}
/**
@ -95,7 +85,7 @@ export class AdminDialog extends IGBDialog {
* @param bot The bot adapter.
* @param min The minimal bot instance data.
*/
public static setup(bot: BotAdapter, min: GBMinInstance) {
public static setup(min: GBMinInstance) {
// Setup services.
const importer = new GBImporter(min.core);
@ -113,9 +103,9 @@ export class AdminDialog extends IGBDialog {
},
async step => {
const locale = step.context.activity.locale;
const password = step.result;
const sensitive = step.result;
if (password === GBConfigService.get('ADMIN_PASS')) {
if (sensitive === GBConfigService.get('ADMIN_PASS')) {
await step.context.sendActivity(Messages[locale].welcome);
return await step.prompt('textPrompt', Messages[locale].which_task);
@ -126,8 +116,9 @@ export class AdminDialog extends IGBDialog {
}
},
async step => {
const locale = step.context.activity.locale;
const text = step.result;
const locale: string = step.context.activity.locale;
// tslint:disable-next-line:no-unsafe-any
const text: string = step.result;
const cmdName = text.split(' ')[0];
step.context.sendActivity(Messages[locale].working(cmdName));
@ -135,28 +126,16 @@ export class AdminDialog extends IGBDialog {
if (text === 'quit') {
return await step.replaceDialog('/');
} else if (cmdName === 'createFarm') {
await AdminDialog.createFarmCommand(text, deployer);
return await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === 'deployPackage') {
await AdminDialog.deployPackageCommand(min, text, deployer);
return await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === 'redeployPackage') {
await AdminDialog.undeployPackageCommand(text, min);
await AdminDialog.deployPackageCommand(min, text, deployer);
return await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === 'rebuildIndex') {
await AdminDialog.rebuildIndexPackageCommand(min, text, deployer);
return await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === 'addConnection') {
await AdminDialog.addConnectionCommand(min, text);
return await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === 'undeployPackage') {
await AdminDialog.undeployPackageCommand(text, min);
await AdminDialog.rebuildIndexPackageCommand(min, deployer);
return await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === 'setupSecurity') {
@ -220,13 +199,13 @@ export class AdminDialog extends IGBDialog {
);
const locale = step.context.activity.locale;
const state = `${min.instance.instanceId}${Math.floor(Math.random() * 1000000000)}`;
const state = `${min.instance.instanceId}${crypto.getRandomValues(new Uint32Array(16))[0]}`;
await min.adminService.setValue(min.instance.instanceId, 'AntiCSRFAttackState', state);
min.adminService.setValue(min.instance.instanceId, 'AntiCSRFAttackState', state);
const url = `https://login.microsoftonline.com/${min.instance.authenticatorTenant}/oauth2/authorize?client_id=${
min.instance.authenticatorClientId
}&response_type=code&redirect_uri=${UrlJoin(
const url = `https://login.microsoftonline.com/${
min.instance.authenticatorTenant
}/oauth2/authorize?client_id=${min.instance.authenticatorClientId}&response_type=code&redirect_uri=${urlJoin(
min.instance.botEndpoint,
min.instance.botId,
'/token'
@ -234,10 +213,9 @@ export class AdminDialog extends IGBDialog {
await step.context.sendActivity(Messages[locale].consent(url));
return await step.replaceDialog('/ask', {isReturning: true});
return await step.replaceDialog('/ask', { isReturning: true });
}
])
);
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,21 +36,35 @@
'use strict';
import urlJoin = require('url-join');
import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { Sequelize } from 'sequelize-typescript';
import { AdminDialog } from './dialogs/AdminDialog';
import { GuaribasAdmin } from './models/AdminModel';
/**
* The package for admin.gbapp.
*/
export class GBAdminPackage implements IGBPackage {
public sysPackages: IGBPackage[] = null;
public sysPackages: IGBPackage[];
public getDialogs(min: GBMinInstance) {
GBLog.verbose(`getDialogs called.`);
}
public unloadPackage(core: IGBCoreService): void {
GBLog.verbose(`unloadPackage called.`);
}
public unloadBot(min: GBMinInstance): void {
GBLog.verbose(`unloadBot called.`);
}
public onNewSession(min: GBMinInstance, step: GBDialogStep): void {
GBLog.verbose(`onNewSession called.`);
}
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
core.sequelize.addModels([GuaribasAdmin]);
}
public loadBot(min: GBMinInstance): void {
AdminDialog.setup(min.bot, min);
AdminDialog.setup(min);
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -39,12 +39,15 @@
import {
Column,
CreatedAt,
DataType,
Model,
Table,
UpdatedAt,
DataType
UpdatedAt
} from 'sequelize-typescript';
/**
* General settings store.
*/
@Table
export class GuaribasAdmin extends Model<GuaribasAdmin> {

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -37,17 +37,17 @@
'use strict';
import { AuthenticationContext, TokenResponse } from 'adal-node';
import { IGBCoreService } from 'botlib';
import { IGBAdminService, IGBCoreService, IGBInstance } from 'botlib';
import urlJoin = require('url-join');
import { GuaribasInstance } from '../../core.gbapp/models/GBModel';
import { GuaribasAdmin } from '../models/AdminModel';
const UrlJoin = require('url-join');
const msRestAzure = require('ms-rest-azure');
const PasswordGenerator = require('strict-password-generator').default;
/**
* Services for server administration.
*/
export class GBAdminService {
export class GBAdminService implements IGBAdminService {
public static GB_PROMPT: string = 'GeneralBots: ';
public static masterBotInstanceId = 0;
@ -65,18 +65,15 @@ export class GBAdminService {
public static async getADALTokenFromUsername(username: string, password: string) {
const credentials = await GBAdminService.getADALCredentialsFromUsername(username, password);
const accessToken = credentials.tokenCache._entries[0].accessToken;
return accessToken;
return credentials.tokenCache._entries[0].accessToken;
}
public static async getADALCredentialsFromUsername(username: string, password: string) {
const credentials = await msRestAzure.loginWithUsernamePassword(username, password);
return credentials;
return await msRestAzure.loginWithUsernamePassword(username, password);
}
public static getRndPassword() {
public static getRndPassword(): string {
const passwordGenerator = new PasswordGenerator();
const options = {
upperCaseAlpha: true,
@ -88,6 +85,7 @@ export class GBAdminService {
};
let password = passwordGenerator.generatePassword(options);
password = password.replace(/[\@\[\=\:\;\?]/g, '#');
return password;
}
@ -101,11 +99,11 @@ export class GBAdminService {
minimumLength: 12,
maximumLength: 14
};
const name = passwordGenerator.generatePassword(options);
return name;
return passwordGenerator.generatePassword(options);
}
public async setValue(instanceId: number, key: string, value: string): Promise<GuaribasAdmin> {
public async setValue(instanceId: number, key: string, value: string) {
const options = { where: {} };
options.where = { key: key };
let admin = await GuaribasAdmin.findOne(options);
@ -115,8 +113,7 @@ export class GBAdminService {
}
admin.value = value;
admin.instanceId = instanceId;
return admin.save();
await admin.save();
}
public async updateSecurityInfo(
@ -125,7 +122,7 @@ export class GBAdminService {
authenticatorAuthorityHostUrl: string,
authenticatorClientId: string,
authenticatorClientSecret: string
): Promise<GuaribasInstance> {
): Promise<IGBInstance> {
const options = { where: {} };
options.where = { instanceId: instanceId };
const item = await GuaribasInstance.findOne(options);
@ -137,7 +134,7 @@ export class GBAdminService {
return item.save();
}
public async getValue(instanceId: number, key: string) {
public async getValue(instanceId: number, key: string): Promise<string> {
const options = { where: {} };
options.where = { key: key, instanceId: instanceId };
const obj = await GuaribasAdmin.findOne(options);
@ -154,7 +151,7 @@ export class GBAdminService {
const accessToken = await this.getValue(instanceId, 'accessToken');
resolve(accessToken);
} else {
const authorizationUrl = UrlJoin(
const authorizationUrl = urlJoin(
instance.authenticatorAuthorityHostUrl,
instance.authenticatorTenant,
'/oauth2/authorize'
@ -169,7 +166,7 @@ export class GBAdminService {
instance.authenticatorClientSecret,
resource,
async (err, res) => {
if (err) {
if (err !== undefined) {
reject(err);
} else {
const token = res as TokenResponse;

View file

@ -16,7 +16,8 @@ export const Messages = {
wrong_password: 'Sorry, wrong password. Please, try again.',
enter_authenticator_tenant: 'Enter the Authenticator Tenant (eg.: domain.onmicrosoft.com):',
enter_authenticator_authority_host_url: 'Enter the Authority Host URL (eg.: https://login.microsoftonline.com): ',
enter_authenticator_client_id: 'Enter the Client Id [Application Id](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredAppsPreview) GUID:',
enter_authenticator_client_id: `Enter the Client Id GUID: Get from
[this url](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredAppsPreview)`,
enter_authenticator_client_secret: 'Enter the Client Secret:'
},
'pt-BR': {

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,18 +36,30 @@
'use strict';
const UrlJoin = require('url-join');
import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { Sequelize } from 'sequelize-typescript';
/**
* .gblib Package handler.
*/
export class GBAnalyticsPackage implements IGBPackage {
public sysPackages: IGBPackage[] = null;
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {}
public unloadPackage(core: IGBCoreService): void {}
public loadBot(min: GBMinInstance): void {}
public unloadBot(min: GBMinInstance): void {}
public onNewSession(min: GBMinInstance, step: any): void {}
public sysPackages: IGBPackage[];
public getDialogs(min: GBMinInstance) {
GBLog.verbose(`getDialogs called.`);
}
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
GBLog.verbose(`loadPackage called.`);
}
public unloadPackage(core: IGBCoreService): void {
GBLog.verbose(`unloadPackage called.`);
}
public loadBot(min: GBMinInstance): void {
GBLog.verbose(`loadBot called.`);
}
public unloadBot(min: GBMinInstance): void {
GBLog.verbose(`unloadBot called.`);
}
public onNewSession(min: GBMinInstance, step: GBDialogStep): void {
GBLog.verbose(`onNewSession called.`);
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,12 +36,6 @@
'use strict';
import {
DataTypeDate,
DataTypeDecimal,
DataTypes,
DataTypeUUIDv4
} from 'sequelize';
import {
AutoIncrement,
@ -65,6 +59,56 @@ import { GuaribasChannel, GuaribasInstance } from '../../core.gbapp/models/GBMod
import { GuaribasSubject } from '../../kb.gbapp/models';
import { GuaribasUser } from '../../security.gblib/models';
/**
* A single message in a conversation.
*/
@Table
export class GuaribasConversationMessage extends Model<GuaribasConversationMessage> {
@PrimaryKey
@AutoIncrement
@Column
public conversationMessageId: number;
@ForeignKey(() => GuaribasSubject)
@Column
public subjectId: number;
@Column(DataType.TEXT)
public content: string;
@Column
@CreatedAt
public createdAt: Date;
@Column
@UpdatedAt
public updatedAt: Date;
//tslint:disable-next-line:no-use-before-declare
@ForeignKey(() => GuaribasConversation)
@Column
public conversationId: number;
//tslint:disable-next-line:no-use-before-declare
@BelongsTo(() => GuaribasConversation)
public conversation: GuaribasConversation;
@ForeignKey(() => GuaribasInstance)
@Column
public instanceId: number;
@ForeignKey(() => GuaribasUser)
@Column
public userId: number;
@BelongsTo(() => GuaribasUser)
public user: GuaribasUser;
}
/**
* A conversation that groups many messages.
*/
@Table
export class GuaribasConversation extends Model<GuaribasConversation> {
@ -106,45 +150,3 @@ export class GuaribasConversation extends Model<GuaribasConversation> {
@BelongsTo(() => GuaribasUser)
public startedBy: GuaribasUser;
}
@Table
export class GuaribasConversationMessage extends Model<GuaribasConversationMessage> {
@PrimaryKey
@AutoIncrement
@Column
public conversationMessageId: number;
@ForeignKey(() => GuaribasSubject)
@Column
public subjectId: number;
@Column(DataType.TEXT)
public content: string;
@Column
@CreatedAt
public createdAt: Date;
@Column
@UpdatedAt
public updatedAt: Date;
@ForeignKey(() => GuaribasConversation)
@Column
public conversationId: number;
@BelongsTo(() => GuaribasConversation)
public conversation: GuaribasConversation;
@ForeignKey(() => GuaribasInstance)
@Column
public instanceId: number;
@ForeignKey(() => GuaribasUser)
@Column
public userId: number;
@BelongsTo(() => GuaribasUser)
public user: GuaribasUser;
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -37,6 +37,9 @@
import { GuaribasUser } from '../../security.gblib/models';
import { GuaribasConversation, GuaribasConversationMessage } from '../models';
/**
* Base services for Bot Analytics.
*/
export class AnalyticsService {
public async createConversation(
user: GuaribasUser
@ -52,7 +55,7 @@ export class AnalyticsService {
});
}
public createMessage(
public async createMessage(
conversation: GuaribasConversation,
user: GuaribasUser,
content: string

View file

@ -1,69 +0,0 @@
/*****************************************************************************\
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |
| |
| The texts of the GNU Affero General Public License with an additional |
| permission and of our proprietary license can be found at and |
| in the LICENSE file you have received along with this program. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY, without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| "General Bots" is a registered trademark of Pragmatismo.io. |
| The licensing of the program under the AGPLv3 does not imply a |
| trademark license. Therefore any rights, title and interest in |
| our trademarks remain entirely with us. |
| |
\*****************************************************************************/
/**
* @fileoverview General Bots server core.
*/
'use strict';
import { BotAdapter } from 'botbuilder';
import { GBMinInstance } from 'botlib';
import { IGBDialog } from 'botlib';
import { Messages } from '../strings';
export class BotFarmDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
*
* @param bot The bot adapter.
* @param min The minimal bot instance data.
*/
public static setup(bot: BotAdapter, min: GBMinInstance) {
min.dialogs.add('/createBotFarm', [
async step => {
const locale = step.context.activity.locale;
await step.prompt('choicePrompt', Messages[locale].what_about_me, [
'1',
'2',
'3',
'4',
'5'
]);
},
async step => {
const locale = step.context.activity.locale;
await step.context.sendActivity(Messages[locale].thanks);
}
]);
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,19 +36,17 @@
'use strict';
import { IGBInstance } from 'botlib';
import { GBLog, IGBInstallationDeployer, IGBInstance } from 'botlib';
import * as fs from 'fs';
import { GBAdminService } from '../../../packages/admin.gbapp/services/GBAdminService';
import { GBConfigService } from '../../../packages/core.gbapp/services/GBConfigService';
import { AzureDeployerService } from '../services/AzureDeployerService';
import { GuaribasInstance } from '../../../packages/core.gbapp/models/GBModel';
const scanf = require('scanf');
/**
* Handles command-line dialog for getting info for Boot Bot.
*/
export class StartDialog {
public static async createBaseInstance() {
public static async createBaseInstance(installationDeployer: IGBInstallationDeployer) {
// No .env so asks for cloud credentials to start a new farm.
if (!fs.existsSync(`.env`)) {
@ -75,7 +73,7 @@ export class StartDialog {
// Connects to the cloud and retrieves subscriptions.
const credentials = await GBAdminService.getADALCredentialsFromUsername(username, password);
const list = await AzureDeployerService.getSubscriptions(credentials);
const list = await installationDeployer.getSubscriptions(credentials);
let subscriptionId: string;
while (subscriptionId === undefined) {
@ -104,9 +102,8 @@ export class StartDialog {
process.stdout.write(`${GBAdminService.GB_PROMPT}Thank you. That is enough information.\nNow building farm...`);
// Prepares the first instance on bot farm.
const instance: IGBInstance = {};
const instance = <IGBInstance>{};
instance.botId = botId;
instance.cloudUsername = username;
@ -123,7 +120,7 @@ export class StartDialog {
private static retrieveUsername() {
let value = GBConfigService.get('CLOUD_USERNAME');
if (!value) {
if (value === undefined) {
process.stdout.write(`${GBAdminService.GB_PROMPT}CLOUD_USERNAME:`);
value = scanf('%s').replace(/(\n|\r)+$/, '');
}
@ -133,25 +130,23 @@ export class StartDialog {
private static retrievePassword() {
let password = GBConfigService.get('CLOUD_PASSWORD');
if (!password) {
if (password === undefined) {
process.stdout.write(`${GBAdminService.GB_PROMPT}CLOUD_PASSWORD:`);
password = scanf('%s').replace(/(\n|\r)+$/, '');
}
return password;
}
private static retrieveBotId() {
let botId = GBConfigService.get('BOT_ID');
if (!botId) {
if (botId === undefined) {
process.stdout.write(
`${GBAdminService.GB_PROMPT}Choose a unique bot Id containing lowercase letters, digits or
dashes (cannot use dash as the first two or last one characters),
cannot start or end with or contain consecutive dashes and having 4 to 42 characters long.\n`
);
process.stdout.write(`${GBAdminService.GB_PROMPT}BOT_ID:`);
// TODO: Update this regexp to match description of it.
botId = scanf('%s').replace(/(\n|\r)+$/, '');
}
@ -160,7 +155,7 @@ cannot start or end with or contain consecutive dashes and having 4 to 42 charac
private static retrieveAuthoringKey() {
let authoringKey = GBConfigService.get('NLP_AUTHORING_KEY');
if (!authoringKey) {
if (authoringKey === undefined) {
process.stdout.write(
`${
GBAdminService.GB_PROMPT
@ -179,12 +174,12 @@ cannot start or end with or contain consecutive dashes and having 4 to 42 charac
private static retrieveAppId() {
let appId = GBConfigService.get('MARKETPLACE_ID');
process.stdout.write(
`Sorry, this part cannot be automated yet due to Microsoft schedule,
if (appId === undefined) {
process.stdout.write(
`Sorry, this part cannot be automated yet due to Microsoft schedule,
please go to https://apps.dev.microsoft.com/portal/register-app to
generate manually an App ID and App Secret.\n`
);
if (!appId) {
);
process.stdout.write('Generated Application Id (MARKETPLACE_ID):');
appId = scanf('%s').replace(/(\n|\r)+$/, '');
}
@ -194,7 +189,7 @@ generate manually an App ID and App Secret.\n`
private static retrieveAppPassword() {
let appPassword = GBConfigService.get('MARKETPLACE_SECRET');
if (!appPassword) {
if (appPassword === undefined) {
process.stdout.write('Generated Password (MARKETPLACE_SECRET):');
appPassword = scanf('%s').replace(/(\n|\r)+$/, '');
}
@ -207,7 +202,7 @@ generate manually an App ID and App Secret.\n`
const map = {};
let index = 1;
list.forEach(element => {
console.log(`${index}: ${element.displayName} (${element.subscriptionId})`);
GBLog.info(`${index}: ${element.displayName} (${element.subscriptionId})`);
map[index++] = element;
});
let subscriptionIndex;
@ -222,8 +217,8 @@ generate manually an App ID and App Secret.\n`
private static retrieveLocation() {
let location = GBConfigService.get('CLOUD_LOCATION');
if (!location) {
process.stdout.write("CLOUD_LOCATION (eg. 'westus'):");
if (location === undefined) {
process.stdout.write('CLOUD_LOCATION (eg. westus):');
location = scanf('%s');
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,19 +36,30 @@
'use strict';
import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { Sequelize } from 'sequelize-typescript';
/**
* Package for Azure Deployer.
*/
export class GBAzureDeployerPackage implements IGBPackage {
public sysPackages: IGBPackage[] = null;
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {}
public unloadPackage(core: IGBCoreService): void {}
public loadBot(min: GBMinInstance): void {}
public unloadBot(min: GBMinInstance): void {}
public onNewSession(min: GBMinInstance, step: any): void {}
public sysPackages: IGBPackage[];
public getDialogs(min: GBMinInstance) {
GBLog.verbose(`getDialogs called.`);
}
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
GBLog.verbose(`loadPackage called.`);
}
public unloadPackage(core: IGBCoreService): void {
GBLog.verbose(`unloadPackage called.`);
}
public loadBot(min: GBMinInstance): void {
GBLog.verbose(`loadBot called.`);
}
public unloadBot(min: GBMinInstance): void {
GBLog.verbose(`unloadBot called.`);
}
public onNewSession(min: GBMinInstance, step: GBDialogStep): void {
GBLog.verbose(`onNewSession called.`);
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -37,39 +37,40 @@
'use strict';
import { CognitiveServicesManagementClient } from 'azure-arm-cognitiveservices';
import { CognitiveServicesAccount } from 'azure-arm-cognitiveservices/lib/models';
import { ResourceManagementClient, SubscriptionClient } from 'azure-arm-resource';
import { SearchManagementClient } from 'azure-arm-search';
import { SqlManagementClient } from 'azure-arm-sql';
import { WebSiteManagementClient } from 'azure-arm-website';
//tslint:disable-next-line:no-submodule-imports
import { AppServicePlan } from 'azure-arm-website/lib/models';
import { GBService, IGBInstance } from 'botlib';
import { HttpMethods, ServiceClient, WebResource } from 'ms-rest-js';
import { GBDeployer } from '../../../packages/core.gbapp/services/GBDeployer';
import * as simplegit from 'simple-git/promise';
import { GBLog, IGBInstallationDeployer, IGBInstance } from 'botlib';
import { HttpHeaders, HttpMethods, ServiceClient, WebResource } from 'ms-rest-js';
import { GBAdminService } from '../../../packages/admin.gbapp/services/GBAdminService';
import { GBCorePackage } from '../../../packages/core.gbapp';
import { GBConfigService } from '../../../packages/core.gbapp/services/GBConfigService';
import { GuaribasInstance } from '../../../packages/core.gbapp/models/GBModel';
import { GBDeployer } from '../../../packages/core.gbapp/services/GBDeployer';
const Spinner = require('cli-spinner').Spinner;
const scanf = require('scanf');
const git = simplegit();
const logger = require('../../../src/logger');
const UrlJoin = require('url-join');
// tslint:disable-next-line:no-submodule-imports
import { CognitiveServicesAccount } from 'azure-arm-cognitiveservices/lib/models';
import urlJoin = require('url-join');
const iconUrl = 'https://github.com/pragmatismo-io/BotServer/blob/master/docs/images/generalbots-logo-squared.png';
const publicIp = require('public-ip');
export class AzureDeployerService extends GBService {
public static apiVersion = '2017-12-01';
public static defaultEndPoint = 'http://localhost:4242';
/**
* Deployer for Microsoft cloud.
*/
export class AzureDeployerService implements IGBInstallationDeployer {
public apiVersion = '2017-12-01';
public defaultEndPoint = 'http://localhost:4242';
public instance: IGBInstance;
public resourceClient: ResourceManagementClient.ResourceManagementClient;
public webSiteClient: WebSiteManagementClient;
public storageClient: SqlManagementClient;
public cognitiveClient: CognitiveServicesManagementClient;
public searchClient: SearchManagementClient;
public static provider = 'Microsoft.BotService';
public provider = 'Microsoft.BotService';
public subscriptionClient: SubscriptionClient.SubscriptionClient;
public accessToken: string;
public location: string;
@ -78,15 +79,28 @@ export class AzureDeployerService extends GBService {
public deployer: GBDeployer;
constructor(deployer: GBDeployer) {
super();
this.deployer = deployer;
}
public static async getSubscriptions(credentials) {
private static createRequestObject(url: string, accessToken: string, verb: HttpMethods, body: string) {
const req = new WebResource();
req.method = verb;
req.url = url;
req.headers.set('Content-Type', 'application/json');
req.headers.set('accept-language', '*');
req.headers.set('Authorization', `Bearer ${accessToken}`);
req.body = body;
return req;
}
public async getSubscriptions(credentials) {
const subscriptionClient = new SubscriptionClient.default(credentials);
return subscriptionClient.subscriptions.list();
}
public static getKBSearchSchema(indexName) {
public getKBSearchSchema(indexName) {
return {
name: indexName,
fields: [
@ -182,21 +196,18 @@ export class AzureDeployerService extends GBService {
}
],
scoringProfiles: [],
defaultScoringProfile: null,
corsOptions: null
defaultScoringProfile: undefined,
corsOptions: undefined
};
}
public static async updateBotProxy(botId, group, endpoint) {
public async updateBotProxy(botId, group, endpoint) {
const baseUrl = `https://management.azure.com/`;
const username = GBConfigService.get('CLOUD_USERNAME');
const password = GBConfigService.get('CLOUD_PASSWORD');
const subscriptionId = GBConfigService.get('CLOUD_SUBSCRIPTIONID');
const accessToken = await GBAdminService.getADALTokenFromUsername(
username,
password
);
const accessToken = await GBAdminService.getADALTokenFromUsername(username, password);
const httpClient = new ServiceClient();
const parameters = {
@ -207,22 +218,18 @@ export class AzureDeployerService extends GBService {
const query = `subscriptions/${subscriptionId}/resourceGroups/${group}/providers/${
this.provider
}/botServices/${botId}?api-version=${AzureDeployerService.apiVersion}`;
const url = UrlJoin(baseUrl, query);
const req = this.createRequestObject(
url,
accessToken,
'PATCH',
JSON.stringify(parameters)
);
}/botServices/${botId}?api-version=${this.apiVersion}`;
const url = urlJoin(baseUrl, query);
const req = AzureDeployerService.createRequestObject(url, accessToken, 'PATCH', JSON.stringify(parameters));
const res = await httpClient.sendRequest(req);
if (!(res.bodyAsJson as any).id) {
// CHECK
if (!JSON.parse(res.bodyAsText).id) {
throw res.bodyAsText;
}
logger.info(`Bot proxy updated at: ${endpoint}.`);
GBLog.info(`Bot proxy updated at: ${endpoint}.`);
}
public static async openStorageFirewall(groupName, serverName) {
public async openStorageFirewall(groupName, serverName) {
const username = GBConfigService.get('CLOUD_USERNAME');
const password = GBConfigService.get('CLOUD_PASSWORD');
const subscriptionId = GBConfigService.get('CLOUD_SUBSCRIPTIONID');
@ -239,19 +246,6 @@ export class AzureDeployerService extends GBService {
await storageClient.firewallRules.createOrUpdate(groupName, serverName, 'gb', params);
}
public static createRequestObject(url: string, accessToken: string, verb: HttpMethods, body: string) {
const req = new WebResource();
req.method = verb;
req.url = url;
req.headers = {};
req.headers['Content-Type'] = 'application/json';
req.headers['accept-language'] = '*';
req.headers.Authorization = 'Bearer ' + accessToken;
req.body = body;
return req;
}
public async deployFarm(
proxyAddress: string,
instance: IGBInstance,
@ -261,22 +255,20 @@ export class AzureDeployerService extends GBService {
const culture = 'en-us';
this.initServices(credentials, subscriptionId);
const spinner = new Spinner('%s');
spinner.start();
spinner.setSpinnerString('|/-\\');
let keys: any;
const name = instance.botId;
logger.info(`Deploying Deploy Group (It may take a few minutes)...`);
GBLog.info(`Deploying Deploy Group (It may take a few minutes)...`);
await this.createDeployGroup(name, instance.cloudLocation);
logger.info(`Deploying Bot Server...`);
GBLog.info(`Deploying Bot Server...`);
const serverFarm = await this.createHostingPlan(name, `${name}-server-plan`, instance.cloudLocation);
await this.createServer(serverFarm.id, name, `${name}-server`, instance.cloudLocation);
logger.info(`Deploying Bot Storage...`);
GBLog.info(`Deploying Bot Storage...`);
const administratorLogin = `sa${GBAdminService.getRndReadableIdentifier()}`;
const administratorPassword = GBAdminService.getRndPassword();
const storageServer = `${name.toLowerCase()}-storage-server`;
@ -296,7 +288,7 @@ export class AzureDeployerService extends GBService {
instance.storageDialect = 'mssql';
instance.storageServer = storageServer;
logger.info(`Deploying Search...`);
GBLog.info(`Deploying Search...`);
const searchName = `${name}-search`.toLowerCase();
await this.createSearch(name, searchName, instance.cloudLocation);
const searchKeys = await this.searchClient.adminKeys.get(name, searchName);
@ -304,27 +296,28 @@ export class AzureDeployerService extends GBService {
instance.searchIndex = 'azuresql-index';
instance.searchIndexer = 'azuresql-indexer';
instance.searchKey = searchKeys.primaryKey;
this.deployer.rebuildIndex(instance);
this.deployer.rebuildIndex(instance, this.deployer);
logger.info(`Deploying Speech...`);
GBLog.info(`Deploying Speech...`);
const speech = await this.createSpeech(name, `${name}-speech`, instance.cloudLocation);
keys = await this.cognitiveClient.accounts.listKeys(name, speech.name);
instance.speechKeyEndpoint = speech.endpoint;
instance.speechEndpoint = speech.endpoint;
instance.speechKey = keys.key1;
logger.info(`Deploying SpellChecker...`);
const spellChecker = await this.createSpellChecker(name, `${name}-spellchecker`, instance.cloudLocation);
GBLog.info(`Deploying SpellChecker...`);
const spellChecker = await this.createSpellChecker(name, `${name}-spellchecker`);
keys = await this.cognitiveClient.accounts.listKeys(name, spellChecker.name);
instance.spellCheckerKey = keys.key1;
instance.spellCheckerEndpoint = spellChecker.endpoint;
instance.spellcheckerKey = keys.key1;
instance.spellcheckerEndpoint = spellChecker.endpoint;
logger.info(`Deploying Text Analytics...`);
GBLog.info(`Deploying Text Analytics...`);
const textAnalytics = await this.createTextAnalytics(name, `${name}-textanalytics`, instance.cloudLocation);
keys = await this.cognitiveClient.accounts.listKeys(name, textAnalytics.name);
instance.textAnalyticsEndpoint = textAnalytics.endpoint;
instance.textAnalyticsEndpoint = textAnalytics.endpoint.replace(`/text/analytics/v2.0`, '');
instance.textAnalyticsKey = keys.key1;
logger.info(`Deploying NLP...`);
GBLog.info(`Deploying NLP...`);
const nlp = await this.createNLP(name, `${name}-nlp`, instance.cloudLocation);
keys = await this.cognitiveClient.accounts.listKeys(name, nlp.name);
const nlpAppId = await this.createNLPService(name, name, instance.cloudLocation, culture, instance.nlpAuthoringKey);
@ -333,8 +326,8 @@ export class AzureDeployerService extends GBService {
instance.nlpKey = keys.key1;
instance.nlpAppId = nlpAppId;
logger.info(`Deploying Bot...`);
instance.botEndpoint = AzureDeployerService.defaultEndPoint;
GBLog.info(`Deploying Bot...`);
instance.botEndpoint = this.defaultEndPoint;
instance = await this.internalDeployBot(
instance,
@ -347,27 +340,27 @@ export class AzureDeployerService extends GBService {
'global',
instance.nlpAppId,
instance.nlpKey,
instance.appId,
instance.appPassword,
instance.marketplaceId,
instance.marketplacePassword,
instance.cloudSubscriptionId
);
spinner.stop();
return instance;
}
public async deployToCloud(
title,
username,
password,
cloudLocation,
authoringKey,
appId,
appPassword,
subscriptionId
title: string,
username: string,
password: string,
cloudLocation: string,
authoringKey: string,
appId: string,
appPassword: string,
subscriptionId: string
) {
const instance: IGBInstance = {};
const instance = <IGBInstance>{};
instance.botId = title;
instance.cloudUsername = username;
@ -380,10 +373,9 @@ export class AzureDeployerService extends GBService {
instance.adminPass = GBAdminService.getRndPassword();
const credentials = await GBAdminService.getADALCredentialsFromUsername(username, password);
this.deployFarm(`http://${instance.botId}.azurewebsites.net`, instance, credentials, subscriptionId);
// TODO: Copy github to webapp.
//const status = await git.status();
// tslint:disable-next-line:no-http-string
const url = `http://${instance.botId}.azurewebsites.net`;
this.deployFarm(url, instance, credentials, subscriptionId);
}
private initServices(credentials: any, subscriptionId: string) {
@ -395,19 +387,6 @@ export class AzureDeployerService extends GBService {
this.accessToken = credentials.tokenCache._entries[0].accessToken;
}
private async updateWebisteConfig(group, serverFarmId, name, location) {
const siteConfig = {
location: location,
serverFarmId: serverFarmId,
numberOfWorkers: 1,
phpVersion: '5.5'
};
// TODO: Copy .env to app settings.
return this.webSiteClient.webApps.createOrUpdateConfiguration(group, name, siteConfig);
}
private async createStorageServer(group, name, administratorLogin, administratorPassword, serverName, location) {
const params = {
location: location,
@ -420,22 +399,16 @@ export class AzureDeployerService extends GBService {
}
private async registerProviders(subscriptionId, baseUrl, accessToken) {
const query = `subscriptions/${subscriptionId}/providers/${
AzureDeployerService.provider
}/register?api-version=2018-02-01`;
const requestUrl = UrlJoin(baseUrl, query);
const query = `subscriptions/${subscriptionId}/providers/${this.provider}/register?api-version=2018-02-01`;
const requestUrl = urlJoin(baseUrl, query);
const req = new WebResource();
req.method = 'POST';
req.url = requestUrl;
req.headers = {};
req.headers = <any>{};
req.headers['Content-Type'] = 'application/json; charset=utf-8';
req.headers['accept-language'] = '*';
req.headers.Authorization = 'Bearer ' + accessToken;
const httpClient = new ServiceClient();
const res = await httpClient.sendRequest(req);
// TODO: Check res for error.
(req.headers as any).Authorization = `Bearer ${accessToken}`;
}
/**
@ -455,7 +428,7 @@ export class AzureDeployerService extends GBService {
appId,
appPassword,
subscriptionId
) {
): Promise<IGBInstance> {
return new Promise(async (resolve, reject) => {
const baseUrl = `https://management.azure.com/`;
await this.registerProviders(subscriptionId, baseUrl, accessToken);
@ -487,25 +460,27 @@ export class AzureDeployerService extends GBService {
const httpClient = new ServiceClient();
let query = `subscriptions/${subscriptionId}/resourceGroups/${group}/providers/${
AzureDeployerService.provider
}/botServices/${botId}?api-version=${AzureDeployerService.apiVersion}`;
let url = UrlJoin(baseUrl, query);
this.provider
}/botServices/${botId}?api-version=${this.apiVersion}`;
let url = urlJoin(baseUrl, query);
let req = AzureDeployerService.createRequestObject(url, accessToken, 'PUT', JSON.stringify(parameters));
const res = await httpClient.sendRequest(req);
if (!(res.bodyAsJson as any).id) {
if (!JSON.parse(res.bodyAsText).id) {
reject(res.bodyAsText);
return;
}
setTimeout(async () => {
try {
//tslint:disable-next-line:max-line-length
query = `subscriptions/${subscriptionId}/resourceGroups/${group}/providers/Microsoft.BotService/botServices/${botId}/channels/WebChatChannel/listChannelWithKeys?api-version=${
AzureDeployerService.apiVersion
}`;
url = UrlJoin(baseUrl, query);
req = AzureDeployerService.createRequestObject(url, accessToken, 'GET', JSON.stringify(parameters));
this.apiVersion
}`;
url = urlJoin(baseUrl, query);
req = AzureDeployerService.createRequestObject(url, accessToken, 'POST', JSON.stringify(parameters));
const resChannel = await httpClient.sendRequest(req);
const key = (resChannel.bodyAsJson as any).properties.properties.sites[0].key;
const key = JSON.parse(resChannel.bodyAsText).properties.properties.sites[0].key;
instance.webchatKey = key;
resolve(instance);
} catch (error) {
@ -529,8 +504,8 @@ export class AzureDeployerService extends GBService {
};
const body = JSON.stringify(parameters);
const apps = await this.makeNlpRequest(location, authoringKey, null, 'GET', 'apps');
const app = (apps.bodyAsJson as any).filter(x => x.name == name)[0];
const apps = await this.makeNlpRequest(location, authoringKey, undefined, 'GET', 'apps');
const app = JSON.parse(apps.bodyAsText).filter(x => x.name === name)[0];
let id: string;
if (!app) {
const res = await this.makeNlpRequest(location, authoringKey, body, 'POST', 'apps');
@ -552,10 +527,9 @@ export class AzureDeployerService extends GBService {
const req = new WebResource();
req.method = method;
req.url = `https://${location}.api.cognitive.microsoft.com/luis/api/v2.0/${resource}`;
req.headers = {};
req.headers['Content-Type'] = 'application/json';
req.headers['accept-language'] = '*';
req.headers['Ocp-Apim-Subscription-Key'] = authoringKey;
req.headers.set('Content-Type', 'application/json');
req.headers.set('accept-language', '*');
req.headers.set('Ocp-Apim-Subscription-Key', authoringKey);
req.body = body;
const httpClient = new ServiceClient();
@ -601,7 +575,7 @@ export class AzureDeployerService extends GBService {
return await this.createCognitiveServices(group, name, location, 'LUIS');
}
private async createSpellChecker(group, name, location): Promise<CognitiveServicesAccount> {
private async createSpellChecker(group, name): Promise<CognitiveServicesAccount> {
return await this.createCognitiveServices(group, name, 'global', 'Bing.SpellCheck.v7');
}
@ -611,6 +585,7 @@ export class AzureDeployerService extends GBService {
private async createDeployGroup(name, location) {
const params = { location: location };
return this.resourceClient.resourceGroups.createOrUpdate(name, params);
}
@ -633,7 +608,7 @@ export class AzureDeployerService extends GBService {
location: location,
serverFarmId: farmId
};
return this.webSiteClient.webApps.createOrUpdate(group, name, parameters);
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,30 +36,33 @@
'use strict';
const UrlJoin = require('url-join');
import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { Sequelize } from 'sequelize-typescript';
import { ConsoleDirectLine } from './services/ConsoleDirectLine';
/**
* Package for console.glib.
*/
export class GBConsolePackage implements IGBPackage {
public sysPackages: IGBPackage[] = null;
public sysPackages: IGBPackage[];
public channel: ConsoleDirectLine;
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
public getDialogs(min: GBMinInstance) {
GBLog.verbose(`getDialogs called.`);
}
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
GBLog.verbose(`loadPackage called.`);
}
public unloadPackage(core: IGBCoreService): void {
GBLog.verbose(`unloadPackage called.`);
}
public unloadBot(min: GBMinInstance): void {
GBLog.verbose(`unloadBot called.`);
}
public onNewSession(min: GBMinInstance, step: GBDialogStep): void {
GBLog.verbose(`onNewSession called.`);
}
public loadBot(min: GBMinInstance): void {
this.channel = new ConsoleDirectLine(min.instance.webchatKey);
}
public unloadBot(min: GBMinInstance): void {
}
public onNewSession(min: GBMinInstance, step: any): void {
}
}

View file

@ -1,192 +1,176 @@
/*****************************************************************************\
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |
| |
| The texts of the GNU Affero General Public License with an additional |
| permission and of our proprietary license can be found at and |
| in the LICENSE file you have received along with this program. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY, without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| "General Bots" is a registered trademark of Pragmatismo.io. |
| The licensing of the program under the AGPLv3 does not imply a |
| trademark license. Therefore any rights, title and interest in |
| our trademarks remain entirely with us. |
| |
\*****************************************************************************/
const Path = require('path');
const Fs = require('fs');
const _ = require('lodash');
const Parse = require('csv-parse');
const Async = require('async');
const UrlJoin = require('url-join');
const logger = require('../../../src/logger');
const Swagger = require('swagger-client');
const rp = require('request-promise');
import { GBService } from 'botlib';
import { GBLog, GBService } from 'botlib';
/**
* Bot simulator in terminal window.
*/
export class ConsoleDirectLine extends GBService {
public pollInterval: number = 1000;
public directLineSecret: string = '';
public directLineClientName: string = 'DirectLineClient';
public directLineSpecUrl: string = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json';
public pollInterval = 1000;
public directLineSecret = '';
public directLineClientName = 'DirectLineClient';
public directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json';
constructor(directLineSecret: string) {
super();
constructor(directLineSecret) {
super();
this.directLineSecret = directLineSecret;
// TODO: Migrate to Swagger 3.
const directLineClient = rp(this.directLineSpecUrl)
.then(function (spec) {
return new Swagger({
spec: JSON.parse(spec.trim()),
usePromise: true
});
})
.then(function (client) {
client.clientAuthorizations.add('AuthorizationBotConnector',
new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + directLineSecret, 'header'));
return client;
})
.catch(function (err) {
console.error('Error initializing DirectLine client', err);
});
// TODO: Remove *this* issue.
const _this_ = this;
directLineClient.then((client) => {
client.Conversations.Conversations_StartConversation()
.then(function (response) {
return response.obj.conversationId;
})
.then(function (conversationId) {
_this_.sendMessagesFromConsole(client, conversationId);
_this_.pollMessages(client, conversationId);
})
.catch(function (err) {
console.error('Error starting conversation', err);
});
this.directLineSecret = directLineSecret;
// tslint:disable-next-line:no-unsafe-any
const directLineClient = rp(this.directLineSpecUrl)
.then((spec: string) => {
// tslint:disable-next-line:no-unsafe-any
return new Swagger({
spec: JSON.parse(spec.trim()),
usePromise: true
});
})
.then(client => {
// tslint:disable-next-line:no-unsafe-any
client.clientAuthorizations.add(
'AuthorizationBotConnector',
// tslint:disable-next-line:no-unsafe-any
new Swagger.ApiKeyAuthorization('Authorization', `Bearer ${directLineSecret}`, 'header')
);
return client;
})
.catch(err => {
GBLog.error(`Error initializing DirectLine client ${err}`);
});
const _this_ = this;
// tslint:disable-next-line:no-unsafe-any
directLineClient.then(client => {
// tslint:disable-next-line:no-unsafe-any
client.Conversations.Conversations_StartConversation()
.then(response => {
// tslint:disable-next-line:no-unsafe-any
return response.obj.conversationId;
})
.then(conversationId => {
_this_.sendMessagesFromConsole(client, conversationId);
_this_.pollMessages(client, conversationId);
})
.catch(err => {
GBLog.error(`Error starting conversation ${err}`);
});
});
}
public sendMessagesFromConsole(client, conversationId) {
const _this_ = this;
process.stdin.resume();
const stdin = process.stdin;
process.stdout.write('Command> ');
stdin.addListener('data', e => {
// tslint:disable-next-line:no-unsafe-any
const input: string = e.toString().trim();
if (input !== undefined) {
// exit
if (input.toLowerCase() === 'exit') {
return process.exit();
}
// tslint:disable-next-line:no-unsafe-any
client.Conversations.Conversations_PostActivity({
conversationId: conversationId,
activity: {
textFormat: 'plain',
text: input,
type: 'message',
from: {
id: _this_.directLineClientName,
name: _this_.directLineClientName
}
}
}).catch(err => {
GBLog.error(`Error sending message: ${err}`);
});
}
public sendMessagesFromConsole(client, conversationId) {
const _this_ = this;
process.stdin.resume();
const stdin = process.stdin;
process.stdout.write('Command> ');
stdin.addListener('data', function (e) {
const input = e.toString().trim();
if (input) {
// exit
if (input.toLowerCase() === 'exit') {
return process.exit();
}
}
});
}
client.Conversations.Conversations_PostActivity(
{
conversationId: conversationId,
activity: {
textFormat: 'plain',
text: input,
type: 'message',
from: {
id: _this_.directLineClientName,
name: _this_.directLineClientName
}
}
}).catch(function (err) {
console.error('Error sending message:', err);
});
public pollMessages(client, conversationId) {
const _this_ = this;
GBLog.info(`Starting polling message for conversationId: ${conversationId}`);
let watermark;
setInterval(() => {
// tslint:disable-next-line:no-unsafe-any
client.Conversations.Conversations_GetActivities({ conversationId: conversationId, watermark: watermark })
.then(response => {
// tslint:disable-next-line:no-unsafe-any
watermark = response.obj.watermark;
process.stdout.write('Command> ');
}
});
// tslint:disable-next-line:no-unsafe-any
return response.obj.activities;
})
.then(_this_.printMessages, _this_.directLineClientName);
// tslint:disable-next-line:align
}, this.pollInterval);
}
// tslint:disable:no-unsafe-any
public printMessages(activities, directLineClientName) {
if (activities && activities.length) {
// ignore own messages
activities = activities.filter(m => {
return m.from.id !== directLineClientName;
});
if (activities.length) {
// print other messages
activities.forEach(activity => {
GBLog.info(activity.text);
// tslint:disable-next-line:align
}, this);
process.stdout.write('Command> ');
}
}
}
// tslint:enable:no-unsafe-any
// tslint:disable:no-unsafe-any
public printMessage(activity) {
if (activity.text) {
GBLog.info(activity.text);
}
/** TBD: Poll Messages from conversation using DirectLine client */
public pollMessages(client, conversationId) {
const _this_ = this;
console.log('Starting polling message for conversationId: ' + conversationId);
let watermark = null;
setInterval(function () {
client.Conversations.Conversations_GetActivities({ conversationId: conversationId, watermark: watermark })
.then(function (response) {
watermark = response.obj.watermark; // use watermark so subsequent requests skip old messages
return response.obj.activities;
})
.then(_this_.printMessages, _this_.directLineClientName);
}, this.pollInterval);
}
if (activity.attachments) {
activity.attachments.forEach(attachment => {
switch (attachment.contentType) {
case 'application/vnd.microsoft.card.hero':
this.renderHeroCard(attachment);
break;
public printMessages(activities, directLineClientName) {
case 'image/png':
GBLog.info(`Opening the requested image ${attachment.contentUrl}`);
open(attachment.contentUrl);
break;
if (activities && activities.length) {
// ignore own messages
activities = activities.filter(function (m) { return m.from.id !== directLineClientName; });
if (activities.length) {
// print other messages
activities.forEach(activity => {
console.log(activity.text);
}, this);
process.stdout.write('Command> ');
}
default:
GBLog.info(`Unknown contentType: ${attachment.contentType}`);
break;
}
});
}
}
// tslint:enable:no-unsafe-any
public printMessage(activity) {
if (activity.text) {
console.log(activity.text);
}
// tslint:disable:no-unsafe-any
public renderHeroCard(attachment) {
const width = 70;
const contentLine = content => {
return `${' '.repeat((width - content.length) / 2)}content${' '.repeat((width - content.length) / 2)}`;
};
if (activity.attachments) {
activity.attachments.forEach(function (attachment) {
switch (attachment.contentType) {
case 'application/vnd.microsoft.card.hero':
this.renderHeroCard(attachment);
break;
case 'image/png':
console.log('Opening the requested image ' + attachment.contentUrl);
open(attachment.contentUrl);
break;
}
});
}
}
public renderHeroCard(attachment) {
const width = 70;
const contentLine = function (content) {
return ' '.repeat((width - content.length) / 2) +
content +
' '.repeat((width - content.length) / 2);
};
console.log('/' + '*'.repeat(width + 1));
console.log('*' + contentLine(attachment.content.title) + '*');
console.log('*' + ' '.repeat(width) + '*');
console.log('*' + contentLine(attachment.content.text) + '*');
console.log('*'.repeat(width + 1) + '/');
}
GBLog.info(`/${'*'.repeat(width + 1)}`);
GBLog.info(`*${contentLine(attachment.content.title)}*`);
GBLog.info(`*${' '.repeat(width)}*`);
GBLog.info(`*${contentLine(attachment.content.text)}*`);
GBLog.info(`${'*'.repeat(width + 1)}/`);
}
// tslint:enable:no-unsafe-any
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -38,10 +38,12 @@
import { BotAdapter } from 'botbuilder';
import {WaterfallDialog } from 'botbuilder-dialogs';
import { IGBDialog } from 'botlib';
import { GBMinInstance } from 'botlib';
import { GBMinInstance, IGBDialog } from 'botlib';
import { Messages } from '../strings';
/**
* Dialog for Welcoming people.
*/
export class WelcomeDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
@ -73,9 +75,9 @@ export class WelcomeDialog extends IGBDialog {
await step.replaceDialog('/ask', { firstTime: true });
if (
step.context.activity &&
step.context.activity.type == 'message' &&
step.context.activity.text != ''
step.context.activity !== undefined &&
step.context.activity.type === 'message' &&
step.context.activity.text !== ''
) {
await step.replaceDialog('/answer', { query: step.context.activity.text });
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -38,10 +38,11 @@
import { BotAdapter } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { IGBDialog } from 'botlib';
import { GBMinInstance } from 'botlib';
import { GBMinInstance, IGBDialog } from 'botlib';
import { Messages } from '../strings';
/**
* Dialog for the bot explains about itself.
*/
export class WhoAmIDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
@ -55,7 +56,7 @@ export class WhoAmIDialog extends IGBDialog {
const locale = step.context.activity.locale;
await step.context.sendActivity(`${min.instance.description}`);
if (min.instance.whoAmIVideo) {
if (min.instance.whoAmIVideo !== undefined) {
await step.context.sendActivity(Messages[locale].show_video);
await min.conversationalService.sendEvent(step, 'play', {
playerType: 'video',

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,42 +36,37 @@
'use strict';
const UrlJoin = require('url-join');
import { GBMinInstance, IGBPackage } from 'botlib';
import { IGBCoreService} from 'botlib';
import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { Sequelize } from 'sequelize-typescript';
import { WelcomeDialog } from './dialogs/WelcomeDialog';
import { WhoAmIDialog } from './dialogs/WhoAmIDialog';
import { GuaribasChannel, GuaribasException, GuaribasInstance, GuaribasPackage } from './models/GBModel';
/**
* Package for core.gbapp.
*/
export class GBCorePackage implements IGBPackage {
public static CurrentEngineName = 'guaribas-1.0.0';
public sysPackages: IGBPackage[] = null;
public sysPackages: IGBPackage[];
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
core.sequelize.addModels([
GuaribasInstance,
GuaribasPackage,
GuaribasChannel,
GuaribasException
]);
core.sequelize.addModels([GuaribasInstance, GuaribasPackage, GuaribasChannel, GuaribasException]);
}
public getDialogs(min: GBMinInstance) {
GBLog.verbose(`getDialogs called.`);
}
public unloadPackage(core: IGBCoreService): void {
GBLog.verbose(`unloadPackage called.`);
}
public unloadBot(min: GBMinInstance): void {
GBLog.verbose(`unloadBot called.`);
}
public onNewSession(min: GBMinInstance, step: GBDialogStep): void {
GBLog.verbose(`onNewSession called.`);
}
public loadBot(min: GBMinInstance): void {
WelcomeDialog.setup(min.bot, min);
WhoAmIDialog.setup(min.bot, min);
}
public unloadBot(min: GBMinInstance): void {
}
public onNewSession(min: GBMinInstance, step: any): void {
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -31,46 +31,20 @@
\*****************************************************************************/
/**
* @fileoverview Logging support.
* @fileoverview General Bots server core.
*/
const { createLogger, format, transports } = require('winston');
'use strict';
const config = {
levels: {
error: 0,
debug: 1,
warn: 2,
data: 3,
info: 4,
verbose: 5,
silly: 6,
custom: 7
},
colors: {
error: 'red',
debug: 'blue',
warn: 'yellow',
data: 'grey',
info: 'green',
verbose: 'cyan',
silly: 'magenta',
custom: 'yellow'
}
};
const logger = createLogger({
format: format.combine(
format.colorize(),
format.simple(),
format.label({ label: 'GeneralBots' }),
format.timestamp(),
format.printf(nfo => {
return `${nfo.timestamp} [${nfo.label}] ${nfo.level}: ${nfo.message}`;
})
),
levels: config.levels,
transports: [new transports.Console()]
});
module.exports = logger;
import {
AutoIncrement,
BelongsTo,
Column,
CreatedAt,
ForeignKey,
Model,
PrimaryKey,
Table,
UpdatedAt
} from 'sequelize-typescript';
import { GuaribasInstance } from './GBModel';

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -51,9 +51,13 @@ import {
import { IGBInstance } from 'botlib';
/**
* Base instance data for a bot.
*/
@Table
export class GuaribasInstance extends Model<GuaribasInstance>
implements IGBInstance {
@PrimaryKey
@AutoIncrement
@Column
@ -80,8 +84,6 @@ export class GuaribasInstance extends Model<GuaribasInstance>
@Column
public enabledAdmin: boolean;
/* Services section on bot.json */
@Column
public engineName: string;
@ -152,7 +154,7 @@ export class GuaribasInstance extends Model<GuaribasInstance>
public speechKey: string;
@Column
public speechKeyEndpoint: string;
public speechEndpoint: string;
@Column
public spellcheckerKey: string;
@ -218,8 +220,6 @@ export class GuaribasInstance extends Model<GuaribasInstance>
@Column
public adminPass: string;
/* Settings section of bot.json */
@Column(DataType.FLOAT)
public nlpVsSearch: number;
@ -238,6 +238,9 @@ export class GuaribasInstance extends Model<GuaribasInstance>
public updatedAt: Date;
}
/**
* Each packaged listed for use in a bot instance.
*/
@Table
export class GuaribasPackage extends Model<GuaribasPackage> {
@PrimaryKey
@ -264,6 +267,9 @@ export class GuaribasPackage extends Model<GuaribasPackage> {
public updatedAt: Date;
}
/**
* A bot channel.
*/
@Table
export class GuaribasChannel extends Model<GuaribasChannel> {
@PrimaryKey
@ -283,7 +289,11 @@ export class GuaribasChannel extends Model<GuaribasChannel> {
public updatedAt: Date;
}
/**
* An exception that has been thrown.
*/
@Table
//tslint:disable-next-line:max-classes-per-file
export class GuaribasException extends Model<GuaribasException> {
@PrimaryKey
@AutoIncrement

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -34,23 +34,32 @@
import { TurnContext } from 'botbuilder';
import { WaterfallStepContext } from 'botbuilder-dialogs';
import { GBMinInstance } from 'botlib';
import { GBLog, GBMinInstance } from 'botlib';
import * as request from 'request-promise-native';
import urlJoin = require('url-join');
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService';
import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService';
import { GBDeployer } from './GBDeployer';
/**
* @fileoverview General Bots server core.
*/
/**
* BASIC system class for extra manipulation of bot behaviour.
*/
class SysClass {
public min: GBMinInstance;
private readonly deployer: GBDeployer;
constructor(min: GBMinInstance) {
constructor(min: GBMinInstance, deployer: GBDeployer) {
this.min = min;
this.deployer = deployer;
}
public async wait(seconds: number) {
const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));
// tslint:disable-next-line no-string-based-set-timeout
const timeout = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
await timeout(seconds * 1000);
}
@ -59,16 +68,16 @@ class SysClass {
}
public async createABotFarmUsing(
botId,
username,
password,
location,
nlpAuthoringKey,
appId,
appPassword,
subscriptionId
botId: string,
username: string,
password: string,
location: string,
nlpAuthoringKey: string,
appId: string,
appPassword: string,
subscriptionId: string
) {
const service = new AzureDeployerService(this.min.deployer);
const service = new AzureDeployerService(this.deployer);
await service.deployToCloud(
botId,
username,
@ -80,21 +89,42 @@ class SysClass {
subscriptionId
);
}
}
/**
* @fileoverview General Bots server core.
*/
export default class DialogClass {
/**
* Generic function to call any REST API.
*/
public async sendEmail(to, subject, body) {
// tslint:disable-next-line:no-console
GBLog.info(`[E-mail]: to:${to}, subject: ${subject}, body: ${body}.`);
}
/**
* Generic function to call any REST API.
*/
public async httpGet(url: string, qs) {
const options = {
uri: urlJoin(url , qs)
};
return request.get(options);
}
}
/**
* Base services of conversation to be called by BASIC.
*/
export class DialogClass {
public min: GBMinInstance;
public context: TurnContext;
public step: WaterfallStepContext;
public internalSys: SysClass;
constructor(min: GBMinInstance) {
constructor(min: GBMinInstance, deployer: GBDeployer) {
this.min = min;
this.internalSys = new SysClass(min);
this.internalSys = new SysClass(min, deployer);
}
public sys(): SysClass {
@ -102,7 +132,7 @@ export default class DialogClass {
}
public async hear(cb) {
const idCallback = Math.floor(Math.random() * 1000000000000);
const idCallback = crypto.getRandomValues(new Uint32Array(16))[0];
this.min.cbMap[idCallback] = cb;
await this.step.beginDialog('/hear', { id: idCallback });
}
@ -110,17 +140,4 @@ export default class DialogClass {
public async talk(text: string) {
return await this.context.sendActivity(text);
}
/**
* Generic function to call any REST API.
*/
public sendEmail(to, subject, body) {
// tslint:disable-next-line:no-console
console.log(`[E-mail]: to:${to}, subject: ${subject}, body: ${body}.`);
}
/**
* Generic function to call any REST API.
*/
public post(url: string, data) {}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -30,16 +30,29 @@
| |
\*****************************************************************************/
const logger = require('../../../src/logger');
import * as fs from 'fs';
'use strict';
import { GBLog } from 'botlib';
/**
* @fileoverview General Bots server core.
*/
'use strict';
/**
* Base configuration for the server like storage.
*/
export class GBConfigService {
public static getServerPort(): number {
if (process.env.port !== undefined) {
return Number(process.env.port);
}
if (process.env.PORT !== undefined) {
return Number(process.env.PORT);
}
return 4242;
}
public static init(): any {
try {
require('dotenv-extended').load({
@ -49,7 +62,7 @@ export class GBConfigService {
overrideProcessEnv: true
});
} catch (e) {
console.error(e.message);
GBLog.error(e.message);
process.exit(3);
}
}
@ -57,7 +70,7 @@ export class GBConfigService {
public static get(key: string): string | undefined {
let value = GBConfigService.tryGet(key);
if (!value) {
if (value === undefined) {
switch (key) {
case 'CLOUD_USERNAME':
value = undefined;
@ -108,18 +121,20 @@ export class GBConfigService {
value = 'true';
break;
default:
logger.warn(`Invalid key on .env file: '${key}'`);
GBLog.warn(`Invalid key on .env file: '${key}'`);
break;
}
}
return value;
}
public static tryGet(key: string) {
let value = process.env['container:' + key];
if (!value) {
public static tryGet(key: string): any {
let value = process.env[`container:${key}`];
if (value === undefined) {
value = process.env[key];
}
return value;
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,13 +36,11 @@
'use strict';
const logger = require('../../../src/logger');
import { MessageFactory } from 'botbuilder';
import { MessageFactory, RecognizerResult } from 'botbuilder';
import { LuisRecognizer } from 'botbuilder-ai';
import { GBMinInstance, IGBConversationalService } from 'botlib';
import { GBDialogStep, GBLog, GBMinInstance, IGBConversationalService, IGBCoreService } from 'botlib';
import { AzureText } from 'pragmatismo-io-framework';
import { Messages } from '../strings';
import { GBCoreService } from './GBCoreService';
const Nexmo = require('nexmo');
export interface LanguagePickerSettings {
@ -50,18 +48,22 @@ export interface LanguagePickerSettings {
supportedLocales?: string[];
}
/**
* Provides basic services for handling messages and dispatching to back-end
* services like NLP or Search.
*/
export class GBConversationalService implements IGBConversationalService {
public coreService: GBCoreService;
public coreService: IGBCoreService;
constructor(coreService: GBCoreService) {
constructor(coreService: IGBCoreService) {
this.coreService = coreService;
}
public getCurrentLanguage(step: any) {
public getCurrentLanguage(step: GBDialogStep) {
return step.context.activity.locale;
}
public async sendEvent(step: any, name: string, value: any): Promise<any> {
public async sendEvent(step: GBDialogStep, name: string, value: Object): Promise<any> {
if (step.context.activity.channelId === 'webchat') {
const msg = MessageFactory.text('');
msg.value = value;
@ -72,6 +74,7 @@ export class GBConversationalService implements IGBConversationalService {
}
}
// tslint:disable:no-unsafe-any due to Nexmo.
public async sendSms(min: GBMinInstance, mobile: string, text: string): Promise<any> {
return new Promise(
(resolve: any, reject: any): any => {
@ -79,6 +82,7 @@ export class GBConversationalService implements IGBConversationalService {
apiKey: min.instance.smsKey,
apiSecret: min.instance.smsSecret
});
// tslint:disable-next-line:no-unsafe-any
nexmo.message.sendSms(min.instance.smsServiceNumber, mobile, text, (err, data) => {
if (err) {
reject(err);
@ -89,11 +93,12 @@ export class GBConversationalService implements IGBConversationalService {
}
);
}
// tslint:enable:no-unsafe-any
public async routeNLP(step: any, min: GBMinInstance, text: string): Promise<boolean> {
public async routeNLP(step: GBDialogStep, min: GBMinInstance, text: string): Promise<boolean> {
// Invokes LUIS.
let endpoint = min.instance.nlpEndpoint.replace('/luis/v2.0', '');
const endpoint = min.instance.nlpEndpoint.replace('/luis/v2.0', '');
const model = new LuisRecognizer({
applicationId: min.instance.nlpAppId,
@ -101,38 +106,42 @@ export class GBConversationalService implements IGBConversationalService {
endpoint: endpoint
});
let nlp: any;
let nlp: RecognizerResult;
try {
nlp = await model.recognize(step.context);
} catch (error) {
// tslint:disable:no-unsafe-any
if (error.statusCode === 404) {
logger.warn('NLP application still not publish and there are no other options for answering.');
GBLog.warn('NLP application still not publish and there are no other options for answering.');
return Promise.resolve(false);
} else {
const msg = `Error calling NLP, check if you have a published model and assigned keys. Error: ${
error.statusCode ? error.statusCode : ''
} ${error.message}`;
} {error.message; }`;
return Promise.reject(new Error(msg));
}
// tslint:enable:no-unsafe-any
}
// Resolves intents returned from LUIS.
const topIntent = LuisRecognizer.topIntent(nlp);
if (topIntent) {
if (topIntent !== undefined) {
const intent = topIntent;
const entity = nlp.entities && nlp.entities.length > 0 ? nlp.entities[0].entity.toUpperCase() : null;
// tslint:disable:no-unsafe-any
const firstEntity = nlp.entities && nlp.entities.length > 0 ? nlp.entities[0].entity.toUpperCase() : undefined;
// tslint:ensable:no-unsafe-any
if (intent === 'None') {
return Promise.resolve(false);
}
logger.info(`NLP called: ${intent}, ${entity}`);
GBLog.info(`NLP called: ${intent} ${firstEntity}`);
try {
await step.replaceDialog(`/${intent}`, nlp.entities);
await step.replaceDialog(` /${intent}`, nlp.entities);
return Promise.resolve(true);
} catch (error) {
@ -145,7 +154,7 @@ export class GBConversationalService implements IGBConversationalService {
return Promise.resolve(false);
}
public async checkLanguage(step, min, text) {
public async checkLanguage(step: GBDialogStep, min, text) {
const locale = await AzureText.getLocale(min.instance.textAnalyticsKey, min.instance.textAnalyticsEndpoint, text);
if (locale !== step.context.activity.locale.split('-')[0]) {
switch (locale) {
@ -158,7 +167,7 @@ export class GBConversationalService implements IGBConversationalService {
await step.context.sendActivity(Messages[locale].changing_language);
break;
default:
await step.context.sendActivity(`Unknown language: ${locale}`);
await step.context.sendActivity(`; Unknown; language: $;{locale;}`);
break;
}
}

View file

@ -36,13 +36,13 @@
'use strict';
import { IGBCoreService, IGBInstance, IGBPackage } from 'botlib';
import { GBLog, IGBCoreService, IGBInstallationDeployer, IGBInstance, IGBPackage } from 'botlib';
import * as fs from 'fs';
import { Sequelize } from 'sequelize-typescript';
import { GBAdminPackage } from '../../admin.gbapp/index';
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService';
import { GBAnalyticsPackage } from '../../analytics.gblib';
import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService';
import { StartDialog } from '../../azuredeployer.gbapp/dialogs/StartDialog';
import { GBCorePackage } from '../../core.gbapp';
import { GBCustomerSatisfactionPackage } from '../../customer-satisfaction.gbapp';
import { GBKBPackage } from '../../kb.gbapp';
@ -50,10 +50,7 @@ import { GBSecurityPackage } from '../../security.gblib';
import { GBWhatsappPackage } from '../../whatsapp.gblib/index';
import { GuaribasInstance } from '../models/GBModel';
import { GBConfigService } from './GBConfigService';
import { StartDialog } from '../../azuredeployer.gbapp/dialogs/StartDialog';
import { WaterfallDialog } from 'botbuilder-dialogs';
const logger = require('../../../src/logger');
const opn = require('opn');
/**
@ -121,11 +118,11 @@ export class GBCoreService implements IGBCoreService {
throw new Error(`Unknown dialect: ${this.dialect}.`);
}
const logging: any =
const logging: boolean | Function =
GBConfigService.get('STORAGE_LOGGING') === 'true'
? (str: string): void => {
logger.info(str);
}
GBLog.info(str);
}
: false;
const encrypt: boolean = GBConfigService.get('STORAGE_ENCRYPT') === 'true';
@ -140,9 +137,10 @@ export class GBCoreService implements IGBCoreService {
dialect: this.dialect,
storage: storage,
dialectOptions: {
encrypt: encrypt
},
pool: {
options: {
encrypt: encrypt
}
}, pool: {
max: 32,
min: 8,
idle: 40000,
@ -153,32 +151,36 @@ export class GBCoreService implements IGBCoreService {
if (this.dialect === 'mssql') {
this.queryGenerator = this.sequelize.getQueryInterface().QueryGenerator;
// tslint:disable:no-unsafe-any
this.createTableQuery = this.queryGenerator.createTableQuery;
this.queryGenerator.createTableQuery = (tableName, attributes, options) =>
this.createTableQueryOverride(tableName, attributes, options);
this.changeColumnQuery = this.queryGenerator.changeColumnQuery;
this.queryGenerator.changeColumnQuery = (tableName, attributes) =>
this.changeColumnQueryOverride(tableName, attributes);
// tslint:enable:no-unsafe-any
}
}
public async checkStorage(azureDeployer: AzureDeployerService) {
public async checkStorage(installationDeployer: IGBInstallationDeployer) {
try {
await this.sequelize.authenticate();
} catch (error) {
logger.info('Opening storage firewall on infrastructure...');
GBLog.info('Opening storage firewall on infrastructure...');
// tslint:disable:no-unsafe-any
if (error.parent.code === 'ELOGIN') {
await this.openStorageFrontier(azureDeployer);
await this.openStorageFrontier(installationDeployer);
} else {
throw error;
}
// tslint:ensable:no-unsafe-any
}
}
public async syncDatabaseStructure() {
if (GBConfigService.get('STORAGE_SYNC') === 'true') {
const alter = GBConfigService.get('STORAGE_SYNC_ALTER') === 'true';
logger.info('Syncing database...');
GBLog.info('Syncing database...');
return this.sequelize.sync({
alter: alter,
@ -186,21 +188,21 @@ export class GBCoreService implements IGBCoreService {
});
} else {
const msg = `Database synchronization is disabled.`;
logger.info(msg);
GBLog.info(msg);
}
}
/**
* Loads all items to start several listeners.
*/
public async loadInstances(): Promise<IGBInstance> {
public async loadInstances(): Promise<IGBInstance[]> {
return GuaribasInstance.findAll({});
}
/**
* Loads just one Bot instance by its internal Id.
*/
public async loadInstanceById(instanceId: string): Promise<IGBInstance> {
public async loadInstanceById(instanceId: number): Promise<IGBInstance> {
const options = { where: { instanceId: instanceId } };
return GuaribasInstance.findOne(options);
@ -240,12 +242,19 @@ STORAGE_SYNC=true
public async ensureProxy(port): Promise<string> {
try {
const ngrok = require('ngrok');
return await ngrok.connect({ port: port });
if (fs.existsSync('node_modules/ngrok/bin/ngrok.exe')) {
const ngrok = require('ngrok');
return await ngrok.connect({ port: port });
} else {
GBLog.warn('ngrok executable not found (only tested on Windows). Check installation or node_modules folder.');
return 'localhost';
}
} catch (error) {
// There are false positive from ngrok regarding to no memory, but it's just
// lack of connection.
logger.verbose(error);
GBLog.verbose(error);
throw new Error('Error connecting to remote ngrok server, please check network connection.');
}
}
@ -267,34 +276,42 @@ STORAGE_SYNC=true
* @param azureDeployer
* @param proxyAddress
*/
public async loadAllInstances(core: GBCoreService, azureDeployer: AzureDeployerService, proxyAddress: string) {
logger.info(`Loading instances from storage...`);
let instances: GuaribasInstance[];
public async loadAllInstances(
core: IGBCoreService,
installationDeployer: IGBInstallationDeployer,
proxyAddress: string
) {
GBLog.info(`Loading instances from storage...`);
let instances: IGBInstance[];
try {
instances = await core.loadInstances();
const instance = instances[0];
if (process.env.NODE_ENV === 'development') {
logger.info(`Updating bot endpoint to local reverse proxy (ngrok)...`);
await AzureDeployerService.updateBotProxy(
GBLog.info(`Updating bot endpoint to local reverse proxy (ngrok)...`);
await installationDeployer.updateBotProxy(
instance.botId,
instance.botId,
`${proxyAddress}/api/messages/${instance.botId}`
);
}
} catch (error) {
// Check if storage is empty and needs formatting.
const isInvalidObject = error.parent.number == 208 || error.parent.errno == 1; // MSSQL or SQLITE.
if (isInvalidObject) {
if (GBConfigService.get('STORAGE_SYNC') != 'true') {
throw new Error(
`Operating storage is out of sync or there is a storage connection error.
Try setting STORAGE_SYNC to true in .env file. Error: ${error.message}.`
);
} else {
logger.info(`Storage is empty. After collecting storage structure from all .gbapps it will get synced.`);
}
} else {
if (error.parent === undefined) {
throw new Error(`Cannot connect to operating storage: ${error.message}.`);
} else {
// Check if storage is empty and needs formatting.
const isInvalidObject = error.parent.number === 208 || error.parent.errno === 1; // MSSQL or SQLITE.
if (isInvalidObject) {
if (GBConfigService.get('STORAGE_SYNC') !== 'true') {
throw new Error(
`Operating storage is out of sync or there is a storage connection error.
Try setting STORAGE_SYNC to true in .env file. Error: ${error.message}.`
);
} else {
GBLog.info(`Storage is empty. After collecting storage structure from all .gbapps it will get synced.`);
}
} else {
throw new Error(`Cannot connect to operating storage: ${error.message}.`);
}
}
}
@ -308,9 +325,9 @@ STORAGE_SYNC=true
* @param bootInstance
* @param core
*/
public async ensureInstances(instances: GuaribasInstance[], bootInstance: any, core: GBCoreService) {
if (!instances) {
const instance: IGBInstance = {};
public async ensureInstances(instances: IGBInstance[], bootInstance: any, core: IGBCoreService) {
if (instances === undefined) {
const instance = new GuaribasInstance();
await instance.save();
instances = await core.loadInstances();
}
@ -329,10 +346,10 @@ STORAGE_SYNC=true
GBCorePackage,
GBSecurityPackage,
GBKBPackage,
GBCustomerSatisfactionPackage,
GBWhatsappPackage
GBCustomerSatisfactionPackage
// GBWhatsappPackage
].forEach(e => {
logger.info(`Loading sys package: ${e.name}...`);
GBLog.info(`Loading sys package: ${e.name}...`);
const p = Object.create(e.prototype) as IGBPackage;
p.loadPackage(core, core.sequelize);
});
@ -347,18 +364,27 @@ STORAGE_SYNC=true
}
}
public async createBootInstance(core: GBCoreService, azureDeployer: AzureDeployerService, proxyAddress: string) {
logger.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`);
public async createBootInstance(
core: GBCoreService,
installationDeployer: IGBInstallationDeployer,
proxyAddress: string
) {
GBLog.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`);
try {
let { instance, credentials, subscriptionId } = await StartDialog.createBaseInstance();
instance = await azureDeployer.deployFarm(proxyAddress, instance, credentials, subscriptionId);
core.writeEnv(instance);
logger.info(`File .env written, starting General Bots...`);
const { instance, credentials, subscriptionId } = await StartDialog.createBaseInstance(installationDeployer);
const changedInstance = await installationDeployer.deployFarm(
proxyAddress,
instance,
credentials,
subscriptionId
);
core.writeEnv(changedInstance);
GBLog.info(`File .env written, starting General Bots...`);
GBConfigService.init();
return instance;
return changedInstance;
} catch (error) {
logger.warn(
GBLog.warn(
`In case of error, please cleanup any infrastructure objects
created during this procedure and .env before running again.`
);
@ -391,13 +417,13 @@ STORAGE_SYNC=true
let sql: string = this.createTableQuery.apply(this.queryGenerator, [tableName, attributes, options]);
const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/;
const matches = re1.exec(sql);
if (matches) {
if (matches !== null) {
const table = matches[1];
const re2 = /PRIMARY\s+KEY\s+\(\[[^\]]*\](?:,\s*\[[^\]]*\])*\)/;
sql = sql.replace(
re2,
(match: string, ...args: any[]): string => {
return 'CONSTRAINT [' + table + '_pk] ' + match;
return `CONSTRAINT [${table}_pk] ${match}`;
}
);
const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g;
@ -407,15 +433,17 @@ STORAGE_SYNC=true
(match: string, ...args: any[]): string => {
const fkcols = args[0];
let fkname = table;
let matches = re4.exec(fkcols);
while (matches != undefined) {
fkname += '_' + matches[1];
matches = re4.exec(fkcols);
let matches2 = re4.exec(fkcols);
while (matches2 !== null) {
fkname += `_${matches2[1]}`;
matches2 = re4.exec(fkcols);
}
return 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')';
return `CONSTRAINT [${fkname}_fk] FOREIGN KEY (${fkcols})`;
}
);
}
return sql;
}
@ -431,7 +459,7 @@ STORAGE_SYNC=true
let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [tableName, attributes]);
const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/;
const matches = re1.exec(sql);
if (matches) {
if (matches !== null) {
const table = matches[1];
const re2 = /(ADD\s+)?CONSTRAINT\s+\[([^\]]*)\]\s+FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g;
const re3 = /\[([^\]]*)\]/g;
@ -440,15 +468,17 @@ STORAGE_SYNC=true
(match: string, ...args: any[]): string => {
const fkcols = args[2];
let fkname = table;
let matches = re3.exec(fkcols);
while (matches != undefined) {
fkname += '_' + matches[1];
matches = re3.exec(fkcols);
let matches2 = re3.exec(fkcols);
while (matches2 !== null) {
fkname += `_${matches2[1]}`;
matches2 = re3.exec(fkcols);
}
return (args[0] ? args[0] : '') + 'CONSTRAINT [' + fkname + '_fk] FOREIGN KEY (' + fkcols + ')';
return `${args[0] ? args[0] : ''}CONSTRAINT [${fkname}_fk] FOREIGN KEY (${fkcols})`;
}
);
}
return sql;
}
@ -457,9 +487,9 @@ STORAGE_SYNC=true
*
* @param azureDeployer Infrastructure Deployer instance.
*/
private async openStorageFrontier(deployer: AzureDeployerService) {
private async openStorageFrontier(installationDeployer: IGBInstallationDeployer) {
const group = GBConfigService.get('CLOUD_GROUP');
const serverName = GBConfigService.get('STORAGE_SERVER').split('.database.windows.net')[0];
await AzureDeployerService.openStorageFirewall(group, serverName);
await installationDeployer.openStorageFirewall(group, serverName);
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,24 +36,20 @@
'use strict';
const logger = require('../../../src/logger');
const Path = require('path');
const UrlJoin = require('url-join');
import urlJoin = require('url-join');
const Fs = require('fs');
const WaitUntil = require('wait-until');
const express = require('express');
const child_process = require('child_process');
const graph = require('@microsoft/microsoft-graph-client');
import { GBMinInstance, IGBCoreService, IGBInstance } from 'botlib';
import { GBError, IGBPackage } from 'botlib';
import { GBError, GBLog, GBMinInstance, IGBCoreService, IGBInstance, IGBPackage } from 'botlib';
import { AzureSearch } from 'pragmatismo-io-framework';
import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService';
import { GuaribasInstance, GuaribasPackage } from '../models/GBModel';
import { GuaribasPackage } from '../models/GBModel';
import { GBAdminService } from './../../admin.gbapp/services/GBAdminService';
import { KBService } from './../../kb.gbapp/services/KBService';
import { GBConfigService } from './GBConfigService';
import { GBCoreService } from './GBCoreService';
import { GBImporter } from './GBImporterService';
import { GBVMService } from './GBVMService';
@ -73,8 +69,8 @@ export class GBDeployer {
this.importer = importer;
}
public static getConnectionStringFromInstance(instance: GuaribasInstance) {
return `Server=tcp:${instance.storageServer}.database.windows.net,1433;Database=${instance.storageName};User ID=${
public static getConnectionStringFromInstance(instance: IGBInstance) {
return `Server=tcp:torageServer}.database.windows.net,1433;Database=${instance.storageName};User ID=${
instance.storageUsername
};Password=${instance.storagePassword};Trusted_Connection=False;Encrypt=True;Connection Timeout=30;`;
}
@ -92,12 +88,12 @@ export class GBDeployer {
let totalPackages = 0;
const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH');
let paths = [GBDeployer.deployFolder];
if (additionalPath) {
if (additionalPath !== undefined && additionalPath !== '') {
paths = paths.concat(additionalPath.toLowerCase().split(';'));
}
const botPackages = new Array<string>();
const gbappPackages = new Array<string>();
let generalPackages = new Array<string>();
const botPackages: string[] = [];
const gbappPackages: string[] = [];
let generalPackages: string[] = [];
function doIt(path) {
const isDirectory = source => Fs.lstatSync(source).isDirectory();
@ -109,7 +105,7 @@ export class GBDeployer {
const dirs = getDirectories(path);
dirs.forEach(element => {
if (element.startsWith('.')) {
logger.info(`Ignoring ${element}...`);
GBLog.info(`Ignoring ${element}...`);
} else {
if (element.endsWith('.gbot')) {
botPackages.push(element);
@ -122,9 +118,9 @@ export class GBDeployer {
});
}
logger.info(`Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...`);
GBLog.info(`Starting looking for packages (.gbot, .gbtheme, .gbkb, .gbapp)...`);
paths.forEach(e => {
logger.info(`Looking in: ${e}...`);
GBLog.info(`Looking in: ${e}...`);
doIt(e);
});
@ -136,11 +132,11 @@ export class GBDeployer {
.interval(1000)
.times(10)
.condition(cb => {
logger.info(`Waiting for app package deployment...`);
GBLog.info(`Waiting for app package deployment...`);
cb(appPackagesProcessed === gbappPackages.length);
})
.done(async result => {
logger.info(`App Package deployment done.`);
.done(async () => {
GBLog.info(`App Package deployment done.`);
({ generalPackages, totalPackages } = await this.deployDataPackages(
core,
@ -162,11 +158,9 @@ export class GBDeployer {
*/
public async deployBot(localPath: string): Promise<IGBInstance> {
const packageType = Path.extname(localPath);
const packageName = Path.basename(localPath);
const instance = await this.importer.importIfNotExistsBotPackage(null, packageName, localPath);
return instance;
return await this.importer.importIfNotExistsBotPackage(undefined, packageName, localPath);
}
public async deployPackageToStorage(instanceId: number, packageName: string): Promise<GuaribasPackage> {
@ -176,7 +170,7 @@ export class GBDeployer {
});
}
public async deployFromSharePoint(instanceId: number, path: string) {
public async deployFromSharePoint(instanceId: number) {
const adminService = new GBAdminService(this.core);
const accessToken = adminService.acquireElevatedToken(instanceId);
@ -184,57 +178,30 @@ export class GBDeployer {
const client = graph.Client.init({
authProvider: done => {
done(null, accessToken);
done(undefined, accessToken);
}
});
const events = await client
.api('/me/events')
.select('subject,organizer,start,end')
.orderby('createdDateTime DESC')
.get();
}
public deployScriptToStorage(instanceId: number, localPath: string) {}
public deployTheme(localPath: string) {
// DISABLED: Until completed, "/ui/public".
// FsExtra.copy(localPath, this.workDir + packageName)
// .then(() => {
// })
// .catch(err => {
// var gberr = GBError.create(
// `GuaribasBusinessError: Error copying package: ${localPath}.`
// )
// })
}
public async deployPackageFromSharePoint(min: GBMinInstance, path: string) {}
public async deployPackageFromLocalPath(min: GBMinInstance, localPath: string) {
public async deployPackage(min: GBMinInstance, localPath: string) {
const packageType = Path.extname(localPath);
switch (packageType) {
case '.gbot':
return this.deployBot(localPath);
case '.gbtheme':
return this.deployTheme(localPath);
// PACKAGE: Put in package logic.
case '.gbkb':
const service = new KBService(this.core.sequelize);
return service.deployKb(this.core, this, localPath);
case '.gbui':
break;
return service.deployKb(this.core, this, localPath);
case '.gbdialog':
const vm = new GBVMService();
return vm.loadDialogPackage(localPath, min, this.core, this);
default:
const err = GBError.create(`GuaribasBusinessError: Unknown package type: ${packageType}.`);
const err = GBError.create(`Unhandled package type: ${packageType}.`);
Promise.reject(err);
break;
}
@ -247,16 +214,9 @@ export class GBDeployer {
const p = await this.getPackageByName(instance.instanceId, packageName);
switch (packageType) {
case '.gbot':
// TODO: this.undeployBot(packageName, localPath)
break;
case '.gbtheme':
// TODO: this.undeployTheme(packageName, localPath)
break;
case '.gbkb':
const service = new KBService(this.core.sequelize);
return service.undeployKbFromStorage(instance, this, p.packageId);
case '.gbui':
@ -266,13 +226,13 @@ export class GBDeployer {
break;
default:
const err = GBError.create(`GuaribasBusinessError: Unknown package type: ${packageType}.`);
const err = GBError.create(`Unhandled package type: ${packageType}.`);
Promise.reject(err);
break;
}
}
public async rebuildIndex(instance: GuaribasInstance) {
public async rebuildIndex(instance: IGBInstance, searchSchema: any) {
const search = new AzureSearch(
instance.searchKey,
instance.searchHost,
@ -286,7 +246,7 @@ export class GBDeployer {
try {
await search.deleteDataSource(dsName);
} catch (err) {
if (err.code != 404) {
if (err.code !== 404) {
// First time, nothing to delete.
throw err;
}
@ -297,25 +257,26 @@ export class GBDeployer {
try {
await search.deleteIndex();
} catch (err) {
if (err.code != 404) {
if (err.code !== 404) {
// First time, nothing to delete.
throw err;
}
}
await search.createIndex(AzureDeployerService.getKBSearchSchema(instance.searchIndex), dsName);
await search.createIndex(searchSchema, dsName);
}
public async getPackageByName(instanceId: number, packageName: string): Promise<GuaribasPackage> {
const where = { packageName: packageName, instanceId: instanceId };
return GuaribasPackage.findOne({
where: where
});
}
public installDefaultGBUI() {
public runOnce() {
const root = 'packages/default.gbui';
if (!Fs.existsSync(`${root}/build`)) {
logger.info(`Preparing default.gbui (it may take some additional time for the first time)...`);
GBLog.info(`Preparing default.gbui (it may take some additional time for the first time)...`);
Fs.writeFileSync(`${root}/.env`, 'SKIP_PREFLIGHT_CHECK=true');
child_process.execSync('npm install', { cwd: root });
child_process.execSync('npm run build', { cwd: root });
@ -323,7 +284,7 @@ export class GBDeployer {
}
private async deployDataPackages(
core: GBCoreService,
core: IGBCoreService,
botPackages: string[],
_this: this,
generalPackages: string[],
@ -342,9 +303,9 @@ export class GBDeployer {
botPackages.forEach(e => {
if (e !== 'packages\\boot.gbot') {
logger.info(`Deploying bot: ${e}...`);
GBLog.info(`Deploying bot: ${e}...`);
_this.deployBot(e);
logger.info(`Bot: ${e} deployed...`);
GBLog.info(`Bot: ${e} deployed...`);
}
});
@ -353,18 +314,18 @@ export class GBDeployer {
generalPackages = generalPackages.filter(p => !p.endsWith('.git'));
generalPackages.forEach(filename => {
const filenameOnly = Path.basename(filename);
logger.info(`Deploying package: ${filename}...`);
GBLog.info(`Deploying package: ${filename}...`);
// Handles apps for general bots - .gbapp must stay out of deploy folder.
if (Path.extname(filename) === '.gbapp' || Path.extname(filename) === '.gblib') {
// Themes for bots.
} else if (Path.extname(filename) === '.gbtheme') {
server.use('/themes/' + filenameOnly, express.static(filename));
logger.info(`Theme (.gbtheme) assets accessible at: ${'/themes/' + filenameOnly}.`);
server.use(`/themes/${filenameOnly}`, express.static(filename));
GBLog.info(`Theme (.gbtheme) assets accessible at: /themes/${filenameOnly}.`);
} else if (Path.extname(filename) === '.gbkb') {
server.use('/kb/' + filenameOnly + '/subjects', express.static(UrlJoin(filename, 'subjects')));
logger.info(`KB (.gbkb) assets accessible at: ${'/kb/' + filenameOnly}.`);
server.use(`/kb/${filenameOnly}/subjects`, express.static(urlJoin(filename, 'subjects')));
GBLog.info(`KB (.gbkb) assets accessible at: /kb/${filenameOnly}.`);
} else if (Path.extname(filename) === '.gbui') {
// Already Handled
} else if (Path.extname(filename) === '.gbdialog') {
@ -381,14 +342,14 @@ export class GBDeployer {
.interval(100)
.times(5)
.condition(cb => {
logger.info(`Waiting for package deployment...`);
GBLog.info(`Waiting for package deployment...`);
cb(totalPackages === generalPackages.length);
})
.done(result => {
.done(() => {
if (botPackages.length === 0) {
logger.info('Use ADDITIONAL_DEPLOY_PATH to point to a .gbai package folder (no external packages).');
GBLog.info('Use ADDITIONAL_DEPLOY_PATH to point to a .gbai package folder (no external packages).');
} else {
logger.info(`Package deployment done.`);
GBLog.info(`Package deployment done.`);
}
resolve();
});
@ -401,17 +362,17 @@ export class GBDeployer {
gbappPackages.forEach(e => {
// Skips .gbapp inside deploy folder.
if (!e.startsWith('packages')) {
logger.info(`Deploying app: ${e}...`);
GBLog.info(`Deploying app: ${e}...`);
let folder = Path.join(e, 'node_modules');
if (!Fs.existsSync(folder)) {
logger.info(`Installing modules for ${e}...`);
GBLog.info(`Installing modules for ${e}...`);
child_process.execSync('npm install', { cwd: e });
}
folder = Path.join(e, 'dist');
if (!Fs.existsSync()) {
logger.info(`Compiling ${e}...`);
GBLog.info(`Compiling ${e}...`);
try {
child_process.execSync(Path.join(e, 'node_modules/.bin/tsc'), { cwd: e });
@ -420,15 +381,15 @@ export class GBDeployer {
const p = new m.Package();
p.loadPackage(core, core.sequelize);
appPackages.push(p);
logger.info(`App (.gbapp) deployed: ${e}.`);
GBLog.info(`App (.gbapp) deployed: ${e}.`);
appPackagesProcessed++;
})
.catch(err => {
logger.error(`Error deploying .gbapp package: ${e}\n${err}`);
GBLog.error(`Error deploying .gbapp package: ${e}\n${err}`);
appPackagesProcessed++;
});
} catch (error) {
logger.error(`Error compiling .gbapp package ${e}:\n${error.stdout.toString()}`);
GBLog.error(`Error compiling .gbapp package ${e}:\n${error.stdout.toString()}`);
appPackagesProcessed++;
}
}
@ -436,6 +397,7 @@ export class GBDeployer {
appPackagesProcessed++;
}
});
return appPackagesProcessed;
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,13 +36,14 @@
'use strict';
const UrlJoin = require('url-join');
import { IGBCoreService, IGBInstance } from 'botlib';
import { IGBCoreService } from 'botlib';
import fs = require('fs');
import path = require('path');
import { SecService } from '../../security.gblib/services/SecService';
import urlJoin = require('url-join');
import { GuaribasInstance } from '../models/GBModel';
/**
* Handles the importing of packages.
*/
export class GBImporter {
public core: IGBCoreService;
@ -51,25 +52,25 @@ export class GBImporter {
}
public async importIfNotExistsBotPackage(botId: string, packageName: string, localPath: string) {
const packageJson = JSON.parse(fs.readFileSync(UrlJoin(localPath, 'package.json'), 'utf8'));
if (!botId) {
const packageJson = JSON.parse(fs.readFileSync(urlJoin(localPath, 'package.json'), 'utf8'));
if (botId === undefined) {
botId = packageJson.botId;
}
const instance = await this.core.loadInstance(botId);
if (instance) {
if (instance !== null) {
return instance;
} else {
return await this.createInstanceInternal(botId, packageName, localPath, packageJson);
return await this.createInstanceInternal(botId, localPath, packageJson);
}
}
private async createInstanceInternal(botId: string, packageName: string, localPath: string, packageJson: any) {
const settings = JSON.parse(fs.readFileSync(UrlJoin(localPath, 'settings.json'), 'utf8'));
const servicesJson = JSON.parse(fs.readFileSync(UrlJoin(localPath, 'services.json'), 'utf8'));
private async createInstanceInternal(botId: string, localPath: string, packageJson: any) {
const settings = JSON.parse(fs.readFileSync(urlJoin(localPath, 'settings.json'), 'utf8'));
const servicesJson = JSON.parse(fs.readFileSync(urlJoin(localPath, 'services.json'), 'utf8'));
packageJson = { ...packageJson, ...settings, ...servicesJson };
if (botId){
if (botId !== undefined) {
packageJson.botId = botId;
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -37,29 +37,40 @@
'use strict';
const { DialogSet, TextPrompt } = require('botbuilder-dialogs');
const UrlJoin = require('url-join');
import urlJoin = require('url-join');
const express = require('express');
const logger = require('../../../src/logger');
const request = require('request-promise-native');
const AuthenticationContext = require('adal-node').AuthenticationContext;
import { AutoSaveStateMiddleware, BotFrameworkAdapter, ConversationState, MemoryStorage, UserState } from 'botbuilder';
import { ConfirmPrompt, WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance, IGBAdminService, IGBConversationalService, IGBCoreService, IGBPackage } from 'botlib';
import {
GBDialogStep,
GBLog,
GBMinInstance,
IGBAdminService,
IGBConversationalService,
IGBCoreService,
IGBInstance,
IGBPackage
} from 'botlib';
import { GBAnalyticsPackage } from '../../analytics.gblib';
import { GBCorePackage } from '../../core.gbapp';
import { GBCustomerSatisfactionPackage } from '../../customer-satisfaction.gbapp';
import { GBKBPackage } from '../../kb.gbapp';
import { AskDialogArgs } from '../../kb.gbapp/dialogs/AskDialog';
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 {
public core: IGBCoreService;
public conversationalService: IGBConversationalService;
@ -94,19 +105,19 @@ export class GBMinService {
*
* @return Loaded minimal bot instance.
*
* */
*/
public async buildMin(
bootInstance: GuaribasInstance,
bootInstance: IGBInstance,
server: any,
appPackages: IGBPackage[],
instances: GuaribasInstance[],
instances: IGBInstance[],
deployer: GBDeployer
): Promise<GBMinInstance> {
) {
// Serves default UI on root address '/'.
const uiPackage = 'default.gbui';
server.use('/', express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')));
server.use('/', express.static(urlJoin(GBDeployer.deployFolder, uiPackage, 'build')));
await Promise.all(
instances.map(async instance => {
@ -129,7 +140,7 @@ export class GBMinService {
// Install default VBA module.
deployer.deployPackageFromLocalPath(min, 'packages/default.gbdialog');
// DISABLED: deployer.deployPackage(min, 'packages/default.gbdialog');
// Call the loadBot context.activity for all packages.
@ -141,15 +152,14 @@ export class GBMinService {
server.post(url, async (req, res) => {
await this.receiver(adapter, req, res, conversationState, min, instance, appPackages);
});
logger.info(`GeneralBots(${instance.engineName}) listening on: ${url}.`);
GBLog.info(`GeneralBots(${instance.engineName}) listening on: ${url}.`);
// Serves individual URL for each bot user interface.
const uiUrl = `/${instance.botId}`;
server.use(uiUrl, express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')));
server.use(uiUrl, express.static(urlJoin(GBDeployer.deployFolder, uiPackage, 'build')));
logger.info(`Bot UI ${uiPackage} accessible at: ${uiUrl}.`);
const state = `${instance.instanceId}${Math.floor(Math.random() * 1000000000)}`;
GBLog.info(`Bot UI ${uiPackage} accessible at: ${uiUrl}.`);
// Clients get redirected here in order to create an OAuth authorize url and redirect them to AAD.
// There they will authenticate and give their consent to allow this app access to
@ -166,34 +176,34 @@ export class GBMinService {
);
}
private handleOAuthTokenRequests(server: any, min: GBMinInstance, instance: GuaribasInstance) {
private handleOAuthTokenRequests(server: any, min: GBMinInstance, instance: IGBInstance) {
server.get(`/${min.instance.botId}/token`, async (req, res) => {
const state = await min.adminService.getValue(min.instance.instanceId, 'AntiCSRFAttackState');
const state = await min.adminService.getValue(instance.instanceId, 'AntiCSRFAttackState');
if (req.query.state !== state) {
const msg = 'WARNING: state field was not provided as anti-CSRF token';
logger.error(msg);
GBLog.error(msg);
throw new Error(msg);
}
const authenticationContext = new AuthenticationContext(
UrlJoin(min.instance.authenticatorAuthorityHostUrl, min.instance.authenticatorTenant)
urlJoin(min.instance.authenticatorAuthorityHostUrl, min.instance.authenticatorTenant)
);
const resource = 'https://graph.microsoft.com';
authenticationContext.acquireTokenWithAuthorizationCode(
req.query.code,
UrlJoin(instance.botEndpoint, min.instance.botId, '/token'),
urlJoin(instance.botEndpoint, min.instance.botId, '/token'),
resource,
instance.authenticatorClientId,
instance.authenticatorClientSecret,
async (err, token) => {
if (err) {
const msg = `Error acquiring token: ${err}`;
logger.error(msg);
GBLog.error(msg);
res.send(msg);
} else {
await this.adminService.setValue(instance.instanceId, 'refreshToken', token.refreshToken);
await this.adminService.setValue(instance.instanceId, 'accessToken', token.accessToken);
await this.adminService.setValue(instance.instanceId, 'expiresOn', token.expiresOn.toString());
await this.adminService.setValue(instance.instanceId, 'AntiCSRFAttackState', null);
this.adminService.setValue(instance.instanceId, 'refreshToken', token.refreshToken);
this.adminService.setValue(instance.instanceId, 'accessToken', token.accessToken);
this.adminService.setValue(instance.instanceId, 'expiresOn', token.expiresOn.toString());
this.adminService.setValue(instance.instanceId, 'AntiCSRFAttackState', undefined);
res.redirect(min.instance.botEndpoint);
}
}
@ -202,15 +212,15 @@ export class GBMinService {
}
private handleOAuthRequests(server: any, min: GBMinInstance) {
server.get(`/${min.instance.botId}/auth`, function(req, res) {
let authorizationUrl = UrlJoin(
server.get(`/${min.instance.botId}/auth`, (req, res) => {
let authorizationUrl = urlJoin(
min.instance.authenticatorAuthorityHostUrl,
min.instance.authenticatorTenant,
'/oauth2/authorize'
);
authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${
min.instance.authenticatorClientId
}&redirect_uri=${UrlJoin(min.instance.botEndpoint, min.instance.botId, 'token')}`;
}&redirect_uri=${urlJoin(min.instance.botEndpoint, min.instance.botId, 'token')}`;
res.redirect(authorizationUrl);
});
}
@ -218,16 +228,16 @@ export class GBMinService {
/**
* Returns the instance object to clients requesting bot info.
*/
private async sendInstanceToClient(req, bootInstance: GuaribasInstance, res: any, webchatToken: any) {
private async sendInstanceToClient(req, bootInstance: IGBInstance, res: any, webchatToken: any) {
let botId = req.params.botId;
if (botId === '[default]') {
botId = bootInstance.botId;
}
const instance = await this.core.loadInstance(botId);
if (instance) {
if (instance !== undefined) {
const speechToken = await this.getSTSToken(instance);
let theme = instance.theme;
if (!theme) {
if (theme !== undefined) {
theme = 'default.gbtheme';
}
res.send(
@ -245,7 +255,7 @@ export class GBMinService {
} else {
const error = `Instance not found: ${botId}.`;
res.sendStatus(error);
logger.error(error);
GBLog.error(error);
}
}
@ -266,11 +276,13 @@ export class GBMinService {
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));
}
}
@ -282,7 +294,6 @@ export class GBMinService {
*
*/
private 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',
@ -296,6 +307,7 @@ export class GBMinService {
return await request(options);
} catch (error) {
const msg = `Error calling Speech to Text client. Error is: ${error}.`;
return Promise.reject(new Error(msg));
}
}
@ -334,7 +346,7 @@ export class GBMinService {
}
private invokeLoadBot(appPackages: any[], min: GBMinInstance, server: any) {
const sysPackages = new Array<IGBPackage>();
const sysPackages : IGBPackage[] = [];
// NOTE: A semicolon is necessary before this line.
[
GBCorePackage,
@ -342,8 +354,8 @@ export class GBMinService {
GBAdminPackage,
GBKBPackage,
GBAnalyticsPackage,
GBCustomerSatisfactionPackage,
GBWhatsappPackage
GBCustomerSatisfactionPackage
// DISABLED: GBWhatsappPackage
].forEach(sysPackage => {
const p = Object.create(sysPackage.prototype) as IGBPackage;
p.loadBot(min);
@ -351,21 +363,21 @@ export class GBMinService {
if (sysPackage.name === 'GBWhatsappPackage') {
const url = '/instances/:botId/whatsapp';
server.post(url, (req, res) => {
p.channel.received(req, res);
(p as any).channel.received(req, res);
});
}
}, this);
}, this);
appPackages.forEach(p => {
p.sysPackages = sysPackages;
p.loadBot(min);
if (p.getDialogs !== undefined) {
let dialogs = p.getDialogs(min);
const dialogs = p.getDialogs(min);
dialogs.forEach(dialog => {
min.dialogs.add(new WaterfallDialog(dialog.name, dialog.waterfall));
});
}
}, this);
}, this);
}
/**
@ -382,9 +394,8 @@ export class GBMinService {
) {
await adapter.processActivity(req, res, async context => {
// Get loaded user state
const state = await conversationState.get(context);
const step = await min.dialogs.createContext(context, state);
step.context.activity.locale = 'en-US'; // TODO: Make dynamic.
const step = await min.dialogs.createContext(context);
step.context.activity.locale = 'en-US';
try {
const user = await min.userProfile.get(context, {});
@ -398,11 +409,11 @@ export class GBMinService {
});
user.loaded = true;
user.subjects = [];
user.cb = null;
user.cb = undefined;
await min.userProfile.set(step.context, user);
}
logger.info(
GBLog.info(
`User>: ${context.activity.text} (${context.activity.type}, ${context.activity.name}, ${
context.activity.channelId
}, {context.activity.value})`
@ -410,7 +421,7 @@ export class GBMinService {
if (context.activity.type === 'conversationUpdate' && context.activity.membersAdded.length > 0) {
const member = context.activity.membersAdded[0];
if (member.name === 'GeneralBots') {
logger.info(`Bot added to conversation, starting chat...`);
GBLog.info(`Bot added to conversation, starting chat...`);
appPackages.forEach(e => {
e.onNewSession(min, step);
});
@ -418,7 +429,7 @@ export class GBMinService {
await step.beginDialog('/');
} else {
logger.info(`Member added to conversation: ${member.name}`);
GBLog.info(`Member added to conversation: ${member.name}`);
}
// Processes messages.
@ -430,14 +441,12 @@ export class GBMinService {
} else if (context.activity.type === 'event') {
// Empties dialog stack before going to the target.
// TODO: Understand MSFT changes: await step.endAll();
await this.processEventActivity(context, step);
}
await conversationState.saveChanges(context, true);
} catch (error) {
const msg = `ERROR: ${error.message} ${error.stack ? error.stack : ''}`;
logger.error(msg);
GBLog.error(msg);
await step.context.sendActivity(Messages[step.context.activity.locale].very_sorry_about_error);
await step.beginDialog('/ask', { isReturning: true });
@ -445,11 +454,11 @@ export class GBMinService {
});
}
private async processEventActivity(context, step: any) {
private async processEventActivity(context, step: GBDialogStep) {
if (context.activity.name === 'whoAmI') {
await step.beginDialog('/whoAmI');
} else if (context.activity.name === 'showSubjects') {
await step.beginDialog('/menu');
await step.beginDialog('/menu', undefined);
} else if (context.activity.name === 'giveFeedback') {
await step.beginDialog('/feedback', {
fromMenu: true
@ -457,7 +466,7 @@ export class GBMinService {
} else if (context.activity.name === 'showFAQ') {
await step.beginDialog('/faq');
} else if (context.activity.name === 'answerEvent') {
await step.beginDialog('/answerEvent', {
await step.beginDialog('/answerEvent', <AskDialogArgs>{
questionId: context.activity.data,
fromFaq: true
});
@ -473,30 +482,28 @@ export class GBMinService {
}
}
private async processMessageActivity(context, min: GBMinInstance, step: any) {
private async processMessageActivity(context, min: GBMinInstance, step: GBDialogStep) {
// Direct script invoking by itent name.
let isVMCall = Object.keys(min.scriptMap).find(key => min.scriptMap[key] === context.activity.text) !== undefined;
const isVMCall = Object.keys(min.scriptMap).find(key => min.scriptMap[key] === context.activity.text) !== undefined;
if (isVMCall) {
let mainMethod = context.activity.text;
const mainMethod = context.activity.text;
min.sandbox.context = context;
min.sandbox.step = step;
min.sandbox[mainMethod].bind(min.sandbox);
await min.sandbox[mainMethod]();
min.sandBoxMap[mainMethod].context = context;
min.sandBoxMap[mainMethod].step = step;
min.sandBoxMap[mainMethod][mainMethod].bind(min.sandBoxMap[mainMethod]);
await min.sandBoxMap[mainMethod][mainMethod]();
} else if (context.activity.text === 'admin') {
await step.beginDialog('/admin');
// Checks for /menu JSON signature.
} else if (context.activity.text.startsWith('{"title"')) {
await step.beginDialog('/menu', {
data: JSON.parse(context.activity.text)
});
await step.beginDialog('/menu', JSON.parse(context.activity.text));
// Otherwise, continue to the active dialog in the stack.
} else {
const user = await min.userProfile.get(context, {});
if (step.activeDialog) {
if (step.activeDialog !== undefined) {
await step.continueDialog();
} else {
await step.beginDialog('/answer', {

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -33,18 +33,19 @@
'use strict';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance, IGBCoreService } from 'botlib';
import { GBLog, GBMinInstance, GBService, IGBCoreService } from 'botlib';
import * as fs from 'fs';
import { GBDeployer } from './GBDeployer';
import { TSCompiler } from './TSCompiler';
import DialogClass from './GBAPIService';
const walkPromise = require('walk-promise');
const logger = require('../../../src/logger');
const vm = require('vm');
const UrlJoin = require('url-join');
import urlJoin = require('url-join');
import { DialogClass } from './GBAPIService';
//tslint:disable-next-line:no-submodule-imports
const vb2ts = require('vbscript-to-typescript/dist/converter');
var beautify = require('js-beautify').js;
const beautify = require('js-beautify').js;
/**
* @fileoverview Virtualization services for emulation of BASIC.
@ -55,11 +56,13 @@ var beautify = require('js-beautify').js;
* translation and enhance classic BASIC experience.
*/
export class GBVMService implements IGBCoreService {
/**
* Basic services for BASIC manipulation.
*/
export class GBVMService extends GBService {
private readonly script = new vm.Script();
public async loadDialogPackage(folder: string, min: GBMinInstance, core: IGBCoreService, deployer: GBDeployer) {
const files = await walkPromise(folder);
this.addHearDialog(min);
@ -74,7 +77,7 @@ export class GBVMService implements IGBCoreService {
const mainName = file.name.replace(/\-|\./g, '');
min.scriptMap[file.name] = mainName;
const filename = UrlJoin(folder, file.name);
const filename = urlJoin(folder, file.name);
fs.watchFile(filename, async () => {
await this.run(filename, min, deployer, mainName);
});
@ -110,6 +113,10 @@ export class GBVMService implements IGBCoreService {
return 'let password = sys().generatePassword()';
});
code = code.replace(/(get)(\s)(.*)/g, ($0, $1, $2) => {
return `sys().httpGet (${$2})`;
});
code = code.replace(/(create a bot farm using)(\s)(.*)/g, ($0, $1, $2, $3) => {
return `sys().createABotFarmUsing (${$3})`;
});
@ -127,7 +134,7 @@ export class GBVMService implements IGBCoreService {
// Converts General Bots BASIC into regular VBS
const basicCode: string = fs.readFileSync(filename, 'utf8');
const vbsCode = await this.convertGBASICToVBS(basicCode);
const vbsCode = this.convertGBASICToVBS(basicCode);
const vbsFile = `${filename}.compiled`;
fs.writeFileSync(vbsFile, vbsCode, 'utf8');
@ -156,9 +163,9 @@ export class GBVMService implements IGBCoreService {
let parsedCode = code;
const hearExp = /(\w+).*hear.*\(\)/;
let match1;
let match1 = hearExp.exec(code);
while ((match1 = hearExp.exec(code))) {
while (match1 !== undefined) {
let pos = 0;
// Writes async body.
@ -178,8 +185,9 @@ export class GBVMService implements IGBCoreService {
let right = 0;
let left = 1;
let match2;
while ((match2 = /\{|\}/.exec(tempCode))) {
let match2 = /\{|\}/.exec(tempCode);
while (match2 !== undefined) {
const c = tempCode.substring(match2.index, match2.index + 1);
if (c === '}') {
@ -194,6 +202,7 @@ export class GBVMService implements IGBCoreService {
if (left === right) {
break;
}
match1 = hearExp.exec(code);
}
parsedCode += code.substring(start + match1[0].length + 1, pos + match1[0].length);
@ -203,19 +212,19 @@ export class GBVMService implements IGBCoreService {
// A interaction will be made for each hear.
code = parsedCode;
match2 = /\{|\}/.exec(tempCode);
}
parsedCode = this.handleThisAndAwait(parsedCode);
parsedCode = beautify(parsedCode, { indent_size: 2, space_in_empty_paren: true })
parsedCode = beautify(parsedCode, { indent_size: 2, space_in_empty_paren: true });
fs.writeFileSync(jsfile, parsedCode);
const sandbox: DialogClass = new DialogClass(min);
const sandbox: DialogClass = new DialogClass(min, deployer);
const context = vm.createContext(sandbox);
vm.runInContext(parsedCode, context);
min.sandbox = sandbox;
await deployer.deployScriptToStorage(min.instanceId, filename);
logger.info(`[GBVMService] Finished loading of ${filename}`);
min.sandBoxMap[mainName] = sandbox;
GBLog.info(`[GBVMService] Finished loading of ${filename}`);
}
}
@ -224,13 +233,13 @@ export class GBVMService implements IGBCoreService {
code = code.replace(/sys\(\)/g, 'this.sys()');
code = code.replace(/("[^"]*"|'[^']*')|\btalk\b/g, ($0, $1) => {
return $1 == undefined ? 'this.talk' : $1;
return $1 === undefined ? 'this.talk' : $1;
});
code = code.replace(/("[^"]*"|'[^']*')|\bhear\b/g, ($0, $1) => {
return $1 == undefined ? 'this.hear' : $1;
return $1 === undefined ? 'this.hear' : $1;
});
code = code.replace(/("[^"]*"|'[^']*')|\bsendEmail\b/g, ($0, $1) => {
return $1 == undefined ? 'this.sendEmail' : $1;
return $1 === undefined ? 'this.sendEmail' : $1;
});
// await insertion.
@ -245,7 +254,7 @@ export class GBVMService implements IGBCoreService {
min.dialogs.add(
new WaterfallDialog('/hear', [
async step => {
step.activeDialog.state.cbId = step.options['id'];
step.activeDialog.state.cbId = (step.options as any).id;
return await step.prompt('textPrompt', {});
},
@ -255,7 +264,7 @@ export class GBVMService implements IGBCoreService {
const cbId = step.activeDialog.state.cbId;
const cb = min.cbMap[cbId];
cb.bind({ step: step, context: step.context }); // TODO: Necessary or min.sandbox?
cb.bind({ step: step, context: step.context });
await step.endDialog();

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,12 +36,14 @@
'use strict';
import { GBLog } from 'botlib';
import * as ts from 'typescript';
const logger = require('../../../src/logger');
/**
* Wrapper for a TypeScript compiler.
*/
export class TSCompiler {
private static shouldIgnoreError(diagnostic) {
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
@ -76,11 +78,11 @@ export class TSCompiler {
if (!TSCompiler.shouldIgnoreError(diagnostic)) {
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
if (diagnostic.file) {
if (diagnostic.file !== undefined) {
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
logger.error(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
GBLog.error(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
} else {
logger.error(`${message}`);
GBLog.error(`${message}`);
}
}
});

View file

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

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -42,9 +42,7 @@ import { GBVMService } from '../services/GBVMService';
describe('Load function', () => {
it('should fail on invalid file', () => {
try {
// const service = new GBVMService();
// TODO: service.loadJS('invalid.file', null, null, null, null);
const service = new GBVMService();
} catch (error) {
expect(error).to.equal(0);
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -38,12 +38,14 @@
import { BotAdapter } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance } from 'botlib';
import { IGBDialog } from 'botlib';
import { GBMinInstance, IGBDialog } from 'botlib';
import { AzureText } from 'pragmatismo-io-framework';
import { CSService } from '../services/CSService';
import { Messages } from '../strings';
/**
* Dialog for feedback collecting.
*/
export class FeedbackDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
@ -58,13 +60,7 @@ export class FeedbackDialog extends IGBDialog {
new WaterfallDialog('/feedbackNumber', [
async step => {
const locale = step.context.activity.locale;
// TODO: Migrate to 4.*+ await step.prompt("choicePrompt", Messages[locale].what_about_me, [
// "1",
// "2",
// "3",
// "4",
// "5"
// ]);
return await step.next();
},
async step => {
@ -73,6 +69,7 @@ export class FeedbackDialog extends IGBDialog {
const user = await min.userProfile.get(context, {});
await service.updateConversationRate(user.conversation, rate);
await step.context.sendActivity(Messages[locale].thanks);
return await step.next();
}
])
@ -84,29 +81,26 @@ export class FeedbackDialog extends IGBDialog {
const locale = step.context.activity.locale;
await step.context.sendActivity(Messages[locale].about_suggestions);
step.activeDialog.state.cbId = step.options['id'];
step.activeDialog.state.cbId = (step.options as any).id;
return await step.prompt('textPrompt', Messages[locale].what_about_service);
},
async step => {
const locale = step.context.activity.locale;
const rate = await AzureText.getSentiment(
min.instance.textAnalyticsKey,
min.instance.textAnalyticsKey,
min.instance.textAnalyticsEndpoint,
min.conversationalService.getCurrentLanguage(step),
step.result
step.result
);
if (rate > 0.5) {
await step.context.sendActivity(Messages[locale].glad_you_liked);
} else {
await step.context.sendActivity(Messages[locale].we_will_improve);
}
// TODO: Record.
}
return await step.replaceDialog('/ask', { isReturning: true });
}
])
);

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,15 +36,16 @@
'use strict';
import { IGBDialog } from 'botlib';
import { GBMinInstance, IGBDialog } from 'botlib';
import { BotAdapter } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance } from 'botlib';
import { CSService } from '../services/CSService';
import { Messages } from '../strings';
const logger = require('../../../src/logger');
/**
* Dialog for collecting quality of answer.
*/
export class QualityDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
@ -63,11 +64,11 @@ export class QualityDialog extends IGBDialog {
const score = step.result;
setTimeout(
() => min.conversationalService.sendEvent(step, 'stop', null),
() => min.conversationalService.sendEvent(step, 'stop', undefined),
400
);
if (score == 0) {
if (score === 0) {
await step.context.sendActivity(Messages[locale].im_sorry_lets_try);
} else {
await step.context.sendActivity(Messages[locale].great_thanks);
@ -79,6 +80,7 @@ export class QualityDialog extends IGBDialog {
);
await step.replaceDialog('/ask', { isReturning: true });
}
return await step.next();
}
]));

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,32 +36,37 @@
'use strict';
const UrlJoin = require('url-join');
import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import urlJoin = require('url-join');
import { FeedbackDialog } from './dialogs/FeedbackDialog';
import { QualityDialog } from './dialogs/QualityDialog';
import { GuaribasQuestionAlternate } from './models/index';
import { Sequelize } from 'sequelize-typescript';
/**
* Package for customer-satisfaction.gblib.
*/
export class GBCustomerSatisfactionPackage implements IGBPackage {
public sysPackages: IGBPackage[] = null;
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
core.sequelize.addModels([
GuaribasQuestionAlternate
]);
public sysPackages: IGBPackage[];
public getDialogs(min: GBMinInstance) {
GBLog.verbose(`getDialogs called.`);
}
public unloadPackage(core: IGBCoreService): void {
GBLog.verbose(`unloadPackage called.`);
}
public unloadBot(min: GBMinInstance): void {
GBLog.verbose(`unloadBot called.`);
}
public onNewSession(min: GBMinInstance, step: GBDialogStep): void {
GBLog.verbose(`onNewSession called.`);
}
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
core.sequelize.addModels([GuaribasQuestionAlternate]);
}
public loadBot(min: GBMinInstance): void {
FeedbackDialog.setup(min.bot, min);
QualityDialog.setup(min.bot, min);
}
public unloadBot(min: GBMinInstance): void {
}
public onNewSession(min: GBMinInstance, step: any): void {
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,33 +36,20 @@
'use strict';
import {
DataTypeDate,
DataTypeDecimal,
DataTypes,
DataTypeUUIDv4
} from 'sequelize';
import {
AutoIncrement,
BelongsTo,
BelongsToMany,
Column,
CreatedAt,
DataType,
ForeignKey,
HasMany,
IsUUID,
Length,
Model,
PrimaryKey,
Sequelize,
Table,
UpdatedAt
} from 'sequelize-typescript';
Table} from 'sequelize-typescript';
import { GuaribasInstance } from '../../core.gbapp/models/GBModel';
/**
* List of saved alternate questions.
*/
@Table
export class GuaribasQuestionAlternate extends Model<GuaribasQuestionAlternate> {

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -33,6 +33,9 @@
import { GuaribasConversation } from '../../analytics.gblib/models';
import { GuaribasQuestionAlternate } from '../models';
/**
* Customer Satisfaction Service Layer.
*/
export class CSService {
public async resolveQuestionAlternate(
@ -62,6 +65,7 @@ export class CSService {
rate: number
): Promise<GuaribasConversation> {
conversation.rate = rate;
return conversation.save();
}
}

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,14 +2,14 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -38,14 +38,22 @@
import { BotAdapter } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { IGBDialog } from 'botlib';
import { GBMinInstance } from 'botlib';
import { GBLog, GBMinInstance, IGBDialog } from 'botlib';
import { AzureText } from 'pragmatismo-io-framework';
import { Messages } from '../strings';
import { KBService } from './../services/KBService';
const logger = require('../../../src/logger');
/**
* Dialog arguments.
*/
export class AskDialogArgs {
public questionId: number;
public fromFaq: boolean;
}
/**
* Handle the ask loop on knowledge base data or delegate to other services.
*/
export class AskDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
@ -55,156 +63,135 @@ export class AskDialog extends IGBDialog {
*/
public static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new KBService(min.core.sequelize);
min.dialogs.add(new WaterfallDialog('/answerEvent', AskDialog.getAnswerEventDialog(service, min)));
min.dialogs.add(new WaterfallDialog('/answer', AskDialog.getAnswerDialog(min, service)));
min.dialogs.add(new WaterfallDialog('/ask', AskDialog.getAskDialog(min)));
}
min.dialogs.add(
new WaterfallDialog('/answerEvent', [
async step => {
if (step.options && step.options['questionId']) {
const question = await service.getQuestionById(min.instance.instanceId, step.options['questionId']);
const answer = await service.getAnswerById(min.instance.instanceId, question.answerId);
private static getAskDialog(min: GBMinInstance) {
return [
async step => {
const locale = step.context.activity.locale;
const user = await min.userProfile.get(step.context, {});
user.isAsking = true;
if (!user.subjects) {
user.subjects = [];
}
let text;
// Three forms of asking.
if (step.options && step.options.firstTime) {
text = Messages[locale].ask_first_time;
} else if (step.options && step.options.isReturning) {
text = Messages[locale].anything_else;
} else if (user.subjects.length > 0) {
text = Messages[locale].which_question;
} else {
throw new Error('Invalid use of /ask');
}
if (text.length > 0) {
return await step.prompt('textPrompt', text);
}
// Sends the answer to all outputs, including projector.
await service.sendAnswer(min.conversationalService, step, answer);
await step.replaceDialog('/ask', { isReturning: true });
}
return await step.next();
},
async step => {
if (step.result) {
return await step.replaceDialog('/answer', { query: step.result });
} else {
return await step.next();
}
])
);
}
];
}
min.dialogs.add(
new WaterfallDialog('/answer', [
async step => {
const user = await min.userProfile.get(step.context, {});
let text = step.options['query'];
if (!text) {
throw new Error(`/answer being called with no args query text.`);
private static getAnswerDialog(min: GBMinInstance, service: KBService) {
return [
async step => {
const user = await min.userProfile.get(step.context, {});
let text = step.options.query;
if (!text) {
throw new Error(`/answer being called with no args query text.`);
}
const locale = step.context.activity.locale;
// Stops any content on projector.
await min.conversationalService.sendEvent(step, 'stop', undefined);
// Handle extra text from FAQ.
if (step.options && step.options.query) {
text = step.options.query;
} else if (step.options && step.options.fromFaq) {
await step.context.sendActivity(Messages[locale].going_answer);
}
// Spells check the input text before sending Search or NLP.
if (min.instance.spellcheckerKey !== undefined) {
const data = await AzureText.getSpelledText(min.instance.spellcheckerKey, text);
if (data !== text) {
GBLog.info(`Spelling corrected: ${data}`);
text = data;
}
}
const locale = step.context.activity.locale;
// Searches KB for the first time.
user.lastQuestion = text;
await min.userProfile.set(step.context, user);
const resultsA = await service.ask(min.instance, text, min.instance.searchScore, user.subjects);
// Stops any content on projector.
await min.conversationalService.sendEvent(step, 'stop', null);
// Handle extra text from FAQ.
if (step.options && step.options['query']) {
text = step.options['query'];
} else if (step.options && step.options['fromFaq']) {
await step.context.sendActivity(Messages[locale].going_answer);
}
// Spells check the input text before sending Search or NLP.
if (min.instance.spellcheckerKey) {
const data = await AzureText.getSpelledText(min.instance.spellcheckerKey, text);
if (data != text) {
logger.info(`Spelling corrected: ${data}`);
text = data;
}
}
// Searches KB for the first time.
user.lastQuestion = text;
// If there is some result, answer immediately.
if (resultsA !== undefined && resultsA.answer !== undefined) {
// Saves some context info.
user.isAsking = false;
user.lastQuestionId = resultsA.questionId;
await min.userProfile.set(step.context, user);
const resultsA = await service.ask(min.instance, text, min.instance.searchScore, user.subjects);
// Sends the answer to all outputs, including projector.
await service.sendAnswer(min.conversationalService, step, resultsA.answer);
// Goes to ask loop, again.
return await step.replaceDialog('/ask', { isReturning: true });
} else {
// Second time running Search, now with no filter.
const resultsB = await service.ask(min.instance, text, min.instance.searchScore, undefined);
// If there is some result, answer immediately.
if (resultsA && resultsA.answer) {
if (resultsB !== undefined && resultsB.answer !== undefined) {
// Saves some context info.
user.isAsking = false;
user.lastQuestionId = resultsA.questionId;
await min.userProfile.set(step.context, user);
const user2 = await min.userProfile.get(step.context, {});
user2.isAsking = false;
user2.lastQuestionId = resultsB.questionId;
await min.userProfile.set(step.context, user2);
// Informs user that a broader search will be used.
if (user2.subjects.length > 0) {
await step.context.sendActivity(Messages[locale].wider_answer);
}
// Sends the answer to all outputs, including projector.
await service.sendAnswer(min.conversationalService, step, resultsA.answer);
// Goes to ask loop, again.
await service.sendAnswer(min.conversationalService, step, resultsB.answer);
return await step.replaceDialog('/ask', { isReturning: true });
} else {
// Second time running Search, now with no filter.
if (!(await min.conversationalService.routeNLP(step, min, text))) {
await step.context.sendActivity(Messages[locale].did_not_find);
const resultsB = await service.ask(min.instance, text, min.instance.searchScore, null);
// If there is some result, answer immediately.
if (resultsB && resultsB.answer) {
// Saves some context info.
const user = await min.userProfile.get(step.context, {});
user.isAsking = false;
user.lastQuestionId = resultsB.questionId;
await min.userProfile.set(step.context, user);
// Informs user that a broader search will be used.
if (user.subjects.length > 0) {
const subjectText = `${KBService.getSubjectItemsSeparatedBySpaces(user.subjects)}`;
await step.context.sendActivity(Messages[locale].wider_answer);
}
// Sends the answer to all outputs, including projector.
await service.sendAnswer(min.conversationalService, step, resultsB.answer);
return await step.replaceDialog('/ask', { isReturning: true });
} else {
if (!(await min.conversationalService.routeNLP(step, min, text))) {
await step.context.sendActivity(Messages[locale].did_not_find);
return await step.replaceDialog('/ask', { isReturning: true });
}
}
}
}
])
);
}
];
}
min.dialogs.add(
new WaterfallDialog('/ask', [
async step => {
const locale = step.context.activity.locale;
const user = await min.userProfile.get(step.context, {});
user.isAsking = true;
if (!user.subjects) {
user.subjects = [];
}
let text;
// Three forms of asking.
if (step.options && step.options['firstTime']) {
text = Messages[locale].ask_first_time;
} else if (step.options && step.options['isReturning']) {
text = Messages[locale].anything_else;
} else if (user.subjects.length > 0) {
text = Messages[locale].which_question;
} else {
throw new Error('Invalid use of /ask');
}
if (text.length > 0) {
return await step.prompt('textPrompt', text);
}
return await step.next();
},
async step => {
if (step.result) {
return await step.replaceDialog('/answer', { query: step.result });
} else {
return await step.next();
}
private static getAnswerEventDialog(service: KBService, min: GBMinInstance) {
return [
async step => {
const data = step.options as AskDialogArgs;
if (data !== undefined && data.questionId !== undefined) {
const question = await service.getQuestionById(min.instance.instanceId, data.questionId);
const answer = await service.getAnswerById(min.instance.instanceId, question.answerId);
// Sends the answer to all outputs, including projector.
await service.sendAnswer(min.conversationalService, step, answer);
await step.replaceDialog('/ask', { isReturning: true });
}
])
);
return await step.next();
}
];
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -38,11 +38,13 @@
import { BotAdapter } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { IGBDialog } from 'botlib';
import { GBMinInstance } from 'botlib';
import { GBMinInstance, IGBDialog } from 'botlib';
import { Messages } from '../strings';
import { KBService } from './../services/KBService';
/**
* Handle display of FAQ allowing direct access to KB.
*/
export class FaqDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
@ -56,15 +58,16 @@ export class FaqDialog extends IGBDialog {
min.dialogs.add(new WaterfallDialog('/faq', [
async step => {
const data = await service.getFaqBySubjectArray('faq', null);
const data = await service.getFaqBySubjectArray('faq', undefined);
const locale = step.context.activity.locale;
if (data) {
if (data !== undefined) {
await min.conversationalService.sendEvent(step, 'play', {
playerType: 'bullet',
data: data.slice(0, 10)
});
await step.context.sendActivity(Messages[locale].see_faq); // TODO: RND messages.
await step.context.sendActivity(Messages[locale].see_faq);
return await step.next();
}
}

View file

@ -1,9 +1,8 @@
/*****************************************************************************\
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -37,17 +36,26 @@
'use strict';
const UrlJoin = require('url-join');
import urlJoin = require('url-join');
import { BotAdapter, CardFactory, MessageFactory } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { IGBDialog } from 'botlib';
import { GBMinInstance } from 'botlib';
import { AzureText } from 'pragmatismo-io-framework';
import { GBMinInstance, IGBDialog } from 'botlib';
import { GuaribasSubject } from '../models';
import { KBService } from '../services/KBService';
import { Messages } from '../strings';
/**
* Dialog arguments.
*/
export class MenuDialogArgs {
public to: string;
public subjectId: string;
}
/**
* Dialogs for handling Menu control.
*/
export class MenuDialog extends IGBDialog {
/**
* Setup dialogs flows and define services call.
@ -58,70 +66,56 @@ export class MenuDialog extends IGBDialog {
public static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new KBService(min.core.sequelize);
min.dialogs.add(new WaterfallDialog('/menu', [
min.dialogs.add(new WaterfallDialog('/menu', MenuDialog.getMenuDialog(min, service)));
}
private static getMenuDialog(min: GBMinInstance, service: KBService) {
return [
async step => {
const locale = step.context.activity.locale;
const user = await min.userProfile.get(step.context, {});
const args: MenuDialogArgs = step.options;
// tslint:disable-next-line: no-null-keyword
let rootSubjectId = null;
if (step.options && step.options['data']) {
const subject = step.options['data'];
if (Object.keys(args).length > 0) {
// If there is a shortcut specified as subject destination, go there.
if (args.to !== null) {
const dialog = args.to.split(':')[1];
if (subject.to) {
const dialog = subject.to.split(':')[1];
await step.replaceDialog('/' + dialog);
await step.endDialog();
return;
return await step.replaceDialog(`/${dialog}`);
}
// Adds to bot a perception of a new subject.
const user = await min.userProfile.get(step.context, {});
user.subjects.push(subject);
rootSubjectId = subject.subjectId;
user.subjects.push(args);
// tslint:disable-next-line: no-null-keyword
rootSubjectId = args.subjectId === undefined ? null : args.subjectId;
// Whenever a subject is selected, shows a faq about it.
if (user.subjects.length > 0) {
const data = await service.getFaqBySubjectArray(
'menu',
user.subjects
);
const list = await service.getFaqBySubjectArray('menu', user.subjects);
await min.conversationalService.sendEvent(step, 'play', {
playerType: 'bullet',
data: data.slice(0, 10)
data: list.slice(0, 10)
});
}
} else {
const user = await min.userProfile.get(step.context, {});
user.subjects = [];
await step.context.sendActivity(Messages[locale].here_is_subjects); // TODO: Handle rnd.
await step.context.sendActivity(Messages[locale].here_is_subjects);
user.isAsking = false;
}
const msg = MessageFactory.text('');
const attachments = [];
const data = await service.getSubjectItems(
min.instance.instanceId,
rootSubjectId
);
const data = await service.getSubjectItems(min.instance.instanceId, rootSubjectId);
msg.attachmentLayout = 'carousel';
data.forEach(function(item: GuaribasSubject) {
data.forEach((item: GuaribasSubject) => {
const subject = item;
const card = CardFactory.heroCard(
subject.title,
subject.description,
CardFactory.images([
UrlJoin('/kb', min.instance.kb, 'subjects', 'subject.png')
]),
CardFactory.images([urlJoin('/kb', min.instance.kb, 'subjects', 'subject.png')]),
CardFactory.actions([
{
channelData: null,
{
type: 'postBack',
title: Messages[locale].menu_select,
value: JSON.stringify({
@ -134,30 +128,23 @@ export class MenuDialog extends IGBDialog {
}
])
);
attachments.push(card);
});
if (attachments.length === 0) {
const user = await min.userProfile.get(step.context, {});
if (user.subjects && user.subjects.length > 0) {
await step.context.sendActivity(
Messages[locale].lets_search(
KBService.getFormattedSubjectItems(user.subjects)
)
Messages[locale].lets_search(KBService.getFormattedSubjectItems(user.subjects))
);
}
} else {
msg.attachments = attachments;
await step.context.sendActivity(msg);
}
const user = await min.userProfile.get(step.context, {});
user.isAsking = true;
return await step.next();
}
]));
];
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,43 +36,37 @@
'use strict';
const UrlJoin = require('url-join');
import { GBMinInstance, IGBPackage } from 'botlib';
import { GuaribasAnswer, GuaribasQuestion, GuaribasSubject } from './models/index';
import { IGBCoreService } from 'botlib';
import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { Sequelize } from 'sequelize-typescript';
import { AskDialog } from './dialogs/AskDialog';
import { FaqDialog } from './dialogs/FaqDialog';
import { MenuDialog } from './dialogs/MenuDialog';
import { GuaribasAnswer, GuaribasQuestion, GuaribasSubject } from './models/index';
/**
* Package for kb.gbapp.
*/
export class GBKBPackage implements IGBPackage {
public sysPackages: IGBPackage[] = null;
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
core.sequelize.addModels([
GuaribasAnswer,
GuaribasQuestion,
GuaribasSubject
]);
public sysPackages: IGBPackage[];
public getDialogs(min: GBMinInstance) {
GBLog.verbose(`getDialogs called.`);
}
public unloadPackage(core: IGBCoreService): void {
GBLog.verbose(`unloadPackage called.`);
}
public unloadBot(min: GBMinInstance): void {
GBLog.verbose(`unloadBot called.`);
}
public onNewSession(min: GBMinInstance, step: GBDialogStep): void {
GBLog.verbose(`onNewSession called.`);
}
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
core.sequelize.addModels([GuaribasAnswer, GuaribasQuestion, GuaribasSubject]);
}
public loadBot(min: GBMinInstance): void {
AskDialog.setup(min.bot, min);
FaqDialog.setup(min.bot, min);
MenuDialog.setup(min.bot, min);
}
public unloadBot(min: GBMinInstance): void {
}
public onNewSession(min: GBMinInstance, step: any): void {
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -60,6 +60,9 @@ import {
} from '../../core.gbapp/models/GBModel';
import { GuaribasUser } from '../../security.gblib/models';
/**
* Subjects to group the pair of questions and answers.
*/
@Table
export class GuaribasSubject extends Model<GuaribasSubject> {
@PrimaryKey
@ -111,6 +114,9 @@ export class GuaribasSubject extends Model<GuaribasSubject> {
public package: GuaribasPackage;
}
/**
* A question and its metadata.
*/
@Table
export class GuaribasQuestion extends Model<GuaribasQuestion> {
@PrimaryKey
@ -155,6 +161,7 @@ export class GuaribasQuestion extends Model<GuaribasQuestion> {
@UpdatedAt
public updatedAt: Date;
//tslint:disable-next-line:no-use-before-declare
@ForeignKey(() => GuaribasAnswer)
@Column
public answerId: number;
@ -174,6 +181,9 @@ export class GuaribasQuestion extends Model<GuaribasQuestion> {
public package: GuaribasPackage;
}
/**
* An answer and its metadata.
*/
@Table
export class GuaribasAnswer extends Model<GuaribasAnswer> {
@PrimaryKey

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -34,31 +34,37 @@
* @fileoverview Knowledge base services and logic.
*/
const logger = require('../../../src/logger');
const Path = require('path');
const Fs = require('fs');
const parse = require('bluebird').promisify(require('csv-parse'));
const UrlJoin = require('url-join');
import urlJoin = require('url-join');
const marked = require('marked');
const path = require('path');
const asyncPromise = require('async-promises');
const walkPromise = require('walk-promise');
import { Messages } from '../strings';
// tslint:disable-next-line:newline-per-chained-call
const parse = require('bluebird').promisify(require('csv-parse'));
import { IGBConversationalService, IGBCoreService, IGBInstance } from 'botlib';
import { GBDialogStep, GBLog, IGBConversationalService, IGBCoreService, IGBInstance } from 'botlib';
import { AzureSearch } from 'pragmatismo-io-framework';
import { Sequelize } from 'sequelize-typescript';
import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService';
import { GuaribasPackage } from '../../core.gbapp/models/GBModel';
import { GBDeployer } from '../../core.gbapp/services/GBDeployer';
import { GuaribasAnswer, GuaribasQuestion, GuaribasSubject } from '../models';
import { Messages } from '../strings';
import { GBConfigService } from './../../core.gbapp/services/GBConfigService';
/**
* Result for quey on KB data.
*/
export class KBServiceSearchResults {
public answer: GuaribasAnswer;
public questionId: number;
}
/**
* All services related to knowledge base management.
*/
export class KBService {
public sequelize: Sequelize;
@ -67,7 +73,7 @@ export class KBService {
}
public static getFormattedSubjectItems(subjects: GuaribasSubject[]) {
if (!subjects) {
if (subjects === null) {
return '';
}
const out = [];
@ -115,7 +121,7 @@ export class KBService {
}
});
if (question) {
if (question !== null) {
const answer = await GuaribasAnswer.findOne({
where: {
instanceId: instanceId,
@ -126,7 +132,7 @@ export class KBService {
return Promise.resolve({ question: question, answer: answer });
}
return Promise.resolve(null);
return Promise.resolve(undefined);
}
public async addAnswer(obj: GuaribasAnswer): Promise<GuaribasAnswer> {
@ -156,40 +162,39 @@ export class KBService {
query = query.replace('/', ' ');
query = query.replace('\\', ' ');
if (subjects) {
if (subjects !== null) {
const text = KBService.getSubjectItemsSeparatedBySpaces(subjects);
if (text) {
if (text !== null) {
query = `${query} ${text}`;
}
}
query = `${query}&$filter=instanceId eq ${instance.instanceId}`;
try {
if (instance.searchKey && GBConfigService.get('STORAGE_DIALECT') === 'mssql') {
const service = new AzureSearch(
instance.searchKey,
instance.searchHost,
instance.searchIndex,
instance.searchIndexer
);
const results = await service.search(query);
if (results && results.length > 0 && results[0]['@search.score'] >= searchScore) {
const value = await this.getAnswerById(instance.instanceId, results[0].answerId);
if (value) {
return Promise.resolve({ answer: value, questionId: results[0].questionId });
} else {
return Promise.resolve({ answer: null, questionId: 0 });
}
}
} else {
const data = await this.getAnswerByText(instance.instanceId, query);
if (data) {
return Promise.resolve({ answer: data.answer, questionId: data.question.questionId });
// tslint:disable:no-unsafe-any
if (instance.searchKey !== null && GBConfigService.get('STORAGE_DIALECT') === 'mssql') {
const service = new AzureSearch(
instance.searchKey,
instance.searchHost,
instance.searchIndex,
instance.searchIndexer
);
const results = await service.search(query);
if (results && results.length > 0 && results[0]['@search.score'] >= searchScore) {
const value = await this.getAnswerById(instance.instanceId, results[0].answerId);
if (value !== null) {
return Promise.resolve({ answer: value, questionId: results[0].questionId });
} else {
return Promise.resolve({ answer: null, questionId: 0 });
return Promise.resolve({ answer: undefined, questionId: 0 });
}
}
} catch (reason) {
return Promise.reject(new Error(reason));
} else {
const data = await this.getAnswerByText(instance.instanceId, query);
if (data) {
return Promise.resolve({ answer: data.answer, questionId: data.question.questionId });
} else {
return Promise.resolve({ answer: undefined, questionId: 0 });
}
}
}
@ -202,29 +207,32 @@ export class KBService {
}
public async getFaqBySubjectArray(from: string, subjects: any): Promise<GuaribasQuestion[]> {
if (subjects) {
const where = {
from: from,
// tslint:disable-next-line: no-null-keyword
subject1: null,
// tslint:disable-next-line: no-null-keyword
subject2: null,
// tslint:disable-next-line: no-null-keyword
subject3: null,
// tslint:disable-next-line: no-null-keyword
subject4: null
};
if (subjects[0]) {
if (subjects[0] && subjects[0].internalId) {
where.subject1 = subjects[0].internalId;
}
if (subjects[1]) {
if (subjects[1] && subjects[1].internalId) {
where.subject2 = subjects[1].internalId;
}
if (subjects[2]) {
if (subjects[2] && subjects[2].internalId) {
where.subject3 = subjects[2].internalId;
}
if (subjects[3]) {
if (subjects[3] && subjects[3].internalId) {
where.subject4 = subjects[3].internalId;
}
@ -270,12 +278,12 @@ export class KBService {
// Extracts answer from external media if any.
if (answer.indexOf('.md') > -1) {
const mediaFilename = UrlJoin(path.dirname(filePath), '..', 'articles', answer);
const mediaFilename = urlJoin(path.dirname(filePath), '..', 'articles', answer);
if (Fs.existsSync(mediaFilename)) {
answer = Fs.readFileSync(mediaFilename, 'utf8');
format = '.md';
} else {
logger.info(`[GBImporter] File not found: ${mediaFilename}.`);
GBLog.info(`[GBImporter] File not found: ${mediaFilename}.`);
answer = '';
}
}
@ -290,13 +298,13 @@ export class KBService {
let indexer = 0;
subjectArray.forEach(element => {
if (indexer == 0) {
if (indexer === 0) {
subject1 = subjectArray[indexer].substring(0, 63);
} else if (indexer == 1) {
} else if (indexer === 1) {
subject2 = subjectArray[indexer].substring(0, 63);
} else if (indexer == 2) {
} else if (indexer === 2) {
subject3 = subjectArray[indexer].substring(0, 63);
} else if (indexer == 3) {
} else if (indexer === 3) {
subject4 = subjectArray[indexer].substring(0, 63);
}
indexer++;
@ -309,7 +317,7 @@ export class KBService {
content: answer,
format: format,
packageId: packageId,
prevId: lastQuestionId ? lastQuestionId : 0
prevId: lastQuestionId !== null ? lastQuestionId : 0
});
const question1 = await GuaribasQuestion.create({
@ -325,8 +333,8 @@ export class KBService {
packageId: packageId
});
if (lastAnswer && lastQuestionId) {
await lastAnswer.updateAttributes({ nextId: lastQuestionId });
if (lastAnswer !== undefined && lastQuestionId !== 0) {
await lastAnswer.update({ nextId: lastQuestionId });
}
lastAnswer = answer1;
lastQuestionId = question1.questionId;
@ -335,21 +343,21 @@ export class KBService {
} else {
// Skips the header.
return Promise.resolve(null);
return Promise.resolve(undefined);
}
});
}
public async sendAnswer(conversationalService: IGBConversationalService, step: any, answer: GuaribasAnswer) {
public async sendAnswer(conversationalService: IGBConversationalService, step: GBDialogStep, answer: GuaribasAnswer) {
if (answer.content.endsWith('.mp4')) {
await conversationalService.sendEvent(step, 'play', {
playerType: 'video',
data: answer.content
});
} else if (answer.content.length > 140 && step.context._activity.channelId === 'webchat') {
} else if (answer.content.length > 140 && step.context.activity.channelId === 'webchat') {
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);
let html = answer.content;
if (answer.format === '.md') {
@ -377,7 +385,7 @@ export class KBService {
});
} else {
await step.context.sendActivity(answer.content);
await conversationalService.sendEvent(step, 'stop', null);
await conversationalService.sendEvent(step, 'stop', undefined);
}
}
@ -388,7 +396,7 @@ export class KBService {
): Promise<any> {
// Imports subjects tree into database and return it.
await this.importSubjectFile(packageStorage.packageId, UrlJoin(localPath, 'subjects.json'), instance);
await this.importSubjectFile(packageStorage.packageId, urlJoin(localPath, 'subjects.json'), instance);
// Import all .tsv files in the tabular directory.
@ -396,24 +404,22 @@ export class KBService {
}
public async importKbTabularDirectory(localPath: string, instance: IGBInstance, packageId: number): Promise<any> {
const files = await walkPromise(UrlJoin(localPath, 'tabular'));
const files = await walkPromise(urlJoin(localPath, 'tabular'));
return Promise.all(
files.map(async file => {
if (file.name.endsWith('.tsv')) {
return this.importKbTabularFile(UrlJoin(file.root, file.name), instance.instanceId, packageId);
return this.importKbTabularFile(urlJoin(file.root, file.name), instance.instanceId, packageId);
}
})
);
}
public async importSubjectFile(packageId: number, filename: string, instance: IGBInstance): Promise<any> {
const subjects = JSON.parse(Fs.readFileSync(filename, 'utf8'));
const subjectsLoaded = JSON.parse(Fs.readFileSync(filename, 'utf8'));
const doIt = async (subjects: GuaribasSubject[], parentSubjectId: number) => {
return asyncPromise.eachSeries(subjects, async item => {
const mediaFilename = item.id + '.png';
const value = await GuaribasSubject.create({
internalId: item.id,
parentSubjectId: parentSubjectId,
@ -432,7 +438,8 @@ export class KBService {
}
});
};
return doIt(subjects.children, null);
return doIt(subjectsLoaded.children, undefined);
}
public async undeployKbFromStorage(instance: IGBInstance, deployer: GBDeployer, packageId: number) {
@ -449,7 +456,7 @@ export class KBService {
where: { instanceId: instance.instanceId, packageId: packageId }
});
await deployer.rebuildIndex(instance);
await deployer.rebuildIndex(instance, new AzureDeployerService(deployer).getKBSearchSchema(instance.searchIndex));
}
/**
@ -460,15 +467,15 @@ export class KBService {
public async deployKb(core: IGBCoreService, deployer: GBDeployer, localPath: string) {
const packageType = Path.extname(localPath);
const packageName = Path.basename(localPath);
logger.info(`[GBDeployer] Opening package: ${localPath}`);
const packageObject = JSON.parse(Fs.readFileSync(UrlJoin(localPath, 'package.json'), 'utf8'));
GBLog.info(`[GBDeployer] Opening package: ${localPath}`);
const packageObject = JSON.parse(Fs.readFileSync(urlJoin(localPath, 'package.json'), 'utf8'));
const instance = await core.loadInstance(packageObject.botId);
logger.info(`[GBDeployer] Importing: ${localPath}`);
GBLog.info(`[GBDeployer] Importing: ${localPath}`);
const p = await deployer.deployPackageToStorage(instance.instanceId, packageName);
await this.importKbPackage(localPath, p, instance);
deployer.rebuildIndex(instance);
logger.info(`[GBDeployer] Finished import of ${localPath}`);
deployer.rebuildIndex(instance, new AzureDeployerService(deployer).getKBSearchSchema(instance.searchIndex));
GBLog.info(`[GBDeployer] Finished import of ${localPath}`);
}
}

View file

@ -10,9 +10,8 @@ export const Messages = {
here_is_subjects: 'Here are some subjects to choose from...',
menu_select: 'Select',
lets_search: query =>
`Vamos pesquisar sobre ${query}... O que deseja saber?`,
see_faq:
'Please take a look at the FAQ I\'ve prepared for you. You can click on them to get the answer.',
`Lets search for ${query}... What do you want to know?`,
see_faq: 'Please take a look at the FAQ I\'ve prepared for you. You can click on them to get the answer.',
will_answer_projector:
'I\'ll answer on the projector to a better experience...',
ask_first_time: 'What are you looking for?'

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,37 +36,35 @@
'use strict';
const UrlJoin = require('url-join');
import urlJoin = require('url-join');
import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { Sequelize } from 'sequelize-typescript';
import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from './models';
/**
* Package for the security module.
*/
export class GBSecurityPackage implements IGBPackage {
public sysPackages: IGBPackage[] = null;
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
core.sequelize.addModels([
GuaribasGroup,
GuaribasUser,
GuaribasUserGroup
]);
core;
public sysPackages: IGBPackage[];
public getDialogs(min: GBMinInstance) {
GBLog.verbose(`getDialogs called.`);
}
public unloadPackage(core: IGBCoreService): void {
GBLog.verbose(`unloadPackage called.`);
}
public loadBot(min: GBMinInstance): void {
GBLog.verbose(`loadBot called.`);
}
public unloadBot(min: GBMinInstance): void {
GBLog.verbose(`unloadBot called.`);
}
public onNewSession(min: GBMinInstance, step: GBDialogStep): void {
GBLog.verbose(`onNewSession called.`);
}
public onNewSession(min: GBMinInstance, step: any): void {
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
core.sequelize.addModels([GuaribasGroup, GuaribasUser, GuaribasUserGroup]);
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,33 +36,23 @@
'use strict';
import {
DataTypeDate,
DataTypeDecimal,
DataTypes,
DataTypeUUIDv4
} from 'sequelize';
import {
AutoIncrement,
BelongsTo,
BelongsToMany,
Column,
CreatedAt,
DataType,
ForeignKey,
HasMany,
IsUUID,
Length,
Model,
PrimaryKey,
Sequelize,
Table,
UpdatedAt
} from 'sequelize-typescript';
Table} from 'sequelize-typescript';
import { GuaribasInstance } from '../../core.gbapp/models/GBModel';
/**
* A user and its metadata.
*/
@Table
export class GuaribasUser extends Model<GuaribasUser> {
@PrimaryKey
@ -90,6 +80,9 @@ export class GuaribasUser extends Model<GuaribasUser> {
public instance: GuaribasInstance;
}
/**
* A group of users.
*/
@Table
export class GuaribasGroup extends Model<GuaribasGroup> {
@PrimaryKey
@ -109,6 +102,9 @@ export class GuaribasGroup extends Model<GuaribasGroup> {
public instance: GuaribasInstance;
}
/**
* Relation of groups and users.
*/
@Table
export class GuaribasUserGroup extends Model<GuaribasUserGroup> {
@ForeignKey(() => GuaribasUser)

View file

@ -1,68 +1,31 @@
/*****************************************************************************\
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |
| |
| The texts of the GNU Affero General Public License with an additional |
| permission and of our proprietary license can be found at and |
| in the LICENSE file you have received along with this program. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY, without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| "General Bots" is a registered trademark of Pragmatismo.io. |
| The licensing of the program under the AGPLv3 does not imply a |
| trademark license. Therefore any rights, title and interest in |
| our trademarks remain entirely with us. |
| |
\*****************************************************************************/
const Path = require('path');
const Fs = require('fs');
const _ = require('lodash');
const Parse = require('csv-parse');
const Async = require('async');
const UrlJoin = require('url-join');
const logger = require('../../../src/logger');
import urlJoin = require('url-join');
import { GBService, GBServiceCallback, IGBInstance } from 'botlib';
import { GBService, IGBInstance } from 'botlib';
import { GuaribasGroup, GuaribasUser, GuaribasUserGroup } from '../models';
/**
* Security service layer.
*/
export class SecService extends GBService {
public async importSecurityFile(localPath: string, instance: IGBInstance) {
const security = JSON.parse(
Fs.readFileSync(UrlJoin(localPath, 'security.json'), 'utf8')
);
const security = JSON.parse(Fs.readFileSync(urlJoin(localPath, 'security.json'), 'utf8'));
security.groups.forEach(group => {
const groupDb = GuaribasGroup.build({
instanceId: instance.instanceId,
displayName: group.displayName
});
groupDb.save().then(groupDb => {
groupDb.save().then(g1 => {
group.users.forEach(user => {
const userDb = GuaribasUser.build({
instanceId: instance.instanceId,
groupId: groupDb.groupId,
groupId: g1.groupId,
userName: user.userName
});
userDb.save().then(userDb => {
userDb.save().then(user2 => {
const userGroup = GuaribasUserGroup.build();
userGroup.groupId = groupDb.groupId;
userGroup.userId = userDb.userId;
userGroup.groupId = g1.groupId;
userGroup.userId = user2.userId;
userGroup.save();
});
});
@ -78,16 +41,15 @@ export class SecService extends GBService {
channelName: string,
displayName: string
): Promise<GuaribasUser> {
return new Promise<GuaribasUser>(
(resolve, reject) => {
GuaribasUser.findOne({
attributes: ['instanceId', 'internalAddress'],
where: {
instanceId: instanceId,
userSystemId: userSystemId
}
}).then(user => {
return new Promise<GuaribasUser>((resolve, reject) => {
GuaribasUser.findOne({
attributes: ['instanceId', 'internalAddress'],
where: {
instanceId: instanceId,
userSystemId: userSystemId
}
})
.then(user => {
if (!user) {
user = GuaribasUser.build();
}
@ -99,7 +61,8 @@ export class SecService extends GBService {
user.defaultChannel = channelName;
user.save();
resolve(user);
}).error(reject);
});
})
.error(reject);
});
}
}

View file

@ -2,7 +2,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -36,39 +36,46 @@
'use strict';
const UrlJoin = require('url-join');
import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { GBDialogStep, GBLog, GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { Sequelize } from 'sequelize-typescript';
import { WhatsappDirectLine } from './services/WhatsappDirectLine';
/**
* Package for whatsapp.gblib
*/
export class GBWhatsappPackage implements IGBPackage {
public sysPackages: IGBPackage[];
public sysPackages: IGBPackage[] = null;
public channel: WhatsappDirectLine;
public channel: WhatsappDirectLine;
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
public loadBot(min: GBMinInstance): void {
// Only loads engine if it is defined on services.json.
if (min.instance.whatsappBotKey !== undefined) {
this.channel = new WhatsappDirectLine(
min.botId,
min.instance.whatsappBotKey,
min.instance.whatsappServiceKey,
min.instance.whatsappServiceNumber,
min.instance.whatsappServiceUrl,
min.instance.whatsappServiceWebhookUrl
);
}
}
public unloadPackage(core: IGBCoreService): void {
}
public loadBot(min: GBMinInstance): void {
// Only loads engine if it is defined on services.json.
if (min.instance.whatsappBotKey) {
this.channel = new WhatsappDirectLine(min.botId, min.instance.whatsappBotKey, min.instance.whatsappServiceKey,
min.instance.whatsappServiceNumber, min.instance.whatsappServiceUrl, min.instance.whatsappServiceWebhookUrl);
}
}
public unloadBot(min: GBMinInstance): void {
}
public onNewSession(min: GBMinInstance, step: any): void {
}
public getDialogs(min: GBMinInstance) {
GBLog.verbose(`getDialogs called.`);
}
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {
GBLog.verbose(`loadPackage called.`);
}
public unloadPackage(core: IGBCoreService): void {
GBLog.verbose(`unloadPackage called.`);
}
public unloadBot(min: GBMinInstance): void {
GBLog.verbose(`unloadBot called.`);
}
public onNewSession(min: GBMinInstance, step: GBDialogStep): void {
GBLog.verbose(`onNewSession called.`);
}
}

View file

@ -1,266 +1,218 @@
/*****************************************************************************\
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |
| |
| The texts of the GNU Affero General Public License with an additional |
| permission and of our proprietary license can be found at and |
| in the LICENSE file you have received along with this program. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY, without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| "General Bots" is a registered trademark of Pragmatismo.io. |
| The licensing of the program under the AGPLv3 does not imply a |
| trademark license. Therefore any rights, title and interest in |
| our trademarks remain entirely with us. |
| |
\*****************************************************************************/
import urlJoin = require('url-join');
const Path = require('path');
const Fs = require('fs');
const _ = require('lodash');
const Parse = require('csv-parse');
const Async = require('async');
const UrlJoin = require('url-join');
const logger = require('../../../src/logger');
const Swagger = require('swagger-client');
const rp = require('request-promise');
import { GBLog, GBService } from 'botlib';
import * as request from 'request-promise-native';
import { GBService, GBServiceCallback, IGBInstance } from 'botlib';
/**
* Support for Whatsapp.
*/
export class WhatsappDirectLine extends GBService {
public pollInterval = 1000;
public directLineClientName = 'DirectLineClient';
public directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json';
public pollInterval = 1000;
public directLineClientName = 'DirectLineClient';
public directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json';
public directLineClient: any;
public whatsappServiceKey: string;
public whatsappServiceNumber: string;
public whatsappServiceUrl: string;
public whatsappServiceWebhookUrl: string;
public botId: string;
public watermark: string;
public directLineClient: any;
public whatsappServiceKey: string;
public whatsappServiceNumber: string;
public whatsappServiceUrl: string;
public whatsappServiceWebhookUrl: string;
public botId: string;
public watermark: string = null;
public conversationIds = {};
public conversationIds = {};
constructor(
botId,
directLineSecret,
whatsappServiceKey,
whatsappServiceNumber,
whatsappServiceUrl,
whatsappServiceWebhookUrl
) {
super();
constructor(botId, directLineSecret, whatsappServiceKey, whatsappServiceNumber, whatsappServiceUrl, whatsappServiceWebhookUrl) {
this.botId = botId;
this.whatsappServiceKey = whatsappServiceKey;
this.whatsappServiceNumber = whatsappServiceNumber;
this.whatsappServiceUrl = whatsappServiceUrl;
this.whatsappServiceWebhookUrl = whatsappServiceWebhookUrl;
super();
this.botId = botId;
this.whatsappServiceKey = whatsappServiceKey;
this.whatsappServiceNumber = whatsappServiceNumber;
this.whatsappServiceUrl = whatsappServiceUrl;
this.whatsappServiceWebhookUrl = whatsappServiceWebhookUrl;
// TODO: Migrate to Swagger 3.
this.directLineClient = rp(this.directLineSpecUrl)
.then((spec) => {
return new Swagger({
spec: JSON.parse(spec.trim()),
usePromise: true
});
})
.then(async (client) => {
client.clientAuthorizations.add('AuthorizationBotConnector',
new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' +
directLineSecret, 'header'));
const options = {
method: 'POST',
url: UrlJoin(this.whatsappServiceUrl, 'webhook'),
qs:
{
token: this.whatsappServiceKey,
webhookUrl: `${this.whatsappServiceWebhookUrl}/instances/${this.botId}/whatsapp`,
set: true
},
headers:
{
'cache-control': 'no-cache'
}
};
try {
const result = await request.post(options);
logger.info(result);
} catch (error) {
logger.error('Error initializing 3rd party Whatsapp provider.', error);
}
return client;
})
.catch((err) => {
logger.error('Error initializing DirectLine client', err);
});
}
public received(req, res) {
const text = req.body.messages[0].body;
const from = req.body.messages[0].author.split('@')[0];
const fromName = req.body.messages[0].senderName;
if (req.body.messages[0].fromMe) {
return; // Exit here.
}
logger.info(`GBWhatsapp: Hook called. from: ${from}(${fromName}), text: ${text})`);
const conversationId = this.conversationIds[from];
this.directLineClient.then((client) => {
if (this.conversationIds[from] == null) {
logger.info(`GBWhatsapp: Starting new conversation on Bot.`);
client.Conversations.Conversations_StartConversation()
.then((response) => {
return response.obj.conversationId;
})
.then((conversationId) => {
this.conversationIds[from] = conversationId;
this.inputMessage(client, conversationId, text,
from, fromName);
this.pollMessages(client, conversationId, from, fromName);
})
.catch((err) => {
console.error('Error starting conversation', err);
});
} else {
this.inputMessage(client, conversationId, text,
from, fromName);
}
res.end();
this.directLineClient = rp(this.directLineSpecUrl)
.then(spec => {
return new Swagger({
spec: JSON.parse(spec.trim()),
usePromise: true
});
}
})
.then(async client => {
client.clientAuthorizations.add(
'AuthorizationBotConnector',
new Swagger.ApiKeyAuthorization('Authorization', `Bearer ${directLineSecret}`, 'header')
);
public inputMessage(client, conversationId, text, from, fromName) {
client.Conversations.Conversations_PostActivity(
{
conversationId: conversationId,
activity: {
textFormat: 'plain',
text: text,
type: 'message',
from: {
id: from,
name: fromName
},
replyToId: from
}
}).catch((err) => {
logger.error(`GBWhatsapp: Error receiving message: ${err}.`);
});
}
public pollMessages(client, conversationId, from, fromName) {
logger.info(`GBWhatsapp: Starting polling message for conversationId:
${conversationId}.`);
setInterval(() => {
client.Conversations.Conversations_GetActivities({
conversationId:
conversationId, watermark: this.watermark
})
.then((response) => {
this.watermark = response.obj.watermark;
return response.obj.activities;
})
.then((activities) => {
this.printMessages(activities, conversationId, from, fromName);
});
}, this.pollInterval);
}
public printMessages(activities, conversationId, from, fromName) {
if (activities && activities.length) {
// Ignore own messages.
activities = activities.filter((m) => (m.from.id === 'GeneralBots') && m.type === 'message');
if (activities.length) {
// Print other messages.
activities.forEach(activity => {
this.printMessage(activity, conversationId, from, fromName);
});
}
}
}
public printMessage(activity, conversationId, from, fromName) {
let output = '';
if (activity.text) {
logger.info(`GBWhatsapp: MSG: ${activity.text}`);
output = activity.text;
}
if (activity.attachments) {
activity.attachments.forEach((attachment) => {
switch (attachment.contentType) {
case 'application/vnd.microsoft.card.hero':
output += `\n${this.renderHeroCard(attachment)}`;
break;
case 'image/png':
logger.info('Opening the requested image ' + attachment.contentUrl);
output += `\n${attachment.contentUrl}`;
break;
}
});
}
this.sendToDevice(conversationId, from, fromName, output);
}
public renderHeroCard(attachment) {
return `${attachment.content.title} - ${attachment.content.text}`;
}
public async sendToDevice(conversationId, to, toName, msg) {
const options = {
method: 'POST',
url: UrlJoin(this.whatsappServiceUrl, 'message'),
qs:
{
token: this.whatsappServiceKey,
phone: to,
body: msg
},
headers:
{
'cache-control': 'no-cache'
}
method: 'POST',
url: urlJoin(this.whatsappServiceUrl, 'webhook'),
qs: {
token: this.whatsappServiceKey,
webhookUrl: `${this.whatsappServiceWebhookUrl}/instances/${this.botId}/whatsapp`,
set: true
},
headers: {
'cache-control': 'no-cache'
}
};
const result = await request.get(options);
try {
const result = request.post(options);
GBLog.info(result);
} catch (error) {
GBLog.error(`Error initializing 3rd party Whatsapp provider(1) ${error}`);
}
return client;
})
.catch(err => {
GBLog.error(`Error initializing 3rd party Whatsapp provider(2) ${err}`);
});
}
public received(req, res) {
const text = req.body.messages[0].body;
const from = req.body.messages[0].author.split('@')[0];
const fromName = req.body.messages[0].senderName;
if (req.body.messages[0].fromMe) {
return; // Exit here.
}
GBLog.info(`GBWhatsapp: Hook called. from: ${from}(${fromName}), text: ${text})`);
const conversationId = this.conversationIds[from];
this.directLineClient.then(client => {
if (this.conversationIds[from] === undefined) {
GBLog.info(`GBWhatsapp: Starting new conversation on Bot.`);
client.Conversations.Conversations_StartConversation()
.then(response => {
return response.obj.conversationId;
})
.then(generatedConversationId => {
this.conversationIds[from] = generatedConversationId;
this.inputMessage(client, generatedConversationId, text, from, fromName);
this.pollMessages(client, generatedConversationId, from, fromName);
})
.catch(err => {
GBLog.error(`Error starting conversation ${err}`);
});
} else {
this.inputMessage(client, conversationId, text, from, fromName);
}
res.end();
});
}
public inputMessage(client, conversationId, text, from, fromName) {
client.Conversations.Conversations_PostActivity({
conversationId: conversationId,
activity: {
textFormat: 'plain',
text: text,
type: 'message',
from: {
id: from,
name: fromName
},
replyToId: from
}
}).catch(err => {
GBLog.error(`GBWhatsapp: Error receiving message: ${err}.`);
});
}
public pollMessages(client, conversationId, from, fromName) {
GBLog.info(`GBWhatsapp: Starting polling message for conversationId:
${conversationId}.`);
setInterval(() => {
client.Conversations.Conversations_GetActivities({
conversationId: conversationId,
watermark: this.watermark
})
.then(response => {
this.watermark = response.obj.watermark;
return response.obj.activities;
})
.then(activities => {
this.printMessages(activities, conversationId, from, fromName);
});
}, this.pollInterval);
}
public printMessages(activities, conversationId, from, fromName) {
if (activities && activities.length) {
// Ignore own messages.
activities = activities.filter(m => m.from.id === 'GeneralBots' && m.type === 'message');
if (activities.length) {
// Print other messages.
activities.forEach(activity => {
this.printMessage(activity, conversationId, from, fromName);
});
}
}
}
public printMessage(activity, conversationId, from, fromName) {
let output = '';
if (activity.text) {
GBLog.info(`GBWhatsapp: MSG: ${activity.text}`);
output = activity.text;
}
if (activity.attachments) {
activity.attachments.forEach(attachment => {
switch (attachment.contentType) {
case 'application/vnd.microsoft.card.hero':
output += `\n${this.renderHeroCard(attachment)}`;
break;
case 'image/png':
GBLog.info(`Opening the requested image ${attachment.contentUrl}`);
output += `\n${attachment.contentUrl}`;
break;
default:
GBLog.info(`Unknown content type: ${attachment.contentType}`);
}
});
}
this.sendToDevice(from, output);
}
public renderHeroCard(attachment) {
return `${attachment.content.title} - ${attachment.content.text}`;
}
public async sendToDevice(to, msg) {
const options = {
method: 'POST',
url: urlJoin(this.whatsappServiceUrl, 'message'),
qs: {
token: this.whatsappServiceKey,
phone: to,
body: msg
},
headers: {
'cache-control': 'no-cache'
}
};
}
}

View file

@ -3,7 +3,7 @@
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
@ -37,23 +37,20 @@
'use strict';
const logger = require('./logger');
const express = require('express');
const bodyParser = require('body-parser');
import { IGBInstance, IGBPackage } from 'botlib';
import { GBLog, IGBCoreService, IGBInstance, IGBPackage } from 'botlib';
import { GBAdminService } from '../packages/admin.gbapp/services/GBAdminService';
import { AzureDeployerService } from '../packages/azuredeployer.gbapp/services/AzureDeployerService';
import { GuaribasInstance } from '../packages/core.gbapp/models/GBModel';
import { GBConfigService } from '../packages/core.gbapp/services/GBConfigService';
import { GBConversationalService } from '../packages/core.gbapp/services/GBConversationalService';
import { GBCoreService } from '../packages/core.gbapp/services/GBCoreService';
import { GBDeployer } from '../packages/core.gbapp/services/GBDeployer';
import { GBImporter } from '../packages/core.gbapp/services/GBImporterService';
import { GBMinService } from '../packages/core.gbapp/services/GBMinService';
import { GBVMService } from '../packages/core.gbapp/services/GBVMService';
import { load } from 'dotenv';
const appPackages = new Array<IGBPackage>();
const appPackages: IGBPackage[] = [];
/**
* General Bots open-core entry point.
@ -64,33 +61,29 @@ export class GBServer {
*/
public static run() {
logger.info(`The Bot Server is in STARTING mode...`);
GBLog.info(`The Bot Server is in STARTING mode...`);
// Creates a basic HTTP server that will serve several URL, one for each
// bot instance. This allows the same server to attend multiple Bot on
// the Marketplace until GB get serverless.
// bot instance.
const port = process.env.port || process.env.PORT || 4242;
const port = GBConfigService.getServerPort();
const server = express();
server.use(bodyParser.json()); // to support JSON-encoded bodies
server.use(bodyParser.json());
server.use(
bodyParser.urlencoded({
// to support URL-encoded bodies
extended: true
})
);
let bootInstance: IGBInstance;
server.listen(port, () => {
(async () => {
try {
logger.info(`Now accepting connections on ${port}...`);
GBLog.info(`Now accepting connections on ${port}...`);
// Reads basic configuration, initialize minimal services.
GBConfigService.init();
const core = new GBCoreService();
const core: IGBCoreService = new GBCoreService();
const importer: GBImporter = new GBImporter(core);
const deployer: GBDeployer = new GBDeployer(core, importer);
@ -100,15 +93,16 @@ export class GBServer {
// Ensure that local development proxy is setup.
logger.info(`Establishing a development local proxy (ngrok)...`);
GBLog.info(`Establishing a development local proxy (ngrok)...`);
const proxyAddress: string = await core.ensureProxy(port);
// Creates a boot instance or load it frmo storage.
// Creates a boot instance or load it from storage.
let bootInstance: IGBInstance = null;
let bootInstance: IGBInstance;
try {
await core.initStorage();
} catch (error) {
GBLog.verbose(`Error initializing storage: ${error}`);
bootInstance = await core.createBootInstance(core, azureDeployer, proxyAddress);
await core.initStorage();
}
@ -117,24 +111,28 @@ export class GBServer {
// Deploys system and user packages.
logger.info(`Deploying packages...`);
GBLog.info(`Deploying packages...`);
core.loadSysPackages(core);
await core.checkStorage(azureDeployer);
await deployer.deployPackages(core, server, appPackages);
// Loads all bot instances.
logger.info(`Publishing instances...`);
GBLog.info(`Publishing instances...`);
const packageInstance = await importer.importIfNotExistsBotPackage(
GBConfigService.get('CLOUD_GROUP'),
'boot.gbot',
'packages/boot.gbot'
);
if (bootInstance === undefined) {
bootInstance = packageInstance;
}
// tslint:disable-next-line:prefer-object-spread
const fullInstance = Object.assign(packageInstance, bootInstance);
await core.saveInstance(fullInstance);
let instances: GuaribasInstance[] = await core.loadAllInstances(core, azureDeployer, proxyAddress);
let instances: IGBInstance[] = await core.loadAllInstances(core, azureDeployer, proxyAddress);
instances = await core.ensureInstances(instances, bootInstance, core);
if (!bootInstance) {
if (bootInstance !== undefined) {
bootInstance = instances[0];
}
@ -145,16 +143,15 @@ export class GBServer {
// Deployment of local applications for the first time.
deployer.installDefaultGBUI();
deployer.runOnce();
logger.info(`The Bot Server is in RUNNING mode...`);
GBLog.info(`The Bot Server is in RUNNING mode...`);
// Opens Navigator.
core.openBrowserInDevelopment();
} catch (err) {
logger.error(`STOP: ${err} ${err.stack ? err.stack : ''}`);
GBLog.error(`STOP: ${err} ${err.stack ? err.stack : ''}`);
process.exit(1);
}
})();

View file

@ -12,6 +12,7 @@
"resolveJsonModule": true,
"outDir": "./dist",
"paths": {
"*": ["types/*"],
"botlib/*": ["node_modules/botlib/*"],
"pragmatismo-io-framework/*": ["node_modules/pragmatismo-io-framework/*"]
},

View file

@ -1,43 +1,43 @@
{
"defaultSeverity": "warning",
"extends": [
"tslint:recommended",
"tslint-microsoft-contrib"
],
"linterOptions": {
"exclude":[
"libraries/botframework-connector/src/generated/**/*",
"libraries/botframework-schema/**/*"
]
},
"rulesDirectory": [
"node_modules/tslint-microsoft-contrib"
],
"jsRules": {},
"rules": {
"no-floating-promises": false,
"no-var-requires":false,
"typedef":false,
"variable-name": false,
"no-parameter-properties": false,
"no-reserved-keywords": false,
"no-unnecessary-class":false,
"no-require-imports": false,
"function-name": false,
"no-redundant-jsdoc": false,
"no-return-await": false,
"prefer-type-cast": false,
"no-object-literal-type-assertion":false,
"no-increment-decrement":false,
"no-any":false,
"interface-name":false,
"no-this-assignment":false,
"switch-final-break":false,
"no-parameter-reassignment":false,
"export-name":false,
"no-relative-imports": false,
"no-backbone-get-set-outside-model": false,
"max-line-length": [true,{"limit":120,"ignore-pattern":"^\\s+\\*"}],
"await-promise": [true, "Bluebird"]
}
"defaultSeverity": "warning",
"extends": ["tslint:recommended", "tslint-microsoft-contrib"],
"linterOptions": {
"exclude": [
"libraries/botframework-connector/src/generated/**/*",
"libraries/botframework-schema/**/*",
"./packages/default.gbui/**/*",
"./packages/**/*.gbdialog"
]
},
"rulesDirectory": ["node_modules/tslint-microsoft-contrib"],
"jsRules": {},
"rules": {
"newline-per-chained-call": false,
"no-unsafe-any": false,
"no-floating-promises": false,
"no-var-requires": false,
"typedef": false,
"variable-name": false,
"no-parameter-properties": false,
"max-line-length": [true, { "limit": 120, "ignore-pattern": "^\\s+\\*" }],
"await-promise": [true, "Bluebird"],
"no-reserved-keywords": false,
"no-unnecessary-class": false,
"no-require-imports": false,
"function-name": false,
"no-relative-imports": false,
"no-redundant-jsdoc": false,
"no-return-await": false,
"prefer-type-cast": false,
"non-literal-fs-path": false,
"no-object-literal-type-assertion": false,
"no-increment-decrement": false,
"no-any": false,
"interface-name": false,
"no-this-assignment": false,
"switch-final-break": false,
"no-parameter-reassignment": false,
"export-name": false,
"no-backbone-get-set-outside-model": false
}
}