/*****************************************************************************\ | ( )_ _ | | _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | | ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | | | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | | | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | | | | ( )_) | | | (_) \___/' | | | | 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. | | | \*****************************************************************************/ const Path = require("path") const Fs = require("fs") const _ = require("lodash") const Parse = require("csv-parse") const Async = require("async") const UrlJoin = require("url-join") const Walk = require("fs-walk") const logger = require("../../../src/logger") const Swagger = require('swagger-client') const rp = require('request-promise') import * as request from "request-promise-native" import { GBServiceCallback, GBService, IGBInstance } from "botlib" export class WhatsappDirectLine extends GBService { pollInterval = 1000 directLineClientName = 'DirectLineClient' directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json' directLineClient: any whatsappServiceKey: string whatsappServiceNumber: string whatsappServiceUrl: string whatsappServiceWebhookUrl: string botId: string watermark: string = null conversationIds = {} constructor(botId, directLineSecret, whatsappServiceKey, whatsappServiceNumber, whatsappServiceUrl, whatsappServiceWebhookUrl) { super() this.botId = botId this.whatsappServiceKey = whatsappServiceKey this.whatsappServiceNumber = whatsappServiceNumber this.whatsappServiceUrl = whatsappServiceUrl this.whatsappServiceWebhookUrl = whatsappServiceWebhookUrl // TODO: Migrate to Swagger 3. this.directLineClient = rp(this.directLineSpecUrl) .then((spec) => { return new Swagger({ spec: JSON.parse(spec.trim()), usePromise: true }) }) .then(async (client) => { client.clientAuthorizations.add('AuthorizationBotConnector', new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + directLineSecret, 'header')) var options = { method: 'POST', url: UrlJoin(this.whatsappServiceUrl, "webhook"), qs: { token: this.whatsappServiceKey, webhookUrl: `${this.whatsappServiceWebhookUrl}/instances/${this.botId}/whatsapp`, set: true }, headers: { 'cache-control': 'no-cache' } } try { const result = await request.post(options) logger.info(result) } catch (error) { logger.error('Error initializing 3rd party Whatsapp provider.', error) } return client }) .catch((err) => { logger.error('Error initializing DirectLine client', err) }) } received(req, res) { let text = req.body.messages[0].body let from = req.body.messages[0].author.split('@')[0] let fromName = req.body.messages[0].senderName if (req.body.messages[0].fromMe) { return // Exit here. } logger.info(`GBWhatsapp: Hook called. from: ${from}(${fromName}), text: ${text})`) let conversationId = this.conversationIds[from] this.directLineClient.then((client) => { if (this.conversationIds[from] == null) { logger.info(`GBWhatsapp: Starting new conversation on Bot.`) client.Conversations.Conversations_StartConversation() .then((response) => { return response.obj.conversationId }) .then((conversationId) => { this.conversationIds[from] = conversationId this.inputMessage(client, conversationId, text, from, fromName) this.pollMessages(client, conversationId, from, fromName) }) .catch((err) => { console.error('Error starting conversation', err) }) } else { this.inputMessage(client, conversationId, text, from, fromName) } res.end() }) } inputMessage(client, conversationId, text, from, fromName) { client.Conversations.Conversations_PostActivity( { conversationId: conversationId, activity: { textFormat: 'plain', text: text, type: 'message', from: { id: from, name: fromName }, replyToId: from } }).catch((err) => { logger.error(`GBWhatsapp: Error receiving message: ${err}.`) }) } pollMessages(client, conversationId, from, fromName) { logger.info(`GBWhatsapp: Starting polling message for conversationId: ${conversationId}.`) setInterval(() => { client.Conversations.Conversations_GetActivities({ conversationId: conversationId, watermark: this.watermark }) .then((response) => { this.watermark = response.obj.watermark return response.obj.activities }) .then((activities) => { this.printMessages(activities, conversationId, from, fromName) }) }, this.pollInterval) } printMessages(activities, conversationId, from, fromName) { if (activities && activities.length) { // Ignore own messages. activities = activities.filter((m) => { return (m.from.id === "GeneralBots") && m.type === "message" }) if (activities.length) { // Print other messages. activities.forEach(activity => { this.printMessage(activity, conversationId, from, fromName) }) } } } printMessage(activity, conversationId, from, fromName) { let output = "" if (activity.text) { logger.info(`GBWhatsapp: MSG: ${activity.text}`) output = activity.text } if (activity.attachments) { activity.attachments.forEach((attachment) => { switch (attachment.contentType) { case "application/vnd.microsoft.card.hero": output += `\n${this.renderHeroCard(attachment)}` break case "image/png": logger.info('Opening the requested image ' + attachment.contentUrl) output += `\n${attachment.contentUrl}` break } }) } this.sendToDevice(conversationId, from, fromName, output) } renderHeroCard(attachment) { return `${attachment.content.title} - ${attachment.content.text}` } async sendToDevice(conversationId, to, toName, msg) { var options = { method: 'POST', url: UrlJoin(this.whatsappServiceUrl, 'message'), qs: { token: this.whatsappServiceKey, phone: to, body: msg }, headers: { 'cache-control': 'no-cache' } } const result = await request.get(options) } }