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
|
||||
- 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
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
=> 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);
|
||||
|
@@ -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 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;
|
||||
var embed = await pageFunc(currentPage);
|
||||
|
||||
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);
|
||||
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)
|
||||
return;
|
||||
var msg = await ctx.Channel.EmbedAsync(embed, components: component);
|
||||
|
||||
await msg.AddReactionAsync(_arrowLeft);
|
||||
await msg.AddReactionAsync(_arrowRight);
|
||||
|
||||
await Task.Delay(2000);
|
||||
|
||||
var lastPageChange = DateTime.MinValue;
|
||||
|
||||
async Task ChangePage(SocketReaction r)
|
||||
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)
|
||||
|
Reference in New Issue
Block a user