diff --git a/endpoint/webhook/tyntec-whatsapp/README.md b/endpoint/webhook/tyntec-whatsapp/README.md index c652629..e92b5c0 100644 --- a/endpoint/webhook/tyntec-whatsapp/README.md +++ b/endpoint/webhook/tyntec-whatsapp/README.md @@ -11,20 +11,11 @@ This [Transformer Function](https://docs.cognigy.com/docs/transformers) converts - [Tyntec - WhatsApp](#tyntec---whatsapp) - [Provided Message Types](#provided-message-types) - - [Cognigy.AI Version 4](#cognigyai-version-4) - [Text](#text) - [Media Attachments](#media-attachments) - [Gallery](#gallery) - [Quick Reply](#quick-reply) - [Location](#location) - - [Cognigy.AI Version 3](#cognigyai-version-3) - - [Text](#text-1) - - [Attachments](#attachments) - - [Gallery](#gallery-1) - - [Quick Reply](#quick-reply-1) - - [Location](#location-1) - -## Cognigy.AI Version 4 ### Text @@ -72,55 +63,4 @@ Use the **Text** type of the SAY Node and define a data only message (No Text), This will be displayed like this: - - - -## Cognigy.AI Version 3 - -### Text - -Use the **Default** tab of the SAY Node. - - - -### Attachments - -Use the **Webchat** tab of the SAY Node and define an **Attachment**. - - - -### Gallery - -Use the **Webchat** tab of the SAY Node and define a **Gallery**. - -**IMPORTANT**: -You have to define an image for each gallery item! - - - -### Quick Reply - -Use the **Webchat** tab of the SAY Node and define a **Quick Reply**. - - - -**Note**: WhatsApp will display the quick replies as text messages with a trailing number, such as `1. first quick reply`. The user can either type in the number or the text of the quick reply to send it back to the AI. - -### Location - -Use the **Default** tab of the SAY Node and define a data only message (No Text), where the data has to look like the following: - -```json -{ - "location": { - "longitude": -122.747986, - "latitude": 37.989981, - "name": "Your Location", - "address": "Shoreline Highway, CA 1, California" - } -} -``` - -This will be displayed like this: - - + \ No newline at end of file diff --git a/endpoint/webhook/tyntec-whatsapp/docs/quickReplies.png b/endpoint/webhook/tyntec-whatsapp/docs/quickReplies.png index 14e1691..749b073 100644 Binary files a/endpoint/webhook/tyntec-whatsapp/docs/quickReplies.png and b/endpoint/webhook/tyntec-whatsapp/docs/quickReplies.png differ diff --git a/endpoint/webhook/tyntec-whatsapp/transformer.ts b/endpoint/webhook/tyntec-whatsapp/transformer.ts index c5ad973..a85c937 100644 --- a/endpoint/webhook/tyntec-whatsapp/transformer.ts +++ b/endpoint/webhook/tyntec-whatsapp/transformer.ts @@ -10,11 +10,13 @@ * } */ +// const TYNTEC_API_KEY = "PYvD7k3JEzaD6rZwvky7zaLXSt5AluUy"; // Tyntec API Key + const TYNTEC_API_KEY = ""; // Tyntec API Key //session timeout in seconds, new session gets generated afterwards //disable by setting to 0 -const SESSION_TIMEOUT = 1800 +const SESSION_TIMEOUT = 1800; const HIDE_USER_ID = true const HIDE_SESSION_ID = true @@ -53,6 +55,28 @@ interface IWhatsAppLocation { address: string; } +interface IWhatsAppQuickReplyMessage extends IWhatsAppMessageBase { + contentType: 'interactive'; + interactive: { + subType: 'buttons'; + components: { + body: { + type: 'text'; + text: string; + }, + buttons: IWhatsAppQuickReply[]; + } + } +} + +interface IWhatsAppQuickReply { + type: 'reply'; + reply: { + payload: string; + title: string; + } +} + interface IWhatsAppTemplateMessage extends IWhatsAppMessageBase { contentType: 'template'; template: { @@ -65,6 +89,17 @@ interface IWhatsAppTemplateMessage extends IWhatsAppMessageBase { }; } +interface IWhatsAppListSection { + title: string; + rows: IWhatsAppListSectionRow[] +} + +interface IWhatsAppListSectionRow { + payload: string; + title: string; + description: string; +} + type TWhatsAppTemplateComponent = IWhatsAppTemplateHeaderComponent | IWhatsAppTemplateBodyComponent | IWhatsAppTemplateButtonComponent; interface IWhatsAppTemplateHeaderComponent { @@ -94,7 +129,7 @@ interface IWhatsAppTemplateComponentTextParameter { text: string; } -type TWhatsAppContent = IWhatsAppTextMessage | IWhatsAppMediaMessage | IWhatsAppTemplateMessage | IWhatsAppLocationMessage; +type TWhatsAppContent = IWhatsAppTextMessage | IWhatsAppMediaMessage | IWhatsAppTemplateMessage | IWhatsAppLocationMessage | IWhatsAppQuickReplyMessage | any; /** * Webchat Interface @@ -111,34 +146,24 @@ interface ISessionStorageQuickReply { quickReply: IDefaultQuickReply; } -const createWhatsAppQuickReplies = (quickReplies: IDefaultQuickReply[], sessionStorage: any): string => { - - // get previous quick replies from session storage - let sessionquickReplyCurrentNumber: number = sessionStorage.quickReplyCurrentNumber || 0; - let sessionQuickReplies: ISessionStorageQuickReply[] = sessionStorage.quickReplies || []; +const createWhatsAppQuickReplies = (quickReplies: IDefaultQuickReply[]): IWhatsAppQuickReply[] => { - // initialize empty text message bubble - let whatsAppQuickReplyMessage: string = ""; + let whatsAppQuickReplies: IWhatsAppQuickReply[] = []; for (let quickReply of quickReplies) { - // store the index to the session storage for further quick replies - sessionquickReplyCurrentNumber += 1; - sessionQuickReplies.push({ - index: sessionquickReplyCurrentNumber, - quickReply - }) - // add the quick reply to the text message bubble - // Example: 1. first quick reply - whatsAppQuickReplyMessage += `\n${sessionquickReplyCurrentNumber}. ${quickReply.title}`; - + whatsAppQuickReplies.push({ + type: "reply", + reply: { + payload: quickReply.payload, + title: quickReply.title + } + }); } - sessionStorage.quickReplyCurrentNumber = sessionquickReplyCurrentNumber; - sessionStorage.quickReplies = sessionQuickReplies; - - return whatsAppQuickReplyMessage; + return whatsAppQuickReplies.slice(0,3); } + const convertWebchatContentToWhatsApp = (output, sessionId: string, sessionStorage: any): TWhatsAppContent[] => { // create list for whatsapp content @@ -204,11 +229,19 @@ const convertWebchatContentToWhatsApp = (output, sessionId: string, sessionStora if (element.buttons?.length) { const galleryItemQuickReplies = element.buttons; - // create quick replies message as message bubble whatsAppContents.push({ from: sessionId, - contentType: "text", - text: createWhatsAppQuickReplies(galleryItemQuickReplies, sessionStorage) + contentType: "interactive", + interactive: { + subType: "buttons", + components: { + body: { + type: "text", + text: "Please select:" + }, + buttons: createWhatsAppQuickReplies(galleryItemQuickReplies) + } + } }); } } @@ -219,18 +252,19 @@ const convertWebchatContentToWhatsApp = (output, sessionId: string, sessionStora let text: string = defaultContent._quickReplies.text; let quickReplies: IDefaultQuickReply[] = defaultContent._quickReplies.quickReplies; - // create quick reply title message whatsAppContents.push({ from: sessionId, - contentType: "text", - text: text - }); - - // create quick replies message as message bubble - whatsAppContents.push({ - from: sessionId, - contentType: "text", - text: createWhatsAppQuickReplies(quickReplies, sessionStorage) + contentType: "interactive", + interactive: { + subType: "buttons", + components: { + body: { + type: "text", + text + }, + buttons: createWhatsAppQuickReplies(quickReplies) + } + } }); } @@ -279,22 +313,8 @@ const convertWebchatContentToWhatsApp = (output, sessionId: string, sessionStora } createWebhookTransformer({ - - /** - * This transformer is executed when receiving a message - * from the user, before executing the Flow. - * - * @param endpoint The configuration object for the used Endpoint. - * @param request The Express request object with a JSON parsed body. - * @param response The Express response object. - * - * @returns A valid userId, sessionId, as well as text and/or data, - * which has been extracted from the request body. - */ handleInput: async ({ endpoint, request, response }) => { - console.log(JSON.stringify(request.body)); - // handle accepted Tyntec WhatsApp messages if (request.body.status) { response.sendStatus(200); @@ -342,26 +362,16 @@ createWebhookTransformer({ processedSessionStorage.clearUserId = clearUserId processedSessionStorage.clearSessionId = clearSessionId - let text = request.body.content.text; + let text: string; const data = request.body; - // check if the user chose a quick reply by inserting a number that fits a stored reply - let sessionQuickReplies: ISessionStorageQuickReply[] = processedSessionStorage.quickReplies || []; - - // compare session quick replies with user input text and check if there is a stored quick reply that should be triggered by the current user input text - for (let sessionQuickReply of sessionQuickReplies) { - // the user can send the number or the title of a quick reply - if (text.toLowerCase().includes(sessionQuickReply.index) || text.toLowerCase().includes(sessionQuickReply.quickReply.title.toLowerCase())) { - if (sessionQuickReply.quickReply.contentType === "trigger_intent") { - text = sessionQuickReply.quickReply.title; - } else { - text = sessionQuickReply.quickReply.payload; - } - } + // Check if quick reply button was clicked + if (request?.body?.postback?.data) { + text = request.body.postback.data; + } else { + text = request.body.content.text; } - - return { userId, sessionId, @@ -369,25 +379,6 @@ createWebhookTransformer({ data }; }, - - /** - * This transformer is executed on every output from the Flow. - * For Webhook based transformers, the return value of this transformer - * will be sent directly to the user. - * - * @param processedOutput The output from the Flow that has been processed into the final object - * that will be sent to the user. It is structured according to the data structure used - * on the specific Endpoint channel. - * - * @param output The raw output from the Flow. - * @param endpoint The configuration object for the used Endpoint. - * @param userId The unique ID of the user. - * @param sessionId The unique ID for this session. Can be used together with the userId - * to retrieve the sessionStorage object. - * - * @returns An object that will be sent to the user, unchanged. It therefore has to have the - * correct format according to the documentation of the specific Endpoint channel. - */ handleOutput: async ({ processedOutput, output, endpoint, userId, sessionId }) => { //create output transformer translation storage const processedSessionStorage = await getSessionStorage(userId, sessionId); @@ -403,13 +394,12 @@ createWebhookTransformer({ let whatsapp: TWhatsAppContent[] = convertWebchatContentToWhatsApp(output, clearSessionId, processedSessionStorage); if (!whatsapp.length) { console.error("Missing WhatsApp compatible channel output!"); - console.log(JSON.stringify(output)) return } - let result; - // decide whether to use the bulks or messages API. If there is only one message, use the messages API. + + // Decide whether to use the bulks or messages API. If there is only one message, use the messages API. if (whatsapp.length === 1) { - result = await httpRequest({ + await httpRequest({ uri: "https://api.tyntec.com/chat-api/v2/messages", method: "POST", headers: { @@ -427,7 +417,7 @@ createWebhookTransformer({ json: true }); } else { - result = await httpRequest({ + await httpRequest({ uri: "https://api.tyntec.com/chat-api/v2/bulks", method: "POST", headers: { @@ -444,41 +434,10 @@ createWebhookTransformer({ json: true }); } - console.log(result); return null; - - return processedOutput; }, - - /** - * This transformer is executed when the Flow execution has finished. - * Since all outputs have been sent to the user, this transformer does not return anything. - * - * @param userId The unique ID of the user. - * @param sessionId The unique ID for this session. Can be used together with the userId - * to retrieve the sessionStorage object. - * - * @param endpoint The configuration object for the used Endpoint. - * - * @returns This transformer cannot return anything. - */ handleExecutionFinished: async ({ sessionId, userId, endpoint }) => { - }, - - /** - * This transformer is executed when receiving an inject event. - * The extracted text and data will be injected into the conversation - * for the given user in the given session. - * - * @param request The Express request object with a JSON parsed body. - * @param response The Express response object. - * @param endpoint The configuration object for the used Endpoint. - * - * @returns A valid userId, sessionId, as well as text and/or data, - * which has been extracted from the request body. The text and data - * will be injected into this conversation. - */ handleInject: async ({ request, response, endpoint }) => { /** @@ -491,10 +450,12 @@ createWebhookTransformer({ * every Endpoint, and the example above needs to be adjusted * accordingly. */ - const userId = ""; - const sessionId = ""; - const text = ""; - const data = {} + const userId = undefined; + const sessionId = undefined; + const text = undefined; + const data = { + timeout: true + } return { userId, @@ -503,20 +464,6 @@ createWebhookTransformer({ data }; }, - - /** - * This transformer is executed when receiving a notify event. - * The extracted text and data will be sent directly to the - * given user in the given session as a notification. - * - * @param request The Express request object with a JSON parsed body. - * @param response The Express response object. - * @param endpoint The configuration object for the used Endpoint. - * - * @returns A valid userId, sessionId, as well as text and/or data, - * which has been extracted from the request body. The text and data - * will be sent directly to the user. - */ handleNotify: async ({ request, response, endpoint }) => { /**