From 2f3e28390308f0812ede5279cfc6c99cd7e33341 Mon Sep 17 00:00:00 2001
From: Toastie <toastie@toastiet0ast.com>
Date: Sun, 31 Mar 2024 23:44:37 +1300
Subject: [PATCH] Added interactions to Ellie

---
 .../Common/Interaction/EllieInteraction.cs    | 82 +++++++++++++++++++
 .../Interaction/EllieInteractionData.cs       |  8 ++
 .../Interaction/EllieInteractionService.cs    | 20 +++++
 .../Interaction/IEllieInteractionService.cs   |  8 ++
 .../Common/Interaction/SimpleInteraction.cs   | 20 +++++
 5 files changed, 138 insertions(+)
 create mode 100644 src/EllieBot/Common/Interaction/EllieInteraction.cs
 create mode 100644 src/EllieBot/Common/Interaction/EllieInteractionData.cs
 create mode 100644 src/EllieBot/Common/Interaction/EllieInteractionService.cs
 create mode 100644 src/EllieBot/Common/Interaction/IEllieInteractionService.cs
 create mode 100644 src/EllieBot/Common/Interaction/SimpleInteraction.cs

diff --git a/src/EllieBot/Common/Interaction/EllieInteraction.cs b/src/EllieBot/Common/Interaction/EllieInteraction.cs
new file mode 100644
index 0000000..e77fede
--- /dev/null
+++ b/src/EllieBot/Common/Interaction/EllieInteraction.cs
@@ -0,0 +1,82 @@
+namespace EllieBot;
+
+public sealed class EllieInteraction
+{
+    private readonly ulong _authorId;
+    private readonly ButtonBuilder _button;
+    private readonly Func<SocketMessageComponent, Task> _onClick;
+    private readonly bool _onlyAuthor;
+    public DiscordSocketClient Client { get; }
+
+    private readonly TaskCompletionSource<bool> _interactionCompletedSource;
+
+    private IUserMessage message = null!;
+
+    public EllieInteraction(DiscordSocketClient client,
+        ulong authorId,
+        ButtonBuilder button,
+        Func<SocketMessageComponent, Task> onClick,
+        bool onlyAuthor)
+    {
+        _authorId = authorId;
+        _button = button;
+        _onClick = onClick;
+        _onlyAuthor = onlyAuthor;
+        _interactionCompletedSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
+
+        Client = client;
+    }
+
+    public async Task RunAsync(IUserMessage msg)
+    {
+        message = msg;
+
+        Client.InteractionCreated += OnInteraction;
+        await Task.WhenAny(Task.Delay(15_000), _interactionCompletedSource.Task);
+        Client.InteractionCreated -= OnInteraction;
+
+        await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build());
+    }
+
+    private Task OnInteraction(SocketInteraction arg)
+    {
+        if (arg is not SocketMessageComponent smc)
+            return Task.CompletedTask;
+
+        if (smc.Message.Id != message.Id)
+            return Task.CompletedTask;
+
+        if (_onlyAuthor && smc.User.Id != _authorId)
+            return Task.CompletedTask;
+
+        if (smc.Data.CustomId != _button.CustomId)
+            return Task.CompletedTask;
+
+        _ = Task.Run(async () =>
+        {
+            await ExecuteOnActionAsync(smc);
+
+            // this should only be a thing on single-response buttons
+            _interactionCompletedSource.TrySetResult(true);
+
+            if (!smc.HasResponded)
+            {
+                await smc.DeferAsync();
+            }
+        });
+
+        return Task.CompletedTask;
+    }
+
+
+    public MessageComponent CreateComponent()
+    {
+        var comp = new ComponentBuilder()
+            .WithButton(_button);
+
+        return comp.Build();
+    }
+
+    public Task ExecuteOnActionAsync(SocketMessageComponent smc)
+        => _onClick(smc);
+}
\ No newline at end of file
diff --git a/src/EllieBot/Common/Interaction/EllieInteractionData.cs b/src/EllieBot/Common/Interaction/EllieInteractionData.cs
new file mode 100644
index 0000000..3e25606
--- /dev/null
+++ b/src/EllieBot/Common/Interaction/EllieInteractionData.cs
@@ -0,0 +1,8 @@
+namespace EllieBot;
+
+/// <summary>
+/// Represents essential interacation data
+/// </summary>
+/// <param name="Emote">Emote which will show on a button</param>
+/// <param name="CustomId">Custom interaction id</param>
+public record EllieInteractionData(IEmote Emote, string CustomId, string? Text = null);
\ No newline at end of file
diff --git a/src/EllieBot/Common/Interaction/EllieInteractionService.cs b/src/EllieBot/Common/Interaction/EllieInteractionService.cs
new file mode 100644
index 0000000..3e97ec1
--- /dev/null
+++ b/src/EllieBot/Common/Interaction/EllieInteractionService.cs
@@ -0,0 +1,20 @@
+namespace EllieBot;
+
+public class EllieInteractionService : IEllieInteractionService, IEService
+{
+    private readonly DiscordSocketClient _client;
+
+    public EllieInteractionService(DiscordSocketClient client)
+    {
+        _client = client;
+    }
+
+    public EllieInteraction Create<T>(
+        ulong userId,
+        SimpleInteraction<T> inter)
+        => new EllieInteraction(_client,
+            userId,
+            inter.Button,
+            inter.TriggerAsync,
+            onlyAuthor: true);
+}
\ No newline at end of file
diff --git a/src/EllieBot/Common/Interaction/IEllieInteractionService.cs b/src/EllieBot/Common/Interaction/IEllieInteractionService.cs
new file mode 100644
index 0000000..03a3ba1
--- /dev/null
+++ b/src/EllieBot/Common/Interaction/IEllieInteractionService.cs
@@ -0,0 +1,8 @@
+namespace EllieBot;
+
+public interface IEllieInteractionService
+{
+    public EllieInteraction Create<T>(
+        ulong userId,
+        SimpleInteraction<T> inter);
+}
\ No newline at end of file
diff --git a/src/EllieBot/Common/Interaction/SimpleInteraction.cs b/src/EllieBot/Common/Interaction/SimpleInteraction.cs
new file mode 100644
index 0000000..045d4fc
--- /dev/null
+++ b/src/EllieBot/Common/Interaction/SimpleInteraction.cs
@@ -0,0 +1,20 @@
+namespace EllieBot;
+
+public class SimpleInteraction<T>
+{
+    public ButtonBuilder Button { get; }
+    private readonly Func<SocketMessageComponent, T, Task> _onClick;
+    private readonly T? _state;
+
+    public SimpleInteraction(ButtonBuilder button, Func<SocketMessageComponent, T?, Task> onClick, T? state = default)
+    {
+        Button = button;
+        _onClick = onClick;
+        _state = state;
+    }
+
+    public async Task TriggerAsync(SocketMessageComponent smc)
+    {
+        await _onClick(smc, _state!);
+    }
+}
\ No newline at end of file