Removed RegexResponder
This commit is contained in:
parent
18e1748c4f
commit
0f3fd350fa
3 changed files with 0 additions and 715 deletions
|
@ -1,309 +0,0 @@
|
||||||
using Discord;
|
|
||||||
using Discord.WebSocket;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Noikoio.RegexBot.ConfigItem;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Noikoio.RegexBot.Feature.RegexResponder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Implements per-message regex matching and executes customizable responses.
|
|
||||||
/// Namesake of this project.
|
|
||||||
/// </summary>
|
|
||||||
partial class EventProcessor : BotFeature
|
|
||||||
{
|
|
||||||
private readonly DiscordSocketClient _client;
|
|
||||||
|
|
||||||
public override string Name => "RegexResponder";
|
|
||||||
|
|
||||||
public EventProcessor(DiscordSocketClient client) : base(client)
|
|
||||||
{
|
|
||||||
_client = client;
|
|
||||||
|
|
||||||
_client.MessageReceived += OnMessageReceived;
|
|
||||||
_client.MessageUpdated += OnMessageUpdated;
|
|
||||||
|
|
||||||
_commands = new ReadOnlyDictionary<string, ResponseProcessor>(
|
|
||||||
new Dictionary<string, ResponseProcessor>() {
|
|
||||||
#if DEBUG
|
|
||||||
{ "crash", RP_Crash },
|
|
||||||
{ "dumpid", RP_DumpID },
|
|
||||||
#endif
|
|
||||||
{ "report", RP_Report },
|
|
||||||
{ "say", RP_Say },
|
|
||||||
{ "remove", RP_Remove },
|
|
||||||
{ "delete", RP_Remove },
|
|
||||||
{ "erase", RP_Remove },
|
|
||||||
{ "exec", RP_Exec },
|
|
||||||
{ "ban", RP_Ban },
|
|
||||||
{ "grantrole", RP_GrantRevokeRole },
|
|
||||||
{ "revokerole", RP_GrantRevokeRole }
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Event handlers
|
|
||||||
private async Task OnMessageReceived(SocketMessage arg)
|
|
||||||
=> await ReceiveMessage(arg);
|
|
||||||
private async Task OnMessageUpdated(Cacheable<IMessage, ulong> arg1, SocketMessage arg2, ISocketMessageChannel arg3)
|
|
||||||
=> await ReceiveMessage(arg2);
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Receives incoming messages and creates tasks to handle them if necessary.
|
|
||||||
/// </summary>
|
|
||||||
private async Task ReceiveMessage(SocketMessage arg)
|
|
||||||
{
|
|
||||||
// Determine channel type - if not a guild channel, stop.
|
|
||||||
var ch = arg.Channel as SocketGuildChannel;
|
|
||||||
if (ch == null) return;
|
|
||||||
|
|
||||||
if (arg.Author == _client.CurrentUser) return; // Don't ever self-trigger
|
|
||||||
|
|
||||||
// Looking up server information and extracting settings
|
|
||||||
SocketGuild g = ch.Guild;
|
|
||||||
ServerConfig sd = null;
|
|
||||||
foreach (var item in RegexBot.Config.Servers)
|
|
||||||
{
|
|
||||||
if (item.Id.HasValue)
|
|
||||||
{
|
|
||||||
// Finding server by ID
|
|
||||||
if (g.Id == item.Id)
|
|
||||||
{
|
|
||||||
sd = item;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Finding server by name and caching ID
|
|
||||||
if (string.Equals(item.Name, g.Name, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
item.Id = g.Id;
|
|
||||||
sd = item;
|
|
||||||
await Logger.GetLogger(Configuration.LogPrefix)
|
|
||||||
($"Suggestion: Server \"{item.Name}\" can be defined as \"{item.Id}::{item.Name}\"");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sd == null) return; // No server configuration found
|
|
||||||
var rules = GetConfig(ch.Guild.Id) as IEnumerable<RuleConfig>;
|
|
||||||
if (rules == null) return;
|
|
||||||
|
|
||||||
// Further processing is sent to the thread pool
|
|
||||||
foreach (var rule in rules)
|
|
||||||
await Task.Run(async () => await ProcessMessage(sd, rule, arg));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Uses information from a single rule and checks if the incoming message is a match.
|
|
||||||
/// If it matches, the rule's responses are executed. To be run in the thread pool.
|
|
||||||
/// </summary>
|
|
||||||
private async Task ProcessMessage(ServerConfig srv, RuleConfig rule, SocketMessage msg)
|
|
||||||
{
|
|
||||||
string msgcontent;
|
|
||||||
|
|
||||||
// Embed mode?
|
|
||||||
if (rule.MatchEmbeds)
|
|
||||||
{
|
|
||||||
var embeds = new StringBuilder();
|
|
||||||
foreach (var e in msg.Embeds) embeds.AppendLine(EmbedToString(e));
|
|
||||||
msgcontent = embeds.ToString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
msgcontent = msg.Content;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Min/max message length check
|
|
||||||
if (rule.MinLength.HasValue && msgcontent.Length <= rule.MinLength.Value) return;
|
|
||||||
if (rule.MaxLength.HasValue && msgcontent.Length >= rule.MaxLength.Value) return;
|
|
||||||
|
|
||||||
// Moderator bypass check
|
|
||||||
if (rule.AllowModBypass == true && srv.Moderators.ExistsInList(msg)) return;
|
|
||||||
// Individual rule filtering check
|
|
||||||
if (rule.Filter.IsFiltered(msg)) return;
|
|
||||||
|
|
||||||
// And finally, pattern matching checks
|
|
||||||
bool success = false;
|
|
||||||
foreach (var regex in rule.Regex)
|
|
||||||
{
|
|
||||||
success = regex.Match(msgcontent).Success;
|
|
||||||
if (success) break;
|
|
||||||
}
|
|
||||||
if (!success) return;
|
|
||||||
|
|
||||||
// Prepare to execute responses
|
|
||||||
await Log($"\"{rule.DisplayName}\" triggered in {srv.Name}/#{msg.Channel} by {msg.Author.ToString()}");
|
|
||||||
|
|
||||||
foreach (string rcmd in rule.Responses)
|
|
||||||
{
|
|
||||||
string cmd = rcmd.TrimStart(' ').Split(' ')[0].ToLower();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ResponseProcessor response;
|
|
||||||
if (!_commands.TryGetValue(cmd, out response))
|
|
||||||
{
|
|
||||||
await Log($"Unknown command defined in response: \"{cmd}\"");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
await response.Invoke(rcmd, rule, msg);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await Log($"Encountered an error while processing \"{cmd}\". Details follow:");
|
|
||||||
await Log(ex.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConfigSection("rules")]
|
|
||||||
public override async Task<object> ProcessConfiguration(JToken configSection)
|
|
||||||
{
|
|
||||||
List<RuleConfig> rules = new List<RuleConfig>();
|
|
||||||
foreach (JObject ruleconf in configSection)
|
|
||||||
{
|
|
||||||
// Try and get at least the name before passing it to RuleItem
|
|
||||||
string name = ruleconf["name"]?.Value<string>();
|
|
||||||
if (name == null)
|
|
||||||
{
|
|
||||||
await Log("Display name not defined within a rule section.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
await Log($"Adding rule \"{name}\"");
|
|
||||||
|
|
||||||
RuleConfig rule;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
rule = new RuleConfig(ruleconf);
|
|
||||||
}
|
|
||||||
catch (RuleImportException ex)
|
|
||||||
{
|
|
||||||
await Log("-> Error: " + ex.Message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
rules.Add(rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rules.AsReadOnly();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Turns an embed into a single string for regex matching purposes
|
|
||||||
/// </summary>
|
|
||||||
private string EmbedToString(Embed e)
|
|
||||||
{
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
if (e.Author.HasValue) result.AppendLine(e.Author.Value.Name ?? "" + e.Author.Value.Url ?? "");
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(e.Title)) result.AppendLine(e.Title);
|
|
||||||
if (!string.IsNullOrWhiteSpace(e.Description)) result.AppendLine(e.Description);
|
|
||||||
|
|
||||||
foreach (var f in e.Fields)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(f.Name)) result.AppendLine(f.Name);
|
|
||||||
if (!string.IsNullOrWhiteSpace(f.Value)) result.AppendLine(f.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.Footer.HasValue)
|
|
||||||
{
|
|
||||||
result.AppendLine(e.Footer.Value.Text ?? "");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string[] SplitParams(string cmd, int? limit = null)
|
|
||||||
{
|
|
||||||
if (limit.HasValue)
|
|
||||||
{
|
|
||||||
return cmd.Split(new char[] { ' ' }, limit.Value, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return cmd.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string ProcessText(string input, SocketMessage m)
|
|
||||||
{
|
|
||||||
// Maybe in the future this will do more.
|
|
||||||
// For now, replaces all instances of @_ with the message sender.
|
|
||||||
return input
|
|
||||||
.Replace("@_", m.Author.Mention)
|
|
||||||
.Replace("@\\_", "@_");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Receives a string (beginning with @ or #) and returns an object
|
|
||||||
/// suitable for sending out messages
|
|
||||||
/// </summary>
|
|
||||||
private async Task<IMessageChannel> GetMessageTargetAsync(string targetName, SocketMessage m)
|
|
||||||
{
|
|
||||||
const string AEShort = "Target name is too short.";
|
|
||||||
|
|
||||||
EntityType et;
|
|
||||||
if (targetName.Length <= 1) throw new ArgumentException(AEShort);
|
|
||||||
|
|
||||||
if (targetName[0] == '#') et = EntityType.Channel;
|
|
||||||
else if (targetName[0] == '@') et = EntityType.User;
|
|
||||||
else throw new ArgumentException("Target is not specified to be either a channel or user.");
|
|
||||||
|
|
||||||
targetName = targetName.Substring(1);
|
|
||||||
if (targetName == "_")
|
|
||||||
{
|
|
||||||
if (et == EntityType.Channel) return m.Channel;
|
|
||||||
else return await m.Author.GetOrCreateDMChannelAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
EntityName ei = new EntityName(targetName, et);
|
|
||||||
SocketGuild g = ((SocketGuildUser)m.Author).Guild;
|
|
||||||
|
|
||||||
if (et == EntityType.Channel)
|
|
||||||
{
|
|
||||||
if (targetName.Length < 2 || targetName.Length > 100)
|
|
||||||
throw new ArgumentException(AEShort);
|
|
||||||
|
|
||||||
foreach (var ch in g.TextChannels)
|
|
||||||
{
|
|
||||||
if (ei.Id.HasValue)
|
|
||||||
{
|
|
||||||
if (ei.Id.Value == ch.Id) return ch;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (string.Equals(ei.Name, ch.Name, StringComparison.OrdinalIgnoreCase)) return ch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (ei.Id.HasValue)
|
|
||||||
{
|
|
||||||
// The easy way
|
|
||||||
return await _client.GetUser(ei.Id.Value).GetOrCreateDMChannelAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
// The hard way
|
|
||||||
foreach (var u in g.Users)
|
|
||||||
{
|
|
||||||
if (string.Equals(ei.Name, u.Username, StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(ei.Name, u.Nickname, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return await u.GetOrCreateDMChannelAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,265 +0,0 @@
|
||||||
using Discord;
|
|
||||||
using Discord.WebSocket;
|
|
||||||
using System;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Noikoio.RegexBot.Feature.RegexResponder
|
|
||||||
{
|
|
||||||
// Contains code for handling each response in a rule.
|
|
||||||
partial class EventProcessor
|
|
||||||
{
|
|
||||||
private delegate Task ResponseProcessor(string cmd, RuleConfig r, SocketMessage m);
|
|
||||||
private readonly ReadOnlyDictionary<string, ResponseProcessor> _commands;
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
/// <summary>
|
|
||||||
/// Throws an exception. Meant to be a quick error handling test.
|
|
||||||
/// No parameters.
|
|
||||||
/// </summary>
|
|
||||||
private Task RP_Crash(string cmd, RuleConfig r, SocketMessage m)
|
|
||||||
{
|
|
||||||
throw new Exception("Requested in response.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Prints all guild values (IDs for users, channels, roles) to console.
|
|
||||||
/// The guild info displayed is the one in which the command is invoked.
|
|
||||||
/// No parameters.
|
|
||||||
/// </summary>
|
|
||||||
private Task RP_DumpID(string cmd, RuleConfig r, SocketMessage m)
|
|
||||||
{
|
|
||||||
var g = ((SocketGuildUser)m.Author).Guild;
|
|
||||||
var result = new StringBuilder();
|
|
||||||
|
|
||||||
result.AppendLine("Users:");
|
|
||||||
foreach (var item in g.Users)
|
|
||||||
result.AppendLine($"{item.Id} {item.Username}#{item.Discriminator}");
|
|
||||||
result.AppendLine();
|
|
||||||
|
|
||||||
result.AppendLine("Channels:");
|
|
||||||
foreach (var item in g.Channels) result.AppendLine($"{item.Id} #{item.Name}");
|
|
||||||
result.AppendLine();
|
|
||||||
result.AppendLine("Roles:");
|
|
||||||
foreach (var item in g.Roles) result.AppendLine($"{item.Id} {item.Name}");
|
|
||||||
result.AppendLine();
|
|
||||||
|
|
||||||
Console.WriteLine(result.ToString());
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
/// <summary>
|
|
||||||
/// Sends a message to a specified channel.
|
|
||||||
/// Parameters: say (channel) (message)
|
|
||||||
/// </summary>
|
|
||||||
private async Task RP_Say(string cmd, RuleConfig r, SocketMessage m)
|
|
||||||
{
|
|
||||||
string[] @in = SplitParams(cmd, 3);
|
|
||||||
if (@in.Length != 3)
|
|
||||||
{
|
|
||||||
await Log("Error: say: Incorrect number of parameters.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var target = await GetMessageTargetAsync(@in[1], m);
|
|
||||||
if (target == null)
|
|
||||||
{
|
|
||||||
await Log("Error: say: Unable to resolve given target.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CHANGE THE SAY
|
|
||||||
@in[2] = ProcessText(@in[2], m);
|
|
||||||
await target.SendMessageAsync(@in[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reports the incoming message to a given channel.
|
|
||||||
/// Parameters: report (channel)
|
|
||||||
/// </summary>
|
|
||||||
private async Task RP_Report(string cmd, RuleConfig r, SocketMessage m)
|
|
||||||
{
|
|
||||||
string[] @in = SplitParams(cmd);
|
|
||||||
if (@in.Length != 2)
|
|
||||||
{
|
|
||||||
await Log("Error: report: Incorrect number of parameters.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var target = await GetMessageTargetAsync(@in[1], m);
|
|
||||||
if (target == null)
|
|
||||||
{
|
|
||||||
await Log("Error: report: Unable to resolve given target.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var responsefield = new StringBuilder();
|
|
||||||
responsefield.AppendLine("```");
|
|
||||||
foreach (var line in r.Responses)
|
|
||||||
responsefield.AppendLine(line.Replace("\r", "").Replace("\n", "\\n"));
|
|
||||||
responsefield.Append("```");
|
|
||||||
await target.SendMessageAsync("", embed: new EmbedBuilder()
|
|
||||||
{
|
|
||||||
Color = new Color(0xEDCE00), // configurable later?
|
|
||||||
|
|
||||||
Author = new EmbedAuthorBuilder()
|
|
||||||
{
|
|
||||||
Name = $"{m.Author.Username}#{m.Author.Discriminator} said:",
|
|
||||||
IconUrl = m.Author.GetAvatarUrl()
|
|
||||||
},
|
|
||||||
Description = m.Content,
|
|
||||||
|
|
||||||
Footer = new EmbedFooterBuilder()
|
|
||||||
{
|
|
||||||
Text = $"Rule '{r.DisplayName}'",
|
|
||||||
IconUrl = _client.CurrentUser.GetAvatarUrl()
|
|
||||||
},
|
|
||||||
Timestamp = m.Timestamp
|
|
||||||
}.AddField(new EmbedFieldBuilder()
|
|
||||||
{
|
|
||||||
Name = "Additional info",
|
|
||||||
Value = $"Channel: <#{m.Channel.Id}>\n" // NOTE: manually mentioning channel here
|
|
||||||
+ $"Username: {m.Author.Mention}\n"
|
|
||||||
+ $"Message ID: {m.Id}"
|
|
||||||
}).AddField(new EmbedFieldBuilder()
|
|
||||||
{
|
|
||||||
Name = "Executing response:",
|
|
||||||
Value = responsefield.ToString()
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deletes the incoming message.
|
|
||||||
/// No parameters.
|
|
||||||
/// </summary>
|
|
||||||
private async Task RP_Remove(string cmd, RuleConfig r, SocketMessage m)
|
|
||||||
{
|
|
||||||
// Parameters are not checked
|
|
||||||
await m.DeleteAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Executes an external program and sends standard output to the given channel.
|
|
||||||
/// Parameters: exec (channel) (command line)
|
|
||||||
/// </summary>
|
|
||||||
private async Task RP_Exec(string cmd, RuleConfig r, SocketMessage m)
|
|
||||||
{
|
|
||||||
var @in = SplitParams(cmd, 4);
|
|
||||||
if (@in.Length < 3)
|
|
||||||
{
|
|
||||||
await Log("exec: Incorrect number of parameters.");
|
|
||||||
}
|
|
||||||
|
|
||||||
string result;
|
|
||||||
var target = await GetMessageTargetAsync(@in[1], m);
|
|
||||||
if (target == null)
|
|
||||||
{
|
|
||||||
await Log("Error: exec: Unable to resolve given channel.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessStartInfo ps = new ProcessStartInfo()
|
|
||||||
{
|
|
||||||
FileName = @in[2],
|
|
||||||
Arguments = (@in.Length > 3 ? @in[3] : ""),
|
|
||||||
UseShellExecute = false,
|
|
||||||
RedirectStandardOutput = true
|
|
||||||
};
|
|
||||||
using (Process p = Process.Start(ps))
|
|
||||||
{
|
|
||||||
p.WaitForExit(5000); // waiting at most 5 seconds
|
|
||||||
if (p.HasExited)
|
|
||||||
{
|
|
||||||
if (p.ExitCode != 0) await Log("exec: Process returned exit code " + p.ExitCode);
|
|
||||||
using (var stdout = p.StandardOutput)
|
|
||||||
{
|
|
||||||
result = await stdout.ReadToEndAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await Log("exec: Process is taking too long to exit. Killing process.");
|
|
||||||
p.Kill();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result = ProcessText(result.Trim(), m);
|
|
||||||
await target.SendMessageAsync(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bans the sender of the incoming message.
|
|
||||||
/// No parameters.
|
|
||||||
/// </summary>
|
|
||||||
// TODO add parameter for message auto-deleting
|
|
||||||
private async Task RP_Ban(string cmd, RuleConfig r, SocketMessage m)
|
|
||||||
{
|
|
||||||
SocketGuild g = ((SocketGuildUser)m.Author).Guild;
|
|
||||||
await g.AddBanAsync(m.Author);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Grants or revokes a specified role to/from a given user.
|
|
||||||
/// Parameters: grantrole/revokerole (user ID or @_) (role ID)
|
|
||||||
/// </summary>
|
|
||||||
private async Task RP_GrantRevokeRole(string cmd, RuleConfig r, SocketMessage m)
|
|
||||||
{
|
|
||||||
string[] @in = SplitParams(cmd);
|
|
||||||
if (@in.Length != 3)
|
|
||||||
{
|
|
||||||
await Log($"Error: {@in[0]}: incorrect number of parameters.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!ulong.TryParse(@in[2], out var roleID))
|
|
||||||
{
|
|
||||||
await Log($"Error: {@in[0]}: Invalid role ID specified.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finding role
|
|
||||||
var gu = (SocketGuildUser)m.Author;
|
|
||||||
SocketRole rl = gu.Guild.GetRole(roleID);
|
|
||||||
if (rl == null)
|
|
||||||
{
|
|
||||||
await Log($"Error: {@in[0]}: Specified role not found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finding user
|
|
||||||
SocketGuildUser target;
|
|
||||||
if (@in[1] == "@_")
|
|
||||||
{
|
|
||||||
target = gu;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!ulong.TryParse(@in[1], out var userID))
|
|
||||||
{
|
|
||||||
await Log($"Error: {@in[0]}: Invalid user ID specified.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
target = gu.Guild.GetUser(userID);
|
|
||||||
if (target == null)
|
|
||||||
{
|
|
||||||
await Log($"Error: {@in[0]}: Given user ID does not exist in this server.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (@in[0].ToLower() == "grantrole")
|
|
||||||
{
|
|
||||||
await target.AddRoleAsync(rl);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await target.RemoveRoleAsync(rl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,141 +0,0 @@
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Noikoio.RegexBot.ConfigItem;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace Noikoio.RegexBot.Feature.RegexResponder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents configuration for a single rule.
|
|
||||||
/// </summary>
|
|
||||||
[System.Diagnostics.DebuggerDisplay("Rule: {DisplayName}")]
|
|
||||||
internal struct RuleConfig
|
|
||||||
{
|
|
||||||
private string _displayName;
|
|
||||||
private IEnumerable<Regex> _regex;
|
|
||||||
private IEnumerable<string> _responses;
|
|
||||||
private FilterList _filter;
|
|
||||||
private int? _minLength;
|
|
||||||
private int? _maxLength;
|
|
||||||
private bool _modBypass;
|
|
||||||
private bool _matchEmbeds;
|
|
||||||
|
|
||||||
public string DisplayName => _displayName;
|
|
||||||
public IEnumerable<Regex> Regex => _regex;
|
|
||||||
public IEnumerable<string> Responses => _responses;
|
|
||||||
public FilterList Filter => _filter;
|
|
||||||
public int? MinLength => _minLength;
|
|
||||||
public int? MaxLength => _maxLength;
|
|
||||||
public bool AllowModBypass => _modBypass;
|
|
||||||
public bool MatchEmbeds => _matchEmbeds;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Takes the JObject for a single rule and retrieves all data for use as a struct.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ruleconf">Rule configuration input</param>
|
|
||||||
/// <exception cref="RuleImportException>">
|
|
||||||
/// Thrown when encountering a missing or invalid value.
|
|
||||||
/// </exception>
|
|
||||||
public RuleConfig(JObject ruleconf)
|
|
||||||
{
|
|
||||||
// display name - validation should've been done outside this constructor already
|
|
||||||
_displayName = ruleconf["name"]?.Value<string>();
|
|
||||||
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<bool>();
|
|
||||||
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<Regex>();
|
|
||||||
var rxconf = ruleconf["regex"];
|
|
||||||
if (rxconf == null)
|
|
||||||
{
|
|
||||||
throw new RuleImportException(RegexError);
|
|
||||||
}
|
|
||||||
if (rxconf.Type == JTokenType.Array)
|
|
||||||
{
|
|
||||||
foreach (var input in rxconf.Values<string>())
|
|
||||||
{
|
|
||||||
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<string>();
|
|
||||||
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<int>();
|
|
||||||
_maxLength = ruleconf["max"]?.Value<int>();
|
|
||||||
}
|
|
||||||
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<string>();
|
|
||||||
var rsconf = ruleconf["response"];
|
|
||||||
if (rsconf == null)
|
|
||||||
{
|
|
||||||
throw new RuleImportException(ResponseError);
|
|
||||||
}
|
|
||||||
if (rsconf.Type == JTokenType.Array)
|
|
||||||
{
|
|
||||||
foreach (var input in rsconf.Values<string>()) responses.Add(input);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
responses.Add(rsconf.Value<string>());
|
|
||||||
}
|
|
||||||
// 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<bool>();
|
|
||||||
_modBypass = modoverride.HasValue ? modoverride.Value : true;
|
|
||||||
|
|
||||||
// embed matching mode
|
|
||||||
bool? embedmode = ruleconf["MatchEmbeds"]?.Value<bool>();
|
|
||||||
_matchEmbeds = (embedmode.HasValue && embedmode == true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue