2017-09-05 17:26:13 +00:00
|
|
|
|
using Discord.WebSocket;
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
2017-08-10 03:16:08 +00:00
|
|
|
|
using Noikoio.RegexBot.ConfigItem;
|
|
|
|
|
using System;
|
2017-09-05 17:53:11 +00:00
|
|
|
|
using System.Collections.Generic;
|
2017-08-10 03:16:08 +00:00
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
|
2017-11-12 03:12:24 +00:00
|
|
|
|
namespace Noikoio.RegexBot.Module.AutoRespond
|
2017-08-10 03:16:08 +00:00
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Represents a single autoresponse definition.
|
|
|
|
|
/// </summary>
|
2017-09-05 17:18:07 +00:00
|
|
|
|
class ConfigItem
|
2017-08-10 03:16:08 +00:00
|
|
|
|
{
|
|
|
|
|
public enum ResponseType { None, Exec, Reply }
|
2018-01-23 22:06:31 +00:00
|
|
|
|
private static Random ChangeRng = new Random();
|
2017-08-10 03:16:08 +00:00
|
|
|
|
|
|
|
|
|
string _label;
|
2017-09-05 17:53:11 +00:00
|
|
|
|
IEnumerable<Regex> _regex;
|
2017-08-10 03:16:08 +00:00
|
|
|
|
ResponseType _rtype;
|
2017-09-05 17:53:11 +00:00
|
|
|
|
string _rbody;
|
2017-08-10 22:19:42 +00:00
|
|
|
|
private FilterList _filter;
|
|
|
|
|
private RateLimitCache _limit;
|
2018-01-23 22:06:31 +00:00
|
|
|
|
private double _random;
|
2017-08-10 03:16:08 +00:00
|
|
|
|
|
|
|
|
|
public string Label => _label;
|
2017-09-05 17:53:11 +00:00
|
|
|
|
public IEnumerable<Regex> Regex => _regex;
|
2017-08-10 03:16:08 +00:00
|
|
|
|
public (ResponseType, string) Response => (_rtype, _rbody);
|
2017-08-10 22:19:42 +00:00
|
|
|
|
public FilterList Filter => _filter;
|
|
|
|
|
public RateLimitCache RateLimit => _limit;
|
2018-01-23 22:06:31 +00:00
|
|
|
|
public double RandomChance => _random;
|
2017-08-10 03:16:08 +00:00
|
|
|
|
|
2017-09-05 17:18:07 +00:00
|
|
|
|
public ConfigItem(JProperty definition)
|
2017-08-10 03:16:08 +00:00
|
|
|
|
{
|
2017-08-30 05:25:47 +00:00
|
|
|
|
_label = definition.Name;
|
|
|
|
|
var data = (JObject)definition.Value;
|
2017-08-10 03:16:08 +00:00
|
|
|
|
|
|
|
|
|
// error postfix string
|
|
|
|
|
string errorpfx = $" in response definition for '{_label}'.";
|
|
|
|
|
|
|
|
|
|
// regex trigger
|
2017-09-05 17:53:11 +00:00
|
|
|
|
const string NoRegexError = "No regular expression patterns are defined";
|
|
|
|
|
var regexes = new List<Regex>();
|
2017-08-10 03:16:08 +00:00
|
|
|
|
const RegexOptions rxopts = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline;
|
2017-09-05 17:53:11 +00:00
|
|
|
|
var rxconf = data["regex"];
|
|
|
|
|
if (rxconf == null) throw new RuleImportException(NoRegexError + errorpfx);
|
|
|
|
|
if (rxconf.Type == JTokenType.Array)
|
2017-08-10 03:16:08 +00:00
|
|
|
|
{
|
2017-09-05 17:53:11 +00:00
|
|
|
|
foreach (var input in rxconf.Values<string>())
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Regex r = new Regex(input, rxopts);
|
|
|
|
|
regexes.Add(r);
|
|
|
|
|
}
|
|
|
|
|
catch (ArgumentException)
|
|
|
|
|
{
|
|
|
|
|
throw new RuleImportException(
|
|
|
|
|
$"Failed to parse regular expression pattern '{input}'{errorpfx}");
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-08-10 03:16:08 +00:00
|
|
|
|
}
|
2017-09-05 17:53:11 +00:00
|
|
|
|
else
|
2017-08-10 03:16:08 +00:00
|
|
|
|
{
|
2017-09-05 17:53:11 +00:00
|
|
|
|
string rxstr = rxconf.Value<string>();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Regex r = new Regex(rxstr, rxopts);
|
|
|
|
|
regexes.Add(r);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex) when (ex is ArgumentException || ex is NullReferenceException)
|
|
|
|
|
{
|
|
|
|
|
throw new RuleImportException(
|
|
|
|
|
$"Failed to parse regular expression pattern '{rxstr}'{errorpfx}");
|
|
|
|
|
}
|
2017-08-10 03:16:08 +00:00
|
|
|
|
}
|
2017-09-05 17:53:11 +00:00
|
|
|
|
_regex = regexes.ToArray();
|
2017-08-10 03:16:08 +00:00
|
|
|
|
|
|
|
|
|
// response - defined in either "exec" or "reply", but not both
|
|
|
|
|
_rbody = null;
|
|
|
|
|
_rtype = ResponseType.None;
|
|
|
|
|
|
2017-09-05 17:53:11 +00:00
|
|
|
|
// exec response
|
2017-08-30 05:25:47 +00:00
|
|
|
|
string execstr = data["exec"]?.Value<string>();
|
2017-08-10 03:16:08 +00:00
|
|
|
|
if (!string.IsNullOrWhiteSpace(execstr))
|
|
|
|
|
{
|
|
|
|
|
_rbody = execstr;
|
|
|
|
|
_rtype = ResponseType.Exec;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// reply response
|
2017-08-30 05:25:47 +00:00
|
|
|
|
string replystr = data["reply"]?.Value<string>();
|
2017-08-10 03:16:08 +00:00
|
|
|
|
if (!string.IsNullOrWhiteSpace(replystr))
|
|
|
|
|
{
|
|
|
|
|
if (_rbody != null)
|
|
|
|
|
throw new RuleImportException("A value for both 'exec' and 'reply' is not allowed" + errorpfx);
|
|
|
|
|
_rbody = replystr;
|
|
|
|
|
_rtype = ResponseType.Reply;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_rbody == null)
|
|
|
|
|
throw new RuleImportException("A response value of either 'exec' or 'reply' was not defined" + errorpfx);
|
|
|
|
|
// ---
|
|
|
|
|
|
|
|
|
|
// whitelist/blacklist filtering
|
2017-08-30 05:25:47 +00:00
|
|
|
|
_filter = new FilterList(data);
|
2017-08-10 22:19:42 +00:00
|
|
|
|
|
|
|
|
|
// rate limiting
|
2017-08-30 07:28:34 +00:00
|
|
|
|
string rlstr = data["ratelimit"]?.Value<string>();
|
2017-08-10 22:19:42 +00:00
|
|
|
|
if (string.IsNullOrWhiteSpace(rlstr))
|
|
|
|
|
{
|
|
|
|
|
_limit = new RateLimitCache(RateLimitCache.DefaultTimeout);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (ushort.TryParse(rlstr, out var rlval))
|
|
|
|
|
{
|
|
|
|
|
_limit = new RateLimitCache(rlval);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw new RuleImportException("Rate limit value is invalid" + errorpfx);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-01-23 22:06:31 +00:00
|
|
|
|
|
|
|
|
|
// random chance
|
|
|
|
|
string randstr = data["RandomChance"]?.Value<string>();
|
|
|
|
|
if (string.IsNullOrWhiteSpace(randstr))
|
|
|
|
|
{
|
|
|
|
|
_random = double.NaN;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (!double.TryParse(randstr, out _random))
|
|
|
|
|
{
|
|
|
|
|
throw new RuleImportException("Random value is invalid (unable to parse)" + errorpfx);
|
|
|
|
|
}
|
|
|
|
|
if (_random > 1 || _random < 0)
|
|
|
|
|
{
|
|
|
|
|
throw new RuleImportException("Random value is invalid (not between 0 and 1)" + errorpfx);
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-08-10 03:16:08 +00:00
|
|
|
|
}
|
2017-08-30 05:39:59 +00:00
|
|
|
|
|
2017-09-05 17:26:13 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Checks given message to see if it matches this rule's constraints.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>If true, the rule's response(s) should be executed.</returns>
|
|
|
|
|
public bool Match(SocketMessage m)
|
|
|
|
|
{
|
|
|
|
|
// Filter check
|
|
|
|
|
if (Filter.IsFiltered(m)) return false;
|
|
|
|
|
|
|
|
|
|
// Match check
|
2017-09-05 17:53:11 +00:00
|
|
|
|
bool matchFound = false;
|
|
|
|
|
foreach (var item in Regex)
|
|
|
|
|
{
|
|
|
|
|
if (item.IsMatch(m.Content))
|
|
|
|
|
{
|
|
|
|
|
matchFound = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!matchFound) return false;
|
2017-09-05 17:26:13 +00:00
|
|
|
|
|
|
|
|
|
// Rate limit check - currently per channel
|
|
|
|
|
if (!RateLimit.AllowUsage(m.Channel.Id)) return false;
|
|
|
|
|
|
2018-01-23 22:06:31 +00:00
|
|
|
|
// Random chance check
|
|
|
|
|
if (!double.IsNaN(RandomChance))
|
|
|
|
|
{
|
|
|
|
|
// Fail if randomly generated value is higher than the parameter
|
|
|
|
|
// Example: To fail a 75% chance, the check value must be between 0.75000...001 and 1.0.
|
|
|
|
|
var chk = ChangeRng.NextDouble();
|
|
|
|
|
if (chk > RandomChance) return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-05 17:26:13 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-30 05:39:59 +00:00
|
|
|
|
public override string ToString() => $"Autoresponse definition '{Label}'";
|
2017-08-10 03:16:08 +00:00
|
|
|
|
}
|
|
|
|
|
}
|