Add preliminary support for buttons

This commit is contained in:
Ske
2021-05-26 22:27:52 +02:00
parent 0b91e71384
commit d7c0592947
11 changed files with 217 additions and 7 deletions

View File

@@ -109,6 +109,8 @@ namespace PluralKit.Bot
await HandleEvent(shard, mdb);
if (evt is MessageReactionAddEvent mra)
await HandleEvent(shard, mra);
if (evt is InteractionCreateEvent ic)
await HandleEvent(shard, ic);
// Update shard status for shards immediately on connect
if (evt is ReadyEvent re)

View File

@@ -0,0 +1,34 @@
using System.Threading.Tasks;
using Autofac;
using Myriad.Gateway;
using Myriad.Types;
namespace PluralKit.Bot
{
public class InteractionCreated: IEventHandler<InteractionCreateEvent>
{
private readonly InteractionDispatchService _interactionDispatch;
private readonly ILifetimeScope _services;
public InteractionCreated(InteractionDispatchService interactionDispatch, ILifetimeScope services)
{
_interactionDispatch = interactionDispatch;
_services = services;
}
public async Task Handle(Shard shard, InteractionCreateEvent evt)
{
if (evt.Type == Interaction.InteractionType.MessageComponent)
{
var customId = evt.Data?.CustomId;
if (customId != null)
{
var ctx = new InteractionContext(evt, _services);
await _interactionDispatch.Dispatch(customId, ctx);
}
}
}
}
}

View File

@@ -72,6 +72,7 @@ namespace PluralKit.Bot
builder.RegisterType<MessageDeleted>().As<IEventHandler<MessageDeleteEvent>>().As<IEventHandler<MessageDeleteBulkEvent>>();
builder.RegisterType<MessageEdited>().As<IEventHandler<MessageUpdateEvent>>();
builder.RegisterType<ReactionAdded>().As<IEventHandler<MessageReactionAddEvent>>();
builder.RegisterType<InteractionCreated>().As<IEventHandler<InteractionCreateEvent>>();
// Event handler queue
builder.RegisterType<HandlerQueue<MessageCreateEvent>>().AsSelf().SingleInstance();
@@ -91,6 +92,7 @@ namespace PluralKit.Bot
builder.RegisterType<LoggerCleanService>().AsSelf().SingleInstance();
builder.RegisterType<ErrorMessageService>().AsSelf().SingleInstance();
builder.RegisterType<CommandMessageService>().AsSelf().SingleInstance();
builder.RegisterType<InteractionDispatchService>().AsSelf().SingleInstance();
// Sentry stuff
builder.Register(_ => new Scope(null)).AsSelf().InstancePerLifetimeScope();

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using NodaTime;
using Serilog;
namespace PluralKit.Bot
{
public class InteractionDispatchService: IDisposable
{
private static readonly Duration DefaultExpiry = Duration.FromMinutes(15);
private readonly ConcurrentDictionary<Guid, RegisteredInteraction> _handlers = new();
private readonly CancellationTokenSource _cts = new();
private readonly IClock _clock;
private readonly ILogger _logger;
private readonly Task _cleanupWorker;
public InteractionDispatchService(IClock clock, ILogger logger)
{
_clock = clock;
_logger = logger.ForContext<InteractionDispatchService>();
_cleanupWorker = CleanupLoop(_cts.Token);
}
public async ValueTask<bool> Dispatch(string customId, InteractionContext context)
{
if (!Guid.TryParse(customId, out var customIdGuid))
return false;
if (!_handlers.TryGetValue(customIdGuid, out var handler))
return false;
await handler.Callback.Invoke(context);
return true;
}
public string Register(Func<InteractionContext, Task> callback, Duration? expiry = null)
{
var key = Guid.NewGuid();
var handler = new RegisteredInteraction
{
Callback = callback,
Expiry = _clock.GetCurrentInstant() + (expiry ?? DefaultExpiry)
};
_handlers[key] = handler;
return key.ToString();
}
private async Task CleanupLoop(CancellationToken ct)
{
while (true)
{
DoCleanup();
await Task.Delay(TimeSpan.FromMinutes(1), ct);
}
}
private void DoCleanup()
{
var now = _clock.GetCurrentInstant();
var removedCount = 0;
foreach (var (key, value) in _handlers.ToArray())
{
if (value.Expiry < now)
{
_handlers.TryRemove(key, out _);
removedCount++;
}
}
_logger.Debug("Removed {ExpiredInteractions} expired interactions", removedCount);
}
private struct RegisteredInteraction
{
public Instant Expiry;
public Func<InteractionContext, Task> Callback;
}
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
}
}

View File

@@ -0,0 +1,45 @@
using System.Threading.Tasks;
using Autofac;
using Myriad.Gateway;
using Myriad.Rest;
using Myriad.Types;
namespace PluralKit.Bot
{
public class InteractionContext
{
private readonly InteractionCreateEvent _evt;
private readonly ILifetimeScope _services;
public InteractionContext(InteractionCreateEvent evt, ILifetimeScope services)
{
_evt = evt;
_services = services;
}
public ulong ChannelId => _evt.ChannelId;
public ulong? MessageId => _evt.Message?.Id;
public GuildMember User => _evt.Member;
public string Token => _evt.Token;
public string? CustomId => _evt.Data?.CustomId;
public InteractionCreateEvent Event => _evt;
public async Task Reply(string content)
{
await Respond(InteractionResponse.ResponseType.ChannelMessageWithSource,
new InteractionApplicationCommandCallbackData
{
Content = content,
Flags = Message.MessageFlags.Ephemeral
});
}
public async Task Respond(InteractionResponse.ResponseType type, InteractionApplicationCommandCallbackData data)
{
var rest = _services.Resolve<DiscordApiClient>();
await rest.CreateInteractionResponse(_evt.Id, _evt.Token, new InteractionResponse {Type = type, Data = data});
}
}
}