diff --git a/BotModule.cs b/BotModule.cs
index 7983dca..808eb25 100644
--- a/BotModule.cs
+++ b/BotModule.cs
@@ -24,37 +24,35 @@ namespace Noikoio.RegexBot
}
///
- /// Processes module-specific configuration.
- /// This method is not called if the user did not provide configuration for the module.
+ /// This method is called on each module when configuration is (re)loaded.
+ /// The module is expected to use this opportunity to set up an object that will hold state data
+ /// for a particular guild, using the incoming configuration object as needed in order to do so.
///
///
- /// Module code should not hold on to this data, but instead use to retrieve
- /// them. This is in the event that configuration is reverted to an earlier state and allows for the
- /// all modules to revert to previously used configuration values with no effort on the part of the
- /// module code itself.
+ /// Module code should not hold on state or configuration data on its own, but instead use
+ /// to retrieve its state object. This is to provide the user
+ /// with the ability to maintain the current bot state in the event that a configuration reload fails.
///
- ///
- /// Processed configuration data prepared for later use.
- ///
- ///
- /// This method should throw
- /// in the event of configuration errors. The exception message will be properly displayed.
- ///
- public abstract Task ProcessConfiguration(JToken configSection);
+ ///
+ /// Configuration data for this module, for this guild. Is null if none was defined.
+ ///
+ /// An object that may later be retrieved by .
+ public virtual Task CreateInstanceState(JToken configSection) => Task.FromResult(null);
///
- /// Gets this module's relevant configuration data associated with the given Discord guild.
+ /// Retrieves this module's relevant state data associated with the given Discord guild.
///
///
- /// The stored configuration data, or null if none exists.
+ /// The stored state data, or null/default if none exists.
///
- protected object GetConfig(ulong guildId)
+ protected T GetState(ulong guildId)
{
+ // TODO investigate if locking may be necessary
var sc = RegexBot.Config.Servers.FirstOrDefault(g => g.Id == guildId);
- if (sc == null) return null;
+ if (sc == null) return default(T);
- if (sc.ModuleConfigs.TryGetValue(this, out var item)) return item;
- else return null;
+ if (sc.ModuleConfigs.TryGetValue(this, out var item)) return (T)item;
+ else return default(T);
}
///
diff --git a/Configuration.cs b/Configuration.cs
index f371e6c..8fee47f 100644
--- a/Configuration.cs
+++ b/Configuration.cs
@@ -158,31 +158,30 @@ namespace Noikoio.RegexBot
EntityList mods = new EntityList(sconf["moderators"]);
if (sconf["moderators"] != null) await SLog("Moderator " + mods.ToString());
- // Load module configurations
+ // Set up module state / load configurations
Dictionary customConfs = new Dictionary();
foreach (var item in _bot.Modules)
{
var confSection = item.Name;
var section = sconf[confSection];
- if (section == null)
- {
- // Section not in config. Do not call loader method.
- await SLog("Additional configuration not defined for " + item.Name);
- continue;
- }
-
- await SLog("Loading additional configuration for " + item.Name);
+ await SLog("Setting up " + item.Name);
object result;
try
{
- result = await item.ProcessConfiguration(section);
+ result = await item.CreateInstanceState(section);
}
catch (RuleImportException ex)
{
await SLog($"{item.Name} failed to load configuration: " + ex.Message);
return false;
}
+ catch (Exception ex)
+ {
+ await SLog("Encountered unhandled exception:");
+ await SLog(ex.ToString());
+ return false;
+ }
customConfs.Add(item, result);
}
diff --git a/EntityCache/Module.cs b/EntityCache/Module.cs
index c4945ab..845dd87 100644
--- a/EntityCache/Module.cs
+++ b/EntityCache/Module.cs
@@ -32,8 +32,6 @@ namespace Noikoio.RegexBot.EntityCache
Log("No database storage available.").Wait();
}
}
-
- public override Task ProcessConfiguration(JToken configSection) => Task.FromResult(null);
// Guild and guild member information has become available.
// This is a very expensive operation, especially when joining larger guilds.
diff --git a/Module/AutoMod/AutoMod.cs b/Module/AutoMod/AutoMod.cs
index 88c4b14..4e48525 100644
--- a/Module/AutoMod/AutoMod.cs
+++ b/Module/AutoMod/AutoMod.cs
@@ -22,8 +22,9 @@ namespace Noikoio.RegexBot.Module.AutoMod
client.MessageUpdated += CMessageUpdated;
}
- public override async Task ProcessConfiguration(JToken configSection)
+ public override async Task CreateInstanceState(JToken configSection)
{
+ if (configSection == null) return null;
List rules = new List();
foreach (var def in configSection.Children())
@@ -52,7 +53,7 @@ namespace Noikoio.RegexBot.Module.AutoMod
if (ch == null) return;
// Get rules
- var rules = GetConfig(ch.Guild.Id) as IEnumerable;
+ var rules = GetState>(ch.Guild.Id);
if (rules == null) return;
foreach (var rule in rules)
diff --git a/Module/AutoRespond/AutoRespond.cs b/Module/AutoRespond/AutoRespond.cs
index e5cfb5a..e9baceb 100644
--- a/Module/AutoRespond/AutoRespond.cs
+++ b/Module/AutoRespond/AutoRespond.cs
@@ -33,15 +33,16 @@ namespace Noikoio.RegexBot.Module.AutoRespond
var ch = arg.Channel as SocketGuildChannel;
if (ch == null) return;
- var defs = GetConfig(ch.Guild.Id) as IEnumerable;
+ var defs = GetState>(ch.Guild.Id);
if (defs == null) return;
foreach (var def in defs)
await Task.Run(async () => await ProcessMessage(arg, def));
}
- public override async Task ProcessConfiguration(JToken configSection)
+ public override async Task CreateInstanceState(JToken configSection)
{
+ if (configSection == null) return null;
var responses = new List();
foreach (var def in configSection.Children())
{
diff --git a/Module/DMLogger/DMLogger.cs b/Module/DMLogger/DMLogger.cs
index 66bde53..2599b9d 100644
--- a/Module/DMLogger/DMLogger.cs
+++ b/Module/DMLogger/DMLogger.cs
@@ -1,6 +1,5 @@
using Discord;
using Discord.WebSocket;
-using Newtonsoft.Json.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -34,8 +33,6 @@ namespace Noikoio.RegexBot.Module.DMLogger
await ProcessMessage(arg2, true);
}
- public override Task ProcessConfiguration(JToken configSection) => Task.FromResult(null);
-
private async Task ProcessMessage(SocketMessage arg, bool edited)
{
var result = new StringBuilder();
diff --git a/Module/EntryAutoRole/EntryAutoRole.cs b/Module/EntryAutoRole/EntryAutoRole.cs
index 19b5bec..22e09e6 100644
--- a/Module/EntryAutoRole/EntryAutoRole.cs
+++ b/Module/EntryAutoRole/EntryAutoRole.cs
@@ -36,8 +36,9 @@ namespace Noikoio.RegexBot.Module.EntryAutoRole
Task.Factory.StartNew(Worker, _workerCancel.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
- public override Task ProcessConfiguration(JToken configSection)
+ public override Task CreateInstanceState(JToken configSection)
{
+ if (configSection == null) return Task.FromResult(null);
if (configSection.Type != JTokenType.Object)
{
throw new RuleImportException("Configuration for this section is invalid.");
@@ -47,7 +48,7 @@ namespace Noikoio.RegexBot.Module.EntryAutoRole
private Task Client_GuildAvailable(SocketGuild arg)
{
- var conf = (ModuleConfig)GetConfig(arg.Id);
+ var conf = GetState(arg.Id);
if (conf == null) return Task.CompletedTask;
SocketRole trole = GetRole(arg);
if (trole == null) return Task.CompletedTask;
@@ -71,19 +72,19 @@ namespace Noikoio.RegexBot.Module.EntryAutoRole
private Task Client_UserLeft(SocketGuildUser arg)
{
- if (GetConfig(arg.Guild.Id) == null) return Task.CompletedTask;
+ if (GetState(arg.Guild.Id) == null) return Task.CompletedTask;
lock (_roleWaitLock) _roleWaitlist.RemoveAll(m => m.GuildId == arg.Guild.Id && m.UserId == arg.Id);
return Task.CompletedTask;
}
private Task Client_UserJoined(SocketGuildUser arg)
{
- if (GetConfig(arg.Guild.Id) == null) return Task.CompletedTask;
+ if (GetState(arg.Guild.Id) == null) return Task.CompletedTask;
lock (_roleWaitLock) _roleWaitlist.Add(new AutoRoleEntry()
{
GuildId = arg.Guild.Id,
UserId = arg.Id,
- ExpireTime = DateTimeOffset.UtcNow.AddSeconds(((ModuleConfig)GetConfig(arg.Guild.Id)).TimeDelay)
+ ExpireTime = DateTimeOffset.UtcNow.AddSeconds((GetState(arg.Guild.Id)).TimeDelay)
});
return Task.CompletedTask;
}
@@ -91,7 +92,7 @@ namespace Noikoio.RegexBot.Module.EntryAutoRole
// can return null
private SocketRole GetRole(SocketGuild g)
{
- var conf = (ModuleConfig)GetConfig(g.Id);
+ var conf = GetState(g.Id);
if (conf == null) return null;
var roleInfo = conf.Role;
diff --git a/Module/ModCommands/ModCommands.cs b/Module/ModCommands/ModCommands.cs
index 82e4773..d63a1b9 100644
--- a/Module/ModCommands/ModCommands.cs
+++ b/Module/ModCommands/ModCommands.cs
@@ -32,8 +32,10 @@ namespace Noikoio.RegexBot.Module.ModCommands
if (arg.Channel is IGuildChannel) await CommandCheckInvoke(arg);
}
- public override async Task ProcessConfiguration(JToken configSection)
+ public override async Task CreateInstanceState(JToken configSection)
{
+ if (configSection == null) return null;
+
// Constructor throws exception on config errors
var conf = new ConfigItem(this, configSection);
@@ -44,8 +46,6 @@ namespace Noikoio.RegexBot.Module.ModCommands
return conf;
}
- private new ConfigItem GetConfig(ulong guildId) => (ConfigItem)base.GetConfig(guildId);
-
public new Task Log(string text) => base.Log(text);
private async Task CommandCheckInvoke(SocketMessage arg)
@@ -67,7 +67,7 @@ namespace Noikoio.RegexBot.Module.ModCommands
int spc = arg.Content.IndexOf(' ');
if (spc != -1) cmdchk = arg.Content.Substring(0, spc);
else cmdchk = arg.Content;
- if (GetConfig(g.Id).Commands.TryGetValue(cmdchk, out var c))
+ if (GetState(g.Id).Commands.TryGetValue(cmdchk, out var c))
{
try
{
diff --git a/Module/ModLogs/MessageCache.cs b/Module/ModLogs/MessageCache.cs
index 6ab2dfb..8b75462 100644
--- a/Module/ModLogs/MessageCache.cs
+++ b/Module/ModLogs/MessageCache.cs
@@ -17,11 +17,11 @@ namespace Noikoio.RegexBot.Module.ModLogs
{
private readonly DiscordSocketClient _dClient;
private readonly AsyncLogger _outLog;
- private readonly Func _outGetConfig;
+ private readonly Func _outGetConfig;
// TODO: How to clear the cache after a time? Can't hold on to this forever.
- public MessageCache(DiscordSocketClient client, AsyncLogger logger, Func getConfFunc)
+ public MessageCache(DiscordSocketClient client, AsyncLogger logger, Func getConfFunc)
{
_dClient = client;
_outLog = logger;
@@ -85,7 +85,7 @@ namespace Noikoio.RegexBot.Module.ModLogs
else return;
// Check if this feature is enabled before doing anything else.
- var cfg = _outGetConfig(guildId) as GuildConfig;
+ var cfg = _outGetConfig(guildId);
if (cfg == null) return;
if (isDelete && (cfg.RptTypes & EventType.MsgDelete) == 0) return;
if (!isDelete && (cfg.RptTypes & EventType.MsgEdit) == 0) return;
diff --git a/Module/ModLogs/ModLogs.cs b/Module/ModLogs/ModLogs.cs
index 7f4c130..7ae1136 100644
--- a/Module/ModLogs/ModLogs.cs
+++ b/Module/ModLogs/ModLogs.cs
@@ -19,14 +19,15 @@ namespace Noikoio.RegexBot.Module.ModLogs
if (!RegexBot.Config.DatabaseAvailable) return;
// MessageCache (reporting of MessageEdit, MessageDelete) handled by helper class
- _msgCacheInstance = new MessageCache(client, Log, GetConfig);
+ _msgCacheInstance = new MessageCache(client, Log, delegate (ulong id) { return GetState(id); });
// TODO add handlers for detecting joins, leaves, bans, kicks, user edits (nick/username/discr)
// TODO add handler for processing the log query command
}
- public override async Task ProcessConfiguration(JToken configSection)
+ public override async Task CreateInstanceState(JToken configSection)
{
+ if (configSection == null) return null;
if (configSection.Type != JTokenType.Object)
throw new RuleImportException("Configuration for this section is invalid.");