Started rewriting all paginted responses into the new system

This commit is contained in:
Kwoth
2024-05-01 06:26:00 +00:00
parent daa2177559
commit a25adefc65
10 changed files with 505 additions and 200 deletions

View File

@@ -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(

View 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());
}
}
}

View File

@@ -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);
}
}

View 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; }
}