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
59 changes: 59 additions & 0 deletions src/SampleApp/Sample/ProcessHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Runtime.CompilerServices;
using System.Text.Json;
using Devlooped.WhatsApp;
using Microsoft.Extensions.Logging;

class ProcessHandler(ILogger<Program> logger, JsonSerializerOptions options) : IWhatsAppHandler
{
public async IAsyncEnumerable<Response> HandleAsync(IEnumerable<IMessage> messages, [EnumeratorCancellation] CancellationToken cancellation = default)
{
// 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)
{
// Showcases how to use a pre-declared template response to reengage the user.
yield return error.Template("reengagement", "es_AR");
}
else
{
logger.LogWarning("⚠️ Unknown error message received: {Error}", message);
}
}
else if (message is InteractiveMessage interactive)
{
logger.LogWarning("👤 chose {Button} ({Title})", interactive.Button.Id, interactive.Button.Title);
yield return interactive.Reply($"👤 chose: {interactive.Button.Title} ({interactive.Button.Id})");
}
else if (message is ReactionMessage reaction)
{
logger.LogInformation("👤 reaction: {Reaction}", reaction.Emoji);
yield return reaction.Reply($"👤 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("🧠");

// simulate some hard work at hand, like doing some LLM-stuff :)
//await Task.Delay(2000);
yield return content.Reply(
$"☑️ 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);
}
}
}
63 changes: 3 additions & 60 deletions src/SampleApp/Sample/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using Devlooped;
using Devlooped.WhatsApp;
Expand Down Expand Up @@ -42,67 +41,11 @@
throw new InvalidOperationException("Missing required App:Storage connection string."));

builder.Services
.AddWhatsApp<ILogger<Program>, JsonSerializerOptions>(builder.Configuration, ProcessMessagesAsync)
.AddWhatsApp<ProcessHandler>(builder.Configuration)
// Matches what we use in ConfigureOpenTelemetry
.UseOpenTelemetry(builder.Environment.ApplicationName)
.UseLogging()
.UseStorage()
.UseConversation();

builder.Build().Run();

static async IAsyncEnumerable<Response> ProcessMessagesAsync(
ILogger<Program> logger,
JsonSerializerOptions options,
IEnumerable<IMessage> 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)
{
// Showcases how to use a pre-declared template response to reengage the user.
yield return error.Template("reengagement", "es_AR");
}
else
{
logger.LogWarning("⚠️ Unknown error message received: {Error}", message);
}
}
else if (message is InteractiveMessage interactive)
{
logger.LogWarning("👤 chose {Button} ({Title})", interactive.Button.Id, interactive.Button.Title);
yield return interactive.Reply($"👤 chose: {interactive.Button.Title} ({interactive.Button.Id})");
}
else if (message is ReactionMessage reaction)
{
logger.LogInformation("👤 reaction: {Reaction}", reaction.Emoji);
yield return reaction.Reply($"👤 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("🧠");

// simulate some hard work at hand, like doing some LLM-stuff :)
//await Task.Delay(2000);
yield return content.Reply(
$"☑️ 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);
}
}
builder.Build().Run();
7 changes: 5 additions & 2 deletions src/WhatsApp/WhatsAppServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,12 @@ public static WhatsAppHandlerBuilder AddWhatsApp<THandler>(
ServiceLifetime lifetime = ServiceLifetime.Singleton)
where THandler : class, IWhatsAppHandler
{
collection.Add(new ServiceDescriptor(typeof(IWhatsAppHandler), services => services.GetRequiredService<THandler>(), lifetime));
if (collection.FirstOrDefault(x => x.ServiceType == typeof(THandler)) == null)
{
collection.Add(new ServiceDescriptor(typeof(THandler), typeof(THandler), lifetime));
}

return collection.AddWhatsApp(configuration, services => services.GetRequiredService<IWhatsAppHandler>(), lifetime);
return collection.AddWhatsApp(configuration, services => services.GetRequiredService<THandler>(), lifetime);
}

/// <summary>
Expand Down