From fd85529e44cefa4c06e07430f208de239e561d9c Mon Sep 17 00:00:00 2001 From: Soufyan Bargach Date: Fri, 9 Dec 2022 14:13:15 +0100 Subject: [PATCH 1/5] Add missing models to send WhatsApp interactive messages plus ABC List pickers --- CHANGELOG.md | 11 + README.md | 49 ++++ lib/MessageApiClient.ts | 11 +- package-lock.json | 4 +- package.json | 2 +- spec/api.spec.ts | 49 ++++ typescript-node-client/api.ts | 473 +++++++++++++++++++++++++++++++++- 7 files changed, 594 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cd3f86..05aa659 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## [1.4.0] - 2022-12-09 +### Added +- Add missing models to send WhatsApp interactive messages +- Add missing models to send ABC listpicker messages. + +## [1.3.6] - 2022-12-05 +### Added +- Support for the Telegram channel +### Updated +- Several dependency updates. + ## [1.3.6] - 2022-12-05 ### Added - Support for the Telegram channel diff --git a/README.md b/README.md index 9a9520c..ee6d374 100644 --- a/README.md +++ b/README.md @@ -96,5 +96,54 @@ response.then((result) => { }); ``` +or send whatsapp interactive messages using the message builder +```javascript +const whatsAppInteractiveContent = { + type: 'list', + header: { + type: "text", + text: "your-header-content" + }, + body: { + text: "your-text-message-content" + }, + footer: { + text: "your-footer-content" + }, + action: { + button: "cta-button-content", + sections: [{ + title: "your-section-title1", + rows: [{ + id: "unique-row-identifier1", + title: "row-title-content", + description: "row-description-content" + }] + }, + { + title: "your-section-title2", + rows: [{ + id: "unique-row-identifier2", + title: "row-title-content", + description: "row-description-content" + }] + } + ] + } +}; + +const response = client.createMessage() + .setMessage(["00316012345678"], "TestSender", "Hello world?!") + .setAllowedChannels(["WhatsApp"]) + .setInteractive(whatsAppInteractiveContent) + .send(); + +response.then((result) => { + console.log(result); +}).catch((error) => { + console.log(error); +}); +``` + ### License @cmdotcom/text-sdk is under the MIT license. See LICENSE file. diff --git a/lib/MessageApiClient.ts b/lib/MessageApiClient.ts index 3a70a3f..f579eee 100644 --- a/lib/MessageApiClient.ts +++ b/lib/MessageApiClient.ts @@ -6,7 +6,7 @@ export type RichMessage = CMTypes.RichMessage; export type Suggestion = CMTypes.Suggestion; export type Template = CMTypes.Template; export type MessagesResponse = CMTypes.MessagesResponse; - +export type WhatsAppInteractive = CMTypes.WhatsAppInteractive; /** * Message client for the CM.com Platform */ @@ -142,6 +142,15 @@ export class Message extends CMTypes.MessageEnvelope { return this; } + /** + * Sets the WhatsAppInteractive Message + * @param template template definition and usage object + */ + public setInteractive(interactive: WhatsAppInteractive): Message { + this.getRichContent().conversation = [{ interactive: interactive }]; + return this; + } + /** * Sends the message to the CM.com Platform */ diff --git a/package-lock.json b/package-lock.json index 687115f..2a2bb17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cmdotcom/text-sdk", - "version": "1.3.6", + "version": "1.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cmdotcom/text-sdk", - "version": "1.3.6", + "version": "1.4.0", "license": "MIT", "dependencies": { "bluebird": "~3.7.2", diff --git a/package.json b/package.json index b05643f..dc2593d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cmdotcom/text-sdk", - "version": "1.3.6", + "version": "1.4.0", "description": "Package to make it very easy to send text messages with CM.com", "keywords": [ "cm", diff --git a/spec/api.spec.ts b/spec/api.spec.ts index 4426508..2100351 100644 --- a/spec/api.spec.ts +++ b/spec/api.spec.ts @@ -170,4 +170,53 @@ describe("MessageApiClient+MessageBuilder", () => { return response.body.details === "Created 1 message(s)"; }); }); + + const whatsAppInteractiveContent = { + type: 'list', + header: { + type: "text", + text: "your-header-content" + }, + body: { + text: "your-text-message-content" + }, + footer: { + text: "your-footer-content" + }, + action: { + button: "cta-button-content", + sections: [{ + title: "your-section-title1", + rows: [{ + id: "unique-row-identifier1", + title: "row-title-content", + description: "row-description-content" + }] + }, + { + title: "your-section-title2", + rows: [{ + id: "unique-row-identifier2", + title: "row-title-content", + description: "row-description-content" + }] + } + ] + } + }; + + it("should create a valid http(s) request, when using the message-builder with a interactive WhatsApp message", () => { + const yourProductToken = "dddd"; + const client = new MessageApiClient(yourProductToken); + + const response = client.createMessage() + .setMessage(["00316012345678"], "TestSender", "Hello world?!") + .setAllowedChannels(["WhatsApp"]) + .setInteractive(whatsAppInteractiveContent) + .send(); + + expect(response).to.be.eventually.fulfilled.and.to.satisfy((response) => { + return response.body.details === "Created 1 message(s)"; + }); + }); }); \ No newline at end of file diff --git a/typescript-node-client/api.ts b/typescript-node-client/api.ts index 99695ce..88d7fe9 100644 --- a/typescript-node-client/api.ts +++ b/typescript-node-client/api.ts @@ -136,6 +136,446 @@ class ObjectSerializer { } } +/** +* Contains information for a listpicker +*/ +export class ListPicker { + /** + * The label which will be shown to the end user to describe the contents of the list picker. + */ + 'Label'?: string; + + /** + * An image, which will be shown to the end user to show information about the list picker + */ + 'Media'?: Media; + + /** + * The items which the end users can choose + */ + 'Options'?: Array; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "Label", + "baseName": "Label", + "type": "string" + }, + { + "name": "Media", + "baseName": "Media", + "type": "Media" + }, + { + "name": "Options", + "baseName": "Options", + "type": "Array" + }]; + + static getAttributeTypeMap() { + return ListPicker.attributeTypeMap; + } +} + +/** +* Describes one item in a ListPicker/>. +*/ +export class ListItem { + /** + * The label which will be shown to the end user to describe the item. + */ + 'Label'?: string; + + /** + * An image, which will be shown to the end user to show information about the list item + */ + 'media'?: Media + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{ name: string, baseName: string, type: string }> = [ + { + "name": "Label", + "baseName": "Label", + "type": "string" + }, + { + "name": "media", + "baseName": "media", + "type": "media" + }]; + + static getAttributeTypeMap() { + return ListItem.attributeTypeMap; + } +} + +/** +* WhatsApp Interactive Messages />. +*/ +export class WhatsAppInteractive { + /** + * The Type that will be used, + * either list or button + */ + 'type'?: string; + + /** + * Your message’s header. + */ + 'header'?: InteractiveHeader + + /** + * Required Your message’s body. + */ + 'body'?: InteractiveBody + + /** + * Required Your message’s footer. + */ + 'footer'?: InteractiveFooter + + /** + * Required. Inside action, you must nest: + * a button field with your button’s content, and + * at least one section object (maximum of 10). + */ + 'action'?: InteractiveAction + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{ name: string, baseName: string, type: string }> = [ + { + "name": "type", + "baseName": "type", + "type": "string" + }, + { + "name": "header", + "baseName": "header", + "type": "InteractiveHeader" + }, + { + "name": "footer", + "baseName": "footer", + "type": "InteractiveBody" + }, + { + "name": "header", + "baseName": "header", + "type": "InteractiveFooter" + }, + { + "name": "action", + "baseName": "action", + "type": "InteractiveAction" + }]; + + static getAttributeTypeMap() { + return WhatsAppInteractive.attributeTypeMap; + } +} + +/** +* Part of WhatsApp interactive mesage +*/ +export class InteractiveHeader { + /** + * Required. The header type you would like to use.Supported values are: + * text: Used for List Messages and Reply Buttons. + */ + 'type'?: string; + + /** + * Required if type is set to text. + * Text for the header.Formatting allows emojis, but not markdown. + */ + 'text'?: string; + + /** + * Required if type is set to text. + * Text for the header.Formatting allows emojis, but not markdown. + */ + 'media'?: Media; + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{ name: string, baseName: string, type: string }> = [ + { + "name": "type", + "baseName": "type", + "type": "string" + }, + { + "name": "text", + "baseName": "text", + "type": "string" + }, + { + "name": "media", + "baseName": "media", + "type": "media" + }]; + + static getAttributeTypeMap() { + return InteractiveHeader.attributeTypeMap; + } +} + +/** +* Part of WhatsApp interactive mesage +*/ +export class InteractiveBody { + /** + * The body content of the message. + * Emojis and markdown are supported. Links are supported. + */ + 'text'?: string; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{ name: string, baseName: string, type: string }> = [ + { + "name": "text", + "baseName": "text", + "type": "string" + }]; + + static getAttributeTypeMap() { + return InteractiveBody.attributeTypeMap; + } +} + +/** +* Part of WhatsApp interactive mesage +*/ +export class InteractiveFooter { + /** + * The footer content. Emojis and markdown are supported. Links are supported. + * Maximum length: 60 characters + */ + 'text'?: string; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{ name: string, baseName: string, type: string }> = [ + { + "name": "text", + "baseName": "text", + "type": "string" + }]; + static getAttributeTypeMap() { + return InteractiveFooter.attributeTypeMap; + } +} + +/** +* Part of WhatsApp interactive mesage +*/ +export class InteractiveAction { + /** + * Required for List Messages. + * Button content. It cannot be an empty string and must be unique within the message + * Does not allow emojis or markdown. + */ + 'button'?: string; + /** + * Required for Reply Button Messages. + */ + 'buttons'?: Array; + /** + * Required for List Messages. + */ + 'sections'?: Array; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{ name: string, baseName: string, type: string }> = [ + { + "name": "button", + "baseName": "button", + "type": "button" + }, + { + "name": "buttons", + "baseName": "buttons", + "type": "Array" + }, + { + "name": "sections", + "baseName": "sections", + "type": "Array" + }]; + + static getAttributeTypeMap() { + return InteractiveAction.attributeTypeMap; + } +} + +/** +* Part of WhatsApp interactive mesage +*/ +export class InteractiveButton { + /** + * type: only supported type is reply (for Reply Button Messages). + */ + 'type'?: string; + + /** + * Button title.It cannot be an empty string and must be unique within the message. + * Does not allow emojis or markdown. Maximum length: 20 characters. + */ + 'title'?: string; + + /** + * id: Unique identifier for your button. + * This ID is returned in the webhook when the button is clicked by the user. + */ + 'id'?: string; + + /** + * Reply Message for your button. + */ + 'reply'?: string; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{ name: string, baseName: string, type: string }> = [ + { + "name": "type", + "baseName": "type", + "type": "string" + }, + { + "name": "title", + "baseName": "title", + "type": "string" + }, + { + "name": "id", + "baseName": "id", + "type": "string" + }, + { + "name": "reply", + "baseName": "reply", + "type": "string" + }]; + + static getAttributeTypeMap() { + return InteractiveButton.attributeTypeMap; + } +} + +/** +* Part of WhatsApp interactive mesage +*/ +export class ReplyMessage { + /** + * The options to select. + */ + 'id'?: string; + + /** + * The options to select. + */ + 'title'?: string; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{ name: string, baseName: string, type: string }> = [ + { + "name": "id", + "baseName": "id", + "type": "string" + }, + { + "name": "title", + "baseName": "title", + "type": "string" + }]; + + static getAttributeTypeMap() { + return ReplyMessage.attributeTypeMap; + } +} + +/** +* Part of WhatsApp interactive mesage +*/ +export class InteractiveSection { + /** + * Title of the row.. + */ + 'title'?: string; + + /** + * Contains a list of rows. + */ + 'rows'?: Array; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{ name: string, baseName: string, type: string }> = [ + { + "name": "title", + "baseName": "title", + "type": "string" + }, + { + "name": "rows", + "baseName": "rows", + "type": "Array" + }]; + + static getAttributeTypeMap() { + return InteractiveSection.attributeTypeMap; + } +} + +/** +* Part of WhatsApp interactive mesage +*/ +export class Rows { + /** + * Title of the row. . + */ + 'title'?: string; + + /** + * Id of the row.. + */ + 'id'?: string; + /** + * Description of the row. + */ + 'description'?: string; + + static discriminator: string | undefined = undefined; + + static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ + { + "name": "title", + "baseName": "title", + "type": "string" + }, + { + "name": "id", + "baseName": "id", + "type": "string" + }, + { + "name": "description", + "baseName": "description", + "type": "string" + }]; + + static getAttributeTypeMap() { + return Rows.attributeTypeMap; + } +} + /** * Contains information for a {CM.Messaging.RCSModels.Models.Suggestion.Calendar} (RCS). */ @@ -989,6 +1429,16 @@ export class RichMessage { */ 'oauth2'?: OAuthMessage; + /** + * Used to send an WhatsApp interactive message. + */ + 'interactive'?: WhatsAppInteractive; + + /** + * Used to send an ABC listpicker message. + */ + 'listPicker'?: ListPicker; + static discriminator: string | undefined = undefined; static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ @@ -1036,7 +1486,17 @@ export class RichMessage { "name": "oauth2", "baseName": "oauth2", "type": "OAuthMessage" - } ]; + }, + { + "name": "interactive", + "baseName": "interactive", + "type": "WhatsAppInteractive" + } , + { + "name": "listPicker", + "baseName": "listPicker", + "type": "ListPicker" + } ]; static getAttributeTypeMap() { return RichMessage.attributeTypeMap; @@ -1781,6 +2241,17 @@ let typeMap: {[index: string]: any} = { "Messages": Messages, "MessagesResponse": MessagesResponse, "Recipient": Recipient, + "ListPicker": ListPicker, + "ListItem": ListItem, + "WhatsAppInteractive": WhatsAppInteractive, + "InteractiveHeader": InteractiveHeader, + "InteractiveBody": InteractiveBody, + "InteractiveFooter": InteractiveFooter, + "InteractiveAction": InteractiveAction, + "ReplyMessage": ReplyMessage, + "InteractiveSection": InteractiveSection, + "InteractiveButton": InteractiveButton, + "Rows": Rows } export interface Authentication { From 2416f46bcd7ae538e9de8ed4ac76afac6824fad1 Mon Sep 17 00:00:00 2001 From: Soufyan Bargach Date: Fri, 9 Dec 2022 15:03:03 +0100 Subject: [PATCH 2/5] update changelog --- CHANGELOG.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05aa659..3c3d578 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,6 @@ ### Updated - Several dependency updates. -## [1.3.6] - 2022-12-05 -### Added -- Support for the Telegram channel -### Updated -- Several dependency updates. - ## [1.3.5] - 2021-07-06 ### Added - Support for the Instagram channel From b5f8b4b74ea27ab0cf0db345ca2633f1c8c77e2b Mon Sep 17 00:00:00 2001 From: Soufyan Bargach Date: Fri, 9 Dec 2022 15:48:55 +0100 Subject: [PATCH 3/5] update naming of ABC to Apple Messages For Business --- CHANGELOG.md | 2 +- typescript-node-client/api.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c3d578..946cd12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## [1.4.0] - 2022-12-09 ### Added - Add missing models to send WhatsApp interactive messages -- Add missing models to send ABC listpicker messages. +- Add missing models to send Apple Messages for Business listpicker messages. ## [1.3.6] - 2022-12-05 ### Added diff --git a/typescript-node-client/api.ts b/typescript-node-client/api.ts index 88d7fe9..faea0db 100644 --- a/typescript-node-client/api.ts +++ b/typescript-node-client/api.ts @@ -1435,7 +1435,7 @@ export class RichMessage { 'interactive'?: WhatsAppInteractive; /** - * Used to send an ABC listpicker message. + * Used to send an Apple Messages for Business listpicker message. */ 'listPicker'?: ListPicker; From 34e20c1c232c313962dbe1a2312bffd210d27ea3 Mon Sep 17 00:00:00 2001 From: Soufyan Bargach Date: Mon, 12 Dec 2022 10:54:29 +0100 Subject: [PATCH 4/5] Misc Updates Rename Apple Business chat to Apple Messages for Business Add Unit test and update models to lower case --- lib/MessageApiClient.ts | 2 +- spec/api.spec.ts | 50 +++++++++++++++++++++++++++++++++++ typescript-node-client/api.ts | 26 +++++++++--------- 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/lib/MessageApiClient.ts b/lib/MessageApiClient.ts index f579eee..a93559f 100644 --- a/lib/MessageApiClient.ts +++ b/lib/MessageApiClient.ts @@ -1,7 +1,7 @@ import * as CMTypes from "../typescript-node-client/api"; import http = require('http'); -export type Channel = "SMS" | "Viber" | "RCS" | "Apple Business Chat" | "WhatsApp" | "Telegram Messenger" | "Twitter" | "MobilePush" | "Facebook Messenger" | "Google Business Messages" | "Instagram"; +export type Channel = "SMS" | "Viber" | "RCS" | "Apple Messages for Business" | "WhatsApp" | "Telegram Messenger" | "Twitter" | "MobilePush" | "Facebook Messenger" | "Google Business Messages" | "Instagram"; export type RichMessage = CMTypes.RichMessage; export type Suggestion = CMTypes.Suggestion; export type Template = CMTypes.Template; diff --git a/spec/api.spec.ts b/spec/api.spec.ts index 2100351..1fc82e7 100644 --- a/spec/api.spec.ts +++ b/spec/api.spec.ts @@ -219,4 +219,54 @@ describe("MessageApiClient+MessageBuilder", () => { return response.body.details === "Created 1 message(s)"; }); }); + + const appleListPickerrichMessage: RichMessage = { + text: "Check out my image", + listPicker: { + label: "Please, pick a card", + media: { + "mediaUri": "https://static.thenounproject.com/png/393234-200.png" + }, + options: [{ + label: "Ace of Hearts", + media: { + mediaUri: "https://proxy.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.N7zZqoCvjxZZvwp2Zi1UVwHaH6%26pid%3D15.1&f=1" + } + }, + { + label: "Ace of Spades", + media: { + mediaUri: "https://proxy.duckduckgo.com/iu/?u=https%3A%2F%2Fcdn.pixabay.com%2Fphoto%2F2013%2F07%2F12%2F12%2F01%2Fsuit-of-spades-145116_960_720.png&f=1" + } + }, + { + label: "Ace of Diamonds", + media: { + mediaUri: "https://proxy.duckduckgo.com/iu/?u=https%3A%2F%2Fcdn.pixabay.com%2Fphoto%2F2012%2F05%2F07%2F18%2F37%2Fsuit-48941_960_720.png&f=1" + } + }, + { + label: "Ace of Clubs", + media: { + mediaUri: "https://proxy.duckduckgo.com/iu/?u=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2Fthumb%2F8%2F8a%2FSuitClubs.svg%2F709px-SuitClubs.svg.png&f=1" + } + } + ] + } + }; + + it("should create a valid http(s) request, when using the message-builder with a Apple for Business listpicker", () => { + const yourProductToken = "dddd"; + const client = new MessageApiClient(yourProductToken); + + const response = client.createMessage() + .setMessage(["00316012345678"], "TestSender", "Hello world?!") + .setAllowedChannels(["Apple Business Chat"]) + .setConversation([appleListPickerrichMessage]) + .send(); + + expect(response).to.be.eventually.fulfilled.and.to.satisfy((response) => { + return response.body.details === "Created 1 message(s)"; + }); + }); }); \ No newline at end of file diff --git a/typescript-node-client/api.ts b/typescript-node-client/api.ts index faea0db..188055b 100644 --- a/typescript-node-client/api.ts +++ b/typescript-node-client/api.ts @@ -143,34 +143,34 @@ export class ListPicker { /** * The label which will be shown to the end user to describe the contents of the list picker. */ - 'Label'?: string; + 'label'?: string; /** * An image, which will be shown to the end user to show information about the list picker */ - 'Media'?: Media; + 'media'?: Media; /** * The items which the end users can choose */ - 'Options'?: Array; + 'options'?: Array; static discriminator: string | undefined = undefined; static attributeTypeMap: Array<{name: string, baseName: string, type: string}> = [ { - "name": "Label", - "baseName": "Label", + "name": "label", + "baseName": "label", "type": "string" }, { - "name": "Media", - "baseName": "Media", - "type": "Media" + "name": "media", + "baseName": "media", + "type": "media" }, { - "name": "Options", - "baseName": "Options", + "name": "options", + "baseName": "options", "type": "Array" }]; @@ -186,7 +186,7 @@ export class ListItem { /** * The label which will be shown to the end user to describe the item. */ - 'Label'?: string; + 'label'?: string; /** * An image, which will be shown to the end user to show information about the list item @@ -197,8 +197,8 @@ export class ListItem { static attributeTypeMap: Array<{ name: string, baseName: string, type: string }> = [ { - "name": "Label", - "baseName": "Label", + "name": "label", + "baseName": "label", "type": "string" }, { From 8a10635ee3c652c0608b3c08f46271b2db9361f1 Mon Sep 17 00:00:00 2001 From: Soufyan Bargach Date: Mon, 12 Dec 2022 10:56:08 +0100 Subject: [PATCH 5/5] fix unit test --- spec/api.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/api.spec.ts b/spec/api.spec.ts index 1fc82e7..0353e11 100644 --- a/spec/api.spec.ts +++ b/spec/api.spec.ts @@ -261,7 +261,7 @@ describe("MessageApiClient+MessageBuilder", () => { const response = client.createMessage() .setMessage(["00316012345678"], "TestSender", "Hello world?!") - .setAllowedChannels(["Apple Business Chat"]) + .setAllowedChannels(["Apple Messages for Business"]) .setConversation([appleListPickerrichMessage]) .send();