Skip to content

Commit

Permalink
Merge pull request #468 from Ixrec/smart-matching
Browse files Browse the repository at this point in the history
smart-ish witty pattern matching and multiple responses
  • Loading branch information
Ixrec authored Aug 17, 2023
2 parents 8f1f645 + 85cd595 commit cbf1511
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 20 deletions.
13 changes: 9 additions & 4 deletions Izzy-Moonbot/Describers/ConfigDescriber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,15 @@ public ConfigDescriber()
// Witty settings
_config.Add("Witties",
new ConfigItem(ConfigItemType.StringDictionary,
"A map from message patterns to automated Izzy responses. Also known as an 'autoresponder.'\n" +
"For example, `.config Witties set \"izzy\" \"that's my name!\"` will make Izzy post \"that's my name!\" whenever anyone posts a message containing \"izzy\" (or any capitalization thereof).\n" +
"This will only happen if the message is in one of the `WittyChannels` channels, and no witty response has been posted within the last `WittyCooldown` seconds, and the message author is not a bot.\n" +
"Currently, there is no special pattern matching syntax; the 'patterns' are just strings taken literally. No wildcards or regexes.",
"A map from message patterns to automated Izzy responses. Also known as an 'autoresponder.'" +
" For example, `.config Witties set \"izzy\" \"hi!\"` will make Izzy post \"hi!\" whenever any non-bot posts" +
" a message with \"izzy\" (in one of `WittyChannels`, at least `WittyCooldown` seconds after the last witty response).\n" +
"If the response contains `|`s, Izzy will treat it as multiple possible responses, and select one at random.\n" +
"There is no regex or wildcard syntax, but witty pattern matching is 'smart' or 'fuzzy' in the following ways:\n" +
" - case-insensitive: `izzy` matches `Izzy` and `IZZY`\n" +
" - some punctuation marks (,.'\"!) are optional: `hi, ponies!` matches `hi ponies`\n" +
" - spaces are optional and allow extra spaces: `hi izzy` matches `hiizzy` and `hi izzy`\n" +
" - whole 'word' matches only: `test` matches `this is a test!` but does NOT match `testy` or `attest`",
ConfigItemCategory.Witty));
_config.Add("WittyChannels",
new ConfigItem(ConfigItemType.ChannelSet,
Expand Down
62 changes: 46 additions & 16 deletions Izzy-Moonbot/EventListeners/MessageListener.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Discord;
using Izzy_Moonbot.Adapters;
Expand Down Expand Up @@ -39,27 +40,56 @@ private async Task ProcessMessageReceived(
if (author.Id == client.CurrentUser.Id) return; // Don't process self.
if (author.IsBot) return; // Don't listen to bots

var content = message.Content.ToLower();
var match = _config.Witties.FirstOrDefault(pair => content.Contains(pair.Key.ToLower()));
if (match.Key == null)
return;

// Ignore messages outside the listed channels
var channelId = message.Channel.Id;
if (!_config.WittyChannels.Contains(channelId))
{
_logger.Log($"Ignoring message {message.Id} matching witty pattern \"{match.Key}\" because it wasn't in one of the WittyChannels");
return;
}
if (!_config.WittyChannels.Contains(channelId)) return;

// Ignore messages posted during the cooldown
var secondsSinceWitty = (DateTimeOffset.UtcNow - _state.LastWittyResponse).TotalSeconds;
if (secondsSinceWitty <= _config.WittyCooldown)
if (secondsSinceWitty <= _config.WittyCooldown) return;

var match = _config.Witties.FirstOrDefault(pair => {
var pattern = pair.Key;
// our use of regex here is an implementation detail, do not expose any regex syntax to the users
pattern = Regex.Escape(pattern);
// every space in the pattern is optional and matches any amount of whitespace
// annoyingly Regex.Escape() escapes whitespace, so we have to remember there's an extra \ before each space now
pattern = pattern.Replace(@"\ ", @"\s*");
// many punctuation marks in the pattern are optional (\? is deliberately kept mandatory)
pattern = pattern.Replace("'", "'?")
.Replace("\"", "\"?")
.Replace(",", ",?")
.Replace(@"\.", @"\.?")
.Replace("!", "!?");
// last but not least, add word boundaries
pattern = @$"\b{pattern}\b";
var match = new Regex(pattern, RegexOptions.IgnoreCase).Match(message.Content);
if (match.Success)
_logger.Log($"Message {message.Id} in #{message.Channel.Name} matched witty pattern \"{pair.Key}\"." +
$"\nRegex implementation: \"{pattern}\"." +
$"\nMatching substring: \"{match.Value}\"" +
$"\nmessage.Content: \"{message.Content}\"");
return match.Success;
});

// If none of the witty patterns matched, do nothing
if (match.Key == null) return;

var response = match.Value;
if (match.Value.Contains('|'))
{
_logger.Log($"Ignoring message {message.Id} matching witty pattern \"{match.Key}\" because it's only been {secondsSinceWitty} seconds since the last witty response");
return;
var responses = match.Value.Split('|');
var responseIndex = new Random().Next(responses.Count());
response = responses[responseIndex];
_logger.Log($"Witty response contained {responses.Count()} |-delimited responses. Chose response {responseIndex} at random: \"{response}\"");
}

_logger.Log($"Received message {message.Id} matching witty pattern \"{match.Key}\". Posting witty response \"{match.Value}\" in {message.Channel.Name}");
await message.Channel.SendMessageAsync(match.Value);
_logger.Log($"Posting witty response \"{response}\" in {message.Channel.Name}");
await message.Channel.SendMessageAsync(response);

_state.LastWittyResponse = DateTimeOffset.UtcNow;
}
Expand Down

0 comments on commit cbf1511

Please sign in to comment.