using Newtonsoft.Json.Linq; using Noikoio.RegexBot.ConfigItem; using System; using System.Collections.Generic; using System.Text.RegularExpressions; namespace Noikoio.RegexBot.Feature.RegexResponder { /// /// Represents configuration for a single rule. /// [System.Diagnostics.DebuggerDisplay("Rule: {DisplayName}")] internal struct RuleConfig { private string _displayName; private IEnumerable _regex; private IEnumerable _responses; private FilterList _filter; private int? _minLength; private int? _maxLength; private bool _modBypass; private bool _matchEmbeds; public string DisplayName => _displayName; public IEnumerable Regex => _regex; public IEnumerable Responses => _responses; public FilterList Filter => _filter; public int? MinLength => _minLength; public int? MaxLength => _maxLength; public bool AllowModBypass => _modBypass; public bool MatchEmbeds => _matchEmbeds; /// /// Takes the JObject for a single rule and retrieves all data for use as a struct. /// /// Rule configuration input /// /// Thrown when encountering a missing or invalid value. /// public RuleConfig(JObject ruleconf) { // display name - validation should've been done outside this constructor already _displayName = ruleconf["name"]?.Value(); if (_displayName == null) throw new RuleImportException("Display name not defined."); // regex options RegexOptions opts = RegexOptions.Compiled | RegexOptions.CultureInvariant; // TODO consider adding an option to specify Singleline and Multiline matching opts |= RegexOptions.Singleline; // case sensitivity must be explicitly defined, else not case sensitive by default bool? regexci = ruleconf["ignorecase"]?.Value(); opts |= RegexOptions.IgnoreCase; if (regexci.HasValue && regexci.Value == false) opts &= ~RegexOptions.IgnoreCase; // regex const string RegexError = "No regular expression patterns are defined."; var regexes = new List(); var rxconf = ruleconf["regex"]; if (rxconf == null) { throw new RuleImportException(RegexError); } if (rxconf.Type == JTokenType.Array) { foreach (var input in rxconf.Values()) { try { Regex r = new Regex(input, opts); regexes.Add(r); } catch (ArgumentException) { throw new RuleImportException("Failed to parse regular expression pattern: " + input); } } } else { string rxstr = rxconf.Value(); try { var rxx = new Regex(rxstr, opts); regexes.Add(rxx); } catch (ArgumentException) { throw new RuleImportException("Failed to parse regular expression pattern: " + rxstr); } } if (regexes.Count == 0) { throw new RuleImportException(RegexError); } _regex = regexes.ToArray(); // min/max length try { _minLength = ruleconf["min"]?.Value(); _maxLength = ruleconf["max"]?.Value(); } catch (FormatException) { throw new RuleImportException("Minimum/maximum values must be an integer."); } // responses const string ResponseError = "No responses have been defined for this rule."; var responses = new List(); var rsconf = ruleconf["response"]; if (rsconf == null) { throw new RuleImportException(ResponseError); } if (rsconf.Type == JTokenType.Array) { foreach (var input in rsconf.Values()) responses.Add(input); } else { responses.Add(rsconf.Value()); } // TODO a bit of response validation here. at least check for blanks or something. _responses = responses.ToArray(); // (white|black)list filtering _filter = new FilterList(ruleconf); // moderator bypass toggle - true by default, must be explicitly set to false bool? modoverride = ruleconf["AllowModBypass"]?.Value(); _modBypass = modoverride.HasValue ? modoverride.Value : true; // embed matching mode bool? embedmode = ruleconf["MatchEmbeds"]?.Value(); _matchEmbeds = (embedmode.HasValue && embedmode == true); } } }