diff --git a/src/Tests/WhatsAppModelTests.cs b/src/Tests/WhatsAppModelTests.cs index 31e4544..bcc539b 100644 --- a/src/Tests/WhatsAppModelTests.cs +++ b/src/Tests/WhatsAppModelTests.cs @@ -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] @@ -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); } @@ -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); } @@ -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); } @@ -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); } @@ -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] @@ -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); } } diff --git a/src/WhatsApp/AzureFunctions.cs b/src/WhatsApp/AzureFunctions.cs index 91ae907..77fc04c 100644 --- a/src/WhatsApp/AzureFunctions.cs +++ b/src/WhatsApp/AzureFunctions.cs @@ -37,7 +37,7 @@ public async Task Message([HttpTrigger(AuthorizationLevel.Anonymo // Ensure idempotent processing var table = tableClient.GetTableClient("whatsapp"); await table.CreateIfNotExistsAsync(); - if (await table.GetEntityIfExistsAsync(message.From.Number, message.NotificationId) is { HasValue: true } existing) + if (await table.GetEntityIfExistsAsync(message.User.Number, message.NotificationId) is { HasValue: true } existing) { logger.LogInformation("Skipping already handled message {Id}", message.Id); return new OkResult(); @@ -56,7 +56,7 @@ public async Task 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) { @@ -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(message.From.Number, message.NotificationId) is { HasValue: true } existing) + if (await table.GetEntityIfExistsAsync(message.User.Number, message.NotificationId) is { HasValue: true } existing) { logger.LogInformation("Skipping already handled message {Id}", message.Id); return; @@ -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 diff --git a/src/WhatsApp/Message.cs b/src/WhatsApp/Message.cs index 16afa42..55e3ad6 100644 --- a/src/WhatsApp/Message.cs +++ b/src/WhatsApp/Message.cs @@ -7,8 +7,8 @@ namespace Devlooped.WhatsApp; /// Base class for WhatsApp Cloud API messages. /// /// The message identifier. -/// The service that received the message from the Cloud API. -/// The user that sent the message. +/// The service that received the message from the Cloud API. +/// The user that sent the message. /// Timestamp of the message. [JsonPolymorphic] [JsonDerivedType(typeof(ContentMessage), "content")] @@ -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 { /// /// Optional related message identifier, such as message being replied @@ -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 @@ -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 @@ -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": ( @@ -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 @@ -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": { @@ -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 @@ -252,5 +252,5 @@ .value.statuses[0] as $status | public abstract MessageType Type { get; } /// - public string Number => From.Number; + public string Number => User.Number; } \ No newline at end of file diff --git a/src/WhatsApp/MessageExtensions.cs b/src/WhatsApp/MessageExtensions.cs index 5d4bdf9..fd23564 100644 --- a/src/WhatsApp/MessageExtensions.cs +++ b/src/WhatsApp/MessageExtensions.cs @@ -11,13 +11,13 @@ public static partial class MessageExtensions /// Creates a reaction response for the user message. /// 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); /// /// Creates a simple template response for the message. /// 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); /// /// Creates a complex template response for the message. @@ -28,19 +28,19 @@ public static TemplateResponse Template(this Message message, string name, strin /// /// 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); /// /// Creates a text response for the message. /// 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); /// /// Creates a text response with buttons for the message. /// 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); /// /// Attempts to retrieve a single message from the specified collection. @@ -70,4 +70,4 @@ internal static bool TrySingle(this IEnumerable messages, [NotNullWhen return message != null; } -} \ No newline at end of file +} diff --git a/src/WhatsApp/WhatsAppClientExtensions.ResolveMedia.cs b/src/WhatsApp/WhatsAppClientExtensions.ResolveMedia.cs index d9d0642..3a3d957 100644 --- a/src/WhatsApp/WhatsAppClientExtensions.ResolveMedia.cs +++ b/src/WhatsApp/WhatsAppClientExtensions.ResolveMedia.cs @@ -19,7 +19,7 @@ public static async Task 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); } /// diff --git a/src/WhatsApp/WhatsAppClientExtensions.cs b/src/WhatsApp/WhatsAppClientExtensions.cs index b8b49ca..78ec5e7 100644 --- a/src/WhatsApp/WhatsAppClientExtensions.cs +++ b/src/WhatsApp/WhatsAppClientExtensions.cs @@ -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. /// public static HttpClient CreateHttp(this IWhatsAppClient client, Message message) - => client.CreateHttp(message.To.Id); + => client.CreateHttp(message.Service.Id); /// /// Marks the message as read. Happens automatically when the @@ -25,7 +25,7 @@ public static HttpClient CreateHttp(this IWhatsAppClient client, Message message /// The message to mark as read. /// The cancellation token. 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); /// /// Marks the message as read. Happens automatically when the @@ -52,7 +52,7 @@ public static Task MarkReadAsync(this IWhatsAppClient client, string from, strin /// The cancellation token. /// 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); /// /// Reacts to a message. @@ -218,12 +218,12 @@ public static Task SendTemplateAsync(this IWhatsAppClient client, string from, s /// The identifier of the reply message. /// public static Task 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 { @@ -258,12 +258,12 @@ public static Task SendTemplateAsync(this IWhatsAppClient client, string from, s /// The identifier of the reply message. /// public static Task 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 { @@ -285,7 +285,7 @@ public static Task SendTemplateAsync(this IWhatsAppClient client, string from, s /// The identifier of the sent message. /// public static Task 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); /// /// Sends a text message a user given his incoming message, without making it a reply. @@ -298,7 +298,7 @@ public static Task SendTemplateAsync(this IWhatsAppClient client, string from, s /// The identifier of the sent message. /// public static Task 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); /// /// Sends a text message a user given his incoming message, without making it a reply. @@ -312,7 +312,7 @@ public static Task SendTemplateAsync(this IWhatsAppClient client, string from, s /// The identifier of the sent message. /// public static Task 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); /// /// Sends a text message a user given his incoming message, without making it a reply. @@ -327,7 +327,7 @@ public static Task SendTemplateAsync(this IWhatsAppClient client, string from, s /// The identifier of the sent message. /// public static Task 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); /// /// Sends a text message a user.