import urlJoin = require('url-join'); const Swagger = require('swagger-client'); const rp = require('request-promise'); import { GBLog, GBService } from 'botlib'; import * as request from 'request-promise-native'; import { GBServer } from '../../../src/app'; /** * Support for Whatsapp. */ export class WhatsappDirectLine extends GBService { public pollInterval = 1000; public directLineClientName = 'DirectLineClient'; public directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json'; public directLineClient: any; public whatsappServiceKey: string; public whatsappServiceNumber: string; public whatsappServiceUrl: string; public whatsappServiceWebhookUrl: string; public botId: string; public watermark: string; public conversationIds = {}; constructor( botId, directLineSecret, whatsappServiceKey, whatsappServiceNumber, whatsappServiceUrl, whatsappServiceWebhookUrl ) { super(); this.botId = botId; this.whatsappServiceKey = whatsappServiceKey; this.whatsappServiceNumber = whatsappServiceNumber; this.whatsappServiceUrl = whatsappServiceUrl; this.whatsappServiceWebhookUrl = whatsappServiceWebhookUrl; 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') ); const options = { method: 'POST', url: urlJoin(this.whatsappServiceUrl, 'webhook'), qs: { token: this.whatsappServiceKey, webhookUrl: `${GBServer.globals.proxyAddress}/instances/${this.botId}/whatsapp`, set: true }, headers: { 'cache-control': 'no-cache' } }; try { const result = request.post(options); GBLog.info(result); } catch (error) { GBLog.error(`Error initializing 3rd party Whatsapp provider(1) ${error}`); } return client; }) .catch(err => { GBLog.error(`Error initializing 3rd party Whatsapp provider(2) ${err}`); }); } public received(req, res) { const text = req.body.messages[0].body; const from = req.body.messages[0].author.split('@')[0]; const fromName = req.body.messages[0].senderName; if (req.body.messages[0].fromMe) { return; // Exit here. } GBLog.info(`GBWhatsapp: Hook called. from: ${from}(${fromName}), text: ${text})`); const conversationId = this.conversationIds[from]; this.directLineClient.then(client => { if (this.conversationIds[from] === undefined) { GBLog.info(`GBWhatsapp: Starting new conversation on Bot.`); client.Conversations.Conversations_StartConversation() .then(response => { return response.obj.conversationId; }) .then(generatedConversationId => { this.conversationIds[from] = generatedConversationId; this.inputMessage(client, generatedConversationId, text, from, fromName); this.pollMessages(client, generatedConversationId, from, fromName); }) .catch(err => { GBLog.error(`Error starting conversation ${err}`); }); } else { this.inputMessage(client, conversationId, text, from, fromName); } res.end(); }); } public 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 => { GBLog.error(`GBWhatsapp: Error receiving message: ${err}.`); }); } public pollMessages(client, conversationId, from, fromName) { GBLog.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); } public printMessages(activities, conversationId, from, fromName) { if (activities && activities.length) { // Ignore own messages. activities = activities.filter(m => m.from.id === 'GeneralBots' && m.type === 'message'); if (activities.length) { // Print other messages. activities.forEach(activity => { this.printMessage(activity, conversationId, from, fromName); }); } } } public printMessage(activity, conversationId, from, fromName) { let output = ''; if (activity.text) { GBLog.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': GBLog.info(`Opening the requested image ${attachment.contentUrl}`); output += `\n${attachment.contentUrl}`; break; default: GBLog.info(`Unknown content type: ${attachment.contentType}`); } }); } this.sendToDevice(from, output); } public renderHeroCard(attachment) { return `${attachment.content.title} - ${attachment.content.text}`; } public async sendToDevice(to, msg) { const options = { method: 'POST', url: urlJoin(this.whatsappServiceUrl, 'message'), qs: { token: this.whatsappServiceKey, phone: to, body: msg }, headers: { 'cache-control': 'no-cache' } }; } }