new(whatsapp.gblib): General Bots WhatsApp provider.

This commit is contained in:
Rodrigo Rodriguez 2022-07-12 13:30:12 -03:00
parent 844004fa01
commit 9e82beaf19
7 changed files with 466 additions and 223 deletions

57
package-lock.json generated
View file

@ -1,6 +1,6 @@
{ {
"name": "botserver", "name": "botserver",
"version": "2.0.153", "version": "2.0.156",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -3240,6 +3240,11 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-0.18.2.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-0.18.2.tgz",
"integrity": "sha512-+0P+PrP9qSFVaayNdek4P1OAGE+PEl2SsufuHDRmUpOY25Wzjo7Atyar56Trjc32jkNy4lID6ZFT6BahsR9P9A==" "integrity": "sha512-+0P+PrP9qSFVaayNdek4P1OAGE+PEl2SsufuHDRmUpOY25Wzjo7Atyar56Trjc32jkNy4lID6ZFT6BahsR9P9A=="
}, },
"@pedroslopez/moduleraid": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@pedroslopez/moduleraid/-/moduleraid-5.0.2.tgz",
"integrity": "sha512-wtnBAETBVYZ9GvcbgdswRVSLkFkYAGv1KzwBBTeRXvGT9sb9cPllOgFFWXCn9PyARQ0H+Ijz6mmoRrGateUDxQ=="
},
"@protobufjs/aspromise": { "@protobufjs/aspromise": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@ -12464,6 +12469,15 @@
"integrity": "sha512-iEjGZ94OBMcESxnLorXNjJmtd/JtQYXUVrQpfwvtAKkuyawRmv+2LM6nqyOsOJkISEYbyY6ziudRE0u4VyPSVA==", "integrity": "sha512-iEjGZ94OBMcESxnLorXNjJmtd/JtQYXUVrQpfwvtAKkuyawRmv+2LM6nqyOsOJkISEYbyY6ziudRE0u4VyPSVA==",
"dev": true "dev": true
}, },
"fluent-ffmpeg": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz",
"integrity": "sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==",
"requires": {
"async": ">=0.2.9",
"which": "^1.1.1"
}
},
"fn.name": { "fn.name": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
@ -15166,6 +15180,11 @@
} }
} }
}, },
"jsqr": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz",
"integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A=="
},
"jszip": { "jszip": {
"version": "3.10.0", "version": "3.10.0",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.0.tgz", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.0.tgz",
@ -17223,6 +17242,11 @@
"resolved": "https://registry.npmjs.org/node-tesseract-ocr/-/node-tesseract-ocr-2.2.1.tgz", "resolved": "https://registry.npmjs.org/node-tesseract-ocr/-/node-tesseract-ocr-2.2.1.tgz",
"integrity": "sha512-Q9cD79JGpPNQBxbi1fV+OAsTxYKLpx22sagsxSyKbu1u+t6UarApf5m32uVc8a5QAP1Wk7fIPN0aJFGGEE9DyQ==" "integrity": "sha512-Q9cD79JGpPNQBxbi1fV+OAsTxYKLpx22sagsxSyKbu1u+t6UarApf5m32uVc8a5QAP1Wk7fIPN0aJFGGEE9DyQ=="
}, },
"node-webpmux": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/node-webpmux/-/node-webpmux-3.1.1.tgz",
"integrity": "sha512-vG75BAe9zKghN+Y+XsJMPdOfVyesn1MmGvd/DMxeQ6gtpB3U053yCWXO1Gl2QWXTfU1++7flTihv/yB6EEdtKQ=="
},
"nodesecurity-npm-utils": { "nodesecurity-npm-utils": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/nodesecurity-npm-utils/-/nodesecurity-npm-utils-6.0.0.tgz", "resolved": "https://registry.npmjs.org/nodesecurity-npm-utils/-/nodesecurity-npm-utils-6.0.0.tgz",
@ -20475,6 +20499,11 @@
"uniq": "^1.0.1" "uniq": "^1.0.1"
} }
}, },
"pptxtemplater": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/pptxtemplater/-/pptxtemplater-1.0.5.tgz",
"integrity": "sha512-Fr9LzjpHMG12bq1gps3i9Jy75aVVXRUzv0fPnOsd0DfZnt7doPTulkXD6mjmTf30PrEu3YvIhZSsn5R88Lylmg=="
},
"pragmatismo-io-framework": { "pragmatismo-io-framework": {
"version": "1.0.20", "version": "1.0.20",
"resolved": "https://registry.npmjs.org/pragmatismo-io-framework/-/pragmatismo-io-framework-1.0.20.tgz", "resolved": "https://registry.npmjs.org/pragmatismo-io-framework/-/pragmatismo-io-framework-1.0.20.tgz",
@ -21362,6 +21391,11 @@
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
}, },
"qrcode-terminal": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz",
"integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ=="
},
"qs": { "qs": {
"version": "6.5.2", "version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
@ -25939,6 +25973,27 @@
} }
} }
}, },
"whatsapp-web.js": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/whatsapp-web.js/-/whatsapp-web.js-1.17.0.tgz",
"integrity": "sha512-oLl5ExnBOt0d6oIx+ksRJKkMNshnmg5b2fU2LKMPCF7XtRaAcwtrdZSEXSnBLAQShJWKMKrG5l0D32U3hOub/w==",
"requires": {
"@pedroslopez/moduleraid": "^5.0.2",
"fluent-ffmpeg": "^2.1.2",
"jsqr": "^1.3.1",
"mime": "^3.0.0",
"node-fetch": "^2.6.5",
"node-webpmux": "^3.1.0",
"puppeteer": "^13.0.0"
},
"dependencies": {
"mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="
}
}
},
"whatwg-url": { "whatwg-url": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",

View file

@ -107,10 +107,12 @@
"pdf-extraction": "1.0.2", "pdf-extraction": "1.0.2",
"pdfkit": "^0.13.0", "pdfkit": "^0.13.0",
"phone": "2.4.21", "phone": "2.4.21",
"pptxtemplater": "1.0.5",
"pragmatismo-io-framework": "1.0.20", "pragmatismo-io-framework": "1.0.20",
"prism-media": "1.3.1", "prism-media": "1.3.1",
"public-ip": "4.0.4", "public-ip": "4.0.4",
"puppeteer": "13.7.0", "puppeteer": "13.7.0",
"qrcode-terminal": "0.12.0",
"readline": "1.3.0", "readline": "1.3.0",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
"request-promise": "4.2.5", "request-promise": "4.2.5",
@ -134,7 +136,8 @@
"url-join": "4.0.1", "url-join": "4.0.1",
"vbscript-to-typescript": "1.0.8", "vbscript-to-typescript": "1.0.8",
"walk-promise": "0.2.0", "walk-promise": "0.2.0",
"washyourmouthoutwithsoap": "1.0.2" "washyourmouthoutwithsoap": "1.0.2",
"whatsapp-web.js": "1.17.0"
}, },
"devDependencies": { "devDependencies": {
"@types/puppeteer": "5.4.6", "@types/puppeteer": "5.4.6",

View file

@ -48,6 +48,9 @@ const DateDiff = require('date-diff');
const puppeteer = require('puppeteer'); const puppeteer = require('puppeteer');
const Path = require('path'); const Path = require('path');
const sgMail = require('@sendgrid/mail'); const sgMail = require('@sendgrid/mail');
const PizZip = require("pizzip");
const Docxtemplater = require("docxtemplater");
var mammoth = require("mammoth");
/** /**
* Base services of conversation to be called by BASIC which * Base services of conversation to be called by BASIC which
@ -564,6 +567,14 @@ export class DialogKeywords {
GBLog.info(`[E-mail]: to:${to}, subject: ${subject}, body: ${body}.`); GBLog.info(`[E-mail]: to:${to}, subject: ${subject}, body: ${body}.`);
const emailToken = process.env.EMAIL_API_KEY; const emailToken = process.env.EMAIL_API_KEY;
// Inline word document used as e-mail body.
if (typeof (body) === "object" )
{
const result = await mammoth.convertToHtml({buffer: body});
body = result.value;
}
return new Promise<any>((resolve, reject) => { return new Promise<any>((resolve, reject) => {
sgMail.setApiKey(emailToken); sgMail.setApiKey(emailToken);
const msg = { const msg = {

View file

@ -475,6 +475,10 @@ export class GBVMService extends GBService {
return `${$1} = sys().asPdf(${$2})\n`; return `${$1} = sys().asPdf(${$2})\n`;
}); });
code = code.replace(/(\w+)\s*\=\s*FILL\s(.*)\sWITH\s(.*)/gi, ($0, $1, $2, $3) => {
return `${1} = sys().fill(${$2}, ${$1})\n`;
});
code = code.replace(/save\s(.*)\sas\s(.*)/gi, ($0, $1, $2, $3) => { code = code.replace(/save\s(.*)\sas\s(.*)/gi, ($0, $1, $2, $3) => {
return `sys().saveFile(${$2}, ${$1})\n`; return `sys().saveFile(${$2}, ${$1})\n`;
}); });

View file

@ -30,17 +30,15 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
'use strict'; 'use strict';
import { GBDialogStep, GBLog, GBMinInstance } from 'botlib'; import { GBLog, GBMinInstance } from 'botlib';
import { GBConfigService } from '../../core.gbapp/services/GBConfigService'; import { GBConfigService } from '../../core.gbapp/services/GBConfigService';
import { CollectionUtil } from 'pragmatismo-io-framework'; import { CollectionUtil } from 'pragmatismo-io-framework';
import * as request from 'request-promise-native'; import * as request from 'request-promise-native';
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService'; import { GBAdminService } from '../../admin.gbapp/services/GBAdminService';
import { GBDeployer } from '../../core.gbapp/services/GBDeployer'; import { GBDeployer } from '../../core.gbapp/services/GBDeployer';
import { DialogKeywords } from './DialogKeywords'; import { DialogKeywords } from './DialogKeywords';
import { Tabulator } from 'tabulator-tables';
import { GBServer } from '../../../src/app'; import { GBServer } from '../../../src/app';
import * as fs from 'fs'; import * as fs from 'fs';
import { jar } from 'request-promise';
const Fs = require('fs'); const Fs = require('fs');
const Excel = require('exceljs'); const Excel = require('exceljs');
@ -51,7 +49,9 @@ const Path = require('path');
const ComputerVisionClient = require('@azure/cognitiveservices-computervision').ComputerVisionClient; const ComputerVisionClient = require('@azure/cognitiveservices-computervision').ComputerVisionClient;
const ApiKeyCredentials = require('@azure/ms-rest-js').ApiKeyCredentials; const ApiKeyCredentials = require('@azure/ms-rest-js').ApiKeyCredentials;
const alasql = require('alasql'); const alasql = require('alasql');
const DateDiff = require('date-diff'); const PizZip = require("pizzip");
const Docxtemplater = require("docxtemplater");
const pptxTemplaterModule = require('pptxtemplater');
/** /**
@ -205,9 +205,8 @@ export class SystemKeywords {
output[0][i] = keys[i]; output[0][i] = keys[i];
} }
} }
else else {
{ output.push({ 'gbarray': '0' });;
output.push({ 'gbarray': '0' }); ;
} }
// Copies data from JSON format into simple array. // Copies data from JSON format into simple array.
@ -504,7 +503,8 @@ export class SystemKeywords {
* @exaple SAVE variable as "my.txt" * @exaple SAVE variable as "my.txt"
* *
*/ */
public async saveFile(file: string, data: any): Promise<any> { public async saveFile(file: any, data: any): Promise<any> {
GBLog.info(`BASIC: Saving '${file}' (SAVE file).`); GBLog.info(`BASIC: Saving '${file}' (SAVE file).`);
let [baseUrl, client] = await GBDeployer.internalGetDriveClient(this.min); let [baseUrl, client] = await GBDeployer.internalGetDriveClient(this.min);
const botId = this.min.instance.botId; const botId = this.min.instance.botId;
@ -816,6 +816,7 @@ export class SystemKeywords {
let foundIndex = 1; let foundIndex = 1;
// Fills the row variable. // Fills the row variable.
let rowCount = 0; let rowCount = 0;
for (; foundIndex < rows.length; foundIndex++) { for (; foundIndex < rows.length; foundIndex++) {
let filterAcceptCount = 0; let filterAcceptCount = 0;
@ -952,8 +953,8 @@ export class SystemKeywords {
} }
row[propertyName] = value; row[propertyName] = value;
} }
row['line'] = rowCount; row['ordinal'] = rowCount;
row['originalLine'] = foundIndex + 1; row['line'] = foundIndex + 1;
table.push(row); table.push(row);
} }
@ -1393,4 +1394,45 @@ export class SystemKeywords {
return text.replace(/\D/gi, ''); return text.replace(/\D/gi, '');
} }
/**
*
* Fills a .docx or .pptx with template data.
*
* doc = FILL "templates/template.docx", data
*
*/
public async fill(templateName, data) {
const botId = this.min.instance.botId;
const gbaiName = `${botId}.gbai`;
const path = `/${botId}.gbai/${botId}.gbdata`;
// Downloads template from .gbdrive.
let [baseUrl, client] = await GBDeployer.internalGetDriveClient(this.min);
let template = await this.internalGetDocument(client, baseUrl, path, templateName);
const url = template['@microsoft.graph.downloadUrl'];
const localName = Path.join('work', gbaiName, 'cache', ``);
const response = await request({ uri: url, encoding: null });
Fs.writeFileSync(localName, response, { encoding: null });
// Loads the file as binary content.
const content = fs.readFileSync(localName, "binary");
const zip = new PizZip(content);
const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true, });
if (localName.endsWith('.pptx')) {
doc.attachModule(pptxTemplaterModule);
}
// Renders the document (Replace {first_name} by John, {last_name} by Doe, ...)
doc.render(data);
// Returns the buffer to be used with SAVE AS for example.
const buf = doc.getZip().generate({ type: "nodebuffer", compression: "DEFLATE", });
return buf;
}
} }

View file

@ -45,6 +45,7 @@ const wash = require('washyourmouthoutwithsoap');
const { FacebookAdapter } = require('botbuilder-adapter-facebook'); const { FacebookAdapter } = require('botbuilder-adapter-facebook');
const path = require('path'); const path = require('path');
const { NerManager } = require('node-nlp'); const { NerManager } = require('node-nlp');
const mkdirp = require('mkdirp');
import { import {
AutoSaveStateMiddleware, AutoSaveStateMiddleware,
BotFrameworkAdapter, BotFrameworkAdapter,
@ -245,6 +246,16 @@ export class GBMinService {
await this.deployer.deployPackage(min, packagePath); await this.deployer.deployPackage(min, packagePath);
} }
let dir = `work/${min.botId}.gbai/cache`;
if (!fs.existsSync(dir)) {
mkdirp.sync(dir);
}
dir = `work/${min.botId}.gbai/profile`;
if (!fs.existsSync(dir)) {
mkdirp.sync(dir);
}
// Loads Named Entity data for this bot. // Loads Named Entity data for this bot.
await KBService.RefreshNER(min); await KBService.RefreshNER(min);
@ -320,8 +331,11 @@ export class GBMinService {
this.createCheckHealthAddress(GBServer.globals.server, min, min.instance); this.createCheckHealthAddress(GBServer.globals.server, min, min.instance);
} }
public static isChatAPI(req) { public static isChatAPI(req, res) {
return req.body.phone_id ? false : true; if (!res) {
return "GeneralBots";
}
return req.body.phone_id ? "maytapi" : "chatapi";
} }
private async WhatsAppCallback(req, res) { private async WhatsAppCallback(req, res) {
@ -334,72 +348,80 @@ export class GBMinService {
return; return;
} }
let chatapi = GBMinService.isChatAPI(req); let provider = GBMinService.isChatAPI(req, res);
let id = provider ? req.body.messages[0].author.split('@')[0] : req.body.user.phone;
let senderName = provider ? req.body.messages[0].senderName : req.body.user.name;
let botId;
let text;
if (chatapi) { switch (provider) {
if (req.body.ack) { case "GeneralBots":
res.status(200);
res.end();
return; id = req.body.messages[0].author.split('@')[0];
} senderName = req.body.messages[0].senderName;
} else { text = req.body;
if (req.body.type !== 'message') {
res.status(200);
res.end();
return; break;
}
case "chatapi":
id = req.body.messages[0].author.split('@')[0];
senderName = req.body.messages[0].senderName;
text = req.body.messages[0].body;
if (req.body.ack) {
res.status(200);
res.end();
return;
}
if (req.body.messages[0].fromMe) {
res.end();
return; // Exit here.
}
botId = req.params.botId;
if (botId === '[default]' || botId === undefined) {
botId = GBConfigService.get('BOT_ID');
}
break;
case "maytapi":
id = req.body.user.phone;
senderName = req.body.user.name;
text = req.body.message.text;
if (req.body.type !== 'message') {
res.status(200);
res.end();
return;
}
if (req.body.message.fromMe) {
res.end();
return; // Exit here.
}
botId = WhatsappDirectLine.phones[req.body.phoneId];
break;
} }
// Detects if the message is echo from itself.
const id = chatapi ? req.body.messages[0].author.split('@')[0] : req.body.user.phone;
const senderName = chatapi ? req.body.messages[0].senderName : req.body.user.name;
const sec = new SecService(); const sec = new SecService();
let user = await sec.getUserFromSystemId(id); let user = await sec.getUserFromSystemId(id);
if (chatapi) {
if (req.body.messages[0].fromMe) {
res.end();
return; // Exit here.
}
} else {
if (req.body.message.fromMe) {
res.end();
return; // Exit here.
}
}
let botId;
if (chatapi) {
botId = req.params.botId;
if (botId === '[default]' || botId === undefined) {
botId = GBConfigService.get('BOT_ID');
}
}
else {
botId = WhatsappDirectLine.phones[req.body.phoneId];
}
GBLog.info(`A WhatsApp mobile requested instance for: ${botId}.`); GBLog.info(`A WhatsApp mobile requested instance for: ${botId}.`);
let urlMin: any = GBServer.globals.minInstances.filter let urlMin: any = GBServer.globals.minInstances.filter
(p => p.instance.botId === botId)[0]; (p => p.instance.botId === botId)[0];
const botNumber = urlMin ? urlMin.core.getParam(urlMin.instance, 'Bot Number', null) : null; const botNumber = urlMin ? urlMin.core.getParam(urlMin.instance, 'Bot Number', null) : null;
let activeMin; let activeMin;
// Processes group behaviour. // Processes group behaviour.
let text = chatapi ? req.body.messages[0].body : req.body.message.text;
text = text.replace(/\@\d+ /gi, ''); text = text.replace(/\@\d+ /gi, '');
if (chatapi) { if (provider === "chatapi") {
// Ensures that the bot group is the active bot for the user (like switching). // Ensures that the bot group is the active bot for the user (like switching).
@ -452,10 +474,10 @@ export class GBMinService {
if (startDialog) { if (startDialog) {
GBLog.info(`Calling /start to Auto start ${startDialog} for ${activeMin.instance.instanceId}...`); GBLog.info(`Calling /start to Auto start ${startDialog} for ${activeMin.instance.instanceId}...`);
if (chatapi) { if (provider === "chatapi") {
req.body.messages[0].body = `/start`; req.body.messages[0].body = `/start`;
} }
else { else if (provider === "maytapi") {
req.body.message = `/start`; req.body.message = `/start`;
} }
@ -488,10 +510,10 @@ export class GBMinService {
if (startDialog) { if (startDialog) {
GBLog.info(`Calling /start for Auto start : ${startDialog} for ${activeMin.instance.botId}...`); GBLog.info(`Calling /start for Auto start : ${startDialog} for ${activeMin.instance.botId}...`);
if (chatapi) { if (provider === "chatapi") {
req.body.messages[0].body = `/start`; req.body.messages[0].body = `/start`;
} }
else { else if (provider === "maytapi") {
req.body.message = `/start`; req.body.message = `/start`;
} }
@ -821,7 +843,7 @@ export class GBMinService {
// If there is WhatsApp configuration specified, initialize // If there is WhatsApp configuration specified, initialize
// infrastructure objects. // infrastructure objects.
if (min.instance.whatsappServiceUrl) { if (min.instance.whatsappServiceKey) {
min.whatsAppDirectLine = new WhatsappDirectLine( min.whatsAppDirectLine = new WhatsappDirectLine(
min, min,
min.botId, min.botId,
@ -1024,10 +1046,6 @@ export class GBMinService {
const folder = `work/${min.instance.botId}.gbai/cache`; const folder = `work/${min.instance.botId}.gbai/cache`;
const filename = `${GBAdminService.generateUuid()}.png`; const filename = `${GBAdminService.generateUuid()}.png`;
if (!Fs.existsSync(folder)) {
Fs.mkdirSync(folder);
}
Fs.writeFileSync(path.join(folder, filename), data); Fs.writeFileSync(path.join(folder, filename), data);
step.context.activity.text = urlJoin(GBServer.globals.publicAddress, `${min.instance.botId}`, 'cache', filename); step.context.activity.text = urlJoin(GBServer.globals.publicAddress, `${min.instance.botId}`, 'cache', filename);
} }

View file

@ -43,6 +43,10 @@ import { SecService } from '../../security.gbapp/services/SecService';
import { Messages } from '../strings'; import { Messages } from '../strings';
import { GuaribasUser } from '../../security.gbapp/models'; import { GuaribasUser } from '../../security.gbapp/models';
import { GBConfigService } from '../../core.gbapp/services/GBConfigService'; import { GBConfigService } from '../../core.gbapp/services/GBConfigService';
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService';
import { DialogKeywords } from '../../basic.gblib/services/DialogKeywords';
const { MessageMedia, Client, LocalAuth } = require('whatsapp-web.js');
const qrcode = require('qrcode-terminal');
/** /**
* Support for Whatsapp. * Support for Whatsapp.
@ -68,8 +72,9 @@ export class WhatsappDirectLine extends GBService {
public min: GBMinInstance; public min: GBMinInstance;
private directLineSecret: string; private directLineSecret: string;
private locale: string = 'pt-BR'; private locale: string = 'pt-BR';
chatapi: any; provider: any;
INSTANCE_URL = 'https://api.maytapi.com/api'; INSTANCE_URL = 'https://api.maytapi.com/api';
private customClient;
constructor( constructor(
min: GBMinInstance, min: GBMinInstance,
@ -87,7 +92,8 @@ export class WhatsappDirectLine extends GBService {
this.whatsappServiceKey = whatsappServiceKey; this.whatsappServiceKey = whatsappServiceKey;
this.whatsappServiceNumber = whatsappServiceNumber; this.whatsappServiceNumber = whatsappServiceNumber;
this.whatsappServiceUrl = whatsappServiceUrl; this.whatsappServiceUrl = whatsappServiceUrl;
this.chatapi = whatsappServiceNumber.indexOf(';') > -1 ? false : false; this.provider = whatsappServiceKey === "gbnative" ?
'GeneralBots' : whatsappServiceNumber.indexOf(';') > -1 ? 'maytapi' : 'chatapi';
} }
public static async asyncForEach(array, callback) { public static async asyncForEach(array, callback) {
@ -111,44 +117,101 @@ export class WhatsappDirectLine extends GBService {
); );
let options; let options;
if (this.chatapi) { switch (this.provider) {
options = { case 'GeneralBots':
method: 'POST',
url: urlJoin(this.whatsappServiceUrl, 'webhook'),
timeout: 10000,
qs: {
token: this.whatsappServiceKey,
webhookUrl: `${GBServer.globals.publicAddress}/webhooks/whatsapp/${this.botId}`,
set: true
},
headers: {
'cache-control': 'no-cache'
}
};
const Path = require('path');
const gbaiName = `${this.min.botId}.gbai`;
let localName = Path.join('work', gbaiName, 'profile');
let client = this.customClient = new Client({
authStrategy: new LocalAuth(),
puppeteer: {
headless: false, args: ['--disable-features=site-per-process',
`--user-data-dir=${localName}`]
}
});
client.initialize();
client.on('message', (async message => {
await this.received(message, null);
}).bind(this));
client.on('qr', ((qr) => {
const adminNumber = this.min.core.getParam(this.min.instance, 'Bot Admin Number', null);
const adminEmail = this.min.core.getParam(this.min.instance, 'Bot Admin E-mail', null);
// Sends QR Code to boot bot admin.
const info = this.customClient.info;
const msg = `Please, scan QR Code with ${info.wid.user}(${info.pushname}) for bot ${this.botId}: ${qr}.`;
GBLog.info(msg);
qrcode.generate(qr, { small: true, scale: 0.5 });
// While handling other bots uses boot instance of this class to send QR Codes.
if (this.botId !== GBServer.globals.minBoot.botId) {
GBServer.globals.minBoot.whatsAppDirectLine.sendMessage(msg);
GBServer.globals.minBoot.whatsAppDirectLine.sendMessage(adminNumber, qr);
const s = new DialogKeywords(null, null, null, null);
s.sendEmail(adminEmail, `Check your WhatsApp for bot ${this.botId}`, msg);
}
}).bind(this));
client.on('authenticated', () => {
GBLog.info(`WhatsApp QR Code authenticated for ${this.botId}.`);
});
setUrl = false;
break;
case 'chatapi':
options = {
method: 'POST',
url: urlJoin(this.whatsappServiceUrl, 'webhook'),
timeout: 10000,
qs: {
token: this.whatsappServiceKey,
webhookUrl: `${GBServer.globals.publicAddress}/webhooks/whatsapp/${this.botId}`,
set: true
},
headers: {
'cache-control': 'no-cache'
}
};
break;
case 'maytapi':
let phoneId = this.whatsappServiceNumber.split(';')[0];
let productId = this.whatsappServiceNumber.split(';')[1]
let url = `${this.INSTANCE_URL}/${productId}/${phoneId}/config`;
WhatsappDirectLine.phones[phoneId] = this.botId;
options = {
url: url,
method: 'POST',
body: {
webhook: `${GBServer.globals.publicAddress}/webhooks/whatsapp/${this.botId}`,
"ack_delivery": false
},
headers: {
'x-maytapi-key': this.whatsappServiceKey,
'Content-Type': 'application/json',
},
json: true,
};
break;
} }
else {
let phoneId = this.whatsappServiceNumber.split(';')[0];
let productId = this.whatsappServiceNumber.split(';')[1]
let url = `${this.INSTANCE_URL}/${productId}/${phoneId}/config`;
WhatsappDirectLine.phones[phoneId] = this.botId;
options = {
url: url,
method: 'POST',
body: {
webhook: `${GBServer.globals.publicAddress}/webhooks/whatsapp/${this.botId}`,
"ack_delivery": false
},
headers: {
'x-maytapi-key': this.whatsappServiceKey,
'Content-Type': 'application/json',
},
json: true,
};
}
if (setUrl) { if (setUrl) {
const express = require('express'); const express = require('express');
GBServer.globals.server.use(`/audios`, express.static('work')); GBServer.globals.server.use(`/audios`, express.static('work'));
@ -186,43 +249,54 @@ export class WhatsappDirectLine extends GBService {
} }
public async received(req, res) { public async received(req, res) {
if (this.chatapi && req.body.messages === undefined) {
if (this.provider === "chatapi" && req.body.messages === undefined) {
res.end(); res.end();
return; // Exit here. return; // Exit here.
} }
let message, from, fromName, text;
const message = this.chatapi ? req.body.messages[0] : req.body.message;
let group = ""; let group = "";
let answerText = null; let answerText = null;
switch (this.provider) {
case 'GeneralBots':
message = req;
break;
let text = this.chatapi ? message.body : message.text; case 'chatapi':
text = text.replace(/\@\d+ /gi, ''); message = req.body.messages[0];
text = message.body;
from = req.body.messages[0].author.split('@')[0];
fromName = req.body.messages[0].senderName;
const from = this.chatapi ? req.body.messages[0].author.split('@')[0] : req.body.user.phone; if (req.body.messages[0].fromMe) {
const fromName = this.chatapi ? req.body.messages[0].senderName : req.body.user.name; res.end();
GBLog.info(`GBWhatsapp: RCV ${from}(${fromName}): ${text})`); return; // Exit here.
}
break;
if (this.chatapi) { case 'maytapi':
if (req.body.messages[0].fromMe) { message = req.body.message;
res.end(); text = message.text;
from = req.body.user.phone;
fromName = req.body.user.name;
return; // Exit here. if (req.body.message.fromMe) {
} res.end();
} else {
if (req.body.message.fromMe) {
res.end();
return; // Exit here. return; // Exit here.
} }
break;
} }
if (this.chatapi) { text = text.replace(/\@\d+ /gi, '');
GBLog.info(`GBWhatsapp: RCV ${from}(${fromName}): ${text})`);
if (this.provider === "chatapi") {
if (message.chatName.charAt(0) !== '+') { if (message.chatName.charAt(0) !== '+') {
group = message.chatName; group = message.chatName;
@ -283,7 +357,6 @@ export class WhatsappDirectLine extends GBService {
} }
const botId = this.min.instance.botId; const botId = this.min.instance.botId;
const state = WhatsappDirectLine.state[botId + from]; const state = WhatsappDirectLine.state[botId + from];
if (state) { if (state) {
WhatsappDirectLine.state[botId + from] = null; WhatsappDirectLine.state[botId + from] = null;
@ -302,7 +375,6 @@ export class WhatsappDirectLine extends GBService {
const user = await sec.ensureUser(this.min.instance.instanceId, from, const user = await sec.ensureUser(this.min.instance.instanceId, from,
fromName, '', 'whatsapp', fromName, null); fromName, '', 'whatsapp', fromName, null);
const locale = user.locale ? user.locale : 'pt'; const locale = user.locale ? user.locale : 'pt';
if (answerText) { if (answerText) {
@ -315,7 +387,7 @@ export class WhatsappDirectLine extends GBService {
if (process.env.AUDIO_DISABLED !== 'true') { if (process.env.AUDIO_DISABLED !== 'true') {
const options = { const options = {
url: this.chatapi ? message.body : message.text, url: this.provider ? message.body : message.text,
method: 'GET', method: 'GET',
encoding: 'binary' encoding: 'binary'
}; };
@ -535,51 +607,57 @@ export class WhatsappDirectLine extends GBService {
public async sendFileToDevice(to, url, filename, caption, chatId) { public async sendFileToDevice(to, url, filename, caption, chatId) {
let options; let options;
if (this.chatapi) { switch (this.provider) {
case 'GeneralBots':
const attachment = MessageMedia.fromurl(url);
await this.customClient.sendMessage(to, attachment, { caption: caption });
break;
options = { case 'chatapi':
method: 'POST',
url: urlJoin(this.whatsappServiceUrl, 'sendFile'),
qs: {
token: this.whatsappServiceKey,
phone: chatId ? null : to,
chatId: chatId,
body: url,
filename: filename,
caption: caption
},
headers: {
'cache-control': 'no-cache'
}
};
options = {
method: 'POST',
url: urlJoin(this.whatsappServiceUrl, 'sendFile'),
qs: {
token: this.whatsappServiceKey,
phone: chatId ? null : to,
chatId: chatId,
body: url,
filename: filename,
caption: caption
},
headers: {
'cache-control': 'no-cache'
}
};
break;
case 'maytapi':
let contents = 0;
let body = {
to_number: to,
type: "media",
message: url,
text: caption
};
let phoneId = this.whatsappServiceNumber.split(';')[0];
let productId = this.whatsappServiceNumber.split(';')[1]
options = {
url: `${this.INSTANCE_URL}/${productId}/${phoneId}/sendMessage`,
method: 'post',
json: true,
body,
headers: {
'Content-Type': 'application/json',
'x-maytapi-key': this.whatsappServiceKey,
},
};
break;
} }
else {
let contents = 0;
let body = {
to_number: to,
type: "media",
message: url,
text: caption
};
let phoneId = this.whatsappServiceNumber.split(';')[0];
let productId = this.whatsappServiceNumber.split(';')[1]
options = {
url: `${this.INSTANCE_URL}/${productId}/${phoneId}/sendMessage`,
method: 'post',
json: true,
body,
headers: {
'Content-Type': 'application/json',
'x-maytapi-key': this.whatsappServiceKey,
},
};
}
try { try {
// tslint:disable-next-line: await-promise // tslint:disable-next-line: await-promise
const result = await request.post(options); const result = await request.post(options);
@ -590,21 +668,42 @@ export class WhatsappDirectLine extends GBService {
} }
public async sendAudioToDevice(to, url, chatId) { public async sendAudioToDevice(to, url, chatId) {
const options = {
method: 'POST', let options;
url: urlJoin(this.whatsappServiceUrl, 'sendPTT'), switch (this.provider) {
qs: { case 'GeneralBots':
token: this.whatsappServiceKey,
phone: chatId ? null : to, const attachment = MessageMedia.fromurl(url);
chatId: chatId, await this.customClient.sendMessage(to, attachment);
body: url
}, break;
headers: {
'cache-control': 'no-cache' case 'chatapi':
}
}; options = {
method: 'POST',
url: urlJoin(this.whatsappServiceUrl, 'sendPTT'),
qs: {
token: this.whatsappServiceKey,
phone: chatId ? null : to,
chatId: chatId,
body: url
},
headers: {
'cache-control': 'no-cache'
}
};
break;
case 'maytapi':
options = {}; // TODO: Code Maytapi.
break;
}
try { try {
// tslint:disable-next-line: await-promise // tslint:disable-next-line: await-promise
const result = await request.post(options); const result = await request.post(options);
GBLog.info(`Audio ${url} sent to ${to}: ${result}`); GBLog.info(`Audio ${url} sent to ${to}: ${result}`);
@ -635,48 +734,59 @@ export class WhatsappDirectLine extends GBService {
} else { } else {
let options; let options;
if (this.chatapi) {
options = { switch (this.provider) {
method: 'POST', case 'GeneralBots':
url: urlJoin(this.whatsappServiceUrl, 'message'),
qs: { this.customClient.sendMessage(to, msg);
token: this.whatsappServiceKey,
phone: chatId ? null : to, break;
chatId: chatId,
body: msg case 'chatapi':
},
headers: {
'cache-control': 'no-cache' options = {
} method: 'POST',
}; url: urlJoin(this.whatsappServiceUrl, 'message'),
qs: {
token: this.whatsappServiceKey,
phone: chatId ? null : to,
chatId: chatId,
body: msg
},
headers: {
'cache-control': 'no-cache'
}
};
break;
case 'maytapi':
let phoneId = this.whatsappServiceNumber.split(';')[0];
let productId = this.whatsappServiceNumber.split(';')[1]
let url = `${this.INSTANCE_URL}/${productId}/${phoneId}/sendMessage`;
options = {
url: url,
method: 'post',
json: true,
body: { type: 'text', message: msg, to_number: to },
headers: {
'Content-Type': 'application/json',
'x-maytapi-key': this.whatsappServiceKey,
},
};
break;
} }
else {
let phoneId = this.whatsappServiceNumber.split(';')[0];
let productId = this.whatsappServiceNumber.split(';')[1]
let url = `${this.INSTANCE_URL}/${productId}/${phoneId}/sendMessage`;
options = {
url: url,
method: 'post',
json: true,
body: { type: 'text', message: msg, to_number: to },
headers: {
'Content-Type': 'application/json',
'x-maytapi-key': this.whatsappServiceKey,
},
};
}
try { try {
// tslint:disable-next-line: await-promise // tslint:disable-next-line: await-promise
const result = await request.post(options); if (this.provider !== "GeneralBots") {
GBLog.info(`Message [${msg}] sent to ${to}: ${result}`); await request.post(options);
}
GBLog.info(`Message [${msg}] sent to ${to}.`);
} catch (error) { } catch (error) {
GBLog.error(`Error sending message to Whatsapp provider ${error.message}`); GBLog.error(`Error sending message to Whatsapp provider ${error.message}`);