new(all): TRUE multicloud.
This commit is contained in:
parent
3299683268
commit
5880355349
14 changed files with 518 additions and 1358 deletions
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "v3",
|
||||
"title": "Bot Connector - Direct Line API - v3.0",
|
||||
|
@ -15,7 +15,7 @@
|
|||
"url": "https://opensource.org/licenses/MIT"
|
||||
}
|
||||
},
|
||||
"host": "directline.botframework.com",
|
||||
"servers": [{"url": "http://127.0.0.1:4242"}],
|
||||
"schemes": [
|
||||
"https"
|
||||
],
|
||||
|
|
10
package.json
10
package.json
|
@ -76,7 +76,7 @@
|
|||
"@azure/keyvault-keys": "4.8.0",
|
||||
"@azure/ms-rest-js": "2.7.0",
|
||||
"@azure/msal-node": "2.8.1",
|
||||
"@azure/openai": "^2.0.0-beta.1",
|
||||
"@azure/openai": "2.0.0-beta.1",
|
||||
"@azure/search-documents": "12.0.0",
|
||||
"@azure/storage-blob": "12.18.0",
|
||||
"@google-cloud/pubsub": "4.4.0",
|
||||
|
@ -137,11 +137,12 @@
|
|||
"google-libphonenumber": "3.2.34",
|
||||
"googleapis": "126.0.1",
|
||||
"hnswlib-node": "3.0.0",
|
||||
"html-to-md": "^0.8.5",
|
||||
"html-to-md": "0.8.5",
|
||||
"http-proxy": "1.18.1",
|
||||
"ibm-watson": "9.1.0",
|
||||
"instagram-private-api": "^1.46.1",
|
||||
"instagram-private-api": "1.46.1",
|
||||
"iso-639-1": "3.1.2",
|
||||
"isomorphic-fetch": "3.0.0",
|
||||
"join-images-updated": "1.1.11",
|
||||
"js-md5": "0.8.3",
|
||||
"json-schema-to-zod": "2.1.0",
|
||||
|
@ -210,7 +211,7 @@
|
|||
"textract": "2.5.0",
|
||||
"twilio": "5.1.0",
|
||||
"twitter-api-v2": "1.17.0",
|
||||
"typeorm": "^0.3.20",
|
||||
"typeorm": "0.3.20",
|
||||
"typescript": "5.4.5",
|
||||
"url-join": "5.0.0",
|
||||
"vhost": "3.0.2",
|
||||
|
@ -218,6 +219,7 @@
|
|||
"vm2-process": "2.1.5",
|
||||
"walk-promise": "0.2.0",
|
||||
"washyourmouthoutwithsoap": "1.0.2",
|
||||
"webdav-server": "2.6.2",
|
||||
"whatsapp-cloud-api": "0.3.1",
|
||||
"whatsapp-web.js": "https://github.com/Julzk/whatsapp-web.js/tarball/jkr_hotfix_7",
|
||||
"winston": "3.13.0",
|
||||
|
|
|
@ -37,6 +37,7 @@ import SwaggerClient from 'swagger-client';
|
|||
import { spawn } from 'child_process';
|
||||
import { CodeServices } from '../../gpt.gblib/services/CodeServices.js';
|
||||
import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js';
|
||||
import { GBUtil } from '../../../src/util.js';
|
||||
|
||||
/**
|
||||
* Web Automation services of conversation to be called by BASIC.
|
||||
|
@ -154,12 +155,8 @@ export class DebuggerService {
|
|||
|
||||
let min: GBMinInstance = GBServer.globals.minInstances.filter(p => p.instance.botId === botId)[0];
|
||||
|
||||
const client = await new SwaggerClient({
|
||||
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
|
||||
requestInterceptor: req => {
|
||||
req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`;
|
||||
}
|
||||
});
|
||||
const client = await GBUtil.getDirectLineClient(min);
|
||||
|
||||
GBServer.globals.debuggers[botId].client = client;
|
||||
const response = await client.apis.Conversations.Conversations_StartConversation();
|
||||
const conversationId = response.obj.conversationId;
|
||||
|
|
|
@ -1348,12 +1348,7 @@ export class DialogKeywords {
|
|||
|
||||
const conversation = min['apiConversations'][pid];
|
||||
|
||||
const client = await new SwaggerClient({
|
||||
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
|
||||
requestInterceptor: req => {
|
||||
req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`;
|
||||
}
|
||||
});
|
||||
const client = await GBUtil.getDirectLineClient(min);
|
||||
conversation.client = client;
|
||||
const response = await client.apis.Conversations.Conversations_StartConversation();
|
||||
conversation.conversationId = response.obj.conversationId;
|
||||
|
|
|
@ -106,7 +106,7 @@ export class GBConfigService {
|
|||
value = undefined;
|
||||
break;
|
||||
case 'STORAGE_FILE':
|
||||
value = './guaribas.sqlite';
|
||||
value = './data.db';
|
||||
break;
|
||||
case 'GBKB_AUTO_DEPLOY':
|
||||
value = false;
|
||||
|
|
|
@ -42,6 +42,7 @@ import { FacebookAdapter } from 'botbuilder-adapter-facebook';
|
|||
import mkdirp from 'mkdirp';
|
||||
import Fs from 'fs';
|
||||
import arrayBufferToBuffer from 'arraybuffer-to-buffer';
|
||||
import { v2 as webdav } from 'webdav-server';
|
||||
import { NlpManager } from 'node-nlp';
|
||||
import Koa from 'koa';
|
||||
import { createRpcServer } from '@push-rpc/core';
|
||||
|
@ -253,6 +254,8 @@ export class GBMinService {
|
|||
GBServer.globals.minInstances.push(min);
|
||||
const user = null; // No user context.
|
||||
|
||||
// Serves individual URL for each bot conversational interface.
|
||||
|
||||
await this.deployer['deployPackage2'](min, user, 'packages/default.gbtheme');
|
||||
|
||||
// Install per bot deployed packages.
|
||||
|
@ -313,6 +316,14 @@ export class GBMinService {
|
|||
if (!Fs.existsSync(dir)) {
|
||||
mkdirp.sync(dir);
|
||||
}
|
||||
|
||||
dir = Path.join(process.env.PWD, 'work', gbai);
|
||||
|
||||
const server = new webdav.WebDAVServer();
|
||||
server.setFileSystem(`/${botId}`,
|
||||
new webdav.PhysicalFileSystem(dir), (success) => {
|
||||
server.start(() => console.log('WEBDAV READY'));
|
||||
})
|
||||
|
||||
// Loads Named Entity data for this bot.
|
||||
|
||||
|
@ -321,16 +332,11 @@ export class GBMinService {
|
|||
// Calls the loadBot context.activity for all packages.
|
||||
|
||||
await this.invokeLoadBot(min.appPackages, GBServer.globals.sysPackages, min);
|
||||
|
||||
// Serves individual URL for each bot conversational interface.
|
||||
|
||||
const receiver = async (req, res) => {
|
||||
await this.receiver(req, res, conversationState, min, instance, GBServer.globals.appPackages);
|
||||
};
|
||||
let url = `/api/messages/${instance.botId}`;
|
||||
GBServer.globals.server.post(url, receiver);
|
||||
url = `/api/messages`;
|
||||
GBServer.globals.server.post(url, receiver);
|
||||
|
||||
GBServer.globals.server.get(url, (req, res) => {
|
||||
if (req.query['hub.mode'] === 'subscribe') {
|
||||
if (req.query['hub.verify_token'] === process.env.FACEBOOK_VERIFY_TOKEN) {
|
||||
|
@ -349,12 +355,7 @@ export class GBMinService {
|
|||
if (process.env.TEST_MESSAGE) {
|
||||
GBLogEx.info(min, `Starting auto test with '${process.env.TEST_MESSAGE}'.`);
|
||||
|
||||
const client = await new SwaggerClient({
|
||||
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
|
||||
requestInterceptor: req => {
|
||||
req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`;
|
||||
}
|
||||
});
|
||||
const client = await GBUtil.getDirectLineClient(min);
|
||||
|
||||
const response = await client.apis.Conversations.Conversations_StartConversation();
|
||||
const conversationId = response.obj.conversationId;
|
||||
|
@ -450,7 +451,9 @@ export class GBMinService {
|
|||
whatsAppDirectLine = WhatsappDirectLine.botsByNumber[to];
|
||||
}
|
||||
|
||||
await whatsAppDirectLine.WhatsAppCallback(req, res, whatsAppDirectLine.botId);
|
||||
if (whatsAppDirectLine) {
|
||||
await whatsAppDirectLine.WhatsAppCallback(req, res, whatsAppDirectLine.botId);
|
||||
}
|
||||
})
|
||||
.bind(min);
|
||||
|
||||
|
@ -672,27 +675,31 @@ export class GBMinService {
|
|||
theme = `default.gbtheme`;
|
||||
}
|
||||
|
||||
res.send(
|
||||
JSON.stringify({
|
||||
instanceId: instance.instanceId,
|
||||
botId: botId,
|
||||
theme: theme,
|
||||
//webchatToken: webchatTokenContainer.token,
|
||||
domain: 'http://localhost:3978/directline',
|
||||
speechToken: speechToken,
|
||||
conversationId: webchatTokenContainer.conversationId,
|
||||
authenticatorTenant: instance.authenticatorTenant,
|
||||
authenticatorClientId: instance.marketplaceId,
|
||||
paramLogoImageUrl: this.core.getParam(instance, 'Logo Image Url', null),
|
||||
paramLogoImageAlt: this.core.getParam(instance, 'Logo Image Alt', null),
|
||||
paramLogoImageWidth: this.core.getParam(instance, 'Logo Image Width', null),
|
||||
paramLogoImageHeight: this.core.getParam(instance, 'Logo Image Height', null),
|
||||
paramLogoImageType: this.core.getParam(instance, 'Logo Image Type', null),
|
||||
logo: this.core.getParam(instance, 'Logo', null),
|
||||
color1: this.core.getParam(instance, 'Color1', null),
|
||||
color2: this.core.getParam(instance, 'Color2', null)
|
||||
})
|
||||
);
|
||||
let config = {
|
||||
instanceId: instance.instanceId,
|
||||
botId: botId,
|
||||
theme: theme,
|
||||
speechToken: speechToken,
|
||||
conversationId: webchatTokenContainer.conversationId,
|
||||
authenticatorTenant: instance.authenticatorTenant,
|
||||
authenticatorClientId: instance.marketplaceId,
|
||||
paramLogoImageUrl: this.core.getParam(instance, 'Logo Image Url', null),
|
||||
paramLogoImageAlt: this.core.getParam(instance, 'Logo Image Alt', null),
|
||||
paramLogoImageWidth: this.core.getParam(instance, 'Logo Image Width', null),
|
||||
paramLogoImageHeight: this.core.getParam(instance, 'Logo Image Height', null),
|
||||
paramLogoImageType: this.core.getParam(instance, 'Logo Image Type', null),
|
||||
logo: this.core.getParam(instance, 'Logo', null),
|
||||
color1: this.core.getParam(instance, 'Color1', null),
|
||||
color2: this.core.getParam(instance, 'Color2', null)
|
||||
};
|
||||
|
||||
if (process.env.STORAGE_FILE) {
|
||||
config['domain'] = `http://localhost:${process.env.PORT}/directline`;
|
||||
} else {
|
||||
config['webchatToken'] = webchatTokenContainer.token;
|
||||
}
|
||||
|
||||
res.send(JSON.stringify(config));
|
||||
} else {
|
||||
const error = `Instance not found while retrieving from .gbui web client: ${botId}.`;
|
||||
res.sendStatus(error);
|
||||
|
@ -749,15 +756,18 @@ export class GBMinService {
|
|||
*/
|
||||
private async buildBotAdapter(instance: any, sysPackages: IGBPackage[], appPackages: IGBPackage[]) {
|
||||
// MSFT stuff.
|
||||
const uri = 'http://localhost:3978';
|
||||
|
||||
const adapter = new BotFrameworkAdapter({
|
||||
clientOptions: { baseUri: uri },
|
||||
let config = {
|
||||
appId: instance.marketplaceId ? instance.marketplaceId : GBConfigService.get('MARKETPLACE_ID'),
|
||||
appPassword: instance.marketplacePassword
|
||||
? instance.marketplacePassword
|
||||
: GBConfigService.get('MARKETPLACE_SECRET')
|
||||
});
|
||||
};
|
||||
if (process.env.STORAGE_FILE) {
|
||||
config['clientOptions'] = { baseUri: `http://localhost:${process.env.PORT}` };
|
||||
}
|
||||
|
||||
const adapter = new BotFrameworkAdapter(config);
|
||||
const storage = new MemoryStorage();
|
||||
const conversationState = new ConversationState(storage);
|
||||
const userState = new UserState(storage);
|
||||
|
@ -770,6 +780,28 @@ export class GBMinService {
|
|||
// The minimal bot is built here.
|
||||
|
||||
const min = new GBMinInstance();
|
||||
|
||||
// Setups default BOT Framework dialogs.
|
||||
|
||||
min.userProfile = conversationState.createProperty('userProfile');
|
||||
const dialogState = conversationState.createProperty('dialogState');
|
||||
|
||||
min.dialogs = new DialogSet(dialogState);
|
||||
min.dialogs.add(new TextPrompt('textPrompt'));
|
||||
min.dialogs.add(new AttachmentPrompt('attachmentPrompt'));
|
||||
|
||||
min.dialogs.add(new ConfirmPrompt('confirmPrompt'));
|
||||
if (process.env.ENABLE_AUTH) {
|
||||
min.dialogs.add(
|
||||
new OAuthPrompt('oAuthPrompt', {
|
||||
connectionName: 'OAuth2',
|
||||
text: 'Please sign in to General Bots.',
|
||||
title: 'Sign in',
|
||||
timeout: 300000
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
min.botId = instance.botId;
|
||||
min.bot = adapter;
|
||||
min.userState = userState;
|
||||
|
@ -792,6 +824,15 @@ export class GBMinService {
|
|||
min['apiConversations'] = {};
|
||||
min.packages = sysPackages;
|
||||
|
||||
const receiver = async (req, res) => {
|
||||
await this.receiver(req, res, conversationState, min, instance, GBServer.globals.appPackages);
|
||||
};
|
||||
|
||||
let url = `/api/messages/${instance.botId}`;
|
||||
GBServer.globals.server.post(url, receiver);
|
||||
url = `/api/messages`;
|
||||
GBServer.globals.server.post(url, receiver);
|
||||
|
||||
// NLP Manager.
|
||||
|
||||
const manager = new NlpManager({ languages: ['pt'], forceNER: true });
|
||||
|
@ -879,26 +920,6 @@ export class GBMinService {
|
|||
WhatsappDirectLine.botsByNumber[botNumber] = min.whatsAppDirectLine;
|
||||
}
|
||||
|
||||
// Setups default BOT Framework dialogs.
|
||||
|
||||
min.userProfile = conversationState.createProperty('userProfile');
|
||||
const dialogState = conversationState.createProperty('dialogState');
|
||||
|
||||
min.dialogs = new DialogSet(dialogState);
|
||||
min.dialogs.add(new TextPrompt('textPrompt'));
|
||||
min.dialogs.add(new AttachmentPrompt('attachmentPrompt'));
|
||||
|
||||
min.dialogs.add(new ConfirmPrompt('confirmPrompt'));
|
||||
if (process.env.ENABLE_AUTH) {
|
||||
min.dialogs.add(
|
||||
new OAuthPrompt('oAuthPrompt', {
|
||||
connectionName: 'OAuth2',
|
||||
text: 'Please sign in to General Bots.',
|
||||
title: 'Sign in',
|
||||
timeout: 300000
|
||||
})
|
||||
);
|
||||
}
|
||||
return { min, adapter, conversationState };
|
||||
}
|
||||
|
||||
|
@ -1177,7 +1198,7 @@ export class GBMinService {
|
|||
await handler(context);
|
||||
// Return status
|
||||
res.status(200);
|
||||
|
||||
|
||||
res.end();
|
||||
} else {
|
||||
await adapter['processActivity'](req, res, handler);
|
||||
|
@ -1376,7 +1397,7 @@ export class GBMinService {
|
|||
context.activity.text = context.activity.text.trim();
|
||||
|
||||
const member = context.activity.from;
|
||||
let memberId, email;
|
||||
let memberId = null, email = null;
|
||||
|
||||
// Processes e-mail from id in case of Teams messages.
|
||||
|
||||
|
@ -1648,12 +1669,7 @@ export class GBMinService {
|
|||
if (script === 'start') {
|
||||
pid = GBVMService.createProcessInfo(user, min, 'api', null);
|
||||
|
||||
const client = await new SwaggerClient({
|
||||
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
|
||||
requestInterceptor: req => {
|
||||
req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`;
|
||||
}
|
||||
});
|
||||
const client = await GBUtil.getDirectLineClient(min);
|
||||
const response = await client.apis.Conversations.Conversations_StartConversation();
|
||||
|
||||
min['apiConversations'][pid] = { conversation: response.obj, client: client };
|
||||
|
|
322
packages/core.gbapp/services/router/bridge.ts
Normal file
322
packages/core.gbapp/services/router/bridge.ts
Normal file
|
@ -0,0 +1,322 @@
|
|||
import bodyParser from 'body-parser';
|
||||
import express from 'express';
|
||||
import fetch from 'isomorphic-fetch';
|
||||
import moment from 'moment';
|
||||
import * as uuidv4 from 'uuid';
|
||||
|
||||
import { IActivity, IBotData, IConversation, IConversationUpdateActivity, IMessageActivity } from './types';
|
||||
|
||||
const expiresIn = 1800;
|
||||
const conversationsCleanupInterval = 10000;
|
||||
const conversations: { [key: string]: IConversation } = {};
|
||||
const botDataStore: { [key: string]: IBotData } = {};
|
||||
|
||||
export const getRouter = (serviceUrl: string, botUrl: string, conversationInitRequired = true): express.Router => {
|
||||
const router = express.Router();
|
||||
|
||||
router.use(bodyParser.json()); // for parsing application/json
|
||||
router.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
|
||||
router.use((req, res, next) => {
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, PATCH, OPTIONS');
|
||||
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, x-ms-bot-agent');
|
||||
next();
|
||||
});
|
||||
// CLIENT ENDPOINT
|
||||
router.options('/directline', (req, res) => {
|
||||
res.status(200).end();
|
||||
});
|
||||
|
||||
// Creates a conversation
|
||||
const reqs = (req, res) => {
|
||||
|
||||
const conversationId: string = uuidv4.v4().toString();
|
||||
conversations[conversationId] = {
|
||||
conversationId,
|
||||
history: [],
|
||||
};
|
||||
console.log('Created conversation with conversationId: ' + conversationId);
|
||||
|
||||
const activity = createConversationUpdateActivity(serviceUrl, conversationId);
|
||||
fetch(botUrl, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(activity),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then((response) => {
|
||||
res.status(response.status).send({
|
||||
conversationId,
|
||||
expiresIn,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
router.post('/v3/directline/conversations',reqs );
|
||||
router.post('/directline/conversations',reqs );
|
||||
|
||||
// Reconnect API
|
||||
router.get('/v3/directline/conversations/:conversationId', (req, res) => {
|
||||
const conversation = getConversation(req.params.conversationId, conversationInitRequired);
|
||||
if (conversation) {
|
||||
res.status(200).send(conversation);
|
||||
} else {
|
||||
// Conversation was never initialized
|
||||
res.status(400).send();
|
||||
}
|
||||
|
||||
console.warn('/v3/directline/conversations/:conversationId not implemented');
|
||||
});
|
||||
|
||||
// Gets activities from store (local history array for now)
|
||||
router.get('/directline/conversations/:conversationId/activities', (req, res) => {
|
||||
const watermark = req.query.watermark && req.query.watermark !== 'null' ? Number(req.query.watermark) : 0;
|
||||
|
||||
const conversation = getConversation(req.params.conversationId, conversationInitRequired);
|
||||
|
||||
if (conversation) {
|
||||
// If the bot has pushed anything into the history array
|
||||
if (conversation.history.length > watermark) {
|
||||
const activities = conversation.history.slice(watermark);
|
||||
res.status(200).json({
|
||||
activities,
|
||||
watermark: watermark + activities.length,
|
||||
});
|
||||
} else {
|
||||
res.status(200).send({
|
||||
activities: [],
|
||||
watermark,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Conversation was never initialized
|
||||
res.status(400).send();
|
||||
}
|
||||
});
|
||||
|
||||
// Sends message to bot. Assumes message activities
|
||||
router.post('/directline/conversations/:conversationId/activities', (req, res) => {
|
||||
const incomingActivity = req.body;
|
||||
// Make copy of activity. Add required fields
|
||||
const activity = createMessageActivity(incomingActivity, serviceUrl, req.params.conversationId);
|
||||
|
||||
const conversation = getConversation(req.params.conversationId, conversationInitRequired);
|
||||
|
||||
if (conversation) {
|
||||
conversation.history.push(activity);
|
||||
fetch(botUrl, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(activity),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then((response) => {
|
||||
res.status(response.status).json({ id: activity.id });
|
||||
});
|
||||
} else {
|
||||
// Conversation was never initialized
|
||||
res.status(400).send();
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/v3/directline/conversations/:conversationId/upload', (req, res) => { console.warn('/v3/directline/conversations/:conversationId/upload not implemented'); });
|
||||
router.get('/v3/directline/conversations/:conversationId/stream', (req, res) => { console.warn('/v3/directline/conversations/:conversationId/stream not implemented'); });
|
||||
|
||||
// BOT CONVERSATION ENDPOINT
|
||||
|
||||
router.post('/v3/conversations', (req, res) => { console.warn('/v3/conversations not implemented'); });
|
||||
|
||||
router.post('/v3/conversations/:conversationId/activities', (req, res) => {
|
||||
let activity: IActivity;
|
||||
|
||||
activity = req.body;
|
||||
activity.id = uuidv4.v4();
|
||||
activity.from = { id: 'id', name: 'Bot' };
|
||||
|
||||
const conversation = getConversation(req.params.conversationId, conversationInitRequired);
|
||||
if (conversation) {
|
||||
conversation.history.push(activity);
|
||||
res.status(200).send();
|
||||
} else {
|
||||
// Conversation was never initialized
|
||||
res.status(400).send();
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/v3/conversations/:conversationId/activities/:activityId', (req, res) => {
|
||||
let activity: IActivity;
|
||||
|
||||
activity = req.body;
|
||||
activity.id = uuidv4.v4();
|
||||
activity.from = { id: 'id', name: 'Bot' };
|
||||
|
||||
const conversation = getConversation(req.params.conversationId, conversationInitRequired);
|
||||
if (conversation) {
|
||||
conversation.history.push(activity);
|
||||
res.status(200).send();
|
||||
} else {
|
||||
// Conversation was never initialized
|
||||
res.status(400).send();
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/v3/conversations/:conversationId/members', (req, res) => { console.warn('/v3/conversations/:conversationId/members not implemented'); });
|
||||
router.get('/v3/conversations/:conversationId/activities/:activityId/members', (req, res) => { console.warn('/v3/conversations/:conversationId/activities/:activityId/members'); });
|
||||
|
||||
// BOTSTATE ENDPOINT
|
||||
|
||||
router.get('/v3/botstate/:channelId/users/:userId', (req, res) => {
|
||||
console.log('Called GET user data');
|
||||
getBotData(req, res);
|
||||
});
|
||||
|
||||
router.get('/v3/botstate/:channelId/conversations/:conversationId', (req, res) => {
|
||||
console.log(('Called GET conversation data'));
|
||||
getBotData(req, res);
|
||||
});
|
||||
|
||||
router.get('/v3/botstate/:channelId/conversations/:conversationId/users/:userId', (req, res) => {
|
||||
console.log('Called GET private conversation data');
|
||||
getBotData(req, res);
|
||||
});
|
||||
|
||||
router.post('/v3/botstate/:channelId/users/:userId', (req, res) => {
|
||||
console.log('Called POST setUserData');
|
||||
setUserData(req, res);
|
||||
});
|
||||
|
||||
router.post('/v3/botstate/:channelId/conversations/:conversationId', (req, res) => {
|
||||
console.log('Called POST setConversationData');
|
||||
setConversationData(req, res);
|
||||
});
|
||||
|
||||
router.post('/v3/botstate/:channelId/conversations/:conversationId/users/:userId', (req, res) => {
|
||||
setPrivateConversationData(req, res);
|
||||
});
|
||||
|
||||
router.delete('/v3/botstate/:channelId/users/:userId', (req, res) => {
|
||||
console.log('Called DELETE deleteStateForUser');
|
||||
deleteStateForUser(req, res);
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param app The express app where your offline-directline endpoint will live
|
||||
* @param port The port where your offline-directline will be hosted
|
||||
* @param botUrl The url of the bot (e.g. http://127.0.0.1:3978/api/messages)
|
||||
* @param conversationInitRequired Requires that a conversation is initialized before it is accessed, returning a 400
|
||||
* when not the case. If set to false, a new conversation reference is created on the fly. This is true by default.
|
||||
*/
|
||||
export const initializeRoutes = (app: express.Express, port: number, botUrl: string, conversationInitRequired = true) => {
|
||||
conversationsCleanup();
|
||||
|
||||
const directLineEndpoint = `http://127.0.0.1:${port}`;
|
||||
const router = getRouter(directLineEndpoint, botUrl, conversationInitRequired);
|
||||
|
||||
app.use(router);
|
||||
console.log(`Routing messages to bot on ${botUrl}`);
|
||||
|
||||
};
|
||||
|
||||
const getConversation = (conversationId: string, conversationInitRequired: boolean) => {
|
||||
|
||||
// Create conversation on the fly when needed and init not required
|
||||
if (!conversations[conversationId] && !conversationInitRequired) {
|
||||
conversations[conversationId] = {
|
||||
conversationId,
|
||||
history: [],
|
||||
};
|
||||
}
|
||||
return conversations[conversationId];
|
||||
};
|
||||
|
||||
const getBotDataKey = (channelId: string, conversationId: string, userId: string) => {
|
||||
return `$${channelId || '*'}!${conversationId || '*'}!${userId || '*'}`;
|
||||
};
|
||||
|
||||
const setBotData = (channelId: string, conversationId: string, userId: string, incomingData: IBotData): IBotData => {
|
||||
const key = getBotDataKey(channelId, conversationId, userId);
|
||||
const newData: IBotData = {
|
||||
eTag: new Date().getTime().toString(),
|
||||
data: incomingData.data,
|
||||
};
|
||||
|
||||
if (incomingData) {
|
||||
botDataStore[key] = newData;
|
||||
} else {
|
||||
delete botDataStore[key];
|
||||
newData.eTag = '*';
|
||||
}
|
||||
|
||||
return newData;
|
||||
};
|
||||
|
||||
const getBotData = (req: express.Request, res: express.Response) => {
|
||||
const key = getBotDataKey(req.params.channelId, req.params.conversationId, req.params.userId);
|
||||
console.log('Data key: ' + key);
|
||||
|
||||
res.status(200).send(botDataStore[key] || { data: null, eTag: '*' });
|
||||
};
|
||||
|
||||
const setUserData = (req: express.Request, res: express.Response) => {
|
||||
res.status(200).send(setBotData(req.params.channelId, req.params.conversationId, req.params.userId, req.body));
|
||||
};
|
||||
|
||||
const setConversationData = (req: express.Request, res: express.Response) => {
|
||||
res.status(200).send(setBotData(req.params.channelId, req.params.conversationId, req.params.userId, req.body));
|
||||
};
|
||||
|
||||
const setPrivateConversationData = (req: express.Request, res: express.Response) => {
|
||||
res.status(200).send(setBotData(req.params.channelId, req.params.conversationId, req.params.userId, req.body));
|
||||
};
|
||||
|
||||
export const start = (server)=>{
|
||||
|
||||
initializeRoutes(server, Number(process.env.PORT), `http://127.0.0.1:${process.env.PORT}/api/messages`);
|
||||
}
|
||||
|
||||
const deleteStateForUser = (req: express.Request, res: express.Response) => {
|
||||
Object.keys(botDataStore)
|
||||
.forEach((key) => {
|
||||
if (key.endsWith(`!{req.query.userId}`)) {
|
||||
delete botDataStore[key];
|
||||
}
|
||||
});
|
||||
res.status(200).send();
|
||||
};
|
||||
|
||||
// CLIENT ENDPOINT HELPERS
|
||||
const createMessageActivity = (incomingActivity: IMessageActivity, serviceUrl: string, conversationId: string): IMessageActivity => {
|
||||
return { ...incomingActivity, channelId: 'emulator', serviceUrl, conversation: { id: conversationId }, id: uuidv4.v4() };
|
||||
};
|
||||
|
||||
const createConversationUpdateActivity = (serviceUrl: string, conversationId: string): IConversationUpdateActivity => {
|
||||
const activity: IConversationUpdateActivity = {
|
||||
type: 'conversationUpdate',
|
||||
channelId: 'emulator',
|
||||
serviceUrl,
|
||||
conversation: { id: conversationId },
|
||||
id: uuidv4.v4(),
|
||||
membersAdded: [],
|
||||
membersRemoved: [],
|
||||
from: { id: 'offline-directline', name: 'Offline Directline Server' },
|
||||
};
|
||||
return activity;
|
||||
};
|
||||
|
||||
const conversationsCleanup = () => {
|
||||
setInterval(() => {
|
||||
const expiresTime = moment().subtract(expiresIn, 'seconds');
|
||||
Object.keys(conversations).forEach((conversationId) => {
|
||||
if (conversations[conversationId].history.length > 0) {
|
||||
const lastTime = moment(conversations[conversationId].history[conversations[conversationId].history.length - 1].localTimestamp);
|
||||
if (lastTime < expiresTime) {
|
||||
delete conversations[conversationId];
|
||||
console.log('deleted cId: ' + conversationId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, conversationsCleanupInterval);
|
||||
};
|
66
packages/core.gbapp/services/router/types.ts
Normal file
66
packages/core.gbapp/services/router/types.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
export interface IUser {
|
||||
id: string,
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface IChannelAccount {
|
||||
id?: string,
|
||||
name?: string,
|
||||
}
|
||||
|
||||
export interface IConversationAccount extends IChannelAccount {
|
||||
isGroup?: boolean,
|
||||
}
|
||||
|
||||
export interface IAttachment {
|
||||
contentType?: string,
|
||||
contentUrl?: string,
|
||||
content?: any,
|
||||
name?: string,
|
||||
thumbnailUrl?: string,
|
||||
}
|
||||
|
||||
export interface IEntity {
|
||||
type?: string,
|
||||
}
|
||||
|
||||
export interface IActivity {
|
||||
type?: string,
|
||||
id?: string,
|
||||
serviceUrl?: string,
|
||||
timestamp?: string,
|
||||
localTimestamp?: string,
|
||||
channelId?: string,
|
||||
from?: IChannelAccount,
|
||||
conversation?: IConversationAccount,
|
||||
recipient?: IChannelAccount,
|
||||
replyToId?: string,
|
||||
channelData?: any,
|
||||
}
|
||||
|
||||
export interface IMessageActivity extends IActivity {
|
||||
locale?: string,
|
||||
text?: string,
|
||||
summary?: string,
|
||||
textFormat?: string,
|
||||
attachmentLayout?: string,
|
||||
attachments?: IAttachment[],
|
||||
entities?: IEntity[],
|
||||
}
|
||||
|
||||
export interface IBotData {
|
||||
eTag: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
export interface IConversation {
|
||||
conversationId: string,
|
||||
history?: IActivity[]
|
||||
}
|
||||
|
||||
export interface IConversationUpdateActivity extends IActivity {
|
||||
membersAdded?: IChannelAccount[],
|
||||
membersRemoved?: IChannelAccount[],
|
||||
topicName?: string,
|
||||
historyDisclosed?: boolean,
|
||||
}
|
|
@ -36,6 +36,7 @@ import { GBLog, GBMinInstance, GBService } from 'botlib';
|
|||
import { GBServer } from '../../../src/app.js';
|
||||
import { SecService } from '../../security.gbapp/services/SecService.js';
|
||||
import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js';
|
||||
import { GBUtil } from '../../../src/util.js';
|
||||
|
||||
/**
|
||||
* Support for Google Chat.
|
||||
|
@ -90,10 +91,7 @@ export class GoogleChatDirectLine extends GBService {
|
|||
}
|
||||
|
||||
public async setup (setUrl) {
|
||||
this.directLineClient = new Swagger({
|
||||
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
|
||||
usePromise: true
|
||||
});
|
||||
this.directLineClient = await GBUtil.getDirectLineClient(this.min);
|
||||
const client = await this.directLineClient;
|
||||
|
||||
client.clientAuthorizations.add(
|
||||
|
|
|
@ -54,8 +54,10 @@ export class SecService extends GBService {
|
|||
user.email = email;
|
||||
user.defaultChannel = channelName;
|
||||
GBServer.globals.users [user.userId] = user;
|
||||
|
||||
return await user.save();
|
||||
if(user.changed()){
|
||||
await user.save();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
|
||||
import mime from 'mime-types';
|
||||
import urlJoin from 'url-join';
|
||||
import SwaggerClient from 'swagger-client';
|
||||
import Path from 'path';
|
||||
import Fs from 'fs';
|
||||
import { GBLog, GBMinInstance, GBService, IGBPackage } from 'botlib';
|
||||
|
@ -46,7 +45,7 @@ import qrcode from 'qrcode-terminal';
|
|||
import express from 'express';
|
||||
import { GBSSR } from '../../core.gbapp/services/GBSSR.js';
|
||||
import pkg from 'whatsapp-web.js';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import fetch from 'node-fetch';
|
||||
import { DialogKeywords } from '../../basic.gblib/services/DialogKeywords.js';
|
||||
import { ChatServices } from '../../gpt.gblib/services/ChatServices.js';
|
||||
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService.js';
|
||||
|
@ -117,20 +116,9 @@ export class WhatsappDirectLine extends GBService {
|
|||
}
|
||||
|
||||
public async setup(setUrl: boolean) {
|
||||
const client = await new SwaggerClient({
|
||||
url: 'http://127.0.0.1:3978/api/messages', // TODO:
|
||||
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
|
||||
requestInterceptor: req => {
|
||||
req.headers['Authorization'] = `Bearer ${this.min.instance.webchatKey}`;
|
||||
}
|
||||
});
|
||||
|
||||
this.directLineClient = client;
|
||||
this.directLineClient = GBUtil.getDirectLineClient(this.min);
|
||||
|
||||
// Warms up MSBF.
|
||||
|
||||
await client.apis.Conversations.Conversations_StartConversation();
|
||||
|
||||
let url: string;
|
||||
let options: any;
|
||||
|
||||
|
|
12
src/app.ts
12
src/app.ts
|
@ -40,6 +40,8 @@ import bodyParser from 'body-parser';
|
|||
import { GBLog, GBMinInstance, IGBCoreService, IGBInstance } from 'botlib';
|
||||
import child_process from 'child_process';
|
||||
import express from 'express';
|
||||
import {start as startRouter} from '../packages/core.gbapp/services/router/bridge.js'
|
||||
|
||||
import fs from 'fs';
|
||||
import http from 'http';
|
||||
import httpProxy from 'http-proxy';
|
||||
|
@ -87,8 +89,9 @@ export class GBServer {
|
|||
|
||||
const server = express();
|
||||
this.initEndpointsDocs(server);
|
||||
|
||||
startRouter(server);
|
||||
GBServer.globals.server = server;
|
||||
|
||||
GBServer.globals.httpsServer = null;
|
||||
GBServer.globals.webSessions = {};
|
||||
GBServer.globals.processes = {};
|
||||
|
@ -118,7 +121,7 @@ export class GBServer {
|
|||
});
|
||||
|
||||
process.on('uncaughtException', (err, p) => {
|
||||
GBLogEx.error(0, `GBEXCEPTION: ${GBUtil.toYAML(err)} ${GBUtil.toYAML(p)}`);
|
||||
GBLogEx.error(0, `GBEXCEPTION: ${GBUtil.toYAML(JSON.parse(JSON.stringify(err, Object.getOwnPropertyNames(err))))}`);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (err, p) => {
|
||||
|
@ -131,7 +134,7 @@ export class GBServer {
|
|||
}
|
||||
|
||||
if (!bypass) {
|
||||
GBLogEx.error(0, `GBREJECTION: ${GBUtil.toYAML(err)} ${GBUtil.toYAML(p)}`);
|
||||
GBLogEx.error(0,`GBREJECTION: ${GBUtil.toYAML(JSON.parse(JSON.stringify(err, Object.getOwnPropertyNames(err))))}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -147,6 +150,7 @@ export class GBServer {
|
|||
(async () => {
|
||||
try {
|
||||
GBLogEx.info(0, `Now accepting connections on ${port}...`);
|
||||
|
||||
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
|
||||
|
||||
// Reads basic configuration, initialize minimal services.
|
||||
|
@ -247,7 +251,7 @@ export class GBServer {
|
|||
const minService: GBMinService = new GBMinService(core, conversationalService, adminService, deployer);
|
||||
GBServer.globals.minService = minService;
|
||||
await minService.buildMin(instances);
|
||||
|
||||
|
||||
server.all('*', async (req, res, next) => {
|
||||
const host = req.headers.host;
|
||||
|
||||
|
|
20
src/util.ts
20
src/util.ts
|
@ -34,6 +34,8 @@
|
|||
|
||||
'use strict';
|
||||
import * as YAML from 'yaml';
|
||||
import SwaggerClient from 'swagger-client';
|
||||
import Fs from 'fs';
|
||||
|
||||
export class GBUtil {
|
||||
public static repeat(chr, count) {
|
||||
|
@ -53,7 +55,7 @@ export class GBUtil {
|
|||
|
||||
return (GBUtil.repeat(pad, length) + value).substr(0, width);
|
||||
}
|
||||
|
||||
|
||||
public static padR(value, width, pad) {
|
||||
if (!width || width < 1) return value;
|
||||
|
||||
|
@ -64,6 +66,22 @@ export class GBUtil {
|
|||
return (value + GBUtil.repeat(pad, length)).substr(0, width);
|
||||
}
|
||||
|
||||
public static async getDirectLineClient(min) {
|
||||
|
||||
let config = {
|
||||
url: `http://127.0.0.1:${process.env.port}/api/messages`,
|
||||
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
|
||||
requestInterceptor: req => {
|
||||
req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`;
|
||||
}
|
||||
};
|
||||
if (process.env.STORAGE_FILE) {
|
||||
config['spec'].servers = [{ url: `http://127.0.0.1:${process.env.PORT}/api/messages` }];
|
||||
config['openapi'] = '3.0.0';
|
||||
}
|
||||
return await new SwaggerClient(config);
|
||||
}
|
||||
|
||||
public static toYAML(json) {
|
||||
const doc = new YAML.Document();
|
||||
doc.contents = json;
|
||||
|
|
Loading…
Add table
Reference in a new issue