Add preliminary support for buttons
This commit is contained in:
@@ -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)
|
||||
|
||||
34
PluralKit.Bot/Handlers/InteractionCreated.cs
Normal file
34
PluralKit.Bot/Handlers/InteractionCreated.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
92
PluralKit.Bot/Services/InteractionDispatchService.cs
Normal file
92
PluralKit.Bot/Services/InteractionDispatchService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
45
PluralKit.Bot/Utils/InteractionContext.cs
Normal file
45
PluralKit.Bot/Utils/InteractionContext.cs
Normal 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});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user