refactor: clean up config reads

Configuration models are now defined and generated by the ConfigurationService which makes reading config a much cleaner process.
This commit is contained in:
Oliver Booth 2023-08-26 13:21:01 +01:00
parent 7a6ae083da
commit d442e4e9b3
Signed by: oliverbooth
GPG Key ID: B89D139977693FED
8 changed files with 180 additions and 39 deletions

View File

@ -0,0 +1,33 @@
namespace VPLink.Configuration;
/// <summary>
/// Represents the bot configuration.
/// </summary>
public sealed class BotConfiguration
{
/// <summary>
/// Gets or sets a value indicating whether the bot should announce avatar events.
/// </summary>
/// <value>
/// <see langword="true" /> if the bot should announce avatar events; otherwise, <see langword="false" />.
/// </value>
public bool AnnounceAvatarEvents { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether the bot should announce avatar events for bots.
/// </summary>
/// <value>
/// <see langword="true" /> if the bot should announce avatar events for bots; otherwise,
/// <see langword="false" />.
/// </value>
public bool AnnounceBots { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether the bot should relay messages from other bots.
/// </summary>
/// <value>
/// <see langword="true" /> if the bot should relay messages from other bots; otherwise,
/// <see langword="false" />.
/// </value>
public bool RelayBotMessages { get; set; } = false;
}

View File

@ -0,0 +1,19 @@
namespace VPLink.Configuration;
/// <summary>
/// Represents the Discord configuration.
/// </summary>
public sealed class DiscordConfiguration
{
/// <summary>
/// Gets or sets the channel ID to which the bot should relay messages.
/// </summary>
/// <value>The channel ID.</value>
public ulong ChannelId { get; set; }
/// <summary>
/// Gets or sets the Discord token.
/// </summary>
/// <value>The Discord token.</value>
public string Token { get; set; } = string.Empty;
}

View File

@ -0,0 +1,31 @@
namespace VPLink.Configuration;
/// <summary>
/// Represents the Virtual Paradise configuration.
/// </summary>
public sealed class VirtualParadiseConfiguration
{
/// <summary>
/// Gets or sets the display name of the bot.
/// </summary>
/// <value>The display name.</value>
public string BotName { get; set; } = "VPLink";
/// <summary>
/// Gets or sets the password with which to log in to Virtual Paradise.
/// </summary>
/// <value>The login password.</value>
public string Password { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the username with which to log in to Virtual Paradise.
/// </summary>
/// <value>The login username.</value>
public string Username { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the world into which the bot should enter.
/// </summary>
/// <value>The world to enter.</value>
public string World { get; set; } = string.Empty;
}

View File

@ -25,6 +25,7 @@ builder.Logging.ClearProviders();
builder.Logging.AddSerilog(); builder.Logging.AddSerilog();
builder.Services.AddSingleton<VirtualParadiseClient>(); builder.Services.AddSingleton<VirtualParadiseClient>();
builder.Services.AddSingleton<IConfigurationService, ConfigurationService>();
builder.Services.AddSingleton<InteractionService>(); builder.Services.AddSingleton<InteractionService>();
builder.Services.AddSingleton<DiscordSocketClient>(); builder.Services.AddSingleton<DiscordSocketClient>();

View File

@ -0,0 +1,37 @@
using Microsoft.Extensions.Configuration;
using VPLink.Configuration;
namespace VPLink.Services;
/// <inheritdoc cref="IConfigurationService" />
internal sealed class ConfigurationService : IConfigurationService
{
private readonly IConfiguration _configuration;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurationService" /> class.
/// </summary>
/// <param name="configuration"></param>
public ConfigurationService(IConfiguration configuration)
{
_configuration = configuration;
}
/// <inheritdoc />
public BotConfiguration BotConfiguration
{
get => _configuration.GetSection("Bot").Get<BotConfiguration>()!;
}
/// <inheritdoc />
public DiscordConfiguration DiscordConfiguration
{
get => _configuration.GetSection("Discord").Get<DiscordConfiguration>()!;
}
/// <inheritdoc />
public VirtualParadiseConfiguration VirtualParadiseConfiguration
{
get => _configuration.GetSection("VirtualParadise").Get<VirtualParadiseConfiguration>()!;
}
}

View File

@ -5,10 +5,10 @@ using System.Text.RegularExpressions;
using Discord; using Discord;
using Discord.Interactions; using Discord.Interactions;
using Discord.WebSocket; using Discord.WebSocket;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using VPLink.Commands; using VPLink.Commands;
using VPLink.Configuration;
using VpSharp.Entities; using VpSharp.Entities;
namespace VPLink.Services; namespace VPLink.Services;
@ -21,7 +21,7 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
private readonly ILogger<DiscordService> _logger; private readonly ILogger<DiscordService> _logger;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly IConfiguration _configuration; private readonly IConfigurationService _configurationService;
private readonly InteractionService _interactionService; private readonly InteractionService _interactionService;
private readonly DiscordSocketClient _discordClient; private readonly DiscordSocketClient _discordClient;
private readonly Subject<IUserMessage> _messageReceived = new(); private readonly Subject<IUserMessage> _messageReceived = new();
@ -31,18 +31,18 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
/// </summary> /// </summary>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="serviceProvider">The service provider.</param> /// <param name="serviceProvider">The service provider.</param>
/// <param name="configuration">The configuration.</param> /// <param name="configurationService">The configuration service.</param>
/// <param name="interactionService">The interaction service.</param> /// <param name="interactionService">The interaction service.</param>
/// <param name="discordClient">The Discord client.</param> /// <param name="discordClient">The Discord client.</param>
public DiscordService(ILogger<DiscordService> logger, public DiscordService(ILogger<DiscordService> logger,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
IConfiguration configuration, IConfigurationService configurationService,
InteractionService interactionService, InteractionService interactionService,
DiscordSocketClient discordClient) DiscordSocketClient discordClient)
{ {
_logger = logger; _logger = logger;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_configuration = configuration; _configurationService = configurationService;
_interactionService = interactionService; _interactionService = interactionService;
_discordClient = discordClient; _discordClient = discordClient;
} }
@ -62,8 +62,8 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
_discordClient.InteractionCreated += OnInteractionCreated; _discordClient.InteractionCreated += OnInteractionCreated;
_discordClient.MessageReceived += OnDiscordMessageReceived; _discordClient.MessageReceived += OnDiscordMessageReceived;
string token = _configuration.GetSection("Discord:Token").Value ?? DiscordConfiguration configuration = _configurationService.DiscordConfiguration;
throw new InvalidOperationException("Token is not set."); string token = configuration.Token ?? throw new InvalidOperationException("Token is not set.");
_logger.LogDebug("Connecting to Discord"); _logger.LogDebug("Connecting to Discord");
await _discordClient.LoginAsync(TokenType.Bot, token); await _discordClient.LoginAsync(TokenType.Bot, token);
@ -75,7 +75,8 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
if (arg is not IUserMessage message) if (arg is not IUserMessage message)
return Task.CompletedTask; return Task.CompletedTask;
if (message.Channel.Id != _configuration.GetSection("Discord:ChannelId").Get<ulong>()) DiscordConfiguration configuration = _configurationService.DiscordConfiguration;
if (message.Channel.Id != configuration.ChannelId)
return Task.CompletedTask; return Task.CompletedTask;
_messageReceived.OnNext(message); _messageReceived.OnNext(message);
@ -153,7 +154,7 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
return Task.CompletedTask; return Task.CompletedTask;
} }
if (author.IsBot && !_configuration.GetSection("Bot:RelayBotMessages").Get<bool>()) if (author.IsBot && !_configurationService.BotConfiguration.RelayBotMessages)
{ {
_logger.LogDebug("Bot messages are disabled, ignoring message"); _logger.LogDebug("Bot messages are disabled, ignoring message");
return Task.CompletedTask; return Task.CompletedTask;
@ -172,7 +173,9 @@ internal sealed partial class DiscordService : BackgroundService, IDiscordServic
private bool TryGetRelayChannel([NotNullWhen(true)] out ITextChannel? channel) private bool TryGetRelayChannel([NotNullWhen(true)] out ITextChannel? channel)
{ {
var channelId = _configuration.GetValue<ulong>("Discord:ChannelId"); DiscordConfiguration configuration = _configurationService.DiscordConfiguration;
ulong channelId = configuration.ChannelId;
if (_discordClient.GetChannel(channelId) is ITextChannel textChannel) if (_discordClient.GetChannel(channelId) is ITextChannel textChannel)
{ {
channel = textChannel; channel = textChannel;

View File

@ -0,0 +1,27 @@
using VPLink.Configuration;
namespace VPLink.Services;
/// <summary>
/// Represents the configuration service.
/// </summary>
public interface IConfigurationService
{
/// <summary>
/// Gets the bot configuration.
/// </summary>
/// <value>The bot configuration.</value>
BotConfiguration BotConfiguration { get; }
/// <summary>
/// Gets the Discord configuration.
/// </summary>
/// <value>The Discord configuration.</value>
DiscordConfiguration DiscordConfiguration { get; }
/// <summary>
/// Gets the Virtual Paradise configuration.
/// </summary>
/// <value>The Virtual Paradise configuration.</value>
VirtualParadiseConfiguration VirtualParadiseConfiguration { get; }
}

View File

@ -1,12 +1,13 @@
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using Discord; using Discord;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using VPLink.Configuration;
using VpSharp; using VpSharp;
using VpSharp.Entities; using VpSharp.Entities;
using Color = System.Drawing.Color; using Color = System.Drawing.Color;
using VirtualParadiseConfiguration = VPLink.Configuration.VirtualParadiseConfiguration;
namespace VPLink.Services; namespace VPLink.Services;
@ -14,7 +15,7 @@ namespace VPLink.Services;
internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadiseService internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadiseService
{ {
private readonly ILogger<VirtualParadiseService> _logger; private readonly ILogger<VirtualParadiseService> _logger;
private readonly IConfiguration _configuration; private readonly IConfigurationService _configurationService;
private readonly VirtualParadiseClient _virtualParadiseClient; private readonly VirtualParadiseClient _virtualParadiseClient;
private readonly Subject<VirtualParadiseMessage> _messageReceived = new(); private readonly Subject<VirtualParadiseMessage> _messageReceived = new();
private readonly Subject<VirtualParadiseAvatar> _avatarJoined = new(); private readonly Subject<VirtualParadiseAvatar> _avatarJoined = new();
@ -24,14 +25,14 @@ internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadi
/// Initializes a new instance of the <see cref="VirtualParadiseService" /> class. /// Initializes a new instance of the <see cref="VirtualParadiseService" /> class.
/// </summary> /// </summary>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="configuration">The configuration.</param> /// <param name="configurationService">The configuration service.</param>
/// <param name="virtualParadiseClient">The Virtual Paradise client.</param> /// <param name="virtualParadiseClient">The Virtual Paradise client.</param>
public VirtualParadiseService(ILogger<VirtualParadiseService> logger, public VirtualParadiseService(ILogger<VirtualParadiseService> logger,
IConfiguration configuration, IConfigurationService configurationService,
VirtualParadiseClient virtualParadiseClient) VirtualParadiseClient virtualParadiseClient)
{ {
_logger = logger; _logger = logger;
_configuration = configuration; _configurationService = configurationService;
_virtualParadiseClient = virtualParadiseClient; _virtualParadiseClient = virtualParadiseClient;
} }
@ -50,7 +51,7 @@ internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadi
if (message is null) throw new ArgumentNullException(nameof(message)); if (message is null) throw new ArgumentNullException(nameof(message));
if (string.IsNullOrWhiteSpace(message.Content)) return Task.CompletedTask; if (string.IsNullOrWhiteSpace(message.Content)) return Task.CompletedTask;
if (message.Author.IsBot && !_configuration.GetSection("Bot:RelayBotMessages").Get<bool>()) if (message.Author.IsBot && !_configurationService.BotConfiguration.RelayBotMessages)
{ {
_logger.LogDebug("Bot messages are disabled, ignoring message"); _logger.LogDebug("Bot messages are disabled, ignoring message");
return Task.CompletedTask; return Task.CompletedTask;
@ -59,7 +60,8 @@ internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadi
_logger.LogInformation("Message by {Author}: {Content}", message.Author, message.Content); _logger.LogInformation("Message by {Author}: {Content}", message.Author, message.Content);
string displayName = message.Author.GlobalName ?? message.Author.Username; string displayName = message.Author.GlobalName ?? message.Author.Username;
return _virtualParadiseClient.SendMessageAsync(displayName, message.Content, FontStyle.Bold, Color.MidnightBlue); return _virtualParadiseClient.SendMessageAsync(displayName, message.Content, FontStyle.Bold,
Color.MidnightBlue);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -70,14 +72,12 @@ internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadi
_virtualParadiseClient.AvatarJoined.Subscribe(OnVirtualParadiseAvatarJoined); _virtualParadiseClient.AvatarJoined.Subscribe(OnVirtualParadiseAvatarJoined);
_virtualParadiseClient.AvatarLeft.Subscribe(OnVirtualParadiseAvatarLeft); _virtualParadiseClient.AvatarLeft.Subscribe(OnVirtualParadiseAvatarLeft);
string username = _configuration.GetSection("VirtualParadise:Username").Value ?? VirtualParadiseConfiguration configuration = _configurationService.VirtualParadiseConfiguration;
throw new InvalidOperationException("Username is not set.");
string password = _configuration.GetSection("VirtualParadise:Password").Value ?? string username = configuration.Username ?? throw new InvalidOperationException("Username is not set.");
throw new InvalidOperationException("Password is not set."); string password = configuration.Password ?? throw new InvalidOperationException("Password is not set.");
string world = _configuration.GetSection("VirtualParadise:World").Value ?? string world = configuration.World ?? throw new InvalidOperationException("World is not set.");
throw new InvalidOperationException("World is not set."); string botName = configuration.BotName ?? throw new InvalidOperationException("Bot name is not set.");
string botName = _configuration.GetSection("VirtualParadise:BotName").Value ??
throw new InvalidOperationException("Bot name is not set.");
_logger.LogDebug("Connecting to Virtual Paradise"); _logger.LogDebug("Connecting to Virtual Paradise");
await _virtualParadiseClient.ConnectAsync().ConfigureAwait(false); await _virtualParadiseClient.ConnectAsync().ConfigureAwait(false);
@ -89,15 +89,10 @@ internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadi
private void OnVirtualParadiseAvatarJoined(VirtualParadiseAvatar avatar) private void OnVirtualParadiseAvatarJoined(VirtualParadiseAvatar avatar)
{ {
if (!_configuration.GetValue<bool>("Bot:AnnounceAvatarEvents")) BotConfiguration configuration = _configurationService.BotConfiguration;
{
_logger.LogDebug("Join/leave events are disabled, ignoring event");
return;
}
if (avatar.IsBot && !_configuration.GetSection("Bot:AnnounceBots").Get<bool>()) if (!configuration.AnnounceAvatarEvents || avatar.IsBot && !configuration.AnnounceBots)
{ {
_logger.LogDebug("Bot events are disabled, ignoring event");
return; return;
} }
@ -106,15 +101,10 @@ internal sealed class VirtualParadiseService : BackgroundService, IVirtualParadi
private void OnVirtualParadiseAvatarLeft(VirtualParadiseAvatar avatar) private void OnVirtualParadiseAvatarLeft(VirtualParadiseAvatar avatar)
{ {
if (!_configuration.GetValue<bool>("Bot:AnnounceAvatarEvents")) BotConfiguration configuration = _configurationService.BotConfiguration;
{
_logger.LogDebug("Join/leave events are disabled, ignoring event");
return;
}
if (avatar.IsBot && !_configuration.GetSection("Bot:AnnounceBots").Get<bool>()) if (!configuration.AnnounceAvatarEvents || avatar.IsBot && !configuration.AnnounceBots)
{ {
_logger.LogDebug("Bot events are disabled, ignoring event");
return; return;
} }