feat(basic): General Bots BASIC 2.0 with new keywords and parenthesis only when needed.

This commit is contained in:
Rodrigo Rodriguez 2019-02-23 13:17:21 -03:00
parent d7c0e5c3be
commit 3cc92ecec7
13 changed files with 1518 additions and 1597 deletions

2516
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -51,7 +51,7 @@
"@microsoft/microsoft-graph-client": "1.4.0", "@microsoft/microsoft-graph-client": "1.4.0",
"@semantic-release/exec": "^3.3.2", "@semantic-release/exec": "^3.3.2",
"adal-node": "0.1.28", "adal-node": "0.1.28",
"async": "2.6.1", "async": "2.6.2",
"async-promises": "0.2.1", "async-promises": "0.2.1",
"azure-arm-cognitiveservices": "2.4.1", "azure-arm-cognitiveservices": "2.4.1",
"azure-arm-resource": "7.3.0", "azure-arm-resource": "7.3.0",
@ -60,39 +60,39 @@
"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.2.1",
"botbuilder-ai": "^4.2.0", "botbuilder-ai": "^4.2.1",
"botbuilder-azure": "^4.2.0", "botbuilder-azure": "^4.2.1",
"botbuilder-choices": "^4.0.0-preview1.2", "botbuilder-choices": "^4.0.0-preview1.2",
"botbuilder-dialogs": "^4.2.0", "botbuilder-dialogs": "^4.2.1",
"botbuilder-prompts": "^4.0.0-preview1.2", "botbuilder-prompts": "^4.0.0-preview1.2",
"botlib": "^0.1.10", "botlib": "^0.1.12",
"chai": "4.2.0", "chai": "4.2.0",
"child_process": "^1.0.2", "child_process": "^1.0.2",
"chokidar": "2.0.4", "chokidar": "2.1.2",
"cli-spinner": "^0.2.8", "cli-spinner": "^0.2.8",
"csv-parse": "4.3.1", "csv-parse": "4.3.3",
"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",
"fs-extra": "7.0.1", "fs-extra": "7.0.1",
"fs-walk": "0.0.2",
"ip": "^1.1.5", "ip": "^1.1.5",
"js-beautify": "^1.8.9",
"localize": "0.4.7", "localize": "0.4.7",
"marked": "0.6.0", "marked": "0.6.1",
"mocha": "5.2.0", "mocha": "6.0.1",
"mocha-typescript": "1.1.17", "mocha-typescript": "1.1.17",
"ms": "2.1.1", "ms": "2.1.1",
"ms-rest-azure": "2.6.0", "ms-rest-azure": "2.6.0",
"nexmo": "2.4.1", "nexmo": "2.4.1",
"ngrok": "^3.1.0", "ngrok": "^3.1.1",
"nyc": "^13.1.0", "nyc": "^13.3.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": "^3.0.0", "public-ip": "^3.0.0",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
"request-promise-native": "1.0.5", "request-promise-native": "1.0.7",
"scanf": "^1.0.2", "scanf": "^1.0.2",
"sequelize": "4.42.0", "sequelize": "4.42.0",
"sequelize-typescript": "0.6.7", "sequelize-typescript": "0.6.7",
@ -100,13 +100,13 @@
"simple-git": "^1.107.0", "simple-git": "^1.107.0",
"sqlite3": "4.0.6", "sqlite3": "4.0.6",
"strict-password-generator": "^1.1.2", "strict-password-generator": "^1.1.2",
"swagger-client": "3.8.22", "swagger-client": "3.8.24",
"tedious": "4.1.3", "tedious": "5.0.3",
"ts-node": "8.0.2", "ts-node": "8.0.2",
"typedoc": "0.14.2", "typedoc": "0.14.2",
"typedoc-plugin-external-module-name": "^2.0.0", "typedoc-plugin-external-module-name": "^2.0.0",
"typedoc-plugin-markdown": "^1.1.25", "typedoc-plugin-markdown": "^1.1.26",
"typescript": "3.3.1", "typescript": "3.3.3333",
"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",
@ -121,13 +121,13 @@
"@semantic-release/npm": "^5.1.4", "@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.6",
"@types/sequelize": "4.27.34", "@types/sequelize": "4.27.37",
"@types/url-join": "4.0.0", "@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.5", "commitizen": "^3.0.7",
"coveralls": "^3.0.2", "coveralls": "^3.0.3",
"cz-conventional-changelog": "^2.1.0", "cz-conventional-changelog": "^2.1.0",
"dependency-check": "3.3.0", "dependency-check": "3.3.0",
"deps-ok": "1.4.1", "deps-ok": "1.4.1",

View file

@ -178,7 +178,7 @@ export class AdminDialog extends IGBDialog {
); );
} }
private static setupSecurityDialogs(min: any) { private static setupSecurityDialogs(min: GBMinInstance) {
min.dialogs.add( min.dialogs.add(
new WaterfallDialog('/setupSecurity', [ new WaterfallDialog('/setupSecurity', [
async step => { async step => {

View file

@ -36,7 +36,6 @@ const _ = require('lodash');
const Parse = require('csv-parse'); const Parse = require('csv-parse');
const Async = require('async'); const Async = require('async');
const UrlJoin = require('url-join'); const UrlJoin = require('url-join');
const Walk = require('fs-walk');
const logger = require('../../../src/logger'); const logger = require('../../../src/logger');
const Swagger = require('swagger-client'); const Swagger = require('swagger-client');
const rp = require('request-promise'); const rp = require('request-promise');

View file

@ -35,6 +35,7 @@
import { TurnContext } from 'botbuilder'; import { TurnContext } from 'botbuilder';
import { WaterfallStepContext } from 'botbuilder-dialogs'; import { WaterfallStepContext } from 'botbuilder-dialogs';
import { GBMinInstance } from 'botlib'; import { GBMinInstance } from 'botlib';
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService';
const WaitUntil = require('wait-until'); const WaitUntil = require('wait-until');
class SysClass { class SysClass {
@ -44,7 +45,11 @@ class SysClass {
this.min = min; this.min = min;
} }
public async deployBot( public generatePassword(){
return GBAdminService.getRndPassword();
}
public async createABotFarmUsing(
botId, botId,
description, description,
location, location,

View file

@ -231,7 +231,7 @@ export class GBDeployer {
case '.gbdialog': case '.gbdialog':
const vm = new GBVMService(); const vm = new GBVMService();
return vm.loadJS(localPath, min, this.core, this, localPath); return vm.loadDialogPackage(localPath, min, this.core, this);
default: default:
const err = GBError.create(`GuaribasBusinessError: Unknown package type: ${packageType}.`); const err = GBError.create(`GuaribasBusinessError: Unknown package type: ${packageType}.`);

View file

@ -45,6 +45,7 @@ const AuthenticationContext = require('adal-node').AuthenticationContext;
import { AutoSaveStateMiddleware, BotFrameworkAdapter, ConversationState, MemoryStorage, UserState } from 'botbuilder'; import { AutoSaveStateMiddleware, BotFrameworkAdapter, ConversationState, MemoryStorage, UserState } from 'botbuilder';
import { ConfirmPrompt } from 'botbuilder-dialogs';
import { GBMinInstance, IGBAdminService, IGBConversationalService, IGBCoreService, IGBPackage } from 'botlib'; import { GBMinInstance, IGBAdminService, IGBConversationalService, IGBCoreService, IGBPackage } from 'botlib';
import { GBAnalyticsPackage } from '../../analytics.gblib'; import { GBAnalyticsPackage } from '../../analytics.gblib';
import { GBCorePackage } from '../../core.gbapp'; import { GBCorePackage } from '../../core.gbapp';
@ -56,7 +57,6 @@ 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. */
@ -108,7 +108,7 @@ export class GBMinService {
const uiPackage = 'default.gbui'; const uiPackage = 'default.gbui';
server.use('/', express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build'))); server.use('/', express.static(UrlJoin(GBDeployer.deployFolder, uiPackage, 'build')));
Promise.all( await Promise.all(
instances.map(async instance => { instances.map(async instance => {
// Gets the authorization key for each instance from Bot Service. // Gets the authorization key for each instance from Bot Service.
@ -119,39 +119,7 @@ export class GBMinService {
server.get('/instances/:botId', (req, res) => { server.get('/instances/:botId', (req, res) => {
(async () => { (async () => {
// Returns the instance object to clients requesting bot info. await this.sendInstanceToClient(req, bootInstance, res, webchatToken);
let botId = req.params.botId;
if (botId === '[default]') {
botId = bootInstance.botId;
}
const instance = await this.core.loadInstance(botId);
if (instance) {
const speechToken = await this.getSTSToken(instance);
let theme = instance.theme;
if (!theme) {
theme = 'default.gbtheme';
}
res.send(
JSON.stringify({
instanceId: instance.instanceId,
botId: botId,
theme: theme,
secret: instance.webchatKey, // TODO: Use token.
speechToken: speechToken,
conversationId: webchatToken.conversationId,
authenticatorTenant: instance.authenticatorTenant,
authenticatorClientId: instance.authenticatorClientId
})
);
} else {
const error = `Instance not found: ${botId}.`;
res.sendStatus(error);
logger.error(error);
}
})(); })();
}); });
@ -171,7 +139,7 @@ export class GBMinService {
const url = `/api/messages/${instance.botId}`; const url = `/api/messages/${instance.botId}`;
server.post(url, async (req, res) => { server.post(url, async (req, res) => {
return await this.receiver(adapter, req, res, conversationState, min, instance, appPackages); await this.receiver(adapter, req, res, conversationState, min, instance, appPackages);
}); });
logger.info(`GeneralBots(${instance.engineName}) listening on: ${url}.`); logger.info(`GeneralBots(${instance.engineName}) listening on: ${url}.`);
@ -186,71 +154,94 @@ export class GBMinService {
// Clients get redirected here in order to create an OAuth authorize url and redirect them to AAD. // 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 // There they will authenticate and give their consent to allow this app access to
// some resource they own. // some resource they own.
server.get(`/${min.instance.botId}/auth`, function(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')}`;
res.redirect(authorizationUrl); this.handleOAuthRequests(server, min);
});
// After consent is granted AAD redirects here. The ADAL library // After consent is granted AAD redirects here. The ADAL library
// is invoked via the AuthenticationContext and retrieves an // is invoked via the AuthenticationContext and retrieves an
// access token that can be used to access the user owned resource. // access token that can be used to access the user owned resource.
server.get(`/${min.instance.botId}/token`, async (req, res) => { this.handleOAuthTokenRequests(server, min, instance);
const state = await min.adminService.getValue(min.instance.instanceId, 'AntiCSRFAttackState');
if (req.query.state !== state) {
const msg = 'WARNING: state field was not provided as anti-CSRF token';
logger.error(msg);
throw new Error(msg);
}
const authenticationContext = new AuthenticationContext(
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'),
resource,
instance.authenticatorClientId,
instance.authenticatorClientSecret,
async (err, token) => {
if (err) {
const msg = `Error acquiring token: ${err}`;
logger.error(msg);
res.send(msg);
} else {
await this.adminService.setValue(instance.instanceId, 'refreshToken', token.refreshToken);
await this.adminService.setValue(instance.instanceId, 'accessToken', token.accessToken);
await this.adminService.setValue(instance.instanceId, 'expiresOn', token.expiresOn.toString());
await this.adminService.setValue(instance.instanceId, 'AntiCSRFAttackState', null);
res.redirect(min.instance.botEndpoint);
}
}
);
});
}) })
); );
} }
private handleOAuthTokenRequests(server: any, min: GBMinInstance, instance: GuaribasInstance) {
server.get(`/${min.instance.botId}/token`, async (req, res) => {
const state = await min.adminService.getValue(min.instance.instanceId, 'AntiCSRFAttackState');
if (req.query.state !== state) {
const msg = 'WARNING: state field was not provided as anti-CSRF token';
logger.error(msg);
throw new Error(msg);
}
const authenticationContext = new AuthenticationContext(
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'),
resource, instance.authenticatorClientId, instance.authenticatorClientSecret, async (err, token) => {
if (err) {
const msg = `Error acquiring token: ${err}`;
logger.error(msg);
res.send(msg);
} else {
await this.adminService.setValue(instance.instanceId, 'refreshToken', token.refreshToken);
await this.adminService.setValue(instance.instanceId, 'accessToken', token.accessToken);
await this.adminService.setValue(instance.instanceId, 'expiresOn', token.expiresOn.toString());
await this.adminService.setValue(instance.instanceId, 'AntiCSRFAttackState', null);
res.redirect(min.instance.botEndpoint);
}
});
});
}
private handleOAuthRequests(server: any, min: GBMinInstance) {
server.get(`/${min.instance.botId}/auth`, function (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')}`;
res.redirect(authorizationUrl);
});
}
/**
* Returns the instance object to clients requesting bot info.
*/
private async sendInstanceToClient(req, bootInstance: GuaribasInstance, res: any, webchatToken: any) {
let botId = req.params.botId;
if (botId === '[default]') {
botId = bootInstance.botId;
}
const instance = await this.core.loadInstance(botId);
if (instance) {
const speechToken = await this.getSTSToken(instance);
let theme = instance.theme;
if (!theme) {
theme = 'default.gbtheme';
}
res.send(JSON.stringify({
instanceId: instance.instanceId,
botId: botId,
theme: theme,
secret: instance.webchatKey,
speechToken: speechToken,
conversationId: webchatToken.conversationId,
authenticatorTenant: instance.authenticatorTenant,
authenticatorClientId: instance.authenticatorClientId
}));
} else {
const error = `Instance not found: ${botId}.`;
res.sendStatus(error);
logger.error(error);
}
}
/** /**
* Get Webchat key from Bot Service. * Get Webchat key from Bot Service.
* *
* @param instance The Bot instance. * @param instance The Bot instance.
* *
*/ */
public async getWebchatToken(instance: any) { private async getWebchatToken(instance: any) {
const options = { const options = {
url: 'https://directline.botframework.com/v3/directline/tokens/generate', url: 'https://directline.botframework.com/v3/directline/tokens/generate',
method: 'POST', method: 'POST',
@ -276,7 +267,7 @@ export class GBMinService {
* @param instance The general bot instance. * @param instance The general bot instance.
* *
*/ */
public async getSTSToken(instance: any) { private async getSTSToken(instance: any) {
// TODO: Make dynamic: https://CHANGE.api.cognitive.microsoft.com/sts/v1.0 // TODO: Make dynamic: https://CHANGE.api.cognitive.microsoft.com/sts/v1.0
const options = { const options = {
@ -327,7 +318,7 @@ export class GBMinService {
return { min, adapter, conversationState }; return { min, adapter, conversationState };
} }
private invokeLoadBot(appPackages: any[], min: any, server: any) { private invokeLoadBot(appPackages: any[], min: GBMinInstance, server: any) {
const sysPackages = new Array<IGBPackage>(); const sysPackages = new Array<IGBPackage>();
// NOTE: A semicolon is necessary before this line. // NOTE: A semicolon is necessary before this line.
[ [
@ -348,12 +339,12 @@ export class GBMinService {
p.channel.received(req, res); p.channel.received(req, res);
}); });
} }
}, this); }, this);
appPackages.forEach(e => { appPackages.forEach(e => {
e.sysPackages = sysPackages; e.sysPackages = sysPackages;
e.loadBot(min); e.loadBot(min);
}, this); }, this);
} }
/** /**
@ -364,11 +355,13 @@ export class GBMinService {
req: any, req: any,
res: any, res: any,
conversationState: ConversationState, conversationState: ConversationState,
min: any, min: GBMinInstance,
instance: any, instance: any,
appPackages: any[] appPackages: any[]
) { ) {
return await adapter.processActivity(req, res, async context => {
await adapter.processActivity(req, res, async context => {
// Get loaded user state // Get loaded user state
const state = await conversationState.get(context); const state = await conversationState.get(context);
const step = await min.dialogs.createContext(context, state); const step = await min.dialogs.createContext(context, state);
@ -412,32 +405,7 @@ export class GBMinService {
// Processes messages. // Processes messages.
} else if (context.activity.type === 'message') { } else if (context.activity.type === 'message') {
// Checks for /admin request. // Checks for /admin request.
if (context.activity.text === 'alpha-vba') { await this.processMessageActivity(context, min, step);
min.sandbox.context = context;
min.sandbox.step = step;
min.sandbox['bot'].bind(min.sandbox);
await min.sandbox['bot']();
} else if (context.activity.text === 'admin') {
await step.beginDialog('/admin');
// Checks for /menu JSON signature.
} else if (context.activity.text.startsWith('{"title"')) {
await step.beginDialog('/menu', {
data: 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) {
await step.continueDialog();
} else {
await step.beginDialog('/answer', {
query: context.activity.text
});
}
}
// Processes events. // Processes events.
} else if (context.activity.type === 'event') { } else if (context.activity.type === 'event') {
@ -445,31 +413,7 @@ export class GBMinService {
// TODO: Understand MSFT changes: await step.endAll(); // TODO: Understand MSFT changes: await step.endAll();
if (context.activity.name === 'whoAmI') { await this.processEventActivity(context, step);
await step.beginDialog('/whoAmI');
} else if (context.activity.name === 'showSubjects') {
await step.beginDialog('/menu');
} else if (context.activity.name === 'giveFeedback') {
await step.beginDialog('/feedback', {
fromMenu: true
});
} else if (context.activity.name === 'showFAQ') {
await step.beginDialog('/faq');
} else if (context.activity.name === 'answerEvent') {
await step.beginDialog('/answerEvent', {
questionId: (context.activity as any).data,
fromFaq: true
});
} else if (context.activity.name === 'quality') {
await step.beginDialog('/quality', {
score: (context.activity as any).data
});
} else if (context.activity.name === 'updateToken') {
const token = (context.activity as any).data;
await step.beginDialog('/adminUpdateToken', { token: token });
} else {
await step.continueDialog();
}
} }
await conversationState.saveChanges(context, true); await conversationState.saveChanges(context, true);
} catch (error) { } catch (error) {
@ -481,4 +425,69 @@ export class GBMinService {
} }
}); });
} }
private async processEventActivity(context, step: any) {
if (context.activity.name === 'whoAmI') {
await step.beginDialog('/whoAmI');
} else if (context.activity.name === 'showSubjects') {
await step.beginDialog('/menu');
} else if (context.activity.name === 'giveFeedback') {
await step.beginDialog('/feedback', {
fromMenu: true
});
} else if (context.activity.name === 'showFAQ') {
await step.beginDialog('/faq');
} else if (context.activity.name === 'answerEvent') {
await step.beginDialog('/answerEvent', {
questionId: (context.activity).data,
fromFaq: true
});
} else if (context.activity.name === 'quality') {
await step.beginDialog('/quality', {
score: (context.activity).data
});
} else if (context.activity.name === 'updateToken') {
const token = (context.activity).data;
await step.beginDialog('/adminUpdateToken', { token: token });
} else {
await step.continueDialog();
}
}
private async processMessageActivity(context, min: GBMinInstance, step: any) {
// Direct script invoking by itent name.
let mainMethod = min.scriptMap[context.activity.text];
if (mainMethod != undefined) {
min.sandbox.context = context;
min.sandbox.step = step;
min.sandbox[mainMethod].bind(min.sandbox);
await min.sandbox[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)
});
// Otherwise, continue to the active dialog in the stack.
} else {
const user = await min.userProfile.get(context, {});
if (step.activeDialog) {
await step.continueDialog();
} else {
await step.beginDialog('/answer', {
query: context.activity.text
});
}
}
}
} }

View file

@ -35,19 +35,22 @@
import { WaterfallDialog } from 'botbuilder-dialogs'; 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 { GBDeployer } from './GBDeployer'; import { GBDeployer } from './GBDeployer';
import { TSCompiler } from './TSCompiler'; import { TSCompiler } from './TSCompiler';
const util = require('util'); import GBAPIService from './GBAPIService';
import DialogClass from './GBAPIService';
const walkPromise = require('walk-promise');
const logger = require('../../../src/logger'); const logger = require('../../../src/logger');
const vm = require('vm'); const vm = require('vm');
const UrlJoin = require('url-join'); const UrlJoin = require('url-join');
const vb2ts = require('vbscript-to-typescript/dist/converter'); const vb2ts = require('vbscript-to-typescript/dist/converter');
var beautify = require('js-beautify').js;
/** /**
* @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 * This alpha version is using a hack in form of converter to
* and string replacements to emulate await code. * translate BASIC to TSand string replacements to emulate await code.
* See http://jsfiddle.net/roderick/dym05hsy for more info on vb2ts, so
* http://stevehanov.ca/blog/index.php?id=92 should be used to run it without * http://stevehanov.ca/blog/index.php?id=92 should be used to run it without
* translation and enhance classic BASIC experience. * translation and enhance classic BASIC experience.
*/ */
@ -55,41 +58,95 @@ const vb2ts = require('vbscript-to-typescript/dist/converter');
export class GBVMService implements IGBCoreService { export class GBVMService implements IGBCoreService {
private readonly script = new vm.Script(); private readonly script = new vm.Script();
public async loadJS( public async loadDialogPackage(folder: string, min: GBMinInstance, core: IGBCoreService, deployer: GBDeployer) {
filename: string, const files = await walkPromise(folder);
min: GBMinInstance,
core: IGBCoreService,
deployer: GBDeployer,
localPath: string
): Promise<void> {
const path = 'packages/default.gbdialog';
const file = 'bot.vbs';
const source = UrlJoin(path, file);
// Example when handled through fs.watch() listener return Promise.all(
fs.watchFile(source, async (curr, prev) => { files.map(async file => {
await this.run(source, path, min, deployer, filename); if (
}); file.name.endsWith('.vbs') ||
await this.run(source, path, min, deployer, filename); file.name.endsWith('.vb') ||
this.addHearDialog(min); file.name.endsWith('.basic') ||
file.name.endsWith('.bas')
) {
const mainName = file.name.replace(/\-|\./g, '');
// min.scriptMap[file.name] = ;
const filename = UrlJoin(folder, file.name);
fs.watchFile(filename, async () => {
await this.run(filename, min, deployer, mainName);
});
await this.run(filename, min, deployer, mainName);
this.addHearDialog(min);
}
})
);
} }
public async run(source: any, path: string, min: any, deployer: GBDeployer, filename: string) { /**
// Converts VBS into TS. * Converts General Bots BASIC
*
*
* @param code General Bots BASIC
*/
public convertGBASICToVBS(code: string) {
// Start and End of VB2TS tags of processing.
vb2ts.convertFile(source); code = `<%\n${code}`;
// Keywords from General Bots BASIC.
code = code.replace(/(hear)\s*(\w+)/g, ($0, $1, $2) => {
return `${$2} = hear()`;
});
code = code.replace(/(wait)\s*(\d+)/g, ($0, $1, $2) => {
return `sys().wait(${$2})`;
});
code = code.replace(/(generate a password)/g, ($0, $1) => {
return 'let password = sys().generatePassword()';
});
code = code.replace(/(create a bot farm using)(\s)(.*)/g, ($0, $1, $2, $3) => {
return `sys().createABotFarmUsing (${$3})`;
});
code = code.replace(/(talk)(\s)(.*)/g, ($0, $1, $2, $3) => {
return `talk (${$3})\n`;
});
code = `${code}\n%>`;
return code;
}
public async run(filename: any, min: GBMinInstance, deployer: GBDeployer, mainName: string) {
// Converts General Bots BASIC into regular VBS
const basicCode: string = fs.readFileSync(filename, 'utf8');
const vbsCode = await this.convertGBASICToVBS(basicCode);
const vbsFile = `${filename}.compiled`;
fs.writeFileSync(vbsFile, vbsCode, 'utf8');
// Converts VBS into TS.
vb2ts.convertFile(vbsFile);
// Convert TS into JS. // Convert TS into JS.
const tsfile = `bot.ts`; const tsfile: string = `${filename}.ts`;
let tsCode: string = fs.readFileSync(tsfile, 'utf8');
tsCode = tsCode.replace(/export.*\n/g, `export function ${mainName}() {`);
fs.writeFileSync(tsfile, tsCode);
const tsc = new TSCompiler(); const tsc = new TSCompiler();
tsc.compile([UrlJoin(path, tsfile)]); tsc.compile([tsfile]);
// Run JS into the GB context. // Run JS into the GB context.
const jsfile = `bot.js`; const jsfile = `${tsfile}.js`.replace('.ts', '');
const localPath = UrlJoin(path, jsfile);
if (fs.existsSync(jsfile)) {
let code: string = fs.readFileSync(jsfile, 'utf8');
if (fs.existsSync(localPath)) {
let code: string = fs.readFileSync(localPath, 'utf8');
code = code.replace(/^.*exports.*$/gm, ''); code = code.replace(/^.*exports.*$/gm, '');
// Finds all hear calls. // Finds all hear calls.
@ -104,7 +161,7 @@ export class GBVMService implements IGBCoreService {
// Writes async body. // Writes async body.
const variable = match1[1]; // variable = hear(); const variable = match1[1]; // Construct variable = hear ().
parsedCode = code.substring(pos, pos + match1.index); parsedCode = code.substring(pos, pos + match1.index);
parsedCode += `hear (async (${variable}) => {\n`; parsedCode += `hear (async (${variable}) => {\n`;
@ -146,22 +203,10 @@ export class GBVMService implements IGBCoreService {
code = parsedCode; code = parsedCode;
} }
parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\btalk\b/g, ($0, $1) => { parsedCode = this.handleThisAndAwait(parsedCode);
return $1 == undefined ? 'this.talk' : $1;
});
parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\bhear\b/g, ($0, $1) => { parsedCode = beautify(parsedCode, { indent_size: 2, space_in_empty_paren: true })
return $1 == undefined ? 'this.hear' : $1; fs.writeFileSync(jsfile, parsedCode);
});
parsedCode = parsedCode.replace(/("[^"]*"|'[^']*')|\bsendEmail\b/g, ($0, $1) => {
return $1 == undefined ? 'this.sendEmail' : $1;
});
parsedCode = parsedCode.replace(/this\./gm, 'await this.');
parsedCode = parsedCode.replace(/function/gm, 'async function');
fs.writeFileSync(localPath, parsedCode);
const sandbox: DialogClass = new DialogClass(min); const sandbox: DialogClass = new DialogClass(min);
const context = vm.createContext(sandbox); const context = vm.createContext(sandbox);
@ -172,6 +217,28 @@ export class GBVMService implements IGBCoreService {
} }
} }
private handleThisAndAwait(code: string) {
// this insertion.
code = code.replace(/sys\(\)/g, 'this.sys()');
code = code.replace(/("[^"]*"|'[^']*')|\btalk\b/g, ($0, $1) => {
return $1 == undefined ? 'this.talk' : $1;
});
code = code.replace(/("[^"]*"|'[^']*')|\bhear\b/g, ($0, $1) => {
return $1 == undefined ? 'this.hear' : $1;
});
code = code.replace(/("[^"]*"|'[^']*')|\bsendEmail\b/g, ($0, $1) => {
return $1 == undefined ? 'this.sendEmail' : $1;
});
// await insertion.
code = code.replace(/this\./gm, 'await this.');
code = code.replace(/function/gm, 'async function');
return code;
}
private addHearDialog(min) { private addHearDialog(min) {
min.dialogs.add( min.dialogs.add(
new WaterfallDialog('/hear', [ new WaterfallDialog('/hear', [

View file

@ -1,54 +1,20 @@
<% ' General Bots Copyright (c) Pragmatismo.io. All rights reserved. Licensed under the AGPL-3.0.
'****************************************************************************
' ( )_ _
' _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _
' ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\
' | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) )
' | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/'
' | | ( )_) |
' (_) \___/'
'
' General Bots Copyright (c) Pragmatismo.io. All rights reserved.
' Licensed under the AGPL-3.0.
'
' This BASIC file is based on this JavaScript file by Rodrigo Ruotolo:
' -> http://jsfiddle.net/roderick/dym05hsy
'
' According to our dual licensing model, this program can be used either
' under the terms of the GNU Affero General Public License, version 3,
' 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.
'
'****************************************************************************
talk ("How many installments do you want to pay your Credit?") talk "How many installments do you want to pay your Credit?"
installments = hear () hear installments
if installments > 60 then if installments > 60 then
talk ("The maximum number of payments is 60") talk "The maximum number of payments is 60"
else else
talk ("What is the amount requested?") talk "What is the amount requested?"
ammount = hear () hear ammount
if ammount >100000 then if ammount >100000 then
talk ("We are sorry, we can only accept proposals bellow 100k") talk "We are sorry, we can only accept proposals bellow 100k"
else else
talk ("What is the best due date?") talk "What is the best due date?"
dueDate = hear () hear dueDate
interestRate = 0 interestRate = 0
adjustment = 0 adjustment = 0
@ -84,15 +50,15 @@ else
end if end if
if installments > 60 then if installments > 60 then
talk ("The maximum number of payments is 60") talk "The maximum number of payments is 60"
end if end if
' TODO: This must be reviewed in terms of financing logic. ' TODO: This must be reviewed in terms of financing logic.
nInstallments = parseInt(installments) nInstallments = parseIntinstallments
vAmmount = parseFloat(ammount) vAmmount = parseFloatammount
initialPayment = parseFloat(vAmmount) * 0.3 ' 30% of the value initialPayment = parseFloatvAmmount * 0.3 ' 30% of the value
tac = 800 tac = 800
adjustment = 1.3 adjustment = 1.3
@ -100,13 +66,12 @@ else
paymentValue = totalValue * adjustment paymentValue = totalValue * adjustment
finalValue = paymentValue * nInstallments + initialPayment finalValue = paymentValue * nInstallments + initialPayment
talk("Congratulations! Your credit analysis is **done**:") talk "Congratulations! Your credit analysis is **done**:"
talk("First payment: **" + initialPayment + "**") talk "First payment: **" + initialPayment + "**"
talk("Payment value: **" + paymentValue + "**") talk "Payment value: **" + paymentValue + "**"
talk("Interest Rate: **" + interestRate + "%**") talk "Interest Rate: **" + interestRate + "%**"
talk("Total Value: **" + totalValue + "**") talk "Total Value: **" + totalValue + "**"
talk("Final Value: **" + finalValue + "**") talk "Final Value: **" + finalValue + "**"
end if end if
end if end if
%>

View file

@ -1,8 +1,7 @@
' General Bots Copyright (c) Pragmatismo.io. All rights reserved. ' General Bots Copyright (c) Pragmatismo.io. All rights reserved. Licensed under the AGPL-3.0.
' Licensed under the AGPL-3.0.
talk "Please, tell me what is the Bot name?" talk "Please, tell me what is the Bot name?"
hear name hear title
talk "If you tell me your username/password, I can show service subscription list to you." talk "If you tell me your username/password, I can show service subscription list to you."
talk "What is your Username (eg.: human@domain.bot)" talk "What is your Username (eg.: human@domain.bot)"
@ -16,7 +15,8 @@ talk "Can you describe in a few words what the bot is about?"
hear description hear description
talk "Please, choose what subscription would you like to connect to:" talk "Please, choose what subscription would you like to connect to:"
hear one of subscriptions (email, password) into subscriptionId
subscriptionId = ""
talk "Please, provide the cloud location just like 'westus'?" talk "Please, provide the cloud location just like 'westus'?"
hear cloudLocation hear cloudLocation
@ -26,6 +26,7 @@ hear nlpKey
talk "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." talk "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."
wait 1 wait 1
talk "Please, provide the App ID you just generated:" talk "Please, provide the App ID you just generated:"
hear appId hear appId
@ -33,4 +34,5 @@ talk "Please, provide the Generated Password:"
hear appPassword hear appPassword
talk "Now, I am going to create a Bot farm... Wait 5 minutes or more..." talk "Now, I am going to create a Bot farm... Wait 5 minutes or more..."
create bot farm (name, username, password, description, cloudLocation, nlpKey, appId, appPassword, subscriptionId)
create a bot farm using title, username, password, description, cloudLocation, nlpKey, appId, appPassword, subscriptionId

View file

@ -36,7 +36,6 @@ const _ = require('lodash');
const Parse = require('csv-parse'); const Parse = require('csv-parse');
const Async = require('async'); const Async = require('async');
const UrlJoin = require('url-join'); const UrlJoin = require('url-join');
const Walk = require('fs-walk');
const logger = require('../../../src/logger'); const logger = require('../../../src/logger');
import { GBService, GBServiceCallback, IGBInstance } from 'botlib'; import { GBService, GBServiceCallback, IGBInstance } from 'botlib';

View file

@ -36,7 +36,6 @@ const _ = require('lodash');
const Parse = require('csv-parse'); const Parse = require('csv-parse');
const Async = require('async'); const Async = require('async');
const UrlJoin = require('url-join'); const UrlJoin = require('url-join');
const Walk = require('fs-walk');
const logger = require('../../../src/logger'); const logger = require('../../../src/logger');
const Swagger = require('swagger-client'); const Swagger = require('swagger-client');
const rp = require('request-promise'); const rp = require('request-promise');

View file

@ -51,6 +51,7 @@ import { GBDeployer } from '../packages/core.gbapp/services/GBDeployer';
import { GBImporter } from '../packages/core.gbapp/services/GBImporterService'; import { GBImporter } from '../packages/core.gbapp/services/GBImporterService';
import { GBMinService } from '../packages/core.gbapp/services/GBMinService'; import { GBMinService } from '../packages/core.gbapp/services/GBMinService';
import { GBVMService } from '../packages/core.gbapp/services/GBVMService'; import { GBVMService } from '../packages/core.gbapp/services/GBVMService';
import { load } from 'dotenv';
const appPackages = new Array<IGBPackage>(); const appPackages = new Array<IGBPackage>();
@ -163,6 +164,9 @@ export class GBServer {
} }
} }
let service:GBVMService = new GBVMService ();
service.loadDialogPackage('C:\\Sources\\opensource\\BotServer\\packages\\default.gbdialog',null,null,null);
// First line to run. // First line to run.
GBServer.run(); // GBServer.run();