add: Added .quteedit command

add: Added an edit button to .quoteshow and .exprshow commands that opens a modal which lets you edit the quote or expr in question
This commit is contained in:
Kwoth
2024-05-21 00:33:40 +00:00
parent b51ce34190
commit 06321380ee
17 changed files with 313 additions and 78 deletions

View File

@@ -2,22 +2,30 @@
public interface INadekoInteractionService
{
public NadekoInteraction Create(
public NadekoInteractionBase Create(
ulong userId,
ButtonBuilder button,
Func<SocketMessageComponent, Task> onTrigger,
bool singleUse = true);
public NadekoInteraction Create<T>(
public NadekoInteractionBase Create<T>(
ulong userId,
ButtonBuilder button,
Func<SocketMessageComponent, T, Task> onTrigger,
in T state,
bool singleUse = true);
NadekoInteraction Create(
NadekoInteractionBase Create(
ulong userId,
SelectMenuBuilder menu,
Func<SocketMessageComponent, Task> onTrigger,
bool singleUse = true);
NadekoInteractionBase Create(
ulong userId,
ButtonBuilder button,
ModalBuilder modal,
Func<SocketModal, Task> onTrigger,
bool singleUse = true);
}

View File

@@ -1,8 +1,8 @@
namespace NadekoBot;
public sealed class NadekoButtonInteraction : NadekoInteraction
public sealed class NadekoButtonInteractionHandler : NadekoInteractionBase
{
public NadekoButtonInteraction(
public NadekoButtonInteractionHandler(
DiscordSocketClient client,
ulong authorId,
ButtonBuilder button,

View File

@@ -3,12 +3,12 @@
public static class NadekoInteractionExtensions
{
public static MessageComponent CreateComponent(
this NadekoInteraction nadekoInteraction
this NadekoInteractionBase nadekoInteractionBase
)
{
var cb = new ComponentBuilder();
nadekoInteraction.AddTo(cb);
nadekoInteractionBase.AddTo(cb);
return cb.Build();
}

View File

@@ -1,8 +1,8 @@
namespace NadekoBot;
public sealed class NadekoSelectInteraction : NadekoInteraction
public sealed class NadekoButtonSelectInteractionHandler : NadekoInteractionBase
{
public NadekoSelectInteraction(
public NadekoButtonSelectInteractionHandler(
DiscordSocketClient client,
ulong authorId,
SelectMenuBuilder menu,

View File

@@ -1,6 +1,6 @@
namespace NadekoBot;
public abstract class NadekoInteraction
public abstract class NadekoInteractionBase
{
private readonly ulong _authorId;
private readonly Func<SocketMessageComponent, Task> _onAction;
@@ -13,7 +13,7 @@ public abstract class NadekoInteraction
private readonly string _customId;
private readonly bool _singleUse;
public NadekoInteraction(
public NadekoInteractionBase(
DiscordSocketClient client,
ulong authorId,
string customId,
@@ -85,4 +85,80 @@ public abstract class NadekoInteraction
public Task ExecuteOnActionAsync(SocketMessageComponent smc)
=> _onAction(smc);
}
public sealed class NadekoModalSubmitHandler
{
private readonly ulong _authorId;
private readonly Func<SocketModal, Task> _onAction;
private readonly bool _onlyAuthor;
public DiscordSocketClient Client { get; }
private readonly TaskCompletionSource<bool> _interactionCompletedSource;
private IUserMessage message = null!;
private readonly string _customId;
public NadekoModalSubmitHandler(
DiscordSocketClient client,
ulong authorId,
string customId,
Func<SocketModal, Task> onAction,
bool onlyAuthor)
{
_authorId = authorId;
_customId = customId;
_onAction = onAction;
_onlyAuthor = onlyAuthor;
_interactionCompletedSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
Client = client;
}
public async Task RunAsync(IUserMessage msg)
{
message = msg;
Client.ModalSubmitted += OnInteraction;
await Task.WhenAny(Task.Delay(300_000), _interactionCompletedSource.Task);
Client.ModalSubmitted -= OnInteraction;
await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build());
}
private Task OnInteraction(SocketModal sm)
{
if (sm.Message.Id != message.Id)
return Task.CompletedTask;
if (_onlyAuthor && sm.User.Id != _authorId)
return Task.CompletedTask;
if (sm.Data.CustomId != _customId)
return Task.CompletedTask;
_ = Task.Run(async () =>
{
try
{
_interactionCompletedSource.TrySetResult(true);
await ExecuteOnActionAsync(sm);
if (!sm.HasResponded)
{
await sm.DeferAsync();
}
}
catch (Exception ex)
{
Log.Warning(ex, "An exception occured while handling a: {Message}", ex.Message);
}
});
return Task.CompletedTask;
}
public Task ExecuteOnActionAsync(SocketModal smd)
=> _onAction(smd);
}

View File

@@ -9,19 +9,19 @@ public class NadekoInteractionService : INadekoInteractionService, INService
_client = client;
}
public NadekoInteraction Create(
public NadekoInteractionBase Create(
ulong userId,
ButtonBuilder button,
Func<SocketMessageComponent, Task> onTrigger,
bool singleUse = true)
=> new NadekoButtonInteraction(_client,
=> new NadekoButtonInteractionHandler(_client,
userId,
button,
onTrigger,
onlyAuthor: true,
singleUse: singleUse);
public NadekoInteraction Create<T>(
public NadekoInteractionBase Create<T>(
ulong userId,
ButtonBuilder button,
Func<SocketMessageComponent, T, Task> onTrigger,
@@ -32,16 +32,46 @@ public class NadekoInteractionService : INadekoInteractionService, INService
((Func<T, Func<SocketMessageComponent, Task>>)((data)
=> smc => onTrigger(smc, data)))(state),
singleUse);
public NadekoInteraction Create(
public NadekoInteractionBase Create(
ulong userId,
SelectMenuBuilder menu,
Func<SocketMessageComponent, Task> onTrigger,
bool singleUse = true)
=> new NadekoSelectInteraction(_client,
=> new NadekoButtonSelectInteractionHandler(_client,
userId,
menu,
onTrigger,
onlyAuthor: true,
singleUse: singleUse);
/// <summary>
/// Create an interaction which opens a modal
/// </summary>
/// <param name="userId">Id of the author</param>
/// <param name="button">Button builder for the button that will open the modal</param>
/// <param name="modal">Modal</param>
/// <param name="onTrigger">The function that will be called when the modal is submitted</param>
/// <param name="singleUse">Whether the button is single use</param>
/// <returns></returns>
public NadekoInteractionBase Create(
ulong userId,
ButtonBuilder button,
ModalBuilder modal,
Func<SocketModal, Task> onTrigger,
bool singleUse = true)
=> Create(userId,
button,
async (smc) =>
{
await smc.RespondWithModalAsync(modal.Build());
var modalHandler = new NadekoModalSubmitHandler(_client,
userId,
modal.CustomId,
onTrigger,
true);
await modalHandler.RunAsync(smc.Message);
},
singleUse: singleUse);
}

View File

@@ -34,11 +34,11 @@ public partial class ResponseBuilder
if (_paginationBuilder.AddPaginatedFooter)
embed.AddPaginatedFooter(currentPage, lastPage);
NadekoInteraction? maybeInter = null;
NadekoInteractionBase? maybeInter = null;
var model = await _builder.BuildAsync(ephemeral);
async Task<(NadekoButtonInteraction left, NadekoInteraction? extra, NadekoButtonInteraction right)>
async Task<(NadekoButtonInteractionHandler left, NadekoInteractionBase? extra, NadekoButtonInteractionHandler right)>
GetInteractions()
{
var leftButton = new ButtonBuilder()
@@ -47,7 +47,7 @@ public partial class ResponseBuilder
.WithEmote(InteractionHelpers.ArrowLeft)
.WithDisabled(lastPage == 0 || currentPage <= 0);
var leftBtnInter = new NadekoButtonInteraction(_client,
var leftBtnInter = new NadekoButtonInteractionHandler(_client,
model.User?.Id ?? 0,
leftButton,
(smc) =>
@@ -80,7 +80,7 @@ public partial class ResponseBuilder
.WithEmote(InteractionHelpers.ArrowRight)
.WithDisabled(lastPage == 0 || currentPage >= lastPage);
var rightBtnInter = new NadekoButtonInteraction(_client,
var rightBtnInter = new NadekoButtonInteractionHandler(_client,
model.User?.Id ?? 0,
rightButton,
(smc) =>

View File

@@ -19,7 +19,7 @@ public sealed partial class ResponseBuilder
private readonly IBotStrings _bs;
private readonly BotConfigService _bcs;
private EmbedBuilder? embedBuilder;
private NadekoInteraction? inter;
private NadekoInteractionBase? inter;
private Stream? fileStream;
private string? fileName;
private EmbedColor color = EmbedColor.Ok;
@@ -340,7 +340,7 @@ public sealed partial class ResponseBuilder
return this;
}
public ResponseBuilder Interaction(NadekoInteraction? interaction)
public ResponseBuilder Interaction(NadekoInteractionBase? interaction)
{
inter = interaction;
return this;
@@ -395,7 +395,7 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
return Task.FromResult<IReadOnlyCollection<T>>(ReadOnlyCollection<T>.Empty);
};
public Func<int, Task<NadekoInteraction>>? InteractionFunc { get; private set; }
public Func<int, Task<NadekoInteractionBase>>? InteractionFunc { get; private set; }
public int? Elems { get; private set; } = 1;
public int ItemsPerPage { get; private set; } = 9;
@@ -478,13 +478,13 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
return paginationSender.SendAsync(IsEphemeral);
}
public SourcedPaginatedResponseBuilder<T> Interaction(Func<int, Task<NadekoInteraction>> func)
public SourcedPaginatedResponseBuilder<T> Interaction(Func<int, Task<NadekoInteractionBase>> func)
{
InteractionFunc = func; //async (i) => await func(i);
return this;
}
public SourcedPaginatedResponseBuilder<T> Interaction(NadekoInteraction inter)
public SourcedPaginatedResponseBuilder<T> Interaction(NadekoInteractionBase inter)
{
InteractionFunc = _ => Task.FromResult(inter);
return this;

View File

@@ -8,5 +8,5 @@
public required AllowedMentions SanitizeMentions { get; set; }
public IUser? User { get; set; }
public bool Ephemeral { get; set; }
public NadekoInteraction? Interaction { get; set; }
public NadekoInteractionBase? Interaction { get; set; }
}