Added BotFeature base class, implemented in RegexResponder
BotFeature is a new base class that all new individual bot features will derive from. At least one new feature is planned for this bot, and in time it may be opened up so external assemblies can be loaded. Full list of changes: -Added BotFeature and ConfigSectionAttribute classes -Renamed ConfigLoader to Configuration -Removed RegexResponder specific configuration data -Added per-feature configuration data storage -LoadInitialConfig() no longer loads all configuration -ReloadServerConfig() now loads remaining configuration, and allows for actual configuration reloading. Live configuration reloading is not an exposed feature yet. -Can now delegate feature-specific configuration loading to feature classes that make use of ConfigSectionAttribute -RegexResponder fully implements BotFeature -Rule configuration loading moved to RegexResponder -Logging output has changed slightly in regards to rule triggering and execution -Changed configuration load behavior on startup -Version pushed up to 1.0.0
This commit is contained in:
parent
0b70dca915
commit
27a25d90fc
10 changed files with 264 additions and 113 deletions
94
BotFeature.cs
Normal file
94
BotFeature.cs
Normal file
|
@ -0,0 +1,94 @@
|
|||
using Discord.WebSocket;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Noikoio.RegexBot
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for bot features
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This may have use in some sort of external plugin system later.
|
||||
/// </remarks>
|
||||
abstract class BotFeature
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly AsyncLogger _logger;
|
||||
|
||||
public abstract string Name { get; }
|
||||
|
||||
protected BotFeature(DiscordSocketClient client)
|
||||
{
|
||||
_client = client;
|
||||
_logger = Logger.GetLogger(this.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes feature-specific configuration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Feature code <i>should not</i> hold on to this data, but instead use <see cref="GetConfig{T}"/> to retrieve
|
||||
/// them. This is in the event that configuration is reverted to an earlier state and allows for the
|
||||
/// bot and all features to revert to previously used configuration values with no effort on the part
|
||||
/// of individual features.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// Processed configuration data prepared for later use.
|
||||
/// </returns>
|
||||
/// <exception cref="ConfigItem.RuleImportException">
|
||||
/// This method should throw RuleImportException in the event of any error.
|
||||
/// The exception message will be properly logged.
|
||||
/// </exception>
|
||||
public abstract Task<object> ProcessConfiguration(JToken configSection);
|
||||
|
||||
/// <summary>
|
||||
/// Gets this feature's relevant configuration data associated with the given Discord guild.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The stored configuration data, or null if none exists.
|
||||
/// </returns>
|
||||
protected object GetConfig(ulong guildId)
|
||||
{
|
||||
var sc = RegexBot.Config.Servers.FirstOrDefault(g => g.Id == guildId);
|
||||
if (sc == null)
|
||||
{
|
||||
throw new ArgumentException("There is no known configuration associated with the given Guild ID.");
|
||||
}
|
||||
|
||||
if (sc.FeatureConfigs.TryGetValue(this, out var item)) return item;
|
||||
else return null;
|
||||
}
|
||||
|
||||
protected async Task Log(string text)
|
||||
{
|
||||
await _logger(text);
|
||||
}
|
||||
|
||||
public sealed override bool Equals(object obj) => base.Equals(obj);
|
||||
public sealed override int GetHashCode() => base.GetHashCode();
|
||||
public sealed override string ToString() => base.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates which section under an individual Discord guild configuration should be passed to the
|
||||
/// feature's <see cref="BotFeature.ProcessConfiguration(JObject)"/> method during configuration load.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
||||
public class ConfigSectionAttribute : Attribute
|
||||
{
|
||||
private readonly string _sectionName;
|
||||
|
||||
public string SectionName => _sectionName;
|
||||
|
||||
public ConfigSectionAttribute(string sectionName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sectionName))
|
||||
{
|
||||
throw new ArgumentNullException("Configuration section name cannot be blank.");
|
||||
}
|
||||
_sectionName = sectionName;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -71,7 +71,7 @@ namespace Noikoio.RegexBot.ConfigItem
|
|||
if (_id.HasValue) return;
|
||||
_id = id;
|
||||
|
||||
var log = Logger.GetLogger(ConfigLoader.LogPrefix);
|
||||
var log = Logger.GetLogger(Configuration.LogPrefix);
|
||||
var thisstr = this.ToString();
|
||||
log(String.Format(
|
||||
"Suggestion: \"{0}\" may be written in configuration as \"{1}\"",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using Noikoio.RegexBot.Feature.RegexResponder;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Noikoio.RegexBot.ConfigItem
|
||||
|
@ -7,27 +7,27 @@ namespace Noikoio.RegexBot.ConfigItem
|
|||
/// <summary>
|
||||
/// Represents known information about a Discord guild (server) and other associated data
|
||||
/// </summary>
|
||||
class Server
|
||||
class ServerConfig
|
||||
{
|
||||
private readonly string _name;
|
||||
private ulong? _id;
|
||||
private IEnumerable<RuleConfig> _rules;
|
||||
private EntityList _moderators;
|
||||
private ReadOnlyDictionary<BotFeature, object> _featureData;
|
||||
|
||||
public string Name => _name;
|
||||
public ulong? Id {
|
||||
get => _id; set { if (!_id.HasValue) _id = value; }
|
||||
}
|
||||
public IEnumerable<RuleConfig> MatchResponseRules => _rules;
|
||||
public EntityList Moderators => _moderators;
|
||||
public ReadOnlyDictionary<BotFeature, object> FeatureConfigs => _featureData;
|
||||
|
||||
public Server(string name, ulong? id, IEnumerable<RuleConfig> rules, EntityList moderators)
|
||||
public ServerConfig(string name, ulong? id, EntityList moderators, ReadOnlyDictionary<BotFeature, object> featureconf)
|
||||
{
|
||||
_name = name;
|
||||
_id = id;
|
||||
_rules = rules;
|
||||
_moderators = moderators;
|
||||
Debug.Assert(_name != null && _rules != null && _moderators != null);
|
||||
_featureData = featureconf;
|
||||
Debug.Assert(_name != null && _moderators != null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Noikoio.RegexBot.ConfigItem;
|
||||
using Noikoio.RegexBot.Feature.RegexResponder;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
@ -14,22 +14,25 @@ namespace Noikoio.RegexBot
|
|||
/// <summary>
|
||||
/// Configuration loader
|
||||
/// </summary>
|
||||
class ConfigLoader
|
||||
class Configuration
|
||||
{
|
||||
public const string LogPrefix = "Config";
|
||||
|
||||
private readonly RegexBot _bot;
|
||||
private readonly string _configPath;
|
||||
private Server[] _servers;
|
||||
private ServerConfig[] _servers;
|
||||
|
||||
// The following values do not change on reload:
|
||||
private string _botToken;
|
||||
private string _currentGame;
|
||||
|
||||
public string BotUserToken => _botToken;
|
||||
public string CurrentGame => _currentGame;
|
||||
public Server[] Servers => _servers;
|
||||
public ServerConfig[] Servers => _servers;
|
||||
|
||||
public ConfigLoader()
|
||||
public Configuration(RegexBot bot)
|
||||
{
|
||||
_bot = bot;
|
||||
var dsc = Path.DirectorySeparatorChar;
|
||||
_configPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)
|
||||
+ dsc + "settings.json";
|
||||
|
@ -64,7 +67,7 @@ namespace Noikoio.RegexBot
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called only on bot startup. Returns false on failure.
|
||||
/// Loads essential, unchanging values needed for bot startup. Returns false on failure.
|
||||
/// </summary>
|
||||
public bool LoadInitialConfig()
|
||||
{
|
||||
|
@ -81,7 +84,7 @@ namespace Noikoio.RegexBot
|
|||
}
|
||||
_currentGame = conf["playing"]?.Value<string>();
|
||||
|
||||
return ProcessServerConfig(conf).GetAwaiter().GetResult();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -90,15 +93,10 @@ namespace Noikoio.RegexBot
|
|||
/// <returns>False on failure. Specific reasons will have been sent to log.</returns>
|
||||
public async Task<bool> ReloadServerConfig()
|
||||
{
|
||||
await Logger.GetLogger(LogPrefix)("Configuration reload currently not supported.");
|
||||
return false;
|
||||
// TODO actually implement this
|
||||
var lt = LoadFile();
|
||||
lt.Wait();
|
||||
JObject conf = lt.Result;
|
||||
if (conf == null) return false;
|
||||
var config = await LoadFile();
|
||||
if (config == null) return false;
|
||||
|
||||
return await ProcessServerConfig(conf);
|
||||
return await ProcessServerConfig(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -114,7 +112,7 @@ namespace Noikoio.RegexBot
|
|||
return false;
|
||||
}
|
||||
|
||||
List<Server> newservers = new List<Server>();
|
||||
List<ServerConfig> newservers = new List<ServerConfig>();
|
||||
await Log("Reading server configurations...");
|
||||
foreach (JObject sconf in conf["servers"].Children<JObject>())
|
||||
{
|
||||
|
@ -146,37 +144,43 @@ namespace Noikoio.RegexBot
|
|||
EntityList mods = new EntityList(sconf["moderators"]);
|
||||
if (sconf["moderators"] != null) await SLog("Moderator " + mods.ToString());
|
||||
|
||||
// Read rules
|
||||
// Also, parsed rules require a server reference. Creating it here.
|
||||
List<RuleConfig> rules = new List<RuleConfig>();
|
||||
Server newserver = new Server(sname, sid, rules, mods);
|
||||
|
||||
foreach (JObject ruleconf in sconf["rules"])
|
||||
// Load feature configurations
|
||||
Dictionary<BotFeature, object> customConfs = new Dictionary<BotFeature, object>();
|
||||
foreach (var item in _bot.Features)
|
||||
{
|
||||
// Try and get at least the name before passing it to RuleItem
|
||||
string name = ruleconf["name"]?.Value<string>();
|
||||
if (name == null)
|
||||
var attr = item.GetType().GetTypeInfo()
|
||||
.GetMethod("ProcessConfiguration").GetCustomAttribute<ConfigSectionAttribute>();
|
||||
if (attr == null)
|
||||
{
|
||||
await SLog("Display name not defined within a rule section.");
|
||||
return false;
|
||||
await SLog("No additional configuration for " + item.Name);
|
||||
continue;
|
||||
}
|
||||
var section = sconf[attr.SectionName];
|
||||
if (section == null)
|
||||
{
|
||||
await SLog("Additional configuration not defined for " + item.Name);
|
||||
continue;
|
||||
}
|
||||
await SLog($"Adding rule \"{name}\"");
|
||||
|
||||
RuleConfig rule;
|
||||
await SLog("Loading additional configuration for " + item.Name);
|
||||
object result;
|
||||
try
|
||||
{
|
||||
rule = new RuleConfig(newserver, ruleconf);
|
||||
} catch (RuleImportException ex)
|
||||
result = await item.ProcessConfiguration(section);
|
||||
}
|
||||
catch (RuleImportException ex)
|
||||
{
|
||||
await SLog("-> Error: " + ex.Message);
|
||||
await SLog($"{item.Name} failed to load configuration: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
rules.Add(rule);
|
||||
|
||||
customConfs.Add(item, result);
|
||||
}
|
||||
|
||||
|
||||
// Switch to using new data
|
||||
List<Tuple<Regex, string[]>> rulesfinal = new List<Tuple<Regex, string[]>>();
|
||||
newservers.Add(newserver);
|
||||
newservers.Add(new ServerConfig(sname, sid, mods, new ReadOnlyDictionary<BotFeature, object>(customConfs)));
|
||||
}
|
||||
|
||||
_servers = newservers.ToArray();
|
|
@ -7,6 +7,7 @@ using System.Collections.ObjectModel;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Noikoio.RegexBot.Feature.RegexResponder
|
||||
{
|
||||
|
@ -14,15 +15,15 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
/// Implements per-message regex matching and executes customizable responses.
|
||||
/// Namesake of this project.
|
||||
/// </summary>
|
||||
partial class EventProcessor
|
||||
partial class EventProcessor : BotFeature
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ConfigLoader _conf;
|
||||
|
||||
public EventProcessor(DiscordSocketClient client, ConfigLoader conf)
|
||||
public override string Name => "RegexResponder";
|
||||
|
||||
public EventProcessor(DiscordSocketClient client) : base(client)
|
||||
{
|
||||
_client = client;
|
||||
_conf = conf;
|
||||
|
||||
_client.MessageReceived += OnMessageReceived;
|
||||
_client.MessageUpdated += OnMessageUpdated;
|
||||
|
@ -58,12 +59,16 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
/// </summary>
|
||||
private async Task ReceiveMessage(SocketMessage arg)
|
||||
{
|
||||
if (arg.Author == _client.CurrentUser) return;
|
||||
// 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 = ((SocketGuildUser)arg.Author).Guild;
|
||||
Server sd = null;
|
||||
foreach (var item in _conf.Servers)
|
||||
SocketGuild g = ch.Guild;
|
||||
ServerConfig sd = null;
|
||||
foreach (var item in RegexBot.Config.Servers)
|
||||
{
|
||||
if (item.Id.HasValue)
|
||||
{
|
||||
|
@ -81,7 +86,7 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
{
|
||||
item.Id = g.Id;
|
||||
sd = item;
|
||||
await Logger.GetLogger(ConfigLoader.LogPrefix)
|
||||
await Logger.GetLogger(Configuration.LogPrefix)
|
||||
($"Suggestion: Server \"{item.Name}\" can be defined as \"{item.Id}::{item.Name}\"");
|
||||
break;
|
||||
}
|
||||
|
@ -89,9 +94,11 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
}
|
||||
|
||||
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 sd.MatchResponseRules)
|
||||
foreach (var rule in rules)
|
||||
await Task.Run(async () => await ProcessMessage(sd, rule, arg));
|
||||
}
|
||||
|
||||
|
@ -99,7 +106,7 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
/// 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(Server srv, RuleConfig rule, SocketMessage msg)
|
||||
private async Task ProcessMessage(ServerConfig srv, RuleConfig rule, SocketMessage msg)
|
||||
{
|
||||
string msgcontent;
|
||||
|
||||
|
@ -134,8 +141,7 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
if (!success) return;
|
||||
|
||||
// Prepare to execute responses
|
||||
var log = Logger.GetLogger(rule.DisplayName);
|
||||
await log($"Triggered in {srv.Name}/#{msg.Channel} by {msg.Author.ToString()}");
|
||||
await Log($"\"{rule.DisplayName}\" triggered in {srv.Name}/#{msg.Channel} by {msg.Author.ToString()}");
|
||||
|
||||
foreach (string rcmd in rule.Responses)
|
||||
{
|
||||
|
@ -145,19 +151,52 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
ResponseProcessor response;
|
||||
if (!_commands.TryGetValue(cmd, out response))
|
||||
{
|
||||
await log($"Unknown command \"{cmd}\"");
|
||||
await Log($"Unknown command defined in response: \"{cmd}\"");
|
||||
continue;
|
||||
}
|
||||
await response.Invoke(log, rcmd, rule, msg);
|
||||
await response.Invoke(rcmd, rule, msg);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await log($"Encountered an error while processing \"{cmd}\"");
|
||||
await log(ex.ToString());
|
||||
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>
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
// Contains code for handling each response in a rule.
|
||||
partial class EventProcessor
|
||||
{
|
||||
private delegate Task ResponseProcessor(AsyncLogger l, string cmd, RuleConfig r, SocketMessage m);
|
||||
private delegate Task ResponseProcessor(string cmd, RuleConfig r, SocketMessage m);
|
||||
private readonly ReadOnlyDictionary<string, ResponseProcessor> _commands;
|
||||
|
||||
#if DEBUG
|
||||
|
@ -19,9 +19,8 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
/// Throws an exception. Meant to be a quick error handling test.
|
||||
/// No parameters.
|
||||
/// </summary>
|
||||
private async Task RP_Crash(AsyncLogger l, string cmd, RuleConfig r, SocketMessage m)
|
||||
private Task RP_Crash(string cmd, RuleConfig r, SocketMessage m)
|
||||
{
|
||||
await l("Will throw an exception.");
|
||||
throw new Exception("Requested in response.");
|
||||
}
|
||||
|
||||
|
@ -30,7 +29,7 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
/// The guild info displayed is the one in which the command is invoked.
|
||||
/// No parameters.
|
||||
/// </summary>
|
||||
private Task RP_DumpID(AsyncLogger l, string cmd, RuleConfig r, SocketMessage m)
|
||||
private Task RP_DumpID(string cmd, RuleConfig r, SocketMessage m)
|
||||
{
|
||||
var g = ((SocketGuildUser)m.Author).Guild;
|
||||
var result = new StringBuilder();
|
||||
|
@ -55,19 +54,19 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
/// Sends a message to a specified channel.
|
||||
/// Parameters: say (channel) (message)
|
||||
/// </summary>
|
||||
private async Task RP_Say(AsyncLogger l, string cmd, RuleConfig r, SocketMessage m)
|
||||
private async Task RP_Say(string cmd, RuleConfig r, SocketMessage m)
|
||||
{
|
||||
string[] @in = SplitParams(cmd, 3);
|
||||
if (@in.Length != 3)
|
||||
{
|
||||
await l("Error: say: Incorrect number of parameters.");
|
||||
await Log("Error: say: Incorrect number of parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
var target = await GetMessageTargetAsync(@in[1], m);
|
||||
if (target == null)
|
||||
{
|
||||
await l("Error: say: Unable to resolve given target.");
|
||||
await Log("Error: say: Unable to resolve given target.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -80,19 +79,19 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
/// Reports the incoming message to a given channel.
|
||||
/// Parameters: report (channel)
|
||||
/// </summary>
|
||||
private async Task RP_Report(AsyncLogger l, string cmd, RuleConfig r, SocketMessage m)
|
||||
private async Task RP_Report(string cmd, RuleConfig r, SocketMessage m)
|
||||
{
|
||||
string[] @in = SplitParams(cmd);
|
||||
if (@in.Length != 2)
|
||||
{
|
||||
await l("Error: report: Incorrect number of parameters.");
|
||||
await Log("Error: report: Incorrect number of parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
var target = await GetMessageTargetAsync(@in[1], m);
|
||||
if (target == null)
|
||||
{
|
||||
await l("Error: report: Unable to resolve given target.");
|
||||
await Log("Error: report: Unable to resolve given target.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -136,7 +135,7 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
/// Deletes the incoming message.
|
||||
/// No parameters.
|
||||
/// </summary>
|
||||
private async Task RP_Remove(AsyncLogger l, string cmd, RuleConfig r, SocketMessage m)
|
||||
private async Task RP_Remove(string cmd, RuleConfig r, SocketMessage m)
|
||||
{
|
||||
// Parameters are not checked
|
||||
await m.DeleteAsync();
|
||||
|
@ -146,19 +145,19 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
/// Executes an external program and sends standard output to the given channel.
|
||||
/// Parameters: exec (channel) (command line)
|
||||
/// </summary>
|
||||
private async Task RP_Exec(AsyncLogger l, string cmd, RuleConfig r, SocketMessage m)
|
||||
private async Task RP_Exec(string cmd, RuleConfig r, SocketMessage m)
|
||||
{
|
||||
var @in = SplitParams(cmd, 4);
|
||||
if (@in.Length < 3)
|
||||
{
|
||||
await l("exec: Incorrect number of parameters.");
|
||||
await Log("exec: Incorrect number of parameters.");
|
||||
}
|
||||
|
||||
string result;
|
||||
var target = await GetMessageTargetAsync(@in[1], m);
|
||||
if (target == null)
|
||||
{
|
||||
await l("Error: exec: Unable to resolve given channel.");
|
||||
await Log("Error: exec: Unable to resolve given channel.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -174,7 +173,7 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
p.WaitForExit(5000); // waiting at most 5 seconds
|
||||
if (p.HasExited)
|
||||
{
|
||||
if (p.ExitCode != 0) await l("exec: Process returned exit code " + p.ExitCode);
|
||||
if (p.ExitCode != 0) await Log("exec: Process returned exit code " + p.ExitCode);
|
||||
using (var stdout = p.StandardOutput)
|
||||
{
|
||||
result = await stdout.ReadToEndAsync();
|
||||
|
@ -182,7 +181,7 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
}
|
||||
else
|
||||
{
|
||||
await l("exec: Process is taking too long to exit. Killing process.");
|
||||
await Log("exec: Process is taking too long to exit. Killing process.");
|
||||
p.Kill();
|
||||
return;
|
||||
}
|
||||
|
@ -197,7 +196,7 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
/// No parameters.
|
||||
/// </summary>
|
||||
// TODO add parameter for message auto-deleting
|
||||
private async Task RP_Ban(AsyncLogger l, string cmd, RuleConfig r, SocketMessage m)
|
||||
private async Task RP_Ban(string cmd, RuleConfig r, SocketMessage m)
|
||||
{
|
||||
SocketGuild g = ((SocketGuildUser)m.Author).Guild;
|
||||
await g.AddBanAsync(m.Author);
|
||||
|
@ -207,17 +206,17 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
/// 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(AsyncLogger l, string cmd, RuleConfig r, SocketMessage m)
|
||||
private async Task RP_GrantRevokeRole(string cmd, RuleConfig r, SocketMessage m)
|
||||
{
|
||||
string[] @in = SplitParams(cmd);
|
||||
if (@in.Length != 3)
|
||||
{
|
||||
await l($"Error: {@in[0]}: incorrect number of parameters.");
|
||||
await Log($"Error: {@in[0]}: incorrect number of parameters.");
|
||||
return;
|
||||
}
|
||||
if (!ulong.TryParse(@in[2], out var roleID))
|
||||
{
|
||||
await l($"Error: {@in[0]}: Invalid role ID specified.");
|
||||
await Log($"Error: {@in[0]}: Invalid role ID specified.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -226,7 +225,7 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
SocketRole rl = gu.Guild.GetRole(roleID);
|
||||
if (rl == null)
|
||||
{
|
||||
await l($"Error: {@in[0]}: Specified role not found.");
|
||||
await Log($"Error: {@in[0]}: Specified role not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -240,13 +239,13 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
{
|
||||
if (!ulong.TryParse(@in[1], out var userID))
|
||||
{
|
||||
await l($"Error: {@in[0]}: Invalid user ID specified.");
|
||||
await Log($"Error: {@in[0]}: Invalid user ID specified.");
|
||||
return;
|
||||
}
|
||||
target = gu.Guild.GetUser(userID);
|
||||
if (target == null)
|
||||
{
|
||||
await l($"Error: {@in[0]}: Given user ID does not exist in this server.");
|
||||
await Log($"Error: {@in[0]}: Given user ID does not exist in this server.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
internal struct RuleConfig
|
||||
{
|
||||
private string _displayName;
|
||||
private Server _server;
|
||||
private IEnumerable<Regex> _regex;
|
||||
private IEnumerable<string> _responses;
|
||||
private FilterType _filtermode;
|
||||
|
@ -25,7 +24,6 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
private bool _matchEmbeds;
|
||||
|
||||
public string DisplayName => _displayName;
|
||||
public Server Server => _server;
|
||||
public IEnumerable<Regex> Regex => _regex;
|
||||
public IEnumerable<string> Responses => _responses;
|
||||
public FilterType FilterMode => _filtermode;
|
||||
|
@ -43,10 +41,8 @@ namespace Noikoio.RegexBot.Feature.RegexResponder
|
|||
/// <exception cref="RuleImportException>">
|
||||
/// Thrown when encountering a missing or invalid value.
|
||||
/// </exception>
|
||||
public RuleConfig(Server serverref, JObject ruleconf)
|
||||
public RuleConfig(JObject ruleconf)
|
||||
{
|
||||
_server = serverref;
|
||||
|
||||
// display name - validation should've been done outside this constructor already
|
||||
_displayName = ruleconf["name"]?.Value<string>();
|
||||
if (_displayName == null)
|
||||
|
|
13
Program.cs
13
Program.cs
|
@ -10,18 +10,7 @@ namespace Noikoio.RegexBot
|
|||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// Attempt to load basic configuration before setting up the client
|
||||
var config = new ConfigLoader();
|
||||
if (!config.LoadInitialConfig())
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine("Press any key to exit.");
|
||||
Console.ReadKey();
|
||||
#endif
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
RegexBot rb = new RegexBot(config);
|
||||
RegexBot rb = new RegexBot();
|
||||
|
||||
Console.CancelKeyPress += rb.Console_CancelKeyPress;
|
||||
//AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
|
|
52
RegexBot.cs
52
RegexBot.cs
|
@ -1,6 +1,7 @@
|
|||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Noikoio.RegexBot
|
||||
|
@ -10,34 +11,63 @@ namespace Noikoio.RegexBot
|
|||
/// </summary>
|
||||
class RegexBot
|
||||
{
|
||||
private readonly ConfigLoader _config;
|
||||
private static Configuration _config;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private BotFeature[] _features;
|
||||
|
||||
// Constructor loads all subsystems. Subsystem constructors hook up their event delegates.
|
||||
internal RegexBot(ConfigLoader conf)
|
||||
internal static Configuration Config => _config;
|
||||
internal IEnumerable<BotFeature> Features => _features;
|
||||
|
||||
internal RegexBot()
|
||||
{
|
||||
// Load configuration
|
||||
_config = new Configuration(this);
|
||||
if (!_config.LoadInitialConfig())
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine("Press any key to exit.");
|
||||
Console.ReadKey();
|
||||
#endif
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
_client = new DiscordSocketClient(new DiscordSocketConfig()
|
||||
{
|
||||
LogLevel = LogSeverity.Info,
|
||||
DefaultRetryMode = RetryMode.AlwaysRetry,
|
||||
MessageCacheSize = 50
|
||||
});
|
||||
_config = conf;
|
||||
|
||||
// Hook up handlers for basic functions
|
||||
_client.Connected += _client_Connected;
|
||||
|
||||
// Initialize features
|
||||
new Feature.RegexResponder.EventProcessor(_client, _config);
|
||||
_features = new BotFeature[]
|
||||
{
|
||||
new Feature.RegexResponder.EventProcessor(_client)
|
||||
};
|
||||
var dlog = Logger.GetLogger("Discord.Net");
|
||||
_client.Log += async (arg) =>
|
||||
await dlog(
|
||||
String.Format("{0}: {1}{2}", arg.Source, ((int)arg.Severity < 3 ? arg.Severity + ": " : ""),
|
||||
arg.Message));
|
||||
|
||||
// With features initialized, finish loading configuration
|
||||
if (!_config.ReloadServerConfig().GetAwaiter().GetResult())
|
||||
{
|
||||
Console.WriteLine("Failed to load server configuration.");
|
||||
#if DEBUG
|
||||
Console.WriteLine("Press any key to exit.");
|
||||
Console.ReadKey();
|
||||
#endif
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task Start()
|
||||
{
|
||||
var dlog = Logger.GetLogger("Discord");
|
||||
_client.Log += async (arg) =>
|
||||
await dlog(String.Format("{0}: {1}{2}",
|
||||
arg.Source, ((int)arg.Severity < 3 ? arg.Severity + ": " : ""), arg.Message));
|
||||
await _client.LoginAsync(TokenType.Bot, _config.BotUserToken);
|
||||
|
||||
await _client.LoginAsync(TokenType.Bot, Config.BotUserToken);
|
||||
await _client.StartAsync();
|
||||
|
||||
await Task.Delay(-1);
|
||||
|
@ -45,7 +75,7 @@ namespace Noikoio.RegexBot
|
|||
|
||||
private async Task _client_Connected()
|
||||
{
|
||||
await _client.SetGameAsync(_config.CurrentGame);
|
||||
await _client.SetGameAsync(Config.CurrentGame);
|
||||
// TODO add support for making use of server invites somewhere around here
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<RootNamespace>Noikoio.RegexBot</RootNamespace>
|
||||
<AssemblyVersion>0.15.0.0</AssemblyVersion>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<Description>Highly configurable Discord moderation bot</Description>
|
||||
<Authors>Noikoio</Authors>
|
||||
<Company />
|
||||
|
|
Loading…
Reference in a new issue