public static async Task RenderMemberList(this Context ctx, LookupContext lookupCtx, SystemId system, string embedTitle, string color, ListOptions opts) { // We take an IDatabase instead of a IPKConnection so we don't keep the handle open for the entire runtime // We wanna release it as soon as the member list is actually *fetched*, instead of potentially minutes later (paginate timeout) var members = (await ctx.Database.Execute(conn => conn.QueryMemberList(system, opts.ToQueryOptions()))) .SortByMemberListOptions(opts, lookupCtx) .ToList(); var itemsPerPage = opts.Type == ListType.Short ? 25 : 5; await ctx.Paginate(members.ToAsyncEnumerable(), members.Count, itemsPerPage, embedTitle, color, Renderer); // Base renderer, dispatches based on type Task Renderer(EmbedBuilder eb, IEnumerable <ListedMember> page) { // Add a global footer with the filter/sort string + result count eb.Footer(new Embed.EmbedFooter($"{opts.CreateFilterString()}. {"result".ToQuantity(members.Count)}.")); // Then call the specific renderers if (opts.Type == ListType.Short) { ShortRenderer(eb, page); } else { LongRenderer(eb, page); } return(Task.CompletedTask); } void ShortRenderer(EmbedBuilder eb, IEnumerable <ListedMember> page) { // We may end up over the description character limit // so run it through a helper that "makes it work" :) eb.WithSimpleLineContent(page.Select(m => { var ret = $"[`{m.Hid}`] **{m.NameFor(ctx)}** "; switch (opts.SortProperty) { case SortProperty.Birthdate: { var birthday = m.BirthdayFor(lookupCtx); if (birthday != null) { ret += $"(birthday: {m.BirthdayString})"; } break; } case SortProperty.DisplayName: { if (m.DisplayName != null && m.NamePrivacy.CanAccess(lookupCtx)) { ret += $"({m.DisplayName})"; } break; } case SortProperty.MessageCount: { if (m.MessageCountFor(lookupCtx) is { } count) { ret += $"({count} messages)"; } break; } case SortProperty.LastSwitch: { if (m.MetadataPrivacy.TryGet(lookupCtx, m.LastSwitchTime, out var lastSw)) { ret += $"(last switched in: <t:{lastSw.Value.ToUnixTimeSeconds()}>)"; } break; } // case SortProperty.LastMessage: // { // if (m.MetadataPrivacy.TryGet(lookupCtx, m.LastMessage, out var lastMsg)) // ret += $"(last message: <t:{DiscordUtils.SnowflakeToInstant(lastMsg.Value).ToUnixTimeSeconds()}>)"; // break; // } case SortProperty.CreationDate: { if (m.MetadataPrivacy.TryGet(lookupCtx, m.Created, out var created)) { ret += $"(created at <t:{created.ToUnixTimeSeconds()}>)"; } break; } default: { if (opts.IncludeMessageCount && m.MessageCountFor(lookupCtx) is { } count) { ret += $"({count} messages)"; }