Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/Tests/WhatsAppClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,39 @@ public async Task SendsButtonAsync()
});
}

[SecretsFact("Meta:VerifyToken", "SendFrom", "SendTo")]
public async Task SendsCallToActionAsync()
{
var (configuration, client) = Initialize();

// Send an interactive message with three buttons showcasing the payload/value
// being different than the button text
await client.SendAsync(configuration["SendFrom"]!, new
{
messaging_product = "whatsapp",
recipient_type = "individual",
to = configuration["SendTo"]!,
type = "interactive",
interactive = new
{
type = "cta_url",
body = new
{
text = "Tap the button to send a message to a contact"
},
action = new
{
name = "cta_url",
parameters = new
{
display_text = "Send",
url = "https://wa.me/541234567890?text=Hi"
}
}
}
});
}

[SecretsFact("Meta:VerifyToken", "MediaTo", Skip = "Media attachments are deleted if user deletes them, so skip.")]
public async Task ResolvesMediaIdFromHttpClient()
{
Expand Down
22 changes: 22 additions & 0 deletions src/WhatsApp/CallToActionResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Devlooped.WhatsApp;

/// <summary>
/// Represents an interactive call to action that can be sent in response to a user message.
/// </summary>
/// <see cref="https://developers.facebook.com/docs/whatsapp/cloud-api/messages/interactive-cta-url-messages/"/>
public record CallToActionResponse(string ServiceId, string UserNumber, string? Context, string Text, string ButtonText, string Url, string? ConversationId) : Response(ServiceId, UserNumber, Context, ConversationId)
{
readonly CompositeService? service;

internal CallToActionResponse(Service service, string userNumber, string? context, string Text, string ButtonText, string Url, string? conversationId)
: this(service.Id, userNumber, context, Text, ButtonText, Url, conversationId)
=> this.service = service as CompositeService;

protected override Task<string?> SendCoreAsync(IWhatsAppClient client, CancellationToken cancellation = default)
{
if (service != null)
return client.SendCallToActionAsync(service.Secondary.Id, UserNumber, Text, ButtonText, Url, cancellation);

return client.SendCallToActionAsync(ServiceId, UserNumber, Text, ButtonText, Url, cancellation);
}
}
1 change: 1 addition & 0 deletions src/WhatsApp/IMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ namespace Devlooped.WhatsApp;
[JsonDerivedType(typeof(TemplateResponse), "response/template")]
[JsonDerivedType(typeof(ReactionResponse), "response/reaction")]
[JsonDerivedType(typeof(TypingResponse), "response/typing")]
[JsonDerivedType(typeof(CallToActionResponse), "response/cta")]
public interface IMessage
{
/// <summary>Gets or sets any additional properties associated with the message.</summary>
Expand Down
8 changes: 8 additions & 0 deletions src/WhatsApp/MessageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ public static TemplateResponse Template(this IMessage message, object template)
public static TypingResponse Typing(this UserMessage message)
=> new(message.Service, message.User.Number, message.Id, message.ConversationId);

/// <summary>
/// Sends an interactive call to action response to the user message.
/// </summary>
public static CallToActionResponse CallToAction(this IMessage message, string text, string action, string url)
=> message is UserMessage user
? new(user.Service, message.UserNumber, message.Id, text, action, url, message.ConversationId)
: new(message.ServiceId, message.UserNumber, message.Id, text, action, url, message.ConversationId);

/// <summary>
/// Creates a text reply for the message.
/// </summary>
Expand Down
36 changes: 36 additions & 0 deletions src/WhatsApp/WhatsAppClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,42 @@ public static Task SendTyping(this IWhatsAppClient client, string serviceId, str
}
}, cancellation);

/// <summary>
/// Sends a call-to-action interactive message to a user.
/// </summary>
/// <param name="client">The WhatsApp client used to send the message.</param>
/// <param name="serviceId">The identifier for the service sending the message.</param>
/// <param name="userNumber">The phone number of the recipient. Must be in a valid format.</param>
/// <param name="message">The text message to be sent to the user.</param>
/// <param name="buttonText">The text displayed on the call-to-action button.</param>
/// <param name="cancellation">A token to monitor for cancellation requests. Defaults to <see cref="CancellationToken.None"/>.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public static Task<string?> SendCallToActionAsync(this IWhatsAppClient client, string serviceId, string userNumber, string message, string buttonText, string url, CancellationToken cancellation = default)
=> client.SendAsync(serviceId, new
{
messaging_product = "whatsapp",
recipient_type = "individual",
to = NormalizeNumber(userNumber),
type = "interactive",
interactive = new
{
type = "cta_url",
body = new
{
text = message
},
action = new
{
name = "cta_url",
parameters = new
{
display_text = buttonText,
url = url
}
}
}
}, cancellation);

static string NormalizeNumber(string number) =>
// On the web, we don't get the 9 after 54 \o/
// so for Argentina numbers, we need to remove the 9.
Expand Down