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
28 changes: 14 additions & 14 deletions src/Tests/WhatsAppModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ public async Task DeserializeMessage(string type, string notification, string id
Assert.Equal(id, message.Id);
}
Assert.Equal(context, message.Context);
Assert.NotNull(message.To);
Assert.NotNull(message.From);
Assert.NotNull(message.Service);
Assert.NotNull(message.User);
}

[Theory]
Expand All @@ -56,8 +56,8 @@ public async Task DeserializeContent(ContentType type)

Assert.NotNull(message);
Assert.NotNull(message.NotificationId);
Assert.NotNull(message.To);
Assert.NotNull(message.From);
Assert.NotNull(message.Service);
Assert.NotNull(message.User);
Assert.NotNull(content.Content);
Assert.Equal(type, content.Content.Type);
}
Expand All @@ -72,8 +72,8 @@ public async Task DeserializeErrorStatus()

Assert.NotNull(message);
Assert.NotNull(message.NotificationId);
Assert.NotNull(message.To);
Assert.NotNull(message.From);
Assert.NotNull(message.Service);
Assert.NotNull(message.User);
Assert.NotNull(error.Error);
Assert.Equal(470, error.Error.Code);
}
Expand All @@ -88,8 +88,8 @@ public async Task DeserializeStatus()

Assert.NotNull(message);
Assert.NotNull(message.NotificationId);
Assert.NotNull(message.To);
Assert.NotNull(message.From);
Assert.NotNull(message.Service);
Assert.NotNull(message.User);
Assert.Equal(Status.Delivered, status.Status);
}

Expand All @@ -103,8 +103,8 @@ public async Task DeserializeInteractive()

Assert.NotNull(message);
Assert.NotNull(message.NotificationId);
Assert.NotNull(message.To);
Assert.NotNull(message.From);
Assert.NotNull(message.Service);
Assert.NotNull(message.User);
Assert.Equal("btn_yes", interactive.Button.Id);
Assert.Equal("Yes", interactive.Button.Title);
}
Expand All @@ -119,8 +119,8 @@ public async Task DeserializeUnsupported()

Assert.NotNull(message);
Assert.NotNull(message.NotificationId);
Assert.NotNull(message.To);
Assert.NotNull(message.From);
Assert.NotNull(message.Service);
Assert.NotNull(message.User);
}

[Fact]
Expand All @@ -133,8 +133,8 @@ public async Task DeserializeReaction()

Assert.NotNull(message);
Assert.NotNull(message.NotificationId);
Assert.NotNull(message.To);
Assert.NotNull(message.From);
Assert.NotNull(message.Service);
Assert.NotNull(message.User);
Assert.Equal("😊", reaction.Emoji);
}
}
8 changes: 4 additions & 4 deletions src/WhatsApp/AzureFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public async Task<IActionResult> Message([HttpTrigger(AuthorizationLevel.Anonymo
// Ensure idempotent processing
var table = tableClient.GetTableClient("whatsapp");
await table.CreateIfNotExistsAsync();
if (await table.GetEntityIfExistsAsync<TableEntity>(message.From.Number, message.NotificationId) is { HasValue: true } existing)
if (await table.GetEntityIfExistsAsync<TableEntity>(message.User.Number, message.NotificationId) is { HasValue: true } existing)
{
logger.LogInformation("Skipping already handled message {Id}", message.Id);
return new OkResult();
Expand All @@ -56,7 +56,7 @@ public async Task<IActionResult> Message([HttpTrigger(AuthorizationLevel.Anonymo
{
// Best-effort to mark as read. This might be an old message callback,
// or the message might have been deleted.
await whatsapp.MarkReadAsync(message.To.Id, message.Id);
await whatsapp.MarkReadAsync(message.Service.Id, message.Id);
}
catch (HttpRequestException e)
{
Expand Down Expand Up @@ -84,7 +84,7 @@ public async Task Process([QueueTrigger("whatsapp", Connection = "AzureWebJobsSt
// happening (and therefore we didn't save the entity yet).
var table = tableClient.GetTableClient("whatsapp");
await table.CreateIfNotExistsAsync();
if (await table.GetEntityIfExistsAsync<TableEntity>(message.From.Number, message.NotificationId) is { HasValue: true } existing)
if (await table.GetEntityIfExistsAsync<TableEntity>(message.User.Number, message.NotificationId) is { HasValue: true } existing)
{
logger.LogInformation("Skipping already handled message {Id}", message.Id);
return;
Expand All @@ -94,7 +94,7 @@ public async Task Process([QueueTrigger("whatsapp", Connection = "AzureWebJobsSt
// No action needed, just make sure all items are processed
await handler.HandleAsync([message]).ToArrayAsync();

await table.UpsertEntityAsync(new TableEntity(message.From.Number, message.Id));
await table.UpsertEntityAsync(new TableEntity(message.User.Number, message.Id));
logger.LogInformation($"Completed work item: {message.Id}");
}
else
Expand Down
44 changes: 22 additions & 22 deletions src/WhatsApp/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ namespace Devlooped.WhatsApp;
/// Base class for WhatsApp Cloud API messages.
/// </summary>
/// <param name="Id">The message identifier.</param>
/// <param name="To">The service that received the message from the Cloud API.</param>
/// <param name="From">The user that sent the message.</param>
/// <param name="Service">The service that received the message from the Cloud API.</param>
/// <param name="User">The user that sent the message.</param>
/// <param name="Timestamp">Timestamp of the message.</param>
[JsonPolymorphic]
[JsonDerivedType(typeof(ContentMessage), "content")]
Expand All @@ -17,7 +17,7 @@ namespace Devlooped.WhatsApp;
[JsonDerivedType(typeof(ReactionMessage), "reaction")]
[JsonDerivedType(typeof(StatusMessage), "status")]
[JsonDerivedType(typeof(UnsupportedMessage), "unsupported")]
public abstract partial record Message(string Id, Service To, User From, long Timestamp) : IMessage
public abstract partial record Message(string Id, Service Service, User User, long Timestamp) : IMessage
{
/// <summary>
/// Optional related message identifier, such as message being replied
Expand Down Expand Up @@ -58,12 +58,12 @@ .value.messages[0] as $msg |
"id": $msg.id,
"context": $context,
"timestamp": $msg.timestamp | tonumber,
"to": {
"service": {
"id": $phone.phone_number_id,
"number": $phone.display_phone_number
},
"from": {
"name": ($user.profile.name // "Unknown"),
"user": {
"name": ($user.profile.name // ""),
"number": $msg.from
},
"button": $msg.interactive.button_reply
Expand All @@ -75,12 +75,12 @@ .value.messages[0] as $msg |
"id": "",
"context": $context,
"timestamp": $msg.timestamp | tonumber,
"to": {
"service": {
"id": $phone.phone_number_id,
"number": $phone.display_phone_number
},
"from": {
"name": ($user.profile.name // "Unknown"),
"user": {
"name": ($user.profile.name // ""),
"number": $msg.from
},
"emoji": $msg.reaction.emoji
Expand All @@ -92,12 +92,12 @@ .value.messages[0] as $msg |
"id": $msg.id,
"context": $context,
"timestamp": $msg.timestamp | tonumber,
"to": {
"service": {
"id": $phone.phone_number_id,
"number": $phone.display_phone_number
},
"from": {
"name": ($user.profile.name // "Unknown"),
"user": {
"name": ($user.profile.name // ""),
"number": $msg.from
},
"content": (
Expand Down Expand Up @@ -149,12 +149,12 @@ .value.messages[0] as $msg |
"id": "",
"context": $context,
"timestamp": $msg.timestamp | tonumber,
"to": {
"service": {
"id": $phone.phone_number_id,
"number": $phone.display_phone_number
},
"from": {
"name": ($user.profile.name // "Unknown"),
"user": {
"name": ($user.profile.name // ""),
"number": $msg.from
},
"raw": $msg
Expand All @@ -178,12 +178,12 @@ .value.statuses[0] as $status |
"notification": $notification,
"id": "",
"timestamp": $status.timestamp | tonumber,
"to": {
"service": {
"id": $phone.phone_number_id,
"number": $phone.display_phone_number
},
"from": {
"name": $status.recipient_id,
"user": {
"name": "",
"number": $status.recipient_id
},
"error": {
Expand All @@ -198,12 +198,12 @@ .value.statuses[0] as $status |
"id": "",
"context": $status.id,
"timestamp": $status.timestamp | tonumber,
"to": {
"service": {
"id": $phone.phone_number_id,
"number": $phone.display_phone_number
},
"from": {
"name": $status.recipient_id,
"user": {
"name": "",
"number": $status.recipient_id
},
"status": $status.status
Expand Down Expand Up @@ -252,5 +252,5 @@ .value.statuses[0] as $status |
public abstract MessageType Type { get; }

/// <inheritdoc/>
public string Number => From.Number;
public string Number => User.Number;
}
12 changes: 6 additions & 6 deletions src/WhatsApp/MessageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ public static partial class MessageExtensions
/// Creates a reaction response for the user message.
/// </summary>
public static ReactionResponse React(this UserMessage message, string emoji)
=> new(message.From.Number, message.To.Id, message.Id, message.ConversationId, emoji);
=> new(message.User.Number, message.Service.Id, message.Id, message.ConversationId, emoji);

/// <summary>
/// Creates a simple template response for the message.
/// </summary>
public static TemplateResponse Template(this Message message, string name, string language)
=> new(message.From.Number, message.To.Id, message.Id, message.ConversationId, name, language);
=> new(message.User.Number, message.Service.Id, message.Id, message.ConversationId, name, language);

/// <summary>
/// Creates a complex template response for the message.
Expand All @@ -28,19 +28,19 @@ public static TemplateResponse Template(this Message message, string name, strin
/// <see cref="https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages/#template-object"/>
/// <see cref="https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages/#components-object"/>
public static TemplateResponse Template(this Message message, object template)
=> new(message.From.Number, message.To.Id, message.Id, message.ConversationId, template);
=> new(message.User.Number, message.Service.Id, message.Id, message.ConversationId, template);

/// <summary>
/// Creates a text response for the message.
/// </summary>
public static TextResponse Reply(this Message message, string text)
=> new(message.From.Number, message.To.Id, message.Id, message.ConversationId, text);
=> new(message.User.Number, message.Service.Id, message.Id, message.ConversationId, text);

/// <summary>
/// Creates a text response with buttons for the message.
/// </summary>
public static TextResponse Reply(this Message message, string text, Button button1, Button? button2 = default)
=> new(message.From.Number, message.To.Id, message.Id, message.ConversationId, text, button1, button2);
=> new(message.User.Number, message.Service.Id, message.Id, message.ConversationId, text, button1, button2);

/// <summary>
/// Attempts to retrieve a single message from the specified collection.
Expand Down Expand Up @@ -70,4 +70,4 @@ internal static bool TrySingle(this IEnumerable<IMessage> messages, [NotNullWhen

return message != null;
}
}
}
2 changes: 1 addition & 1 deletion src/WhatsApp/WhatsAppClientExtensions.ResolveMedia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static async Task<MediaReference> ResolveMediaAsync(this IWhatsAppClient
if (message.Content is not MediaContent media)
throw new NotSupportedException("Message does not contain media.");

return await ResolveMediaAsync(client, message.To.Id, media.Id, cancellation);
return await ResolveMediaAsync(client, message.Service.Id, media.Id, cancellation);
}

/// <summary>
Expand Down
22 changes: 11 additions & 11 deletions src/WhatsApp/WhatsAppClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static HttpClient CreateHttp(this IWhatsAppClient client, Service service
/// Creates an authenticated HTTP client for the service number that received the given message.
/// </summary>
public static HttpClient CreateHttp(this IWhatsAppClient client, Message message)
=> client.CreateHttp(message.To.Id);
=> client.CreateHttp(message.Service.Id);

/// <summary>
/// Marks the message as read. Happens automatically when the <see cref="AzureFunctions.Message(Microsoft.AspNetCore.Http.HttpRequest)"/>
Expand All @@ -25,7 +25,7 @@ public static HttpClient CreateHttp(this IWhatsAppClient client, Message message
/// <param name="message">The message to mark as read.</param>
/// <param name="cancellation">The cancellation token.</param>
public static Task MarkReadAsync(this IWhatsAppClient client, UserMessage message, CancellationToken cancellation = default)
=> MarkReadAsync(client, message.To.Id, message.Id, cancellation);
=> MarkReadAsync(client, message.Service.Id, message.Id, cancellation);

/// <summary>
/// Marks the message as read. Happens automatically when the <see cref="AzureFunctions.Message(Microsoft.AspNetCore.Http.HttpRequest)"/>
Expand All @@ -52,7 +52,7 @@ public static Task MarkReadAsync(this IWhatsAppClient client, string from, strin
/// <param name="cancellation">The cancellation token.</param>
/// <see cref="https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages/#reaction-object"/>
public static Task ReactAsync(this IWhatsAppClient client, UserMessage message, string emoji, CancellationToken cancellation = default)
=> ReactAsync(client, message.To.Id, message.From.Number, message.Id, emoji, cancellation);
=> ReactAsync(client, message.Service.Id, message.From.Number, message.Id, emoji, cancellation);

/// <summary>
/// Reacts to a message.
Expand Down Expand Up @@ -218,12 +218,12 @@ public static Task SendTemplateAsync(this IWhatsAppClient client, string from, s
/// <returns>The identifier of the reply message.</returns>
/// <see cref="https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages/#interactive-object"/>
public static Task<string?> ReplyAsync(this IWhatsAppClient client, UserMessage message, string reply, Button button1, Button button2, Button button3, CancellationToken cancellation = default)
=> client.SendAsync(message.To.Id, new
=> client.SendAsync(message.Service.Id, new
{
messaging_product = "whatsapp",
preview_url = false,
recipient_type = "individual",
to = NormalizeNumber(message.From.Number),
to = NormalizeNumber(message.User.Number),
type = "interactive",
context = new
{
Expand Down Expand Up @@ -258,12 +258,12 @@ public static Task SendTemplateAsync(this IWhatsAppClient client, string from, s
/// <returns>The identifier of the reply message.</returns>
/// <see cref="https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages/#text-object"/>
public static Task<string?> ReplyAsync(this IWhatsAppClient client, ReactionMessage message, string reply, CancellationToken cancellation = default)
=> client.SendAsync(message.To.Id, new
=> client.SendAsync(message.Service.Id, new
{
messaging_product = "whatsapp",
preview_url = false,
recipient_type = "individual",
to = NormalizeNumber(message.From.Number),
to = NormalizeNumber(message.User.Number),
type = "text",
context = new
{
Expand All @@ -285,7 +285,7 @@ public static Task SendTemplateAsync(this IWhatsAppClient client, string from, s
/// <returns>The identifier of the sent message.</returns>
/// <see cref="https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages/#text-object"/>
public static Task<string?> SendAsync(this IWhatsAppClient client, Message to, string message, CancellationToken cancellation = default)
=> SendAsync(client, to.To.Id, to.From.Number, message, cancellation);
=> SendAsync(client, to.Service.Id, to.User.Number, message, cancellation);

/// <summary>
/// Sends a text message a user given his incoming message, without making it a reply.
Expand All @@ -298,7 +298,7 @@ public static Task SendTemplateAsync(this IWhatsAppClient client, string from, s
/// <returns>The identifier of the sent message.</returns>
/// <see cref="https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages/#interactive-object"/>
public static Task<string?> SendAsync(this IWhatsAppClient client, Message to, string message, Button button, CancellationToken cancellation = default)
=> SendAsync(client, to.To.Id, to.From.Number, message, button, cancellation);
=> SendAsync(client, to.Service.Id, to.User.Number, message, button, cancellation);

/// <summary>
/// Sends a text message a user given his incoming message, without making it a reply.
Expand All @@ -312,7 +312,7 @@ public static Task SendTemplateAsync(this IWhatsAppClient client, string from, s
/// <returns>The identifier of the sent message.</returns>
/// <see cref="https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages/#interactive-object"/>
public static Task<string?> SendAsync(this IWhatsAppClient client, Message to, string message, Button button1, Button button2, CancellationToken cancellation = default)
=> SendAsync(client, to.To.Id, to.From.Number, message, button1, button2, cancellation);
=> SendAsync(client, to.Service.Id, to.User.Number, message, button1, button2, cancellation);

/// <summary>
/// Sends a text message a user given his incoming message, without making it a reply.
Expand All @@ -327,7 +327,7 @@ public static Task SendTemplateAsync(this IWhatsAppClient client, string from, s
/// <returns>The identifier of the sent message.</returns>
/// <see cref="https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages/#interactive-object"/>
public static Task<string?> SendAsync(this IWhatsAppClient client, Message to, string message, Button button1, Button button2, Button button3, CancellationToken cancellation = default)
=> SendAsync(client, to.To.Id, to.From.Number, message, button1, button2, button3, cancellation);
=> SendAsync(client, to.Service.Id, to.User.Number, message, button1, button2, button3, cancellation);

/// <summary>
/// Sends a text message a user.
Expand Down