2020-06-12 18:29:50 +00:00
|
|
|
using NodaTime;
|
|
|
|
|
|
|
|
using PluralKit.Core;
|
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
namespace PluralKit.Bot;
|
|
|
|
|
|
|
|
public class ProxyMatcher
|
2020-06-12 18:29:50 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
private static readonly char AutoproxyEscapeCharacter = '\\';
|
|
|
|
public static readonly Duration DefaultLatchExpiryTime = Duration.FromHours(6);
|
|
|
|
|
|
|
|
private readonly IClock _clock;
|
|
|
|
private readonly ProxyTagParser _parser;
|
|
|
|
|
|
|
|
public ProxyMatcher(ProxyTagParser parser, IClock clock)
|
2020-06-12 18:29:50 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
_parser = parser;
|
|
|
|
_clock = clock;
|
|
|
|
}
|
2020-06-12 18:29:50 +00:00
|
|
|
|
2022-03-22 03:43:33 +00:00
|
|
|
public bool TryMatch(MessageContext ctx, AutoproxySettings settings, IReadOnlyCollection<ProxyMember> members, out ProxyMatch match,
|
2021-11-27 02:10:56 +00:00
|
|
|
string messageContent,
|
|
|
|
bool hasAttachments, bool allowAutoproxy)
|
|
|
|
{
|
|
|
|
if (TryMatchTags(members, messageContent, hasAttachments, out match)) return true;
|
2022-03-22 03:43:33 +00:00
|
|
|
if (allowAutoproxy && TryMatchAutoproxy(ctx, settings, members, messageContent, out match)) return true;
|
2021-11-27 02:10:56 +00:00
|
|
|
return false;
|
|
|
|
}
|
2020-06-12 18:29:50 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
private bool TryMatchTags(IReadOnlyCollection<ProxyMember> members, string messageContent, bool hasAttachments,
|
|
|
|
out ProxyMatch match)
|
|
|
|
{
|
|
|
|
if (!_parser.TryMatch(members, messageContent, out match)) return false;
|
2020-06-12 18:29:50 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
// 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
|
|
|
|
return hasAttachments || match.Content.Trim().Length > 0;
|
|
|
|
}
|
|
|
|
|
2022-03-22 03:43:33 +00:00
|
|
|
private bool TryMatchAutoproxy(MessageContext ctx, AutoproxySettings settings, IReadOnlyCollection<ProxyMember> members,
|
2021-11-27 02:10:56 +00:00
|
|
|
string messageContent,
|
|
|
|
out ProxyMatch match)
|
|
|
|
{
|
|
|
|
match = default;
|
2020-06-12 18:29:50 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
// Skip autoproxy match if we hit the escape character
|
|
|
|
if (messageContent.StartsWith(AutoproxyEscapeCharacter))
|
|
|
|
throw new ProxyService.ProxyChecksFailedException(
|
|
|
|
"This message matches none of your proxy tags, and it was not autoproxied because it starts with a backslash (`\\`).");
|
|
|
|
|
|
|
|
// Find the member we should autoproxy (null if none)
|
2022-03-22 03:43:33 +00:00
|
|
|
var member = settings.AutoproxyMode switch
|
2020-06-12 18:29:50 +00:00
|
|
|
{
|
2022-03-22 03:43:33 +00:00
|
|
|
AutoproxyMode.Member when settings.AutoproxyMember != null =>
|
|
|
|
members.FirstOrDefault(m => m.Id == settings.AutoproxyMember),
|
2021-08-27 15:03:47 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
AutoproxyMode.Front when ctx.LastSwitchMembers.Length > 0 =>
|
|
|
|
members.FirstOrDefault(m => m.Id == ctx.LastSwitchMembers[0]),
|
|
|
|
|
2022-03-22 03:43:33 +00:00
|
|
|
AutoproxyMode.Latch when settings.AutoproxyMember != null =>
|
|
|
|
members.FirstOrDefault(m => m.Id == settings.AutoproxyMember.Value),
|
2020-06-12 18:29:50 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
_ => null
|
|
|
|
};
|
|
|
|
// Throw an error if the member is null, message varies depending on autoproxy mode
|
|
|
|
if (member == null)
|
2020-06-12 18:29:50 +00:00
|
|
|
{
|
2022-03-22 03:43:33 +00:00
|
|
|
if (settings.AutoproxyMode == AutoproxyMode.Front)
|
2021-11-27 02:10:56 +00:00
|
|
|
throw new ProxyService.ProxyChecksFailedException(
|
|
|
|
"You are using autoproxy front, but no members are currently registered as fronting. Please use `pk;switch <member>` to log a new switch.");
|
2022-03-22 03:43:33 +00:00
|
|
|
if (settings.AutoproxyMode == AutoproxyMode.Member)
|
2021-11-27 02:10:56 +00:00
|
|
|
throw new ProxyService.ProxyChecksFailedException(
|
|
|
|
"You are using member-specific autoproxy with an invalid member. Was this member deleted?");
|
2022-03-22 03:43:33 +00:00
|
|
|
if (settings.AutoproxyMode == AutoproxyMode.Latch)
|
2021-11-27 02:10:56 +00:00
|
|
|
throw new ProxyService.ProxyChecksFailedException(
|
|
|
|
"You are using autoproxy latch, but have not sent any messages yet in this server. Please send a message using proxy tags first.");
|
|
|
|
throw new ProxyService.ProxyChecksFailedException(
|
|
|
|
"This message matches none of your proxy tags and autoproxy is not enabled.");
|
2020-06-12 18:29:50 +00:00
|
|
|
}
|
2020-06-12 21:13:21 +00:00
|
|
|
|
2022-03-22 03:43:33 +00:00
|
|
|
if (settings.AutoproxyMode != AutoproxyMode.Member && !member.AllowAutoproxy)
|
2021-11-27 02:10:56 +00:00
|
|
|
throw new ProxyService.ProxyChecksFailedException(
|
|
|
|
"This member has autoproxy disabled. To enable it, use `pk;m <member> autoproxy on`.");
|
|
|
|
|
|
|
|
// Moved the IsLatchExpired() check to here, so that an expired latch and a latch without any previous messages throw different errors
|
2022-03-22 03:43:33 +00:00
|
|
|
if (settings.AutoproxyMode == AutoproxyMode.Latch && IsLatchExpired(ctx, settings))
|
2021-11-27 02:10:56 +00:00
|
|
|
throw new ProxyService.ProxyChecksFailedException(
|
|
|
|
"Latch-mode autoproxy has timed out. Please send a new message using proxy tags.");
|
|
|
|
|
|
|
|
match = new ProxyMatch
|
2020-06-12 21:13:21 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
Content = messageContent,
|
|
|
|
Member = member,
|
|
|
|
|
|
|
|
// We're autoproxying, so not using any proxy tags here
|
|
|
|
// we just find the first pair of tags (if any), otherwise null
|
|
|
|
ProxyTags = member.ProxyTags.FirstOrDefault()
|
|
|
|
};
|
|
|
|
return true;
|
|
|
|
}
|
2020-12-08 11:57:17 +00:00
|
|
|
|
2022-03-22 03:43:33 +00:00
|
|
|
private bool IsLatchExpired(MessageContext ctx, AutoproxySettings settings)
|
2021-11-27 02:10:56 +00:00
|
|
|
{
|
|
|
|
if (ctx.LatchTimeout == 0) return false;
|
2020-11-21 00:44:15 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var timeout = ctx.LatchTimeout.HasValue
|
|
|
|
? Duration.FromSeconds(ctx.LatchTimeout.Value)
|
|
|
|
: DefaultLatchExpiryTime;
|
|
|
|
|
2022-03-22 03:43:33 +00:00
|
|
|
return _clock.GetCurrentInstant() - settings.LastLatchTimestamp > timeout;
|
2020-06-12 18:29:50 +00:00
|
|
|
}
|
|
|
|
}
|