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 bool Tts { get; set; }
|
||||||
public AllowedMentions? AllowedMentions { get; set; }
|
public AllowedMentions? AllowedMentions { get; set; }
|
||||||
public Embed? Embed { get; set; }
|
public Embed? Embed { get; set; }
|
||||||
|
public MessageComponent[]? Components { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,8 +2,10 @@
|
|||||||
{
|
{
|
||||||
public record ApplicationCommandInteractionData
|
public record ApplicationCommandInteractionData
|
||||||
{
|
{
|
||||||
public ulong Id { get; init; }
|
public ulong? Id { get; init; }
|
||||||
public string Name { get; init; }
|
public string? Name { get; init; }
|
||||||
public ApplicationCommandInteractionDataOption[] Options { 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
|
public enum InteractionType
|
||||||
{
|
{
|
||||||
Ping = 1,
|
Ping = 1,
|
||||||
ApplicationCommand = 2
|
ApplicationCommand = 2,
|
||||||
|
MessageComponent = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong Id { get; init; }
|
public ulong Id { get; init; }
|
||||||
@ -15,5 +16,6 @@
|
|||||||
public ulong ChannelId { get; init; }
|
public ulong ChannelId { get; init; }
|
||||||
public GuildMember Member { get; init; }
|
public GuildMember Member { get; init; }
|
||||||
public string Token { get; init; }
|
public string Token { get; init; }
|
||||||
|
public Message? Message { get; init; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,10 +5,10 @@
|
|||||||
public enum ResponseType
|
public enum ResponseType
|
||||||
{
|
{
|
||||||
Pong = 1,
|
Pong = 1,
|
||||||
Acknowledge = 2,
|
|
||||||
ChannelMessage = 3,
|
|
||||||
ChannelMessageWithSource = 4,
|
ChannelMessageWithSource = 4,
|
||||||
AckWithSource = 5
|
DeferredChannelMessageWithSource = 5,
|
||||||
|
DeferredUpdateMessage = 6,
|
||||||
|
UpdateMessage = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResponseType Type { get; init; }
|
public ResponseType Type { get; init; }
|
||||||
|
@ -64,6 +64,7 @@ namespace Myriad.Types
|
|||||||
|
|
||||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||||
public Optional<Message?> ReferencedMessage { get; init; }
|
public Optional<Message?> ReferencedMessage { get; init; }
|
||||||
|
public MessageComponent[]? Components { get; init; }
|
||||||
|
|
||||||
public record Reference(ulong? GuildId, ulong? ChannelId, ulong? MessageId);
|
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);
|
await HandleEvent(shard, mdb);
|
||||||
if (evt is MessageReactionAddEvent mra)
|
if (evt is MessageReactionAddEvent mra)
|
||||||
await HandleEvent(shard, mra);
|
await HandleEvent(shard, mra);
|
||||||
|
if (evt is InteractionCreateEvent ic)
|
||||||
|
await HandleEvent(shard, ic);
|
||||||
|
|
||||||
// Update shard status for shards immediately on connect
|
// Update shard status for shards immediately on connect
|
||||||
if (evt is ReadyEvent re)
|
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<MessageDeleted>().As<IEventHandler<MessageDeleteEvent>>().As<IEventHandler<MessageDeleteBulkEvent>>();
|
||||||
builder.RegisterType<MessageEdited>().As<IEventHandler<MessageUpdateEvent>>();
|
builder.RegisterType<MessageEdited>().As<IEventHandler<MessageUpdateEvent>>();
|
||||||
builder.RegisterType<ReactionAdded>().As<IEventHandler<MessageReactionAddEvent>>();
|
builder.RegisterType<ReactionAdded>().As<IEventHandler<MessageReactionAddEvent>>();
|
||||||
|
builder.RegisterType<InteractionCreated>().As<IEventHandler<InteractionCreateEvent>>();
|
||||||
|
|
||||||
// Event handler queue
|
// Event handler queue
|
||||||
builder.RegisterType<HandlerQueue<MessageCreateEvent>>().AsSelf().SingleInstance();
|
builder.RegisterType<HandlerQueue<MessageCreateEvent>>().AsSelf().SingleInstance();
|
||||||
@ -91,6 +92,7 @@ namespace PluralKit.Bot
|
|||||||
builder.RegisterType<LoggerCleanService>().AsSelf().SingleInstance();
|
builder.RegisterType<LoggerCleanService>().AsSelf().SingleInstance();
|
||||||
builder.RegisterType<ErrorMessageService>().AsSelf().SingleInstance();
|
builder.RegisterType<ErrorMessageService>().AsSelf().SingleInstance();
|
||||||
builder.RegisterType<CommandMessageService>().AsSelf().SingleInstance();
|
builder.RegisterType<CommandMessageService>().AsSelf().SingleInstance();
|
||||||
|
builder.RegisterType<InteractionDispatchService>().AsSelf().SingleInstance();
|
||||||
|
|
||||||
// Sentry stuff
|
// Sentry stuff
|
||||||
builder.Register(_ => new Scope(null)).AsSelf().InstancePerLifetimeScope();
|
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…
x
Reference in New Issue
Block a user