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
120 changes: 57 additions & 63 deletions src/SampleApp/Sample/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization;
using Devlooped.WhatsApp;
using Microsoft.Azure.Functions.Worker.Builder;
Expand Down Expand Up @@ -34,71 +35,64 @@
});

builder.Services
.AddWhatsApp<IWhatsAppClient, ILogger<Program>, JsonSerializerOptions>(async (client, logger, options, messages, cancellation) =>
{
var message = messages.Last();
logger.LogInformation("💬 Received message: {Message}", message);
.AddWhatsApp<ILogger<Program>, JsonSerializerOptions>(ProcessMessagesAsync)
// Matches what we use in ConfigureOpenTelemetry
.UseOpenTelemetry(builder.Environment.ApplicationName)
.UseLogging();

if (message is ErrorMessage error)
{
// Reengagement error, we need to invite the user.
if (error.Error.Code == 131047)
{
await client.SendAsync(error.To.Id, new
{
messaging_product = "whatsapp",
to = error.From.Number,
type = "template",
template = new
{
name = "reengagement",
language = new
{
code = "es_AR"
}
}
});
}
else
{
logger.LogWarning("⚠️ Unknown error message received: {Error}", message);
}
return;
}
else if (message is InteractiveMessage interactive)
{
logger.LogWarning("👤 chose {Button} ({Title})", interactive.Button.Id, interactive.Button.Title);
await client.ReplyAsync(interactive, $"👤 chose: {interactive.Button.Title} ({interactive.Button.Id})");
return;
}
else if (message is ReactionMessage reaction)
{
logger.LogInformation("👤 reaction: {Reaction}", reaction.Emoji);
await client.ReplyAsync(reaction, $"👤 reaction: {reaction.Emoji}");
return;
}
else if (message is StatusMessage status)
{
logger.LogInformation("☑️ status: {Status}", status.Status);
return;
}
else if (message is ContentMessage content)
builder.Build().Run();

static async IAsyncEnumerable<Response> ProcessMessagesAsync(
ILogger<Program> logger,
JsonSerializerOptions options,
IEnumerable<Message> messages,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
// Avoid warning CS1998 // Async method lacks 'await' operators and will run synchronously
await Task.CompletedTask;

var message = messages.Last();
logger.LogInformation("💬 Received message: {Message}", message);

if (message is ErrorMessage error)
{
// Reengagement error, we need to invite the user.
if (error.Error.Code == 131047)
{
await client.ReactAsync(content, "🧠");
// simulate some hard work at hand, like doing some LLM-stuff :)
//await Task.Delay(2000);
await client.ReplyAsync(content, $"☑️ Got your {content.Content.Type}:\r\n{JsonSerializer.Serialize(content, options)}",
new Button("btn_good", "👍"),
new Button("btn_bad", "👎"));
yield return error.Reengage();
}
else if (message is UnsupportedMessage unsupported)
else
{
logger.LogWarning("⚠️ {Message}", unsupported);
return;
logger.LogWarning("⚠️ Unknown error message received: {Error}", message);
}
})
// Matches what we use in ConfigureOpenTelemetry
.UseOpenTelemetry(builder.Environment.ApplicationName)
.UseLogging();
}
else if (message is InteractiveMessage interactive)
{
logger.LogWarning("👤 chose {Button} ({Title})", interactive.Button.Id, interactive.Button.Title);
yield return interactive.Text($"👤 chose: {interactive.Button.Title} ({interactive.Button.Id})");
}
else if (message is ReactionMessage reaction)
{
logger.LogInformation("👤 reaction: {Reaction}", reaction.Emoji);
yield return reaction.Text($"👤 reaction: {reaction.Emoji}");
}
else if (message is StatusMessage status)
{
logger.LogInformation("☑️ status: {Status}", status.Status);
}
else if (message is ContentMessage content)
{
yield return content.React("🧠");

builder.Build().Run();
// simulate some hard work at hand, like doing some LLM-stuff :)
//await Task.Delay(2000);
yield return content.TextWithButtons(
$"☑️ Got your {content.Content.Type}:\r\n{JsonSerializer.Serialize(content, options)}",
new Button("btn_good", "👍"),
new Button("btn_bad", "👎"));
}
else if (message is UnsupportedMessage unsupported)
{
logger.LogWarning("⚠️ {Message}", unsupported);
}
}
3 changes: 2 additions & 1 deletion src/Tests/PipelineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ public async Task CanBuildLoggingPipeline()
Assert.True(before);
Assert.True(target);
target = true;
return Task.CompletedTask;

return AsyncEnumerable.Empty<Response>();
}))
.Use((message, inner, cancellation) =>
{
Expand Down
4 changes: 2 additions & 2 deletions src/Tests/WhatsAppHandlerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
public static class WhatsAppHandlerExtensions
{
public static Task HandleAsync(this IWhatsAppHandler handler, Message message, CancellationToken cancellation = default)
=> handler.HandleAsync([message], cancellation);
}
=> handler.HandleAsync([message], cancellation).ForEachAsync(x => { }, cancellation);
}
6 changes: 3 additions & 3 deletions src/WhatsApp/AnonymousDelegatingWhatsAppHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ namespace Devlooped.WhatsApp;
/// <param name="handlerFunc">A delegate that provides the implementation for <see cref="HandleAsync"/></param>
class AnonymousDelegatingWhatsAppHandler(
IWhatsAppHandler innerHandler,
Func<IEnumerable<Message>, IWhatsAppHandler, CancellationToken, Task> handlerFunc) : DelegatingWhatsAppHandler(innerHandler)
Func<IEnumerable<Message>, IWhatsAppHandler, CancellationToken, IAsyncEnumerable<Response>> handlerFunc) : DelegatingWhatsAppHandler(innerHandler)
{
/// <summary>The delegate to use as the implementation of <see cref="Handle"/>.</summary>
readonly Func<IEnumerable<Message>, IWhatsAppHandler, CancellationToken, Task> handlerFunc = Throw.IfNull(handlerFunc);
readonly Func<IEnumerable<Message>, IWhatsAppHandler, CancellationToken, IAsyncEnumerable<Response>> handlerFunc = Throw.IfNull(handlerFunc);

public override Task HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default)
public override IAsyncEnumerable<Response> HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default)
=> handlerFunc(messages, InnerHandler, cancellation);
}
48 changes: 24 additions & 24 deletions src/WhatsApp/AnonymousWhatsAppHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,101 +6,101 @@
public class AnonymousWhatsAppHandler : IWhatsAppHandler
{
readonly IServiceProvider services;
readonly Func<IServiceProvider, IEnumerable<Message>, CancellationToken, Task> handler;
readonly Func<IServiceProvider, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler;

AnonymousWhatsAppHandler(IServiceProvider services, Func<IServiceProvider, IEnumerable<Message>, CancellationToken, Task> handler)
AnonymousWhatsAppHandler(IServiceProvider services, Func<IServiceProvider, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler)
=> (this.services, this.handler) = (Throw.IfNull(services), Throw.IfNull(handler));

AnonymousWhatsAppHandler(IServiceProvider services, Func<IEnumerable<Message>, CancellationToken, Task> handler)
AnonymousWhatsAppHandler(IServiceProvider services, Func<IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler)
: this(services, (_, messages, cancellation) => handler(messages, cancellation)) { }

/// <inheritdoc />
public Task HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default)
public IAsyncEnumerable<Response> HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default)
=> handler(services, messages, cancellation);

/// <summary>
/// Creates a new instance of an <see cref="IWhatsAppHandler"/> with the specified service provider and message
/// handler.
/// </summary>
public static IWhatsAppHandler Create(IServiceProvider services, Func<IEnumerable<Message>, CancellationToken, Task> handler)
public static IWhatsAppHandler Create(IServiceProvider services, Func<IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler)
=> new AnonymousWhatsAppHandler(services, handler);

/// <summary>
/// Creates a new instance of an <see cref="IWhatsAppHandler"/> with the specified service provider and message
/// handler.
/// </summary>
public static IWhatsAppHandler Create(IServiceProvider services, Func<IServiceProvider, IEnumerable<Message>, CancellationToken, Task> handler)
public static IWhatsAppHandler Create(IServiceProvider services, Func<IServiceProvider, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler)
=> new AnonymousWhatsAppHandler(services, handler);

/// <summary>
/// Creates a new instance of an <see cref="IWhatsAppHandler"/> using the specified service and message handler
/// function.
/// </summary>
public static IWhatsAppHandler Create<TService>(TService service, Func<TService, IEnumerable<Message>, CancellationToken, Task> handler)
public static IWhatsAppHandler Create<TService>(TService service, Func<TService, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler)
=> new AnonymousWhatsAppHandler1<TService>(service, handler);

/// <summary>
/// Creates a new instance of a WhatsApp message handler that processes messages using the specified services and
/// handler function.
/// </summary>
public static IWhatsAppHandler Create<TService1, TService2>(TService1 service1, TService2 service2, Func<TService1, TService2, IEnumerable<Message>, CancellationToken, Task> handler)
public static IWhatsAppHandler Create<TService1, TService2>(TService1 service1, TService2 service2, Func<TService1, TService2, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler)
=> new AnonymousWhatsAppHandler2<TService1, TService2>(service1, service2, handler);

/// <summary>
/// Creates a new instance of a WhatsApp message handler that processes messages using the specified services and
/// handler function.
/// </summary>
public static IWhatsAppHandler Create<TService1, TService2, TService3>(TService1 service1, TService2 service2, TService3 service3, Func<TService1, TService2, TService3, IEnumerable<Message>, CancellationToken, Task> handler)
public static IWhatsAppHandler Create<TService1, TService2, TService3>(TService1 service1, TService2 service2, TService3 service3, Func<TService1, TService2, TService3, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler)
=> new AnonymousWhatsAppHandler3<TService1, TService2, TService3>(service1, service2, service3, handler);

/// <summary>
/// Creates a new instance of a WhatsApp message handler that processes messages using the specified services and
/// handler function.
/// </summary>
public static IWhatsAppHandler Create<TService1, TService2, TService3, TService4>(TService1 service1, TService2 service2, TService3 service3, TService4 service4, Func<TService1, TService2, TService3, TService4, IEnumerable<Message>, CancellationToken, Task> handler)
public static IWhatsAppHandler Create<TService1, TService2, TService3, TService4>(TService1 service1, TService2 service2, TService3 service3, TService4 service4, Func<TService1, TService2, TService3, TService4, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler)
=> new AnonymousWhatsAppHandler4<TService1, TService2, TService3, TService4>(service1, service2, service3, service4, handler);

/// <summary>
/// Creates a new instance of a WhatsApp message handler that processes messages using the specified services and
/// handler function.
/// </summary>
public static IWhatsAppHandler Create<TService1, TService2, TService3, TService4, TService5>(TService1 service1, TService2 service2, TService3 service3, TService4 service4, TService5 service5, Func<TService1, TService2, TService3, TService4, TService5, IEnumerable<Message>, CancellationToken, Task> handler)
public static IWhatsAppHandler Create<TService1, TService2, TService3, TService4, TService5>(TService1 service1, TService2 service2, TService3 service3, TService4 service4, TService5 service5, Func<TService1, TService2, TService3, TService4, TService5, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler)
=> new AnonymousWhatsAppHandler5<TService1, TService2, TService3, TService4, TService5>(service1, service2, service3, service4, service5, handler);

/// <summary>
/// Creates a new instance of a WhatsApp message handler that processes messages using the specified services and
/// handler function.
/// </summary>
public static IWhatsAppHandler Create<TService1, TService2, TService3, TService4, TService5, TService6>(TService1 service1, TService2 service2, TService3 service3, TService4 service4, TService5 service5, TService6 service6, Func<TService1, TService2, TService3, TService4, TService5, TService6, IEnumerable<Message>, CancellationToken, Task> handler)
public static IWhatsAppHandler Create<TService1, TService2, TService3, TService4, TService5, TService6>(TService1 service1, TService2 service2, TService3 service3, TService4 service4, TService5 service5, TService6 service6, Func<TService1, TService2, TService3, TService4, TService5, TService6, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler)
=> new AnonymousWhatsAppHandler6<TService1, TService2, TService3, TService4, TService5, TService6>(service1, service2, service3, service4, service5, service6, handler);

class AnonymousWhatsAppHandler1<TService>(TService service, Func<TService, IEnumerable<Message>, CancellationToken, Task> handler) : IWhatsAppHandler
class AnonymousWhatsAppHandler1<TService>(TService service, Func<TService, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler) : IWhatsAppHandler
{
public Task HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default) => handler(service, messages, cancellation);
public IAsyncEnumerable<Response> HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default) => handler(service, messages, cancellation);
}

class AnonymousWhatsAppHandler2<TService1, TService2>(TService1 service1, TService2 service2, Func<TService1, TService2, IEnumerable<Message>, CancellationToken, Task> handler) : IWhatsAppHandler
class AnonymousWhatsAppHandler2<TService1, TService2>(TService1 service1, TService2 service2, Func<TService1, TService2, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler) : IWhatsAppHandler
{
public Task HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default) => handler(service1, service2, messages, cancellation);
public IAsyncEnumerable<Response> HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default) => handler(service1, service2, messages, cancellation);
}

class AnonymousWhatsAppHandler3<TService1, TService2, TService3>(TService1 service1, TService2 service2, TService3 service3, Func<TService1, TService2, TService3, IEnumerable<Message>, CancellationToken, Task> handler) : IWhatsAppHandler
class AnonymousWhatsAppHandler3<TService1, TService2, TService3>(TService1 service1, TService2 service2, TService3 service3, Func<TService1, TService2, TService3, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler) : IWhatsAppHandler
{
public Task HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default) => handler(service1, service2, service3, messages, cancellation);
public IAsyncEnumerable<Response> HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default) => handler(service1, service2, service3, messages, cancellation);
}

class AnonymousWhatsAppHandler4<TService1, TService2, TService3, TService4>(TService1 service1, TService2 service2, TService3 service3, TService4 service4, Func<TService1, TService2, TService3, TService4, IEnumerable<Message>, CancellationToken, Task> handler) : IWhatsAppHandler
class AnonymousWhatsAppHandler4<TService1, TService2, TService3, TService4>(TService1 service1, TService2 service2, TService3 service3, TService4 service4, Func<TService1, TService2, TService3, TService4, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler) : IWhatsAppHandler
{
public Task HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default) => handler(service1, service2, service3, service4, messages, cancellation);
public IAsyncEnumerable<Response> HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default) => handler(service1, service2, service3, service4, messages, cancellation);
}

class AnonymousWhatsAppHandler5<TService1, TService2, TService3, TService4, TService5>(TService1 service1, TService2 service2, TService3 service3, TService4 service4, TService5 service5, Func<TService1, TService2, TService3, TService4, TService5, IEnumerable<Message>, CancellationToken, Task> handler) : IWhatsAppHandler
class AnonymousWhatsAppHandler5<TService1, TService2, TService3, TService4, TService5>(TService1 service1, TService2 service2, TService3 service3, TService4 service4, TService5 service5, Func<TService1, TService2, TService3, TService4, TService5, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler) : IWhatsAppHandler
{
public Task HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default) => handler(service1, service2, service3, service4, service5, messages, cancellation);
public IAsyncEnumerable<Response> HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default) => handler(service1, service2, service3, service4, service5, messages, cancellation);
}

class AnonymousWhatsAppHandler6<TService1, TService2, TService3, TService4, TService5, TService6>(TService1 service1, TService2 service2, TService3 service3, TService4 service4, TService5 service5, TService6 service6, Func<TService1, TService2, TService3, TService4, TService5, TService6, IEnumerable<Message>, CancellationToken, Task> handler) : IWhatsAppHandler
class AnonymousWhatsAppHandler6<TService1, TService2, TService3, TService4, TService5, TService6>(TService1 service1, TService2 service2, TService3 service3, TService4 service4, TService5 service5, TService6 service6, Func<TService1, TService2, TService3, TService4, TService5, TService6, IEnumerable<Message>, CancellationToken, IAsyncEnumerable<Response>> handler) : IWhatsAppHandler
{
public Task HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default) => handler(service1, service2, service3, service4, service5, service6, messages, cancellation);
public IAsyncEnumerable<Response> HandleAsync(IEnumerable<Message> messages, CancellationToken cancellation = default) => handler(service1, service2, service3, service4, service5, service6, messages, cancellation);
}
}
Loading