new(all): TRUE multicloud.

This commit is contained in:
Rodrigo Rodriguez 2024-08-19 16:12:23 -03:00
parent 3299683268
commit 5880355349
14 changed files with 518 additions and 1358 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
{ {
"swagger": "2.0", "openapi": "3.0.0",
"info": { "info": {
"version": "v3", "version": "v3",
"title": "Bot Connector - Direct Line API - v3.0", "title": "Bot Connector - Direct Line API - v3.0",
@ -15,7 +15,7 @@
"url": "https://opensource.org/licenses/MIT" "url": "https://opensource.org/licenses/MIT"
} }
}, },
"host": "directline.botframework.com", "servers": [{"url": "http://127.0.0.1:4242"}],
"schemes": [ "schemes": [
"https" "https"
], ],

View file

@ -76,7 +76,7 @@
"@azure/keyvault-keys": "4.8.0", "@azure/keyvault-keys": "4.8.0",
"@azure/ms-rest-js": "2.7.0", "@azure/ms-rest-js": "2.7.0",
"@azure/msal-node": "2.8.1", "@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/search-documents": "12.0.0",
"@azure/storage-blob": "12.18.0", "@azure/storage-blob": "12.18.0",
"@google-cloud/pubsub": "4.4.0", "@google-cloud/pubsub": "4.4.0",
@ -137,11 +137,12 @@
"google-libphonenumber": "3.2.34", "google-libphonenumber": "3.2.34",
"googleapis": "126.0.1", "googleapis": "126.0.1",
"hnswlib-node": "3.0.0", "hnswlib-node": "3.0.0",
"html-to-md": "^0.8.5", "html-to-md": "0.8.5",
"http-proxy": "1.18.1", "http-proxy": "1.18.1",
"ibm-watson": "9.1.0", "ibm-watson": "9.1.0",
"instagram-private-api": "^1.46.1", "instagram-private-api": "1.46.1",
"iso-639-1": "3.1.2", "iso-639-1": "3.1.2",
"isomorphic-fetch": "3.0.0",
"join-images-updated": "1.1.11", "join-images-updated": "1.1.11",
"js-md5": "0.8.3", "js-md5": "0.8.3",
"json-schema-to-zod": "2.1.0", "json-schema-to-zod": "2.1.0",
@ -210,7 +211,7 @@
"textract": "2.5.0", "textract": "2.5.0",
"twilio": "5.1.0", "twilio": "5.1.0",
"twitter-api-v2": "1.17.0", "twitter-api-v2": "1.17.0",
"typeorm": "^0.3.20", "typeorm": "0.3.20",
"typescript": "5.4.5", "typescript": "5.4.5",
"url-join": "5.0.0", "url-join": "5.0.0",
"vhost": "3.0.2", "vhost": "3.0.2",
@ -218,6 +219,7 @@
"vm2-process": "2.1.5", "vm2-process": "2.1.5",
"walk-promise": "0.2.0", "walk-promise": "0.2.0",
"washyourmouthoutwithsoap": "1.0.2", "washyourmouthoutwithsoap": "1.0.2",
"webdav-server": "2.6.2",
"whatsapp-cloud-api": "0.3.1", "whatsapp-cloud-api": "0.3.1",
"whatsapp-web.js": "https://github.com/Julzk/whatsapp-web.js/tarball/jkr_hotfix_7", "whatsapp-web.js": "https://github.com/Julzk/whatsapp-web.js/tarball/jkr_hotfix_7",
"winston": "3.13.0", "winston": "3.13.0",

View file

@ -37,6 +37,7 @@ import SwaggerClient from 'swagger-client';
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import { CodeServices } from '../../gpt.gblib/services/CodeServices.js'; import { CodeServices } from '../../gpt.gblib/services/CodeServices.js';
import { GBLogEx } from '../../core.gbapp/services/GBLogEx.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. * 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]; let min: GBMinInstance = GBServer.globals.minInstances.filter(p => p.instance.botId === botId)[0];
const client = await new SwaggerClient({ const client = await GBUtil.getDirectLineClient(min);
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
requestInterceptor: req => {
req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`;
}
});
GBServer.globals.debuggers[botId].client = client; GBServer.globals.debuggers[botId].client = client;
const response = await client.apis.Conversations.Conversations_StartConversation(); const response = await client.apis.Conversations.Conversations_StartConversation();
const conversationId = response.obj.conversationId; const conversationId = response.obj.conversationId;

View file

@ -1348,12 +1348,7 @@ export class DialogKeywords {
const conversation = min['apiConversations'][pid]; const conversation = min['apiConversations'][pid];
const client = await new SwaggerClient({ const client = await GBUtil.getDirectLineClient(min);
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
requestInterceptor: req => {
req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`;
}
});
conversation.client = client; conversation.client = client;
const response = await client.apis.Conversations.Conversations_StartConversation(); const response = await client.apis.Conversations.Conversations_StartConversation();
conversation.conversationId = response.obj.conversationId; conversation.conversationId = response.obj.conversationId;

View file

@ -106,7 +106,7 @@ export class GBConfigService {
value = undefined; value = undefined;
break; break;
case 'STORAGE_FILE': case 'STORAGE_FILE':
value = './guaribas.sqlite'; value = './data.db';
break; break;
case 'GBKB_AUTO_DEPLOY': case 'GBKB_AUTO_DEPLOY':
value = false; value = false;

View file

@ -42,6 +42,7 @@ import { FacebookAdapter } from 'botbuilder-adapter-facebook';
import mkdirp from 'mkdirp'; import mkdirp from 'mkdirp';
import Fs from 'fs'; import Fs from 'fs';
import arrayBufferToBuffer from 'arraybuffer-to-buffer'; import arrayBufferToBuffer from 'arraybuffer-to-buffer';
import { v2 as webdav } from 'webdav-server';
import { NlpManager } from 'node-nlp'; import { NlpManager } from 'node-nlp';
import Koa from 'koa'; import Koa from 'koa';
import { createRpcServer } from '@push-rpc/core'; import { createRpcServer } from '@push-rpc/core';
@ -253,6 +254,8 @@ export class GBMinService {
GBServer.globals.minInstances.push(min); GBServer.globals.minInstances.push(min);
const user = null; // No user context. const user = null; // No user context.
// Serves individual URL for each bot conversational interface.
await this.deployer['deployPackage2'](min, user, 'packages/default.gbtheme'); await this.deployer['deployPackage2'](min, user, 'packages/default.gbtheme');
// Install per bot deployed packages. // Install per bot deployed packages.
@ -314,6 +317,14 @@ export class GBMinService {
mkdirp.sync(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. // Loads Named Entity data for this bot.
// TODO: await KBService.RefreshNER(min); // TODO: await KBService.RefreshNER(min);
@ -321,16 +332,11 @@ export class GBMinService {
// Calls the loadBot context.activity for all packages. // Calls the loadBot context.activity for all packages.
await this.invokeLoadBot(min.appPackages, GBServer.globals.sysPackages, min); await this.invokeLoadBot(min.appPackages, GBServer.globals.sysPackages, min);
// Serves individual URL for each bot conversational interface.
const receiver = async (req, res) => { const receiver = async (req, res) => {
await this.receiver(req, res, conversationState, min, instance, GBServer.globals.appPackages); await this.receiver(req, res, conversationState, min, instance, GBServer.globals.appPackages);
}; };
let url = `/api/messages/${instance.botId}`; 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) => { GBServer.globals.server.get(url, (req, res) => {
if (req.query['hub.mode'] === 'subscribe') { if (req.query['hub.mode'] === 'subscribe') {
if (req.query['hub.verify_token'] === process.env.FACEBOOK_VERIFY_TOKEN) { if (req.query['hub.verify_token'] === process.env.FACEBOOK_VERIFY_TOKEN) {
@ -349,12 +355,7 @@ export class GBMinService {
if (process.env.TEST_MESSAGE) { if (process.env.TEST_MESSAGE) {
GBLogEx.info(min, `Starting auto test with '${process.env.TEST_MESSAGE}'.`); GBLogEx.info(min, `Starting auto test with '${process.env.TEST_MESSAGE}'.`);
const client = await new SwaggerClient({ const client = await GBUtil.getDirectLineClient(min);
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
requestInterceptor: req => {
req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`;
}
});
const response = await client.apis.Conversations.Conversations_StartConversation(); const response = await client.apis.Conversations.Conversations_StartConversation();
const conversationId = response.obj.conversationId; const conversationId = response.obj.conversationId;
@ -450,7 +451,9 @@ export class GBMinService {
whatsAppDirectLine = WhatsappDirectLine.botsByNumber[to]; whatsAppDirectLine = WhatsappDirectLine.botsByNumber[to];
} }
await whatsAppDirectLine.WhatsAppCallback(req, res, whatsAppDirectLine.botId); if (whatsAppDirectLine) {
await whatsAppDirectLine.WhatsAppCallback(req, res, whatsAppDirectLine.botId);
}
}) })
.bind(min); .bind(min);
@ -672,27 +675,31 @@ export class GBMinService {
theme = `default.gbtheme`; theme = `default.gbtheme`;
} }
res.send( let config = {
JSON.stringify({ instanceId: instance.instanceId,
instanceId: instance.instanceId, botId: botId,
botId: botId, theme: theme,
theme: theme, speechToken: speechToken,
//webchatToken: webchatTokenContainer.token, conversationId: webchatTokenContainer.conversationId,
domain: 'http://localhost:3978/directline', authenticatorTenant: instance.authenticatorTenant,
speechToken: speechToken, authenticatorClientId: instance.marketplaceId,
conversationId: webchatTokenContainer.conversationId, paramLogoImageUrl: this.core.getParam(instance, 'Logo Image Url', null),
authenticatorTenant: instance.authenticatorTenant, paramLogoImageAlt: this.core.getParam(instance, 'Logo Image Alt', null),
authenticatorClientId: instance.marketplaceId, paramLogoImageWidth: this.core.getParam(instance, 'Logo Image Width', null),
paramLogoImageUrl: this.core.getParam(instance, 'Logo Image Url', null), paramLogoImageHeight: this.core.getParam(instance, 'Logo Image Height', null),
paramLogoImageAlt: this.core.getParam(instance, 'Logo Image Alt', null), paramLogoImageType: this.core.getParam(instance, 'Logo Image Type', null),
paramLogoImageWidth: this.core.getParam(instance, 'Logo Image Width', null), logo: this.core.getParam(instance, 'Logo', null),
paramLogoImageHeight: this.core.getParam(instance, 'Logo Image Height', null), color1: this.core.getParam(instance, 'Color1', null),
paramLogoImageType: this.core.getParam(instance, 'Logo Image Type', null), color2: this.core.getParam(instance, 'Color2', 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 { } else {
const error = `Instance not found while retrieving from .gbui web client: ${botId}.`; const error = `Instance not found while retrieving from .gbui web client: ${botId}.`;
res.sendStatus(error); res.sendStatus(error);
@ -749,15 +756,18 @@ export class GBMinService {
*/ */
private async buildBotAdapter(instance: any, sysPackages: IGBPackage[], appPackages: IGBPackage[]) { private async buildBotAdapter(instance: any, sysPackages: IGBPackage[], appPackages: IGBPackage[]) {
// MSFT stuff. // MSFT stuff.
const uri = 'http://localhost:3978';
const adapter = new BotFrameworkAdapter({ let config = {
clientOptions: { baseUri: uri },
appId: instance.marketplaceId ? instance.marketplaceId : GBConfigService.get('MARKETPLACE_ID'), appId: instance.marketplaceId ? instance.marketplaceId : GBConfigService.get('MARKETPLACE_ID'),
appPassword: instance.marketplacePassword appPassword: instance.marketplacePassword
? instance.marketplacePassword ? instance.marketplacePassword
: GBConfigService.get('MARKETPLACE_SECRET') : 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 storage = new MemoryStorage();
const conversationState = new ConversationState(storage); const conversationState = new ConversationState(storage);
const userState = new UserState(storage); const userState = new UserState(storage);
@ -770,6 +780,28 @@ export class GBMinService {
// The minimal bot is built here. // The minimal bot is built here.
const min = new GBMinInstance(); 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.botId = instance.botId;
min.bot = adapter; min.bot = adapter;
min.userState = userState; min.userState = userState;
@ -792,6 +824,15 @@ export class GBMinService {
min['apiConversations'] = {}; min['apiConversations'] = {};
min.packages = sysPackages; 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. // NLP Manager.
const manager = new NlpManager({ languages: ['pt'], forceNER: true }); const manager = new NlpManager({ languages: ['pt'], forceNER: true });
@ -879,26 +920,6 @@ export class GBMinService {
WhatsappDirectLine.botsByNumber[botNumber] = min.whatsAppDirectLine; 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 }; return { min, adapter, conversationState };
} }
@ -1376,7 +1397,7 @@ export class GBMinService {
context.activity.text = context.activity.text.trim(); context.activity.text = context.activity.text.trim();
const member = context.activity.from; const member = context.activity.from;
let memberId, email; let memberId = null, email = null;
// Processes e-mail from id in case of Teams messages. // Processes e-mail from id in case of Teams messages.
@ -1648,12 +1669,7 @@ export class GBMinService {
if (script === 'start') { if (script === 'start') {
pid = GBVMService.createProcessInfo(user, min, 'api', null); pid = GBVMService.createProcessInfo(user, min, 'api', null);
const client = await new SwaggerClient({ const client = await GBUtil.getDirectLineClient(min);
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
requestInterceptor: req => {
req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`;
}
});
const response = await client.apis.Conversations.Conversations_StartConversation(); const response = await client.apis.Conversations.Conversations_StartConversation();
min['apiConversations'][pid] = { conversation: response.obj, client: client }; min['apiConversations'][pid] = { conversation: response.obj, client: client };

View 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);
};

View 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,
}

View file

@ -36,6 +36,7 @@ import { GBLog, GBMinInstance, GBService } from 'botlib';
import { GBServer } from '../../../src/app.js'; import { GBServer } from '../../../src/app.js';
import { SecService } from '../../security.gbapp/services/SecService.js'; import { SecService } from '../../security.gbapp/services/SecService.js';
import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js'; import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js';
import { GBUtil } from '../../../src/util.js';
/** /**
* Support for Google Chat. * Support for Google Chat.
@ -90,10 +91,7 @@ export class GoogleChatDirectLine extends GBService {
} }
public async setup (setUrl) { public async setup (setUrl) {
this.directLineClient = new Swagger({ this.directLineClient = await GBUtil.getDirectLineClient(this.min);
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
usePromise: true
});
const client = await this.directLineClient; const client = await this.directLineClient;
client.clientAuthorizations.add( client.clientAuthorizations.add(

View file

@ -54,8 +54,10 @@ export class SecService extends GBService {
user.email = email; user.email = email;
user.defaultChannel = channelName; user.defaultChannel = channelName;
GBServer.globals.users [user.userId] = user; GBServer.globals.users [user.userId] = user;
if(user.changed()){
return await user.save(); await user.save();
}
return user;
} }
/** /**

View file

@ -30,7 +30,6 @@
import mime from 'mime-types'; import mime from 'mime-types';
import urlJoin from 'url-join'; import urlJoin from 'url-join';
import SwaggerClient from 'swagger-client';
import Path from 'path'; import Path from 'path';
import Fs from 'fs'; import Fs from 'fs';
import { GBLog, GBMinInstance, GBService, IGBPackage } from 'botlib'; import { GBLog, GBMinInstance, GBService, IGBPackage } from 'botlib';
@ -46,7 +45,7 @@ import qrcode from 'qrcode-terminal';
import express from 'express'; import express from 'express';
import { GBSSR } from '../../core.gbapp/services/GBSSR.js'; import { GBSSR } from '../../core.gbapp/services/GBSSR.js';
import pkg from 'whatsapp-web.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 { DialogKeywords } from '../../basic.gblib/services/DialogKeywords.js';
import { ChatServices } from '../../gpt.gblib/services/ChatServices.js'; import { ChatServices } from '../../gpt.gblib/services/ChatServices.js';
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService.js'; import { GBAdminService } from '../../admin.gbapp/services/GBAdminService.js';
@ -117,19 +116,8 @@ export class WhatsappDirectLine extends GBService {
} }
public async setup(setUrl: boolean) { 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 url: string;
let options: any; let options: any;

View file

@ -40,6 +40,8 @@ import bodyParser from 'body-parser';
import { GBLog, GBMinInstance, IGBCoreService, IGBInstance } from 'botlib'; import { GBLog, GBMinInstance, IGBCoreService, IGBInstance } from 'botlib';
import child_process from 'child_process'; import child_process from 'child_process';
import express from 'express'; import express from 'express';
import {start as startRouter} from '../packages/core.gbapp/services/router/bridge.js'
import fs from 'fs'; import fs from 'fs';
import http from 'http'; import http from 'http';
import httpProxy from 'http-proxy'; import httpProxy from 'http-proxy';
@ -87,8 +89,9 @@ export class GBServer {
const server = express(); const server = express();
this.initEndpointsDocs(server); this.initEndpointsDocs(server);
startRouter(server);
GBServer.globals.server = server; GBServer.globals.server = server;
GBServer.globals.httpsServer = null; GBServer.globals.httpsServer = null;
GBServer.globals.webSessions = {}; GBServer.globals.webSessions = {};
GBServer.globals.processes = {}; GBServer.globals.processes = {};
@ -118,7 +121,7 @@ export class GBServer {
}); });
process.on('uncaughtException', (err, p) => { 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) => { process.on('unhandledRejection', (err, p) => {
@ -131,7 +134,7 @@ export class GBServer {
} }
if (!bypass) { 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 () => { (async () => {
try { try {
GBLogEx.info(0, `Now accepting connections on ${port}...`); GBLogEx.info(0, `Now accepting connections on ${port}...`);
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
// Reads basic configuration, initialize minimal services. // Reads basic configuration, initialize minimal services.

View file

@ -34,6 +34,8 @@
'use strict'; 'use strict';
import * as YAML from 'yaml'; import * as YAML from 'yaml';
import SwaggerClient from 'swagger-client';
import Fs from 'fs';
export class GBUtil { export class GBUtil {
public static repeat(chr, count) { public static repeat(chr, count) {
@ -64,6 +66,22 @@ export class GBUtil {
return (value + GBUtil.repeat(pad, length)).substr(0, width); 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) { public static toYAML(json) {
const doc = new YAML.Document(); const doc = new YAML.Document();
doc.contents = json; doc.contents = json;