Add preliminary support for buttons
This commit is contained in:
parent
0b91e71384
commit
d7c0592947
@ -9,5 +9,6 @@ namespace Myriad.Rest.Types.Requests
|
||||
public bool Tts { get; set; }
|
||||
public AllowedMentions? AllowedMentions { get; set; }
|
||||
public Embed? Embed { get; set; }
|
||||
public MessageComponent[]? Components { get; set; }
|
||||
}
|
||||
}
|
@ -2,8 +2,10 @@
|
||||
{
|
||||
public record ApplicationCommandInteractionData
|
||||
{
|
||||
public ulong Id { get; init; }
|
||||
public string Name { get; init; }
|
||||
public ApplicationCommandInteractionDataOption[] Options { get; init; }
|
||||
public ulong? Id { get; init; }
|
||||
public string? Name { get; init; }
|
||||
public ApplicationCommandInteractionDataOption[]? Options { get; init; }
|
||||
public string? CustomId { get; init; }
|
||||
public MessageComponent.ComponentType? ComponentType { get; init; }
|
||||
}
|
||||
}
|
@ -5,7 +5,8 @@
|
||||
public enum InteractionType
|
||||
{
|
||||
Ping = 1,
|
||||
ApplicationCommand = 2
|
||||
ApplicationCommand = 2,
|
||||
MessageComponent = 3
|
||||
}
|
||||
|
||||
public ulong Id { get; init; }
|
||||
@ -15,5 +16,6 @@
|
||||
public ulong ChannelId { get; init; }
|
||||
public GuildMember Member { get; init; }
|
||||
public string Token { get; init; }
|
||||
public Message? Message { get; init; }
|
||||
}
|
||||
}
|
@ -5,10 +5,10 @@
|
||||
public enum ResponseType
|
||||
{
|
||||
Pong = 1,
|
||||
Acknowledge = 2,
|
||||
ChannelMessage = 3,
|
||||
ChannelMessageWithSource = 4,
|
||||
AckWithSource = 5
|
||||
DeferredChannelMessageWithSource = 5,
|
||||
DeferredUpdateMessage = 6,
|
||||
UpdateMessage = 7
|
||||
}
|
||||
|
||||
public ResponseType Type { get; init; }
|
||||
|
@ -64,6 +64,7 @@ namespace Myriad.Types
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public Optional<Message?> ReferencedMessage { get; init; }
|
||||
public MessageComponent[]? Components { get; init; }
|
||||
|
||||
public record Reference(ulong? GuildId, ulong? ChannelId, ulong? MessageId);
|
||||
|
||||
|
29
Myriad/Types/MessageComponent.cs
Normal file
29
Myriad/Types/MessageComponent.cs
Normal file
@ -0,0 +1,29 @@
|
||||
namespace Myriad.Types
|
||||
{
|
||||
public record MessageComponent
|
||||
{
|
||||
public ComponentType Type { get; init; }
|
||||
public ButtonStyle? Style { get; init; }
|
||||
public string? Label { get; init; }
|
||||
public Emoji? Emoji { get; init; }
|
||||
public string? CustomId { get; init; }
|
||||
public string? Url { get; init; }
|
||||
public bool? Disabled { get; init; }
|
||||
public MessageComponent[]? Components { get; init; }
|
||||
|
||||
public enum ComponentType
|
||||
{
|
||||
ActionRow = 1,
|
||||
Button = 2
|
||||
}
|
||||
|
||||
public enum ButtonStyle
|
||||
{
|
||||
Primary = 1,
|
||||
Secondary = 2,
|
||||
Success = 3,
|
||||
Danger = 4,
|
||||
Link = 5
|
||||
}
|
||||
}
|
||||
}
|
@ -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});
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user