Several bug fixes and start for handling ms-graph calls and sharepoint basic storage #81

Merged
rodrigorodriguez merged 4 commits from master into master 2019-02-01 13:06:17 +00:00
18 changed files with 2076 additions and 2645 deletions

View file

@ -0,0 +1,3 @@
{
"git.ignoreLimitWarning": true
}

View file

@ -8,9 +8,10 @@
| Releases | [![General Bots](https://img.shields.io/npm/dt/botserver.svg?logo=npm&label=botserver)](https://www.npmjs.com/package/botserver/) [![.gbapp lib](https://img.shields.io/npm/dt/botlib.svg?logo=npm&label=botlib)](https://www.npmjs.com/package/botlib/) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)| | Releases | [![General Bots](https://img.shields.io/npm/dt/botserver.svg?logo=npm&label=botserver)](https://www.npmjs.com/package/botserver/) [![.gbapp lib](https://img.shields.io/npm/dt/botlib.svg?logo=npm&label=botlib)](https://www.npmjs.com/package/botlib/) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)|
| [Docker Image](https://github.com/lpicanco/docker-botserver) | ![Docker Automated build](https://img.shields.io/docker/automated/lpicanco/botserver.svg) ![Docker Build Status](https://img.shields.io/docker/build/lpicanco/botserver.svg) ![MicroBadger Size](https://img.shields.io/microbadger/image-size/lpicanco/botserver.svg) ![MicroBadger Layers](https://img.shields.io/microbadger/layers/lpicanco/botserver.svg) ![Docker Pulls](https://img.shields.io/docker/pulls/lpicanco/botserver.svg) <br/> *Provided by [@lpicanco](https://github.com/lpicanco/docker-botserver)* | | [Docker Image](https://github.com/lpicanco/docker-botserver) | ![Docker Automated build](https://img.shields.io/docker/automated/lpicanco/botserver.svg) ![Docker Build Status](https://img.shields.io/docker/build/lpicanco/botserver.svg) ![MicroBadger Size](https://img.shields.io/microbadger/image-size/lpicanco/botserver.svg) ![MicroBadger Layers](https://img.shields.io/microbadger/layers/lpicanco/botserver.svg) ![Docker Pulls](https://img.shields.io/docker/pulls/lpicanco/botserver.svg) <br/> *Provided by [@lpicanco](https://github.com/lpicanco/docker-botserver)* |
#### Watch a video about easeness authoring of bot packages, development environment and self-deployment #### Watch a video about easeness authoring of bot packages, development environment and self-deployment
* Now with General Bots you can press F5 on Visual Studio to get a bot factory on your environment* published on November 10th, 2018. * Now with the General Bots server you can press F5 on Visual Studio to get a bot factory on your environment* published on November 10th, 2018.
[![General Bot Video](https://raw.githubusercontent.com/pragmatismo-io/BotServer/master/docs/images/video-01-thumb.jpg)](https://www.youtube.com/watch?v=AfKTwljoMOs) [![General Bot Video](https://raw.githubusercontent.com/pragmatismo-io/BotServer/master/docs/images/video-01-thumb.jpg)](https://www.youtube.com/watch?v=AfKTwljoMOs)
@ -20,7 +21,7 @@ Welcome to General Bot Community Edition
![General Bot Logo](https://raw.githubusercontent.com/pragmatismo-io/BotServer/master/logo.png) ![General Bot Logo](https://raw.githubusercontent.com/pragmatismo-io/BotServer/master/logo.png)
General Bot is a package based chat bot server focused in convention over configuration and code-less approaches, which brings software packages and application server concepts to help parallel bot development. General Bot is a strongly typed package based chat bot server focused in convention over configuration and code-less approaches, which brings software packages and application server concepts to help parallel bot development.
## Sample Package #1: [default.gbdialog (VBA)](https://github.com/pragmatismo-io/BotServer/tree/master/packages/default.gbdialog) ## Sample Package #1: [default.gbdialog (VBA)](https://github.com/pragmatismo-io/BotServer/tree/master/packages/default.gbdialog)

4230
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -48,30 +48,30 @@
"commit": "git-cz" "commit": "git-cz"
}, },
"dependencies": { "dependencies": {
"@microsoft/microsoft-graph-client": "1.3.0", "@microsoft/microsoft-graph-client": "1.4.0",
"@semantic-release/exec": "^3.3.0", "@semantic-release/exec": "^3.3.2",
"adal-node": "0.1.28", "adal-node": "0.1.28",
"async": "2.6.1", "async": "2.6.1",
"async-promises": "0.2.1", "async-promises": "0.2.1",
"azure-arm-cognitiveservices": "2.4.0", "azure-arm-cognitiveservices": "2.4.1",
"azure-arm-resource": "7.2.1", "azure-arm-resource": "7.3.0",
"azure-arm-search": "^1.3.0-preview", "azure-arm-search": "^1.3.0-preview",
"azure-arm-sql": "5.6.0", "azure-arm-sql": "5.6.0",
"azure-arm-website": "5.7.0", "azure-arm-website": "5.7.0",
"bluebird": "^3.5.3", "bluebird": "^3.5.3",
"body-parser": "1.18.3", "body-parser": "1.18.3",
"botbuilder": "^4.1.7", "botbuilder": "^4.1.7",
"botbuilder-ai": "^4.1.7", "botbuilder-ai": "^4.2.0",
"botbuilder-azure": "^4.1.7", "botbuilder-azure": "^4.2.0",
"botbuilder-choices": "^4.0.0-preview1.2", "botbuilder-choices": "^4.0.0-preview1.2",
"botbuilder-dialogs": "^4.1.7", "botbuilder-dialogs": "^4.2.0",
"botbuilder-prompts": "^4.0.0-preview1.2", "botbuilder-prompts": "^4.0.0-preview1.2",
"botlib": "^0.1.8", "botlib": "^0.1.10",
"chai": "4.2.0", "chai": "4.2.0",
"child_process": "^1.0.2", "child_process": "^1.0.2",
"chokidar": "2.0.4", "chokidar": "2.0.4",
"cli-spinner": "^0.2.8", "cli-spinner": "^0.2.8",
"csv-parse": "4.1.0", "csv-parse": "4.3.1",
"dotenv-extended": "2.3.0", "dotenv-extended": "2.3.0",
"express": "4.16.4", "express": "4.16.4",
"express-promise-router": "3.0.3", "express-promise-router": "3.0.3",
@ -79,68 +79,68 @@
"fs-walk": "0.0.2", "fs-walk": "0.0.2",
"ip": "^1.1.5", "ip": "^1.1.5",
"localize": "0.4.7", "localize": "0.4.7",
"marked": "0.5.2", "marked": "0.6.0",
"mocha": "5.2.0", "mocha": "5.2.0",
"mocha-typescript": "1.1.17", "mocha-typescript": "1.1.17",
"ms": "2.1.1", "ms": "2.1.1",
"ms-rest-azure": "2.5.9", "ms-rest-azure": "2.6.0",
"nexmo": "2.4.0", "nexmo": "2.4.1",
"ngrok": "^3.1.0", "ngrok": "^3.1.0",
"nyc": "^13.1.0", "nyc": "^13.1.0",
"opn": "^5.4.0", "opn": "^5.4.0",
"pragmatismo-io-framework": "1.0.19", "pragmatismo-io-framework": "1.0.19",
"process-exists": "^3.1.0", "process-exists": "^3.1.0",
"public-ip": "^2.4.0", "public-ip": "^3.0.0",
"reflect-metadata": "0.1.12", "reflect-metadata": "0.1.13",
"request-promise-native": "1.0.5", "request-promise-native": "1.0.5",
"scanf": "^1.0.2", "scanf": "^1.0.2",
"sequelize": "4.41.2", "sequelize": "4.42.0",
"sequelize-typescript": "0.6.6", "sequelize-typescript": "0.6.7",
"shx": "^0.3.2", "shx": "^0.3.2",
"simple-git": "^1.107.0", "simple-git": "^1.107.0",
"sqlite3": "4.0.4", "sqlite3": "4.0.6",
"strict-password-generator": "^1.1.1", "strict-password-generator": "^1.1.2",
"swagger-client": "3.8.22", "swagger-client": "3.8.22",
"tedious": "3.0.1", "tedious": "4.1.3",
"ts-node": "7.0.1", "ts-node": "8.0.2",
"typedoc": "0.13.0", "typedoc": "0.14.2",
"typedoc-plugin-external-module-name": "^1.1.3", "typedoc-plugin-external-module-name": "^2.0.0",
"typedoc-plugin-markdown": "^1.1.19", "typedoc-plugin-markdown": "^1.1.25",
"typescript": "3.2.1", "typescript": "3.3.1",
"url-join": "4.0.0", "url-join": "4.0.0",
"vbscript-to-typescript": "^1.0.8", "vbscript-to-typescript": "^1.0.8",
"wait-until": "0.0.2", "wait-until": "0.0.2",
"walk-promise": "0.2.0", "walk-promise": "0.2.0",
"winston": "3.1.0" "winston": "3.2.1"
}, },
"devDependencies": { "devDependencies": {
"@semantic-release/changelog": "^3.0.1", "@semantic-release/changelog": "^3.0.2",
"@semantic-release/commit-analyzer": "^6.1.0", "@semantic-release/commit-analyzer": "^6.1.0",
"@semantic-release/git": "^7.0.5", "@semantic-release/git": "^7.0.8",
"@semantic-release/github": "^5.2.5", "@semantic-release/github": "^5.2.10",
"@semantic-release/npm": "^5.1.1", "@semantic-release/npm": "^5.1.4",
"@semantic-release/release-notes-generator": "^7.1.4", "@semantic-release/release-notes-generator": "^7.1.4",
"@types/chai": "4.1.7", "@types/chai": "4.1.7",
"@types/mocha": "5.2.5", "@types/mocha": "5.2.5",
"@types/sequelize": "4.27.32", "@types/sequelize": "4.27.34",
"@types/url-join": "0.8.2", "@types/url-join": "4.0.0",
"@types/winston": "2.4.4", "@types/winston": "2.4.4",
"ban-sensitive-files": "1.9.2", "ban-sensitive-files": "1.9.2",
"commitizen": "^3.0.4", "commitizen": "^3.0.5",
"coveralls": "^3.0.2", "coveralls": "^3.0.2",
"cz-conventional-changelog": "^2.1.0", "cz-conventional-changelog": "^2.1.0",
"dependency-check": "3.2.1", "dependency-check": "3.3.0",
"deps-ok": "1.4.1", "deps-ok": "1.4.1",
"git-issues": "1.3.1", "git-issues": "1.3.1",
"license-checker": "24.0.1", "license-checker": "25.0.1",
"nsp": "3.2.1", "nsp": "3.2.1",
"pre-git": "3.17.1", "pre-git": "3.17.1",
"prettier-standard": "8.0.1", "prettier-standard": "9.1.1",
"semantic-release": "^15.12.4", "semantic-release": "^15.13.3",
"standard": "12.0.1", "standard": "12.0.1",
"travis-deploy-once": "5.0.9", "travis-deploy-once": "5.0.11",
"ts-loader": "^5.3.1", "ts-loader": "^5.3.3",
"tslint": "^5.11.0", "tslint": "^5.12.1",
"tslint-microsoft-contrib": "^6.0.0" "tslint-microsoft-contrib": "^6.0.0"
}, },
"eslintConfig": { "eslintConfig": {

View file

@ -38,7 +38,7 @@
const UrlJoin = require('url-join'); const UrlJoin = require('url-join');
import { BotAdapter } from 'botbuilder'; import { BotAdapter } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs'; import { WaterfallDialog, WaterfallStep, WaterfallStepContext } from 'botbuilder-dialogs';
import { GBMinInstance } from 'botlib'; import { GBMinInstance } from 'botlib';
import { IGBDialog } from 'botlib'; import { IGBDialog } from 'botlib';
import { GBConfigService } from '../../core.gbapp/services/GBConfigService'; import { GBConfigService } from '../../core.gbapp/services/GBConfigService';
@ -60,28 +60,35 @@ export class AdminDialog extends IGBDialog {
await deployer.undeployPackageFromLocalPath(min.instance, UrlJoin('packages', packageName)); await deployer.undeployPackageFromLocalPath(min.instance, UrlJoin('packages', packageName));
} }
public static isSharePointPath(path: string) {
return path.indexOf('sharepoint.com') > 0;
}
public static async deployPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) { public static async deployPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) {
const packageName = text.split(' ')[1]; const packageName = text.split(' ')[1];
const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH');
if (!additionalPath) if (AdminDialog.isSharePointPath(packageName)) {
{ await deployer.deployFromSharePoint(min.instance.instanceId, packageName);
throw new Error('ADDITIONAL_DEPLOY_PATH is not set and deployPackage was called.'); } else {
const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH');
if (!additionalPath) {
throw new Error('ADDITIONAL_DEPLOY_PATH is not set and deployPackage was called.');
}
await deployer.deployPackageFromLocalPath(min, UrlJoin(additionalPath, packageName));
} }
await deployer.deployPackageFromLocalPath(min, UrlJoin(additionalPath, packageName));
} }
public static async rebuildIndexPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) { public static async rebuildIndexPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) {
await deployer.rebuildIndex(min.instance); await deployer.rebuildIndex(min.instance);
} }
public static async addConnectionCommand(min: GBMinInstance, text: any) { public static async addConnectionCommand(min: GBMinInstance, text: any) {
const packageName = text.split(' ')[1]; const packageName = text.split(' ')[1];
const importer = new GBImporter(min.core); const importer = new GBImporter(min.core);
const admin = new GBAdminService(min.core); const admin = new GBAdminService(min.core);
// TODO: await admin.addConnection // TODO: await admin.addConnection
} }
/** /**
* Setup dialogs flows and define services call. * Setup dialogs flows and define services call.
* *
@ -94,6 +101,8 @@ export class AdminDialog extends IGBDialog {
const importer = new GBImporter(min.core); const importer = new GBImporter(min.core);
const deployer = new GBDeployer(min.core, importer); const deployer = new GBDeployer(min.core, importer);
AdminDialog.setupSecurityDialogs(min);
min.dialogs.add( min.dialogs.add(
new WaterfallDialog('/admin', [ new WaterfallDialog('/admin', [
async step => { async step => {
@ -151,7 +160,7 @@ export class AdminDialog extends IGBDialog {
return await step.replaceDialog('/admin', { firstRun: false }); return await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === 'setupSecurity') { } else if (cmdName === 'setupSecurity') {
await AdminDialog.setupSecurity(min, step); return await step.beginDialog('/setupSecurity');
} else { } else {
unknownCommand = true; unknownCommand = true;
} }
@ -159,7 +168,7 @@ export class AdminDialog extends IGBDialog {
if (unknownCommand) { if (unknownCommand) {
await step.context.sendActivity(Messages[locale].unknown_command); await step.context.sendActivity(Messages[locale].unknown_command);
} else { } else {
await step.context.sendActivity(Messages[locale].finshed_working(cmdName)); await step.context.sendActivity(Messages[locale].finished_working);
} }
await step.endDialog(); await step.endDialog();
@ -169,16 +178,66 @@ export class AdminDialog extends IGBDialog {
); );
} }
private static async setupSecurity(min: any, step: any) { private static setupSecurityDialogs(min: any) {
const locale = step.activity.locale; min.dialogs.add(
const state = `${min.instance.instanceId}${Math.floor(Math.random() * 1000000000)}`; new WaterfallDialog('/setupSecurity', [
await min.adminService.setValue(min.instance.instanceId, 'AntiCSRFAttackState', state); async step => {
const url = `https://login.microsoftonline.com/${min.instance.authenticatorTenant}/oauth2/authorize?client_id=${ const locale = step.context.activity.locale;
min.instance.authenticatorClientId const prompt = Messages[locale].enter_authenticator_tenant;
}&response_type=code&redirect_uri=${min.instance.botEndpoint}/${
min.instance.botId
}/token&state=${state}&response_mode=query`;
await step.sendActivity(Messages[locale].consent(url)); return await step.prompt('textPrompt', prompt);
},
async step => {
step.activeDialog.state.authenticatorTenant = step.result;
const locale = step.context.activity.locale;
const prompt = Messages[locale].enter_authenticator_authority_host_url;
return await step.prompt('textPrompt', prompt);
},
async step => {
step.activeDialog.state.authenticatorAuthorityHostUrl = step.result;
const locale = step.context.activity.locale;
const prompt = Messages[locale].enter_authenticator_client_id;
return await step.prompt('textPrompt', prompt);
},
async step => {
step.activeDialog.state.authenticatorClientId = step.result;
const locale = step.context.activity.locale;
const prompt = Messages[locale].enter_authenticator_client_secret;
return await step.prompt('textPrompt', prompt);
},
async step => {
step.activeDialog.state.authenticatorClientSecret = step.result;
await min.adminService.updateSecurityInfo(
min.instance.instanceId,
step.activeDialog.state.authenticatorTenant,
step.activeDialog.state.authenticatorAuthorityHostUrl,
step.activeDialog.state.authenticatorClientId,
step.activeDialog.state.authenticatorClientSecret
);
const locale = step.context.activity.locale;
const state = `${min.instance.instanceId}${Math.floor(Math.random() * 1000000000)}`;
await min.adminService.setValue(min.instance.instanceId, 'AntiCSRFAttackState', state);
const url = `https://login.microsoftonline.com/${min.instance.authenticatorTenant}/oauth2/authorize?client_id=${
min.instance.authenticatorClientId
}&response_type=code&redirect_uri=${UrlJoin(
min.instance.botEndpoint,
min.instance.botId,
'/token'
)}&state=${state}&response_mode=query`;
await step.context.sendActivity(Messages[locale].consent(url));
return await step.replaceDialog('/ask', {isReturning: true});
}
])
);
} }
} }

View file

@ -41,7 +41,8 @@ import {
CreatedAt, CreatedAt,
Model, Model,
Table, Table,
UpdatedAt UpdatedAt,
DataType
} from 'sequelize-typescript'; } from 'sequelize-typescript';
@Table @Table
@ -53,7 +54,7 @@ export class GuaribasAdmin extends Model<GuaribasAdmin> {
@Column @Column
public key: string; public key: string;
@Column @Column(DataType.STRING(1024))
public value: string; public value: string;
@Column @Column

View file

@ -38,19 +38,20 @@
import { AuthenticationContext, TokenResponse } from 'adal-node'; import { AuthenticationContext, TokenResponse } from 'adal-node';
import { IGBCoreService } from 'botlib'; import { IGBCoreService } from 'botlib';
import { GuaribasInstance } from '../../core.gbapp/models/GBModel';
import { GuaribasAdmin } from '../models/AdminModel'; import { GuaribasAdmin } from '../models/AdminModel';
const UrlJoin = require('url-join'); const UrlJoin = require('url-join');
const msRestAzure = require('ms-rest-azure'); const msRestAzure = require('ms-rest-azure');
const PasswordGenerator = require('strict-password-generator').default; const PasswordGenerator = require('strict-password-generator').default;
/**
* Services for server administration.
*/
export class GBAdminService { export class GBAdminService {
public static GB_PROMPT: string = 'GeneralBots: '; public static GB_PROMPT: string = 'GeneralBots: ';
public static masterBotInstanceId = 0; public static masterBotInstanceId = 0;
public static StrongRegex = new RegExp( public static StrongRegex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*+_-])(?=.{8,})');
'^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*+_-])(?=.{8,})'
);
public core: IGBCoreService; public core: IGBCoreService;
@ -62,26 +63,16 @@ export class GBAdminService {
return msRestAzure.generateUuid(); return msRestAzure.generateUuid();
} }
public static async getADALTokenFromUsername( public static async getADALTokenFromUsername(username: string, password: string) {
username: string, const credentials = await GBAdminService.getADALCredentialsFromUsername(username, password);
password: string
) {
const credentials = await GBAdminService.getADALCredentialsFromUsername(
username,
password
);
const accessToken = credentials.tokenCache._entries[0].accessToken; const accessToken = credentials.tokenCache._entries[0].accessToken;
return accessToken; return accessToken;
} }
public static async getADALCredentialsFromUsername( public static async getADALCredentialsFromUsername(username: string, password: string) {
username: string, const credentials = await msRestAzure.loginWithUsernamePassword(username, password);
password: string
) {
const credentials = await msRestAzure.loginWithUsernamePassword(
username,
password
);
return credentials; return credentials;
} }
@ -114,31 +105,47 @@ export class GBAdminService {
return name; return name;
} }
public async setValue( public async setValue(instanceId: number, key: string, value: string): Promise<GuaribasAdmin> {
instanceId: number,
key: string,
value: string
): Promise<GuaribasAdmin> {
const options = { where: {} }; const options = { where: {} };
options.where = { key: key }; options.where = { key: key };
let admin = await GuaribasAdmin.findOne(options); let admin = await GuaribasAdmin.findOne(options);
if (admin == null) { if (admin === null) {
admin = new GuaribasAdmin(); admin = new GuaribasAdmin();
admin.key = key; admin.key = key;
} }
admin.value = value; admin.value = value;
admin.instanceId = instanceId; admin.instanceId = instanceId;
return admin.save(); return admin.save();
} }
public async updateSecurityInfo(
instanceId: number,
authenticatorTenant: string,
authenticatorAuthorityHostUrl: string,
authenticatorClientId: string,
authenticatorClientSecret: string
): Promise<GuaribasInstance> {
const options = { where: {} };
options.where = { instanceId: instanceId };
const item = await GuaribasInstance.findOne(options);
item.authenticatorTenant = authenticatorTenant;
item.authenticatorAuthorityHostUrl = authenticatorAuthorityHostUrl;
item.authenticatorClientId = authenticatorClientId;
item.authenticatorClientSecret = authenticatorClientSecret;
return item.save();
}
public async getValue(instanceId: number, key: string) { public async getValue(instanceId: number, key: string) {
const options = { where: {} }; const options = { where: {} };
options.where = { key: key, instanceId: instanceId }; options.where = { key: key, instanceId: instanceId };
const obj = await GuaribasAdmin.findOne(options); const obj = await GuaribasAdmin.findOne(options);
return Promise.resolve(obj.value); return Promise.resolve(obj.value);
} }
public async acquireElevatedToken(instanceId): Promise<string> { public async acquireElevatedToken(instanceId: number): Promise<string> {
return new Promise<string>(async (resolve, reject) => { return new Promise<string>(async (resolve, reject) => {
const instance = await this.core.loadInstanceById(instanceId); const instance = await this.core.loadInstanceById(instanceId);
@ -166,21 +173,9 @@ export class GBAdminService {
reject(err); reject(err);
} else { } else {
const token = res as TokenResponse; const token = res as TokenResponse;
await this.setValue( await this.setValue(instanceId, 'accessToken', token.accessToken);
instanceId, await this.setValue(instanceId, 'refreshToken', token.refreshToken);
'accessToken', await this.setValue(instanceId, 'expiresOn', token.expiresOn.toString());
token.accessToken
);
await this.setValue(
instanceId,
'refreshToken',
token.refreshToken
);
await this.setValue(
instanceId,
'expiresOn',
token.expiresOn.toString()
);
resolve(token.accessToken); resolve(token.accessToken);
} }
} }
@ -188,5 +183,4 @@ export class GBAdminService {
} }
}); });
} }
} }

View file

@ -4,7 +4,7 @@ export const Messages = {
welcome: 'Welcome to Pragmatismo.io GeneralBots Administration.', welcome: 'Welcome to Pragmatismo.io GeneralBots Administration.',
which_task: 'Which task do you wanna run now?', which_task: 'Which task do you wanna run now?',
working: (command) => `I'm working on ${command}...`, working: (command) => `I'm working on ${command}...`,
finshed_working: 'Done.', finished_working: 'Done.',
unknown_command: text => unknown_command: text =>
`Well, but ${text} is not a administrative General Bots command, I will try to search for it.`, `Well, but ${text} is not a administrative General Bots command, I will try to search for it.`,
hi: text => `Hello, ${text}.`, hi: text => `Hello, ${text}.`,
@ -13,7 +13,11 @@ export const Messages = {
redeployPackage: text => `Redeploying package ${text}...`, redeployPackage: text => `Redeploying package ${text}...`,
packageUndeployed: text => `Package ${text} undeployed...`, packageUndeployed: text => `Package ${text} undeployed...`,
consent: (url) => `Please, consent access to this app at: [Microsoft Online](${url}).`, consent: (url) => `Please, consent access to this app at: [Microsoft Online](${url}).`,
wrong_password: 'Sorry, wrong password. Please, try again.' wrong_password: 'Sorry, wrong password. Please, try again.',
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_secret: 'Enter the Client Secret:'
}, },
'pt-BR': { 'pt-BR': {
show_video: 'Vou te mostrar um vídeo. Por favor, aguarde...', show_video: 'Vou te mostrar um vídeo. Por favor, aguarde...',

View file

@ -36,11 +36,11 @@
'use strict'; 'use strict';
const UrlJoin = require('url-join');
import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib'; import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { Sequelize } from 'sequelize-typescript'; import { Sequelize } from 'sequelize-typescript';
export class GBWhatsappPackage implements IGBPackage { export class GBAzureDeployerPackage implements IGBPackage {
public sysPackages: IGBPackage[] = null; public sysPackages: IGBPackage[] = null;
public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {} public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {}

View file

@ -224,7 +224,7 @@ export class AzureDeployerService extends GBService {
logger.info(`Deploying Bot Storage...`); logger.info(`Deploying Bot Storage...`);
const administratorLogin = `sa${GBAdminService.getRndReadableIdentifier()}`; const administratorLogin = `sa${GBAdminService.getRndReadableIdentifier()}`;
const administratorPassword = GBAdminService.getRndPassword(); const administratorPassword = GBAdminService.getRndPassword();
const storageServer = `${name}-storage-server`; const storageServer = `${name.toLowerCase()}-storage-server`;
const storageName = `${name}-storage`; const storageName = `${name}-storage`;
await this.createStorageServer( await this.createStorageServer(
name, name,
@ -311,6 +311,11 @@ export class AzureDeployerService extends GBService {
instance.nlpAppId = nlpAppId; instance.nlpAppId = nlpAppId;
logger.info(`Deploying Bot...`); logger.info(`Deploying Bot...`);
// TODO: Default endpoint, will be updated when it runs in production.
instance.botEndpoint = 'http://localhost:4242';
instance = await this.deployBootBot( instance = await this.deployBootBot(
instance, instance,
name, name,

View file

@ -177,12 +177,11 @@ export class GBCoreService implements IGBCoreService {
public async syncDatabaseStructure() { public async syncDatabaseStructure() {
if (GBConfigService.get('STORAGE_SYNC') === 'true') { if (GBConfigService.get('STORAGE_SYNC') === 'true') {
const alter = GBConfigService.get('STORAGE_SYNC_ALTER') === 'true'; const alter = GBConfigService.get('STORAGE_SYNC_ALTER') === 'true';
const force = GBConfigService.get('STORAGE_SYNC_FORCE') === 'true';
logger.info('Syncing database...'); logger.info('Syncing database...');
return this.sequelize.sync({ return this.sequelize.sync({
alter: alter, alter: alter,
force: force force: false // Keep it false this due to data loss danger.
}); });
} else { } else {
const msg = `Database synchronization is disabled.`; const msg = `Database synchronization is disabled.`;

View file

@ -43,19 +43,25 @@ const Fs = require('fs');
const WaitUntil = require('wait-until'); const WaitUntil = require('wait-until');
const express = require('express'); const express = require('express');
const child_process = require('child_process'); const child_process = require('child_process');
const graph = require('@microsoft/microsoft-graph-client');
import { GBMinInstance, IGBCoreService, IGBInstance } from 'botlib'; import { GBMinInstance, IGBCoreService, IGBInstance } from 'botlib';
import { GBError,IGBPackage } from 'botlib'; import { GBError, IGBPackage } from 'botlib';
import { AzureSearch } from 'pragmatismo-io-framework'; import { AzureSearch } from 'pragmatismo-io-framework';
import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService'; import { AzureDeployerService } from '../../azuredeployer.gbapp/services/AzureDeployerService';
import { GuaribasInstance, GuaribasPackage } from '../models/GBModel'; import { GuaribasInstance, GuaribasPackage } from '../models/GBModel';
import { GBAdminService } from './../../admin.gbapp/services/GBAdminService';
import { KBService } from './../../kb.gbapp/services/KBService'; import { KBService } from './../../kb.gbapp/services/KBService';
import { GBConfigService } from './GBConfigService'; import { GBConfigService } from './GBConfigService';
import { GBCoreService } from './GBCoreService'; import { GBCoreService } from './GBCoreService';
import { GBImporter } from './GBImporterService'; import { GBImporter } from './GBImporterService';
import { GBVMService } from './GBVMService'; import { GBVMService } from './GBVMService';
/** Deployer service for bots, themes, ai and more. */ /**
*
* Deployer service for bots, themes, ai and more.
*/
export class GBDeployer { export class GBDeployer {
public static deployFolder = 'packages'; public static deployFolder = 'packages';
public core: IGBCoreService; public core: IGBCoreService;
@ -170,6 +176,25 @@ export class GBDeployer {
}); });
} }
public async deployFromSharePoint(instanceId: number, path: string) {
const adminService = new GBAdminService(this.core);
const accessToken = adminService.acquireElevatedToken(instanceId);
// Initialize Graph client.
const client = graph.Client.init({
authProvider: done => {
done(null, accessToken);
}
});
const events = await client
.api('/me/events')
.select('subject,organizer,start,end')
.orderby('createdDateTime DESC')
.get();
}
public deployScriptToStorage(instanceId: number, localPath: string) {} public deployScriptToStorage(instanceId: number, localPath: string) {}
public deployTheme(localPath: string) { public deployTheme(localPath: string) {
@ -184,6 +209,8 @@ export class GBDeployer {
// }) // })
} }
public async deployPackageFromSharePoint(min: GBMinInstance, path: string) {}
public async deployPackageFromLocalPath(min: GBMinInstance, localPath: string) { public async deployPackageFromLocalPath(min: GBMinInstance, localPath: string) {
const packageType = Path.extname(localPath); const packageType = Path.extname(localPath);
@ -375,18 +402,36 @@ export class GBDeployer {
// Skips .gbapp inside deploy folder. // Skips .gbapp inside deploy folder.
if (!e.startsWith('packages')) { if (!e.startsWith('packages')) {
logger.info(`Deploying app: ${e}...`); logger.info(`Deploying app: ${e}...`);
import(e)
.then(m => { let folder = Path.join(e, 'node_modules');
const p = new m.Package(); if (!Fs.existsSync(folder)) {
p.loadPackage(core, core.sequelize); logger.info(`Installing modules for ${e}...`);
appPackages.push(p); child_process.execSync('npm install', { cwd: e });
logger.info(`App (.gbapp) deployed: ${e}.`); }
folder = Path.join(e, 'dist');
if (!Fs.existsSync()) {
logger.info(`Compiling ${e}...`);
try {
child_process.execSync(Path.join(e, 'node_modules/.bin/tsc'), { cwd: e });
import(e)
.then(m => {
const p = new m.Package();
p.loadPackage(core, core.sequelize);
appPackages.push(p);
logger.info(`App (.gbapp) deployed: ${e}.`);
appPackagesProcessed++;
})
.catch(err => {
logger.error(`Error deploying .gbapp package: ${e}\n${err}`);
appPackagesProcessed++;
});
} catch (error) {
logger.error(`Error compiling .gbapp package ${e}:\n${error.stdout.toString()}`);
appPackagesProcessed++; appPackagesProcessed++;
}) }
.catch(err => { }
logger.error(`Error deploying App (.gbapp): ${e}: ${err}`);
appPackagesProcessed++;
});
} else { } else {
appPackagesProcessed++; appPackagesProcessed++;
} }

View file

@ -56,6 +56,7 @@ import { GuaribasInstance } from '../models/GBModel';
import { Messages } from '../strings'; import { Messages } from '../strings';
import { GBAdminPackage } from './../../admin.gbapp/index'; import { GBAdminPackage } from './../../admin.gbapp/index';
import { GBDeployer } from './GBDeployer'; import { GBDeployer } from './GBDeployer';
import { ConfirmPrompt } from 'botbuilder-dialogs';
/** Minimal service layer for a bot. */ /** Minimal service layer for a bot. */
@ -193,7 +194,7 @@ export class GBMinService {
); );
authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${ authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${
min.instance.authenticatorClientId min.instance.authenticatorClientId
}&redirect_uri=${min.instance.botEndpoint}/${min.instance.botId}/token`; }&redirect_uri=${UrlJoin(min.instance.botEndpoint, min.instance.botId, 'token')}`;
res.redirect(authorizationUrl); res.redirect(authorizationUrl);
}); });
@ -321,6 +322,7 @@ export class GBMinService {
min.dialogs = new DialogSet(dialogState); min.dialogs = new DialogSet(dialogState);
min.dialogs.add(new TextPrompt('textPrompt')); min.dialogs.add(new TextPrompt('textPrompt'));
min.dialogs.add(new ConfirmPrompt('confirmPrompt'));
return { min, adapter, conversationState }; return { min, adapter, conversationState };
} }

View file

@ -32,12 +32,12 @@
'use strict'; 'use strict';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { GBMinInstance, IGBCoreService } from 'botlib'; import { GBMinInstance, IGBCoreService } from 'botlib';
import * as fs from 'fs'; import * as fs from 'fs';
import { DialogClass } from './GBAPIService'; import { DialogClass } from './GBAPIService';
import { GBDeployer } from './GBDeployer'; import { GBDeployer } from './GBDeployer';
import { TSCompiler } from './TSCompiler'; import { TSCompiler } from './TSCompiler';
import { WaterfallDialog } from 'botbuilder-dialogs';
const util = require('util'); const util = require('util');
const logger = require('../../../src/logger'); const logger = require('../../../src/logger');
const vm = require('vm'); const vm = require('vm');
@ -46,10 +46,14 @@ const vb2ts = require('vbscript-to-typescript/dist/converter');
/** /**
* @fileoverview Virtualization services for emulation of BASIC. * @fileoverview Virtualization services for emulation of BASIC.
* This alpha version is using a converter to translate BASIC to TS
* and string replacements to emulate await code.
* http://stevehanov.ca/blog/index.php?id=92 should be used to run it without
* translation and enhance classic BASIC experience.
*/ */
export class GBVMService implements IGBCoreService { export class GBVMService implements IGBCoreService {
private script = new vm.Script(); private readonly script = new vm.Script();
public async loadJS( public async loadJS(
filename: string, filename: string,
@ -82,7 +86,7 @@ export class GBVMService implements IGBCoreService {
// Run JS into the GB context. // Run JS into the GB context.
const jsfile = `bot.js`; const jsfile = `bot.js`;
let localPath = UrlJoin(path, jsfile); const localPath = UrlJoin(path, jsfile);
if (fs.existsSync(localPath)) { if (fs.existsSync(localPath)) {
let code: string = fs.readFileSync(localPath, 'utf8'); let code: string = fs.readFileSync(localPath, 'utf8');
@ -91,12 +95,11 @@ export class GBVMService implements IGBCoreService {
// Finds all hear calls. // Finds all hear calls.
let parsedCode = code; let parsedCode = code;
let hearExp = /(\w+).*hear.*\(\)/; const hearExp = /(\w+).*hear.*\(\)/;
let match1; let match1;
while ((match1 = hearExp.exec(code))) { while ((match1 = hearExp.exec(code))) {
let pos = 0; let pos = 0;
// Writes async body. // Writes async body.
@ -110,7 +113,7 @@ export class GBVMService implements IGBCoreService {
pos = pos + match1.index; pos = pos + match1.index;
let tempCode = code.substring(pos + match1[0].length + 1); let tempCode = code.substring(pos + match1[0].length + 1);
let start = pos; const start = pos;
// Balances code blocks and checks for exits. // Balances code blocks and checks for exits.
@ -143,15 +146,15 @@ export class GBVMService implements IGBCoreService {
code = parsedCode; code = parsedCode;
} }
parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\btalk\b/g, function($0, $1) { parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\btalk\b/g, ($0, $1) => {
return $1 == undefined ? 'this.talk' : $1; return $1 == undefined ? 'this.talk' : $1;
}); });
parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\bhear\b/g, function($0, $1) { parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\bhear\b/g, ($0, $1) => {
return $1 == undefined ? 'this.hear' : $1; return $1 == undefined ? 'this.hear' : $1;
}); });
parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\bsendEmail\b/g, function($0, $1) { parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\bsendEmail\b/g, ($0, $1) => {
return $1 == undefined ? 'this.sendEmail' : $1; return $1 == undefined ? 'this.sendEmail' : $1;
}); });
@ -183,8 +186,8 @@ export class GBVMService implements IGBCoreService {
const cbId = step.activeDialog.state.cbId; const cbId = step.activeDialog.state.cbId;
const cb = min.cbMap[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 }); // TODO: Necessary or min.sandbox?
await step.endDialog(); await step.endDialog();
return await cb(step.result); return await cb(step.result);

View file

@ -40,6 +40,18 @@ import * as ts from 'typescript';
const logger = require('../../../src/logger'); const logger = require('../../../src/logger');
export class TSCompiler { export class TSCompiler {
private static shouldIgnoreError(diagnostic) {
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
if (message.indexOf('Cannot find name') >= 0 || message.indexOf('Cannot use imports') >= 0) {
return true;
}
return false;
}
public compile( public compile(
fileNames: string[], fileNames: string[],
options: ts.CompilerOptions = { options: ts.CompilerOptions = {
@ -61,15 +73,19 @@ export class TSCompiler {
const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
allDiagnostics.forEach(diagnostic => { allDiagnostics.forEach(diagnostic => {
if (diagnostic.file) { if (!TSCompiler.shouldIgnoreError(diagnostic)) {
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
logger.error(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
} else { if (diagnostic.file) {
logger.error(`${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`); const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
logger.error(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
} else {
logger.error(`${message}`);
}
} }
}); });
return emitResult; return emitResult;
} }
} }

View file

@ -60,14 +60,8 @@ export class AskDialog extends IGBDialog {
new WaterfallDialog('/answerEvent', [ new WaterfallDialog('/answerEvent', [
async step => { async step => {
if (step.options && step.options['questionId']) { if (step.options && step.options['questionId']) {
const question = await service.getQuestionById( const question = await service.getQuestionById(min.instance.instanceId, step.options['questionId']);
min.instance.instanceId, const answer = await service.getAnswerById(min.instance.instanceId, question.answerId);
step.options['questionId']
);
const answer = await service.getAnswerById(
min.instance.instanceId,
question.answerId
);
// Sends the answer to all outputs, including projector. // Sends the answer to all outputs, including projector.
@ -106,10 +100,7 @@ export class AskDialog extends IGBDialog {
// Spells check the input text before sending Search or NLP. // Spells check the input text before sending Search or NLP.
if (min.instance.spellcheckerKey) { if (min.instance.spellcheckerKey) {
const data = await AzureText.getSpelledText( const data = await AzureText.getSpelledText(min.instance.spellcheckerKey, text);
min.instance.spellcheckerKey,
text
);
if (data != text) { if (data != text) {
logger.info(`Spelling corrected: ${data}`); logger.info(`Spelling corrected: ${data}`);
@ -121,12 +112,7 @@ export class AskDialog extends IGBDialog {
user.lastQuestion = text; user.lastQuestion = text;
await min.userProfile.set(step.context, user); await min.userProfile.set(step.context, user);
const resultsA = await service.ask( const resultsA = await service.ask(min.instance, text, min.instance.searchScore, user.subjects);
min.instance,
text,
min.instance.searchScore,
user.subjects
);
// If there is some result, answer immediately. // If there is some result, answer immediately.
@ -139,30 +125,19 @@ export class AskDialog extends IGBDialog {
// Sends the answer to all outputs, including projector. // Sends the answer to all outputs, including projector.
await service.sendAnswer( await service.sendAnswer(min.conversationalService, step, resultsA.answer);
min.conversationalService,
step,
resultsA.answer
);
// Goes to ask loop, again. // Goes to ask loop, again.
return await step.replaceDialog('/ask', { isReturning: true }); return await step.replaceDialog('/ask', { isReturning: true });
} else { } else {
// Second time running Search, now with no filter. // Second time running Search, now with no filter.
const resultsB = await service.ask( const resultsB = await service.ask(min.instance, text, min.instance.searchScore, null);
min.instance,
text,
min.instance.searchScore,
null
);
// If there is some result, answer immediately. // If there is some result, answer immediately.
if (resultsB && resultsB.answer) { if (resultsB && resultsB.answer) {
// Saves some context info. // Saves some context info.
const user = await min.userProfile.get(step.context, {}); const user = await min.userProfile.get(step.context, {});
@ -173,24 +148,16 @@ export class AskDialog extends IGBDialog {
// Informs user that a broader search will be used. // Informs user that a broader search will be used.
if (user.subjects.length > 0) { if (user.subjects.length > 0) {
const subjectText = `${KBService.getSubjectItemsSeparatedBySpaces( const subjectText = `${KBService.getSubjectItemsSeparatedBySpaces(user.subjects)}`;
user.subjects
)}`;
await step.context.sendActivity(Messages[locale].wider_answer); await step.context.sendActivity(Messages[locale].wider_answer);
} }
// Sends the answer to all outputs, including projector. // Sends the answer to all outputs, including projector.
await service.sendAnswer( await service.sendAnswer(min.conversationalService, step, resultsB.answer);
min.conversationalService,
step,
resultsB.answer
);
return await step.replaceDialog('/ask', { isReturning: true }); return await step.replaceDialog('/ask', { isReturning: true });
} else { } else {
if ( if (!(await min.conversationalService.routeNLP(step, min, text))) {
!(await min.conversationalService.routeNLP(step, min, text))
) {
await step.context.sendActivity(Messages[locale].did_not_find); await step.context.sendActivity(Messages[locale].did_not_find);
return await step.replaceDialog('/ask', { isReturning: true }); return await step.replaceDialog('/ask', { isReturning: true });
} }
@ -229,7 +196,11 @@ export class AskDialog extends IGBDialog {
return await step.next(); return await step.next();
}, },
async step => { async step => {
return await step.replaceDialog('/answer', { query: step.result }); if (step.result) {
return await step.replaceDialog('/answer', { query: step.result });
} else {
return await step.next();
}
} }
]) ])
); );

View file

@ -64,7 +64,7 @@ export class MenuDialog extends IGBDialog {
let rootSubjectId = null; let rootSubjectId = null;
if (step.options && step.options['data']) { if (step.options && step.options['data']) {
const subject = step.result.data; const subject = step.options['data'];
// If there is a shortcut specified as subject destination, go there. // If there is a shortcut specified as subject destination, go there.
@ -149,7 +149,6 @@ export class MenuDialog extends IGBDialog {
); );
} }
await step.replaceDialog('/ask', {});
} else { } else {
msg.attachments = attachments; msg.attachments = attachments;
await step.context.sendActivity(msg); await step.context.sendActivity(msg);
@ -158,16 +157,6 @@ export class MenuDialog extends IGBDialog {
const user = await min.userProfile.get(step.context, {}); const user = await min.userProfile.get(step.context, {});
user.isAsking = true; user.isAsking = true;
return await step.next(); return await step.next();
},
async step => {
const text = step.result;
const locale = step.context.activity.locale;
if (AzureText.isIntentNo(locale, text)) {
await step.replaceDialog('/feedback');
} else {
await step.replaceDialog('/ask');
}
return await step.next();
} }
])); ]));
} }

View file

@ -36,6 +36,7 @@
"export-name":false, "export-name":false,
"no-relative-imports": false, "no-relative-imports": false,
"no-backbone-get-set-outside-model": false, "no-backbone-get-set-outside-model": false,
"max-line-length": [true,{"limit":120,"ignore-pattern":"^\\s+\\*"}] "max-line-length": [true,{"limit":120,"ignore-pattern":"^\\s+\\*"}],
"await-promise": [true, "Bluebird"]
} }
} }