using System.Text; using NodaTime; using PluralKit.Core; namespace PluralKit.Bot; public class SystemFront { private readonly EmbedService _embeds; public SystemFront(EmbedService embeds) { _embeds = embeds; } public async Task SystemFronter(Context ctx, PKSystem system) { if (system == null) throw Errors.NoSystemError; ctx.CheckSystemPrivacy(system.Id, system.FrontPrivacy); var sw = await ctx.Repository.GetLatestSwitch(system.Id); if (sw == null) throw Errors.NoRegisteredSwitches; await ctx.Reply(embed: await _embeds.CreateFronterEmbed(sw, ctx.Zone, ctx.LookupContextFor(system.Id))); } public async Task SystemFrontHistory(Context ctx, PKSystem system) { if (system == null) throw Errors.NoSystemError; ctx.CheckSystemPrivacy(system.Id, system.FrontHistoryPrivacy); var totalSwitches = await ctx.Repository.GetSwitchCount(system.Id); if (totalSwitches == 0) throw Errors.NoRegisteredSwitches; var sws = ctx.Repository.GetSwitches(system.Id) .Scan(new FrontHistoryEntry(null, null), (lastEntry, newSwitch) => new FrontHistoryEntry(lastEntry.ThisSwitch?.Timestamp, newSwitch)); var embedTitle = system.Name != null ? $"Front history of {system.Name} (`{system.Hid}`)" : $"Front history of `{system.Hid}`"; var showMemberId = ctx.MatchFlag("with-id", "wid"); await ctx.Paginate( sws, totalSwitches, 10, embedTitle, system.Color, async (builder, switches) => { var sb = new StringBuilder(); foreach (var entry in switches) { var lastSw = entry.LastTime; var sw = entry.ThisSwitch; // Fetch member list and format var members = await ctx.Database.Execute(c => ctx.Repository.GetSwitchMembers(c, sw.Id)).ToListAsync(); var membersStr = members.Any() ? string.Join(", ", members.Select(m => $"**{m.NameFor(ctx)}**{(showMemberId ? $" (`{m.Hid}`)" : "")}")) : "**no fronter**"; var switchSince = SystemClock.Instance.GetCurrentInstant() - sw.Timestamp; // If this isn't the latest switch, we also show duration string stringToAdd; if (lastSw != null) { // Calculate the time between the last switch (that we iterated - ie. the next one on the timeline) and the current one var switchDuration = lastSw.Value - sw.Timestamp; stringToAdd = $"{membersStr} ({sw.Timestamp.FormatZoned(ctx.Zone)}, {switchSince.FormatDuration()} ago, for {switchDuration.FormatDuration()})\n"; } else { stringToAdd = $"{membersStr} ({sw.Timestamp.FormatZoned(ctx.Zone)}, {switchSince.FormatDuration()} ago)\n"; } if (sb.Length + stringToAdd.Length >= 4096) break; sb.Append(stringToAdd); } builder.Description(sb.ToString()); } ); } public async Task FrontPercent(Context ctx, PKSystem? system = null, PKGroup? group = null) { if (system == null && group == null) throw Errors.NoSystemError; if (system == null) system = await GetGroupSystem(ctx, group); ctx.CheckSystemPrivacy(system.Id, system.FrontHistoryPrivacy); var totalSwitches = await ctx.Repository.GetSwitchCount(system.Id); if (totalSwitches == 0) throw Errors.NoRegisteredSwitches; var ignoreNoFronters = ctx.MatchFlag("fo", "fronters-only"); var showFlat = ctx.MatchFlag("flat"); var durationStr = ctx.RemainderOrNull() ?? "30d"; // Picked the UNIX epoch as a random date // even though we don't store switch timestamps in UNIX time // I assume most people won't have switches logged previously to that (?) if (durationStr == "full") durationStr = "1970-01-01"; var now = SystemClock.Instance.GetCurrentInstant(); var rangeStart = DateUtils.ParseDateTime(durationStr, true, ctx.Zone); if (rangeStart == null) throw Errors.InvalidDateTime(durationStr); if (rangeStart.Value.ToInstant() > now) throw Errors.FrontPercentTimeInFuture; var title = new StringBuilder("Frontpercent of "); if (group != null) title.Append($"{group.NameFor(ctx)} (`{group.Hid}`)"); else if (system.Name != null) title.Append($"{system.Name} (`{system.Hid}`)"); else title.Append($"`{system.Hid}`"); var frontpercent = await ctx.Database.Execute(c => ctx.Repository.GetFrontBreakdown(c, system.Id, group?.Id, rangeStart.Value.ToInstant(), now)); await ctx.Reply(embed: await _embeds.CreateFrontPercentEmbed(frontpercent, system, group, ctx.Zone, ctx.LookupContextFor(system.Id), title.ToString(), ignoreNoFronters, showFlat)); } private async Task GetGroupSystem(Context ctx, PKGroup target) { var system = ctx.System; if (system?.Id == target.System) return system; return await ctx.Repository.GetSystem(target.System)!; } private struct FrontHistoryEntry { public readonly Instant? LastTime; public readonly PKSwitch ThisSwitch; public FrontHistoryEntry(Instant? lastTime, PKSwitch thisSwitch) { LastTime = lastTime; ThisSwitch = thisSwitch; } } }