From 9e82beaf19ca539423e5813dc2a0876512452582 Mon Sep 17 00:00:00 2001 From: Rodrigo Rodriguez Date: Tue, 12 Jul 2022 13:30:12 -0300 Subject: [PATCH] new(whatsapp.gblib): General Bots WhatsApp provider. --- package-lock.json | 57 ++- package.json | 5 +- .../basic.gblib/services/DialogKeywords.ts | 11 + packages/basic.gblib/services/GBVMService.ts | 4 + .../basic.gblib/services/SystemKeywords.ts | 66 ++- packages/core.gbapp/services/GBMinService.ts | 132 +++--- .../services/WhatsappDirectLine.ts | 414 +++++++++++------- 7 files changed, 466 insertions(+), 223 deletions(-) diff --git a/package-lock.json b/package-lock.json index 27c7c41b..81302440 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "botserver", - "version": "2.0.153", + "version": "2.0.156", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3240,6 +3240,11 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-0.18.2.tgz", "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": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -12464,6 +12469,15 @@ "integrity": "sha512-iEjGZ94OBMcESxnLorXNjJmtd/JtQYXUVrQpfwvtAKkuyawRmv+2LM6nqyOsOJkISEYbyY6ziudRE0u4VyPSVA==", "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": { "version": "1.1.0", "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": { "version": "3.10.0", "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", "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": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/nodesecurity-npm-utils/-/nodesecurity-npm-utils-6.0.0.tgz", @@ -20475,6 +20499,11 @@ "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": { "version": "1.0.20", "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", "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": { "version": "6.5.2", "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": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", diff --git a/package.json b/package.json index 349ae38d..929653ae 100644 --- a/package.json +++ b/package.json @@ -107,10 +107,12 @@ "pdf-extraction": "1.0.2", "pdfkit": "^0.13.0", "phone": "2.4.21", + "pptxtemplater": "1.0.5", "pragmatismo-io-framework": "1.0.20", "prism-media": "1.3.1", "public-ip": "4.0.4", "puppeteer": "13.7.0", + "qrcode-terminal": "0.12.0", "readline": "1.3.0", "reflect-metadata": "0.1.13", "request-promise": "4.2.5", @@ -134,7 +136,8 @@ "url-join": "4.0.1", "vbscript-to-typescript": "1.0.8", "walk-promise": "0.2.0", - "washyourmouthoutwithsoap": "1.0.2" + "washyourmouthoutwithsoap": "1.0.2", + "whatsapp-web.js": "1.17.0" }, "devDependencies": { "@types/puppeteer": "5.4.6", diff --git a/packages/basic.gblib/services/DialogKeywords.ts b/packages/basic.gblib/services/DialogKeywords.ts index f4e9932b..b144203c 100644 --- a/packages/basic.gblib/services/DialogKeywords.ts +++ b/packages/basic.gblib/services/DialogKeywords.ts @@ -48,6 +48,9 @@ const DateDiff = require('date-diff'); const puppeteer = require('puppeteer'); const Path = require('path'); 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 @@ -564,6 +567,14 @@ export class DialogKeywords { GBLog.info(`[E-mail]: to:${to}, subject: ${subject}, body: ${body}.`); 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((resolve, reject) => { sgMail.setApiKey(emailToken); const msg = { diff --git a/packages/basic.gblib/services/GBVMService.ts b/packages/basic.gblib/services/GBVMService.ts index 6e7c874d..9a3548a3 100644 --- a/packages/basic.gblib/services/GBVMService.ts +++ b/packages/basic.gblib/services/GBVMService.ts @@ -475,6 +475,10 @@ export class GBVMService extends GBService { 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) => { return `sys().saveFile(${$2}, ${$1})\n`; }); diff --git a/packages/basic.gblib/services/SystemKeywords.ts b/packages/basic.gblib/services/SystemKeywords.ts index a18d8029..fd14f222 100644 --- a/packages/basic.gblib/services/SystemKeywords.ts +++ b/packages/basic.gblib/services/SystemKeywords.ts @@ -30,17 +30,15 @@ | | \*****************************************************************************/ 'use strict'; -import { GBDialogStep, GBLog, GBMinInstance } from 'botlib'; +import { GBLog, GBMinInstance } from 'botlib'; import { GBConfigService } from '../../core.gbapp/services/GBConfigService'; import { CollectionUtil } from 'pragmatismo-io-framework'; import * as request from 'request-promise-native'; import { GBAdminService } from '../../admin.gbapp/services/GBAdminService'; import { GBDeployer } from '../../core.gbapp/services/GBDeployer'; import { DialogKeywords } from './DialogKeywords'; -import { Tabulator } from 'tabulator-tables'; import { GBServer } from '../../../src/app'; import * as fs from 'fs'; -import { jar } from 'request-promise'; const Fs = require('fs'); const Excel = require('exceljs'); @@ -51,7 +49,9 @@ const Path = require('path'); const ComputerVisionClient = require('@azure/cognitiveservices-computervision').ComputerVisionClient; const ApiKeyCredentials = require('@azure/ms-rest-js').ApiKeyCredentials; const alasql = require('alasql'); -const DateDiff = require('date-diff'); +const PizZip = require("pizzip"); +const Docxtemplater = require("docxtemplater"); +const pptxTemplaterModule = require('pptxtemplater'); /** @@ -194,20 +194,19 @@ export class SystemKeywords { if (isObject || JSON.parse(data) !== null) { let keys = Object.keys(data[0]); - + if (headers) { output[0] = []; // Copies headers as the first element. for (let i = 0; i < keys.length; i++) { - + output[0][i] = keys[i]; } } - else - { - output.push({ 'gbarray': '0' }); ; + else { + output.push({ 'gbarray': '0' });; } // Copies data from JSON format into simple array. @@ -504,7 +503,8 @@ export class SystemKeywords { * @exaple SAVE variable as "my.txt" * */ - public async saveFile(file: string, data: any): Promise { + public async saveFile(file: any, data: any): Promise { + GBLog.info(`BASIC: Saving '${file}' (SAVE file).`); let [baseUrl, client] = await GBDeployer.internalGetDriveClient(this.min); const botId = this.min.instance.botId; @@ -816,6 +816,7 @@ export class SystemKeywords { let foundIndex = 1; // Fills the row variable. + let rowCount = 0; for (; foundIndex < rows.length; foundIndex++) { let filterAcceptCount = 0; @@ -952,8 +953,8 @@ export class SystemKeywords { } row[propertyName] = value; } - row['line'] = rowCount; - row['originalLine'] = foundIndex + 1; + row['ordinal'] = rowCount; + row['line'] = foundIndex + 1; table.push(row); } @@ -1393,4 +1394,45 @@ export class SystemKeywords { 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; + } } diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index ed187613..a1f5ea01 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -45,6 +45,7 @@ const wash = require('washyourmouthoutwithsoap'); const { FacebookAdapter } = require('botbuilder-adapter-facebook'); const path = require('path'); const { NerManager } = require('node-nlp'); +const mkdirp = require('mkdirp'); import { AutoSaveStateMiddleware, BotFrameworkAdapter, @@ -245,6 +246,16 @@ export class GBMinService { 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. await KBService.RefreshNER(min); @@ -320,8 +331,11 @@ export class GBMinService { this.createCheckHealthAddress(GBServer.globals.server, min, min.instance); } - public static isChatAPI(req) { - return req.body.phone_id ? false : true; + public static isChatAPI(req, res) { + if (!res) { + return "GeneralBots"; + } + return req.body.phone_id ? "maytapi" : "chatapi"; } private async WhatsAppCallback(req, res) { @@ -334,72 +348,80 @@ export class GBMinService { 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) { - if (req.body.ack) { - res.status(200); - res.end(); + switch (provider) { + case "GeneralBots": - return; - } - } else { - if (req.body.type !== 'message') { - res.status(200); - res.end(); + id = req.body.messages[0].author.split('@')[0]; + senderName = req.body.messages[0].senderName; + text = req.body; - 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(); 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}.`); let urlMin: any = GBServer.globals.minInstances.filter (p => p.instance.botId === botId)[0]; const botNumber = urlMin ? urlMin.core.getParam(urlMin.instance, 'Bot Number', null) : null; - let activeMin; // Processes group behaviour. - let text = chatapi ? req.body.messages[0].body : req.body.message.text; text = text.replace(/\@\d+ /gi, ''); - if (chatapi) { + if (provider === "chatapi") { // Ensures that the bot group is the active bot for the user (like switching). @@ -452,10 +474,10 @@ export class GBMinService { if (startDialog) { GBLog.info(`Calling /start to Auto start ${startDialog} for ${activeMin.instance.instanceId}...`); - if (chatapi) { + if (provider === "chatapi") { req.body.messages[0].body = `/start`; } - else { + else if (provider === "maytapi") { req.body.message = `/start`; } @@ -488,10 +510,10 @@ export class GBMinService { if (startDialog) { GBLog.info(`Calling /start for Auto start : ${startDialog} for ${activeMin.instance.botId}...`); - if (chatapi) { + if (provider === "chatapi") { req.body.messages[0].body = `/start`; } - else { + else if (provider === "maytapi") { req.body.message = `/start`; } @@ -821,7 +843,7 @@ export class GBMinService { // If there is WhatsApp configuration specified, initialize // infrastructure objects. - if (min.instance.whatsappServiceUrl) { + if (min.instance.whatsappServiceKey) { min.whatsAppDirectLine = new WhatsappDirectLine( min, min.botId, @@ -1024,10 +1046,6 @@ export class GBMinService { const folder = `work/${min.instance.botId}.gbai/cache`; const filename = `${GBAdminService.generateUuid()}.png`; - if (!Fs.existsSync(folder)) { - Fs.mkdirSync(folder); - } - Fs.writeFileSync(path.join(folder, filename), data); step.context.activity.text = urlJoin(GBServer.globals.publicAddress, `${min.instance.botId}`, 'cache', filename); } diff --git a/packages/whatsapp.gblib/services/WhatsappDirectLine.ts b/packages/whatsapp.gblib/services/WhatsappDirectLine.ts index 296b7c3f..91ea3363 100644 --- a/packages/whatsapp.gblib/services/WhatsappDirectLine.ts +++ b/packages/whatsapp.gblib/services/WhatsappDirectLine.ts @@ -43,6 +43,10 @@ import { SecService } from '../../security.gbapp/services/SecService'; import { Messages } from '../strings'; import { GuaribasUser } from '../../security.gbapp/models'; 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. @@ -68,8 +72,9 @@ export class WhatsappDirectLine extends GBService { public min: GBMinInstance; private directLineSecret: string; private locale: string = 'pt-BR'; - chatapi: any; + provider: any; INSTANCE_URL = 'https://api.maytapi.com/api'; + private customClient; constructor( min: GBMinInstance, @@ -87,7 +92,8 @@ export class WhatsappDirectLine extends GBService { this.whatsappServiceKey = whatsappServiceKey; this.whatsappServiceNumber = whatsappServiceNumber; 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) { @@ -111,44 +117,101 @@ export class WhatsappDirectLine extends GBService { ); let options; - if (this.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' - } - }; + switch (this.provider) { + case 'GeneralBots': + 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) { const express = require('express'); GBServer.globals.server.use(`/audios`, express.static('work')); @@ -186,43 +249,54 @@ export class WhatsappDirectLine extends GBService { } public async received(req, res) { - if (this.chatapi && req.body.messages === undefined) { + + if (this.provider === "chatapi" && req.body.messages === undefined) { res.end(); return; // Exit here. } - - const message = this.chatapi ? req.body.messages[0] : req.body.message; + let message, from, fromName, text; let group = ""; - let answerText = null; + switch (this.provider) { + case 'GeneralBots': + message = req; + break; - let text = this.chatapi ? message.body : message.text; - text = text.replace(/\@\d+ /gi, ''); + case 'chatapi': + 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; - const fromName = this.chatapi ? req.body.messages[0].senderName : req.body.user.name; + if (req.body.messages[0].fromMe) { + res.end(); - GBLog.info(`GBWhatsapp: RCV ${from}(${fromName}): ${text})`); + return; // Exit here. + } + break; - if (this.chatapi) { - if (req.body.messages[0].fromMe) { - res.end(); + case 'maytapi': + message = req.body.message; + text = message.text; + from = req.body.user.phone; + fromName = req.body.user.name; - return; // Exit here. - } - } else { - if (req.body.message.fromMe) { - res.end(); + 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) !== '+') { group = message.chatName; @@ -283,7 +357,6 @@ export class WhatsappDirectLine extends GBService { } const botId = this.min.instance.botId; - const state = WhatsappDirectLine.state[botId + from]; if (state) { WhatsappDirectLine.state[botId + from] = null; @@ -302,7 +375,6 @@ export class WhatsappDirectLine extends GBService { const user = await sec.ensureUser(this.min.instance.instanceId, from, fromName, '', 'whatsapp', fromName, null); - const locale = user.locale ? user.locale : 'pt'; if (answerText) { @@ -315,7 +387,7 @@ export class WhatsappDirectLine extends GBService { if (process.env.AUDIO_DISABLED !== 'true') { const options = { - url: this.chatapi ? message.body : message.text, + url: this.provider ? message.body : message.text, method: 'GET', encoding: 'binary' }; @@ -535,51 +607,57 @@ export class WhatsappDirectLine extends GBService { public async sendFileToDevice(to, url, filename, caption, chatId) { 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 = { - 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' - } - }; + case 'chatapi': + 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 { // tslint:disable-next-line: await-promise const result = await request.post(options); @@ -590,21 +668,42 @@ export class WhatsappDirectLine extends GBService { } public async sendAudioToDevice(to, url, chatId) { - const 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' - } - }; + + let options; + switch (this.provider) { + case 'GeneralBots': + + const attachment = MessageMedia.fromurl(url); + await this.customClient.sendMessage(to, attachment); + + break; + + 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 { + // tslint:disable-next-line: await-promise const result = await request.post(options); GBLog.info(`Audio ${url} sent to ${to}: ${result}`); @@ -635,48 +734,59 @@ export class WhatsappDirectLine extends GBService { } else { let options; - if (this.chatapi) { - 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' - } - }; + switch (this.provider) { + case 'GeneralBots': + + this.customClient.sendMessage(to, msg); + + break; + + case 'chatapi': + + + 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 { // tslint:disable-next-line: await-promise - const result = await request.post(options); - GBLog.info(`Message [${msg}] sent to ${to}: ${result}`); + if (this.provider !== "GeneralBots") { + await request.post(options); + } + GBLog.info(`Message [${msg}] sent to ${to}.`); + } catch (error) { GBLog.error(`Error sending message to Whatsapp provider ${error.message}`);