Pagination is now using buttons instead of reactions

This commit is contained in:
Kwoth
2022-04-28 06:10:06 +02:00
parent 39ae070c9d
commit 35ddd150ba
6 changed files with 68 additions and 180 deletions

View File

@@ -14,6 +14,10 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
- There shouldn't be any breaking changes - There shouldn't be any breaking changes
- Added `.stondel` command which, when toggled, will make the bot delete online stream messages on the server when the stream goes offline - Added `.stondel` command which, when toggled, will make the bot delete online stream messages on the server when the stream goes offline
### Changed
- Pagination is now using buttons instead of reactions
### Fixed ### Fixed
- Fixed `.deletexp` command - Fixed `.deletexp` command

View File

@@ -5,9 +5,13 @@ namespace NadekoBot;
public static class MedusaExtensions public static class MedusaExtensions
{ {
public static Task<IUserMessage> EmbedAsync(this IMessageChannel ch, IEmbedBuilder embed, string msg = "") public static Task<IUserMessage> EmbedAsync(this IMessageChannel ch,
IEmbedBuilder embed,
string msg = "",
MessageComponent? components = null)
=> ch.SendMessageAsync(msg, => ch.SendMessageAsync(msg,
embed: embed.Build(), embed: embed.Build(),
components: components,
options: new() options: new()
{ {
RetryMode = RetryMode.AlwaysRetry RetryMode = RetryMode.AlwaysRetry

View File

@@ -1,95 +0,0 @@
#nullable disable
namespace NadekoBot.Common;
public sealed class ReactionEventWrapper : IDisposable
{
public event Action<SocketReaction> OnReactionAdded = delegate { };
public event Action<SocketReaction> OnReactionRemoved = delegate { };
public event Action OnReactionsCleared = delegate { };
public IUserMessage Message { get; }
private readonly DiscordSocketClient _client;
private bool disposing;
public ReactionEventWrapper(DiscordSocketClient client, IUserMessage msg)
{
Message = msg ?? throw new ArgumentNullException(nameof(msg));
_client = client;
_client.ReactionAdded += Discord_ReactionAdded;
_client.ReactionRemoved += Discord_ReactionRemoved;
_client.ReactionsCleared += Discord_ReactionsCleared;
}
public void Dispose()
{
if (disposing)
return;
disposing = true;
UnsubAll();
}
private Task Discord_ReactionsCleared(Cacheable<IUserMessage, ulong> msg, Cacheable<IMessageChannel, ulong> channel)
{
Task.Run(() =>
{
try
{
if (msg.Id == Message.Id)
OnReactionsCleared?.Invoke();
}
catch { }
});
return Task.CompletedTask;
}
private Task Discord_ReactionRemoved(
Cacheable<IUserMessage, ulong> msg,
Cacheable<IMessageChannel, ulong> cacheable,
SocketReaction reaction)
{
Task.Run(() =>
{
try
{
if (msg.Id == Message.Id)
OnReactionRemoved?.Invoke(reaction);
}
catch { }
});
return Task.CompletedTask;
}
private Task Discord_ReactionAdded(
Cacheable<IUserMessage, ulong> msg,
Cacheable<IMessageChannel, ulong> cacheable,
SocketReaction reaction)
{
Task.Run(() =>
{
try
{
if (msg.Id == Message.Id)
OnReactionAdded?.Invoke(reaction);
}
catch
{
}
});
return Task.CompletedTask;
}
public void UnsubAll()
{
_client.ReactionAdded -= Discord_ReactionAdded;
_client.ReactionRemoved -= Discord_ReactionRemoved;
_client.ReactionsCleared -= Discord_ReactionsCleared;
OnReactionAdded = null;
OnReactionRemoved = null;
OnReactionsCleared = null;
}
}

View File

@@ -143,27 +143,6 @@ public static class Extensions
public static IEmbedBuilder WithErrorColor(this IEmbedBuilder eb) public static IEmbedBuilder WithErrorColor(this IEmbedBuilder eb)
=> eb.WithColor(EmbedColor.Error); => eb.WithColor(EmbedColor.Error);
public static ReactionEventWrapper OnReaction(
this IUserMessage msg,
DiscordSocketClient client,
Func<SocketReaction, Task> reactionAdded,
Func<SocketReaction, Task>? reactionRemoved = null)
{
if (reactionRemoved is null)
reactionRemoved = _ => Task.CompletedTask;
var wrap = new ReactionEventWrapper(client, msg);
wrap.OnReactionAdded += r =>
{
_ = Task.Run(() => reactionAdded(r));
};
wrap.OnReactionRemoved += r =>
{
_ = Task.Run(() => reactionRemoved(r));
};
return wrap;
}
public static HttpClient AddFakeHeaders(this HttpClient http) public static HttpClient AddFakeHeaders(this HttpClient http)
{ {
AddFakeHeaders(http.DefaultRequestHeaders); AddFakeHeaders(http.DefaultRequestHeaders);

View File

@@ -2,12 +2,14 @@ namespace NadekoBot.Extensions;
public static class MessageChannelExtensions public static class MessageChannelExtensions
{ {
private static readonly IEmote _arrowLeft = new Emoji("⬅"); public static Task<IUserMessage> EmbedAsync(
private static readonly IEmote _arrowRight = new Emoji("➡"); this IMessageChannel ch,
IEmbedBuilder embed,
public static Task<IUserMessage> EmbedAsync(this IMessageChannel ch, IEmbedBuilder embed, string msg = "") string msg = "",
MessageComponent? components = null)
=> ch.SendMessageAsync(msg, => ch.SendMessageAsync(msg,
embed: embed.Build(), embed: embed.Build(),
components: components,
options: new() options: new()
{ {
RetryMode = RetryMode.AlwaysRetry RetryMode = RetryMode.AlwaysRetry
@@ -117,9 +119,12 @@ public static class MessageChannelExtensions
itemsPerPage, itemsPerPage,
addPaginatedFooter); addPaginatedFooter);
/// <summary> private const string BUTTON_LEFT = "BUTTON_LEFT";
/// danny kamisama private const string BUTTON_RIGHT = "BUTTON_RIGHT";
/// </summary>
private static readonly IEmote _arrowLeft = new Emoji("⬅️");
private static readonly IEmote _arrowRight = new Emoji("➡️");
public static async Task SendPaginatedConfirmAsync( public static async Task SendPaginatedConfirmAsync(
this ICommandContext ctx, this ICommandContext ctx,
int currentPage, int currentPage,
@@ -128,87 +133,78 @@ public static class MessageChannelExtensions
int itemsPerPage, int itemsPerPage,
bool addPaginatedFooter = true) bool addPaginatedFooter = true)
{ {
var embed = await pageFunc(currentPage);
var lastPage = (totalElements - 1) / itemsPerPage; var lastPage = (totalElements - 1) / itemsPerPage;
var canPaginate = true; var embed = await pageFunc(currentPage);
if (ctx.Guild is SocketGuild sg && !sg.CurrentUser.GetPermissions((IGuildChannel)ctx.Channel).AddReactions)
canPaginate = false;
if (!canPaginate) if (addPaginatedFooter)
embed.WithFooter("⚠️ AddReaction permission required for pagination.");
else if (addPaginatedFooter)
embed.AddPaginatedFooter(currentPage, lastPage); embed.AddPaginatedFooter(currentPage, lastPage);
var msg = await ctx.Channel.EmbedAsync(embed); var component = new ComponentBuilder()
.WithButton(new ButtonBuilder()
.WithStyle(ButtonStyle.Secondary)
.WithCustomId(BUTTON_LEFT)
.WithDisabled(lastPage == 0)
.WithEmote(_arrowLeft))
.WithButton(new ButtonBuilder()
.WithStyle(ButtonStyle.Primary)
.WithCustomId(BUTTON_RIGHT)
.WithDisabled(lastPage == 0)
.WithEmote(_arrowRight))
.Build();
if (lastPage == 0 || !canPaginate) var msg = await ctx.Channel.EmbedAsync(embed, components: component);
return;
await msg.AddReactionAsync(_arrowLeft); Task OnInteractionAsync(SocketInteraction si)
await msg.AddReactionAsync(_arrowRight);
await Task.Delay(2000);
var lastPageChange = DateTime.MinValue;
async Task ChangePage(SocketReaction r)
{ {
try _ = Task.Run(async () =>
{ {
if (r.UserId != ctx.User.Id) await si.DeferAsync();
if (si is not SocketMessageComponent smc)
return; return;
if (DateTime.UtcNow - lastPageChange < TimeSpan.FromSeconds(1))
if (smc.User.Id != ctx.User.Id || smc.Message.Id != msg.Id)
return; return;
if (r.Emote.Name == _arrowLeft.Name)
if (smc.Data.CustomId == BUTTON_LEFT)
{ {
if (currentPage == 0) if (currentPage == 0)
return; return;
lastPageChange = DateTime.UtcNow;
var toSend = await pageFunc(--currentPage); var toSend = await pageFunc(--currentPage);
if (addPaginatedFooter) if (addPaginatedFooter)
toSend.AddPaginatedFooter(currentPage, lastPage); toSend.AddPaginatedFooter(currentPage, lastPage);
await msg.ModifyAsync(x => x.Embed = toSend.Build());
await smc.ModifyOriginalResponseAsync(x => x.Embed = toSend.Build());
} }
else if (r.Emote.Name == _arrowRight.Name) else if (smc.Data.CustomId == BUTTON_RIGHT)
{ {
if (lastPage > currentPage) if (lastPage > currentPage)
{ {
lastPageChange = DateTime.UtcNow;
var toSend = await pageFunc(++currentPage); var toSend = await pageFunc(++currentPage);
if (addPaginatedFooter) if (addPaginatedFooter)
toSend.AddPaginatedFooter(currentPage, lastPage); toSend.AddPaginatedFooter(currentPage, lastPage);
await msg.ModifyAsync(x => x.Embed = toSend.Build());
await smc.ModifyOriginalResponseAsync(x => x.Embed = toSend.Build());
} }
} }
} });
catch (Exception)
{ return Task.CompletedTask;
//ignored
}
} }
using (msg.OnReaction((DiscordSocketClient)ctx.Client, ChangePage, ChangePage)) if (lastPage == 0)
{ return;
await Task.Delay(30000);
}
try var client = (DiscordSocketClient)ctx.Client;
{
if (msg.Channel is ITextChannel && ((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages) client.InteractionCreated += OnInteractionAsync;
await msg.RemoveAllReactionsAsync();
else await Task.Delay(30_000);
{
await msg.Reactions.Where(x => x.Value.IsMe) client.InteractionCreated -= OnInteractionAsync;
.Select(x => msg.RemoveReactionAsync(x.Key, ctx.Client.CurrentUser))
.WhenAll(); await msg.ModifyAsync(mp => mp.Components = new ComponentBuilder().Build());
}
}
catch
{
// ignored
}
} }
public static Task OkAsync(this ICommandContext ctx) public static Task OkAsync(this ICommandContext ctx)