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
JavaScript
;
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;