mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Pagination is now using buttons instead of reactions
This commit is contained in:
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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);
|
||||||
|
@@ -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)
|
||||||
|
Reference in New Issue
Block a user