Skip to content

Commit b146fb4

Browse files
committed
Add documentation and improve sample
1 parent f557fe8 commit b146fb4

File tree

2 files changed

+138
-7
lines changed

2 files changed

+138
-7
lines changed

readme.md

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,137 @@
1-
![Icon](assets/img/icon.png) WhatsApp agents for Azure Functions
1+
![Icon](assets/img/icon.png) WhatsApp agents for Azure Functions
22
============
33

4+
[![Version](https://img.shields.io/nuget/vpre/Devlooped.WhatsApp.svg?color=royalblue)](https://www.nuget.org/packages/Devlooped.WhatsApp)
5+
[![Downloads](https://img.shields.io/nuget/dt/Devlooped.WhatsApp.svg?color=green)](https://www.nuget.org/packages/Devlooped.WhatsApp)
6+
[![License](https://img.shields.io/github/license/devlooped/WhatsApp.svg?color=blue)](https://github.com//devlooped/WhatsApp/blob/main/license.txt)
7+
[![Build](https://github.com/devlooped/WhatsApp/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/devlooped/WhatsApp/actions/workflows/build.yml)
8+
49
Create agents for WhatsApp using Azure Functions.
510

11+
## Usage
12+
13+
```csharp
14+
var builder = FunctionsApplication.CreateBuilder(args);
15+
builder.ConfigureFunctionsWebApplication();
16+
17+
builder.UseWhatsApp<MyWhatsAppHandler>();
18+
19+
builder.Build().Run();
20+
```
21+
22+
Instead of providing an `IWhatsAppHandler` implementation, you can also
23+
register an inline handler using minimal API style:
24+
25+
```csharp
26+
builder.UseWhatsApp(message =>
27+
{
28+
// MessageType: Content | Error | Interactive | Status
29+
Console.WriteLine($"Got message type {message.Type}");
30+
switch (message)
31+
{
32+
case ContentMessage content:
33+
// ContentType = Text | Contact | Document | Image | Audio | Video | Location | Unknown (raw JSON)
34+
Console.WriteLine($"Got content type {content.Content.Type}");
35+
break;
36+
case ErrorMessage error:
37+
Console.WriteLine($"Error: {error.Error.Message} ({error.Error.Code})");
38+
break;
39+
case InteractiveMessage interactive:
40+
Console.WriteLine($"Interactive: {interactive.Button.Title} ({interactive.Button.Id})");
41+
break;
42+
case StatusMessage status:
43+
Console.WriteLine($"Status: {status.Status}");
44+
break;
45+
}
46+
return Task.CompletedTask;
47+
});
48+
```
49+
50+
If the handler needs additional services, they can be provided directly
51+
as generic parameters of the `UseWhatsApp` method, such as:
52+
53+
```csharp
54+
builder.UseWhatsApp<IWhatsAppClient, ILogger<Program>>(async (client, logger, message) =>
55+
{
56+
logger.LogInformation($"Got message type {message.Type}");
57+
// Reply to an incoming content message, for example.
58+
if (message is ContentMessage content)
59+
await client.SendTextAync(message.To.Id, message.From.Number, $"Got your {content.}");
60+
}
61+
```
62+
63+
You can also specify the parameter types in the delegate itself and avoid the
64+
generic parameters:
65+
66+
```csharp
67+
builder.UseWhatsApp(async (IWhatsAppClient client, ILogger<Program> logger, Message message) =>
68+
```
69+
70+
The provided `IWhatsAppClient` provides a very thin abstraction allowing you to send
71+
arbitrary payloads to WhatsApp for Business:
72+
73+
```csharp
74+
public interface IWhatsAppClient
75+
{
76+
/// Payloads from https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages
77+
Task SendAync(string from, object payload);
78+
}
79+
```
80+
81+
Extensions methods for this interface take care of simplifying usage for some
82+
common scenarios, such as reacting to a message and replying with plain text:
83+
84+
```csharp
85+
if (message is ContentMessage content)
86+
{
87+
await client.ReactAsync(from: message.To.Id, to: message.From.Number, message.Id, "🧠");
88+
// simulate some hard work at hand, like doing some LLM-stuff :)
89+
await Task.Delay(2000);
90+
await client.SendTextAync(message.To.Id, message.From.Number, $"☑️ Processed your {content.Type}");
91+
}
92+
```
93+
94+
## Configuration
95+
96+
You need to register an app in the Meta [App Dashboard](https://developers.facebook.com/apps/].
97+
The app must then be configured to use the WhatsApp Business API, and the webhook and
98+
verification token (an arbitrary value) must be set up in the app settings under WhatsApp.
99+
The webhook URL is `/whatsapp` under your Azure Functions app.
100+
101+
Make sure you subscribe the webhook to the `messages` field, with API version `v22.0` or later.
102+
103+
Configuration on the Azure Functions side is done via the
104+
[ASP.NET options pattern](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options)
105+
and the `MetaOptions` type. When you call `UseWhatsApp`, the options will be bound by
106+
default to the `Meta` section in the configuration. You can also configure it programmatically
107+
as follows:
108+
109+
```csharp
110+
builder.Services.Configure<MetaOptions>(options =>
111+
{
112+
options.VerifyToken = "my-webhook-1234";
113+
options.Numbers["12345678"] = "asff=";
114+
});
115+
```
116+
117+
Via configuration:
118+
119+
```json
120+
{
121+
"Meta": {
122+
"VerifyToken": "my-webhook-1234",
123+
"Numbers": {
124+
"12345678": "asff="
125+
}
126+
}
127+
}
128+
```
129+
130+
The `Numbers` dictionary is a map of WhatsApp phone identifiers and the
131+
corresponding access token for it. To get a permanent access token for
132+
use, you'd need to create a [system user](https://business.facebook.com/latest/settings/system_users)
133+
with full control permissions to the WhatsApp Business API (app).
134+
6135
## License
7136

8137
We offer this project under a dual licensing model, tailored to the needs

src/Sample/Program.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
builder.ConfigureFunctionsWebApplication();
1010
builder.Configuration.AddUserSecrets<Program>();
1111

12-
builder.UseWhatsApp<IWhatsAppClient, ILogger<IWhatsAppClient>>(async (client, logger, message) =>
12+
builder.UseWhatsApp<IWhatsAppClient, ILogger<Program>>(async (client, logger, message) =>
1313
{
1414
logger.LogInformation("💬 Received message: {Message}", message);
1515

@@ -49,11 +49,13 @@
4949
logger.LogInformation("☑️ New message status: {Status}", status.Status);
5050
return;
5151
}
52-
53-
await client.ReactAsync(message.To.Id, message.From.Number, message.Id, "🧠");
54-
// simulate some hard work at hand, like doing some LLM-stuff :)
55-
await Task.Delay(2000);
56-
await client.SendTextAync(message.To.Id, message.From.Number, "I'm alive, but I'm just a sample 🤷‍.");
52+
else if (message is ContentMessage content)
53+
{
54+
await client.ReactAsync(from: message.To.Id, to: message.From.Number, message.Id, "🧠");
55+
// simulate some hard work at hand, like doing some LLM-stuff :)
56+
await Task.Delay(2000);
57+
await client.SendTextAync(message.To.Id, message.From.Number, $"☑️ Got your {content.Type.ToString().ToLowerInvariant()}");
58+
}
5759
});
5860

5961
builder.Build().Run();

0 commit comments

Comments
 (0)