2018-04-21 02:59:30 -03:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * \
| ( ) _ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | , _ ) ( _ ) ___ ___ _ |
| ( '_`\ ( ' __ ) / '_` ) /' _ ` \ /' _ ` _ ` \ /'_ ` ) | | | | /',__)/ ' _ `\ /' _ ` \ |
2019-03-09 16:59:31 -03:00
| | ( _ ) ) | | ( ( _ | | ( ( _ ) || ( ) ( ) | ( ( _ | || | _ | | \ __ , \ | ( ˅ ) | ( ( _ ) ) |
2018-04-21 02:59:30 -03:00
| | , __ / '(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( ) _ ) | |
| ( _ ) \ ___ / ' |
| |
| General Bots Copyright ( c ) Pragmatismo . io . All rights reserved . |
| Licensed under the AGPL - 3.0 . |
2018-11-11 19:09:18 -02:00
| |
2018-04-21 02:59:30 -03:00
| 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 , |
2018-09-11 19:40:53 -03:00
| but WITHOUT ANY WARRANTY , without even the implied warranty of |
2018-04-21 02:59:30 -03:00
| 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 . |
| |
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2018-11-11 19:09:18 -02:00
/ * *
* @fileoverview General Bots server core .
* /
'use strict' ;
2018-04-21 02:59:30 -03:00
2019-03-09 16:59:31 -03:00
import urlJoin = require ( 'url-join' ) ;
2019-06-17 21:41:41 -03:00
const { DialogSet , TextPrompt } = require ( 'botbuilder-dialogs' ) ;
2018-11-12 12:20:44 -02:00
const express = require ( 'express' ) ;
const request = require ( 'request-promise-native' ) ;
2019-08-22 01:54:30 +00:00
const removeRoute = require ( 'express-remove-route' ) ;
2018-11-12 12:20:44 -02:00
const AuthenticationContext = require ( 'adal-node' ) . AuthenticationContext ;
2020-01-26 15:27:17 -03:00
const wash = require ( 'washyourmouthoutwithsoap' ) ;
2018-12-01 14:38:08 -02:00
import { AutoSaveStateMiddleware , BotFrameworkAdapter , ConversationState , MemoryStorage , UserState } from 'botbuilder' ;
2020-05-17 19:05:18 +00:00
import { CollectionUtil , AzureText } from 'pragmatismo-io-framework' ;
2019-02-28 15:15:51 -03:00
import { ConfirmPrompt , WaterfallDialog } from 'botbuilder-dialogs' ;
2019-03-09 16:59:31 -03:00
import {
GBDialogStep ,
GBLog ,
GBMinInstance ,
IGBAdminService ,
IGBConversationalService ,
IGBCoreService ,
IGBInstance ,
IGBPackage
} from 'botlib' ;
2019-06-17 21:41:41 -03:00
import { MicrosoftAppCredentials } from 'botframework-connector' ;
import { GBServer } from '../../../src/app' ;
2019-03-09 16:59:31 -03:00
import { AskDialogArgs } from '../../kb.gbapp/dialogs/AskDialog' ;
2020-03-30 14:03:12 -03:00
import { KBService } from '../../kb.gbapp/services/KBService' ;
2018-11-12 12:20:44 -02:00
import { Messages } from '../strings' ;
2019-05-27 11:01:34 -03:00
import { GBConfigService } from './GBConfigService' ;
2019-06-17 21:41:41 -03:00
import { GBDeployer } from './GBDeployer' ;
2019-08-22 19:36:23 -03:00
import { SecService } from '../../security.gblib/services/SecService' ;
2020-01-27 16:19:09 -03:00
import { AnalyticsService } from '../../analytics.gblib/services/AnalyticsService' ;
2020-04-03 09:46:13 -03:00
import { WhatsappDirectLine } from '../../whatsapp.gblib/services/WhatsappDirectLine' ;
2020-05-14 12:46:57 -03:00
import fs = require ( 'fs' ) ;
2020-05-17 21:30:21 +00:00
import { GBConversationalService } from './GBConversationalService' ;
2018-04-21 02:59:30 -03:00
2019-03-09 16:59:31 -03:00
/ * *
* Minimal service layer for a bot .
* /
2018-04-21 02:59:30 -03:00
export class GBMinService {
2018-11-12 12:20:44 -02:00
public core : IGBCoreService ;
public conversationalService : IGBConversationalService ;
public adminService : IGBAdminService ;
public deployer : GBDeployer ;
2020-03-30 14:03:12 -03:00
2019-08-22 01:54:30 +00:00
private static uiPackage = 'default.gbui' ;
2018-04-21 02:59:30 -03:00
2018-11-12 12:20:44 -02:00
public corePackage = 'core.gbai' ;
2018-11-02 14:19:41 -03:00
2018-04-21 02:59:30 -03:00
/ * *
2018-06-04 05:33:37 -03:00
* Static initialization of minimal instance .
2018-04-21 02:59:30 -03:00
*
* @param core Basic database services to identify instance , for example .
* /
constructor (
2018-09-24 11:04:36 -03:00
core : IGBCoreService ,
conversationalService : IGBConversationalService ,
adminService : IGBAdminService ,
2018-11-27 22:56:11 -02:00
deployer : GBDeployer
2018-04-21 02:59:30 -03:00
) {
2018-09-24 11:04:36 -03:00
this . core = core ;
this . conversationalService = conversationalService ;
this . adminService = adminService ;
this . deployer = deployer ;
2018-04-21 02:59:30 -03:00
}
2018-09-11 19:33:58 -03:00
/ * *
*
* Constructs a new minimal instance for each bot .
*
2018-09-10 12:09:48 -03:00
* @param server An HTTP server .
* @param appPackages List of loaded . gbapp associated with this instance .
2018-09-11 19:33:58 -03:00
*
2018-09-10 12:09:48 -03:00
* @return Loaded minimal bot instance .
2018-09-11 19:33:58 -03:00
*
2019-03-09 16:59:31 -03:00
* /
2018-04-21 02:59:30 -03:00
2018-11-12 12:20:44 -02:00
public async buildMin (
2019-03-08 06:37:13 -03:00
instances : IGBInstance [ ] ,
) {
2019-06-28 11:17:41 -03:00
// Serves default UI on root address '/' if web enabled.
if ( process . env . DISABLE_WEB !== 'true' ) {
2020-02-26 15:20:47 -03:00
let url = GBServer . globals . wwwroot ?
GBServer . globals . wwwroot :
2020-03-31 09:11:04 -03:00
urlJoin ( GBDeployer . deployFolder , GBMinService . uiPackage , 'build' ) ;
2020-02-25 10:13:38 -03:00
2020-02-26 15:20:47 -03:00
GBServer . globals . server . use ( '/' , express . static ( url ) ) ;
2019-08-22 01:54:30 +00:00
}
// Serves the bot information object via HTTP so clients can get
// instance information stored on server.
if ( process . env . DISABLE_WEB !== 'true' ) {
GBServer . globals . server . get ( '/instances/:botId' , ( req , res ) = > {
( async ( ) = > {
2020-01-27 16:19:09 -03:00
await this . handleGetInstanceForClient ( req , res ) ;
2019-08-22 01:54:30 +00:00
} ) ( ) ;
} ) ;
2019-06-28 11:17:41 -03:00
}
2019-08-22 17:28:11 -03:00
const url = '/webhooks/whatsapp' ;
GBServer . globals . server . post ( url , async ( req , res ) = > {
2019-08-23 14:36:47 -03:00
try {
2019-08-23 02:23:00 -03:00
2019-08-23 14:36:47 -03:00
const id = req . body . messages [ 0 ] . chatId . split ( '@' ) [ 0 ] ;
2020-05-02 21:28:13 -03:00
const senderName = req . body . messages [ 0 ] . senderName ;
2019-08-23 14:36:47 -03:00
const text = req . body . messages [ 0 ] . body ;
if ( req . body . messages [ 0 ] . fromMe ) {
res . end ( ) ;
return ; // Exit here.
}
2020-04-03 00:17:21 -03:00
let activeMin ;
if ( process . env . WHATSAPP_WELCOME_DISABLED !== "true" ) {
2020-06-04 00:05:53 -03:00
let toSwitchMin = GBServer . globals . minInstances . filter ( p = > p . instance . botId . toLowerCase ( ) === text . toLowerCase ( ) ) [ 0 ] ;
2020-06-04 16:18:02 -03:00
if ( ! toSwitchMin ) {
2020-06-04 21:00:53 -03:00
toSwitchMin = GBServer . globals . minInstances . filter ( p = > p . instance . activationCode ? p . instance . activationCode . toLowerCase ( ) === text . toLowerCase ( ) : false ) [ 0 ] ;
2020-06-04 00:05:53 -03:00
}
2020-04-03 00:17:21 -03:00
activeMin = toSwitchMin ? toSwitchMin : GBServer.globals.minBoot ;
2019-08-23 14:36:47 -03:00
2020-04-03 00:17:21 -03:00
let sec = new SecService ( ) ;
2020-05-27 23:01:44 -03:00
2020-05-02 21:28:13 -03:00
let user = await sec . getUserFromSystemId ( id ) ;
2019-08-23 02:23:00 -03:00
2020-04-03 00:17:21 -03:00
if ( user === null ) {
2020-05-02 21:28:13 -03:00
user = await sec . ensureUser ( activeMin . instance . instanceId , id , senderName , "" , "whatsapp" , senderName ) ;
2020-04-15 20:56:28 -03:00
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. ` ) ;
2019-08-23 14:36:47 -03:00
res . end ( ) ;
2020-04-03 00:17:21 -03:00
} else {
// User wants to switch bots.
if ( toSwitchMin !== undefined ) {
2020-06-04 16:18:02 -03:00
2020-06-04 00:05:53 -03:00
const instance = await this . core . loadInstanceByBotId ( activeMin . botId ) ;
2020-05-02 21:28:13 -03:00
await sec . updateUserInstance ( id , instance . instanceId ) ;
2020-04-15 01:42:54 +00:00
await ( activeMin as any ) . whatsAppDirectLine . resetConversationId ( id ) ;
2020-04-03 00:17:21 -03:00
await ( activeMin as any ) . whatsAppDirectLine . sendToDevice ( id , ` Agora falando com ${ activeMin . instance . title } ... ` ) ;
res . end ( ) ;
}
else {
2020-05-23 11:10:06 -03:00
activeMin = GBServer . globals . minInstances . filter ( p = > p . instance . instanceId === user . instanceId ) [ 0 ] ;
2020-04-30 09:14:32 -03:00
if ( activeMin === undefined ) {
activeMin = GBServer . globals . minBoot ;
2020-05-02 21:28:13 -03:00
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 } ... ` ) ;
2020-04-30 09:14:32 -03:00
}
2020-04-15 01:42:54 +00:00
await ( activeMin as any ) . whatsAppDirectLine . received ( req , res ) ;
2020-04-03 00:17:21 -03:00
}
2019-08-23 14:36:47 -03:00
}
}
2020-04-03 00:17:21 -03:00
else {
2020-04-15 01:42:54 +00:00
await ( GBServer . globals . minBoot as any ) . whatsAppDirectLine . received ( req , res ) ;
2020-04-03 00:17:21 -03:00
}
2019-08-23 14:36:47 -03:00
} catch ( error ) {
2020-06-14 21:40:41 -03:00
GBLog . error ( ` Error on Whatsapp callback: ${ error . data ? error.data : error } ` ) ;
2019-08-23 14:36:47 -03:00
}
2020-04-03 00:17:21 -03:00
2019-08-22 17:28:11 -03:00
} ) ;
2018-10-14 19:58:54 -03:00
2020-05-06 14:12:47 +00:00
await CollectionUtil . asyncForEach ( instances , async instance = > {
2020-05-12 19:20:59 -03:00
try {
2019-08-22 01:54:30 +00:00
await this . mountBot ( instance ) ;
2020-05-06 14:12:47 +00:00
} catch ( error ) {
GBLog . error ( ` Error mounting bot ${ instance . botId } : ${ error . message } ` ) ;
}
} ) ;
2019-08-22 01:54:30 +00:00
}
2018-09-24 11:04:36 -03:00
2019-08-22 01:54:30 +00:00
public async unmountBot ( botId : string ) {
const url = ` /api/messages/ ${ botId } ` ;
2019-08-22 17:28:11 -03:00
removeRoute ( GBServer . globals . server , url ) ;
2019-02-23 13:17:21 -03:00
2019-08-22 01:54:30 +00:00
const uiUrl = ` / ${ botId } ` ;
removeRoute ( GBServer . globals . server , uiUrl ) ;
2019-08-22 17:28:11 -03:00
2020-05-02 21:28:13 -03:00
GBServer . globals . minInstances = GBServer . globals . minInstances . filter ( p = > p . instance . botId !== botId ) ;
2019-08-22 01:54:30 +00:00
}
2018-09-24 11:04:36 -03:00
2019-08-22 01:54:30 +00:00
public async mountBot ( instance : IGBInstance ) {
2019-08-22 17:28:11 -03:00
2019-08-22 01:54:30 +00:00
// Build bot adapter.
2020-06-12 15:55:18 -03:00
const { min , adapter , conversationState } = await this . buildBotAdapter ( instance , GBServer . globals . sysPackages , GBServer . globals . appPackages ) ;
2019-08-22 17:28:11 -03:00
GBServer . globals . minInstances . push ( min ) ;
2020-05-15 14:07:30 -03:00
await this . deployer . deployPackage ( min , 'packages/default.gbtheme' ) ;
2020-05-14 12:48:36 -03:00
2020-05-14 12:46:57 -03:00
// Install per bot deployed packages.
2020-05-12 19:20:59 -03:00
2020-05-14 12:46:57 -03:00
let packagePath = ` work/ ${ min . botId } .gbdialog ` ;
if ( fs . existsSync ( packagePath ) ) {
await this . deployer . deployPackage ( min , packagePath ) ;
}
packagePath = ` work/ ${ min . botId } .gbapp ` ;
if ( fs . existsSync ( packagePath ) ) {
await this . deployer . deployPackage ( min , packagePath ) ;
}
packagePath = ` work/ ${ min . botId } .gbtheme ` ;
if ( fs . existsSync ( packagePath ) ) {
await this . deployer . deployPackage ( min , packagePath ) ;
}
packagePath = ` work/ ${ min . botId } .gblib ` ;
if ( fs . existsSync ( packagePath ) ) {
await this . deployer . deployPackage ( min , packagePath ) ;
}
2019-08-22 17:28:11 -03:00
2019-08-22 01:54:30 +00:00
// Call the loadBot context.activity for all packages.
2020-04-13 19:14:55 -03:00
await this . invokeLoadBot ( GBServer . globals . appPackages , GBServer . globals . sysPackages , min ) ;
2019-08-22 17:28:11 -03:00
2019-08-22 01:54:30 +00:00
// Serves individual URL for each bot conversational interface...
const url = ` /api/messages/ ${ instance . botId } ` ;
GBServer . globals . server . post ( url , async ( req , res ) = > {
await this . receiver ( adapter , req , res , conversationState , min , instance , GBServer . globals . appPackages ) ;
} ) ;
GBLog . info ( ` GeneralBots( ${ instance . engineName } ) listening on: ${ url } . ` ) ;
2019-08-22 17:28:11 -03:00
2019-08-22 01:54:30 +00:00
// Serves individual URL for each bot user interface.
if ( process . env . DISABLE_WEB !== 'true' ) {
const uiUrl = ` / ${ instance . botId } ` ;
2020-05-19 14:59:07 -03:00
const uiUrlAlt = ` / ${ instance . activationCode } ` ;
2019-08-22 01:54:30 +00:00
GBServer . globals . server . use ( uiUrl , express . static ( urlJoin ( GBDeployer . deployFolder , GBMinService . uiPackage , 'build' ) ) ) ;
2020-05-19 14:59:07 -03:00
GBServer . globals . server . use ( uiUrlAlt , express . static ( urlJoin ( GBDeployer . deployFolder , GBMinService . uiPackage , 'build' ) ) ) ;
GBLog . info ( ` Bot UI ${ GBMinService . uiPackage } accessible at: ${ uiUrl } and ${ uiUrlAlt } . ` ) ;
2019-08-22 01:54:30 +00:00
}
2018-09-24 11:04:36 -03:00
2019-08-22 01:54:30 +00:00
// 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
// some resource they own.
this . handleOAuthRequests ( GBServer . globals . server , min ) ;
2019-08-22 17:28:11 -03:00
2019-08-22 01:54:30 +00:00
// After consent is granted AAD redirects here. The ADAL library
// is invoked via the AuthenticationContext and retrieves an
// access token that can be used to access the user owned resource.
this . handleOAuthTokenRequests ( GBServer . globals . server , min , instance ) ;
2020-05-12 19:20:59 -03:00
this . createCheckHealthAddress ( GBServer . globals . server , min , min . instance ) ;
}
private createCheckHealthAddress ( server : any , min : GBMinInstance , instance : IGBInstance ) {
server . get ( ` / ${ min . instance . botId } /check ` , async ( req , res ) = > {
try {
if ( min . whatsAppDirectLine != undefined && instance . whatsappServiceKey !== null ) {
2020-05-12 19:41:51 -03:00
if ( ! await min . whatsAppDirectLine . check ( min ) ) {
const error = ` WhatsApp API lost connection. ` ;
GBLog . error ( error ) ;
res . status ( 500 ) . send ( error ) ;
2020-05-14 12:46:57 -03:00
2020-05-12 19:41:51 -03:00
return ;
}
2020-05-12 19:20:59 -03:00
}
res . status ( 200 ) . send ( ` General Bot ${ min . botId } is healthly. ` ) ;
} catch ( error ) {
GBLog . error ( error ) ;
res . status ( 500 ) . send ( error . toString ( ) ) ;
}
} ) ;
2018-04-21 02:59:30 -03:00
}
2020-05-12 19:20:59 -03:00
2019-03-08 06:37:13 -03:00
private handleOAuthTokenRequests ( server : any , min : GBMinInstance , instance : IGBInstance ) {
2019-02-23 13:17:21 -03:00
server . get ( ` / ${ min . instance . botId } /token ` , async ( req , res ) = > {
2019-03-08 06:37:13 -03:00
const state = await min . adminService . getValue ( instance . instanceId , 'AntiCSRFAttackState' ) ;
2019-02-23 13:17:21 -03:00
if ( req . query . state !== state ) {
const msg = 'WARNING: state field was not provided as anti-CSRF token' ;
2019-03-08 17:05:58 -03:00
GBLog . error ( msg ) ;
2019-02-23 13:17:21 -03:00
throw new Error ( msg ) ;
}
const authenticationContext = new AuthenticationContext (
2019-03-09 16:59:31 -03:00
urlJoin ( min . instance . authenticatorAuthorityHostUrl , min . instance . authenticatorTenant )
2019-02-25 08:36:43 -03:00
) ;
2019-02-23 13:17:21 -03:00
const resource = 'https://graph.microsoft.com' ;
authenticationContext . acquireTokenWithAuthorizationCode (
2019-02-25 08:36:43 -03:00
req . query . code ,
2019-03-09 16:59:31 -03:00
urlJoin ( instance . botEndpoint , min . instance . botId , '/token' ) ,
2019-02-25 08:36:43 -03:00
resource ,
2020-05-14 17:16:27 -03:00
instance . marketplaceId ,
instance . marketplacePassword ,
2019-02-25 08:36:43 -03:00
async ( err , token ) = > {
if ( err ) {
const msg = ` Error acquiring token: ${ err } ` ;
2019-03-08 17:05:58 -03:00
GBLog . error ( msg ) ;
2019-02-25 08:36:43 -03:00
res . send ( msg ) ;
} else {
2019-03-11 19:32:47 -03:00
this . adminService . setValue ( instance . instanceId , 'refreshToken' , token . refreshToken ) ;
this . adminService . setValue ( instance . instanceId , 'accessToken' , token . accessToken ) ;
this . adminService . setValue ( instance . instanceId , 'expiresOn' , token . expiresOn . toString ( ) ) ;
this . adminService . setValue ( instance . instanceId , 'AntiCSRFAttackState' , undefined ) ;
2019-02-25 08:36:43 -03:00
res . redirect ( min . instance . botEndpoint ) ;
}
2019-02-23 13:17:21 -03:00
}
2019-02-25 08:36:43 -03:00
) ;
2019-02-23 13:17:21 -03:00
} ) ;
}
private handleOAuthRequests ( server : any , min : GBMinInstance ) {
2019-03-09 16:59:31 -03:00
server . get ( ` / ${ min . instance . botId } /auth ` , ( req , res ) = > {
let authorizationUrl = urlJoin (
2019-02-25 08:36:43 -03:00
min . instance . authenticatorAuthorityHostUrl ,
min . instance . authenticatorTenant ,
'/oauth2/authorize'
) ;
authorizationUrl = ` ${ authorizationUrl } ?response_type=code&client_id= ${
2020-05-14 17:16:27 -03:00
min . instance . marketplaceId
2019-05-14 23:02:21 -03:00
} & redirect_uri = $ { urlJoin ( min . instance . botEndpoint , min . instance . botId , 'token' ) } ` ;
2019-02-23 13:17:21 -03:00
res . redirect ( authorizationUrl ) ;
} ) ;
}
/ * *
* Returns the instance object to clients requesting bot info .
* /
2020-01-27 16:19:09 -03:00
private async handleGetInstanceForClient ( req : any , res : any ) {
2019-02-23 13:17:21 -03:00
let botId = req . params . botId ;
2019-06-04 11:21:32 -03:00
if ( botId === '[default]' || botId === undefined ) {
2019-05-27 11:01:34 -03:00
botId = GBConfigService . get ( 'BOT_ID' ) ;
2019-02-23 13:17:21 -03:00
}
2020-05-24 17:06:05 -03:00
let instance = await this . core . loadInstanceByBotId ( botId ) ;
2020-05-27 23:01:44 -03:00
if ( instance === null ) {
2020-05-24 17:06:05 -03:00
instance = await this . core . loadInstanceByActivationCode ( botId ) ;
}
2019-05-15 12:41:04 -03:00
if ( instance !== null ) {
2020-01-10 10:04:26 -03:00
const webchatTokenContainer = await this . getWebchatToken ( instance ) ;
2019-05-15 12:41:04 -03:00
const speechToken = instance . speechKey != null ? await this . getSTSToken ( instance ) : null ;
2019-02-23 13:17:21 -03:00
let theme = instance . theme ;
2019-07-04 15:14:26 -03:00
if ( theme === undefined ) {
2019-02-23 13:17:21 -03:00
theme = 'default.gbtheme' ;
}
2019-02-25 08:36:43 -03:00
res . send (
JSON . stringify ( {
instanceId : instance.instanceId ,
botId : botId ,
theme : theme ,
2020-01-10 10:04:26 -03:00
webchatToken : webchatTokenContainer.token ,
2019-02-25 08:36:43 -03:00
speechToken : speechToken ,
2020-01-10 10:04:26 -03:00
conversationId : webchatTokenContainer.conversationId ,
2019-02-25 08:36:43 -03:00
authenticatorTenant : instance.authenticatorTenant ,
2020-05-14 17:16:27 -03:00
authenticatorClientId : instance.marketplaceId
2019-02-25 08:36:43 -03:00
} )
) ;
2019-02-23 13:17:21 -03:00
} else {
const error = ` Instance not found: ${ botId } . ` ;
res . sendStatus ( error ) ;
2019-03-08 17:05:58 -03:00
GBLog . error ( error ) ;
2019-02-23 13:17:21 -03:00
}
}
2018-11-12 12:20:44 -02:00
/ * *
* Get Webchat key from Bot Service .
*
* @param instance The Bot instance .
*
* /
2019-02-23 13:17:21 -03:00
private async getWebchatToken ( instance : any ) {
2018-11-12 12:20:44 -02:00
const options = {
url : 'https://directline.botframework.com/v3/directline/tokens/generate' ,
method : 'POST' ,
headers : {
2018-11-27 22:56:11 -02:00
Authorization : ` Bearer ${ instance . webchatKey } `
}
2018-11-12 12:20:44 -02:00
} ;
try {
const json = await request ( options ) ;
2019-03-08 06:49:22 -03:00
2018-11-12 12:20:44 -02:00
return Promise . resolve ( JSON . parse ( json ) ) ;
} catch ( error ) {
2018-11-17 08:52:16 -02:00
const msg = ` [botId: ${
instance . botId
2019-05-14 23:02:21 -03:00
} ] Error calling Direct Line client , verify Bot endpoint on the cloud . Error is : $ { error } . ` ;
2019-03-08 06:49:22 -03:00
2018-11-12 12:20:44 -02:00
return Promise . reject ( new Error ( msg ) ) ;
}
}
/ * *
* Gets a Speech to Text / Text to Speech token from the provider .
*
* @param instance The general bot instance .
*
* /
2019-02-23 13:17:21 -03:00
private async getSTSToken ( instance : any ) {
2018-11-12 12:20:44 -02:00
const options = {
2019-10-23 22:05:46 -03:00
url : instance.speechEndpoint ,
2018-11-12 12:20:44 -02:00
method : 'POST' ,
headers : {
2018-11-27 22:56:11 -02:00
'Ocp-Apim-Subscription-Key' : instance . speechKey
}
2018-11-12 12:20:44 -02:00
} ;
try {
return await request ( options ) ;
} catch ( error ) {
const msg = ` Error calling Speech to Text client. Error is: ${ error } . ` ;
2019-03-08 06:49:22 -03:00
2018-11-12 12:20:44 -02:00
return Promise . reject ( new Error ( msg ) ) ;
}
}
2020-06-12 15:55:18 -03:00
private async buildBotAdapter ( instance : any , sysPackages : IGBPackage [ ] , appPackages : IGBPackage [ ] ) {
2018-11-12 12:20:44 -02:00
const adapter = new BotFrameworkAdapter ( {
2018-09-10 12:09:48 -03:00
appId : instance.marketplaceId ,
2018-11-27 22:56:11 -02:00
appPassword : instance.marketplacePassword
2018-09-24 11:04:36 -03:00
} ) ;
const storage = new MemoryStorage ( ) ;
2019-06-17 21:41:41 -03:00
2018-09-24 11:04:36 -03:00
const conversationState = new ConversationState ( storage ) ;
const userState = new UserState ( storage ) ;
2018-11-01 18:00:09 -03:00
adapter . use ( new AutoSaveStateMiddleware ( conversationState , userState ) ) ;
2019-06-17 21:41:41 -03:00
MicrosoftAppCredentials . trustServiceUrl ( 'https://directline.botframework.com' ,
new Date ( new Date ( ) . setFullYear ( new Date ( ) . getFullYear ( ) + 10 ) ) ) ;
2018-09-10 12:09:48 -03:00
// The minimal bot is built here.
2018-09-11 19:33:58 -03:00
2018-11-12 12:20:44 -02:00
const min = new GBMinInstance ( ) ;
2020-04-15 01:42:54 +00:00
if ( GBServer . globals . minBoot === undefined ) {
GBServer . globals . minBoot = min ;
}
2018-09-24 11:04:36 -03:00
min . botId = instance . botId ;
min . bot = adapter ;
min . userState = userState ;
min . core = this . core ;
min . conversationalService = this . conversationalService ;
min . adminService = this . adminService ;
2020-02-25 12:37:10 -03:00
min . deployService = this . deployer ;
2020-03-30 14:03:12 -03:00
min . kbService = new KBService ( this . core . sequelize ) ;
2020-06-15 00:40:25 -03:00
min . instance = instance ;
2018-12-01 17:31:57 -02:00
min . cbMap = { } ;
2019-02-25 08:36:43 -03:00
min . scriptMap = { } ;
2019-05-15 22:30:14 -03:00
min . sandBoxMap = { } ;
2019-06-18 16:03:19 -03:00
min . packages = sysPackages ;
2020-06-12 15:55:18 -03:00
min . appPackages = appPackages ;
2020-04-15 01:42:54 +00:00
2020-04-03 09:58:14 -03:00
if ( min . instance . whatsappServiceKey !== null ) {
2020-04-03 09:46:13 -03:00
min . whatsAppDirectLine = new WhatsappDirectLine (
2020-04-13 19:14:55 -03:00
min ,
2020-04-03 09:46:13 -03:00
min . botId ,
min . instance . whatsappBotKey ,
min . instance . whatsappServiceKey ,
min . instance . whatsappServiceNumber ,
min . instance . whatsappServiceUrl
) ;
2020-04-30 09:14:32 -03:00
await min . whatsAppDirectLine . setup ( true ) ;
2020-04-03 09:46:13 -03:00
}
2020-04-14 19:33:24 -03:00
else {
2020-04-15 01:42:54 +00:00
const minBoot = GBServer . globals . minBoot as any ;
2020-06-11 09:47:59 -03:00
if ( minBoot . instance . whatsappServiceKey ) {
min . whatsAppDirectLine =
new WhatsappDirectLine (
min ,
min . botId ,
2020-06-15 00:40:25 -03:00
min . instance . whatsappBotKey ,
2020-06-11 09:47:59 -03:00
minBoot . instance . whatsappServiceKey ,
minBoot . instance . whatsappServiceNumber ,
minBoot . instance . whatsappServiceUrl
) ;
await min . whatsAppDirectLine . setup ( false ) ;
}
2020-04-14 19:33:24 -03:00
}
2020-04-30 09:14:32 -03:00
2018-11-12 12:20:44 -02:00
min . userProfile = conversationState . createProperty ( 'userProfile' ) ;
const dialogState = conversationState . createProperty ( 'dialogState' ) ;
2018-12-01 14:38:08 -02:00
2018-11-01 18:00:09 -03:00
min . dialogs = new DialogSet ( dialogState ) ;
2018-11-12 12:20:44 -02:00
min . dialogs . add ( new TextPrompt ( 'textPrompt' ) ) ;
2019-01-31 11:32:33 -02:00
min . dialogs . add ( new ConfirmPrompt ( 'confirmPrompt' ) ) ;
2018-09-24 11:04:36 -03:00
return { min , adapter , conversationState } ;
2018-09-10 12:09:48 -03:00
}
2018-09-09 14:39:37 -03:00
2020-04-13 19:14:55 -03:00
private async invokeLoadBot ( appPackages : IGBPackage [ ] , sysPackages : IGBPackage [ ] , min : GBMinInstance ) {
await CollectionUtil . asyncForEach ( sysPackages , async e = > {
await e . loadBot ( min ) ;
} ) ;
2020-04-03 09:58:14 -03:00
2020-04-13 19:14:55 -03:00
await CollectionUtil . asyncForEach ( appPackages , async p = > {
2019-02-28 15:15:51 -03:00
p . sysPackages = sysPackages ;
2020-04-13 19:14:55 -03:00
await p . loadBot ( min ) ;
2019-02-28 15:15:51 -03:00
if ( p . getDialogs !== undefined ) {
2020-04-13 19:14:55 -03:00
const dialogs = await p . getDialogs ( min ) ;
2019-02-28 15:15:51 -03:00
dialogs . forEach ( dialog = > {
2019-06-04 11:21:32 -03:00
min . dialogs . add ( new WaterfallDialog ( dialog . id , dialog . waterfall ) ) ;
2019-02-28 15:15:51 -03:00
} ) ;
}
2020-04-13 19:14:55 -03:00
} ) ;
2018-09-10 12:09:48 -03:00
}
2018-04-21 02:59:30 -03:00
2018-09-11 12:04:50 -03:00
/ * *
* Bot Service hook method .
* /
2018-12-01 14:38:08 -02:00
private async receiver (
2018-09-11 19:33:58 -03:00
adapter : BotFrameworkAdapter ,
req : any ,
res : any ,
conversationState : ConversationState ,
2019-02-23 13:17:21 -03:00
min : GBMinInstance ,
2018-09-11 19:33:58 -03:00
instance : any ,
2018-11-27 22:56:11 -02:00
appPackages : any [ ]
2018-09-11 19:33:58 -03:00
) {
2019-02-23 13:17:21 -03:00
await adapter . processActivity ( req , res , async context = > {
2018-12-01 14:38:08 -02:00
// Get loaded user state
2019-03-08 06:37:13 -03:00
const step = await min . dialogs . createContext ( context ) ;
2019-05-15 22:30:14 -03:00
step . context . activity . locale = 'pt-BR' ;
2018-10-14 19:58:54 -03:00
2018-09-16 17:00:17 -03:00
try {
2019-06-17 21:41:41 -03:00
2018-11-01 18:00:09 -03:00
const user = await min . userProfile . get ( context , { } ) ;
2018-11-02 14:19:41 -03:00
2019-08-22 19:36:23 -03:00
// First time processing.
2018-09-16 17:00:17 -03:00
if ( ! user . loaded ) {
2020-05-17 21:30:21 +00:00
await min . conversationalService . sendEvent ( min , step , 'loadInstance' , {
2018-09-16 17:00:17 -03:00
instanceId : instance.instanceId ,
botId : instance.botId ,
2018-11-17 08:52:16 -02:00
theme : instance.theme ? instance . theme : 'default.gbtheme' ,
2018-11-27 22:56:11 -02:00
secret : instance.webchatKey
2018-09-24 11:04:36 -03:00
} ) ;
user . loaded = true ;
user . subjects = [ ] ;
2019-03-09 16:59:31 -03:00
user . cb = undefined ;
2020-01-27 16:19:09 -03:00
2020-06-04 16:18:02 -03:00
if ( context . activity . from . id !== min . botId ) {
2019-08-24 18:46:04 -03:00
let sec = new SecService ( ) ;
2020-06-04 16:18:02 -03:00
const member = context . activity . from ;
2019-08-22 19:36:23 -03:00
2020-01-27 16:19:09 -03:00
const persistedUser = await sec . ensureUser ( instance . instanceId , member . id ,
2020-05-02 21:28:13 -03:00
member . name , "" , "web" , member . name ) ;
2020-01-27 16:19:09 -03:00
const analytics = new AnalyticsService ( ) ;
user . systemUser = persistedUser ;
user . conversation = await analytics . createConversation ( persistedUser ) ;
2019-08-24 18:46:04 -03:00
}
2020-01-27 16:19:09 -03:00
await min . userProfile . set ( step . context , user ) ;
2018-09-16 17:00:17 -03:00
}
2018-09-13 18:21:22 -03:00
2019-03-08 17:05:58 -03:00
GBLog . info (
2020-05-25 14:53:28 -03:00
` User>: text: ${ context . activity . text } (type: ${ context . activity . type } , name: ${ context . activity . name } , channelId: ${ context . activity . channelId } , value: ${ context . activity . value } ) `
2018-09-24 11:04:36 -03:00
) ;
2018-12-01 14:38:08 -02:00
if ( context . activity . type === 'conversationUpdate' && context . activity . membersAdded . length > 0 ) {
2018-11-12 12:20:44 -02:00
const member = context . activity . membersAdded [ 0 ] ;
2019-06-17 21:41:41 -03:00
if ( member . name === min . instance . title ) {
2019-03-08 17:05:58 -03:00
GBLog . info ( ` Bot added to conversation, starting chat... ` ) ;
2020-04-13 19:14:55 -03:00
await CollectionUtil . asyncForEach ( appPackages , async e = > {
await e . onNewSession ( min , step ) ;
2018-09-24 11:04:36 -03:00
} ) ;
2020-02-26 15:20:47 -03:00
await step . beginDialog ( '/' ) ;
2018-09-16 17:00:17 -03:00
} else {
2019-03-08 17:05:58 -03:00
GBLog . info ( ` Member added to conversation: ${ member . name } ` ) ;
2018-09-16 17:00:17 -03:00
}
2018-09-13 18:21:22 -03:00
2018-09-16 17:00:17 -03:00
// Processes messages.
2018-11-12 12:20:44 -02:00
} else if ( context . activity . type === 'message' ) {
2018-09-16 17:00:17 -03:00
// Checks for /admin request.
2019-02-23 13:17:21 -03:00
await this . processMessageActivity ( context , min , step ) ;
2018-09-12 05:18:37 -03:00
2018-09-16 17:00:17 -03:00
// Processes events.
2018-11-12 12:20:44 -02:00
} else if ( context . activity . type === 'event' ) {
2018-09-16 17:00:17 -03:00
// Empties dialog stack before going to the target.
2019-02-23 13:17:21 -03:00
await this . processEventActivity ( context , step ) ;
2018-09-10 12:09:48 -03:00
}
2018-12-01 14:38:08 -02:00
await conversationState . saveChanges ( context , true ) ;
2018-09-16 17:00:17 -03:00
} catch ( error ) {
2018-11-12 12:20:44 -02:00
const msg = ` ERROR: ${ error . message } ${ error . stack ? error . stack : '' } ` ;
2019-03-08 17:05:58 -03:00
GBLog . error ( msg ) ;
2018-10-14 19:58:54 -03:00
2020-05-17 21:30:21 +00:00
await min . conversationalService . sendText ( min , step , Messages [ step . context . activity . locale ] . very_sorry_about_error ) ;
2018-11-12 12:20:44 -02:00
await step . beginDialog ( '/ask' , { isReturning : true } ) ;
2018-09-10 12:09:48 -03:00
}
2018-09-24 11:04:36 -03:00
} ) ;
2018-09-10 12:09:48 -03:00
}
2019-02-23 13:17:21 -03:00
2019-03-08 17:05:58 -03:00
private async processEventActivity ( context , step : GBDialogStep ) {
2019-02-23 13:17:21 -03:00
if ( context . activity . name === 'whoAmI' ) {
await step . beginDialog ( '/whoAmI' ) ;
} else if ( context . activity . name === 'showSubjects' ) {
2019-04-08 11:30:01 -03:00
await step . beginDialog ( '/menu' , undefined ) ;
2019-02-23 13:17:21 -03:00
} 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' ) {
2019-03-09 16:59:31 -03:00
await step . beginDialog ( '/answerEvent' , < AskDialogArgs > {
2019-02-25 08:36:43 -03:00
questionId : context.activity.data ,
2019-02-23 13:17:21 -03:00
fromFaq : true
} ) ;
} else if ( context . activity . name === 'quality' ) {
await step . beginDialog ( '/quality' , {
2019-02-25 08:36:43 -03:00
score : context.activity.data
2019-02-23 13:17:21 -03:00
} ) ;
} else if ( context . activity . name === 'updateToken' ) {
2019-02-25 08:36:43 -03:00
const token = context . activity . data ;
2019-02-23 13:17:21 -03:00
await step . beginDialog ( '/adminUpdateToken' , { token : token } ) ;
} else {
await step . continueDialog ( ) ;
}
}
2019-03-08 17:05:58 -03:00
private async processMessageActivity ( context , min : GBMinInstance , step : GBDialogStep ) {
2020-01-27 16:19:09 -03:00
2020-03-31 09:11:04 -03:00
if ( process . env . PRIVACY_STORE_MESSAGES === "true" ) {
2020-01-27 16:19:09 -03:00
2020-03-31 09:11:04 -03:00
// Adds message to the analytics layer.
2020-01-27 16:19:09 -03:00
2020-03-31 09:11:04 -03:00
const analytics = new AnalyticsService ( ) ;
const user = await min . userProfile . get ( context , { } ) ;
2020-06-04 16:18:02 -03:00
if ( user ) {
analytics . createMessage ( min . instance . instanceId ,
user . conversation , user . systemUser . userId ,
context . activity . text ) ;
}
2020-03-31 09:11:04 -03:00
}
2020-04-03 00:17:21 -03:00
2020-01-27 16:19:09 -03:00
// Checks for global exit kewywords cancelling any active dialogs.
2019-07-04 15:14:26 -03:00
const globalQuit = ( locale , utterance ) = > {
2020-05-17 19:05:18 +00:00
return utterance . match ( Messages . global_quit ) ;
2019-07-04 15:14:26 -03:00
}
2019-03-08 06:49:22 -03:00
const isVMCall = Object . keys ( min . scriptMap ) . find ( key = > min . scriptMap [ key ] === context . activity . text ) !== undefined ;
2019-02-25 08:36:43 -03:00
2020-01-26 15:27:17 -03:00
const simpleLocale = context . activity . locale . substring ( 0 , 2 ) ;
const hasBadWord = wash . check ( simpleLocale , context . activity . text ) ;
2020-01-27 16:19:09 -03:00
2020-06-14 18:06:29 -03:00
2020-01-26 15:27:17 -03:00
if ( hasBadWord ) {
await step . beginDialog ( '/pleaseNoBadWords' ) ;
2020-01-27 16:19:09 -03:00
} else if ( isVMCall ) {
2020-01-10 10:04:26 -03:00
await GBMinService . callVM ( context . activity . text , min , step ) ;
2019-06-04 11:21:32 -03:00
} else if ( context . activity . text . charAt ( 0 ) === '/' ) {
2020-03-31 09:11:04 -03:00
let text = context . activity . text ;
let parts = text . split ( ' ' ) ;
let dialogName = parts [ 0 ] ;
parts . splice ( 0 , 1 ) ;
let args = parts . join ( ' ' ) ;
await step . beginDialog ( dialogName , { args : args } ) ;
2019-06-04 11:21:32 -03:00
2020-05-17 19:05:18 +00:00
} else if ( globalQuit ( step . context . activity . locale , context . activity . text ) ) { // TODO: Hard-code additional languages.
2019-07-04 15:14:26 -03:00
await step . cancelAllDialogs ( ) ;
2020-05-17 21:30:21 +00:00
await min . conversationalService . sendText ( min , step , Messages [ step . context . activity . locale ] . canceled ) ;
2019-02-25 08:36:43 -03:00
} else if ( context . activity . text === 'admin' ) {
2019-02-23 13:17:21 -03:00
await step . beginDialog ( '/admin' ) ;
2019-02-25 08:36:43 -03:00
// Checks for /menu JSON signature.
2019-02-23 13:17:21 -03:00
} else if ( context . activity . text . startsWith ( '{"title"' ) ) {
2019-03-09 16:59:31 -03:00
await step . beginDialog ( '/menu' , JSON . parse ( context . activity . text ) ) ;
2019-02-23 13:17:21 -03:00
// Otherwise, continue to the active dialog in the stack.
} else {
2020-06-15 00:40:25 -03:00
if ( ! await this . deployer . getStoragePackageByName ( min . instance . instanceId , ` ${ min . instance . botId } .gbkb ` ) ) {
await step . context . sendActivity ( ` Oi, ainda não possuo pacotes de conhecimento publicados. Por favor, aguarde alguns segundos enquanto eu auto-publico alguns pacotes. ` ) ;
return await step . beginDialog ( '/publish' , { confirm : true } ) ;
}
2019-03-09 16:59:31 -03:00
if ( step . activeDialog !== undefined ) {
2019-02-23 13:17:21 -03:00
await step . continueDialog ( ) ;
} else {
2020-05-17 19:05:18 +00:00
2020-06-15 00:40:25 -03:00
let query = context . activity . text ;
2020-06-14 18:06:29 -03:00
2020-06-15 00:40:25 -03:00
const translatorEnabled = ( ) = > {
if ( min . instance . params ) {
const params = JSON . parse ( min . instance . params ) ;
return params ? params [ 'Enable Worldwide Translator' ] === "TRUE" : false ;
2020-06-04 20:14:02 -03:00
}
2020-06-15 00:40:25 -03:00
return false ;
} // TODO: Encapsulate.
let locale = 'pt' ;
if ( process . env . TRANSLATOR_DISABLED !== "true" || translatorEnabled ( ) ) {
const minBoot = GBServer . globals . minBoot as any ; // TODO: Switch keys automatically to master/per bot.
locale = await AzureText . getLocale ( minBoot . instance . textAnalyticsKey ?
minBoot.instance.textAnalyticsKey : minBoot.instance.textAnalyticsKey ,
minBoot . instance . textAnalyticsEndpoint ?
minBoot.instance.textAnalyticsEndpoint : minBoot.instance.textAnalyticsEndpoint , query ) ;
2020-06-14 18:06:29 -03:00
}
2020-06-15 00:40:25 -03:00
let sec = new SecService ( ) ;
const member = step . context . activity . from ;
const user = await sec . ensureUser ( min . instance . instanceId , member . id ,
member . name , "" , "web" , member . name ) ;
user . locale = locale ;
await user . save ( ) ;
const minBoot = GBServer . globals . minBoot as any ;
query = await min . conversationalService . translate ( min ,
min . instance . translatorKey ? min.instance.translatorKey : minBoot.instance.translatorKey ,
min . instance . translatorEndpoint ? min.instance.translatorEndpoint : minBoot.instance.translatorEndpoint ,
query ,
'pt' ) ;
GBLog . info ( ` Translated text: ${ query } . ` )
await step . beginDialog ( '/answer' , {
query : query
} ) ;
2019-02-23 13:17:21 -03:00
}
}
}
2020-01-10 10:04:26 -03:00
public static async callVM ( text : string , min : GBMinInstance , step : GBDialogStep ) {
2020-06-03 21:31:00 -03:00
const mainMethod = text . toLowerCase ( ) ;
2020-01-10 10:04:26 -03:00
min . sandBoxMap [ mainMethod ] [ mainMethod ] . bind ( min . sandBoxMap [ mainMethod ] ) ;
return await min . sandBoxMap [ mainMethod ] [ mainMethod ] ( step ) ;
}
2018-09-11 19:33:58 -03:00
}