Skip to content

Commit 81c06cb

Browse files
authored
Add ACS WhatsApp interactive message (#43782)
Added WhatsApp interactive, sticker and reaction message
1 parent 4e6dd82 commit 81c06cb

38 files changed

+3449
-33
lines changed

sdk/communication/azure-communication-messages/CHANGELOG.md

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
# Release History
22

3-
## 1.2.0-beta.1 (Unreleased)
3+
## 1.2.0-beta.1 (2025-01-15)
44

55
### Features Added
6-
7-
### Breaking Changes
8-
9-
### Bugs Fixed
10-
11-
### Other Changes
6+
- Added Interactive Message.
7+
- Added Reaction Message.
8+
- Added Sticker Message.
129

1310

1411
## 1.1.1 (2024-12-04)
@@ -24,7 +21,7 @@
2421
## 1.1.0 (2024-10-23)
2522

2623
### Features Added
27-
- Added ImageNotificationContent to send image messgae.
24+
- Added ImageNotificationContent to send image message.
2825
- Added DocumentNotificationContent to send document message.
2926
- Added VideoNotificationContent to send video message.
3027
- Added AudioNotificationContent to send audio message.

sdk/communication/azure-communication-messages/README.md

+121
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,127 @@ public void sendDocumentMessage() {
274274
result.getReceipts().forEach(r -> System.out.println("Message sent to:" + r.getTo() + " and message id:" + r.getMessageId()));
275275
}
276276
```
277+
```java readme-sample-sendStickerMessage
278+
/*
279+
* This sample shows how to send sticker message with below details
280+
* Supported sticker type - (.webp)
281+
* Note: Business cannot initiate conversation with media message.
282+
* */
283+
public static void sendStickerMessage() {
284+
//Update the Media URL
285+
String mediaUrl = "https://www.gstatic.com/webp/gallery/1.sm.webp";
286+
List<String> recipients = new ArrayList<>();
287+
recipients.add("<RECIPIENT_IDENTIFIER e.g. PhoneNumber>");
288+
NotificationMessagesClient client = new NotificationMessagesClientBuilder()
289+
.connectionString("<CONNECTION_STRING>")
290+
.buildClient();
291+
SendMessageResult result = client.send(
292+
new StickerNotificationContent("<CHANNEL_ID>", recipients, mediaUrl));
293+
294+
result.getReceipts().forEach(r -> System.out.println("Message sent to:" + r.getTo() + " and message id:" + r.getMessageId()));
295+
}
296+
```
297+
```java readme-sample-sendReactionMessage
298+
/*
299+
* This sample shows how to send reaction message with below details
300+
* Emoji - unicode for emoji character.
301+
* Reply Message ID - ID of the message to be replied with emoji
302+
* Note: Business cannot initiate conversation with media message.
303+
* */
304+
public static void sendReactionMessage() {
305+
List<String> recipients = new ArrayList<>();
306+
recipients.add("<RECIPIENT_IDENTIFIER e.g. PhoneNumber>");
307+
NotificationMessagesClient client = new NotificationMessagesClientBuilder()
308+
.connectionString("<CONNECTION_STRING>")
309+
.buildClient();
310+
SendMessageResult result = client.send(
311+
new ReactionNotificationContent("<CHANNEL_ID>", recipients, "\uD83D\uDE00", "<REPLY_MESSAGE_ID>"));
312+
313+
result.getReceipts().forEach(r -> System.out.println("Message sent to:" + r.getTo() + " and message id:" + r.getMessageId()));
314+
}
315+
```
316+
```java readme-sample-sendInteractiveMessageWithButtonAction
317+
/*
318+
* This sample shows how to send interactive message with Button Action
319+
* Note: Business cannot initiate conversation with interactive message.
320+
* */
321+
public void sendInteractiveMessageWithButtonAction() {
322+
List<String> recipients = new ArrayList<>();
323+
recipients.add("<RECIPIENT_IDENTIFIER e.g. PhoneNumber>");
324+
NotificationMessagesClient client = new NotificationMessagesClientBuilder()
325+
.connectionString("<CONNECTION_STRING>")
326+
.buildClient();
327+
List<ButtonContent> buttonActions = new ArrayList<>();
328+
buttonActions.add(new ButtonContent("no", "No"));
329+
buttonActions.add(new ButtonContent("yes", "Yes"));
330+
ButtonSetContent buttonSet = new ButtonSetContent(buttonActions);
331+
InteractiveMessage interactiveMessage = new InteractiveMessage(
332+
new TextMessageContent("Do you want to proceed?"), new WhatsAppButtonActionBindings(buttonSet));
333+
SendMessageResult result = client.send(
334+
new InteractiveNotificationContent("<CHANNEL_ID>", recipients, interactiveMessage));
335+
336+
result.getReceipts().forEach(r -> System.out.println("Message sent to:" + r.getTo() + " and message id:" + r.getMessageId()));
337+
}
338+
```
339+
```java readme-sample-sendInteractiveMessageWithListAction
340+
/*
341+
* This sample shows how to send list action interactive message
342+
* Note: Business cannot initiate conversation with interactive message.
343+
* */
344+
public static void sendInteractiveMessageWithListAction() {
345+
List<ActionGroupItem> group1 = new ArrayList<>();
346+
group1.add(new ActionGroupItem("priority_express", "Priority Mail Express", "Delivered on same day!"));
347+
group1.add(new ActionGroupItem("priority_mail", "Priority Mail", "Delivered in 1-2 days"));
348+
349+
List<ActionGroupItem> group2 = new ArrayList<>();
350+
group2.add(new ActionGroupItem("usps_ground_advantage", "USPS Ground Advantage", "Delivered in 2-5 days"));
351+
group2.add(new ActionGroupItem("media_mail", "Media Mail", "Delivered in 5-8 days"));
352+
353+
List<ActionGroup> options = new ArrayList<>();
354+
options.add(new ActionGroup("Express Delivery", group1));
355+
options.add(new ActionGroup("Normal Delivery", group2));
356+
357+
ActionGroupContent actionGroupContent = new ActionGroupContent("Shipping Options", options);
358+
InteractiveMessage interactiveMessage = new InteractiveMessage(
359+
new TextMessageContent("Which shipping option do you want?"), new WhatsAppListActionBindings(actionGroupContent));
360+
interactiveMessage.setFooter(new TextMessageContent("Eagle Logistic"));
361+
interactiveMessage.setHeader(new TextMessageContent("Shipping Options"));
362+
363+
List<String> recipients = new ArrayList<>();
364+
recipients.add("<RECIPIENT_IDENTIFIER e.g. PhoneNumber>");
365+
NotificationMessagesClient client = new NotificationMessagesClientBuilder()
366+
.connectionString("<CONNECTION_STRING>")
367+
.buildClient();
368+
SendMessageResult result = client.send(
369+
new InteractiveNotificationContent("<CHANNEL_ID>", recipients, interactiveMessage));
370+
371+
result.getReceipts().forEach(r -> System.out.println("Message sent to:" + r.getTo() + " and message id:" + r.getMessageId()));
372+
373+
}
374+
```
375+
```java readme-sample-sendInteractiveMessageWithUrlAction
376+
/*
377+
* This sample shows how to send url action interactive message
378+
* Note: Business cannot initiate conversation with interactive message.
379+
* */
380+
public static void sendInteractiveMessageWithUrlAction() {
381+
LinkContent urlAction = new LinkContent("Rocket is the best!", "https://wallpapercave.com/wp/wp2163723.jpg");
382+
InteractiveMessage interactiveMessage = new InteractiveMessage(
383+
new TextMessageContent("The best Guardian of Galaxy"), new WhatsAppUrlActionBindings(urlAction));
384+
interactiveMessage.setFooter(new TextMessageContent("Intergalactic New Ltd"));
385+
386+
List<String> recipients = new ArrayList<>();
387+
recipients.add("<RECIPIENT_IDENTIFIER e.g. PhoneNumber>");
388+
NotificationMessagesClient client = new NotificationMessagesClientBuilder()
389+
.connectionString("<CONNECTION_STRING>")
390+
.buildClient();
391+
SendMessageResult result = client.send(
392+
new InteractiveNotificationContent("<CHANNEL_ID>", recipients, interactiveMessage));
393+
394+
result.getReceipts().forEach(r -> System.out.println("Message sent to:" + r.getTo() + " and message id:" + r.getMessageId()));
395+
}
396+
```
397+
277398
### Get Template List for given channel example:
278399
```java readme-sample-ListTemplates
279400
MessageTemplateClient templateClient =

sdk/communication/azure-communication-messages/assets.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo" : "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath" : "java",
44
"TagPrefix" : "java/communication/azure-communication-messages",
5-
"Tag" : "java/communication/azure-communication-messages_d7c7441f3b"
5+
"Tag" : "java/communication/azure-communication-messages_d7e86da684"
66
}

sdk/communication/azure-communication-messages/customization/src/main/java/MessagesSdkCustomization.java

+128-4
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,19 @@
88
import com.github.javaparser.ast.Modifier;
99
import com.github.javaparser.ast.Node;
1010
import com.github.javaparser.ast.NodeList;
11+
import com.github.javaparser.ast.body.MethodDeclaration;
12+
import com.github.javaparser.ast.body.Parameter;
1113
import com.github.javaparser.ast.stmt.BlockStmt;
1214
import com.github.javaparser.ast.type.ClassOrInterfaceType;
1315
import com.github.javaparser.javadoc.Javadoc;
1416
import com.github.javaparser.javadoc.description.JavadocDescription;
1517
import com.github.javaparser.javadoc.description.JavadocSnippet;
1618
import org.slf4j.Logger;
1719

20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import java.util.stream.Collectors;
23+
1824
public class MessagesSdkCustomization extends Customization {
1925

2026
@Override
@@ -32,13 +38,24 @@ public void customize(LibraryCustomization libraryCustomization, Logger logger)
3238
customizeMessageTemplateLocation(modelsPackage);
3339
customizeMessageTemplateItemModel(modelsPackage);
3440

41+
//Handle Interactive message content models
42+
updateModelClassModifierToAbstract(modelsPackage, "MessageContent");
43+
updateModelClassModifierToAbstract(modelsPackage, "ActionBindings");
44+
updateJavaDocForMethodFromJson(modelsPackage, "ActionBindings");
45+
updateJavaDocForMethodFromJson(modelsPackage, "MessageContent");
46+
customizeInteractiveMessage(modelsPackage);
47+
3548
PackageCustomization channelsModelsPackage = libraryCustomization.getPackage(
3649
"com.azure.communication.messages.models.channels");
3750
updateWhatsAppMessageTemplateItemWithBinaryDataContent(channelsModelsPackage);
3851

39-
AddDeprecateAnnotationToMediaNotificationContent(modelsPackage);
52+
addDeprecateAnnotationToMediaNotificationContent(modelsPackage);
53+
54+
addDeprecateAnnotationForImageV0CommunicationKind(modelsPackage);
4055

41-
AddDeprecateAnnotationForImageV0CommunicationKind(modelsPackage);
56+
customizeActionGroup(modelsPackage);
57+
customizeActionGroupContent(modelsPackage);
58+
customizeButtonSetContent(modelsPackage);
4259
}
4360

4461
private void updateModelClassModifierToAbstract(PackageCustomization modelsPackage, String className) {
@@ -174,6 +191,45 @@ private void customizeMessageTemplateLocation(PackageCustomization modelsPackage
174191
});
175192
}
176193

194+
private void customizeInteractiveMessage(PackageCustomization modelsPackage) {
195+
modelsPackage.getClass("InteractiveMessage").customizeAst(ast -> {
196+
ast.getClassByName("InteractiveMessage").ifPresent(clazz -> {
197+
clazz.addMethod("getHeader", Modifier.Keyword.PUBLIC)
198+
.setType(clazz.getMethodsByName("getHeaderProperty").get(0).getType())
199+
.setBody(clazz.getMethodsByName("getHeaderProperty").get(0).getBody().get())
200+
.setJavadocComment(clazz.getMethodsByName("getHeaderProperty").get(0).getJavadocComment().get());
201+
202+
clazz.getMethodsByName("getHeaderProperty").forEach(Node::remove);
203+
204+
MethodDeclaration setHeaderMethodDeclaration = clazz.getMethodsByName("setHeaderProperty").get(0);
205+
List<Parameter> parameters = setHeaderMethodDeclaration
206+
.getParameters()
207+
.stream()
208+
.map(p -> p.setName("header"))
209+
.collect(Collectors.toList());
210+
String methodBodyContent = setHeaderMethodDeclaration
211+
.getBody()
212+
.get()
213+
.toString()
214+
.replace("headerProperty;", "header;");
215+
216+
String docComment = setHeaderMethodDeclaration
217+
.getJavadocComment()
218+
.get()
219+
.getContent()
220+
.replace("headerProperty", "header");
221+
222+
clazz.addMethod("setHeader", Modifier.Keyword.PUBLIC)
223+
.setParameters(new NodeList<Parameter>(parameters))
224+
.setType(setHeaderMethodDeclaration.getType())
225+
.setBody(StaticJavaParser.parseBlock(methodBodyContent))
226+
.setJavadocComment(new Javadoc(JavadocDescription.parseText(docComment)));
227+
228+
clazz.getMethodsByName("setHeaderProperty").forEach(Node::remove);
229+
});
230+
});
231+
}
232+
177233
private void updateWhatsAppMessageTemplateItemWithBinaryDataContent(PackageCustomization channelsModelsPackage) {
178234
channelsModelsPackage.getClass("WhatsAppMessageTemplateItem").customizeAst(ast -> {
179235
// ast.addImport("com.azure.core.util.BinaryData");
@@ -206,7 +262,7 @@ private void removeJsonKnownDiscriminatorMethod(PackageCustomization modelPackag
206262
});
207263
}
208264

209-
private void AddDeprecateAnnotationToMediaNotificationContent(PackageCustomization modelsPackage) {
265+
private void addDeprecateAnnotationToMediaNotificationContent(PackageCustomization modelsPackage) {
210266
modelsPackage.getClass("MediaNotificationContent").customizeAst(ast -> {
211267
ast.getClassByName("MediaNotificationContent").ifPresent(clazz -> {
212268
clazz.addAnnotation(Deprecated.class);
@@ -222,7 +278,7 @@ private void AddDeprecateAnnotationToMediaNotificationContent(PackageCustomizati
222278
});
223279
}
224280

225-
private void AddDeprecateAnnotationForImageV0CommunicationKind(PackageCustomization modelsPackage) {
281+
private void addDeprecateAnnotationForImageV0CommunicationKind(PackageCustomization modelsPackage) {
226282
modelsPackage.getClass("CommunicationMessageKind").customizeAst(ast -> {
227283
ast.getClassByName("CommunicationMessageKind")
228284
.flatMap(clazz -> clazz.getFieldByName("IMAGE_V0"))
@@ -240,4 +296,72 @@ private void AddDeprecateAnnotationForImageV0CommunicationKind(PackageCustomiza
240296
});
241297
});
242298
}
299+
300+
private void updateJavaDocForMethodFromJson(PackageCustomization modelPackage, String className) {
301+
String originalDocText = String.format("@throws IOException If an error occurs while reading the %s.", className);
302+
modelPackage.getClass(className).customizeAst(ast -> {
303+
ast.getClassByName(className).ifPresent( clazz -> {
304+
String fromJsonDoc = clazz.getMethodsByName("fromJson")
305+
.get(0).getJavadoc().get().toText()
306+
.replace(originalDocText,
307+
"@throws IllegalStateException If the deserialized JSON object was missing any required properties.\n" +
308+
originalDocText);
309+
clazz.getMethodsByName("fromJson").get(0).setJavadocComment(fromJsonDoc);
310+
});
311+
});
312+
}
313+
314+
private void customizeActionGroup(PackageCustomization modelsPackage) {
315+
modelsPackage.getClass("ActionGroup").customizeAst(ast -> {
316+
ast.getClassByName("ActionGroup")
317+
.flatMap(clazz -> clazz.getConstructorByParameterTypes(String.class, List.class))
318+
.ifPresent(c -> {
319+
String body = c.getBody().toString().replace("this.items = items;",
320+
"this.items = new ArrayList<>(items);");
321+
c.setBody(StaticJavaParser.parseBlock(body));
322+
});
323+
324+
ast.getClassByName("ActionGroup").ifPresent(clazz -> {
325+
String getItemsBody = clazz.getMethodsByName("getItems").get(0).getBody().get().toString()
326+
.replace("return this.items;", "return new ArrayList<>(this.items);");
327+
clazz.getMethodsByName("getItems").get(0).setBody(StaticJavaParser.parseBlock(getItemsBody));
328+
});
329+
});
330+
}
331+
332+
private void customizeActionGroupContent(PackageCustomization modelsPackage) {
333+
modelsPackage.getClass("ActionGroupContent").customizeAst(ast -> {
334+
ast.getClassByName("ActionGroupContent")
335+
.flatMap(clazz -> clazz.getConstructorByParameterTypes(String.class, List.class))
336+
.ifPresent(c -> {
337+
String body = c.getBody().toString().replace("this.groups = groups;",
338+
"this.groups = new ArrayList<>(groups);");
339+
c.setBody(StaticJavaParser.parseBlock(body));
340+
});
341+
342+
ast.getClassByName("ActionGroupContent").ifPresent(clazz -> {
343+
String getItemsBody = clazz.getMethodsByName("getGroups").get(0).getBody().get().toString()
344+
.replace("return this.groups;", "return new ArrayList<>(this.groups);");
345+
clazz.getMethodsByName("getGroups").get(0).setBody(StaticJavaParser.parseBlock(getItemsBody));
346+
});
347+
});
348+
}
349+
350+
private void customizeButtonSetContent(PackageCustomization modelsPackage) {
351+
modelsPackage.getClass("ButtonSetContent").customizeAst(ast -> {
352+
ast.getClassByName("ButtonSetContent")
353+
.flatMap(clazz -> clazz.getConstructorByParameterTypes(List.class))
354+
.ifPresent(c -> {
355+
String body = c.getBody().toString().replace("this.buttons = buttons;",
356+
"this.buttons = new ArrayList<>(buttons);");
357+
c.setBody(StaticJavaParser.parseBlock(body));
358+
});
359+
360+
ast.getClassByName("ButtonSetContent").ifPresent(clazz -> {
361+
String getItemsBody = clazz.getMethodsByName("getButtons").get(0).getBody().get().toString()
362+
.replace("return this.buttons;", "return new ArrayList<>(this.buttons);");
363+
clazz.getMethodsByName("getButtons").get(0).setBody(StaticJavaParser.parseBlock(getItemsBody));
364+
});
365+
});
366+
}
243367
}

sdk/communication/azure-communication-messages/src/main/java/com/azure/communication/messages/MessagesServiceVersion.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ public enum MessagesServiceVersion implements ServiceVersion {
1818
/**
1919
* Enum value 2024-08-30.
2020
*/
21-
V2024_08_30("2024-08-30");
21+
V2024_08_30("2024-08-30"),
22+
23+
/**
24+
* Enum value 2025-01-15-preview.
25+
*/
26+
V2025_01_15_PREVIEW("2025-01-15-preview");
2227

2328
private final String version;
2429

@@ -40,6 +45,6 @@ public String getVersion() {
4045
* @return The latest {@link MessagesServiceVersion}.
4146
*/
4247
public static MessagesServiceVersion getLatest() {
43-
return V2024_08_30;
48+
return V2025_01_15_PREVIEW;
4449
}
4550
}

sdk/communication/azure-communication-messages/src/main/java/com/azure/communication/messages/NotificationMessagesAsyncClient.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public final class NotificationMessagesAsyncClient {
5555
* <pre>
5656
* {@code
5757
* {
58-
* kind: String(text/image/image_v0/document/video/audio/template) (Required)
58+
* kind: String(text/image/image_v0/document/video/audio/template/sticker/reaction/interactive) (Required)
5959
* channelRegistrationId: String (Required)
6060
* to (Required): [
6161
* String (Required)

sdk/communication/azure-communication-messages/src/main/java/com/azure/communication/messages/NotificationMessagesClient.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public final class NotificationMessagesClient {
5353
* <pre>
5454
* {@code
5555
* {
56-
* kind: String(text/image/image_v0/document/video/audio/template) (Required)
56+
* kind: String(text/image/image_v0/document/video/audio/template/sticker/reaction/interactive) (Required)
5757
* channelRegistrationId: String (Required)
5858
* to (Required): [
5959
* String (Required)

0 commit comments

Comments
 (0)