Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add new properties to Messages API #504

Merged
merged 11 commits into from
Jan 4, 2024
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

# [8.1.0] - 2024-01-??
- Added various missing fields in Messages API:
- `webhook_version` and `webhook_url` for all outbound messages
- MMS vCard `caption` (outbound)
- MMS image `caption` (inbound)
- Whatsapp file `name` (outbound)
- Whatsapp `context_status` and `referral` (inbound)
- SMS `count_total` and `network_code` (inbound)
- SMS `ttl`, `encoding_type`, `content_id` and `entity_id` (outbound)
- Whatsapp conversation type and ID (status update)
- Added optional `from` parameter to Verify v2 SMS workflow
- Fixed `length` not being set in `VerifyClient.verify` overload method
- Fixed incorrect HTTP method for updating Video Broadcast layout
Expand Down
60 changes: 54 additions & 6 deletions src/main/java/com/vonage/client/messages/InboundMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@
*/
package com.vonage.client.messages;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.*;
import com.vonage.client.Jsonable;
import com.vonage.client.messages.sms.SmsInboundMetadata;
import com.vonage.client.messages.whatsapp.*;
Expand All @@ -28,7 +25,7 @@
import java.util.UUID;

/**
* Convenience class representing an inbound message webhook.
* Convenience class representing an inbound message webhook. This maps all known fields for all message types.
* <p>
* Refer to the
* <a href=https://developer.vonage.com/api/messages-olympus#webhooks>Messages API Webhook reference</a>
Expand All @@ -43,10 +40,22 @@ protected static class UrlWrapper {
@JsonProperty("url") protected URI url;
}

@JsonIgnoreProperties(ignoreUnknown = true)
protected static class UrlWrapperWithCaption extends UrlWrapper {
@JsonProperty("caption") protected String caption;
}

@JsonIgnoreProperties(ignoreUnknown = true)
protected static class Whatsapp {
@JsonProperty("referral") protected Referral referral;
}

protected InboundMessage() {}

@JsonAnySetter protected Map<String, Object> unknownProperties;

@JsonProperty("whatsapp") private Whatsapp whatsapp;

@JsonProperty("timestamp") protected Instant timestamp;
@JsonProperty("channel") protected Channel channel;
@JsonProperty("message_type") protected MessageType messageType;
Expand All @@ -57,14 +66,15 @@ protected InboundMessage() {}
@JsonProperty("provider_message") String providerMessage;

@JsonProperty("text") protected String text;
@JsonProperty("image") protected UrlWrapper image;
@JsonProperty("image") protected UrlWrapperWithCaption image;
@JsonProperty("audio") protected UrlWrapper audio;
@JsonProperty("video") protected UrlWrapper video;
@JsonProperty("file") protected UrlWrapper file;
@JsonProperty("vcard") protected UrlWrapper vcard;
@JsonProperty("sticker") protected UrlWrapper sticker;

@JsonProperty("profile") protected Profile whatsappProfile;
@JsonProperty("context_status") protected ContextStatus whatsappContextStatus;
@JsonProperty("context") protected Context whatsappContext;
@JsonProperty("location") protected Location whatsappLocation;
@JsonProperty("reply") protected Reply whatsappReply;
Expand Down Expand Up @@ -164,6 +174,17 @@ public URI getImageUrl() {
return image != null ? image.url : null;
}

/**
* Additional text accompanying the image. Applicable to MMS image messages only.
*
* @return The image caption if present, or {@code null} if not applicable.
*
* @since 8.1.0
*/
public String getImageCaption() {
return image != null ? image.caption : null;
}

/**
* If {@linkplain #getMessageType()} is {@linkplain MessageType#AUDIO}, returns the URL of the audio.
*
Expand Down Expand Up @@ -270,6 +291,20 @@ public Context getWhatsappContext() {
return whatsappContext;
}

/**
* If the {@linkplain #getChannel()} is {@linkplain Channel#WHATSAPP}, returns an enum indicating whether there
* is a context for this inbound message. If there is a context, and it is available, the context details will be
* contained in a context object. If there is a context, but it is unavailable,or if there is no context for
* message ({@linkplain ContextStatus#NONE}), then there will be no context object included in the body.
*
* @return The deserialized WhatsApp context status, or {@code null} if not applicable.
*
* @since 8.1.0
*/
public ContextStatus getWhatsappContextStatus() {
return whatsappContextStatus;
}

/**
* If the {@linkplain #getChannel()} is {@linkplain Channel#SMS}, returns the usage
* information (charged incurred for the message).
Expand All @@ -290,6 +325,19 @@ public SmsInboundMetadata getSmsMetadata() {
return smsMetadata;
}

/**
* If the {@linkplain #getChannel()} is {@linkplain Channel#WHATSAPP} and a content referral is present in
* the message, returns the metadata related to the post or advertisement that the user clicked on.
*
* @return The Whatsapp referral object, or {@code null} if not present or applicable.
*
* @since 8.1.0
*/
@JsonIgnore
public Referral getWhatsappReferral() {
return whatsapp != null ? whatsapp.referral : null;
}

/**
* Constructs an instance of this class from a JSON payload. Known fields will be mapped, whilst
* unknown fields can be manually obtained through the {@linkplain #getUnmappedProperties()} method.
Expand Down
65 changes: 65 additions & 0 deletions src/main/java/com/vonage/client/messages/MessageRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.vonage.client.Jsonable;
import com.vonage.client.common.E164;
import java.net.URI;
import java.util.Objects;

/**
Expand All @@ -36,6 +37,8 @@ public abstract class MessageRequest implements Jsonable {
final MessageType messageType;
final Channel channel;
final String clientRef;
final URI webhookUrl;
final MessagesVersion webhookVersion;
protected String from, to;

/**
Expand All @@ -57,6 +60,8 @@ protected MessageRequest(Builder<?, ?> builder, Channel channel, MessageType mes
clientRef = validateClientReference(builder.clientRef);
from = builder.from;
to = builder.to;
webhookUrl = builder.webhookUrl;
webhookVersion = builder.webhookVersion;
validateSenderAndRecipient(from, to);
}

Expand Down Expand Up @@ -115,6 +120,16 @@ public String getClientRef() {
return clientRef;
}

@JsonProperty("webhook_url")
public URI getWebhookUrl() {
return webhookUrl;
}

@JsonProperty("webhook_version")
public MessagesVersion getWebhookVersion() {
return webhookVersion;
}

@Override
public String toString() {
return getClass().getSimpleName()+' '+toJson();
Expand All @@ -133,6 +148,8 @@ public String toString() {
@SuppressWarnings("unchecked")
public abstract static class Builder<M extends MessageRequest, B extends Builder<? extends M, ? extends B>> {
protected String from, to, clientRef;
protected URI webhookUrl;
protected MessagesVersion webhookVersion;

/**
* Protected constructor to prevent users from explicitly creating this object.
Expand Down Expand Up @@ -178,6 +195,54 @@ public B clientRef(String clientRef) {
return (B) this;
}

/**
* (OPTIONAL)
* Specifies the URL to which Status Webhook messages will be sent for this particular message.
* Overrides account-level and application-level Status Webhook url settings on a per-message basis.
*
* @param webhookUrl The status webhook URL as a string.
*
* @return This builder.
*
* @since 8.1.0
*/
public B webhookUrl(String webhookUrl) {
return webhookUrl(URI.create(webhookUrl));
}

/**
* (OPTIONAL)
* Specifies the URL to which Status Webhook messages will be sent for this particular message.
* Overrides account-level and application-level Status Webhook url settings on a per-message basis.
*
* @param webhookUrl The status webhook URL.
*
* @return This builder.
*
* @since 8.1.0
*/
private B webhookUrl(URI webhookUrl) {
this.webhookUrl = webhookUrl;
return (B) this;
}

/**
* Specifies which version of the Messages API will be used to send Status Webhook messages for
* this particular message. For example, if {@linkplain MessagesVersion#V0_1} is set, then the
* JSON body of Status Webhook messages for this message will be sent in Messages v0.1 format.
* Over-rides account-level and application-level API version settings on a per-message basis.
*
* @param webhookVersion The messages API version enum.
*
* @return This builder.
*
* @since 8.1.0
*/
public B webhookVersion(MessagesVersion webhookVersion) {
this.webhookVersion = webhookVersion;
return (B) this;
}

/**
* Builds the MessageRequest.
*
Expand Down
98 changes: 96 additions & 2 deletions src/main/java/com/vonage/client/messages/MessageStatus.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.fasterxml.jackson.annotation.*;
import com.vonage.client.Jsonable;
import com.vonage.client.messages.whatsapp.ConversationType;
import java.net.URI;
import java.time.Instant;
import java.util.Currency;
Expand Down Expand Up @@ -186,10 +187,36 @@ public String toString() {
}
}

@JsonInclude(value = JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
static class Destination {
@JsonProperty("network_code") String networkCode;
}

@JsonInclude(value = JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
static class Sms {
@JsonProperty("count_total") Integer countTotal;
}

@JsonInclude(value = JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
static class Whatsapp {
static class Conversation {
static class Origin {
@JsonProperty("type") ConversationType type;
}
@JsonProperty("id") String id;
@JsonProperty("origin") Origin origin;
}
@JsonProperty("conversation") Conversation conversation;
}

protected MessageStatus() {
}

@JsonAnySetter protected Map<String, Object> unknownProperties;

@JsonProperty("timestamp") protected Instant timestamp;
@JsonProperty("message_uuid") protected UUID messageUuid;
@JsonProperty("to") protected String to;
Expand All @@ -200,6 +227,10 @@ protected MessageStatus() {
@JsonProperty("error") protected Error error;
@JsonProperty("usage") protected Usage usage;

@JsonProperty("destination") private Destination destination;
@JsonProperty("sms") private Sms sms;
@JsonProperty("whatsapp") private Whatsapp whatsapp;


/**
* Unique identifier of the message that was sent, as returned in {@link MessageResponse#getMessageUuid()}.
Expand Down Expand Up @@ -283,6 +314,62 @@ public Usage getUsage() {
return usage;
}

/**
* If {@linkplain #getChannel()} is {@linkplain Channel#SMS} or {@linkplain Channel#MMS},
* returns the network code for the destination.
*
* @return The mobile network code as a string, or {@code null} if not applicable.
*
* @since 8.1.0
*/
@JsonIgnore
public String getDestinationNetworkCode() {
return destination != null ? destination.networkCode : null;
}

/**
* {@linkplain #getChannel()} is {@linkplain Channel#SMS}, returns the number of SMS messages concatenated together
* to comprise the submitted message. SMS messages are 160 characters, if a submitted message exceeds that size it
* is sent as multiple SMS messages. This number indicates how many SMS messages are required.
*
* @return The number of SMS messages used for this message, or {@code null} if not applicable.
*
* @since 8.1.0
*/
@JsonIgnore
public Integer getSmsTotalCount() {
return sms != null ? sms.countTotal : null;
}

/**
* If the {@linkplain #getChannel()} is {@linkplain Channel#WHATSAPP} and {@linkplain #getStatus()} is
* {@linkplain Status#DELIVERED}, returns the conversation's origin type.
*
* @return The WhatsApp conversation category as an enum, {@code null} if absent or not applicable.
*
* @since 8.1.0
*/
@JsonIgnore
public ConversationType getWhatsappConversationType() {
return whatsapp != null &&
whatsapp.conversation != null &&
whatsapp.conversation.origin != null ?
whatsapp.conversation.origin.type : null;
}

/**
* If the {@linkplain #getChannel()} is {@linkplain Channel#WHATSAPP} and {@linkplain #getStatus()} is
* {@linkplain Status#DELIVERED}, returns the conversation ID of the message that triggered this callback.
*
* @return The WhatsApp conversation ID, {@code null} if absent or not applicable.
*
* @since 8.1.0
*/
@JsonIgnore
public String getWhatsappConversationId() {
return whatsapp != null && whatsapp.conversation != null ? whatsapp.conversation.id : null;
}

/**
* Catch-all for properties which are not mapped by this class during deserialization.
*
Expand Down Expand Up @@ -318,11 +405,18 @@ public boolean equals(Object o) {
Objects.equals(to, that.to) && Objects.equals(from, that.from) &&
status == that.status && channel == that.channel &&
Objects.equals(clientRef, that.clientRef) &&
Objects.equals(error, that.error) && Objects.equals(usage, that.usage);
Objects.equals(error, that.error) && Objects.equals(usage, that.usage) &&
Objects.equals(getDestinationNetworkCode(), that.getDestinationNetworkCode()) &&
Objects.equals(getSmsTotalCount(), that.getSmsTotalCount()) &&
Objects.equals(getWhatsappConversationId(), that.getWhatsappConversationId()) &&
Objects.equals(getWhatsappConversationType(), that.getWhatsappConversationType());
}

@Override
public int hashCode() {
return Objects.hash(timestamp, messageUuid, to, from, status, channel, clientRef, error, usage);
return Objects.hash(
timestamp, messageUuid, to, from, status, channel,
clientRef, error, usage, getDestinationNetworkCode(), getSmsTotalCount()
);
}
}
Loading
Loading