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
- 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 `.deletexp` command

View File

@@ -5,9 +5,13 @@ namespace NadekoBot;
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,
embed: embed.Build(),
components: components,
options: new()
{
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

@@ -48,7 +48,7 @@ public partial class Utility : NadekoModule
_tracker = tracker;
_httpFactory = httpFactory;
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]

View File

@@ -143,27 +143,6 @@ public static class Extensions
public static IEmbedBuilder WithErrorColor(this IEmbedBuilder eb)
=> 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)
{
AddFakeHeaders(http.DefaultRequestHeaders);

View File

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