feat(bot): Case insensitive proxy tags matching (#490)

This commit is contained in:
Katrix 2022-11-23 09:48:24 +01:00 committed by GitHub
parent 09ac002d26
commit 4f0236d766
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 89 additions and 19 deletions

View File

@ -511,6 +511,8 @@ public partial class CommandTree
return ctx.Execute<Config>(null, m => m.GroupDefaultPrivacy(ctx));
if (ctx.MatchMultiple(new[] { "show" }, new[] { "private" }) || ctx.Match("sp"))
return ctx.Execute<Config>(null, m => m.ShowPrivateInfo(ctx));
if (ctx.MatchMultiple(new[] { "proxy" }, new[] { "case" }))
return ctx.Execute<Config>(null, m => m.CaseSensitiveProxyTags(ctx));
// todo: maybe add the list of configuration keys here?
return ctx.Reply($"{Emojis.Error} Could not find a setting with that name. Please see `pk;commands config` for the list of possible config settings.");

View File

@ -243,7 +243,7 @@ public class Checks
try
{
_proxy.ShouldProxy(channel, msg, context);
_matcher.TryMatch(context, autoproxySettings, members, out var match, msg.Content, msg.Attachments.Length > 0, true);
_matcher.TryMatch(context, autoproxySettings, members, out var match, msg.Content, msg.Attachments.Length > 0, true, ctx.Config.CaseSensitiveProxyTags);
await ctx.Reply("I'm not sure why this message was not proxied, sorry.");
}

View File

@ -88,6 +88,13 @@ public class Config
Limits.MaxGroupCount.ToString()
));
items.Add(new(
"Case sensitive proxy tags",
"If proxy tags should be case sensitive",
EnabledDisabled(ctx.Config.CaseSensitiveProxyTags),
"enabled"
));
await ctx.Paginate<PaginatedConfigItem>(
items.ToAsyncEnumerable(),
items.Count,
@ -383,4 +390,27 @@ public class Config
await ctx.Reply("Private information will now be **hidden** when looking up your own info. Use the `-private` flag to show it.");
}
}
public async Task CaseSensitiveProxyTags(Context ctx)
{
if (!ctx.HasNext())
{
if (ctx.Config.CaseSensitiveProxyTags) { await ctx.Reply("Proxy tags are currently case sensitive"); }
else { await ctx.Reply("Proxy tags are currently case insensitive"); }
return;
}
if (ctx.MatchToggle(true))
{
await ctx.Repository.UpdateSystemConfig(ctx.System.Id, new() { CaseSensitiveProxyTags = true });
await ctx.Reply("Proxy tags are now case sensitive");
}
else
{
await ctx.Repository.UpdateSystemConfig(ctx.System.Id, new() { CaseSensitiveProxyTags = false });
await ctx.Reply("Proxy tags are now case insensitive");
}
}
}

View File

@ -20,17 +20,17 @@ public class ProxyMatcher
public bool TryMatch(MessageContext ctx, AutoproxySettings settings, IReadOnlyCollection<ProxyMember> members, out ProxyMatch match,
string messageContent,
bool hasAttachments, bool allowAutoproxy)
bool hasAttachments, bool allowAutoproxy, bool caseSensitive)
{
if (TryMatchTags(members, messageContent, hasAttachments, out match)) return true;
if (TryMatchTags(members, messageContent, hasAttachments, caseSensitive, out match)) return true;
if (allowAutoproxy && TryMatchAutoproxy(ctx, settings, members, messageContent, out match)) return true;
return false;
}
private bool TryMatchTags(IReadOnlyCollection<ProxyMember> members, string messageContent, bool hasAttachments,
out ProxyMatch match)
bool caseSensitive, out ProxyMatch match)
{
if (!_parser.TryMatch(members, messageContent, out match)) return false;
if (!_parser.TryMatch(members, messageContent, caseSensitive, out match)) return false;
// Edge case: If we got a match with blank inner text, we'd normally just send w/ attachments
// However, if there are no attachments, the user probably intended something else, so we "un-match" and proceed to autoproxy

View File

@ -78,7 +78,7 @@ public class ProxyService
members = (await _repo.GetProxyMembers(message.Author.Id, message.GuildId!.Value)).ToList();
if (!_matcher.TryMatch(ctx, autoproxySettings, members, out var match, message.Content, message.Attachments.Length > 0,
allowAutoproxy)) return false;
allowAutoproxy, ctx.CaseSensitiveProxyTags)) return false;
// this is hopefully temporary, so not putting it into a separate method
if (message.Content != null && message.Content.Length > 2000)
@ -208,8 +208,9 @@ public class ProxyService
"Proxying was disabled in this channel by a server administrator (via the proxy blacklist).");
var autoproxySettings = await _repo.GetAutoproxySettings(ctx.SystemId.Value, msg.Guild!.Value, null);
var config = await _repo.GetSystemConfig(ctx.SystemId.Value);
var prevMatched = _matcher.TryMatch(ctx, autoproxySettings, members, out var prevMatch, originalMsg.Content,
originalMsg.Attachments.Length > 0, false);
originalMsg.Attachments.Length > 0, false, ctx.CaseSensitiveProxyTags);
var match = new ProxyMatch
{

View File

@ -10,7 +10,7 @@ public class ProxyTagParser
private readonly Regex prefixPattern = new(@"^<(?:@!?|#|@&|a?:[\d\w_]+?:)\d+>");
private readonly Regex suffixPattern = new(@"<(?:@!?|#|@&|a?:[\d\w_]+?:)\d+>$");
public bool TryMatch(IEnumerable<ProxyMember> members, string? input, out ProxyMatch result)
public bool TryMatch(IEnumerable<ProxyMember> members, string? input, bool caseSensitive, out ProxyMatch result)
{
result = default;
@ -42,7 +42,7 @@ public class ProxyTagParser
if (tag.Suffix == ">" && suffixPattern.IsMatch(input)) continue;
// Can we match with these tags?
if (TryMatchTagsInner(input, tag, out result.Content))
if (TryMatchTagsInner(input, tag, caseSensitive, out result.Content))
{
// If we extracted a leading mention before, add that back now
if (leadingMention != null) result.Content = $"{leadingMention} {result.Content}";
@ -56,7 +56,7 @@ public class ProxyTagParser
return false;
}
private bool TryMatchTagsInner(string input, ProxyTag tag, out string inner)
private bool TryMatchTagsInner(string input, ProxyTag tag, bool caseSensitive, out string inner)
{
inner = "";
@ -64,9 +64,14 @@ public class ProxyTagParser
var prefix = tag.Prefix ?? "";
var suffix = tag.Suffix ?? "";
var comparision = caseSensitive
? StringComparison.CurrentCulture
: StringComparison.CurrentCultureIgnoreCase;
// Check if our input starts/ends with the tags
var isMatch = input.Length >= prefix.Length + suffix.Length
&& input.StartsWith(prefix) && input.EndsWith(suffix);
&& input.StartsWith(prefix, comparision)
&& input.EndsWith(suffix, comparision);
// Special case: image-only proxies + proxy tags with spaces
// Trim everything, then see if we have a "contentless tag pair" (normally disallowed, but OK if we have an attachment)

View File

@ -28,4 +28,5 @@ public class MessageContext
public string? SystemAvatar { get; }
public bool AllowAutoproxy { get; }
public int? LatchTimeout { get; }
public bool CaseSensitiveProxyTags { get; }
}

View File

@ -14,12 +14,14 @@
tag_enabled bool,
system_avatar text,
allow_autoproxy bool,
latch_timeout integer
latch_timeout integer,
case_sensitive_proxy_tags bool
)
as $$
-- CTEs to query "static" (accessible only through args) data
with
system as (select systems.*, system_config.latch_timeout, system_guild.tag as guild_tag, system_guild.tag_enabled as tag_enabled, allow_autoproxy as account_autoproxy from accounts
system as (select systems.*, system_config.latch_timeout, system_guild.tag as guild_tag, system_guild.tag_enabled as tag_enabled,
allow_autoproxy as account_autoproxy, system_config.case_sensitive_proxy_tags from accounts
left join systems on systems.id = accounts.system
left join system_config on system_config.system = accounts.system
left join system_guild on system_guild.system = accounts.system and system_guild.guild = guild_id
@ -40,7 +42,8 @@ as $$
coalesce(system.tag_enabled, true) as tag_enabled,
system.avatar_url as system_avatar,
system.account_autoproxy as allow_autoproxy,
system.latch_timeout as latch_timeout
system.latch_timeout as latch_timeout,
system.case_sensitive_proxy_tags as case_sensitive_proxy_tags
-- We need a "from" clause, so we just use some bogus data that's always present
-- This ensure we always have exactly one row going forward, so we can left join afterwards and still get data
from (select 1) as _placeholder

View File

@ -0,0 +1,5 @@
-- schema version 31
alter table system_config add column case_sensitive_proxy_tags boolean not null default true;
update info set schema_version = 31;

View File

@ -9,7 +9,7 @@ namespace PluralKit.Core;
internal class DatabaseMigrator
{
private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files
private const int TargetSchemaVersion = 30;
private const int TargetSchemaVersion = 31;
private readonly ILogger _logger;
public DatabaseMigrator(ILogger logger)

View File

@ -17,6 +17,7 @@ public class SystemConfigPatch: PatchObject
public Partial<int?> MemberLimitOverride { get; set; }
public Partial<int?> GroupLimitOverride { get; set; }
public Partial<string[]> DescriptionTemplates { get; set; }
public Partial<bool> CaseSensitiveProxyTags { get; set; }
public override Query Apply(Query q) => q.ApplyPatch(wrapper => wrapper
@ -29,6 +30,7 @@ public class SystemConfigPatch: PatchObject
.With("member_limit_override", MemberLimitOverride)
.With("group_limit_override", GroupLimitOverride)
.With("description_templates", DescriptionTemplates)
.With("case_sensitive_proxy_tags", CaseSensitiveProxyTags)
);
public new void AssertIsValid()
@ -78,6 +80,9 @@ public class SystemConfigPatch: PatchObject
if (DescriptionTemplates.IsPresent)
o.Add("description_templates", JArray.FromObject(DescriptionTemplates.Value));
if (CaseSensitiveProxyTags.IsPresent)
o.Add("case_sensitive_proxy_tags", CaseSensitiveProxyTags.Value);
return o;
}
@ -103,6 +108,9 @@ public class SystemConfigPatch: PatchObject
if (o.ContainsKey("description_templates"))
patch.DescriptionTemplates = o.Value<JArray>("description_templates").Select(x => x.Value<string>()).ToArray();
if (o.ContainsKey("case_sensitive_proxy_tags"))
patch.CaseSensitiveProxyTags = o.Value<bool>("case_sensitive_proxy_tags");
return patch;
}
}

View File

@ -18,6 +18,8 @@ public class SystemConfig
public ICollection<string> DescriptionTemplates { get; }
public DateTimeZone Zone => DateTimeZoneProviders.Tzdb.GetZoneOrNull(UiTz);
public bool CaseSensitiveProxyTags { get; set; }
}
public static class SystemConfigExt
@ -34,6 +36,7 @@ public static class SystemConfigExt
o.Add("show_private_info", cfg.ShowPrivateInfo);
o.Add("member_limit", cfg.MemberLimitOverride ?? Limits.MaxMemberCount);
o.Add("group_limit", cfg.GroupLimitOverride ?? Limits.MaxGroupCount);
o.Add("case_sensitive_proxy_tags", cfg.CaseSensitiveProxyTags);
o.Add("description_templates", JArray.FromObject(cfg.DescriptionTemplates));

View File

@ -9,9 +9,10 @@ namespace PluralKit.Tests;
public class ProxyTagParserTests
{
internal static ProxyMatch AssertMatch(IEnumerable<ProxyMember> members, string input, string? name = null,
string? prefix = null, string? suffix = null, string? content = null)
string? prefix = null, string? suffix = null, string? content = null,
bool caseSensitive = true)
{
Assert.True(new ProxyTagParser().TryMatch(members, input, out var result));
Assert.True(new ProxyTagParser().TryMatch(members, input, caseSensitive, out var result));
if (name != null) Assert.Equal(name, result.Member.Name);
if (prefix != null) Assert.Equal(prefix, result.ProxyTags?.Prefix);
if (suffix != null) Assert.Equal(suffix, result.ProxyTags?.Suffix);
@ -19,9 +20,9 @@ public class ProxyTagParserTests
return result;
}
internal static void AssertNoMatch(IEnumerable<ProxyMember> members, string? input)
internal static void AssertNoMatch(IEnumerable<ProxyMember> members, string? input, bool caseSensitive = true)
{
Assert.False(new ProxyTagParser().TryMatch(members, input, out _));
Assert.False(new ProxyTagParser().TryMatch(members, input, caseSensitive, out _));
}
public class Basics
@ -46,6 +47,16 @@ public class ProxyTagParserTests
public void StringWithTagsMatch(string input) =>
AssertMatch(members, input);
[Theory]
[InlineData("a:tag with lowercase prefix")]
public void StringWithLowercaseUsingDefaultConfigMatchesNothing(string input) =>
AssertNoMatch(members, input);
[Theory]
[InlineData("a:tag with lowercase prefix")]
public void StringWithLowercaseUsingCaseInsensitiveConfigMatches(string input) =>
AssertMatch(members, input, caseSensitive: false);
[Theory]
[InlineData("[john's tags]", "John")]
[InlineData("{bob's tags}", "Bob")]

View File

@ -117,6 +117,7 @@ Some arguments indicate the use of specific Discord features. These include:
- `pk;config ping <enable|disable>` - Changes your system's ping preferences.
- `pk;config autoproxy timeout [<duration>|off|reset]` - Sets the latch timeout duration for your system.
- `pk;config autoproxy account [on|off]` - Toggles autoproxy globally for the current account.
- `pk;config proxy case [on|off]` - Toggles case sensitive proxy tags for your system.
## Server owner commands
*(all commands here require Manage Server permission)*