From 7d60b3e7cf134a6661bd34dbe4c3590516f4ca03 Mon Sep 17 00:00:00 2001 From: BeautifulPixel <83858570+BeautifulPixel@users.noreply.github.com> Date: Sun, 26 Sep 2021 20:08:38 -0400 Subject: [PATCH] Add switch editing functionality --- PluralKit.Bot/Commands/CommandTree.cs | 11 +++- PluralKit.Bot/Commands/Switch.cs | 54 ++++++++++++++++++- PluralKit.Bot/Errors.cs | 1 + .../Repository/ModelRepository.Switch.cs | 27 ++++++++++ docs/content/command-list.md | 3 +- 5 files changed, 92 insertions(+), 4 deletions(-) diff --git a/PluralKit.Bot/Commands/CommandTree.cs b/PluralKit.Bot/Commands/CommandTree.cs index 79860097..d79676bb 100644 --- a/PluralKit.Bot/Commands/CommandTree.cs +++ b/PluralKit.Bot/Commands/CommandTree.cs @@ -72,6 +72,8 @@ namespace PluralKit.Bot public static Command Switch = new Command("switch", "switch [member 2] [member 3...]", "Registers a switch"); public static Command SwitchOut = new Command("switch out", "switch out", "Registers a switch with no members"); public static Command SwitchMove = new Command("switch move", "switch move ", "Moves the latest switch in time"); + public static Command SwitchEdit = new Command("switch edit", "switch edit [member 2] [member 3...]", "Edits the members in the latest switch"); + public static Command SwitchEditOut = new Command("switch edit out", "switch edit out", "Turns the latest switch into a switch-out"); public static Command SwitchDelete = new Command("switch delete", "switch delete", "Deletes the latest switch"); public static Command SwitchDeleteAll = new Command("switch delete", "switch delete all", "Deletes all logged switches"); public static Command Link = new Command("link", "link ", "Links your system to another account"); @@ -120,7 +122,7 @@ namespace PluralKit.Bot GroupDelete, GroupMemberRandom, GroupFrontPercent }; - public static Command[] SwitchCommands = { Switch, SwitchOut, SwitchMove, SwitchDelete, SwitchDeleteAll }; + public static Command[] SwitchCommands = { Switch, SwitchOut, SwitchMove, SwitchEdit, SwitchEditOut, SwitchDelete, SwitchDeleteAll }; public static Command[] AutoproxyCommands = { AutoproxySet, AutoproxyTimeout, AutoproxyAccount }; @@ -482,6 +484,11 @@ namespace PluralKit.Bot await ctx.Execute(SwitchOut, m => m.SwitchOut(ctx)); else if (ctx.Match("move", "shift", "offset")) await ctx.Execute(SwitchMove, m => m.SwitchMove(ctx)); + else if (ctx.Match("edit", "replace")) + if (ctx.Match("out")) + await ctx.Execute(SwitchEditOut, m => m.SwitchEditOut(ctx)); + else + await ctx.Execute(SwitchEdit, m => m.SwitchEdit(ctx)); else if (ctx.Match("delete", "remove", "erase", "cancel", "yeet")) await ctx.Execute(SwitchDelete, m => m.SwitchDelete(ctx)); else if (ctx.Match("commands", "help")) @@ -489,7 +496,7 @@ namespace PluralKit.Bot else if (ctx.HasNext()) // there are following arguments await ctx.Execute(Switch, m => m.SwitchDo(ctx)); else - await PrintCommandNotFoundError(ctx, Switch, SwitchOut, SwitchMove, SwitchDelete, SystemFronter, SystemFrontHistory); + await PrintCommandNotFoundError(ctx, Switch, SwitchOut, SwitchMove, SwitchEdit, SwitchEditOut, SwitchDelete, SystemFronter, SystemFrontHistory); } private async Task CommandHelpRoot(Context ctx) diff --git a/PluralKit.Bot/Commands/Switch.cs b/PluralKit.Bot/Commands/Switch.cs index fe6673e0..8df4ceb7 100644 --- a/PluralKit.Bot/Commands/Switch.cs +++ b/PluralKit.Bot/Commands/Switch.cs @@ -83,7 +83,7 @@ namespace PluralKit.Bot // If we don't have a switch to move, don't bother if (lastTwoSwitches.Count == 0) throw Errors.NoRegisteredSwitches; - // If there's a switch *behind* the one we move, we check to make srue we're not moving the time further back than that + // If there's a switch *behind* the one we move, we check to make sure we're not moving the time further back than that if (lastTwoSwitches.Count == 2) { if (lastTwoSwitches[1].Timestamp > time.ToInstant()) @@ -108,6 +108,58 @@ namespace PluralKit.Bot await ctx.Reply($"{Emojis.Success} Switch moved to ({newSwitchDeltaStr} ago)."); } + public async Task SwitchEdit(Context ctx) + { + ctx.CheckSystem(); + + var members = await ctx.ParseMemberList(ctx.System.Id); + await DoEditCommand(ctx, members); + } + + public async Task SwitchEditOut(Context ctx) + { + ctx.CheckSystem(); + await DoEditCommand(ctx, new PKMember[] { }); + + } + public async Task DoEditCommand(Context ctx, ICollection members) + { + // Make sure there are no dupes in the list + // We do this by checking if removing duplicate member IDs results in a list of different length + if (members.Select(m => m.Id).Distinct().Count() != members.Count) throw Errors.DuplicateSwitchMembers; + + // Find the switch to edit + await using var conn = await _db.Obtain(); + var lastSwitch = await _repo.GetLatestSwitch(conn, ctx.System.Id); + // Make sure there's at least one switch + if (lastSwitch == null) throw Errors.NoRegisteredSwitches; + var lastSwitchMembers = _repo.GetSwitchMembers(conn, lastSwitch.Id); + // Make sure switch isn't being edited to have the members it already does + if (await lastSwitchMembers.Select(m => m.Id).SequenceEqualAsync(members.Select(m => m.Id).ToAsyncEnumerable())) + throw Errors.SameSwitch(members, ctx.LookupContextFor(ctx.System)); + + // Send a prompt asking the user to confirm the switch + var lastSwitchDeltaStr = (SystemClock.Instance.GetCurrentInstant() - lastSwitch.Timestamp).FormatDuration(); + var lastSwitchMemberStr = string.Join(", ", await lastSwitchMembers.Select(m => m.NameFor(ctx)).ToListAsync()); + var newSwitchMemberStr = string.Join(", ", members.Select(m => m.NameFor(ctx))); + + string msg; + if (members.Count == 0) + msg = $"{Emojis.Warn} This will turn the latest switch ({lastSwitchMemberStr}, {lastSwitchDeltaStr} ago) into a switch-out. Is this okay?"; + else + msg = $"{Emojis.Warn} This will change the latest switch ({lastSwitchMemberStr}, {lastSwitchDeltaStr} ago) to {newSwitchMemberStr}. Is this okay?"; + if (!await ctx.PromptYesNo(msg, "Edit")) throw Errors.SwitchEditCancelled; + + // Actually edit the switch + await _repo.EditSwitch(conn, lastSwitch.Id, members.Select(m => m.Id).ToList()); + + // Tell the user the edit suceeded + if (members.Count == 0) + await ctx.Reply($"{Emojis.Success} Switch edited. The latest switch is now a switch-out."); + else + await ctx.Reply($"{Emojis.Success} Switch edited. Current fronter is now {newSwitchMemberStr}."); + } + public async Task SwitchDelete(Context ctx) { ctx.CheckSystem(); diff --git a/PluralKit.Bot/Errors.cs b/PluralKit.Bot/Errors.cs index 59cf93d8..bfd29442 100644 --- a/PluralKit.Bot/Errors.cs +++ b/PluralKit.Bot/Errors.cs @@ -83,6 +83,7 @@ namespace PluralKit.Bot public static PKError SwitchMoveBeforeSecondLast(ZonedDateTime time) => new PKError($"Can't move switch to before last switch time ({time.FormatZoned()}), as it would cause conflicts."); public static PKError SwitchMoveCancelled => new PKError("Switch move cancelled."); + public static PKError SwitchEditCancelled => new PKError("Switch edit cancelled."); public static PKError SwitchDeleteCancelled => new PKError("Switch deletion cancelled."); public static PKError TimezoneParseError(string timezone) => new PKError($"Could not parse timezone offset {timezone}. Offset must be a value like 'UTC+5' or 'GMT-4:30'."); diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs b/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs index 5665be4c..03fa1bc2 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs @@ -39,6 +39,33 @@ namespace PluralKit.Core _logger.Information("Created {SwitchId} in {SystemId}: {Members}", sw.Id, system, members); } + public async Task EditSwitch(IPKConnection conn, SwitchId switchId, IReadOnlyCollection members) + { + // Use a transaction here since we're doing multiple executed commands in one + await using var tx = await conn.BeginTransactionAsync(); + + // Remove the old members from the switch + await conn.ExecuteAsync("delete from switch_members where switch = @Switch", + new {Switch = switchId}); + + // Add the new members + await using (var w = conn.BeginBinaryImport("copy switch_members (switch, member) from stdin (format binary)")) + { + foreach (var member in members) + { + await w.StartRowAsync(); + await w.WriteAsync(switchId.Value, NpgsqlDbType.Integer); + await w.WriteAsync(member.Value, NpgsqlDbType.Integer); + } + + await w.CompleteAsync(); + } + + // Finally we commit the tx, since the using block will otherwise rollback it + await tx.CommitAsync(); + + _logger.Information("Updated {SwitchId} members: {Members}", switchId, members); + } public async Task MoveSwitch(IPKConnection conn, SwitchId id, Instant time) { diff --git a/docs/content/command-list.md b/docs/content/command-list.md index 1ab8dcb3..621510ee 100644 --- a/docs/content/command-list.md +++ b/docs/content/command-list.md @@ -99,10 +99,11 @@ Some arguments indicate the use of specific Discord features. These include: ## Switching commands - `pk;switch [member...]` - Registers a switch with the given members. +- `pk;switch out` - Registers a 'switch-out' - a switch with no associated members. +- `pk;switch edit ` - Edits the members in the latest switch. - `pk;switch move