fix(core.gbapp): More comments in GBMinService.
This commit is contained in:
parent
0c8623e1a2
commit
9bc82abf11
2 changed files with 373 additions and 207 deletions
|
@ -69,7 +69,7 @@
|
||||||
"botbuilder-ai": "4.11.0",
|
"botbuilder-ai": "4.11.0",
|
||||||
"botbuilder-dialogs": "4.11.0",
|
"botbuilder-dialogs": "4.11.0",
|
||||||
"botframework-connector": "4.11.0",
|
"botframework-connector": "4.11.0",
|
||||||
"botlib": "1.7.0",
|
"botlib": "1.7.1",
|
||||||
"cli-spinner": "0.2.10",
|
"cli-spinner": "0.2.10",
|
||||||
"core-js": "3.7.0",
|
"core-js": "3.7.0",
|
||||||
"dotenv-extended": "2.9.0",
|
"dotenv-extended": "2.9.0",
|
||||||
|
|
|
@ -81,18 +81,35 @@ import { GBAdminService } from '../../admin.gbapp/services/GBAdminService';
|
||||||
import { GBConversationalService } from './GBConversationalService';
|
import { GBConversationalService } from './GBConversationalService';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimal service layer for a bot.
|
* Minimal service layer for a bot and encapsulation of BOT Framework.
|
||||||
*/
|
*/
|
||||||
export class GBMinService {
|
export class GBMinService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main core service attached to this bot service.
|
||||||
|
*/
|
||||||
public core: IGBCoreService;
|
public core: IGBCoreService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to conversation services like receive and prompt text.
|
||||||
|
*/
|
||||||
public conversationalService: IGBConversationalService;
|
public conversationalService: IGBConversationalService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conversational administration services like publishing packages.
|
||||||
|
*/
|
||||||
public adminService: IGBAdminService;
|
public adminService: IGBAdminService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deployent of packages and publishing related services.
|
||||||
|
*/
|
||||||
public deployer: GBDeployer;
|
public deployer: GBDeployer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default General Bots User Interface package.
|
||||||
|
*/
|
||||||
private static uiPackage = 'default.gbui';
|
private static uiPackage = 'default.gbui';
|
||||||
|
|
||||||
public corePackage = 'core.gbai';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static initialization of minimal instance.
|
* Static initialization of minimal instance.
|
||||||
*
|
*
|
||||||
|
@ -110,19 +127,124 @@ export class GBMinService {
|
||||||
this.deployer = deployer;
|
this.deployer = deployer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Constructs a new minimal instance for each bot.
|
|
||||||
*
|
|
||||||
* @param server An HTTP server.
|
|
||||||
* @param appPackages List of loaded .gbapp associated with this instance.
|
|
||||||
*
|
|
||||||
* @return Loaded minimal bot instance.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
private async WhatsAppCallback(req, res) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Detects if the message is echo fro itself.
|
||||||
|
|
||||||
|
const id = req.body.messages[0].chatId.split('@')[0];
|
||||||
|
const senderName = req.body.messages[0].senderName;
|
||||||
|
const text = req.body.messages[0].body;
|
||||||
|
if (req.body.messages[0].fromMe) {
|
||||||
|
res.end();
|
||||||
|
return; // Exit here.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detects if the welcome message is enabled.
|
||||||
|
|
||||||
|
let activeMin;
|
||||||
|
if (process.env.WHATSAPP_WELCOME_DISABLED !== 'true') {
|
||||||
|
|
||||||
|
// Tries to find if user wants to switch bots.
|
||||||
|
|
||||||
|
let toSwitchMin = GBServer.globals.minInstances.filter(
|
||||||
|
p => p.instance.botId.toLowerCase() === text.toLowerCase()
|
||||||
|
)[0];
|
||||||
|
if (!toSwitchMin) {
|
||||||
|
toSwitchMin = GBServer.globals.minInstances.filter(p =>
|
||||||
|
p.instance.activationCode ? p.instance.activationCode.toLowerCase() === text.toLowerCase() : false
|
||||||
|
)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find active bot instance.
|
||||||
|
|
||||||
|
activeMin = toSwitchMin ? toSwitchMin : GBServer.globals.minBoot;
|
||||||
|
|
||||||
|
// If it is the first time for the user, tries to auto-execute
|
||||||
|
// start dialog if any is specified in Config.xlsx.
|
||||||
|
|
||||||
|
let sec = new SecService();
|
||||||
|
let user = await sec.getUserFromSystemId(id);
|
||||||
|
if (user === null || user.hearOnDialog) {
|
||||||
|
user = await sec.ensureUser(activeMin.instance.instanceId, id, senderName, '', 'whatsapp', senderName);
|
||||||
|
|
||||||
|
let startDialog = user.hearOnDialog ?
|
||||||
|
user.hearOnDialog :
|
||||||
|
activeMin.core.getParam(activeMin.instance, 'Start Dialog', null);
|
||||||
|
|
||||||
|
GBLog.info(`Auto start dialog is now being called: ${startDialog}...`);
|
||||||
|
if (startDialog) {
|
||||||
|
req.body.messages[0].body = `${startDialog}`;
|
||||||
|
|
||||||
|
// Resets HEAR ON DIALOG value to none and passes
|
||||||
|
// current dialog to the direct line.
|
||||||
|
|
||||||
|
await sec.updateUserHearOnDialog(user.userId, null);
|
||||||
|
await (activeMin as any).whatsAppDirectLine.received(req, res);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await (activeMin as any).whatsAppDirectLine.sendToDevice(
|
||||||
|
id,
|
||||||
|
`Olá! Seja bem-vinda(o)!\nMe chamo ${activeMin.instance.title}. Como posso ajudar? Pode me falar que eu te ouço, me manda um aúdio.`
|
||||||
|
);
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// User wants to switch bots.
|
||||||
|
|
||||||
|
if (toSwitchMin !== undefined) {
|
||||||
|
|
||||||
|
// So gets the new bot instance information and prepares to
|
||||||
|
// auto start dialog if any is specified.
|
||||||
|
|
||||||
|
const instance = await this.core.loadInstanceByBotId(activeMin.botId);
|
||||||
|
await sec.updateUserInstance(id, instance.instanceId);
|
||||||
|
await (activeMin as any).whatsAppDirectLine.resetConversationId(id);
|
||||||
|
let startDialog = activeMin.core.getParam(activeMin.instance, 'Start Dialog', null);
|
||||||
|
GBLog.info(`Auto start dialog is now being called: ${startDialog}...`);
|
||||||
|
|
||||||
|
if (startDialog) {
|
||||||
|
req.body.messages[0].body = `${startDialog}`;
|
||||||
|
await (activeMin as any).whatsAppDirectLine.received(req, res);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await (activeMin as any).whatsAppDirectLine.sendToDevice(
|
||||||
|
id,
|
||||||
|
`Agora falando com ${activeMin.instance.title}...`
|
||||||
|
);
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
activeMin = GBServer.globals.minInstances.filter(p => p.instance.instanceId === user.instanceId)[0];
|
||||||
|
if (activeMin === undefined) {
|
||||||
|
activeMin = GBServer.globals.minBoot;
|
||||||
|
await (activeMin as any).whatsAppDirectLine.sendToDevice(
|
||||||
|
id,
|
||||||
|
`O outro Bot que você estava falando(${user.instanceId}), não está mais disponível. Agora você está falando comigo, ${activeMin.instance.title}...`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await (activeMin as any).whatsAppDirectLine.received(req, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Just pass the message to the receiver.
|
||||||
|
|
||||||
|
await (GBServer.globals.minBoot as any).whatsAppDirectLine.received(req, res);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
GBLog.error(`Error on Whatsapp callback: ${error.data ? error.data : error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new minimal instance for each bot.
|
||||||
|
*/
|
||||||
public async buildMin(instances: IGBInstance[]) {
|
public async buildMin(instances: IGBInstance[]) {
|
||||||
// Serves default UI on root address '/' if web enabled.
|
|
||||||
|
// Servers default UI on root address '/' if web enabled.
|
||||||
if (process.env.DISABLE_WEB !== 'true') {
|
if (process.env.DISABLE_WEB !== 'true') {
|
||||||
let url = GBServer.globals.wwwroot
|
let url = GBServer.globals.wwwroot
|
||||||
? GBServer.globals.wwwroot
|
? GBServer.globals.wwwroot
|
||||||
|
@ -130,106 +252,19 @@ export class GBMinService {
|
||||||
|
|
||||||
GBServer.globals.server.use('/', express.static(url));
|
GBServer.globals.server.use('/', express.static(url));
|
||||||
}
|
}
|
||||||
// Serves the bot information object via HTTP so clients can get
|
|
||||||
|
// Servers the bot information object via HTTP so clients can get
|
||||||
// instance information stored on server.
|
// instance information stored on server.
|
||||||
|
|
||||||
if (process.env.DISABLE_WEB !== 'true') {
|
if (process.env.DISABLE_WEB !== 'true') {
|
||||||
GBServer.globals.server.get('/instances/:botId', (req, res) => {
|
GBServer.globals.server.get('/instances/:botId', this.handleGetInstanceForClient);
|
||||||
(async () => {
|
|
||||||
await this.handleGetInstanceForClient(req, res);
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
const url = '/webhooks/whatsapp';
|
|
||||||
GBServer.globals.server.post(url, async (req, res) => {
|
|
||||||
try {
|
|
||||||
const id = req.body.messages[0].chatId.split('@')[0];
|
|
||||||
const senderName = req.body.messages[0].senderName;
|
|
||||||
const text = req.body.messages[0].body;
|
|
||||||
if (req.body.messages[0].fromMe) {
|
|
||||||
res.end();
|
|
||||||
return; // Exit here.
|
|
||||||
}
|
|
||||||
let activeMin;
|
|
||||||
if (process.env.WHATSAPP_WELCOME_DISABLED !== 'true') {
|
|
||||||
let toSwitchMin = GBServer.globals.minInstances.filter(
|
|
||||||
p => p.instance.botId.toLowerCase() === text.toLowerCase()
|
|
||||||
)[0];
|
|
||||||
if (!toSwitchMin) {
|
|
||||||
toSwitchMin = GBServer.globals.minInstances.filter(p =>
|
|
||||||
p.instance.activationCode ? p.instance.activationCode.toLowerCase() === text.toLowerCase() : false
|
|
||||||
)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
activeMin = toSwitchMin ? toSwitchMin : GBServer.globals.minBoot;
|
// Servers the WhatsApp callback.
|
||||||
|
|
||||||
let sec = new SecService();
|
GBServer.globals.server.post('/webhooks/whatsapp', this.WhatsAppCallback);
|
||||||
let user = await sec.getUserFromSystemId(id);
|
|
||||||
|
|
||||||
if (user === null || user.hearOnDialog) {
|
// Call mountBot event to all bots.
|
||||||
user = await sec.ensureUser(activeMin.instance.instanceId, id, senderName, '', 'whatsapp', senderName);
|
|
||||||
|
|
||||||
let startDialog = user.hearOnDialog ?
|
|
||||||
user.hearOnDialog :
|
|
||||||
activeMin.core.getParam(activeMin.instance, 'Start Dialog', null);
|
|
||||||
|
|
||||||
GBLog.info(`Auto start dialog is now being called: ${startDialog}...`);
|
|
||||||
if (startDialog) {
|
|
||||||
req.body.messages[0].body = `${startDialog}`;
|
|
||||||
|
|
||||||
// Resets HEAR ON DIALOG value to none and passes
|
|
||||||
// current dialog to the direct line.
|
|
||||||
|
|
||||||
await sec.updateUserHearOnDialog(user.userId, null);
|
|
||||||
await (activeMin as any).whatsAppDirectLine.received(req, res);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
await (activeMin as any).whatsAppDirectLine.sendToDevice(
|
|
||||||
id,
|
|
||||||
`Olá! Seja bem-vinda(o)!\nMe chamo ${activeMin.instance.title}. Como posso ajudar? Pode me falar que eu te ouço, me manda um aúdio.`
|
|
||||||
);
|
|
||||||
res.end();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// User wants to switch bots.
|
|
||||||
if (toSwitchMin !== undefined) {
|
|
||||||
const instance = await this.core.loadInstanceByBotId(activeMin.botId);
|
|
||||||
await sec.updateUserInstance(id, instance.instanceId);
|
|
||||||
|
|
||||||
await (activeMin as any).whatsAppDirectLine.resetConversationId(id);
|
|
||||||
let startDialog = activeMin.core.getParam(activeMin.instance, 'Start Dialog', null);
|
|
||||||
|
|
||||||
GBLog.info(`Auto start dialog is now being called: ${startDialog}...`);
|
|
||||||
|
|
||||||
if (startDialog) {
|
|
||||||
req.body.messages[0].body = `${startDialog}`;
|
|
||||||
await (activeMin as any).whatsAppDirectLine.received(req, res);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
await (activeMin as any).whatsAppDirectLine.sendToDevice(
|
|
||||||
id,
|
|
||||||
`Agora falando com ${activeMin.instance.title}...`
|
|
||||||
);
|
|
||||||
res.end();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
activeMin = GBServer.globals.minInstances.filter(p => p.instance.instanceId === user.instanceId)[0];
|
|
||||||
if (activeMin === undefined) {
|
|
||||||
activeMin = GBServer.globals.minBoot;
|
|
||||||
await (activeMin as any).whatsAppDirectLine.sendToDevice(
|
|
||||||
id,
|
|
||||||
`O outro Bot que você estava falando(${user.instanceId}), não está mais disponível. Agora você está falando comigo, ${activeMin.instance.title}...`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await (activeMin as any).whatsAppDirectLine.received(req, res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await (GBServer.globals.minBoot as any).whatsAppDirectLine.received(req, res);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
GBLog.error(`Error on Whatsapp callback: ${error.data ? error.data : error}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await CollectionUtil.asyncForEach(instances, async instance => {
|
await CollectionUtil.asyncForEach(instances, async instance => {
|
||||||
try {
|
try {
|
||||||
|
@ -240,6 +275,10 @@ export class GBMinService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes bot endpoint from web listeners and remove bot instance
|
||||||
|
* from list of global server bot instances.
|
||||||
|
*/
|
||||||
public async unmountBot(botId: string) {
|
public async unmountBot(botId: string) {
|
||||||
const url = `/api/messages/${botId}`;
|
const url = `/api/messages/${botId}`;
|
||||||
removeRoute(GBServer.globals.server, url);
|
removeRoute(GBServer.globals.server, url);
|
||||||
|
@ -250,8 +289,15 @@ export class GBMinService {
|
||||||
GBServer.globals.minInstances = GBServer.globals.minInstances.filter(p => p.instance.botId !== botId);
|
GBServer.globals.minInstances = GBServer.globals.minInstances.filter(p => p.instance.botId !== botId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount the instance by creating an BOT Framework bot object,
|
||||||
|
* serving bot endpoint in several URL like WhatsApp endpoint, .gbkb assets,
|
||||||
|
* installing all BASIC artifacts from .gbdialog and OAuth2.
|
||||||
|
*/
|
||||||
public async mountBot(instance: IGBInstance) {
|
public async mountBot(instance: IGBInstance) {
|
||||||
|
|
||||||
// Build bot adapter.
|
// Build bot adapter.
|
||||||
|
|
||||||
const { min, adapter, conversationState } = await this.buildBotAdapter(
|
const { min, adapter, conversationState } = await this.buildBotAdapter(
|
||||||
instance,
|
instance,
|
||||||
GBServer.globals.sysPackages,
|
GBServer.globals.sysPackages,
|
||||||
|
@ -280,10 +326,12 @@ export class GBMinService {
|
||||||
await this.deployer.deployPackage(min, packagePath);
|
await this.deployer.deployPackage(min, packagePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the loadBot context.activity for all packages.
|
// Calls the loadBot context.activity for all packages.
|
||||||
|
|
||||||
await this.invokeLoadBot(GBServer.globals.appPackages, GBServer.globals.sysPackages, min);
|
await this.invokeLoadBot(GBServer.globals.appPackages, GBServer.globals.sysPackages, min);
|
||||||
|
|
||||||
// Serves individual URL for each bot conversational interface...
|
// Serves individual URL for each bot conversational interface...
|
||||||
|
|
||||||
const url = `/api/messages/${instance.botId}`;
|
const url = `/api/messages/${instance.botId}`;
|
||||||
GBServer.globals.server.post(url, async (req, res) => {
|
GBServer.globals.server.post(url, async (req, res) => {
|
||||||
await this.receiver(adapter, req, res, conversationState, min, instance, GBServer.globals.appPackages);
|
await this.receiver(adapter, req, res, conversationState, min, instance, GBServer.globals.appPackages);
|
||||||
|
@ -291,6 +339,7 @@ export class GBMinService {
|
||||||
GBLog.info(`GeneralBots(${instance.engineName}) listening on: ${url}.`);
|
GBLog.info(`GeneralBots(${instance.engineName}) listening on: ${url}.`);
|
||||||
|
|
||||||
// Serves individual URL for each bot user interface.
|
// Serves individual URL for each bot user interface.
|
||||||
|
|
||||||
if (process.env.DISABLE_WEB !== 'true') {
|
if (process.env.DISABLE_WEB !== 'true') {
|
||||||
const uiUrl = `/${instance.botId}`;
|
const uiUrl = `/${instance.botId}`;
|
||||||
const uiUrlAlt = `/${instance.activationCode}`;
|
const uiUrlAlt = `/${instance.activationCode}`;
|
||||||
|
@ -309,19 +358,30 @@ 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.
|
||||||
|
|
||||||
this.handleOAuthRequests(GBServer.globals.server, min);
|
this.handleOAuthRequests(GBServer.globals.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.
|
||||||
|
|
||||||
this.handleOAuthTokenRequests(GBServer.globals.server, min, instance);
|
this.handleOAuthTokenRequests(GBServer.globals.server, min, instance);
|
||||||
|
|
||||||
|
// Provides checking of instance health.
|
||||||
|
|
||||||
this.createCheckHealthAddress(GBServer.globals.server, min, min.instance);
|
this.createCheckHealthAddress(GBServer.globals.server, min, min.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a listener that can be used by external monitors to check
|
||||||
|
* bot instance health.
|
||||||
|
*/
|
||||||
private createCheckHealthAddress(server: any, min: GBMinInstance, instance: IGBInstance) {
|
private createCheckHealthAddress(server: any, min: GBMinInstance, instance: IGBInstance) {
|
||||||
server.get(`/${min.instance.botId}/check`, async (req, res) => {
|
server.get(`/${min.instance.botId}/check`, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
// Performs the checking of WhatsApp API if enabled for this instance.
|
||||||
|
|
||||||
if (min.whatsAppDirectLine != undefined && instance.whatsappServiceKey !== null) {
|
if (min.whatsAppDirectLine != undefined && instance.whatsappServiceKey !== null) {
|
||||||
if (!(await min.whatsAppDirectLine.check(min))) {
|
if (!(await min.whatsAppDirectLine.check(min))) {
|
||||||
const error = `WhatsApp API lost connection.`;
|
const error = `WhatsApp API lost connection.`;
|
||||||
|
@ -331,16 +391,31 @@ export class GBMinService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GB is OK, so 200.
|
||||||
|
|
||||||
res.status(200).send(`General Bot ${min.botId} is healthly.`);
|
res.status(200).send(`General Bot ${min.botId} is healthly.`);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
|
// GB is not OK, 500 and detail the information on response content.
|
||||||
|
|
||||||
GBLog.error(error);
|
GBLog.error(error);
|
||||||
res.status(500).send(error.toString());
|
res.status(500).send(error.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle OAuth2 web service calls for token requests
|
||||||
|
* on https://<gbhost>/<BotId>/token URL.
|
||||||
|
*/
|
||||||
private handleOAuthTokenRequests(server: any, min: GBMinInstance, instance: IGBInstance) {
|
private handleOAuthTokenRequests(server: any, min: GBMinInstance, instance: IGBInstance) {
|
||||||
|
|
||||||
server.get(`/${min.instance.botId}/token`, async (req, res) => {
|
server.get(`/${min.instance.botId}/token`, async (req, res) => {
|
||||||
|
|
||||||
|
// Checks request state by reading AntiCSRFAttackState from GB Admin infrastructure.
|
||||||
|
|
||||||
const state = await min.adminService.getValue(instance.instanceId, 'AntiCSRFAttackState');
|
const state = await min.adminService.getValue(instance.instanceId, 'AntiCSRFAttackState');
|
||||||
if (req.query.state !== state) {
|
if (req.query.state !== state) {
|
||||||
const msg = 'WARNING: state field was not provided as anti-CSRF token';
|
const msg = 'WARNING: state field was not provided as anti-CSRF token';
|
||||||
|
@ -351,6 +426,9 @@ export class GBMinService {
|
||||||
urlJoin(min.instance.authenticatorAuthorityHostUrl, min.instance.authenticatorTenant)
|
urlJoin(min.instance.authenticatorAuthorityHostUrl, min.instance.authenticatorTenant)
|
||||||
);
|
);
|
||||||
const resource = 'https://graph.microsoft.com';
|
const resource = 'https://graph.microsoft.com';
|
||||||
|
|
||||||
|
// Calls MSFT to get token.
|
||||||
|
|
||||||
authenticationContext.acquireTokenWithAuthorizationCode(
|
authenticationContext.acquireTokenWithAuthorizationCode(
|
||||||
req.query.code,
|
req.query.code,
|
||||||
urlJoin(instance.botEndpoint, min.instance.botId, '/token'),
|
urlJoin(instance.botEndpoint, min.instance.botId, '/token'),
|
||||||
|
@ -363,11 +441,16 @@ export class GBMinService {
|
||||||
GBLog.error(msg);
|
GBLog.error(msg);
|
||||||
res.send(msg);
|
res.send(msg);
|
||||||
} else {
|
} else {
|
||||||
// TODO: await these calls.
|
|
||||||
this.adminService.setValue(instance.instanceId, 'accessToken', token.accessToken);
|
// Saves token to the database.
|
||||||
this.adminService.setValue(instance.instanceId, 'refreshToken', token.refreshToken);
|
|
||||||
this.adminService.setValue(instance.instanceId, 'expiresOn', token.expiresOn.toString());
|
await this.adminService.setValue(instance.instanceId, 'accessToken', token.accessToken);
|
||||||
this.adminService.setValue(instance.instanceId, 'AntiCSRFAttackState', undefined);
|
await this.adminService.setValue(instance.instanceId, 'refreshToken', token.refreshToken);
|
||||||
|
await this.adminService.setValue(instance.instanceId, 'expiresOn', token.expiresOn.toString());
|
||||||
|
await this.adminService.setValue(instance.instanceId, 'AntiCSRFAttackState', undefined);
|
||||||
|
|
||||||
|
// Inform the home for default .gbui after finishing token retrival.
|
||||||
|
|
||||||
res.redirect(min.instance.botEndpoint);
|
res.redirect(min.instance.botEndpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,6 +458,10 @@ export class GBMinService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle OAuth2 web service calls for authorization requests
|
||||||
|
* on https://<gbhost>/<BotId>/auth URL.
|
||||||
|
*/
|
||||||
private handleOAuthRequests(server: any, min: GBMinInstance) {
|
private handleOAuthRequests(server: any, min: GBMinInstance) {
|
||||||
server.get(`/${min.instance.botId}/auth`, (req, res) => {
|
server.get(`/${min.instance.botId}/auth`, (req, res) => {
|
||||||
let authorizationUrl = urlJoin(
|
let authorizationUrl = urlJoin(
|
||||||
|
@ -392,27 +479,38 @@ export class GBMinService {
|
||||||
* Returns the instance object to clients requesting bot info.
|
* Returns the instance object to clients requesting bot info.
|
||||||
*/
|
*/
|
||||||
private async handleGetInstanceForClient(req: any, res: any) {
|
private async handleGetInstanceForClient(req: any, res: any) {
|
||||||
let botId = req.params.botId;
|
|
||||||
if (botId === '[default]' || botId === undefined) {
|
|
||||||
botId = GBConfigService.get('BOT_ID');
|
|
||||||
}
|
|
||||||
let instance = await this.core.loadInstanceByBotId(botId);
|
|
||||||
|
|
||||||
|
// Translates the requested botId.
|
||||||
|
|
||||||
|
let id = req.params.botId;
|
||||||
|
if (id === '[default]' || id === undefined) {
|
||||||
|
id = GBConfigService.get('BOT_ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loads by the botId itself or by the activationCode field.
|
||||||
|
|
||||||
|
let instance = await this.core.loadInstanceByBotId(id);
|
||||||
if (instance === null) {
|
if (instance === null) {
|
||||||
instance = await this.core.loadInstanceByActivationCode(botId);
|
instance = await this.core.loadInstanceByActivationCode(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instance !== null) {
|
if (instance !== null) {
|
||||||
|
|
||||||
|
// Gets the webchat token, speech token and theme.
|
||||||
|
|
||||||
const webchatTokenContainer = await this.getWebchatToken(instance);
|
const webchatTokenContainer = await this.getWebchatToken(instance);
|
||||||
const speechToken = instance.speechKey != null ? await this.getSTSToken(instance) : null;
|
const speechToken = instance.speechKey != null ? await this.getSTSToken(instance) : null;
|
||||||
let theme = instance.theme;
|
let theme = instance.theme;
|
||||||
|
|
||||||
|
// Sends all information to the .gbui web client.
|
||||||
|
|
||||||
if (theme === undefined) {
|
if (theme === undefined) {
|
||||||
theme = 'default.gbtheme';
|
theme = 'default.gbtheme';
|
||||||
}
|
}
|
||||||
res.send(
|
res.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
instanceId: instance.instanceId,
|
instanceId: instance.instanceId,
|
||||||
botId: botId,
|
botId: id,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
webchatToken: webchatTokenContainer.token,
|
webchatToken: webchatTokenContainer.token,
|
||||||
speechToken: speechToken,
|
speechToken: speechToken,
|
||||||
|
@ -422,17 +520,14 @@ export class GBMinService {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const error = `Instance not found: ${botId}.`;
|
const error = `Instance not found while retrieving from .gbui web client: ${id}.`;
|
||||||
res.sendStatus(error);
|
res.sendStatus(error);
|
||||||
GBLog.error(error);
|
GBLog.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Webchat key from Bot Service.
|
* Gets Webchat token from Bot Service.
|
||||||
*
|
|
||||||
* @param instance The Bot instance.
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
private async getWebchatToken(instance: any) {
|
private async getWebchatToken(instance: any) {
|
||||||
const options = {
|
const options = {
|
||||||
|
@ -445,20 +540,15 @@ export class GBMinService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const json = await request(options);
|
const json = await request(options);
|
||||||
|
|
||||||
return JSON.parse(json);
|
return JSON.parse(json);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const msg = `[botId:${instance.botId}] Error calling Direct Line client, verify Bot endpoint on the cloud. Error is: ${error}.`;
|
const msg = `[botId:${instance.botId}] Error calling Direct Line to generate a token for Web control: ${error}.`;
|
||||||
|
|
||||||
return Promise.reject(new Error(msg));
|
return Promise.reject(new Error(msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a Speech to Text / Text to Speech token from the provider.
|
* Gets a Speech to Text / Text to Speech token from the provider.
|
||||||
*
|
|
||||||
* @param instance The general bot instance.
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
private async getSTSToken(instance: any) {
|
private async getSTSToken(instance: any) {
|
||||||
const options = {
|
const options = {
|
||||||
|
@ -478,30 +568,25 @@ export class GBMinService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the BOT Framework & GB infrastructures.
|
||||||
|
*/
|
||||||
private async buildBotAdapter(instance: any, sysPackages: IGBPackage[], appPackages: IGBPackage[]) {
|
private async buildBotAdapter(instance: any, sysPackages: IGBPackage[], appPackages: IGBPackage[]) {
|
||||||
const adapter = new BotFrameworkAdapter({
|
|
||||||
appId: instance.marketplaceId,
|
|
||||||
appPassword: instance.marketplacePassword
|
|
||||||
});
|
|
||||||
const storage = new MemoryStorage();
|
|
||||||
|
|
||||||
|
// MSFT stuff.
|
||||||
|
|
||||||
|
const adapter = new BotFrameworkAdapter({ appId: instance.marketplaceId, appPassword: instance.marketplacePassword });
|
||||||
|
const storage = new MemoryStorage();
|
||||||
const conversationState = new ConversationState(storage);
|
const conversationState = new ConversationState(storage);
|
||||||
const userState = new UserState(storage);
|
const userState = new UserState(storage);
|
||||||
adapter.use(new AutoSaveStateMiddleware(conversationState, userState));
|
adapter.use(new AutoSaveStateMiddleware(conversationState, userState));
|
||||||
|
MicrosoftAppCredentials.trustServiceUrl('https://directline.botframework.com',
|
||||||
MicrosoftAppCredentials.trustServiceUrl(
|
|
||||||
'https://directline.botframework.com',
|
|
||||||
new Date(new Date().setFullYear(new Date().getFullYear() + 10))
|
new Date(new Date().setFullYear(new Date().getFullYear() + 10))
|
||||||
);
|
);
|
||||||
|
|
||||||
// The minimal bot is built here.
|
// The minimal bot is built here.
|
||||||
|
|
||||||
const min = new GBMinInstance();
|
const min = new GBMinInstance();
|
||||||
|
|
||||||
if (GBServer.globals.minBoot === undefined) {
|
|
||||||
GBServer.globals.minBoot = min;
|
|
||||||
}
|
|
||||||
|
|
||||||
min.botId = instance.botId;
|
min.botId = instance.botId;
|
||||||
min.bot = adapter;
|
min.bot = adapter;
|
||||||
min.userState = userState;
|
min.userState = userState;
|
||||||
|
@ -516,11 +601,13 @@ export class GBMinService {
|
||||||
min.sandBoxMap = {};
|
min.sandBoxMap = {};
|
||||||
min.packages = sysPackages;
|
min.packages = sysPackages;
|
||||||
min.appPackages = appPackages;
|
min.appPackages = appPackages;
|
||||||
|
if (GBServer.globals.minBoot === undefined) {
|
||||||
|
GBServer.globals.minBoot = min;
|
||||||
|
}
|
||||||
// TODO: min.appPackages = core.getPackagesByInstanceId(min.instance.instanceId);
|
// TODO: min.appPackages = core.getPackagesByInstanceId(min.instance.instanceId);
|
||||||
|
|
||||||
// Create a hub of services available in .gbapps.
|
// Creates a hub of services available in .gbapps.
|
||||||
|
|
||||||
let handled = false;
|
|
||||||
await CollectionUtil.asyncForEach(min.appPackages, async (e: IGBPackage) => {
|
await CollectionUtil.asyncForEach(min.appPackages, async (e: IGBPackage) => {
|
||||||
let services: ConcatArray<never>;
|
let services: ConcatArray<never>;
|
||||||
if ((services = await e.onExchangeData(min, 'getServices', null))) {
|
if ((services = await e.onExchangeData(min, 'getServices', null))) {
|
||||||
|
@ -528,6 +615,9 @@ export class GBMinService {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If there is WhatsApp configuration specified, initialize
|
||||||
|
// infrastructure objects.
|
||||||
|
|
||||||
if (min.instance.whatsappServiceKey !== null) {
|
if (min.instance.whatsappServiceKey !== null) {
|
||||||
min.whatsAppDirectLine = new WhatsappDirectLine(
|
min.whatsAppDirectLine = new WhatsappDirectLine(
|
||||||
min,
|
min,
|
||||||
|
@ -553,6 +643,8 @@ export class GBMinService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setups default BOT Framework dialogs.
|
||||||
|
|
||||||
min.userProfile = conversationState.createProperty('userProfile');
|
min.userProfile = conversationState.createProperty('userProfile');
|
||||||
const dialogState = conversationState.createProperty('dialogState');
|
const dialogState = conversationState.createProperty('dialogState');
|
||||||
|
|
||||||
|
@ -571,7 +663,13 @@ export class GBMinService {
|
||||||
return { min, adapter, conversationState };
|
return { min, adapter, conversationState };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs calling of loadBot event in all .gbapps.
|
||||||
|
*/
|
||||||
private async invokeLoadBot(appPackages: IGBPackage[], sysPackages: IGBPackage[], min: GBMinInstance) {
|
private async invokeLoadBot(appPackages: IGBPackage[], sysPackages: IGBPackage[], min: GBMinInstance) {
|
||||||
|
|
||||||
|
// Calls loadBot event in all .gbapp packages.
|
||||||
|
|
||||||
await CollectionUtil.asyncForEach(sysPackages, async p => {
|
await CollectionUtil.asyncForEach(sysPackages, async p => {
|
||||||
p.sysPackages = sysPackages;
|
p.sysPackages = sysPackages;
|
||||||
if (p.getDialogs !== undefined) {
|
if (p.getDialogs !== undefined) {
|
||||||
|
@ -586,6 +684,8 @@ export class GBMinService {
|
||||||
await p.loadBot(min);
|
await p.loadBot(min);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Adds all dialogs from .gbapps into global dialo list for this minimal instance.
|
||||||
|
|
||||||
await CollectionUtil.asyncForEach(appPackages, async p => {
|
await CollectionUtil.asyncForEach(appPackages, async p => {
|
||||||
p.sysPackages = sysPackages;
|
p.sysPackages = sysPackages;
|
||||||
await p.loadBot(min);
|
await p.loadBot(min);
|
||||||
|
@ -601,7 +701,7 @@ export class GBMinService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bot Service hook method.
|
* BOT Framework web service hook method.
|
||||||
*/
|
*/
|
||||||
private async receiver(
|
private async receiver(
|
||||||
adapter: BotFrameworkAdapter,
|
adapter: BotFrameworkAdapter,
|
||||||
|
@ -612,8 +712,13 @@ export class GBMinService {
|
||||||
instance: any,
|
instance: any,
|
||||||
appPackages: any[]
|
appPackages: any[]
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
// Default activity processing and handler.
|
||||||
|
|
||||||
await adapter.processActivity(req, res, async context => {
|
await adapter.processActivity(req, res, async context => {
|
||||||
|
|
||||||
// Get loaded user state
|
// Get loaded user state
|
||||||
|
|
||||||
const step = await min.dialogs.createContext(context);
|
const step = await min.dialogs.createContext(context);
|
||||||
step.context.activity.locale = 'pt-BR';
|
step.context.activity.locale = 'pt-BR';
|
||||||
let firstTime = false;
|
let firstTime = false;
|
||||||
|
@ -625,21 +730,29 @@ export class GBMinService {
|
||||||
|
|
||||||
const sec = new SecService();
|
const sec = new SecService();
|
||||||
if (!user.loaded) {
|
if (!user.loaded) {
|
||||||
|
user.loaded = true;
|
||||||
|
user.subjects = [];
|
||||||
|
user.cb = undefined;
|
||||||
|
user.welcomed = false;
|
||||||
firstTime = true;
|
firstTime = true;
|
||||||
|
|
||||||
|
// Sends loadInstance event to .gbui clients.
|
||||||
|
|
||||||
await min.conversationalService.sendEvent(min, step, 'loadInstance', {
|
await min.conversationalService.sendEvent(min, step, 'loadInstance', {
|
||||||
instanceId: instance.instanceId,
|
instanceId: instance.instanceId,
|
||||||
botId: instance.botId,
|
botId: instance.botId,
|
||||||
theme: instance.theme ? instance.theme : 'default.gbtheme',
|
theme: instance.theme ? instance.theme : 'default.gbtheme',
|
||||||
secret: instance.webchatKey
|
secret: instance.webchatKey
|
||||||
});
|
});
|
||||||
user.loaded = true;
|
|
||||||
user.subjects = [];
|
// This same event is dispatched either to all participants
|
||||||
user.cb = undefined;
|
// including the bot, that is filtered bellow.
|
||||||
user.welcomed = false;
|
|
||||||
|
|
||||||
if (context.activity.from.id !== min.botId) {
|
if (context.activity.from.id !== min.botId) {
|
||||||
const member = context.activity.from;
|
|
||||||
|
|
||||||
|
// Creates a new row in user table if it does not exists.
|
||||||
|
|
||||||
|
const member = context.activity.from;
|
||||||
const persistedUser = await sec.ensureUser(
|
const persistedUser = await sec.ensureUser(
|
||||||
instance.instanceId,
|
instance.instanceId,
|
||||||
member.id,
|
member.id,
|
||||||
|
@ -648,40 +761,59 @@ export class GBMinService {
|
||||||
'web',
|
'web',
|
||||||
member.name
|
member.name
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Required for MSTEAMS handling of persisted conversations.
|
||||||
|
|
||||||
if (step.context.activity.channelId === 'msteams') {
|
if (step.context.activity.channelId === 'msteams') {
|
||||||
persistedUser.conversationReference = JSON.stringify(
|
persistedUser.conversationReference = JSON.stringify(
|
||||||
TurnContext.getConversationReference(context.activity)
|
TurnContext.getConversationReference(context.activity)
|
||||||
);
|
);
|
||||||
await persistedUser.save();
|
await persistedUser.save();
|
||||||
}
|
}
|
||||||
const analytics = new AnalyticsService();
|
|
||||||
|
|
||||||
|
// Stores conversation associated to the user to group each message.
|
||||||
|
|
||||||
|
const analytics = new AnalyticsService();
|
||||||
user.systemUser = persistedUser;
|
user.systemUser = persistedUser;
|
||||||
user.conversation = await analytics.createConversation(persistedUser);
|
user.conversation = await analytics.createConversation(persistedUser);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Saves session user (persisted GuaribasUser is inside).
|
||||||
|
|
||||||
await min.userProfile.set(step.context, user);
|
await min.userProfile.set(step.context, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
GBLog.info(
|
GBLog.info(`User>: text:${context.activity.text} (type: ${context.activity.type}, name: ${context.activity.name}, channelId: ${context.activity.channelId}, value: ${context.activity.value})`);
|
||||||
`User>: text:${context.activity.text} (type: ${context.activity.type}, name: ${context.activity.name}, channelId: ${context.activity.channelId}, value: ${context.activity.value})`
|
|
||||||
);
|
// Answer to specific BOT Framework event conversationUpdate to auto start dialogs.
|
||||||
|
|
||||||
if (context.activity.type === 'conversationUpdate' && context.activity.membersAdded.length > 0) {
|
if (context.activity.type === 'conversationUpdate' && context.activity.membersAdded.length > 0) {
|
||||||
|
|
||||||
|
// Check if a bot or a human participant is being added to the conversation.
|
||||||
|
|
||||||
const member = context.activity.membersAdded[0];
|
const member = context.activity.membersAdded[0];
|
||||||
if (context.activity.membersAdded[0].id === context.activity.recipient.id) {
|
if (context.activity.membersAdded[0].id === context.activity.recipient.id) {
|
||||||
GBLog.info(`Bot added to conversation, starting chat...`);
|
GBLog.info(`Bot added to conversation, starting chat...`);
|
||||||
|
|
||||||
|
// Calls onNewSession event on each .gbapp package.
|
||||||
|
|
||||||
await CollectionUtil.asyncForEach(appPackages, async e => {
|
await CollectionUtil.asyncForEach(appPackages, async e => {
|
||||||
await e.onNewSession(min, step);
|
await e.onNewSession(min, step);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Auto starts dialogs if any is specified.
|
||||||
|
|
||||||
const startDialog = min.core.getParam(min.instance, 'Start Dialog', null);
|
const startDialog = min.core.getParam(min.instance, 'Start Dialog', null);
|
||||||
if (startDialog && !user.welcomed) {
|
if (startDialog && !user.welcomed) {
|
||||||
user.welcomed = true;
|
user.welcomed = true;
|
||||||
GBLog.info(`Auto start dialog is now being called: ${startDialog}...`);
|
GBLog.info(`Auto start dialog is now being called: ${startDialog}...`);
|
||||||
await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer);
|
await GBVMService.callVM(startDialog.toLowerCase(), min, step, this.deployer);
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
|
// Otherwise, calls / (root) to default welcome users.
|
||||||
|
|
||||||
await step.beginDialog('/');
|
await step.beginDialog('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -690,19 +822,26 @@ export class GBMinService {
|
||||||
GBLog.info(`Member added to conversation: ${member.name}`);
|
GBLog.info(`Member added to conversation: ${member.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processes messages.
|
|
||||||
} else if (context.activity.type === 'message') {
|
} else if (context.activity.type === 'message') {
|
||||||
// Checks for /admin request.
|
|
||||||
|
// Process messages activities.
|
||||||
|
|
||||||
await this.processMessageActivity(context, min, step);
|
await this.processMessageActivity(context, min, step);
|
||||||
|
|
||||||
// Processes events.
|
|
||||||
} else if (context.activity.type === 'event') {
|
} else if (context.activity.type === 'event') {
|
||||||
// Empties dialog stack before going to the target.
|
|
||||||
|
// Processes events activies.
|
||||||
|
|
||||||
await this.processEventActivity(context, step);
|
await this.processEventActivity(context, step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Saves conversation state for later use.
|
||||||
|
|
||||||
await conversationState.saveChanges(context, true);
|
await conversationState.saveChanges(context, true);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
const msg = `ERROR: ${error.message} ${error.stack ? error.stack : ''}`;
|
const msg = `ERROR: ${error.message} ${error.stack ? error.stack : ''}`;
|
||||||
GBLog.error(msg);
|
GBLog.error(msg);
|
||||||
|
|
||||||
|
@ -711,12 +850,17 @@ export class GBMinService {
|
||||||
step,
|
step,
|
||||||
Messages[step.context.activity.locale].very_sorry_about_error
|
Messages[step.context.activity.locale].very_sorry_about_error
|
||||||
);
|
);
|
||||||
|
|
||||||
await step.beginDialog('/ask', { isReturning: true });
|
await step.beginDialog('/ask', { isReturning: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to handle all event sent by .gbui clients.
|
||||||
|
*/
|
||||||
private async processEventActivity(context, step: GBDialogStep) {
|
private async processEventActivity(context, step: GBDialogStep) {
|
||||||
|
|
||||||
if (context.activity.name === 'whoAmI') {
|
if (context.activity.name === 'whoAmI') {
|
||||||
await step.beginDialog('/whoAmI');
|
await step.beginDialog('/whoAmI');
|
||||||
} else if (context.activity.name === 'showSubjects') {
|
} else if (context.activity.name === 'showSubjects') {
|
||||||
|
@ -744,19 +888,23 @@ export class GBMinService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to handle all text messages sent and received by the bot.
|
||||||
|
*/
|
||||||
private async processMessageActivity(context, min: GBMinInstance, step: GBDialogStep) {
|
private async processMessageActivity(context, min: GBMinInstance, step: GBDialogStep) {
|
||||||
const user = await min.userProfile.get(context, {});
|
|
||||||
|
|
||||||
// Removes <at>Bot Id</at> from MS Teams.
|
// Removes <at>Bot Id</at> from MS Teams.
|
||||||
|
|
||||||
context.activity.text = context.activity.text.trim();
|
context.activity.text = context.activity.text.trim();
|
||||||
context.activity.text = context.activity.text.replace(/\<at\>.*\<\/at\>\s/gi, '')
|
context.activity.text = context.activity.text.replace(/\<at\>.*\<\/at\>\s/gi, '')
|
||||||
|
|
||||||
|
const user = await min.userProfile.get(context, {});
|
||||||
let message: GuaribasConversationMessage;
|
let message: GuaribasConversationMessage;
|
||||||
if (process.env.PRIVACY_STORE_MESSAGES === 'true') {
|
if (process.env.PRIVACY_STORE_MESSAGES === 'true') {
|
||||||
const analytics = new AnalyticsService();
|
|
||||||
// Adds message to the analytics layer.
|
// Adds message to the analytics layer.
|
||||||
|
|
||||||
|
const analytics = new AnalyticsService();
|
||||||
if (user) {
|
if (user) {
|
||||||
|
|
||||||
if (!user.conversation) {
|
if (!user.conversation) {
|
||||||
|
@ -778,12 +926,18 @@ export class GBMinService {
|
||||||
return utterance.match(Messages.global_quit);
|
return utterance.match(Messages.global_quit);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Files in .gbdialog can be called directly by typing its name normalized into JS .
|
||||||
|
|
||||||
const 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) {
|
if (isVMCall) {
|
||||||
await GBVMService.callVM(context.activity.text, min, step, this.deployer);
|
await GBVMService.callVM(context.activity.text, min, step, this.deployer);
|
||||||
} else if (context.activity.text.charAt(0) === '/') {
|
}
|
||||||
|
|
||||||
|
// When user starts text by typing a slash, it can call either a dialog directly
|
||||||
|
// or /call <script> will invoke VM for a given .gbdialog script.
|
||||||
|
|
||||||
|
else if (context.activity.text.charAt(0) === '/') {
|
||||||
|
|
||||||
let text = context.activity.text;
|
let text = context.activity.text;
|
||||||
let parts = text.split(' ');
|
let parts = text.split(' ');
|
||||||
let cmdOrDialogName = parts[0];
|
let cmdOrDialogName = parts[0];
|
||||||
|
@ -795,20 +949,34 @@ export class GBMinService {
|
||||||
} else {
|
} else {
|
||||||
await step.beginDialog(cmdOrDialogName, { args: args });
|
await step.beginDialog(cmdOrDialogName, { args: args });
|
||||||
}
|
}
|
||||||
} else if (globalQuit(step.context.activity.locale, context.activity.text)) {
|
}
|
||||||
|
|
||||||
// TODO: Hard-code additional languages.
|
// Processes global escape keywords like 'quit'.
|
||||||
|
|
||||||
|
else if (globalQuit(step.context.activity.locale, context.activity.text)) {
|
||||||
await step.cancelAllDialogs();
|
await step.cancelAllDialogs();
|
||||||
await min.conversationalService.sendText(min, step, Messages[step.context.activity.locale].canceled);
|
await min.conversationalService.sendText(min, step, Messages[step.context.activity.locale].canceled);
|
||||||
} else if (context.activity.text === 'admin') {
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the admin conversational keyword.
|
||||||
|
|
||||||
|
else if (context.activity.text === 'admin') {
|
||||||
await step.beginDialog('/admin');
|
await step.beginDialog('/admin');
|
||||||
|
|
||||||
// Checks for /menu JSON signature.
|
}
|
||||||
} else if (context.activity.text.startsWith('{"title"')) {
|
|
||||||
|
// Checks for /menu JSON signature.
|
||||||
|
|
||||||
|
else if (context.activity.text.startsWith('{"title"')) {
|
||||||
await step.beginDialog('/menu', JSON.parse(context.activity.text));
|
await step.beginDialog('/menu', JSON.parse(context.activity.text));
|
||||||
|
|
||||||
// Otherwise, continue to the active dialog in the stack.
|
|
||||||
} else if (
|
}
|
||||||
|
|
||||||
|
// Checks if it is the first time the bot are talking and auto-publish default packages.
|
||||||
|
|
||||||
|
else if (
|
||||||
!(await this.deployer.getStoragePackageByName(min.instance.instanceId, `${min.instance.botId}.gbkb`)) &&
|
!(await this.deployer.getStoragePackageByName(min.instance.instanceId, `${min.instance.botId}.gbkb`)) &&
|
||||||
process.env.GBKB_ENABLE_AUTO_PUBLISH === "true"
|
process.env.GBKB_ENABLE_AUTO_PUBLISH === "true"
|
||||||
) {
|
) {
|
||||||
|
@ -816,28 +984,27 @@ export class GBMinService {
|
||||||
`Oi, ainda não possuo pacotes de conhecimento publicados. Por favor, aguarde alguns segundos enquanto eu auto-publico alguns pacotes.`
|
`Oi, ainda não possuo pacotes de conhecimento publicados. Por favor, aguarde alguns segundos enquanto eu auto-publico alguns pacotes.`
|
||||||
);
|
);
|
||||||
await step.beginDialog('/publish', { confirm: true, firstTime: true });
|
await step.beginDialog('/publish', { confirm: true, firstTime: true });
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
// Otherwise, continue to the default dialog processing of the input message.
|
||||||
|
|
||||||
|
else {
|
||||||
|
|
||||||
|
// Removes unwanted chars in input text.
|
||||||
|
|
||||||
let text = context.activity.text;
|
let text = context.activity.text;
|
||||||
const originalText = text;
|
const originalText = text;
|
||||||
text = text.replace(/<([^>]+?)([^>]*?)>(.*?)<\/\1>/gi, '');
|
text = text.replace(/<([^>]+?)([^>]*?)>(.*?)<\/\1>/gi, '');
|
||||||
|
|
||||||
// Spells check the input text before translating,
|
// Saves special words (keep text) in tokens to prevent it from
|
||||||
// keeping fixed tokens as specified in Config.
|
// spell checking and translation.
|
||||||
|
|
||||||
const keepText: string = min.core.getParam(
|
|
||||||
min.instance,
|
|
||||||
'Keep Text',
|
|
||||||
''
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const keepText: string = min.core.getParam(min.instance, 'Keep Text', '');
|
||||||
let keepTextList = [];
|
let keepTextList = [];
|
||||||
|
|
||||||
if (keepTextList) {
|
if (keepTextList) {
|
||||||
keepTextList = keepTextList.concat(keepText.split(';'));
|
keepTextList = keepTextList.concat(keepText.split(';'));
|
||||||
}
|
}
|
||||||
|
|
||||||
let replacements = [];
|
let replacements = [];
|
||||||
|
|
||||||
await CollectionUtil.asyncForEach(min.appPackages, async (e: IGBPackage) => {
|
await CollectionUtil.asyncForEach(min.appPackages, async (e: IGBPackage) => {
|
||||||
const result = await e.onExchangeData(min, 'getKeepText', {});
|
const result = await e.onExchangeData(min, 'getKeepText', {});
|
||||||
if (result) {
|
if (result) {
|
||||||
|
@ -852,25 +1019,25 @@ export class GBMinService {
|
||||||
let it = GBConversationalService.removeDiacritics(item);
|
let it = GBConversationalService.removeDiacritics(item);
|
||||||
|
|
||||||
if (textProcessed.toLowerCase().indexOf(it.toLowerCase()) != -1) {
|
if (textProcessed.toLowerCase().indexOf(it.toLowerCase()) != -1) {
|
||||||
const replacementToken = 'X' + GBAdminService['getNumberIdentifier']().substr(0,4);
|
const replacementToken = 'X' + GBAdminService['getNumberIdentifier']().substr(0, 4);
|
||||||
replacements[i] = { text: item, replacementToken: replacementToken };
|
replacements[i] = { text: item, replacementToken: replacementToken };
|
||||||
i++;
|
i++;
|
||||||
textProcessed = textProcessed.replace(new RegExp(`\\b${it.trim()}\\b`, 'gi'), `${replacementToken}`);
|
textProcessed = textProcessed.replace(new RegExp(`\\b${it.trim()}\\b`, 'gi'), `${replacementToken}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spells check the input text before translating,
|
||||||
|
// keeping fixed tokens as specified in Config.
|
||||||
|
|
||||||
text = await min.conversationalService.spellCheck(min, textProcessed);
|
text = await min.conversationalService.spellCheck(min, textProcessed);
|
||||||
|
|
||||||
// Detects user typed language and updates their locale profile if applies.
|
// Detects user typed language and updates their locale profile if applies.
|
||||||
|
|
||||||
let locale = min.core.getParam<string>(
|
let locale = min.core.getParam<string>(min.instance, 'Default User Language',
|
||||||
min.instance,
|
|
||||||
'Default User Language',
|
|
||||||
GBConfigService.get('DEFAULT_USER_LANGUAGE')
|
GBConfigService.get('DEFAULT_USER_LANGUAGE')
|
||||||
);
|
);
|
||||||
let detectLanguage = min.core.getParam<boolean>(
|
let detectLanguage = min.core.getParam<boolean>(min.instance, 'Language Detector',
|
||||||
min.instance,
|
|
||||||
'Language Detector',
|
|
||||||
GBConfigService.getBoolean('LANGUAGE_DETECTOR')
|
GBConfigService.getBoolean('LANGUAGE_DETECTOR')
|
||||||
) === 'true';
|
) === 'true';
|
||||||
const systemUser = user.systemUser;
|
const systemUser = user.systemUser;
|
||||||
|
@ -902,6 +1069,8 @@ export class GBMinService {
|
||||||
text = await min.conversationalService.translate(min, text, contentLocale);
|
text = await min.conversationalService.translate(min, text, contentLocale);
|
||||||
GBLog.info(`Translated text (processMessageActivity): ${text}.`);
|
GBLog.info(`Translated text (processMessageActivity): ${text}.`);
|
||||||
|
|
||||||
|
// Restores all token text back after spell checking and translation.
|
||||||
|
|
||||||
if (keepTextList) {
|
if (keepTextList) {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
await CollectionUtil.asyncForEach(replacements, item => {
|
await CollectionUtil.asyncForEach(replacements, item => {
|
||||||
|
@ -909,23 +1078,24 @@ export class GBMinService {
|
||||||
text = text.replace(new RegExp(`${item.replacementToken}`, 'gi'), item.text);
|
text = text.replace(new RegExp(`${item.replacementToken}`, 'gi'), item.text);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
GBLog.info(`Final text ready for NLP/Search/.gbapp: ${text}.`);
|
|
||||||
|
|
||||||
context.activity.text = text;
|
context.activity.text = text;
|
||||||
context.activity.originalText = originalText;
|
context.activity.originalText = originalText;
|
||||||
|
GBLog.info(`Final text ready for NLP/Search/.gbapp: ${text}.`);
|
||||||
|
|
||||||
|
|
||||||
// If there is a dialog in course, continue to the next step.
|
// If there is a dialog in course, continue to the next step.
|
||||||
|
|
||||||
if (step.activeDialog !== undefined) {
|
if (step.activeDialog !== undefined) {
|
||||||
await step.continueDialog();
|
await step.continueDialog();
|
||||||
|
|
||||||
} else {
|
}
|
||||||
// Checks if any .gbapp will handle this answer, if not goes to standard kb.gbapp.
|
|
||||||
|
// Checks if any .gbapp will handle this answer, if not goes to standard kb.gbapp.
|
||||||
|
|
||||||
|
else {
|
||||||
|
|
||||||
let nextDialog = null;
|
let nextDialog = null;
|
||||||
await CollectionUtil.asyncForEach(min.appPackages, async (e: IGBPackage) => {
|
await CollectionUtil.asyncForEach(min.appPackages, async (e: IGBPackage) => {
|
||||||
|
|
||||||
nextDialog = await e.onExchangeData(min, 'handleAnswer', {
|
nextDialog = await e.onExchangeData(min, 'handleAnswer', {
|
||||||
query: text,
|
query: text,
|
||||||
step: step,
|
step: step,
|
||||||
|
@ -933,16 +1103,12 @@ export class GBMinService {
|
||||||
message: message ? message['dataValues'] : null,
|
message: message ? message['dataValues'] : null,
|
||||||
user: user ? user['dataValues'] : null
|
user: user ? user['dataValues'] : null
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await step.beginDialog(nextDialog ? nextDialog : '/answer', {
|
await step.beginDialog(nextDialog ? nextDialog : '/answer', {
|
||||||
query: text,
|
query: text,
|
||||||
user: user ? user['dataValues'] : null,
|
user: user ? user['dataValues'] : null,
|
||||||
message: message
|
message: message
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue