Minor improvements throughout
This commit is contained in:
parent
4ca38fa881
commit
9bd9a21531
6 changed files with 65 additions and 58 deletions
|
@ -12,47 +12,36 @@ namespace Kerobot
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class Kerobot
|
public partial class Kerobot
|
||||||
{
|
{
|
||||||
// Partial class: Services are able to add their own methods and properties to this class.
|
|
||||||
// This is to prevent this file from having too many references to different and unrelated features.
|
|
||||||
|
|
||||||
private readonly InstanceConfig _icfg;
|
|
||||||
private readonly DiscordSocketClient _client;
|
|
||||||
private IReadOnlyCollection<Service> _services;
|
|
||||||
private IReadOnlyCollection<ModuleBase> _modules;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets application instance configuration.
|
/// Gets application instance configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal InstanceConfig Config => _icfg;
|
internal InstanceConfig Config { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Discord client instance.
|
/// Gets the Discord client instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DiscordSocketClient DiscordClient => _client;
|
public DiscordSocketClient DiscordClient { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All loaded services in an iterable form.
|
/// Gets all loaded services in an iterable form.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal IReadOnlyCollection<Service> Services => _services;
|
internal IReadOnlyCollection<Service> Services { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All loaded modules in an iterable form.
|
/// Gets all loaded modules in an iterable form.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal IReadOnlyCollection<ModuleBase> Modules => _modules;
|
internal IReadOnlyCollection<ModuleBase> Modules { get; }
|
||||||
|
|
||||||
internal Kerobot(InstanceConfig conf, DiscordSocketClient client)
|
internal Kerobot(InstanceConfig conf, DiscordSocketClient client)
|
||||||
{
|
{
|
||||||
_icfg = conf;
|
Config = conf;
|
||||||
_client = client;
|
DiscordClient = client;
|
||||||
|
|
||||||
// 'Ready' event handler. Because there's no other place for it.
|
|
||||||
_client.Ready += async delegate
|
|
||||||
{
|
|
||||||
await InstanceLogAsync(true, "Kerobot", "Connected and ready.");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get all services started up
|
// Get all services started up
|
||||||
_services = InitializeServices();
|
Services = InitializeServices();
|
||||||
|
|
||||||
// Load externally defined functionality
|
// Load externally defined functionality
|
||||||
_modules = ModuleLoader.Load(_icfg, this);
|
Modules = ModuleLoader.Load(Config, this);
|
||||||
|
|
||||||
// Everything's ready to go. Print the welcome message here.
|
// Everything's ready to go. Print the welcome message here.
|
||||||
var ver = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
var ver = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||||
|
@ -81,7 +70,7 @@ namespace Kerobot
|
||||||
/// </param>
|
/// </param>
|
||||||
internal async Task<NpgsqlConnection> GetOpenNpgsqlConnectionAsync(ulong? guild)
|
internal async Task<NpgsqlConnection> GetOpenNpgsqlConnectionAsync(ulong? guild)
|
||||||
{
|
{
|
||||||
string cs = _icfg.PostgresConnString;
|
string cs = Config.PostgresConnString;
|
||||||
if (guild.HasValue) cs += ";searchpath=guild_" + guild.Value;
|
if (guild.HasValue) cs += ";searchpath=guild_" + guild.Value;
|
||||||
|
|
||||||
var db = new NpgsqlConnection(cs);
|
var db = new NpgsqlConnection(cs);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Newtonsoft.Json.Linq;
|
using Discord.WebSocket;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -9,23 +10,26 @@ namespace Kerobot
|
||||||
/// user input (both by means of configuration and incoming Discord events) and process it accordingly.
|
/// user input (both by means of configuration and incoming Discord events) and process it accordingly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Implementing classes should not rely on local/instance variables to store data. Make use of
|
/// Implementing classes should not rely on local variables to store runtime data regarding guilds.
|
||||||
/// <see cref="CreateGuildStateAsync(JToken)"/> and <see cref="GetGuildState{T}(ulong)"/>.
|
/// Use <see cref="CreateGuildStateAsync(JToken)"/> and <see cref="GetGuildState{T}(ulong)"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public abstract class ModuleBase
|
public abstract class ModuleBase
|
||||||
{
|
{
|
||||||
private readonly Kerobot _kb;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the Kerobot instance.
|
/// Retrieves the Kerobot instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Kerobot Kerobot => _kb;
|
public Kerobot Kerobot { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the Discord client instance.
|
||||||
|
/// </summary>
|
||||||
|
public DiscordSocketClient DiscordClient { get => Kerobot.DiscordClient; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When a module is loaded, this constructor is called.
|
/// When a module is loaded, this constructor is called.
|
||||||
/// Services are available at this point. Do not attempt to communicate to Discord within the constructor.
|
/// Services are available at this point. Do not attempt to communicate to Discord within the constructor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ModuleBase(Kerobot kb) => _kb = kb;
|
public ModuleBase(Kerobot kb) => Kerobot = kb;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the module name.
|
/// Gets the module name.
|
||||||
|
@ -53,6 +57,20 @@ namespace Kerobot
|
||||||
/// Thrown if the stored state object cannot be cast as specified.
|
/// Thrown if the stored state object cannot be cast as specified.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
protected T GetGuildState<T>(ulong guildId) => Kerobot.GetGuildState<T>(guildId, GetType());
|
protected T GetGuildState<T>(ulong guildId) => Kerobot.GetGuildState<T>(guildId, GetType());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends a message to the global instance log. Use sparingly.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="report">
|
||||||
|
/// Specifies if the log message should be sent to the reporting channel.
|
||||||
|
/// Only messages of very high importance should use this option.
|
||||||
|
/// </param>
|
||||||
|
protected Task LogAsync(string message, bool report = false) => Kerobot.InstanceLogAsync(report, Name, message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends a message to the log for the specified guild.
|
||||||
|
/// </summary>
|
||||||
|
protected Task LogAsync(ulong guild, string message) => Kerobot.GuildLogAsync(guild, Name, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -10,12 +10,11 @@ namespace Kerobot
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class Program
|
class Program
|
||||||
{
|
{
|
||||||
static DateTimeOffset _startTime;
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Timestamp specifying the date and time that the program began running.
|
/// Timestamp specifying the date and time that the program began running.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static DateTimeOffset StartTime => _startTime;
|
public static DateTimeOffset StartTime { get; private set; }
|
||||||
|
|
||||||
static Kerobot _main;
|
static Kerobot _main;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -23,8 +22,8 @@ namespace Kerobot
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static async Task Main(string[] args)
|
static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
_startTime = DateTimeOffset.UtcNow;
|
StartTime = DateTimeOffset.UtcNow;
|
||||||
Console.WriteLine("Bot start time: " + _startTime.ToString("u"));
|
Console.WriteLine("Bot start time: " + StartTime.ToString("u"));
|
||||||
|
|
||||||
// Get instance configuration from file and parameters
|
// Get instance configuration from file and parameters
|
||||||
var opts = Options.ParseOptions(args); // Program can exit here.
|
var opts = Options.ParseOptions(args); // Program can exit here.
|
||||||
|
@ -73,7 +72,7 @@ namespace Kerobot
|
||||||
// send error notification to instance log channel, if possible
|
// send error notification to instance log channel, if possible
|
||||||
|
|
||||||
// And off we go.
|
// And off we go.
|
||||||
await _main.DiscordClient.LoginAsync(Discord.TokenType.Bot, cfg.BotToken);
|
await _main.DiscordClient.LoginAsync(TokenType.Bot, cfg.BotToken);
|
||||||
await _main.DiscordClient.StartAsync();
|
await _main.DiscordClient.StartAsync();
|
||||||
await Task.Delay(-1);
|
await Task.Delay(-1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,41 +9,41 @@ namespace Kerobot.Services.GuildState
|
||||||
class StateInfo : IDisposable
|
class StateInfo : IDisposable
|
||||||
{
|
{
|
||||||
static readonly TimeSpan TimeUntilStale = new TimeSpan(0, 15, 0);
|
static readonly TimeSpan TimeUntilStale = new TimeSpan(0, 15, 0);
|
||||||
|
|
||||||
private readonly object _data;
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Module-provided data.
|
/// Module-provided data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public object Data => _data;
|
public object Data { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hash of the JToken used to generate the data. In certain casaes, it is used to check
|
/// Hash of the JToken used to generate the data. In certain casaes, it is used to check
|
||||||
/// if the configuration may be stale and needs to be reloaded.
|
/// if the configuration may be stale and needs to be reloaded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly int _configHash;
|
private readonly int _configHash;
|
||||||
|
|
||||||
private readonly DateTimeOffset _creationTs;
|
private DateTimeOffset _lastStaleCheck;
|
||||||
|
|
||||||
public StateInfo(object data, int configHash)
|
public StateInfo(object data, int configHash)
|
||||||
{
|
{
|
||||||
_data = data;
|
Data = data;
|
||||||
_configHash = configHash;
|
_configHash = configHash;
|
||||||
_creationTs = DateTimeOffset.UtcNow;
|
_lastStaleCheck = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_data is IDisposable dd) { dd.Dispose(); }
|
if (Data is IDisposable dd) { dd.Dispose(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the current data may be stale, based on the data's age or
|
/// Checks if the current data may be stale, based on the last staleness check or
|
||||||
/// through comparison with incoming configuration.
|
/// if the underlying configuration has changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsStale(JToken comparison)
|
public bool IsStale(JToken comparison)
|
||||||
{
|
{
|
||||||
if (DateTimeOffset.UtcNow - _creationTs > TimeUntilStale) return true;
|
if (DateTimeOffset.UtcNow - _lastStaleCheck > TimeUntilStale) return true;
|
||||||
if (comparison.GetHashCode() != _configHash) return true;
|
if (comparison.GetHashCode() != _configHash) return true;
|
||||||
|
_lastStaleCheck = DateTimeOffset.UtcNow;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,10 @@ namespace Kerobot.Services.Logging
|
||||||
|
|
||||||
// Discord.Net log handling (client logging option is specified in Program.cs)
|
// Discord.Net log handling (client logging option is specified in Program.cs)
|
||||||
kb.DiscordClient.Log += DiscordClient_Log;
|
kb.DiscordClient.Log += DiscordClient_Log;
|
||||||
|
|
||||||
|
// Ready message too
|
||||||
|
kb.DiscordClient.Ready +=
|
||||||
|
async delegate { await DoInstanceLogAsync(true, "Kerobot", "Connected and ready."); };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
namespace Kerobot.Services
|
namespace Kerobot.Services
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for Kerobot service.
|
/// Base class for Kerobot services.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Services provide the core functionality of this program. Modules are expected to call into methods
|
/// Services provide the core functionality of this program. Modules are expected to call into methods
|
||||||
|
@ -11,18 +11,15 @@ namespace Kerobot.Services
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
internal abstract class Service
|
internal abstract class Service
|
||||||
{
|
{
|
||||||
private readonly Kerobot _kb;
|
public Kerobot Kerobot { get; }
|
||||||
public Kerobot Kerobot => _kb;
|
|
||||||
|
|
||||||
public string Name => this.GetType().Name;
|
public string Name => this.GetType().Name;
|
||||||
|
|
||||||
public Service(Kerobot kb)
|
public Service(Kerobot kb) => Kerobot = kb;
|
||||||
{
|
|
||||||
_kb = kb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes database tables per-guild. Called when entering a guild.
|
/// Initializes database tables per-guild.
|
||||||
|
/// This method is called by GuildStateService when entering a guild.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="db">An opened database connection with the appropriate schema option set.</param>
|
/// <param name="db">An opened database connection with the appropriate schema option set.</param>
|
||||||
/// <remarks>If overriding, calling the base method is not necessary.</remarks>
|
/// <remarks>If overriding, calling the base method is not necessary.</remarks>
|
||||||
|
|
Loading…
Reference in a new issue