From 87c889266b6871bf356efc937705511aeafb2562 Mon Sep 17 00:00:00 2001 From: Andrei Tudor Date: Sun, 6 Apr 2025 18:36:20 +0300 Subject: [PATCH] Improved UI experience for the Main page --- .../IPluginLoader.cs | 3 + .../PluginLoader.cs | 48 +++++++++------ DiscordBotCore/Bot/DiscordBotApplication.cs | 25 +++++++- DiscordBotCore/Bot/IDiscordBotApplication.cs | 6 ++ WebUI/Controllers/HomeController.cs | 57 ++++++++++++++--- WebUI/Program.cs | 18 +++--- WebUI/Views/Home/Index.cshtml | 61 +++++++++++++++++-- WebUI/Views/Home/Privacy.cshtml | 6 -- 8 files changed, 176 insertions(+), 48 deletions(-) delete mode 100644 WebUI/Views/Home/Privacy.cshtml diff --git a/DiscordBotCore.PluginManagement.Loading/IPluginLoader.cs b/DiscordBotCore.PluginManagement.Loading/IPluginLoader.cs index eea9e0e..67ef5a1 100644 --- a/DiscordBotCore.PluginManagement.Loading/IPluginLoader.cs +++ b/DiscordBotCore.PluginManagement.Loading/IPluginLoader.cs @@ -1,3 +1,4 @@ +using Discord.WebSocket; using DiscordBotCore.PluginCore; using DiscordBotCore.PluginCore.Interfaces; @@ -9,4 +10,6 @@ public interface IPluginLoader List Events { get; } List SlashCommands { get; } Task LoadPlugins(); + + void SetClient(DiscordSocketClient client); } \ No newline at end of file diff --git a/DiscordBotCore.PluginManagement.Loading/PluginLoader.cs b/DiscordBotCore.PluginManagement.Loading/PluginLoader.cs index 653cd4c..3d1ac25 100644 --- a/DiscordBotCore.PluginManagement.Loading/PluginLoader.cs +++ b/DiscordBotCore.PluginManagement.Loading/PluginLoader.cs @@ -13,27 +13,19 @@ namespace DiscordBotCore.PluginManagement.Loading; public sealed class PluginLoader : IPluginLoader { - private readonly DiscordSocketClient _DiscordClient; private readonly IPluginManager _PluginManager; private readonly ILogger _Logger; private readonly IConfiguration _Configuration; - - public delegate void CommandLoaded(IDbCommand eCommand); - public delegate void EventLoaded(IDbEvent eEvent); - public delegate void SlashCommandLoaded(IDbSlashCommand eSlashCommand); - - public CommandLoaded? OnCommandLoaded; - public EventLoaded? OnEventLoaded; - public SlashCommandLoaded? OnSlashCommandLoaded; public List Commands { get; private set; } = new List(); public List Events { get; private set; } = new List(); public List SlashCommands { get; private set; } = new List(); - public PluginLoader(IPluginManager pluginManager, ILogger logger, IConfiguration configuration, DiscordSocketClient discordSocketDiscordClient) + private DiscordSocketClient? _discordClient; + + public PluginLoader(IPluginManager pluginManager, ILogger logger, IConfiguration configuration) { _PluginManager = pluginManager; - _DiscordClient = discordSocketDiscordClient; _Logger = logger; _Configuration = configuration; } @@ -54,6 +46,24 @@ public sealed class PluginLoader : IPluginLoader await loader.Load(); } + public void SetClient(DiscordSocketClient client) + { + if (_discordClient is not null) + { + _Logger.Log("A client is already set. Please set the client only once.", this, LogType.Error); + return; + } + + if (client.LoginState != LoginState.LoggedIn) + { + _Logger.Log("Client is not logged in. Retry after the client is logged in", this, LogType.Error); + return; + } + + _Logger.Log("Client is set to the plugin loader", this); + _discordClient = client; + } + private void FileLoadedException(string fileName, Exception exception) { _Logger.LogException(exception, this); @@ -62,7 +72,7 @@ public sealed class PluginLoader : IPluginLoader private void InitializeDbCommand(IDbCommand command) { Commands.Add(command); - OnCommandLoaded?.Invoke(command); + _Logger.Log("Command loaded: " + command.Command, this); } private void InitializeEvent(IDbEvent eEvent) @@ -73,7 +83,7 @@ public sealed class PluginLoader : IPluginLoader } Events.Add(eEvent); - OnEventLoaded?.Invoke(eEvent); + _Logger.Log("Event loaded: " + eEvent, this); } private async void InitializeSlashCommand(IDbSlashCommand slashCommand) @@ -83,9 +93,9 @@ public sealed class PluginLoader : IPluginLoader () => { if (slashCommand.HasInteraction) - _DiscordClient.InteractionCreated += interaction => slashCommand.ExecuteInteraction(_Logger, interaction); + _discordClient.InteractionCreated += interaction => slashCommand.ExecuteInteraction(_Logger, interaction); SlashCommands.Add(slashCommand); - OnSlashCommandLoaded?.Invoke(slashCommand); + _Logger.Log("Slash command loaded: " + slashCommand.Name, this); }, HandleError ); @@ -116,7 +126,7 @@ public sealed class PluginLoader : IPluginLoader } IDbEventExecutingArgument args = new DbEventExecutingArgument( _Logger, - _DiscordClient, + _discordClient, _Configuration.Get("prefix"), new DirectoryInfo(Path.Combine(_Configuration.Get("ResourcesPath"), dbEvent.Name))); dbEvent.Start(args); @@ -139,7 +149,7 @@ public sealed class PluginLoader : IPluginLoader return Result.Failure(new Exception("dbSlashCommand is null")); } - if (_DiscordClient.Guilds.Count == 0) + if (_discordClient.Guilds.Count == 0) { return Result.Failure(new Exception("No guilds found")); } @@ -166,7 +176,7 @@ public sealed class PluginLoader : IPluginLoader } } - await _DiscordClient.CreateGlobalApplicationCommandAsync(builder.Build()); + await _discordClient.CreateGlobalApplicationCommandAsync(builder.Build()); return Result.Success(); } @@ -178,7 +188,7 @@ public sealed class PluginLoader : IPluginLoader private async Task EnableSlashCommandPerGuild(ulong guildId, SlashCommandBuilder builder) { - SocketGuild? guild = _DiscordClient.GetGuild(guildId); + SocketGuild? guild = _discordClient.GetGuild(guildId); if (guild is null) { _Logger.Log("Failed to get guild with ID " + guildId, typeof(PluginLoader), LogType.Error); diff --git a/DiscordBotCore/Bot/DiscordBotApplication.cs b/DiscordBotCore/Bot/DiscordBotApplication.cs index 06285b4..6b259a7 100644 --- a/DiscordBotCore/Bot/DiscordBotApplication.cs +++ b/DiscordBotCore/Bot/DiscordBotApplication.cs @@ -20,7 +20,7 @@ public class DiscordBotApplication : IDiscordBotApplication private readonly IConfiguration _Configuration; private readonly IPluginLoader _PluginLoader; - private bool IsReady { get; set; } + public bool IsReady { get; private set; } public DiscordSocketClient Client { get; private set; } @@ -34,6 +34,28 @@ public class DiscordBotApplication : IDiscordBotApplication this._PluginLoader = pluginLoader; } + public async Task StopAsync() + { + if (!IsReady) + { + _Logger.Log("Can not stop the bot. It is not yet initialized.", this, LogType.Error); + return; + } + + await Client.LogoutAsync(); + await Client.StopAsync(); + + Client.Log -= Log; + Client.LoggedIn -= LoggedIn; + Client.Ready -= Ready; + Client.Disconnected -= Client_Disconnected; + + await Client.DisposeAsync(); + + IsReady = false; + + } + /// /// The start method for the bot. This method is used to load the bot /// @@ -91,6 +113,7 @@ public class DiscordBotApplication : IDiscordBotApplication private Task LoggedIn() { _Logger.Log("Successfully Logged In", this); + _PluginLoader.SetClient(Client); return Task.CompletedTask; } diff --git a/DiscordBotCore/Bot/IDiscordBotApplication.cs b/DiscordBotCore/Bot/IDiscordBotApplication.cs index 8521552..3478fdd 100644 --- a/DiscordBotCore/Bot/IDiscordBotApplication.cs +++ b/DiscordBotCore/Bot/IDiscordBotApplication.cs @@ -5,10 +5,16 @@ namespace DiscordBotCore.Bot; public interface IDiscordBotApplication { + public bool IsReady { get; } public DiscordSocketClient Client { get; } /// /// The start method for the bot. This method is used to load the bot /// Task StartAsync(); + + /// + /// Stops the bot and cleans up resources. + /// + Task StopAsync(); } \ No newline at end of file diff --git a/WebUI/Controllers/HomeController.cs b/WebUI/Controllers/HomeController.cs index 6a827b3..0b6312a 100644 --- a/WebUI/Controllers/HomeController.cs +++ b/WebUI/Controllers/HomeController.cs @@ -1,6 +1,6 @@ -using System.Diagnostics; +using DiscordBotCore.Bot; +using DiscordBotCore.PluginManagement.Loading; using Microsoft.AspNetCore.Mvc; -using WebUI.Models; using ILogger = DiscordBotCore.Logging.ILogger; namespace WebUI.Controllers; @@ -8,25 +8,64 @@ namespace WebUI.Controllers; public class HomeController : Controller { private readonly ILogger _logger; - public HomeController(ILogger logger) + private readonly IDiscordBotApplication _discordBotApplication; + private readonly IPluginLoader _pluginLoader; + + public HomeController(ILogger logger, IDiscordBotApplication discordBotApplication, IPluginLoader pluginLoader) { _logger = logger; + _discordBotApplication = discordBotApplication; + _pluginLoader = pluginLoader; } + [HttpGet] public IActionResult Index() { _logger.Log("Index page loaded", this); + ViewBag.IsRunning = _discordBotApplication.IsReady; return View(); } - public IActionResult Privacy() + [HttpPost] + public async Task StartApplication() { - return View(); - } + if (_discordBotApplication.IsReady) + { + _logger.Log("Application already started", this); + return RedirectToAction("Index"); + } - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public IActionResult Error() + await _discordBotApplication.StartAsync(); + return RedirectToAction("Index"); + } + + [HttpPost] + public async Task StopApplication() { - return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + if (!_discordBotApplication.IsReady) + { + _logger.Log("Application already stopped", this); + return RedirectToAction("Index"); + } + + await _discordBotApplication.StopAsync(); + return RedirectToAction("Index"); + } + + [HttpGet] + public JsonResult GetLogs() + { + var logText = _logger.GetLogsHistory(); + var logs = logText.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); + return Json(logs); + } + + [HttpPost] + public async Task LoadPlugins() + { + _logger.Log("Loading plugins", this); + await _pluginLoader.LoadPlugins(); + _logger.Log("Plugins loaded", this); + return RedirectToAction("Index"); } } \ No newline at end of file diff --git a/WebUI/Program.cs b/WebUI/Program.cs index e045fdd..6db2b94 100644 --- a/WebUI/Program.cs +++ b/WebUI/Program.cs @@ -142,6 +142,15 @@ builder.Services.AddSingleton(sp => IPluginManager pluginManager = new PluginManager(pluginRepository, logger, configuration); return pluginManager; }); + +builder.Services.AddSingleton(sp => +{ + IPluginManager pluginManager = sp.GetRequiredService(); + ILogger logger = sp.GetRequiredService(); + IConfiguration configuration = sp.GetRequiredService(); + return new PluginLoader(pluginManager, logger, configuration); +}); + builder.Services.AddSingleton(sp => { ILogger logger = sp.GetRequiredService(); @@ -150,14 +159,7 @@ builder.Services.AddSingleton(sp => return new DiscordBotApplication(logger, configuration, pluginLoader); }); -builder.Services.AddSingleton(sp => -{ - IPluginManager pluginManager = sp.GetRequiredService(); - ILogger logger = sp.GetRequiredService(); - IConfiguration configuration = sp.GetRequiredService(); - IDiscordBotApplication discordBotApplication = sp.GetRequiredService(); - return new PluginLoader(pluginManager, logger, configuration, discordBotApplication.Client); -}); + var app = builder.Build(); diff --git a/WebUI/Views/Home/Index.cshtml b/WebUI/Views/Home/Index.cshtml index 09285dd..14f97ae 100644 --- a/WebUI/Views/Home/Index.cshtml +++ b/WebUI/Views/Home/Index.cshtml @@ -1,8 +1,59 @@ @{ - ViewData["Title"] = "Home Page"; + var isRunning = ViewBag.IsRunning ?? false; + ViewBag.Title = "Console Log Viewer"; } -
-

Welcome

-

Learn about building Web apps with ASP.NET Core.

-
\ No newline at end of file +
+
+
+

Console Log

+
+
+ +
+

Controls

+
+ +
+ @if (isRunning) + { +
+ +
+
+ +
+ } + +
+
+
+ +@section Scripts { + +} diff --git a/WebUI/Views/Home/Privacy.cshtml b/WebUI/Views/Home/Privacy.cshtml deleted file mode 100644 index e6eef70..0000000 --- a/WebUI/Views/Home/Privacy.cshtml +++ /dev/null @@ -1,6 +0,0 @@ -@{ - ViewData["Title"] = "Privacy Policy"; -} -

@ViewData["Title"]

- -

Use this page to detail your site's privacy policy.

\ No newline at end of file