From c8f2a137a0b98d44717f997a35f622314fb9afee Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 9 Sep 2023 22:58:54 -0700 Subject: [PATCH] feat(bot): add regex message editing (#587) --- PluralKit.Bot/Commands/Message.cs | 91 ++++++++++++++++++++++++++++++- docs/content/tips-and-tricks.md | 7 ++- 2 files changed, 93 insertions(+), 5 deletions(-) diff --git a/PluralKit.Bot/Commands/Message.cs b/PluralKit.Bot/Commands/Message.cs index 9d7924d3..6aac61eb 100644 --- a/PluralKit.Bot/Commands/Message.cs +++ b/PluralKit.Bot/Commands/Message.cs @@ -1,4 +1,5 @@ #nullable enable +using System; using System.Text; using System.Text.RegularExpressions; @@ -101,6 +102,9 @@ public class ProxiedMessage if (originalMsg == null) throw new PKError("Could not edit message."); + // Regex flag + var useRegex = ctx.MatchFlag("regex", "x"); + // Check if we should append or prepend var mutateSpace = ctx.MatchFlag("nospace", "ns") ? "" : " "; var append = ctx.MatchFlag("append", "a"); @@ -118,12 +122,93 @@ public class ProxiedMessage if (newContent == null) throw new PKSyntaxError("You need to include the message to edit in."); + // Can't append or prepend a Regex + if (useRegex && (append || prepend)) + throw new PKError("You can't use the append or prepend options with a Regex."); + + // Use the Regex to substitute the message content + if (useRegex) + { + const string regexErrorStr = "Could not parse Regex. The expected formats are s|X|Y or s|X|Y|F, where | is any character, X is a valid Regex to search for matches of, Y is a substitution string, and F is a set of Regex flags."; + + // Smallest valid Regex string is "s||"; 3 chars long + if (newContent.Length < 3 || !newContent.StartsWith('s')) + throw new PKError(regexErrorStr); + + var separator = newContent[1]; + + // s|X|Y => ["s", "X", "Y"] + // s|X|Y|F => ["s", "X", "Y", "F"] ("F" may be empty) + var splitString = newContent.Split(separator); + + if (splitString.Length != 3 && splitString.Length != 4) + throw new PKError(regexErrorStr); + + var flags = splitString.Length == 4 ? splitString[3] : ""; + + var regexOptions = RegexOptions.None; + var globalMatch = false; + + // Parse flags + foreach (char c in flags) + { + switch (c) + { + case 'g': + globalMatch = true; + break; + + case 'i': + regexOptions |= RegexOptions.IgnoreCase; + break; + + case 'm': + regexOptions |= RegexOptions.Multiline; + break; + + case 'n': + regexOptions |= RegexOptions.ExplicitCapture; + break; + + case 's': + regexOptions |= RegexOptions.Singleline; + break; + + case 'x': + regexOptions |= RegexOptions.IgnorePatternWhitespace; + break; + + default: + throw new PKError($"Invalid Regex flag '{c}'. Valid flags include 'g', 'i', 'm', 'n', 's', and 'x'."); + } + } + + try + { + // I would use RegexOptions.NonBacktracking but that's only .NET 7 :( + var regex = new Regex(splitString[1], regexOptions, TimeSpan.FromSeconds(0.5)); + var numMatches = globalMatch ? -1 : 1; // Negative means all matches + newContent = regex.Replace(originalContent!, splitString[2], numMatches); + } + catch (ArgumentException) + { + throw new PKError(regexErrorStr); + } + catch (RegexMatchTimeoutException) + { + throw new PKError("Regex took too long to run."); + } + } + // Append or prepend the new content to the original message content if needed. // If no flag is supplied, the new contents will completly overwrite the old contents // If both flags are specified. the message will be prepended AND appended - if (append && prepend) newContent = $"{newContent}{mutateSpace}{originalContent}{mutateSpace}{newContent}"; - else if (append) newContent = $"{originalContent}{mutateSpace}{newContent}"; - else if (prepend) newContent = $"{newContent}{mutateSpace}{originalContent}"; + if (append && prepend) + newContent = $"{newContent}{mutateSpace}{originalContent}{mutateSpace}{newContent}"; + else if (append) + newContent = $"{originalContent}{mutateSpace}{newContent}"; + else if (prepend) + newContent = $"{newContent}{mutateSpace}{originalContent}"; if (newContent.Length > 2000) throw new PKError("PluralKit cannot proxy messages over 2000 characters in length."); diff --git a/docs/content/tips-and-tricks.md b/docs/content/tips-and-tricks.md index 259ddd40..df7a09d4 100644 --- a/docs/content/tips-and-tricks.md +++ b/docs/content/tips-and-tricks.md @@ -75,8 +75,11 @@ You cannot look up private members or groups of another system. |pk;system frontpercent|-flat||Show "flat" frontpercent - percentages add up to 100%| |pk;group \ frontpercent|-fronters-only|-fo|Show a group's frontpercent without the "no fronter" entry| |pk;group \ frontpercent|-flat||Show "flat" frontpercent - percentages add up to 100%| -|pk;edit|-append||Append the new content to the old message instead of overwriting it| -|pk;edit|-prepend||Prepend the new content to the old message instead of overwriting it| +|pk;edit|-append|-a|Append the new content to the old message instead of overwriting it| +|pk;edit|-prepend|-p|Prepend the new content to the old message instead of overwriting it| +|pk;edit|-nospace|-ns|Append/prepend without adding a space| +|pk;edit|-clear-embed|-ce|Remove embeds from a message| +|pk;edit|-regex|-x|Edit using a C# Regex formatted like s|X|Y or s|X|Y|F, where | is any character, X is a Regex, Y is a substitution string, and F is a set of Regex flags| |Most commands|-all|-a|Show hidden/private information| |Most commands|-raw|-r|Show text with formatting, for easier copy-pasting| |All commands|-private|-priv|Show private information|