mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Started rewriting all paginted responses into the new system
This commit is contained in:
@@ -96,7 +96,7 @@ public static class MessageChannelExtensions
|
||||
embed: embed?.Build(),
|
||||
embeds: embeds?.Map(x => x.Build()),
|
||||
replyTo: replyTo);
|
||||
|
||||
|
||||
// embed title and optional footer overloads
|
||||
|
||||
public static Task SendPaginatedConfirmAsync(
|
||||
|
155
src/NadekoBot/_common/Sender/ResponseBuilder.PaginationSender.cs
Normal file
155
src/NadekoBot/_common/Sender/ResponseBuilder.PaginationSender.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
public partial class ResponseBuilder
|
||||
{
|
||||
public class PaginationSender<T>
|
||||
{
|
||||
private const string BUTTON_LEFT = "BUTTON_LEFT";
|
||||
private const string BUTTON_RIGHT = "BUTTON_RIGHT";
|
||||
|
||||
private static readonly IEmote _arrowLeft = Emote.Parse("<:x:1232256519844790302>");
|
||||
private static readonly IEmote _arrowRight = Emote.Parse("<:x:1232256515298295838>");
|
||||
|
||||
private readonly SourcedPaginatedResponseBuilder<T> _paginationBuilder;
|
||||
private readonly ResponseBuilder builder;
|
||||
private readonly DiscordSocketClient client;
|
||||
private int currentPage;
|
||||
|
||||
public PaginationSender(
|
||||
SourcedPaginatedResponseBuilder<T> paginationBuilder,
|
||||
ResponseBuilder builder
|
||||
)
|
||||
{
|
||||
this._paginationBuilder = paginationBuilder;
|
||||
this.builder = builder;
|
||||
|
||||
client = (DiscordSocketClient)builder.ctx.Client;
|
||||
currentPage = 0;
|
||||
}
|
||||
|
||||
public async Task SendAsync(bool ephemeral = false)
|
||||
{
|
||||
var lastPage = (_paginationBuilder.TotalElements - 1)
|
||||
/ _paginationBuilder.ItemsPerPage;
|
||||
|
||||
var items = (await _paginationBuilder.ItemsFunc(currentPage)).ToArray();
|
||||
var embed = await _paginationBuilder.PageFunc(items, currentPage);
|
||||
|
||||
if (_paginationBuilder.AddPaginatedFooter)
|
||||
embed.AddPaginatedFooter(currentPage, lastPage);
|
||||
|
||||
SimpleInteraction<T>? maybeInter = null;
|
||||
|
||||
async Task<ComponentBuilder> GetComponentBuilder()
|
||||
{
|
||||
var cb = new ComponentBuilder();
|
||||
|
||||
cb.WithButton(new ButtonBuilder()
|
||||
.WithStyle(ButtonStyle.Primary)
|
||||
.WithCustomId(BUTTON_LEFT)
|
||||
.WithDisabled(lastPage == 0)
|
||||
.WithEmote(_arrowLeft)
|
||||
.WithDisabled(currentPage <= 0));
|
||||
// todo
|
||||
// if (interFactory is not null)
|
||||
// {
|
||||
// maybeInter = await interFactory(currentPage);
|
||||
//
|
||||
// if (maybeInter is not null)
|
||||
// cb.WithButton(maybeInter.Button);
|
||||
// }
|
||||
|
||||
cb.WithButton(new ButtonBuilder()
|
||||
.WithStyle(ButtonStyle.Primary)
|
||||
.WithCustomId(BUTTON_RIGHT)
|
||||
.WithDisabled(lastPage == 0 || currentPage >= lastPage)
|
||||
.WithEmote(_arrowRight));
|
||||
|
||||
return cb;
|
||||
}
|
||||
|
||||
async Task UpdatePageAsync(SocketMessageComponent smc)
|
||||
{
|
||||
var pageItems = (await _paginationBuilder.ItemsFunc(currentPage)).ToArray();
|
||||
var toSend = await _paginationBuilder.PageFunc(pageItems, currentPage);
|
||||
if (_paginationBuilder.AddPaginatedFooter)
|
||||
toSend.AddPaginatedFooter(currentPage, lastPage);
|
||||
|
||||
var component = (await GetComponentBuilder()).Build();
|
||||
|
||||
await smc.ModifyOriginalResponseAsync(x =>
|
||||
{
|
||||
x.Embed = toSend.Build();
|
||||
x.Components = component;
|
||||
});
|
||||
}
|
||||
|
||||
var model = builder.Build(ephemeral);
|
||||
|
||||
var component = (await GetComponentBuilder()).Build();
|
||||
var msg = await model.TargetChannel
|
||||
.SendMessageAsync(model.Text,
|
||||
embed: embed.Build(),
|
||||
components: component,
|
||||
messageReference: model.MessageReference);
|
||||
|
||||
async Task OnInteractionAsync(SocketInteraction si)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (si is not SocketMessageComponent smc)
|
||||
return;
|
||||
|
||||
if (smc.Message.Id != msg.Id)
|
||||
return;
|
||||
|
||||
await si.DeferAsync();
|
||||
if (smc.User.Id != model.User.Id)
|
||||
return;
|
||||
|
||||
if (smc.Data.CustomId == BUTTON_LEFT)
|
||||
{
|
||||
if (currentPage == 0)
|
||||
return;
|
||||
|
||||
--currentPage;
|
||||
_ = UpdatePageAsync(smc);
|
||||
}
|
||||
else if (smc.Data.CustomId == BUTTON_RIGHT)
|
||||
{
|
||||
if (currentPage >= lastPage)
|
||||
return;
|
||||
|
||||
++currentPage;
|
||||
_ = UpdatePageAsync(smc);
|
||||
}
|
||||
else if (maybeInter is { } inter && inter.Button.CustomId == smc.Data.CustomId)
|
||||
{
|
||||
await inter.TriggerAsync(smc);
|
||||
_ = UpdatePageAsync(smc);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error in pagination: {ErrorMessage}", ex.Message);
|
||||
}
|
||||
}
|
||||
// todo re-add
|
||||
// if (lastPage == 0 && interFactory is null)
|
||||
// return;
|
||||
|
||||
if (lastPage == 0)
|
||||
return;
|
||||
|
||||
var client = this.client;
|
||||
|
||||
client.InteractionCreated += OnInteractionAsync;
|
||||
|
||||
await Task.Delay(30_000);
|
||||
|
||||
client.InteractionCreated -= OnInteractionAsync;
|
||||
|
||||
await msg.ModifyAsync(mp => mp.Components = new ComponentBuilder().Build());
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
public sealed class ResponseBuilder
|
||||
public sealed partial class ResponseBuilder
|
||||
{
|
||||
private ICommandContext? ctx = null;
|
||||
private IMessageChannel? channel = null;
|
||||
@@ -44,33 +44,53 @@ public sealed class ResponseBuilder
|
||||
failIfNotExists: false);
|
||||
}
|
||||
|
||||
public async Task<IUserMessage> SendAsync(bool ephemeral = false)
|
||||
public ResponseMessageModel Build(bool ephemeral = false)
|
||||
{
|
||||
// todo use ephemeral in interactions
|
||||
var targetChannel = InternalResolveChannel() ?? throw new ArgumentNullException(nameof(channel));
|
||||
var msgReference = CreateMessageReference(targetChannel);
|
||||
|
||||
var txt = GetText(locTxt);
|
||||
// todo check message sanitization
|
||||
|
||||
if (sanitizeMentions)
|
||||
txt = txt?.SanitizeMentions(true);
|
||||
var buildModel = new ResponseMessageModel()
|
||||
{
|
||||
TargetChannel = targetChannel,
|
||||
MessageReference = msgReference,
|
||||
Text = txt,
|
||||
User = ctx?.User,
|
||||
Embed = embed ?? embedBuilder?.Build(),
|
||||
Embeds = embeds?.Map(x => x.Build()),
|
||||
SanitizeMentions = sanitizeMentions ? new(AllowedMentionTypes.Users) : AllowedMentions.All
|
||||
};
|
||||
|
||||
return buildModel;
|
||||
}
|
||||
|
||||
public Task<IUserMessage> SendAsync(bool ephemeral = false)
|
||||
{
|
||||
var model = Build(ephemeral);
|
||||
return SendAsync(model);
|
||||
}
|
||||
|
||||
public async Task<IUserMessage> SendAsync(ResponseMessageModel model)
|
||||
{
|
||||
if (this.fileStream is Stream stream)
|
||||
return await targetChannel.SendFileAsync(stream,
|
||||
return await model.TargetChannel.SendFileAsync(stream,
|
||||
filename: fileName,
|
||||
txt,
|
||||
embed: embed ?? embedBuilder?.Build(),
|
||||
model.Text,
|
||||
embed: model.Embed,
|
||||
components: null,
|
||||
allowedMentions: sanitizeMentions ? new(AllowedMentionTypes.Users) : AllowedMentions.All,
|
||||
messageReference: msgReference);
|
||||
allowedMentions: model.SanitizeMentions,
|
||||
messageReference: model.MessageReference);
|
||||
|
||||
return await targetChannel.SendMessageAsync(
|
||||
txt,
|
||||
embed: embed ?? embedBuilder?.Build(),
|
||||
embeds: embeds?.Map(x => x.Build()),
|
||||
return await model.TargetChannel.SendMessageAsync(
|
||||
model.Text,
|
||||
embed: model.Embed,
|
||||
embeds: model.Embeds,
|
||||
components: null,
|
||||
allowedMentions: sanitizeMentions ? new(AllowedMentionTypes.Users) : AllowedMentions.All,
|
||||
messageReference: msgReference);
|
||||
allowedMentions: model.SanitizeMentions,
|
||||
messageReference: model.MessageReference);
|
||||
}
|
||||
|
||||
private ulong? InternalResolveGuildId(IMessageChannel? targetChannel)
|
||||
@@ -263,4 +283,94 @@ public sealed class ResponseBuilder
|
||||
this.fileName = fileName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaginatedResponseBuilder Paginated()
|
||||
=> new(this);
|
||||
}
|
||||
|
||||
public class PaginatedResponseBuilder
|
||||
{
|
||||
protected readonly ResponseBuilder _builder;
|
||||
|
||||
public PaginatedResponseBuilder(ResponseBuilder builder)
|
||||
{
|
||||
_builder = builder;
|
||||
}
|
||||
|
||||
public SourcedPaginatedResponseBuilder<T> Items<T>(IReadOnlyCollection<T> items)
|
||||
=> new SourcedPaginatedResponseBuilder<T>(_builder)
|
||||
.Items(items);
|
||||
}
|
||||
|
||||
public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilder
|
||||
{
|
||||
private IReadOnlyCollection<T>? items;
|
||||
public Func<IReadOnlyList<T>, int, Task<EmbedBuilder>> PageFunc { get; private set; }
|
||||
public Func<int, Task<IEnumerable<T>>> ItemsFunc { get; set; }
|
||||
public int TotalElements { get; private set; } = 1;
|
||||
public int ItemsPerPage { get; private set; } = 9;
|
||||
public bool AddPaginatedFooter { get; private set; } = true;
|
||||
public bool IsEphemeral { get; private set; }
|
||||
|
||||
public SourcedPaginatedResponseBuilder(ResponseBuilder builder)
|
||||
: base(builder)
|
||||
{
|
||||
}
|
||||
|
||||
public SourcedPaginatedResponseBuilder<T> Items(IReadOnlyCollection<T> items)
|
||||
{
|
||||
this.items = items;
|
||||
ItemsFunc = (i) => Task.FromResult(this.items.Skip(i * ItemsPerPage).Take(ItemsPerPage));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public SourcedPaginatedResponseBuilder<T> PageSize(int i)
|
||||
{
|
||||
ItemsPerPage = i;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SourcedPaginatedResponseBuilder<T> CurrentPage(int i)
|
||||
{
|
||||
InitialPage = i;
|
||||
return this;
|
||||
}
|
||||
|
||||
// todo use it
|
||||
public int InitialPage { get; set; }
|
||||
|
||||
public SourcedPaginatedResponseBuilder<T> Page(Func<IReadOnlyList<T>, int, EmbedBuilder> pageFunc)
|
||||
{
|
||||
this.PageFunc = (xs, x) => Task.FromResult(pageFunc(xs, x));
|
||||
return this;
|
||||
}
|
||||
|
||||
public SourcedPaginatedResponseBuilder<T> Page(Func<IReadOnlyList<T>, int, Task<EmbedBuilder>> pageFunc)
|
||||
{
|
||||
this.PageFunc = pageFunc;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SourcedPaginatedResponseBuilder<T> AddFooter()
|
||||
{
|
||||
AddPaginatedFooter = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SourcedPaginatedResponseBuilder<T> Ephemeral()
|
||||
{
|
||||
IsEphemeral = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Task SendAsync()
|
||||
{
|
||||
var paginationSender = new ResponseBuilder.PaginationSender<T>(
|
||||
this,
|
||||
_builder);
|
||||
|
||||
return paginationSender.SendAsync(IsEphemeral);
|
||||
}
|
||||
}
|
10
src/NadekoBot/_common/Sender/ResponseMessageModel.cs
Normal file
10
src/NadekoBot/_common/Sender/ResponseMessageModel.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
public class ResponseMessageModel
|
||||
{
|
||||
public IMessageChannel TargetChannel { get; set; }
|
||||
public MessageReference MessageReference { get; set; }
|
||||
public string Text { get; set; }
|
||||
public Embed Embed { get; set; }
|
||||
public Embed[] Embeds { get; set; }
|
||||
public AllowedMentions SanitizeMentions { get; set; }
|
||||
public IUser User { get; set; }
|
||||
}
|
Reference in New Issue
Block a user