UNPKG

botbuilder-adapter-tyntec-whatsapp

Version:

A Microsoft Bot Framework adapter for handling connectivity with the WhatsApp channel in tyntec Conversations API.

518 lines (517 loc) 31.2 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TyntecWhatsAppAdapter = void 0; const botbuilder_1 = require("botbuilder"); const axios_1 = require("./tyntec/axios"); class TyntecWhatsAppAdapter extends botbuilder_1.BotAdapter { constructor(settings) { super(); this.maxBodySize = 1024; this.axiosInstance = settings.axiosInstance; if (settings.maxBodySize !== undefined) { this.maxBodySize = settings.maxBodySize; } this.tyntecApikey = settings.tyntecApikey; } continueConversation(reference, logic) { const activity = botbuilder_1.TurnContext.applyConversationReference({ type: "event", name: "continueConversation" }, reference, true); const context = new botbuilder_1.TurnContext(this, activity); return this.runMiddleware(context, logic); } deleteActivity(context, reference) { return __awaiter(this, void 0, void 0, function* () { throw Error("Operation deleteActivity not supported."); }); } processActivity(req, res, logic) { return __awaiter(this, void 0, void 0, function* () { try { const requestBody = yield new Promise((resolve, reject) => { if (req.body !== undefined) { return resolve(req.body); } let requestJson = ''; req.on('data', (chunk) => { if (requestJson.length + chunk.length > this.maxBodySize) { reject(new Error(`Request body too large: > ${this.maxBodySize}`)); } requestJson += chunk; }); req.on('end', () => { try { resolve(JSON.parse(requestJson)); } catch (e) { reject(e); } }); }); const activity = yield this.parseTyntecWhatsAppMessageEvent({ body: requestBody, headers: req.headers, params: req.params, query: req.query }); const context = new botbuilder_1.TurnContext(this, activity); yield this.runMiddleware(context, logic); res.status(200); res.end(); } catch (e) { res.status(500); res.send(`Failed to process request: ${e}`); res.end(); } }); } sendActivities(context, activities) { return __awaiter(this, void 0, void 0, function* () { const responses = []; for (const activity of activities) { const tyntecRequest = this.composeTyntecWhatsAppMessageRequest(activity); const axiosRequest = (0, axios_1.composeTyntecSendWhatsAppMessageRequestConfig)(this.tyntecApikey, tyntecRequest); axiosRequest.validateStatus = () => true; const axiosResponse = yield this.axiosInstance.request(axiosRequest); const messageId = (0, axios_1.parseTyntecSendWhatsAppMessageResponse)(axiosResponse); responses.push({ id: messageId }); } return responses; }); } updateActivity(context, activity) { return __awaiter(this, void 0, void 0, function* () { throw Error("Operation updateActivity not supported."); }); } composeTyntecWhatsAppMessageRequest(activity) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r; if (activity.type !== botbuilder_1.ActivityTypes.Message) { throw Error(`TyntecWhatsAppAdapter: Activity.type other than ${botbuilder_1.ActivityTypes.Message} not supported: ${activity.type}`); } if (activity.attachmentLayout !== undefined) { console.warn(`TyntecWhatsAppAdapter: Activity.attachmentLayout not supported: ${activity.attachmentLayout}`); } if (activity.channelId !== "whatsapp") { throw Error(`TyntecWhatsAppAdapter: Activity.channelId other than whatsapp not supported: ${activity.channelId}`); } if (activity.conversation === undefined) { throw Error(`TyntecWhatsAppAdapter: Activity.conversation is required: ${activity.conversation}`); } if (activity.deliveryMode !== undefined) { throw Error(`TyntecWhatsAppAdapter: Activity.deliveryMode not supported: ${activity.deliveryMode}`); } if (activity.entities !== undefined) { console.warn(`TyntecWhatsAppAdapter: Activity.entities not supported: ${activity.entities}`); } if (activity.expiration !== undefined) { console.warn(`TyntecWhatsAppAdapter: Activity.expiration not supported: ${activity.expiration}`); } if (activity.from === undefined) { throw Error(`TyntecWhatsAppAdapter: Activity.from is required: ${activity.from}`); } if (activity.id !== undefined) { console.warn(`TyntecWhatsAppAdapter: Activity.id not supported: ${activity.id}`); } if (activity.importance !== undefined) { console.warn(`TyntecWhatsAppAdapter: Activity.importance not supported: ${activity.importance}`); } if (activity.inputHint !== undefined) { console.warn(`TyntecWhatsAppAdapter: Activity.inputHint not supported: ${activity.inputHint}`); } if (activity.listenFor !== undefined) { console.warn(`TyntecWhatsAppAdapter: Activity.listenFor not supported: ${activity.listenFor}`); } if (activity.locale !== undefined) { console.warn(`TyntecWhatsAppAdapter: Activity.locale not supported: ${activity.locale}`); } if (activity.replyToId !== undefined) { console.warn(`TyntecWhatsAppAdapter: Activity.replyToId not supported: ${activity.replyToId}`); } if (activity.semanticAction !== undefined) { console.warn(`TyntecWhatsAppAdapter: Activity.semanticAction not supported: ${activity.semanticAction}`); } if (activity.speak !== undefined) { throw Error(`TyntecWhatsAppAdapter: Activity.speak not supported: ${activity.speak}`); } if (activity.suggestedActions !== undefined) { console.warn(`TyntecWhatsAppAdapter: Activity.suggestedActions not supported: ${activity.suggestedActions}`); } if (activity.textFormat !== undefined) { throw Error(`TyntecWhatsAppAdapter: Activity.textFormat not supported: ${activity.textFormat}`); } if (((_a = activity.channelData) === null || _a === void 0 ? void 0 : _a.contentType) === undefined || ((_b = activity.channelData) === null || _b === void 0 ? void 0 : _b.contentType) === "text") { if (activity.attachments !== undefined) { throw Error(`TyntecWhatsAppAdapter: both text Activity.channelData.contentType and Activity.attachments not supported: ${(_c = activity.channelData) === null || _c === void 0 ? void 0 : _c.contentType} and ${JSON.stringify(activity.attachments)}`); } if (((_d = activity.channelData) === null || _d === void 0 ? void 0 : _d.contacts) !== undefined) { throw Error(`TyntecWhatsAppAdapter: both text Activity.channelData.contentType and Activity.channelData.contacts not supported: ${(_e = activity.channelData) === null || _e === void 0 ? void 0 : _e.contentType} and ${JSON.stringify((_f = activity.channelData) === null || _f === void 0 ? void 0 : _f.contacts)}`); } if (((_g = activity.channelData) === null || _g === void 0 ? void 0 : _g.interactive) !== undefined) { throw Error(`TyntecWhatsAppAdapter: both text Activity.channelData.contentType and Activity.channelData.interactive not supported: ${(_h = activity.channelData) === null || _h === void 0 ? void 0 : _h.contentType} and ${JSON.stringify((_j = activity.channelData) === null || _j === void 0 ? void 0 : _j.interactive)}`); } if (((_k = activity.channelData) === null || _k === void 0 ? void 0 : _k.location) !== undefined) { throw Error(`TyntecWhatsAppAdapter: both text Activity.channelData.contentType and Activity.channelData.location not supported: ${(_l = activity.channelData) === null || _l === void 0 ? void 0 : _l.contentType} and ${JSON.stringify((_m = activity.channelData) === null || _m === void 0 ? void 0 : _m.location)}`); } if (((_o = activity.channelData) === null || _o === void 0 ? void 0 : _o.template) !== undefined) { throw Error(`TyntecWhatsAppAdapter: both text Activity.channelData.contentType and Activity.channelData.template not supported: ${(_p = activity.channelData) === null || _p === void 0 ? void 0 : _p.contentType} and ${JSON.stringify((_q = activity.channelData) === null || _q === void 0 ? void 0 : _q.template)}`); } if (activity.text === undefined) { throw Error(`TyntecWhatsAppAdapter: text Activity.channelData.contentType requires Activity.text: ${activity.text}`); } return { from: activity.from.id, to: activity.conversation.id, channel: "whatsapp", content: { contentType: "text", text: activity.text } }; } if (activity.channelData.contentType === "audio" || activity.channelData.contentType === "document" || activity.channelData.contentType === "image" || activity.channelData.contentType === "sticker" || activity.channelData.contentType === "video") { if (activity.attachments === undefined || activity.attachments.length !== 1) { throw Error(`TyntecWhatsAppAdapter: other than exactly one Activity.attachments not supported: ${(_r = activity.attachments) === null || _r === void 0 ? void 0 : _r.length}`); } if (activity.attachments[0].content !== undefined) { throw Error(`TyntecWhatsAppAdapter: Activity.attachments.content not supported: ${activity.attachments[0].content}`); } if (activity.attachments[0].contentUrl === undefined) { throw Error(`TyntecWhatsAppAdapter: Activity.attachments.contentUrl is required: ${activity.attachments[0].contentUrl}`); } if (activity.attachments[0].thumbnailUrl !== undefined) { console.warn(`TyntecWhatsAppAdapter: Activity.attachments.thumbnailUrl not supported: ${activity.attachments[0].thumbnailUrl}`); } if (activity.channelData.contacts !== undefined) { throw Error(`TyntecWhatsAppAdapter: both media Activity.channelData.contentType and Activity.channelData.contacts not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.channelData.contacts)}`); } if (activity.channelData.interactive !== undefined) { throw Error(`TyntecWhatsAppAdapter: both media Activity.channelData.contentType and Activity.channelData.interactive not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.channelData.interactive)}`); } if (activity.channelData.location !== undefined) { throw Error(`TyntecWhatsAppAdapter: both media Activity.channelData.contentType and Activity.channelData.location not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.channelData.location)}`); } if (activity.channelData.template !== undefined) { throw Error(`TyntecWhatsAppAdapter: both media Activity.channelData.contentType and Activity.channelData.template not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.channelData.template)}`); } if (activity.channelData.contentType === "audio") { if (activity.text !== undefined) { throw Error(`TyntecWhatsAppAdapter: both audio Activity.channelData.contentType and Activity.text not supported: ${activity.channelData.contentType} and ${activity.text}`); } return { from: activity.from.id, to: activity.conversation.id, channel: "whatsapp", content: { audio: { url: activity.attachments[0].contentUrl }, contentType: "audio" } }; } if (activity.channelData.contentType === "document") { return { from: activity.from.id, to: activity.conversation.id, channel: "whatsapp", content: { contentType: "document", document: { caption: activity.text, filename: activity.attachments[0].name, url: activity.attachments[0].contentUrl } } }; } if (activity.channelData.contentType === "image") { return { from: activity.from.id, to: activity.conversation.id, channel: "whatsapp", content: { contentType: "image", image: { caption: activity.text, url: activity.attachments[0].contentUrl } } }; } if (activity.channelData.contentType === "sticker") { if (activity.text !== undefined) { throw Error(`TyntecWhatsAppAdapter: both sticker Activity.channelData.contentType and Activity.text not supported: ${activity.channelData.contentType} and ${activity.text}`); } return { from: activity.from.id, to: activity.conversation.id, channel: "whatsapp", content: { contentType: "sticker", sticker: { url: activity.attachments[0].contentUrl } } }; } if (activity.channelData.contentType === "video") { return { from: activity.from.id, to: activity.conversation.id, channel: "whatsapp", content: { contentType: "video", video: { caption: activity.text, url: activity.attachments[0].contentUrl } } }; } } if (activity.channelData.contentType === "contacts") { if (activity.attachments !== undefined) { throw Error(`TyntecWhatsAppAdapter: both contacts Activity.channelData.contentType and Activity.attachments not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.attachments)}`); } if (activity.channelData.contacts === undefined) { throw Error(`TyntecWhatsAppAdapter: contacts Activity.channelData.contentType requires Activity.channelData.contacts: ${activity.channelData.contacts}`); } if (activity.channelData.interactive !== undefined) { throw Error(`TyntecWhatsAppAdapter: both contacts Activity.channelData.contentType and Activity.channelData.interactive not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.channelData.interactive)}`); } if (activity.channelData.location !== undefined) { throw Error(`TyntecWhatsAppAdapter: both contacts Activity.channelData.contentType and Activity.channelData.location not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.channelData.location)}`); } if (activity.channelData.template !== undefined) { throw Error(`TyntecWhatsAppAdapter: both contacts Activity.channelData.contentType and Activity.channelData.template not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.channelData.template)}`); } if (activity.text !== undefined) { throw Error(`TyntecWhatsAppAdapter: both contacts Activity.channelData.contentType and Activity.text not supported: ${activity.channelData.contentType} and ${activity.text}`); } return { from: activity.from.id, to: activity.conversation.id, channel: "whatsapp", content: { contentType: "contacts", contacts: activity.channelData.contacts } }; } if (activity.channelData.contentType === "interactive") { if (activity.attachments !== undefined) { throw Error(`TyntecWhatsAppAdapter: both interactive Activity.channelData.contentType and Activity.attachments not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.attachments)}`); } if (activity.channelData.contacts !== undefined) { throw Error(`TyntecWhatsAppAdapter: both interactive Activity.channelData.contentType and Activity.channelData.contacts not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.channelData.contacts)}`); } if (activity.channelData.interactive === undefined) { throw Error(`TyntecWhatsAppAdapter: interactive Activity.channelData.contentType requires Activity.channelData.interactive: ${activity.channelData.interactive}`); } if (activity.channelData.location !== undefined) { throw Error(`TyntecWhatsAppAdapter: both interactive Activity.channelData.contentType and Activity.channelData.location not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.channelData.location)}`); } if (activity.channelData.template !== undefined) { throw Error(`TyntecWhatsAppAdapter: both interactive Activity.channelData.contentType and Activity.channelData.template not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.channelData.template)}`); } if (activity.text !== undefined) { throw Error(`TyntecWhatsAppAdapter: both interactive Activity.channelData.contentType and Activity.text not supported: ${activity.channelData.contentType} and ${activity.text}`); } return { from: activity.from.id, to: activity.conversation.id, channel: "whatsapp", content: { contentType: "interactive", interactive: activity.channelData.interactive } }; } if (activity.channelData.contentType === "location") { if (activity.attachments !== undefined) { throw Error(`TyntecWhatsAppAdapter: both location Activity.channelData.contentType and Activity.attachments not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.attachments)}`); } if (activity.channelData.contacts !== undefined) { throw Error(`TyntecWhatsAppAdapter: both location Activity.channelData.contentType and Activity.channelData.contacts not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.channelData.contacts)}`); } if (activity.channelData.interactive !== undefined) { throw Error(`TyntecWhatsAppAdapter: both location Activity.channelData.contentType and Activity.channelData.interactive not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.channelData.interactive)}`); } if (activity.channelData.location === undefined) { throw Error(`TyntecWhatsAppAdapter: location Activity.channelData.contentType requires Activity.channelData.location: ${activity.channelData.location}`); } if (activity.channelData.template !== undefined) { throw Error(`TyntecWhatsAppAdapter: both location Activity.channelData.contentType and Activity.channelData.template not supported: ${activity.channelData.contentType} and ${JSON.stringify(activity.channelData.template)}`); } if (activity.text !== undefined) { throw Error(`TyntecWhatsAppAdapter: both location Activity.channelData.contentType and Activity.text not supported: ${activity.channelData.contentType} and ${activity.text}`); } return { from: activity.from.id, to: activity.conversation.id, channel: "whatsapp", content: { contentType: "location", location: activity.channelData.location } }; } if (activity.channelData.contentType === "template") { if (activity.attachments !== undefined) { throw Error(`TyntecWhatsAppAdapter: both Activity.attachments and template Activity.channelData.contentType not supported: ${JSON.stringify(activity.attachments)} and ${activity.channelData.contentType}`); } if (activity.channelData.contacts !== undefined) { throw Error(`TyntecWhatsAppAdapter: both Activity.channelData.contacts and template Activity.channelData.contentType not supported: ${JSON.stringify(activity.channelData.contacts)} and ${activity.channelData.contentType}`); } if (activity.channelData.interactive !== undefined) { throw Error(`TyntecWhatsAppAdapter: both Activity.channelData.interactive and template Activity.channelData.contentType not supported: ${JSON.stringify(activity.channelData.interactive)} and ${activity.channelData.contentType}`); } if (activity.channelData.location !== undefined) { throw Error(`TyntecWhatsAppAdapter: both Activity.channelData.location and template Activity.channelData.contentType not supported: ${JSON.stringify(activity.channelData.location)} and ${activity.channelData.contentType}`); } if (activity.channelData.template === undefined) { throw Error(`TyntecWhatsAppAdapter: template Activity.channelData.contentType requires Activity.channelData.template: ${activity.channelData.template}`); } if (activity.text !== undefined) { throw Error(`TyntecWhatsAppAdapter: both Activity.text and template Activity.channelData.contentType not supported: ${activity.text} and ${activity.channelData.contentType}`); } return { from: activity.from.id, to: activity.conversation.id, channel: "whatsapp", content: { contentType: "template", template: activity.channelData.template } }; } throw Error(`TyntecWhatsAppAdapter: invalid input: ${activity}`); } // Deprecated since version 1.3.0. Use parseTyntecWebhookRequest instead. parseTyntecWhatsAppMessageEvent(req) { return __awaiter(this, void 0, void 0, function* () { return this.parseTyntecWebhookRequest(req); }); } parseTyntecWebhookRequest(req) { return __awaiter(this, void 0, void 0, function* () { if (req.body.event === "MoMessage") { return this.parseTyntecWebhookWhatsAppMoMessage(req.body); } if (req.body.event === "MoMessage::Postback") { return this.parseTyntecWebhookMoMessagePostback(req.body); } throw Error(`TyntecWhatsAppAdapter: ITyntecMoMessage.event other than MoMessage or MoMessage::Postback not supported: ${req.body.event}`); }); } parseTyntecWebhookWhatsAppMoMessage(message) { var _a, _b, _c, _d; return __awaiter(this, void 0, void 0, function* () { if (message.content.contentType === "media" && (message.content.media.type !== "audio" && message.content.media.type !== "document" && message.content.media.type !== "image" && message.content.media.type !== "sticker" && message.content.media.type !== "video" && message.content.media.type !== "voice")) { throw Error(`TyntecWhatsAppAdapter: ITyntecMoMessage.content.media.type other than audio, document, image, sticker and video not supported: ${message.content.media.type}`); } if (message.groupId !== undefined) { throw Error(`TyntecWhatsAppAdapter: ITyntecMoMessage.groupId not supported: ${message.groupId}`); } if (message.to === undefined) { throw Error(`TyntecWhatsAppAdapter: ITyntecMoMessage.to is required: ${message.to}`); } const recipient = { id: message.to }; const activity = Object.assign(Object.assign({}, this.parseTyntecWebhookAPIEvent(message)), { channelData: {}, id: message.messageId, recipient: recipient, replyToId: (_a = message.context) === null || _a === void 0 ? void 0 : _a.messageId, type: botbuilder_1.ActivityTypes.Message }); if ((_b = message.whatsapp) === null || _b === void 0 ? void 0 : _b.senderName) { const conversation = activity.conversation; conversation.name = (_c = message.whatsapp) === null || _c === void 0 ? void 0 : _c.senderName; const from = activity.from; from.name = (_d = message.whatsapp) === null || _d === void 0 ? void 0 : _d.senderName; } if (message.content.contentType === "text") { activity.channelData.contentType = "text"; activity.text = message.content.text; return activity; } if (message.content.contentType === "contacts") { activity.channelData.contentType = "contacts"; activity.channelData.contacts = message.content.contacts; return activity; } if (message.content.contentType === "location") { activity.channelData.contentType = "location"; activity.channelData.location = message.content.location; return activity; } if (message.content.contentType === "media") { const mediaRequest = (0, axios_1.composeTyntecRequestConfig)("get", message.content.media.url, this.tyntecApikey, "*/*"); mediaRequest.validateStatus = () => true; let mediaResponse; try { mediaResponse = yield this.axiosInstance.request(mediaRequest); } catch (e) { throw new Error(`Failed to download media: ${e.response.status}: ${JSON.stringify(e.response.data)}`); } activity.attachments = [ { contentType: mediaResponse.headers["content-type"], contentUrl: message.content.media.url } ]; activity.channelData.contentType = message.content.media.type; activity.text = message.content.media.caption; return activity; } throw Error(`TyntecWhatsAppAdapter: invalid input: ${JSON.stringify(message)}`); }); } parseTyntecWebhookMoMessagePostback(message) { var _a; return __awaiter(this, void 0, void 0, function* () { if (message.groupId !== undefined) { throw Error(`TyntecWhatsAppAdapter: ITyntecMoMessage.groupId not supported: ${message.groupId}`); } if (message.to === undefined) { throw Error(`TyntecWhatsAppAdapter: ITyntecMoMessage.to is required: ${message.to}`); } const recipient = { id: message.to }; const activity = Object.assign(Object.assign({}, this.parseTyntecWebhookAPIEvent(message)), { channelData: { contentType: "postback", postback: { data: message.postback.data } }, id: message.messageId, recipient: recipient, replyToId: (_a = message.context) === null || _a === void 0 ? void 0 : _a.messageId, type: botbuilder_1.ActivityTypes.Message }); if (message.whatsapp) { activity.channelData.postback.whatsapp = { description: message.whatsapp.description, text: message.whatsapp.text, title: message.whatsapp.title }; } return activity; }); } parseTyntecWebhookAPIEvent(event) { const tyntecSendWhatsAppMessageRequestConfig = (0, axios_1.composeTyntecSendWhatsAppMessageRequestConfig)(this.tyntecApikey, { from: "example", to: "example", channel: "whatsapp", content: { contentType: "text", text: "example" } }); const conversation = { id: event.from, isGroup: false }; const from = { id: event.from }; return { channelId: event.channel, conversation: conversation, from: from, serviceUrl: tyntecSendWhatsAppMessageRequestConfig.url, timestamp: event.timestamp !== undefined ? new Date(event.timestamp) : undefined }; } } exports.TyntecWhatsAppAdapter = TyntecWhatsAppAdapter;