diff --git a/PluralKit.Bot/Commands/CommandTree.cs b/PluralKit.Bot/Commands/CommandTree.cs index 6363c5e7..ad763670 100644 --- a/PluralKit.Bot/Commands/CommandTree.cs +++ b/PluralKit.Bot/Commands/CommandTree.cs @@ -60,6 +60,7 @@ namespace PluralKit.Bot public static Command GroupPrivacy = new Command("group privacy", "group privacy ", "Changes a group's privacy settings"); public static Command GroupIcon = new Command("group icon", "group icon [url|@mention]", "Changes a group's icon"); public static Command GroupDelete = new Command("group delete", "group delete", "Deletes a group"); + public static Command GroupFrontPercent = new Command("group frontpercent", "group frontpercent [timespan]", "Shows a group's front breakdown."); public static Command GroupMemberRandom = new Command("group random", "group random", "Shows the info card of a randomly selected member in a group."); public static Command GroupRandom = new Command("random", "random group", "Shows the info card of a randomly selected group in your system."); public static Command Switch = new Command("switch", "switch [member 2] [member 3...]", "Registers a switch"); @@ -107,7 +108,7 @@ namespace PluralKit.Bot public static Command[] GroupCommandsTargeted = { GroupInfo, GroupAdd, GroupRemove, GroupMemberList, GroupRename, GroupDesc, GroupIcon, GroupPrivacy, - GroupDelete, GroupMemberRandom + GroupDelete, GroupMemberRandom, GroupFrontPercent }; public static Command[] SwitchCommands = {Switch, SwitchOut, SwitchMove, SwitchDelete, SwitchDeleteAll}; @@ -397,6 +398,8 @@ namespace PluralKit.Bot await ctx.Execute(GroupDelete, g => g.DeleteGroup(ctx, target)); else if (ctx.Match("avatar", "picture", "icon", "image", "pic", "pfp")) await ctx.Execute(GroupIcon, g => g.GroupIcon(ctx, target)); + else if (ctx.Match("fp", "frontpercent", "front%", "frontbreakdown")) + await ctx.Execute(GroupFrontPercent, g => g.GroupFrontPercent(ctx, target)); else if (!ctx.HasNext()) await ctx.Execute(GroupInfo, g => g.ShowGroupCard(ctx, target)); else diff --git a/PluralKit.Bot/Commands/Groups.cs b/PluralKit.Bot/Commands/Groups.cs index ce93f8fe..4259dff8 100644 --- a/PluralKit.Bot/Commands/Groups.cs +++ b/PluralKit.Bot/Commands/Groups.cs @@ -8,6 +8,8 @@ using Dapper; using Humanizer; +using NodaTime; + using Myriad.Builders; using PluralKit.Core; @@ -428,6 +430,31 @@ namespace PluralKit.Bot await ctx.Reply($"{Emojis.Success} Group deleted."); } + public async Task GroupFrontPercent(Context ctx, PKGroup target) + { + await using var conn = await _db.Obtain(); + + var targetSystem = await GetGroupSystem(ctx, target, conn); + ctx.CheckSystemPrivacy(targetSystem, targetSystem.FrontHistoryPrivacy); + + string durationStr = ctx.RemainderOrNull() ?? "30d"; + + var now = SystemClock.Instance.GetCurrentInstant(); + + var rangeStart = DateUtils.ParseDateTime(durationStr, true, targetSystem.Zone); + if (rangeStart == null) throw Errors.InvalidDateTime(durationStr); + if (rangeStart.Value.ToInstant() > now) throw Errors.FrontPercentTimeInFuture; + + var title = new StringBuilder($"Frontpercent of {target.DisplayName ?? target.Name} (`{target.Hid}`) in "); + if (targetSystem.Name != null) + title.Append($"{targetSystem.Name} (`{targetSystem.Hid}`)"); + else + title.Append($"`{targetSystem.Hid}`"); + + var frontpercent = await _db.Execute(c => _repo.GetFrontBreakdown(c, targetSystem.Id, target.Id, rangeStart.Value.ToInstant(), now)); + await ctx.Reply(embed: await _embeds.CreateFrontPercentEmbed(frontpercent, targetSystem, target, targetSystem.Zone, ctx.LookupContextFor(targetSystem), title.ToString())); + } + private async Task GetGroupSystem(Context ctx, PKGroup target, IPKConnection conn) { var system = ctx.System; diff --git a/PluralKit.Bot/Commands/SystemFront.cs b/PluralKit.Bot/Commands/SystemFront.cs index 3f8cbc0b..d9531216 100644 --- a/PluralKit.Bot/Commands/SystemFront.cs +++ b/PluralKit.Bot/Commands/SystemFront.cs @@ -124,8 +124,14 @@ namespace PluralKit.Bot if (rangeStart == null) throw Errors.InvalidDateTime(durationStr); if (rangeStart.Value.ToInstant() > now) throw Errors.FrontPercentTimeInFuture; - var frontpercent = await _db.Execute(c => _repo.GetFrontBreakdown(c, system.Id, rangeStart.Value.ToInstant(), now)); - await ctx.Reply(embed: await _embeds.CreateFrontPercentEmbed(frontpercent, system.Zone, ctx.LookupContextFor(system))); + var title = new StringBuilder($"Frontpercent of "); + if (system.Name != null) + title.Append($"{system.Name} (`{system.Hid}`)"); + else + title.Append($"`{system.Hid}`"); + + var frontpercent = await _db.Execute(c => _repo.GetFrontBreakdown(c, system.Id, null, rangeStart.Value.ToInstant(), now)); + await ctx.Reply(embed: await _embeds.CreateFrontPercentEmbed(frontpercent, system, null, system.Zone, ctx.LookupContextFor(system), title.ToString())); } } } \ No newline at end of file diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index fd0a971a..5528f1cf 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -293,12 +293,13 @@ namespace PluralKit.Bot { return eb.Build(); } - public Task CreateFrontPercentEmbed(FrontBreakdown breakdown, DateTimeZone tz, LookupContext ctx) + public Task CreateFrontPercentEmbed(FrontBreakdown breakdown, PKSystem system, PKGroup group, DateTimeZone tz, LookupContext ctx, string embedTitle) { var actualPeriod = breakdown.RangeEnd - breakdown.RangeStart; var eb = new EmbedBuilder() + .Title(embedTitle) .Color(DiscordUtils.Gray) - .Footer(new($"Since {breakdown.RangeStart.FormatZoned(tz)} ({actualPeriod.FormatDuration()} ago)")); + .Footer(new($"System ID: {system.Hid} | {(group != null ? $"Group ID: {group.Hid} | " : "") }Since {breakdown.RangeStart.FormatZoned(tz)} ({actualPeriod.FormatDuration()} ago)")); var maxEntriesToDisplay = 24; // max 25 fields allowed in embed - reserve 1 for "others" diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs b/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs index f313f00a..d8633acf 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs @@ -122,7 +122,7 @@ namespace PluralKit.Core await GetSwitches(conn, system).FirstOrDefaultAsync(); public async Task> GetPeriodFronters(IPKConnection conn, - SystemId system, Instant periodStart, + SystemId system, GroupId? group, Instant periodStart, Instant periodEnd) { // TODO: IAsyncEnumerable-ify this one @@ -139,7 +139,14 @@ namespace PluralKit.Core new {Switches = switchMembers.Select(m => m.Member.Value).Distinct().ToList()}); var memberObjects = membersList.ToDictionary(m => m.Id); + // check if a group ID is provided. if so, query DB for all members of said group, otherwise use membersList + var groupMembersList = group != null ? await conn.QueryAsync( + "select * from members inner join group_members on members.id = group_members.member_id where group_id = @id", + new {id = group}) : membersList; + var groupMemberObjects = groupMembersList.ToDictionary(m => m.Id); + // Initialize entries - still need to loop to determine the TimespanEnd below + // use groupMemberObjects to make sure no members outside of the specified group (if present) are selected var entries = from item in switchMembers group item by item.Timestamp @@ -147,7 +154,7 @@ namespace PluralKit.Core select new SwitchListEntry { TimespanStart = g.Key, - Members = g.Where(x => x.Member != default(MemberId)).Select(x => memberObjects[x.Member]) + Members = g.Where(x => x.Member != default(MemberId) && groupMemberObjects.Any(m => x.Member == m.Key) ).Select(x => memberObjects[x.Member]) .ToList() }; @@ -174,7 +181,7 @@ namespace PluralKit.Core return outList; } - public async Task GetFrontBreakdown(IPKConnection conn, SystemId system, Instant periodStart, + public async Task GetFrontBreakdown(IPKConnection conn, SystemId system, GroupId? group, Instant periodStart, Instant periodEnd) { // TODO: this doesn't belong in the repo @@ -188,7 +195,7 @@ namespace PluralKit.Core var actualStart = periodEnd; // will be "pulled" down var actualEnd = periodStart; // will be "pulled" up - foreach (var sw in await GetPeriodFronters(conn, system, periodStart, periodEnd)) + foreach (var sw in await GetPeriodFronters(conn, system, group, periodStart, periodEnd)) { var span = sw.TimespanEnd - sw.TimespanStart; foreach (var member in sw.Members) diff --git a/PluralKit.Core/Services/DataFileService.cs b/PluralKit.Core/Services/DataFileService.cs index 05dd3b37..cc259d3b 100644 --- a/PluralKit.Core/Services/DataFileService.cs +++ b/PluralKit.Core/Services/DataFileService.cs @@ -51,7 +51,7 @@ namespace PluralKit.Core // Export switches var switches = new List(); - var switchList = await _repo.GetPeriodFronters(conn, system.Id, Instant.FromDateTimeUtc(DateTime.MinValue.ToUniversalTime()), SystemClock.Instance.GetCurrentInstant()); + var switchList = await _repo.GetPeriodFronters(conn, system.Id, null, Instant.FromDateTimeUtc(DateTime.MinValue.ToUniversalTime()), SystemClock.Instance.GetCurrentInstant()); switches.AddRange(switchList.Select(x => new DataFileSwitch { Timestamp = x.TimespanStart.FormatExport(),