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
44 changes: 43 additions & 1 deletion src/Tests/WhatsAppClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,49 @@ public async Task SendsMessageAsync()
{
var (configuration, client) = Initialize();

await client.SendAsync(configuration["SendFrom"]!, configuration["SendTo"]!, "Hi there!");
var id = await client.SendAsync(configuration["SendFrom"]!, configuration["SendTo"]!, "Hi there!");

Assert.NotNull(id);
Assert.NotEmpty(id);
}

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

var id = await client.SendAsync(configuration["SendFrom"]!, configuration["SendTo"]!, "Hi there!");

Assert.NotNull(id);
Assert.NotEmpty(id);

await client.ReactAsync(configuration["SendFrom"]!, configuration["SendTo"]!, id, "🙏");
}

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

var id = await client.SendAsync(configuration["SendFrom"]!, configuration["SendTo"]!, "Hi there!");

Assert.NotNull(id);
Assert.NotEmpty(id);

var reply = await client.ReplyAsync(
new ContentMessage(id,
new Service(from, from),
new User(to, to),
DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
new TextContent("Hi there!")),
"Reply here!");

Assert.NotNull(reply);
Assert.NotEmpty(reply);

Assert.NotEqual(id, reply);
}

[SecretsFact("Meta:VerifyToken", "SendFrom", "SendTo")]
Expand Down
4 changes: 2 additions & 2 deletions src/WhatsApp/IWhatsAppClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ public interface IWhatsAppClient
/// </summary>
/// <param name="numberId">The phone identifier to send the message from, which must be configured via <see cref="MetaOptions.Numbers"/>.</param>
/// <param name="payload">The message payload.</param>
/// <returns>Whether the message was successfully sent.</returns>
/// <returns>The message id that was sent/reacted/marked, if any.</returns>
/// <see cref="https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages"/>
/// <exception cref="ArgumentException">The number <paramref name="numberId"/> is not registered in <see cref="MetaOptions"/>.</exception>
/// <exception cref="HttpRequestException">The HTTP request failed. Exception message contains the error response body from WhatsApp.</exception>
[Description(nameof(Devlooped) + nameof(WhatsApp) + nameof(IWhatsAppClient) + nameof(SendAsync))]
Task SendAsync(string numberId, object payload);
Task<string?> SendAsync(string numberId, object payload);
}
6 changes: 5 additions & 1 deletion src/WhatsApp/WhatsAppClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public HttpClient CreateHttp(string numberId)
}

/// <inheritdoc />
public async Task SendAsync(string numberId, object payload)
public async Task<string?> SendAsync(string numberId, object payload)
{
if (!options.Numbers.TryGetValue(numberId, out var token))
throw new ArgumentException($"The number '{numberId}' is not registered in the options.", nameof(numberId));
Expand All @@ -59,5 +59,9 @@ public async Task SendAsync(string numberId, object payload)
logger.LogError("Failed to send WhatsApp message from {From}: {Error}", numberId, error);
throw new HttpRequestException(error, null, result.StatusCode);
}

var response = await result.Content.ReadFromJsonAsync(WhatsAppSerializerContext.Default.SendResponse);

return response?.Messages.FirstOrDefault()?.Id;
}
}
39 changes: 26 additions & 13 deletions src/WhatsApp/WhatsAppClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ public static Task ReactAsync(this IWhatsAppClient client, string from, string t
/// <param name="client">The WhatsApp client.</param>
/// <param name="message">The message to reply to.</param>
/// <param name="reply">The text message to respond with.</param>
public static Task ReplyAsync(this IWhatsAppClient client, UserMessage message, string reply)
/// <returns>The identifier of the reply.</returns>
public static Task<string?> ReplyAsync(this IWhatsAppClient client, UserMessage message, string reply)
=> client.SendAsync(message.To.Id, new
{
messaging_product = "whatsapp",
Expand All @@ -98,7 +99,8 @@ public static Task ReplyAsync(this IWhatsAppClient client, UserMessage message,
/// <param name="message">The message to reply to.</param>
/// <param name="reply">The text message to respond with.</param>
/// <param name="button">Interactive button for users to reply.</param>
public static Task ReplyAsync(this IWhatsAppClient client, UserMessage message, string reply, Button button)
/// <returns>The identifier of the reply message.</returns>
public static Task<string?> ReplyAsync(this IWhatsAppClient client, UserMessage message, string reply, Button button)
=> client.SendAsync(message.To.Id, new
{
messaging_product = "whatsapp",
Expand Down Expand Up @@ -135,7 +137,8 @@ public static Task ReplyAsync(this IWhatsAppClient client, UserMessage message,
/// <param name="reply">The text message to respond with.</param>
/// <param name="button1">Interactive button for a user choice.</param>
/// <param name="button2">Interactive button for a user choice.</param>
public static Task ReplyAsync(this IWhatsAppClient client, UserMessage message, string reply, Button button1, Button button2)
/// <returns>The identifier of the reply message.</returns>
public static Task<string?> ReplyAsync(this IWhatsAppClient client, UserMessage message, string reply, Button button1, Button button2)
=> client.SendAsync(message.To.Id, new
{
messaging_product = "whatsapp",
Expand Down Expand Up @@ -174,7 +177,8 @@ public static Task ReplyAsync(this IWhatsAppClient client, UserMessage message,
/// <param name="button1">Interactive button for a user choice.</param>
/// <param name="button2">Interactive button for a user choice.</param>
/// <param name="button3">Interactive button for a user choice.</param>
public static Task ReplyAsync(this IWhatsAppClient client, UserMessage message, string reply, Button button1, Button button2, Button button3)
/// <returns>The identifier of the reply message.</returns>
public static Task<string?> ReplyAsync(this IWhatsAppClient client, UserMessage message, string reply, Button button1, Button button2, Button button3)
=> client.SendAsync(message.To.Id, new
{
messaging_product = "whatsapp",
Expand Down Expand Up @@ -211,7 +215,8 @@ public static Task ReplyAsync(this IWhatsAppClient client, UserMessage message,
/// <param name="client">The WhatsApp client.</param>
/// <param name="reaction">The reaction from the user.</param>
/// <param name="reply">The text message to respond with.</param>
public static Task ReplyAsync(this IWhatsAppClient client, ReactionMessage message, string reply)
/// <returns>The identifier of the reply message.</returns>
public static Task<string?> ReplyAsync(this IWhatsAppClient client, ReactionMessage message, string reply)
=> client.SendAsync(message.To.Id, new
{
messaging_product = "whatsapp",
Expand All @@ -235,7 +240,8 @@ public static Task ReplyAsync(this IWhatsAppClient client, ReactionMessage messa
/// <param name="client">The WhatsApp client.</param>
/// <param name="to">The originating user to send a message to.</param>
/// <param name="message">The text message to send.</param>
public static Task SendAsync(this IWhatsAppClient client, Message to, string message)
/// <returns>The identifier of the sent message.</returns>
public static Task<string?> SendAsync(this IWhatsAppClient client, Message to, string message)
=> SendAsync(client, to.To.Id, to.From.Number, message);

/// <summary>
Expand All @@ -245,7 +251,8 @@ public static Task SendAsync(this IWhatsAppClient client, Message to, string mes
/// <param name="to">The originating user to send a message to.</param>
/// <param name="message">The text message to send.</param>
/// <param name="button">Interactive button for users to reply.</param>
public static Task SendAsync(this IWhatsAppClient client, Message to, string message, Button button)
/// <returns>The identifier of the sent message.</returns>
public static Task<string?> SendAsync(this IWhatsAppClient client, Message to, string message, Button button)
=> SendAsync(client, to.To.Id, to.From.Number, message, button);

/// <summary>
Expand All @@ -256,7 +263,8 @@ public static Task SendAsync(this IWhatsAppClient client, Message to, string mes
/// <param name="message">The text message to send.</param>
/// <param name="button1">Interactive button for a user choice.</param>
/// <param name="button2">Interactive button for a user choice.</param>
public static Task SendAsync(this IWhatsAppClient client, Message to, string message, Button button1, Button button2)
/// <returns>The identifier of the sent message.</returns>
public static Task<string?> SendAsync(this IWhatsAppClient client, Message to, string message, Button button1, Button button2)
=> SendAsync(client, to.To.Id, to.From.Number, message, button1, button2);

/// <summary>
Expand All @@ -268,7 +276,8 @@ public static Task SendAsync(this IWhatsAppClient client, Message to, string mes
/// <param name="button1">Interactive button for a user choice.</param>
/// <param name="button2">Interactive button for a user choice.</param>
/// <param name="button3">Interactive button for a user choice.</param>
public static Task SendAsync(this IWhatsAppClient client, Message to, string message, Button button1, Button button2, Button button3)
/// <returns>The identifier of the sent message.</returns>
public static Task<string?> SendAsync(this IWhatsAppClient client, Message to, string message, Button button1, Button button2, Button button3)
=> SendAsync(client, to.To.Id, to.From.Number, message, button1, button2, button3);

/// <summary>
Expand All @@ -278,7 +287,8 @@ public static Task SendAsync(this IWhatsAppClient client, Message to, string mes
/// <param name="from">The service number to send the message through.</param>
/// <param name="to">The user phone number to send the message to.</param>
/// <param name="message">The text message to send.</param>
public static Task SendAsync(this IWhatsAppClient client, string from, string to, string message)
/// <returns>The identifier of the sent message.</returns>
public static Task<string?> SendAsync(this IWhatsAppClient client, string from, string to, string message)
=> client.SendAsync(from, new
{
messaging_product = "whatsapp",
Expand All @@ -300,7 +310,8 @@ public static Task SendAsync(this IWhatsAppClient client, string from, string to
/// <param name="to">The user phone number to send the message to.</param>
/// <param name="message">The text message to send.</param>
/// <param name="button">Interactive button for users to reply.</param>
public static Task SendAsync(this IWhatsAppClient client, string from, string to, string message, Button button)
/// <returns>The identifier of the sent message.</returns>
public static Task<string?> SendAsync(this IWhatsAppClient client, string from, string to, string message, Button button)
=> client.SendAsync(from, new
{
messaging_product = "whatsapp",
Expand Down Expand Up @@ -334,7 +345,8 @@ public static Task SendAsync(this IWhatsAppClient client, string from, string to
/// <param name="message">The text message to send.</param>
/// <param name="button1">Interactive button for a user choice.</param>
/// <param name="button2">Interactive button for a user choice.</param>
public static Task SendAsync(this IWhatsAppClient client, string from, string to, string message, Button button1, Button button2)
/// <returns>The identifier of the sent message.</returns>
public static Task<string?> SendAsync(this IWhatsAppClient client, string from, string to, string message, Button button1, Button button2)
=> client.SendAsync(from, new
{
messaging_product = "whatsapp",
Expand Down Expand Up @@ -370,7 +382,8 @@ public static Task SendAsync(this IWhatsAppClient client, string from, string to
/// <param name="button1">Interactive button for a user choice.</param>
/// <param name="button2">Interactive button for a user choice.</param>
/// <param name="button3">Interactive button for a user choice.</param>
public static Task SendAsync(this IWhatsAppClient client, string from, string to, string message, Button button1, Button button2, Button button3)
/// <returns>The identifier of the sent message.</returns>
public static Task<string?> SendAsync(this IWhatsAppClient client, string from, string to, string message, Button button1, Button button2, Button button3)
=> client.SendAsync(from, new
{
messaging_product = "whatsapp",
Expand Down
8 changes: 7 additions & 1 deletion src/WhatsApp/WhatsAppSerializerContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ namespace Devlooped.WhatsApp;
[JsonSerializable(typeof(MediaReference))]
[JsonSerializable(typeof(GraphMethodException))]
[JsonSerializable(typeof(ErrorResponse))]
[JsonSerializable(typeof(SendResponse))]
[JsonSerializable(typeof(MessageId))]
partial class WhatsAppSerializerContext : JsonSerializerContext { }

record ErrorResponse(GraphMethodException Error);
record ErrorResponse(GraphMethodException Error);

record SendResponse(MessageId[] Messages);

record MessageId(string Id);