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

@@ -32,13 +32,15 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
var ex = await _service.AddAsync(ctx.Guild?.Id, key, message); var ex = await _service.AddAsync(ctx.Guild?.Id, key, message);
await Response().Embed(new EmbedBuilder() await Response()
.WithOkColor() .Embed(new EmbedBuilder()
.WithTitle(GetText(strs.expr_new)) .WithOkColor()
.WithDescription($"#{new kwum(ex.Id)}") .WithTitle(GetText(strs.expr_new))
.AddField(GetText(strs.trigger), key) .WithDescription($"#{new kwum(ex.Id)}")
.AddField(GetText(strs.response), .AddField(GetText(strs.trigger), key)
message.Length > 1024 ? GetText(strs.redacted_too_long) : message)).SendAsync(); .AddField(GetText(strs.response),
message.Length > 1024 ? GetText(strs.redacted_too_long) : message))
.SendAsync();
} }
[Cmd] [Cmd]
@@ -101,12 +103,12 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
if (ex is not null) if (ex is not null)
{ {
await ctx.Channel.EmbedAsync(new EmbedBuilder() await ctx.Channel.EmbedAsync(new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithTitle(GetText(strs.expr_edited)) .WithTitle(GetText(strs.expr_edited))
.WithDescription($"#{id}") .WithDescription($"#{id}")
.AddField(GetText(strs.trigger), ex.Trigger) .AddField(GetText(strs.trigger), ex.Trigger)
.AddField(GetText(strs.response), .AddField(GetText(strs.response),
message.Length > 1024 ? GetText(strs.redacted_too_long) : message)); message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
} }
else else
{ {
@@ -123,33 +125,36 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
return; return;
} }
var expressions = _service.GetExpressionsFor(ctx.Guild?.Id); var allExpressions = _service.GetExpressionsFor(ctx.Guild?.Id)
.OrderBy(x => x.Trigger)
.ToArray();
if (expressions is null || !expressions.Any()) if (allExpressions is null || !allExpressions.Any())
{ {
await Response().Error(strs.expr_no_found).SendAsync(); await Response().Error(strs.expr_no_found).SendAsync();
return; return;
} }
await ctx.SendPaginatedConfirmAsync(page, await Response()
curPage => .Paginated()
{ .Items(allExpressions)
var desc = expressions.OrderBy(ex => ex.Trigger) .PageSize(20)
.Skip(curPage * 20) .CurrentPage(page)
.Take(20) .Page((exprs, _) =>
.Select(ex => $"{(ex.ContainsAnywhere ? "🗯" : "")}" {
+ $"{(ex.DmResponse ? "" : "")}" var desc = exprs
+ $"{(ex.AutoDeleteTrigger ? "" : "")}" .Select(ex => $"{(ex.ContainsAnywhere ? "🗯" : "")}"
+ $"`{(kwum)ex.Id}` {ex.Trigger}" + $"{(ex.DmResponse ? "" : "")}"
+ (string.IsNullOrWhiteSpace(ex.Reactions) + $"{(ex.AutoDeleteTrigger ? "" : "")}"
? string.Empty + $"`{(kwum)ex.Id}` {ex.Trigger}"
: " // " + string.Join(" ", ex.GetReactions()))) + (string.IsNullOrWhiteSpace(ex.Reactions)
.Join('\n'); ? string.Empty
: " // " + string.Join(" ", ex.GetReactions())))
.Join('\n');
return new EmbedBuilder().WithOkColor().WithTitle(GetText(strs.expressions)).WithDescription(desc); return new EmbedBuilder().WithOkColor().WithTitle(GetText(strs.expressions)).WithDescription(desc);
}, })
expressions.Length, .SendAsync();
20);
} }
[Cmd] [Cmd]
@@ -164,11 +169,11 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
} }
await ctx.Channel.EmbedAsync(new EmbedBuilder() await ctx.Channel.EmbedAsync(new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithDescription($"#{id}") .WithDescription($"#{id}")
.AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024)) .AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
.AddField(GetText(strs.response), .AddField(GetText(strs.response),
found.Response.TrimTo(1000).Replace("](", "]\\("))); found.Response.TrimTo(1000).Replace("](", "]\\(")));
} }
public async Task ExprDeleteInternalAsync(kwum id) public async Task ExprDeleteInternalAsync(kwum id)
@@ -178,11 +183,11 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
if (ex is not null) if (ex is not null)
{ {
await ctx.Channel.EmbedAsync(new EmbedBuilder() await ctx.Channel.EmbedAsync(new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithTitle(GetText(strs.expr_deleted)) .WithTitle(GetText(strs.expr_deleted))
.WithDescription($"#{id}") .WithDescription($"#{id}")
.AddField(GetText(strs.trigger), ex.Trigger.TrimTo(1024)) .AddField(GetText(strs.trigger), ex.Trigger.TrimTo(1024))
.AddField(GetText(strs.response), ex.Response.TrimTo(1024))); .AddField(GetText(strs.response), ex.Response.TrimTo(1024)));
} }
else else
{ {
@@ -328,8 +333,8 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
public async Task ExprClear() public async Task ExprClear()
{ {
if (await PromptUserConfirmAsync(new EmbedBuilder() if (await PromptUserConfirmAsync(new EmbedBuilder()
.WithTitle("Expression clear") .WithTitle("Expression clear")
.WithDescription("This will delete all expressions on this server."))) .WithDescription("This will delete all expressions on this server.")))
{ {
var count = _service.DeleteAllExpressions(ctx.Guild.Id); var count = _service.DeleteAllExpressions(ctx.Guild.Id);
await Response().Confirm(strs.exprs_cleared(count)).SendAsync(); await Response().Confirm(strs.exprs_cleared(count)).SendAsync();

View File

@@ -20,7 +20,7 @@ public partial class Permissions
throw new ArgumentOutOfRangeException(nameof(page)); throw new ArgumentOutOfRangeException(nameof(page));
var list = _service.GetBlacklist(); var list = _service.GetBlacklist();
var items = await list.Where(x => x.Type == type) var allItems = await list.Where(x => x.Type == type)
.Select(async i => .Select(async i =>
{ {
try try
@@ -52,18 +52,25 @@ public partial class Permissions
}) })
.WhenAll(); .WhenAll();
await ctx.SendPaginatedConfirmAsync(page, await Response()
curPage => .Paginated()
{ .Items(allItems)
var pageItems = items.Skip(10 * curPage).Take(10).ToList(); .PageSize(10)
.CurrentPage(page)
.Page((pageItems, _) =>
{
if (pageItems.Count == 0)
return new EmbedBuilder()
.WithOkColor()
.WithTitle(title)
.WithDescription(GetText(strs.empty_page));
if (pageItems.Count == 0) return new EmbedBuilder()
return new EmbedBuilder().WithOkColor().WithTitle(title).WithDescription(GetText(strs.empty_page)); .WithTitle(title)
.WithDescription(allItems.Join('\n'))
return new EmbedBuilder().WithTitle(title).WithDescription(pageItems.Join('\n')).WithOkColor(); .WithOkColor();
}, })
items.Length, .SendAsync();
10);
} }
[Cmd] [Cmd]
@@ -130,13 +137,17 @@ public partial class Permissions
if (action == AddRemove.Add) if (action == AddRemove.Add)
{ {
await Response().Confirm(strs.blacklisted(Format.Code(type.ToString()), await Response()
Format.Code(id.ToString()))).SendAsync(); .Confirm(strs.blacklisted(Format.Code(type.ToString()),
Format.Code(id.ToString())))
.SendAsync();
} }
else else
{ {
await Response().Confirm(strs.unblacklisted(Format.Code(type.ToString()), await Response()
Format.Code(id.ToString()))).SendAsync(); .Confirm(strs.unblacklisted(Format.Code(type.ToString()),
Format.Code(id.ToString())))
.SendAsync();
} }
} }
} }

View File

@@ -120,7 +120,7 @@ public partial class Searches
await Response().Embed(new EmbedBuilder().WithOkColor().WithDescription(GetText(strs.feed_no_feed))).SendAsync(); await Response().Embed(new EmbedBuilder().WithOkColor().WithDescription(GetText(strs.feed_no_feed))).SendAsync();
return; return;
} }
await ctx.SendPaginatedConfirmAsync(0, await ctx.SendPaginatedConfirmAsync(0,
cur => cur =>
{ {

View File

@@ -70,7 +70,7 @@ public partial class Searches
if (page-- < 1) if (page-- < 1)
return; return;
var streams = new List<FollowedStream>(); var allStreams = new List<FollowedStream>();
await using (var uow = _db.GetDbContext()) await using (var uow = _db.GetDbContext())
{ {
var all = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(gc => gc.FollowedStreams)) var all = uow.GuildConfigsForId(ctx.Guild.Id, set => set.Include(gc => gc.FollowedStreams))
@@ -83,34 +83,32 @@ public partial class Searches
if (((SocketGuild)ctx.Guild).GetTextChannel(fs.ChannelId) is null) if (((SocketGuild)ctx.Guild).GetTextChannel(fs.ChannelId) is null)
await _service.UnfollowStreamAsync(fs.GuildId, index); await _service.UnfollowStreamAsync(fs.GuildId, index);
else else
streams.Insert(0, fs); allStreams.Insert(0, fs);
} }
} }
await ctx.SendPaginatedConfirmAsync(page, await Response()
cur => .Paginated()
{ .Items(allStreams)
var elements = streams .PageSize(12)
.Skip(cur * 12) .CurrentPage(page)
.Take(12) .Page((elements, cur) =>
.ToList(); {
if (elements.Count == 0)
return new EmbedBuilder().WithDescription(GetText(strs.streams_none)).WithErrorColor();
if (elements.Count == 0) var eb = new EmbedBuilder().WithTitle(GetText(strs.streams_follow_title)).WithOkColor();
return new EmbedBuilder().WithDescription(GetText(strs.streams_none)).WithErrorColor(); for (var index = 0; index < elements.Count; index++)
{
var elem = elements[index];
eb.AddField($"**#{index + 1 + (12 * cur)}** {elem.Username.ToLower()}",
$"【{elem.Type}】\n<#{elem.ChannelId}>\n{elem.Message?.TrimTo(50)}",
true);
}
var eb = new EmbedBuilder().WithTitle(GetText(strs.streams_follow_title)).WithOkColor(); return eb;
for (var index = 0; index < elements.Count; index++) })
{ .SendAsync();
var elem = elements[index];
eb.AddField($"**#{index + 1 + (12 * cur)}** {elem.Username.ToLower()}",
$"【{elem.Type}】\n<#{elem.ChannelId}>\n{elem.Message?.TrimTo(50)}",
true);
}
return eb;
},
streams.Count,
12);
} }
[Cmd] [Cmd]

View File

@@ -37,36 +37,39 @@ public partial class Utility
var invites = await channel.GetInvitesAsync(); var invites = await channel.GetInvitesAsync();
await ctx.SendPaginatedConfirmAsync(page,
cur =>
{
var i = 1;
var invs = invites.Skip(cur * 9).Take(9).ToList();
if (!invs.Any()) await Response()
return new EmbedBuilder().WithErrorColor().WithDescription(GetText(strs.no_invites)); .Paginated()
.Items(invites)
.PageSize(9)
.Page((invs, _) =>
{
var i = 1;
var embed = new EmbedBuilder().WithOkColor(); if (!invs.Any())
foreach (var inv in invites) return new EmbedBuilder().WithErrorColor().WithDescription(GetText(strs.no_invites));
{
var expiryString = inv.MaxAge is null or 0 || inv.CreatedAt is null
? "∞"
: (inv.CreatedAt.Value.AddSeconds(inv.MaxAge.Value).UtcDateTime - DateTime.UtcNow).ToString(
"""d\.hh\:mm\:ss""");
var creator = inv.Inviter.ToString().TrimTo(25);
var usesString = $"{inv.Uses} / {(inv.MaxUses == 0 ? "" : inv.MaxUses?.ToString())}";
var desc = $@"`{GetText(strs.inv_uses)}` **{usesString}** var embed = new EmbedBuilder().WithOkColor();
foreach (var inv in invs)
{
var expiryString = inv.MaxAge is null or 0 || inv.CreatedAt is null
? "∞"
: (inv.CreatedAt.Value.AddSeconds(inv.MaxAge.Value).UtcDateTime - DateTime.UtcNow)
.ToString(
"""d\.hh\:mm\:ss""");
var creator = inv.Inviter.ToString().TrimTo(25);
var usesString = $"{inv.Uses} / {(inv.MaxUses == 0 ? "" : inv.MaxUses?.ToString())}";
var desc = $@"`{GetText(strs.inv_uses)}` **{usesString}**
`{GetText(strs.inv_expire)}` **{expiryString}** `{GetText(strs.inv_expire)}` **{expiryString}**
{inv.Url} "; {inv.Url} ";
embed.AddField($"#{i++} {creator}", desc); embed.AddField($"#{i++} {creator}", desc);
} }
return embed; return embed;
}, })
invites.Count, .SendAsync();
9);
} }
[Cmd] [Cmd]

View File

@@ -29,12 +29,14 @@ public partial class Xp
} }
else else
{ {
await Response().Confirm( await Response()
strs.club_transfered( .Confirm(
Format.Bold(club.Name), strs.club_transfered(
Format.Bold(newOwner.ToString()) Format.Bold(club.Name),
) Format.Bold(newOwner.ToString())
).SendAsync(); )
)
.SendAsync();
} }
} }
@@ -65,7 +67,7 @@ public partial class Xp
await Response().Error(strs.club_name_too_long).SendAsync(); await Response().Error(strs.club_name_too_long).SendAsync();
return; return;
} }
if (result == ClubCreateResult.NameTaken) if (result == ClubCreateResult.NameTaken)
{ {
await Response().Error(strs.club_name_taken).SendAsync(); await Response().Error(strs.club_name_taken).SendAsync();
@@ -110,49 +112,52 @@ public partial class Xp
private async Task InternalClubInfoAsync(ClubInfo club) private async Task InternalClubInfoAsync(ClubInfo club)
{ {
var lvl = new LevelStats(club.Xp); var lvl = new LevelStats(club.Xp);
var users = club.Members.OrderByDescending(x => var allUsers = club.Members.OrderByDescending(x =>
{ {
var l = new LevelStats(x.TotalXp).Level; var l = new LevelStats(x.TotalXp).Level;
if (club.OwnerId == x.Id) if (club.OwnerId == x.Id)
return int.MaxValue; return int.MaxValue;
if (x.IsClubAdmin) if (x.IsClubAdmin)
return (int.MaxValue / 2) + l; return (int.MaxValue / 2) + l;
return l; return l;
}); })
.ToList();
await ctx.SendPaginatedConfirmAsync(0, await Response()
page => .Paginated()
{ .Items(allUsers)
var embed = new EmbedBuilder() .PageSize(10)
.WithOkColor() .CurrentPage(0)
.WithTitle($"{club}") .Page((users, _) =>
.WithDescription(GetText(strs.level_x(lvl.Level + $" ({club.Xp} xp)"))) {
.AddField(GetText(strs.desc), var embed = new EmbedBuilder()
string.IsNullOrWhiteSpace(club.Description) ? "-" : club.Description) .WithOkColor()
.AddField(GetText(strs.owner), club.Owner.ToString(), true) .WithTitle($"{club}")
// .AddField(GetText(strs.level_req), club.MinimumLevelReq.ToString(), true) .WithDescription(GetText(strs.level_x(lvl.Level + $" ({club.Xp} xp)")))
.AddField(GetText(strs.members), .AddField(GetText(strs.desc),
string.Join("\n", string.IsNullOrWhiteSpace(club.Description) ? "-" : club.Description)
users.Skip(page * 10) .AddField(GetText(strs.owner), club.Owner.ToString(), true)
.Take(10) // .AddField(GetText(strs.level_req), club.MinimumLevelReq.ToString(), true)
.Select(x => .AddField(GetText(strs.members),
{ string.Join("\n",
var l = new LevelStats(x.TotalXp); users
var lvlStr = Format.Bold($" ⟪{l.Level}⟫"); .Select(x =>
if (club.OwnerId == x.Id) {
return x + "🌟" + lvlStr; var l = new LevelStats(x.TotalXp);
if (x.IsClubAdmin) var lvlStr = Format.Bold($" ⟪{l.Level}⟫");
return x + "⭐" + lvlStr; if (club.OwnerId == x.Id)
return x + lvlStr; return x + "🌟" + lvlStr;
}))); if (x.IsClubAdmin)
return x + "⭐" + lvlStr;
return x + lvlStr;
})));
if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute)) if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute))
return embed.WithThumbnailUrl(club.ImageUrl); return embed.WithThumbnailUrl(club.ImageUrl);
return embed; return embed;
}, })
club.Members.Count, .SendAsync();
10);
} }
[Cmd] [Cmd]
@@ -204,14 +209,16 @@ public partial class Xp
return ctx.SendPaginatedConfirmAsync(page, return ctx.SendPaginatedConfirmAsync(page,
_ => _ =>
{ {
var toShow = string.Join("\n", bans var toShow = string.Join("\n",
.Skip(page * 10).Take(10) bans
.Select(x => x.ToString())); .Skip(page * 10)
.Take(10)
.Select(x => x.ToString()));
return new EmbedBuilder() return new EmbedBuilder()
.WithTitle(GetText(strs.club_bans_for(club.ToString()))) .WithTitle(GetText(strs.club_bans_for(club.ToString())))
.WithDescription(toShow) .WithDescription(toShow)
.WithOkColor(); .WithOkColor();
}, },
bans.Length, bans.Length,
10); 10);
@@ -235,9 +242,9 @@ public partial class Xp
var toShow = string.Join("\n", apps.Skip(page * 10).Take(10).Select(x => x.ToString())); var toShow = string.Join("\n", apps.Skip(page * 10).Take(10).Select(x => x.ToString()));
return new EmbedBuilder() return new EmbedBuilder()
.WithTitle(GetText(strs.club_apps_for(club.ToString()))) .WithTitle(GetText(strs.club_apps_for(club.ToString())))
.WithDescription(toShow) .WithDescription(toShow)
.WithOkColor(); .WithOkColor();
}, },
apps.Length, apps.Length,
10); 10);
@@ -283,7 +290,7 @@ public partial class Xp
else if (result == ClubAcceptResult.NotOwnerOrAdmin) else if (result == ClubAcceptResult.NotOwnerOrAdmin)
await Response().Error(strs.club_admin_perms).SendAsync(); await Response().Error(strs.club_admin_perms).SendAsync();
} }
[Cmd] [Cmd]
[Priority(1)] [Priority(1)]
public Task ClubReject(IUser user) public Task ClubReject(IUser user)
@@ -296,9 +303,9 @@ public partial class Xp
var result = _service.RejectApplication(ctx.User.Id, userName, out var discordUser); var result = _service.RejectApplication(ctx.User.Id, userName, out var discordUser);
if (result == ClubDenyResult.Rejected) if (result == ClubDenyResult.Rejected)
await Response().Confirm(strs.club_rejected(Format.Bold(discordUser.ToString()))).SendAsync(); await Response().Confirm(strs.club_rejected(Format.Bold(discordUser.ToString()))).SendAsync();
else if(result == ClubDenyResult.NoSuchApplicant) else if (result == ClubDenyResult.NoSuchApplicant)
await Response().Error(strs.club_accept_invalid_applicant).SendAsync(); await Response().Error(strs.club_accept_invalid_applicant).SendAsync();
else if(result == ClubDenyResult.NotOwnerOrAdmin) else if (result == ClubDenyResult.NotOwnerOrAdmin)
await Response().Error(strs.club_admin_perms).SendAsync(); await Response().Error(strs.club_admin_perms).SendAsync();
} }
@@ -327,8 +334,10 @@ public partial class Xp
var result = _service.Kick(ctx.User.Id, userName, out var club); var result = _service.Kick(ctx.User.Id, userName, out var club);
if (result == ClubKickResult.Success) if (result == ClubKickResult.Success)
{ {
return Response().Confirm(strs.club_user_kick(Format.Bold(userName), return Response()
Format.Bold(club.ToString()))).SendAsync(); .Confirm(strs.club_user_kick(Format.Bold(userName),
Format.Bold(club.ToString())))
.SendAsync();
} }
if (result == ClubKickResult.Hierarchy) if (result == ClubKickResult.Hierarchy)
@@ -352,8 +361,10 @@ public partial class Xp
var result = _service.Ban(ctx.User.Id, userName, out var club); var result = _service.Ban(ctx.User.Id, userName, out var club);
if (result == ClubBanResult.Success) if (result == ClubBanResult.Success)
{ {
return Response().Confirm(strs.club_user_banned(Format.Bold(userName), return Response()
Format.Bold(club.ToString()))).SendAsync(); .Confirm(strs.club_user_banned(Format.Bold(userName),
Format.Bold(club.ToString())))
.SendAsync();
} }
if (result == ClubBanResult.Unbannable) if (result == ClubBanResult.Unbannable)
@@ -378,8 +389,10 @@ public partial class Xp
if (result == ClubUnbanResult.Success) if (result == ClubUnbanResult.Success)
{ {
return Response().Confirm(strs.club_user_unbanned(Format.Bold(userName), return Response()
Format.Bold(club.ToString()))).SendAsync(); .Confirm(strs.club_user_unbanned(Format.Bold(userName),
Format.Bold(club.ToString())))
.SendAsync();
} }
if (result == ClubUnbanResult.WrongUser) if (result == ClubUnbanResult.WrongUser)
@@ -400,10 +413,10 @@ public partial class Xp
: desc; : desc;
var eb = new EmbedBuilder() var eb = new EmbedBuilder()
.WithAuthor(ctx.User) .WithAuthor(ctx.User)
.WithTitle(GetText(strs.club_desc_update)) .WithTitle(GetText(strs.club_desc_update))
.WithOkColor() .WithOkColor()
.WithDescription(desc); .WithDescription(desc);
await Response().Embed(eb).SendAsync(); await Response().Embed(eb).SendAsync();
} }
@@ -450,11 +463,11 @@ public partial class Xp
await Response().Error(strs.club_name_too_long).SendAsync(); await Response().Error(strs.club_name_too_long).SendAsync();
return; return;
case ClubRenameResult.Success: case ClubRenameResult.Success:
{ {
var embed = new EmbedBuilder().WithTitle(GetText(strs.club_renamed(clubName))).WithOkColor(); var embed = new EmbedBuilder().WithTitle(GetText(strs.club_renamed(clubName))).WithOkColor();
await Response().Embed(embed).SendAsync(); await Response().Embed(embed).SendAsync();
return; return;
} }
case ClubRenameResult.NameTaken: case ClubRenameResult.NameTaken:
await Response().Error(strs.club_name_taken).SendAsync(); await Response().Error(strs.club_name_taken).SendAsync();
return; return;

View File

@@ -96,7 +96,7 @@ public static class MessageChannelExtensions
embed: embed?.Build(), embed: embed?.Build(),
embeds: embeds?.Map(x => x.Build()), embeds: embeds?.Map(x => x.Build()),
replyTo: replyTo); replyTo: replyTo);
// embed title and optional footer overloads // embed title and optional footer overloads
public static Task SendPaginatedConfirmAsync( 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; namespace NadekoBot.Extensions;
public sealed class ResponseBuilder public sealed partial class ResponseBuilder
{ {
private ICommandContext? ctx = null; private ICommandContext? ctx = null;
private IMessageChannel? channel = null; private IMessageChannel? channel = null;
@@ -44,33 +44,53 @@ public sealed class ResponseBuilder
failIfNotExists: false); failIfNotExists: false);
} }
public async Task<IUserMessage> SendAsync(bool ephemeral = false) public ResponseMessageModel Build(bool ephemeral = false)
{ {
// todo use ephemeral in interactions // todo use ephemeral in interactions
var targetChannel = InternalResolveChannel() ?? throw new ArgumentNullException(nameof(channel)); var targetChannel = InternalResolveChannel() ?? throw new ArgumentNullException(nameof(channel));
var msgReference = CreateMessageReference(targetChannel); var msgReference = CreateMessageReference(targetChannel);
var txt = GetText(locTxt); var txt = GetText(locTxt);
// todo check message sanitization
if (sanitizeMentions) var buildModel = new ResponseMessageModel()
txt = txt?.SanitizeMentions(true); {
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) if (this.fileStream is Stream stream)
return await targetChannel.SendFileAsync(stream, return await model.TargetChannel.SendFileAsync(stream,
filename: fileName, filename: fileName,
txt, model.Text,
embed: embed ?? embedBuilder?.Build(), embed: model.Embed,
components: null, components: null,
allowedMentions: sanitizeMentions ? new(AllowedMentionTypes.Users) : AllowedMentions.All, allowedMentions: model.SanitizeMentions,
messageReference: msgReference); messageReference: model.MessageReference);
return await targetChannel.SendMessageAsync( return await model.TargetChannel.SendMessageAsync(
txt, model.Text,
embed: embed ?? embedBuilder?.Build(), embed: model.Embed,
embeds: embeds?.Map(x => x.Build()), embeds: model.Embeds,
components: null, components: null,
allowedMentions: sanitizeMentions ? new(AllowedMentionTypes.Users) : AllowedMentions.All, allowedMentions: model.SanitizeMentions,
messageReference: msgReference); messageReference: model.MessageReference);
} }
private ulong? InternalResolveGuildId(IMessageChannel? targetChannel) private ulong? InternalResolveGuildId(IMessageChannel? targetChannel)
@@ -263,4 +283,94 @@ public sealed class ResponseBuilder
this.fileName = fileName; this.fileName = fileName;
return this; 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; }
}