Greatly unboilerplated and simplified nadeko interaction button construction

This commit is contained in:
Kwoth
2022-08-07 17:00:36 +02:00
parent 13d2fbd560
commit 38e3badb87
18 changed files with 110 additions and 291 deletions

View File

@@ -0,0 +1,8 @@
namespace NadekoBot;
public interface INadekoInteractionService
{
public NadekoInteraction Create<T>(
ulong userId,
SimpleInteraction<T> inter);
}

View File

@@ -1,29 +0,0 @@
namespace NadekoBot;
public sealed class NadekoButtonActionInteraction : NadekoButtonOwnInteraction
{
private readonly NadekoInteractionData _data;
private readonly Func<SocketMessageComponent, Task> _action;
public NadekoButtonActionInteraction(
DiscordSocketClient client,
ulong authorId,
NadekoInteractionData data,
Func<SocketMessageComponent, Task> action
)
: base(client, authorId)
{
_data = data;
_action = action;
}
protected override string Name
=> _data.CustomId;
protected override IEmote Emote
=> _data.Emote;
protected override string? Text
=> _data.Text;
public override Task ExecuteOnActionAsync(SocketMessageComponent smc)
=> _action(smc);
}

View File

@@ -1,25 +1,30 @@
namespace NadekoBot;
public abstract class NadekoButtonInteraction
public sealed class NadekoInteraction
{
// improvements:
// - state in OnAction
// - configurable delay
// -
protected abstract string Name { get; }
protected abstract IEmote Emote { get; }
protected virtual string? Text { get; } = null;
private readonly ulong _authorId;
private readonly ButtonBuilder _button;
private readonly Func<SocketMessageComponent, Task> _onClick;
private readonly bool _onlyAuthor;
public DiscordSocketClient Client { get; }
protected readonly TaskCompletionSource<bool> _interactionCompletedSource;
private readonly TaskCompletionSource<bool> _interactionCompletedSource;
protected IUserMessage message = null!;
private IUserMessage message = null!;
protected NadekoButtonInteraction(DiscordSocketClient client)
public NadekoInteraction(DiscordSocketClient client,
ulong authorId,
ButtonBuilder button,
Func<SocketMessageComponent, Task> onClick,
bool onlyAuthor)
{
Client = client;
_authorId = authorId;
_button = button;
_onClick = onClick;
_onlyAuthor = onlyAuthor;
_interactionCompletedSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
Client = client;
}
public async Task RunAsync(IUserMessage msg)
@@ -27,13 +32,12 @@ public abstract class NadekoButtonInteraction
message = msg;
Client.InteractionCreated += OnInteraction;
await Task.WhenAny(Task.Delay(10_000), _interactionCompletedSource.Task);
await Task.WhenAny(Task.Delay(15_000), _interactionCompletedSource.Task);
Client.InteractionCreated -= OnInteraction;
await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build());
}
protected abstract ValueTask<bool> Validate(SocketMessageComponent smc);
private async Task OnInteraction(SocketInteraction arg)
{
if (arg is not SocketMessageComponent smc)
@@ -42,14 +46,11 @@ public abstract class NadekoButtonInteraction
if (smc.Message.Id != message.Id)
return;
if (smc.Data.CustomId != Name)
if (_onlyAuthor && smc.User.Id != _authorId)
return;
if (!await Validate(smc))
{
await smc.DeferAsync();
if (smc.Data.CustomId != _button.CustomId)
return;
}
_ = Task.Run(async () =>
{
@@ -66,18 +67,14 @@ public abstract class NadekoButtonInteraction
}
public virtual MessageComponent CreateComponent()
public MessageComponent CreateComponent()
{
var comp = new ComponentBuilder()
.WithButton(GetButtonBuilder());
.WithButton(_button);
return comp.Build();
}
public ButtonBuilder GetButtonBuilder()
=> new ButtonBuilder(style: ButtonStyle.Secondary, emote: Emote, customId: Name, label: Text);
public abstract Task ExecuteOnActionAsync(SocketMessageComponent smc);
public Task ExecuteOnActionAsync(SocketMessageComponent smc)
=> _onClick(smc);
}
// this is all so wrong ...

View File

@@ -1,43 +0,0 @@
// namespace NadekoBot;
//
// public class NadekoButtonInteractionArray : NadekoButtonInteraction
// {
// private readonly ButtonBuilder[] _bbs;
// private readonly NadekoButtonInteraction[] _inters;
//
// public NadekoButtonInteractionArray(params NadekoButtonInteraction[] inters)
// : base(inters[0].Client)
// {
// _inters = inters;
// _bbs = inters.Map(x => x.GetButtonBuilder());
// }
//
// protected override string Name
// => throw new NotSupportedException();
// protected override IEmote Emote
// => throw new NotSupportedException();
//
// protected override ValueTask<bool> Validate(SocketMessageComponent smc)
// => new(true);
//
// public override Task ExecuteOnActionAsync(SocketMessageComponent smc)
// {
// for (var i = 0; i < _bbs.Length; i++)
// {
// if (_bbs[i].CustomId == smc.Data.CustomId)
// return _inters[i].ExecuteOnActionAsync(smc);
// }
//
// return Task.CompletedTask;
// }
//
// public override MessageComponent CreateComponent()
// {
// var comp = new ComponentBuilder();
//
// foreach (var bb in _bbs)
// comp.WithButton(bb);
//
// return comp.Build();
// }
// }

View File

@@ -1,42 +0,0 @@
namespace NadekoBot;
/// <summary>
/// Builder class for NadekoInteractions
/// </summary>
public class NadekoInteractionBuilder
{
private NadekoInteractionData? iData;
private Func<SocketMessageComponent, Task>? action;
// private bool isOwn;
public NadekoInteractionBuilder WithData<T>(in T data)
where T : NadekoInteractionData
{
iData = data;
return this;
}
// public NadekoOwnInteractionBuiler WithIsOwn(bool isOwn = true)
// {
// this.isOwn = isOwn;
// return this;
// }
public NadekoInteractionBuilder WithAction(in Func<SocketMessageComponent, Task> fn)
{
this.action = fn;
return this;
}
public NadekoButtonActionInteraction Build(DiscordSocketClient client, ulong userId)
{
if (iData is null)
throw new InvalidOperationException("You have to specify the data before building the interaction");
if (action is null)
throw new InvalidOperationException("You have to specify the action before building the interaction");
return new(client, userId, iData, action);
}
}

View File

@@ -0,0 +1,20 @@
namespace NadekoBot;
public class NadekoInteractionService : INadekoInteractionService, INService
{
private readonly DiscordSocketClient _client;
public NadekoInteractionService(DiscordSocketClient client)
{
_client = client;
}
public NadekoInteraction Create<T>(
ulong userId,
SimpleInteraction<T> inter)
=> new NadekoInteraction(_client,
userId,
inter.Button,
inter.TriggerAsync,
onlyAuthor: true);
}

View File

@@ -1,15 +0,0 @@
namespace NadekoBot;
/// <summary>
/// Interaction which only the author can use
/// </summary>
public abstract class NadekoButtonOwnInteraction : NadekoButtonInteraction
{
protected readonly ulong _authorId;
protected NadekoButtonOwnInteraction(DiscordSocketClient client, ulong authorId) : base(client)
=> _authorId = authorId;
protected override ValueTask<bool> Validate(SocketMessageComponent smc)
=> new(smc.User.Id == _authorId);
}

View File

@@ -1,26 +0,0 @@
namespace NadekoBot.Common;
public abstract class NInteraction
{
private readonly DiscordSocketClient _client;
private readonly ulong _userId;
private readonly Func<SocketMessageComponent, Task> _action;
protected abstract NadekoInteractionData Data { get; }
public NInteraction(
DiscordSocketClient client,
ulong userId,
Func<SocketMessageComponent, Task> action)
{
_client = client;
_userId = userId;
_action = action;
}
public NadekoButtonInteraction GetInteraction()
=> new NadekoInteractionBuilder()
.WithData(Data)
.WithAction(_action)
.Build(_client, _userId);
}

View File

@@ -18,6 +18,7 @@ public abstract class NadekoModule : ModuleBase
public CommandHandler _cmdHandler { get; set; }
public ILocalization _localization { get; set; }
public IEmbedBuilderService _eb { get; set; }
public INadekoInteractionService _inter { get; set; }
protected string prefix
=> _cmdHandler.GetPrefix(ctx.Guild);
@@ -36,7 +37,7 @@ public abstract class NadekoModule : ModuleBase
string error,
string url = null,
string footer = null,
NadekoButtonInteraction inter = null)
NadekoInteraction inter = null)
=> ctx.Channel.SendErrorAsync(_eb, title, error, url, footer);
public Task<IUserMessage> SendConfirmAsync(
@@ -47,32 +48,32 @@ public abstract class NadekoModule : ModuleBase
=> ctx.Channel.SendConfirmAsync(_eb, title, text, url, footer);
//
public Task<IUserMessage> SendErrorAsync(string text, NadekoButtonInteraction inter = null)
public Task<IUserMessage> SendErrorAsync(string text, NadekoInteraction inter = null)
=> ctx.Channel.SendAsync(_eb, text, MessageType.Error, inter);
public Task<IUserMessage> SendConfirmAsync(string text, NadekoButtonInteraction inter = null)
public Task<IUserMessage> SendConfirmAsync(string text, NadekoInteraction inter = null)
=> ctx.Channel.SendAsync(_eb, text, MessageType.Ok, inter);
public Task<IUserMessage> SendPendingAsync(string text, NadekoButtonInteraction inter = null)
public Task<IUserMessage> SendPendingAsync(string text, NadekoInteraction inter = null)
=> ctx.Channel.SendAsync(_eb, text, MessageType.Pending, inter);
// localized normal
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str, NadekoInteraction inter = null)
=> SendErrorAsync(GetText(str), inter);
public Task<IUserMessage> PendingLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
public Task<IUserMessage> PendingLocalizedAsync(LocStr str, NadekoInteraction inter = null)
=> SendPendingAsync(GetText(str), inter);
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str, NadekoInteraction inter = null)
=> SendConfirmAsync(GetText(str), inter);
// localized replies
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str, NadekoInteraction inter = null)
=> SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}", inter);
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str, NadekoInteraction inter = null)
=> SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}", inter);
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str, NadekoInteraction inter = null)
=> SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}", inter);
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)

View File

@@ -1,13 +0,0 @@
#nullable disable
namespace NadekoBot.Modules.Gambling;
public class CashInteraction : NInteraction
{
protected override NadekoInteractionData Data
=> new NadekoInteractionData(new Emoji("🏦"), "cash:bank_show_balance");
public CashInteraction(DiscordSocketClient client, ulong userId, Func<SocketMessageComponent, Task> action)
: base(client, userId, action)
{
}
}

View File

@@ -99,26 +99,7 @@ public partial class Gambling : GamblingModule<GamblingService>
PrettyName = "Timely"
};
public class RemindMeInteraction : NInteraction
{
public RemindMeInteraction(
[NotNull] DiscordSocketClient client,
ulong userId,
[NotNull] Func<SocketMessageComponent, Task> action)
: base(client, userId, action)
{
}
protected override NadekoInteractionData Data
=> new NadekoInteractionData(
Emote: Emoji.Parse("⏰"),
CustomId: "timely:remind_me",
Text: "Remind me"
);
}
private Func<SocketMessageComponent, Task> RemindTimelyAction(DateTime when)
=> async smc =>
private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when)
{
var tt = TimestampTag.FromDateTime(when, TimestampTagStyles.Relative);
@@ -130,7 +111,7 @@ public partial class Gambling : GamblingModule<GamblingService>
GetText(strs.timely_time));
await smc.RespondConfirmAsync(_eb, GetText(strs.remind_timely(tt)), ephemeral: true);
};
}
[Cmd]
public async Task Timely()
@@ -157,11 +138,17 @@ public partial class Gambling : GamblingModule<GamblingService>
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
var inter = new RemindMeInteraction(_client,
ctx.User.Id,
RemindTimelyAction(DateTime.UtcNow.Add(TimeSpan.FromHours(period))));
var inter = _inter
.Create(ctx.User.Id,
new SimpleInteraction<DateTime>(
new ButtonBuilder(
label: "Remind me",
emote: Emoji.Parse("⏰"),
customId: "timely:remind_me"),
RemindTimelyAction,
DateTime.UtcNow.Add(TimeSpan.FromHours(period))));
await ReplyConfirmLocalizedAsync(strs.timely(N(val), period), inter.GetInteraction());
await ReplyConfirmLocalizedAsync(strs.timely(N(val), period), inter);
}
[Cmd]
@@ -362,7 +349,7 @@ public partial class Gambling : GamblingModule<GamblingService>
await ReplyConfirmLocalizedAsync(strs.has(Format.Code(userId.ToString()), cur));
}
private async Task BankAction(SocketMessageComponent smc)
private async Task BankAction(SocketMessageComponent smc, object _)
{
var balance = await _bank.GetBalanceAsync(ctx.User.Id);
@@ -372,8 +359,12 @@ public partial class Gambling : GamblingModule<GamblingService>
.Pipe(text => smc.RespondConfirmAsync(_eb, text, ephemeral: true));
}
private NadekoButtonInteraction CreateCashInteraction()
=> new CashInteraction(_client, ctx.User.Id, BankAction).GetInteraction();
private NadekoInteraction CreateCashInteraction()
=> _inter.Create<object>(ctx.User.Id,
new(new(
customId: "cash:bank_show_balance",
emote: new Emoji("🏦")),
BankAction));
[Cmd]
[Priority(1)]

View File

@@ -73,16 +73,6 @@ public partial class Gambling
await ctx.Channel.EmbedAsync(embed);
}
public sealed class SlotInteraction : NInteraction
{
public SlotInteraction(DiscordSocketClient client, ulong userId, Func<SocketMessageComponent, Task> action) : base(client, userId, action)
{
}
protected override NadekoInteractionData Data { get; } = new(Emoji.Parse("🔁"),
"slot:again",
"Pull Again");
}
[Cmd]
public async Task Slot(ShmartNumber amount)
@@ -117,7 +107,7 @@ public partial class Gambling
.WithImageUrl($"attachment://result.png")
.WithOkColor();
// var inter = slotInteraction.GetInteraction();
//new(Emoji.Parse("🔁"), "slot:again", "Pull Again");
await ctx.Channel.SendFileAsync(imgStream,
"result.png",
embed: eb.Build()

View File

@@ -424,7 +424,7 @@ public partial class Help : NadekoModule<HelpService>
}
catch (Exception)
{
Log.Information("No old version list found. Creating a new one.");
Log.Information("No old version list found. Creating a new one");
}
var versionList = JsonSerializer.Deserialize<List<string>>(versionListString);
@@ -469,7 +469,7 @@ public partial class Help : NadekoModule<HelpService>
"https://nadekobot.readthedocs.io/en/latest/"));
private Task SelfhostAction(SocketMessageComponent smc)
private Task SelfhostAction(SocketMessageComponent smc, object _)
=> smc.RespondConfirmAsync(_eb,
@"- In case you don't want or cannot Donate to NadekoBot project, but you
- NadekoBot is a completely free and fully [open source](https://gitlab.com/kwoth/nadekobot) project which means you can run your own ""selfhosted"" instance on your computer or server for free.
@@ -484,7 +484,13 @@ public partial class Help : NadekoModule<HelpService>
[OnlyPublicBot]
public async Task Donate()
{
var selfhostInter = new DonateSelfhostingInteraction(_client, ctx.User.Id, SelfhostAction);
// => new NadekoInteractionData(new Emoji("🖥️"), "donate:selfhosting", "Selfhosting");
var selfhostInter = _inter.Create(ctx.User.Id,
new SimpleInteraction<object>(new ButtonBuilder(
emote: new Emoji("🖥️"),
customId: "donate:selfhosting",
label: "Selfhosting"),
SelfhostAction));
var eb = _eb.Create(ctx)
.WithOkColor()
@@ -525,7 +531,7 @@ Nadeko will DM you the welcome instructions, and you may start using the patron-
try
{
await (await ctx.User.CreateDMChannelAsync()).EmbedAsync(eb, inter: selfhostInter.GetInteraction());
await (await ctx.User.CreateDMChannelAsync()).EmbedAsync(eb, inter: selfhostInter);
_ = ctx.OkAsync();
}
catch

View File

@@ -1,12 +0,0 @@
namespace NadekoBot.Modules.Help;
public class DonateSelfhostingInteraction : NInteraction
{
protected override NadekoInteractionData Data
=> new NadekoInteractionData(new Emoji("🖥️"), "donate:selfhosting", "Selfhosting");
public DonateSelfhostingInteraction(DiscordSocketClient client, ulong userId, Func<SocketMessageComponent, Task> action)
: base(client, userId, action)
{
}
}

View File

@@ -1,12 +0,0 @@
namespace NadekoBot.Modules.Help;
public class DonateTroubleshootInteraction : NInteraction
{
protected override NadekoInteractionData Data
=> new NadekoInteractionData(new Emoji("❓"), "donate:troubleshoot", "Troubleshoot");
public DonateTroubleshootInteraction(DiscordSocketClient client, ulong userId, Func<SocketMessageComponent, Task> action)
: base(client, userId, action)
{
}
}

View File

@@ -433,7 +433,6 @@ public partial class Xp : NadekoModule<XpService>
? GetText(strs.in_use)
: GetText(strs.use),
"XP_SHOP_USE",
ButtonStyle.Primary,
emote: Emoji.Parse("👐"),
isDisabled: ownedItem.IsUsing);
@@ -448,7 +447,6 @@ public partial class Xp : NadekoModule<XpService>
{
var button = new ButtonBuilder(GetText(strs.buy),
"XP_SHOP_BUY",
ButtonStyle.Primary,
emote: Emoji.Parse("💰"));
var inter = new SimpleInteraction<(string key, XpShopItemType type)?>(

View File

@@ -33,7 +33,7 @@ public static class MessageChannelExtensions
public static async Task<IUserMessage> SendAsync(
this IMessageChannel channel,
string? plainText,
NadekoButtonInteraction? inter,
NadekoInteraction? inter,
Embed? embed = null,
IReadOnlyCollection<Embed>? embeds = null,
bool sanitizeAll = false)
@@ -72,7 +72,7 @@ public static class MessageChannelExtensions
IEmbedBuilder? embed,
string plainText = "",
IReadOnlyCollection<IEmbedBuilder>? embeds = null,
NadekoButtonInteraction? inter = null)
NadekoInteraction? inter = null)
=> ch.SendAsync(plainText,
inter,
embed: embed?.Build(),
@@ -83,7 +83,7 @@ public static class MessageChannelExtensions
IEmbedBuilderService eb,
string text,
MessageType type,
NadekoButtonInteraction? inter = null)
NadekoInteraction? inter = null)
{
var builder = eb.Create().WithDescription(text);

View File

@@ -56,7 +56,7 @@ public static class SocketMessageComponentExtensions
IEmbedBuilder? embed,
string plainText = "",
IReadOnlyCollection<IEmbedBuilder>? embeds = null,
NadekoButtonInteraction? inter = null,
NadekoInteraction? inter = null,
bool ephemeral = false)
=> smc.RespondAsync(plainText,
embed: embed?.Build(),
@@ -69,7 +69,7 @@ public static class SocketMessageComponentExtensions
string text,
MessageType type,
bool ephemeral = false,
NadekoButtonInteraction? inter = null)
NadekoInteraction? inter = null)
{
var builder = eb.Create().WithDescription(text);