botserver/packages/google-chat.gblib/services/GoogleChatDirectLine.ts
2022-12-26 13:37:11 -03:00

273 lines
10 KiB
TypeScript

/*****************************************************************************\
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' v `\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |
| |
| The texts of the GNU Affero General Public License with an additional |
| permission and of our proprietary license can be found at and |
| in the LICENSE file you have received along with this program. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY, without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| "General Bots" is a registered trademark of Pragmatismo.io. |
| The licensing of the program under the AGPLv3 does not imply a |
| trademark license. Therefore any rights, title and interest in |
| our trademarks remain entirely with us. |
| |
\*****************************************************************************/
import Swagger from 'swagger-client';
import { google } from 'googleapis';
import { promisify } from 'util';
import { PubSub } from '@google-cloud/pubsub';
import Fs from 'fs';
import { GBLog, GBMinInstance, GBService } from 'botlib';
import { GBServer } from '../../../src/app.js';
import { SecService } from '../../security.gbapp/services/SecService.js';
/**
* Support for Google Chat.
*/
export class GoogleChatDirectLine extends GBService {
public static conversationIds = {};
public pollInterval = 5000;
public directLineClientName = 'DirectLineClient';
public directLineClient: any;
public GoogleChatSubscriptionName: string;
public botId: string;
public min: GBMinInstance;
private directLineSecret: string;
pubSubClient: any;
GoogleChatApiKey: any;
GoogleClientEmail: any;
GoogleClientPrivateKey: any;
GoogleProjectId: any;
constructor (
min: GBMinInstance,
botId,
directLineSecret,
GoogleChatSubscriptionName,
GoogleChatApiKey,
GoogleClientEmail,
GoogleClientPrivateKey,
GoogleProjectId
) {
super();
this.min = min;
this.botId = botId;
this.directLineSecret = directLineSecret;
this.GoogleChatSubscriptionName = GoogleChatSubscriptionName;
this.GoogleChatApiKey = GoogleChatApiKey;
this.GoogleClientEmail = GoogleClientEmail;
this.GoogleClientPrivateKey = GoogleClientPrivateKey;
this.GoogleProjectId = GoogleProjectId;
this.pubSubClient = new PubSub({
projectId: this.GoogleProjectId,
credentials: { client_email: GoogleClientEmail, private_key: GoogleClientPrivateKey }
});
}
public static async asyncForEach (array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
public async setup (setUrl) {
this.directLineClient = new Swagger({
spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')),
usePromise: true
});
const client = await this.directLineClient;
client.clientAuthorizations.add(
'AuthorizationBotConnector',
new Swagger.ApiKeyAuthorization('Authorization', `Bearer ${this.directLineSecret}`, 'header')
);
if (setUrl) {
try {
const subscription = this.pubSubClient.subscription(this.GoogleChatSubscriptionName);
subscription.on('message', this.receiver.bind(this));
} catch (error) {
GBLog.error(`Error initializing 3rd party GoogleChat provider(1) ${error.message}`);
}
}
}
public async resetConversationId (key) {
GoogleChatDirectLine.conversationIds[key] = undefined;
}
public async check () {
GBLog.info(`GBGoogleChat: Checking server...`);
}
public async receiver (message) {
const event = JSON.parse(Buffer.from(message.data, 'binary').toString());
let from = '';
let fromName = '';
let text;
const threadName = event.message.thread.name;
if (event['type'] === 'ADDED_TO_SPACE' && event['space']['singleUserBotDm']) {
} else if (event['type'] === 'MESSAGE') {
text = event.message.text;
fromName = event.message.sender.displayName;
from = event.message.sender.email;
GBLog.info(`Received message from ${from} (${fromName}): ${text}.`);
}
message.ack();
const sec = new SecService();
const user = await sec.ensureUser(this.min.instance.instanceId, from, from, '', 'googlechat', fromName, from);
await sec.updateConversationReferenceById(user.userId, threadName);
GBLog.info(`GBGoogleChat: RCV ${from}: ${text})`);
const client = await this.directLineClient;
const conversationId = GoogleChatDirectLine.conversationIds[from];
if (GoogleChatDirectLine.conversationIds[from] === undefined) {
GBLog.info(`GBGoogleChat: Starting new conversation on Bot.`);
const response = await client.Conversations.Conversations_StartConversation();
const generatedConversationId = response.obj.conversationId;
GoogleChatDirectLine.conversationIds[from] = generatedConversationId;
this.pollMessages(client, generatedConversationId, threadName, from, fromName);
this.inputMessage(client, generatedConversationId, threadName, text, from, fromName);
} else {
this.inputMessage(client, conversationId, threadName, text, from, fromName);
}
}
public inputMessage (client, conversationId, threadName, text, from, fromName) {
return client.Conversations.Conversations_PostActivity({
conversationId: conversationId,
activity: {
textFormat: 'plain',
text: text,
type: 'message',
mobile: from,
from: {
id: from,
name: fromName
},
replyToId: from
}
});
}
public pollMessages (client, conversationId, threadName, from, fromName) {
GBLog.info(`GBGoogleChat: Starting message polling(${from}, ${conversationId}).`);
let watermark: any;
const worker = async () => {
try {
const response = await client.Conversations.Conversations_GetActivities({
conversationId: conversationId,
watermark: watermark
});
watermark = response.obj.watermark;
await this.printMessages(response.obj.activities, conversationId, threadName, from, fromName);
} catch (err) {
GBLog.error(`Error calling printMessages on GoogleChat channel ${err.data === undefined ? err : err.data}`);
}
};
setInterval(worker, this.pollInterval);
}
public async printMessages (activities, conversationId, threadName, from, fromName) {
if (activities && activities.length) {
// Ignore own messages.
activities = activities.filter(m => m.from.id === this.botId && m.type === 'message');
if (activities.length) {
// Print other messages.
await GoogleChatDirectLine.asyncForEach(activities, async activity => {
await this.printMessage(activity, conversationId, threadName, from, fromName);
});
}
}
}
public async printMessage (activity, conversationId, threadName, from, fromName) {
let output = '';
if (activity.text) {
GBLog.info(`GBGoogleChat: SND ${from}(${fromName}): ${activity.text}`);
output = activity.text;
}
if (activity.attachments) {
activity.attachments.forEach(attachment => {
switch (attachment.contentType) {
case 'image/png':
GBLog.info(`Opening the requested image ${attachment.contentUrl}`);
output += `\n${attachment.contentUrl}`;
break;
default:
GBLog.info(`Unknown content type: ${attachment.contentType}`);
}
});
}
await this.sendToDevice(from, conversationId, threadName, output);
}
public async sendToDevice (from: string, conversationId: string, threadName, msg: string) {
try {
let threadParts = threadName.split('/');
let spaces = threadParts[1];
let threadKey = threadParts[3];
const scopes = ['https://www.googleapis.com/auth/chat.bot'];
const jwtClient = new google.auth.JWT(this.GoogleClientEmail, null, this.GoogleClientPrivateKey, scopes, null);
await jwtClient.authorize();
const chat = google.chat({ version: 'v1', auth: jwtClient });
const res = await chat.spaces.messages.create({
parent: `spaces/${spaces}`,
threadKey: threadKey,
requestBody: {
text: msg
}
});
GBLog.info(res);
GBLog.info(`Message [${msg}] sent to ${from}: `);
} catch (error) {
GBLog.error(`Error sending message to GoogleChat provider ${error.message}`);
}
}
public async sendToDeviceEx (to, conversationId, threadName, text, locale) {
const minBoot = GBServer.globals.minBoot as any;
text = await minBoot.conversationalService.translate(minBoot, text, locale);
await this.sendToDevice(to, conversationId, threadName, text);
}
}