mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 01:38:27 -04:00
fix: Fixed .h not working on some commands
add: Added select menu for the .mdls command dev: Reworked the way interactions are created and sent. It is much better but far from perfect
This commit is contained in:
@@ -2,7 +2,22 @@
|
||||
|
||||
public interface INadekoInteractionService
|
||||
{
|
||||
public NadekoInteraction Create(
|
||||
ulong userId,
|
||||
ButtonBuilder button,
|
||||
Func<SocketMessageComponent, Task> onTrigger,
|
||||
bool singleUse = true);
|
||||
|
||||
public NadekoInteraction Create<T>(
|
||||
ulong userId,
|
||||
SimpleInteraction<T> inter);
|
||||
ButtonBuilder button,
|
||||
Func<SocketMessageComponent, T, Task> onTrigger,
|
||||
in T state,
|
||||
bool singleUse = true);
|
||||
|
||||
NadekoInteraction Create(
|
||||
ulong userId,
|
||||
SelectMenuBuilder menu,
|
||||
Func<SocketMessageComponent, Task> onTrigger,
|
||||
bool singleUse = true);
|
||||
}
|
7
src/NadekoBot/_common/Interaction/InteractionHelpers.cs
Normal file
7
src/NadekoBot/_common/Interaction/InteractionHelpers.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
public static class InteractionHelpers
|
||||
{
|
||||
public static readonly IEmote ArrowLeft = Emote.Parse("<:x:1232256519844790302>");
|
||||
public static readonly IEmote ArrowRight = Emote.Parse("<:x:1232256515298295838>");
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
public sealed class NadekoButtonInteraction : NadekoInteraction
|
||||
{
|
||||
public NadekoButtonInteraction(
|
||||
DiscordSocketClient client,
|
||||
ulong authorId,
|
||||
ButtonBuilder button,
|
||||
Func<SocketMessageComponent, Task> onClick,
|
||||
bool onlyAuthor,
|
||||
bool singleUse = true)
|
||||
: base(client, authorId, button.CustomId, onClick, onlyAuthor, singleUse)
|
||||
{
|
||||
Button = button;
|
||||
}
|
||||
|
||||
public ButtonBuilder Button { get; }
|
||||
|
||||
public override void AddTo(ComponentBuilder cb)
|
||||
=> cb.WithButton(Button);
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
public static class NadekoInteractionExtensions
|
||||
{
|
||||
public static MessageComponent CreateComponent(
|
||||
this NadekoInteraction nadekoInteraction
|
||||
)
|
||||
{
|
||||
var cb = new ComponentBuilder();
|
||||
|
||||
nadekoInteraction.AddTo(cb);
|
||||
|
||||
return cb.Build();
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
public sealed class NadekoSelectInteraction : NadekoInteraction
|
||||
{
|
||||
public NadekoSelectInteraction(
|
||||
DiscordSocketClient client,
|
||||
ulong authorId,
|
||||
SelectMenuBuilder menu,
|
||||
Func<SocketMessageComponent, Task> onClick,
|
||||
bool onlyAuthor,
|
||||
bool singleUse = true)
|
||||
: base(client, authorId, menu.CustomId, onClick, onlyAuthor, singleUse)
|
||||
{
|
||||
Menu = menu;
|
||||
}
|
||||
|
||||
public SelectMenuBuilder Menu { get; }
|
||||
|
||||
public override void AddTo(ComponentBuilder cb)
|
||||
=> cb.WithSelectMenu(Menu);
|
||||
}
|
@@ -1,9 +1,8 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
public sealed class NadekoInteraction
|
||||
public abstract class NadekoInteraction
|
||||
{
|
||||
private readonly ulong _authorId;
|
||||
private readonly ButtonBuilder _button;
|
||||
private readonly Func<SocketMessageComponent, Task> _onClick;
|
||||
private readonly bool _onlyAuthor;
|
||||
public DiscordSocketClient Client { get; }
|
||||
@@ -11,19 +10,24 @@ public sealed class NadekoInteraction
|
||||
private readonly TaskCompletionSource<bool> _interactionCompletedSource;
|
||||
|
||||
private IUserMessage message = null!;
|
||||
private readonly string _customId;
|
||||
private readonly bool _singleUse;
|
||||
|
||||
public NadekoInteraction(DiscordSocketClient client,
|
||||
public NadekoInteraction(
|
||||
DiscordSocketClient client,
|
||||
ulong authorId,
|
||||
ButtonBuilder button,
|
||||
string customId,
|
||||
Func<SocketMessageComponent, Task> onClick,
|
||||
bool onlyAuthor)
|
||||
bool onlyAuthor,
|
||||
bool singleUse = true)
|
||||
{
|
||||
_authorId = authorId;
|
||||
_button = button;
|
||||
_customId = customId;
|
||||
_onClick = onClick;
|
||||
_onlyAuthor = onlyAuthor;
|
||||
_singleUse = singleUse;
|
||||
_interactionCompletedSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
|
||||
Client = client;
|
||||
}
|
||||
|
||||
@@ -32,12 +36,15 @@ public sealed class NadekoInteraction
|
||||
message = msg;
|
||||
|
||||
Client.InteractionCreated += OnInteraction;
|
||||
await Task.WhenAny(Task.Delay(15_000), _interactionCompletedSource.Task);
|
||||
if (_singleUse)
|
||||
await Task.WhenAny(Task.Delay(30_000), _interactionCompletedSource.Task);
|
||||
else
|
||||
await Task.Delay(30_000);
|
||||
Client.InteractionCreated -= OnInteraction;
|
||||
|
||||
await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build());
|
||||
}
|
||||
|
||||
|
||||
private Task OnInteraction(SocketInteraction arg)
|
||||
{
|
||||
if (arg is not SocketMessageComponent smc)
|
||||
@@ -49,33 +56,25 @@ public sealed class NadekoInteraction
|
||||
if (_onlyAuthor && smc.User.Id != _authorId)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (smc.Data.CustomId != _button.CustomId)
|
||||
if (smc.Data.CustomId != _customId)
|
||||
return Task.CompletedTask;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await ExecuteOnActionAsync(smc);
|
||||
|
||||
// this should only be a thing on single-response buttons
|
||||
_interactionCompletedSource.TrySetResult(true);
|
||||
await ExecuteOnActionAsync(smc);
|
||||
|
||||
if (!smc.HasResponded)
|
||||
{
|
||||
await smc.DeferAsync();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
public MessageComponent CreateComponent()
|
||||
{
|
||||
var comp = new ComponentBuilder()
|
||||
.WithButton(_button);
|
||||
|
||||
return comp.Build();
|
||||
}
|
||||
public abstract void AddTo(ComponentBuilder cb);
|
||||
|
||||
public Task ExecuteOnActionAsync(SocketMessageComponent smc)
|
||||
=> _onClick(smc);
|
||||
|
@@ -1,8 +0,0 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
/// <summary>
|
||||
/// Represents essential interacation data
|
||||
/// </summary>
|
||||
/// <param name="Emote">Emote which will show on a button</param>
|
||||
/// <param name="CustomId">Custom interaction id</param>
|
||||
public record NadekoInteractionData(IEmote Emote, string CustomId, string? Text = null);
|
@@ -9,12 +9,39 @@ public class NadekoInteractionService : INadekoInteractionService, INService
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public NadekoInteraction Create(
|
||||
ulong userId,
|
||||
ButtonBuilder button,
|
||||
Func<SocketMessageComponent, Task> onTrigger,
|
||||
bool singleUse = true)
|
||||
=> new NadekoButtonInteraction(_client,
|
||||
userId,
|
||||
button,
|
||||
onTrigger,
|
||||
onlyAuthor: true,
|
||||
singleUse: singleUse);
|
||||
|
||||
public NadekoInteraction Create<T>(
|
||||
ulong userId,
|
||||
SimpleInteraction<T> inter)
|
||||
=> new NadekoInteraction(_client,
|
||||
ButtonBuilder button,
|
||||
Func<SocketMessageComponent, T, Task> onTrigger,
|
||||
in T state,
|
||||
bool singleUse = true)
|
||||
=> Create(userId,
|
||||
button,
|
||||
((Func<T, Func<SocketMessageComponent, Task>>)((data)
|
||||
=> smc => onTrigger(smc, data)))(state),
|
||||
singleUse);
|
||||
|
||||
public NadekoInteraction Create(
|
||||
ulong userId,
|
||||
SelectMenuBuilder menu,
|
||||
Func<SocketMessageComponent, Task> onTrigger,
|
||||
bool singleUse = true)
|
||||
=> new NadekoSelectInteraction(_client,
|
||||
userId,
|
||||
inter.Button,
|
||||
inter.TriggerAsync,
|
||||
onlyAuthor: true);
|
||||
menu,
|
||||
onTrigger,
|
||||
onlyAuthor: true,
|
||||
singleUse: singleUse);
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
public static class InteractionHelpers
|
||||
{
|
||||
public static readonly IEmote ArrowLeft = Emote.Parse("<:x:1232256519844790302>");
|
||||
public static readonly IEmote ArrowRight = Emote.Parse("<:x:1232256515298295838>");
|
||||
}
|
||||
|
||||
public abstract class SimpleInteractionBase
|
||||
{
|
||||
public abstract Task TriggerAsync(SocketMessageComponent smc);
|
||||
public abstract ButtonBuilder Button { get; }
|
||||
}
|
||||
|
||||
public class SimpleInteraction<T> : SimpleInteractionBase
|
||||
{
|
||||
public override ButtonBuilder Button { get; }
|
||||
private readonly Func<SocketMessageComponent, T, Task> _onClick;
|
||||
private readonly T? _state;
|
||||
|
||||
public SimpleInteraction(ButtonBuilder button, Func<SocketMessageComponent, T?, Task> onClick, T? state = default)
|
||||
{
|
||||
Button = button;
|
||||
_onClick = onClick;
|
||||
_state = state;
|
||||
}
|
||||
|
||||
public override async Task TriggerAsync(SocketMessageComponent smc)
|
||||
{
|
||||
await _onClick(smc, _state!);
|
||||
}
|
||||
}
|
@@ -34,34 +34,79 @@ public partial class ResponseBuilder
|
||||
if (_paginationBuilder.AddPaginatedFooter)
|
||||
embed.AddPaginatedFooter(currentPage, lastPage);
|
||||
|
||||
SimpleInteractionBase? maybeInter = null;
|
||||
NadekoInteraction? maybeInter = null;
|
||||
|
||||
async Task<ComponentBuilder> GetComponentBuilder()
|
||||
var model = await _builder.BuildAsync(ephemeral);
|
||||
|
||||
async Task<(NadekoButtonInteraction left, NadekoInteraction? extra, NadekoButtonInteraction right)>
|
||||
GetInteractions()
|
||||
{
|
||||
var cb = new ComponentBuilder();
|
||||
var leftButton = new ButtonBuilder()
|
||||
.WithStyle(ButtonStyle.Primary)
|
||||
.WithCustomId(BUTTON_LEFT)
|
||||
.WithDisabled(lastPage == 0)
|
||||
.WithEmote(InteractionHelpers.ArrowLeft)
|
||||
.WithDisabled(currentPage <= 0);
|
||||
|
||||
cb.WithButton(new ButtonBuilder()
|
||||
.WithStyle(ButtonStyle.Primary)
|
||||
.WithCustomId(BUTTON_LEFT)
|
||||
.WithDisabled(lastPage == 0)
|
||||
.WithEmote(InteractionHelpers.ArrowLeft)
|
||||
.WithDisabled(currentPage <= 0));
|
||||
var leftBtnInter = new NadekoButtonInteraction(_client,
|
||||
model.User?.Id ?? 0,
|
||||
leftButton,
|
||||
(smc) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (currentPage > 0)
|
||||
currentPage--;
|
||||
|
||||
_ = UpdatePageAsync(smc);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error in pagination: {ErrorMessage}", ex.Message);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
true,
|
||||
singleUse: false);
|
||||
|
||||
if (_paginationBuilder.InteractionFunc is not null)
|
||||
{
|
||||
maybeInter = await _paginationBuilder.InteractionFunc(currentPage);
|
||||
|
||||
if (maybeInter is not null)
|
||||
cb.WithButton(maybeInter.Button);
|
||||
}
|
||||
|
||||
cb.WithButton(new ButtonBuilder()
|
||||
.WithStyle(ButtonStyle.Primary)
|
||||
.WithCustomId(BUTTON_RIGHT)
|
||||
.WithDisabled(lastPage is not null && (lastPage == 0 || currentPage >= lastPage))
|
||||
.WithEmote(InteractionHelpers.ArrowRight));
|
||||
var rightButton = new ButtonBuilder()
|
||||
.WithStyle(ButtonStyle.Primary)
|
||||
.WithCustomId(BUTTON_RIGHT)
|
||||
.WithDisabled(lastPage == 0)
|
||||
.WithEmote(InteractionHelpers.ArrowRight)
|
||||
.WithDisabled(lastPage == 0 || currentPage > lastPage);
|
||||
|
||||
return cb;
|
||||
var rightBtnInter = new NadekoButtonInteraction(_client,
|
||||
model.User?.Id ?? 0,
|
||||
rightButton,
|
||||
(smc) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (currentPage >= lastPage)
|
||||
return Task.CompletedTask;
|
||||
|
||||
currentPage++;
|
||||
|
||||
_ = UpdatePageAsync(smc);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error in pagination: {ErrorMessage}", ex.Message);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
true,
|
||||
singleUse: false);
|
||||
|
||||
return (leftBtnInter, maybeInter, rightBtnInter);
|
||||
}
|
||||
|
||||
async Task UpdatePageAsync(SocketMessageComponent smc)
|
||||
@@ -71,75 +116,37 @@ public partial class ResponseBuilder
|
||||
if (_paginationBuilder.AddPaginatedFooter)
|
||||
toSend.AddPaginatedFooter(currentPage, lastPage);
|
||||
|
||||
var component = (await GetComponentBuilder()).Build();
|
||||
var (left, extra, right) = (await GetInteractions());
|
||||
|
||||
var cb = new ComponentBuilder();
|
||||
left.AddTo(cb);
|
||||
right.AddTo(cb);
|
||||
extra?.AddTo(cb);
|
||||
|
||||
await smc.ModifyOriginalResponseAsync(x =>
|
||||
{
|
||||
x.Embed = toSend.Build();
|
||||
x.Components = component;
|
||||
x.Components = cb.Build();
|
||||
});
|
||||
}
|
||||
|
||||
var model = await _builder.BuildAsync(ephemeral);
|
||||
var (left, extra, right) = await GetInteractions();
|
||||
|
||||
var cb = new ComponentBuilder();
|
||||
left.AddTo(cb);
|
||||
right.AddTo(cb);
|
||||
extra?.AddTo(cb);
|
||||
|
||||
var component = (await GetComponentBuilder()).Build();
|
||||
var msg = await model.TargetChannel
|
||||
.SendMessageAsync(model.Text,
|
||||
embed: embed.Build(),
|
||||
components: component,
|
||||
components: cb.Build(),
|
||||
messageReference: model.MessageReference);
|
||||
|
||||
async Task OnInteractionAsync(SocketInteraction si)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (si is not SocketMessageComponent smc)
|
||||
return;
|
||||
|
||||
if (smc.Message.Id != msg.Id)
|
||||
return;
|
||||
|
||||
await si.DeferAsync();
|
||||
|
||||
if (smc.User.Id != model.User?.Id)
|
||||
return;
|
||||
|
||||
if (smc.Data.CustomId == BUTTON_LEFT)
|
||||
{
|
||||
if (currentPage == 0)
|
||||
return;
|
||||
|
||||
--currentPage;
|
||||
_ = UpdatePageAsync(smc);
|
||||
}
|
||||
else if (smc.Data.CustomId == BUTTON_RIGHT)
|
||||
{
|
||||
if (currentPage >= lastPage)
|
||||
return;
|
||||
|
||||
++currentPage;
|
||||
_ = UpdatePageAsync(smc);
|
||||
}
|
||||
else if (maybeInter is { } inter && inter.Button.CustomId == smc.Data.CustomId)
|
||||
{
|
||||
await inter.TriggerAsync(smc);
|
||||
_ = UpdatePageAsync(smc);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error in pagination: {ErrorMessage}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
if (lastPage == 0 && _paginationBuilder.InteractionFunc is null)
|
||||
return;
|
||||
|
||||
_client.InteractionCreated += OnInteractionAsync;
|
||||
|
||||
await Task.Delay(30_000);
|
||||
|
||||
_client.InteractionCreated -= OnInteractionAsync;
|
||||
await Task.WhenAll(left.RunAsync(msg), extra?.RunAsync(msg) ?? Task.CompletedTask, right.RunAsync(msg));
|
||||
|
||||
await msg.ModifyAsync(mp => mp.Components = new ComponentBuilder().Build());
|
||||
}
|
||||
|
@@ -395,7 +395,7 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
|
||||
return Task.FromResult<IReadOnlyCollection<T>>(ReadOnlyCollection<T>.Empty);
|
||||
};
|
||||
|
||||
public Func<int, Task<SimpleInteractionBase>>? InteractionFunc { get; private set; }
|
||||
public Func<int, Task<NadekoInteraction>>? 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<SimpleInteractionBase>> func)
|
||||
public SourcedPaginatedResponseBuilder<T> Interaction(Func<int, Task<NadekoInteraction>> func)
|
||||
{
|
||||
InteractionFunc = func; //async (i) => await func(i);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SourcedPaginatedResponseBuilder<T> Interaction(SimpleInteractionBase inter)
|
||||
public SourcedPaginatedResponseBuilder<T> Interaction(NadekoInteraction inter)
|
||||
{
|
||||
InteractionFunc = _ => Task.FromResult(inter);
|
||||
return this;
|
||||
|
@@ -40,19 +40,29 @@ public sealed class CommandsUtilityService : ICommandsUtilityService, INService
|
||||
var culture = _loc.GetCultureInfo(guild);
|
||||
|
||||
var em = _sender.CreateEmbed()
|
||||
.AddField(str, $"{com.RealSummary(_strings, _medusae, culture, prefix)}", true);
|
||||
.AddField(str, $"{com.RealSummary(_strings, _medusae, culture, prefix)}", true);
|
||||
|
||||
_dpos.TryGetOverrides(guild?.Id ?? 0, com.Name, out var overrides);
|
||||
var reqs = GetCommandRequirements(com, (GuildPermission?)overrides);
|
||||
if (reqs.Any())
|
||||
em.AddField(GetText(strs.requires, guild), string.Join("\n", reqs));
|
||||
|
||||
var paramList = _strings.GetCommandStrings(com.Name, culture)?.Params;
|
||||
em
|
||||
.WithOkColor()
|
||||
.AddField(_strings.GetText(strs.usage),
|
||||
string.Join("\n", com.RealRemarksArr(_strings, _medusae, culture, prefix).Map(arg => Format.Code(arg))))
|
||||
.WithFooter(GetText(strs.module(com.Module.GetTopLevelModule().Name), guild));
|
||||
|
||||
if (paramList is not null and not [])
|
||||
{
|
||||
var pl = paramList
|
||||
.Select(x => Format.Code($"{prefix}{com.Name} {x.Keys.Select(y => $"<{y}>").Join(' ')}"))
|
||||
.Join('\n');
|
||||
|
||||
em.AddField(GetText(strs.overloads, guild), pl);
|
||||
}
|
||||
|
||||
var opt = GetNadekoOptionType(com.Attributes);
|
||||
if (opt is not null)
|
||||
{
|
||||
|
Reference in New Issue
Block a user