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
5 changes: 5 additions & 0 deletions src/Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@
//await Task.Delay(2000);
await client.ReplyAsync(message, $"☑️ Got your {content.Content.Type}:\r\n{JsonSerializer.Serialize(content, options)}");
}
else if (message is UnsupportedMessage unsupported)
{
await client.ReactAsync(message, "⚠️");
return;
}
});

builder.Build().Run();
42 changes: 42 additions & 0 deletions src/Tests/Content/WhatsApp/Unsupported.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "837625914708254",
"changes": [
{
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "5492847619038",
"phone_number_id": "781364925037481"
},
"contacts": [
{
"profile": { "name": "User745" },
"wa_id": "5493726104859"
}
],
"messages": [
{
"from": "5493726104859",
"id": "wamid.HBgNNTQ5MzcyNjEwNDg1OVUCABIYFjJCRDM5RTg0QkY3OEQxMjM2RkE0QjcA",
"timestamp": "1678945321",
"errors": [
{
"code": 131051,
"title": "Message type unknown",
"message": "Message type unknown",
"error_data": { "details": "Message type is currently not supported." }
}
],
"type": "unsupported"
}
]
},
"field": "messages"
}
]
}
]
}
13 changes: 13 additions & 0 deletions src/Tests/WhatsAppModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,17 @@ public async Task DeserializeInteractive()
Assert.Equal("btn_yes", interactive.Button.Id);
Assert.Equal("Yes", interactive.Button.Title);
}

[Fact]
public async Task DeserializeUnsupported()
{
var json = await File.ReadAllTextAsync($"Content/WhatsApp/Unsupported.json");
var message = await Message.DeserializeAsync(json);

var unsupported = Assert.IsType<UnsupportedMessage>(message);

Assert.NotNull(message);
Assert.NotNull(message.To);
Assert.NotNull(message.From);
}
}
8 changes: 8 additions & 0 deletions src/WhatsApp/Content.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public abstract record Content
public record DocumentContent(string Id, string Name, string Mime, string Sha256) : Content
{
/// <inheritdoc/>
[JsonIgnore]
public override ContentType Type => ContentType.Document;
}

Expand All @@ -46,6 +47,7 @@ public record DocumentContent(string Id, string Name, string Mime, string Sha256
public record ContactContent(string Name, string Surname, string[] Numbers) : Content
{
/// <inheritdoc/>
[JsonIgnore]
public override ContentType Type => ContentType.Contact;
}

Expand All @@ -56,6 +58,7 @@ public record ContactContent(string Name, string Surname, string[] Numbers) : Co
public record TextContent(string Text) : Content
{
/// <inheritdoc/>
[JsonIgnore]
public override ContentType Type => ContentType.Text;
}

Expand All @@ -76,6 +79,7 @@ public record Location(double Latitude, double Longitude);
public record LocationContent(Location Location, string? Address, string? Name, string? Url) : Content
{
/// <inheritdoc/>
[JsonIgnore]
public override ContentType Type => ContentType.Location;
}

Expand All @@ -96,6 +100,7 @@ public abstract record MediaContent(string Id, string Mime, string Sha256) : Con
public record AudioContent(string Id, string Mime, string Sha256) : MediaContent(Id, Mime, Sha256)
{
/// <inheritdoc/>
[JsonIgnore]
public override ContentType Type => ContentType.Audio;
}

Expand All @@ -108,6 +113,7 @@ public record AudioContent(string Id, string Mime, string Sha256) : MediaContent
public record ImageContent(string Id, string Mime, string Sha256) : MediaContent(Id, Mime, Sha256)
{
/// <inheritdoc/>
[JsonIgnore]
public override ContentType Type => ContentType.Image;
}

Expand All @@ -120,6 +126,7 @@ public record ImageContent(string Id, string Mime, string Sha256) : MediaContent
public record VideoContent(string Id, string Mime, string Sha256) : MediaContent(Id, Mime, Sha256)
{
/// <inheritdoc/>
[JsonIgnore]
public override ContentType Type => ContentType.Video;
}

Expand All @@ -130,5 +137,6 @@ public record VideoContent(string Id, string Mime, string Sha256) : MediaContent
public record UnknownContent(JsonElement Raw) : Content
{
/// <inheritdoc/>
[JsonIgnore]
public override ContentType Type => ContentType.Unknown;
}
71 changes: 4 additions & 67 deletions src/WhatsApp/ContentMessage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Devlooped.WhatsApp;
using System.Text.Json.Serialization;

namespace Devlooped.WhatsApp;

/// <summary>
/// A <see cref="Message"/> containing <see cref="Content"/>.
Expand All @@ -10,72 +12,7 @@
/// <param name="Content">Message content.</param>
public record ContentMessage(string Id, Service To, User From, long Timestamp, Content Content) : Message(Id, To, From, Timestamp)
{
/// <summary>
/// A JQ query that transforms WhatsApp Cloud API JSON into polymorphic JSON for
/// C# deserialization of a <see cref="ContentMessage"/>.
/// </summary>
public const string JQ =
"""
.entry[].changes[].value.metadata as $phone |
.entry[].changes[].value.contacts[]? as $user |
.entry[].changes[].value.messages[]? |
select(. != null and .type != "interactive") |
.type as $type |
{
id: .id,
timestamp: .timestamp | tonumber,
to: {
id: $phone.phone_number_id,
number: $phone.display_phone_number
},
from: {
name: $user.profile.name,
number: $user.wa_id
},
content: (
if $type == "document" then {
"$type": $type,
id: .document.id,
name: .document.filename,
mime: .document.mime_type,
sha256: .document.sha256
}
elif $type == "contacts" then {
"$type": $type,
name: .contacts[].name.first_name,
surname: .contacts[].name.last_name,
numbers: [.contacts[].phones[] |
select(.wa_id? != null) | .wa_id]
}
elif $type == "text" then {
"$type": $type,
text: .text.body
}
elif $type == "location" then {
"$type": $type,
location: {
latitude: .location.latitude,
longitude: .location.longitude
},
address: .location.address,
name: .location.name,
url: .location.url
}
elif $type == "image" or $type == "video" or $type == "audio" then {
"$type": $type,
id: .[$type].id,
mime: .[$type].mime_type,
sha256: .[$type].sha256
}
else {
"$type": "unknown",
raw: .
}
end
)
}
""";

/// <inheritdoc/>
[JsonIgnore]
public override MessageType Type => MessageType.Content;
}
32 changes: 4 additions & 28 deletions src/WhatsApp/ErrorMessage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Devlooped.WhatsApp;
using System.Text.Json.Serialization;

namespace Devlooped.WhatsApp;

/// <summary>
/// A <see cref="Message"/> containing an <see cref="Error"/>.
Expand All @@ -10,34 +12,8 @@
/// <param name="Error">The error.</param>
public record ErrorMessage(string Id, Service To, User From, long Timestamp, Error Error) : Message(Id, To, From, Timestamp)
{
/// <summary>
/// A JQ query that transforms WhatsApp Cloud API JSON into the serialization
/// expected by <see cref="ErrorMessage"/>.
/// </summary>
public const string JQ =
"""
.entry[].changes[].value.metadata as $phone |
.entry[].changes[].value.statuses[]? |
select(. != null) |
{
id: .id,
timestamp: .timestamp | tonumber,
to: {
id: $phone.phone_number_id,
number: $phone.display_phone_number
},
from: {
name: .recipient_id,
number: .recipient_id
},
error: .errors[]? | {
code: .code,
message: (.error_data.details // .message),
}
}
""";

/// <inheritdoc/>
[JsonIgnore]
public override MessageType Type => MessageType.Error;
}

Expand Down
30 changes: 4 additions & 26 deletions src/WhatsApp/InteractiveMessage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Devlooped.WhatsApp;
using System.Text.Json.Serialization;

namespace Devlooped.WhatsApp;

/// <summary>
/// A <see cref="Message"/> containing an interactive button reply.
Expand All @@ -10,32 +12,8 @@
/// <param name="Button">The button selected by the user.</param>
public record InteractiveMessage(string Id, Service To, User From, long Timestamp, Button Button) : Message(Id, To, From, Timestamp)
{
/// <summary>
/// A JQ query that transforms WhatsApp Cloud API JSON into the serialization
/// expected by <see cref="InteractiveMessage"/>.
/// </summary>
public const string JQ =
"""
.entry[].changes[].value.metadata as $phone |
.entry[].changes[].value.contacts[]? as $user |
.entry[].changes[].value.messages[]? |
select(. != null and .type == "interactive") |
{
id: .id,
timestamp: .timestamp | tonumber,
to: {
id: $phone.phone_number_id,
number: $phone.display_phone_number
},
from: {
name: $user.profile.name,
number: $user.wa_id
},
button: .interactive.button_reply
}
""";

/// <inheritdoc/>
[JsonIgnore]
public override MessageType Type => MessageType.Interactive;
}

Expand Down
Loading