Changed module configuration model
-Changed a few method names to better reflect its capabilities. --Specifically, references to "Config" were renamed to "State". -Changed the way that what is now CreateInstanceState is called --It is now always called instead of only when configuration exists --A basic null check was added to prevent issues resulting from this --General exception handler added to configuration loader
This commit is contained in:
parent
287bb33d77
commit
1773fd2fc2
10 changed files with 50 additions and 54 deletions
38
BotModule.cs
38
BotModule.cs
|
@ -24,37 +24,35 @@ namespace Noikoio.RegexBot
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Module code <i>should not</i> hold on to this data, but instead use <see cref="GetConfig(ulong)"/> 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 <i>should not</i> hold on state or configuration data on its own, but instead use
|
||||
/// <see cref="GetState{T}(ulong)"/> 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.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// Processed configuration data prepared for later use.
|
||||
/// </returns>
|
||||
/// <exception cref="ConfigItem.RuleImportException">
|
||||
/// This method should throw <see cref="ConfigItem.RuleImportException"/>
|
||||
/// in the event of configuration errors. The exception message will be properly displayed.
|
||||
/// </exception>
|
||||
public abstract Task<object> ProcessConfiguration(JToken configSection);
|
||||
/// <param name="configSection">
|
||||
/// Configuration data for this module, for this guild. Is null if none was defined.
|
||||
/// </param>
|
||||
/// <returns>An object that may later be retrieved by <see cref="GetState{T}(ulong)"/>.</returns>
|
||||
public virtual Task<object> CreateInstanceState(JToken configSection) => Task.FromResult<object>(null);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The stored configuration data, or null if none exists.
|
||||
/// The stored state data, or null/default if none exists.
|
||||
/// </returns>
|
||||
protected object GetConfig(ulong guildId)
|
||||
protected T GetState<T>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -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<BotModule, object> customConfs = new Dictionary<BotModule, object>();
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -33,8 +33,6 @@ namespace Noikoio.RegexBot.EntityCache
|
|||
}
|
||||
}
|
||||
|
||||
public override Task<object> ProcessConfiguration(JToken configSection) => Task.FromResult<object>(null);
|
||||
|
||||
// Guild and guild member information has become available.
|
||||
// This is a very expensive operation, especially when joining larger guilds.
|
||||
private async Task Client_GuildAvailable(SocketGuild arg)
|
||||
|
|
|
@ -22,8 +22,9 @@ namespace Noikoio.RegexBot.Module.AutoMod
|
|||
client.MessageUpdated += CMessageUpdated;
|
||||
}
|
||||
|
||||
public override async Task<object> ProcessConfiguration(JToken configSection)
|
||||
public override async Task<object> CreateInstanceState(JToken configSection)
|
||||
{
|
||||
if (configSection == null) return null;
|
||||
List<ConfigItem> rules = new List<ConfigItem>();
|
||||
|
||||
foreach (var def in configSection.Children<JProperty>())
|
||||
|
@ -52,7 +53,7 @@ namespace Noikoio.RegexBot.Module.AutoMod
|
|||
if (ch == null) return;
|
||||
|
||||
// Get rules
|
||||
var rules = GetConfig(ch.Guild.Id) as IEnumerable<ConfigItem>;
|
||||
var rules = GetState<IEnumerable<ConfigItem>>(ch.Guild.Id);
|
||||
if (rules == null) return;
|
||||
|
||||
foreach (var rule in rules)
|
||||
|
|
|
@ -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<ConfigItem>;
|
||||
var defs = GetState<IEnumerable<ConfigItem>>(ch.Guild.Id);
|
||||
if (defs == null) return;
|
||||
|
||||
foreach (var def in defs)
|
||||
await Task.Run(async () => await ProcessMessage(arg, def));
|
||||
}
|
||||
|
||||
public override async Task<object> ProcessConfiguration(JToken configSection)
|
||||
public override async Task<object> CreateInstanceState(JToken configSection)
|
||||
{
|
||||
if (configSection == null) return null;
|
||||
var responses = new List<ConfigItem>();
|
||||
foreach (var def in configSection.Children<JProperty>())
|
||||
{
|
||||
|
|
|
@ -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<object> ProcessConfiguration(JToken configSection) => Task.FromResult<object>(null);
|
||||
|
||||
private async Task ProcessMessage(SocketMessage arg, bool edited)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
|
|
|
@ -36,8 +36,9 @@ namespace Noikoio.RegexBot.Module.EntryAutoRole
|
|||
Task.Factory.StartNew(Worker, _workerCancel.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||
}
|
||||
|
||||
public override Task<object> ProcessConfiguration(JToken configSection)
|
||||
public override Task<object> CreateInstanceState(JToken configSection)
|
||||
{
|
||||
if (configSection == null) return Task.FromResult<object>(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<ModuleConfig>(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<object>(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<object>(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<ModuleConfig>(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<ModuleConfig>(g.Id);
|
||||
if (conf == null) return null;
|
||||
var roleInfo = conf.Role;
|
||||
|
||||
|
|
|
@ -32,8 +32,10 @@ namespace Noikoio.RegexBot.Module.ModCommands
|
|||
if (arg.Channel is IGuildChannel) await CommandCheckInvoke(arg);
|
||||
}
|
||||
|
||||
public override async Task<object> ProcessConfiguration(JToken configSection)
|
||||
public override async Task<object> 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<ConfigItem>(g.Id).Commands.TryGetValue(cmdchk, out var c))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
@ -17,11 +17,11 @@ namespace Noikoio.RegexBot.Module.ModLogs
|
|||
{
|
||||
private readonly DiscordSocketClient _dClient;
|
||||
private readonly AsyncLogger _outLog;
|
||||
private readonly Func<ulong, object> _outGetConfig;
|
||||
private readonly Func<ulong, GuildConfig> _outGetConfig;
|
||||
|
||||
// TODO: How to clear the cache after a time? Can't hold on to this forever.
|
||||
|
||||
public MessageCache(DiscordSocketClient client, AsyncLogger logger, Func<ulong, object> getConfFunc)
|
||||
public MessageCache(DiscordSocketClient client, AsyncLogger logger, Func<ulong, GuildConfig> 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;
|
||||
|
|
|
@ -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<GuildConfig>(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<object> ProcessConfiguration(JToken configSection)
|
||||
public override async Task<object> CreateInstanceState(JToken configSection)
|
||||
{
|
||||
if (configSection == null) return null;
|
||||
if (configSection.Type != JTokenType.Object)
|
||||
throw new RuleImportException("Configuration for this section is invalid.");
|
||||
|
||||
|
|
Loading…
Reference in a new issue