using Discord;
using RegexBot.Common;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
namespace RegexBot.Modules.RegexModerator;
///
/// Representation of a single RegexModerator rule for a guild.
/// Data in this class is immutable. Contains various helper methods.
///
[DebuggerDisplay("RM rule '{Label}'")]
class ConfDefinition {
public string Label { get; }
// Matching settings
private IEnumerable Regex { get; }
private FilterList Filter { get; }
private bool IgnoreMods { get; }
private bool ScanEmbeds { get; }
// Response settings
public EntityName? ReportingChannel { get; }
public IReadOnlyList Response { get; }
public int BanPurgeDays { get; }
public bool NotifyChannelOfRemoval { get; }
public bool NotifyUserOfRemoval { get; }
public ConfDefinition(JObject def) {
Label = def[nameof(Label)]?.Value()
?? throw new ModuleLoadException($"Encountered a rule without a defined {nameof(Label)}.");
var errpostfx = $" in the rule definition for '{Label}'.";
var rptch = def[nameof(ReportingChannel)]?.Value();
if (rptch != null) {
try {
ReportingChannel = new EntityName(rptch, EntityType.Channel);
} catch (FormatException) {
throw new ModuleLoadException($"'{nameof(ReportingChannel)}' is not defined as a channel{errpostfx}");
}
}
// Regex loading
var opts = RegexOptions.Compiled | RegexOptions.CultureInvariant;
// Reminder: in Singleline mode, all contents are subject to the same regex (useful if e.g. spammer separates words line by line)
opts |= RegexOptions.Singleline;
// IgnoreCase is enabled by default; must be explicitly set to false
if (def["IgnoreCase"]?.Value() ?? true) opts |= RegexOptions.IgnoreCase;
const string ErrBadRegex = "Unable to parse regular expression pattern";
var regexRules = new List();
List regexStrings;
try {
regexStrings = Utilities.LoadStringOrStringArray(def[nameof(Regex)]);
} catch (ArgumentNullException) {
throw new ModuleLoadException($"No patterns were defined under '{nameof(Regex)}'{errpostfx}");
} catch (ArgumentException) {
throw new ModuleLoadException($"'{nameof(Regex)}' is not properly defined{errpostfx}");
}
foreach (var input in regexStrings) {
try {
regexRules.Add(new Regex(input, opts));
} catch (ArgumentException) {
throw new ModuleLoadException($"{ErrBadRegex}{errpostfx}");
}
}
Regex = regexRules.AsReadOnly();
// Filtering
Filter = new FilterList(def);
// Misc options
// IgnoreMods is enabled by default; must be explicitly set to false
IgnoreMods = def[nameof(IgnoreMods)]?.Value() ?? true;
ScanEmbeds = def[nameof(ScanEmbeds)]?.Value() ?? false; // false by default
// Load response(s) and response settings
try {
Response = Utilities.LoadStringOrStringArray(def[nameof(Response)]).AsReadOnly();
} catch (ArgumentNullException) {
throw new ModuleLoadException($"No responses were defined under '{nameof(Response)}'{errpostfx}");
} catch (ArgumentException) {
throw new ModuleLoadException($"'{nameof(Response)}' is not properly defined{errpostfx}");
}
BanPurgeDays = def[nameof(BanPurgeDays)]?.Value() ?? 0;
NotifyChannelOfRemoval = def[nameof(NotifyChannelOfRemoval)]?.Value() ?? true;
NotifyUserOfRemoval = def[nameof(NotifyUserOfRemoval)]?.Value() ?? true;
}
///
/// Checks the given message to determine if it matches this definition's constraints.
///
/// True if match.
public bool IsMatch(SocketMessage m, bool senderIsModerator) {
if (Filter.IsFiltered(m, false)) return false;
if (senderIsModerator && IgnoreMods) return false;
foreach (var regex in Regex) {
if (ScanEmbeds && regex.IsMatch(SerializeEmbed(m.Embeds))) return true;
if (regex.IsMatch(m.Content)) return true;
}
return false;
}
private static string SerializeEmbed(IReadOnlyCollection