diff --git a/.gitignore b/.gitignore
index a12e00a..d5c81c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -373,4 +373,5 @@ FodyWeavers.xsd
/DiscordBot/Updater/
.idea/
DiscordBot/Launcher.exe
-DiscordBotUI/*
+DiscordBotUI/bin
+DiscordBotUI/obj
diff --git a/DiscordBotUI/App.axaml b/DiscordBotUI/App.axaml
new file mode 100644
index 0000000..cb6daa8
--- /dev/null
+++ b/DiscordBotUI/App.axaml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DiscordBotUI/App.axaml.cs b/DiscordBotUI/App.axaml.cs
new file mode 100644
index 0000000..b613a55
--- /dev/null
+++ b/DiscordBotUI/App.axaml.cs
@@ -0,0 +1,24 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+
+namespace DiscordBotUI
+{
+ public partial class App : Application
+ {
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ desktop.MainWindow = new HomePage();
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+ }
+}
\ No newline at end of file
diff --git a/DiscordBotUI/Bot/Commands/Help.cs b/DiscordBotUI/Bot/Commands/Help.cs
new file mode 100644
index 0000000..8f9ec34
--- /dev/null
+++ b/DiscordBotUI/Bot/Commands/Help.cs
@@ -0,0 +1,90 @@
+using System.Collections.Generic;
+using Discord;
+using PluginManager;
+using PluginManager.Interfaces;
+using PluginManager.Loaders;
+using PluginManager.Others;
+
+namespace DiscordBotUI.Bot.Commands;
+
+///
+/// The help command
+///
+internal class Help: DBCommand
+{
+ ///
+ /// Command name
+ ///
+ public string Command => "help";
+
+ public List Aliases => null;
+
+ ///
+ /// Command Description
+ ///
+ public string Description => "This command allows you to check all loaded commands";
+
+ ///
+ /// Command usage
+ ///
+ public string Usage => "help ";
+
+ ///
+ /// Check if the command require administrator to be executed
+ ///
+ public bool requireAdmin => false;
+
+ ///
+ /// The main body of the command
+ ///
+ /// The command context
+ public void ExecuteServer(DbCommandExecutingArguments args)
+ {
+ if (args.arguments is not null)
+ {
+ var e = GenerateHelpCommand(args.arguments[0]);
+ if (e is null)
+ args.context.Channel.SendMessageAsync("Unknown Command " + args.arguments[0]);
+ else
+ args.context.Channel.SendMessageAsync(embed: e.Build());
+
+
+ return;
+ }
+
+ var embedBuilder = new EmbedBuilder();
+
+ var adminCommands = "";
+ var normalCommands = "";
+
+ foreach (var cmd in PluginLoader.Commands)
+ if (cmd.requireAdmin)
+ adminCommands += cmd.Command + " ";
+ else
+ normalCommands += cmd.Command + " ";
+
+
+ if (adminCommands.Length > 0)
+ embedBuilder.AddField("Admin Commands", adminCommands);
+ if (normalCommands.Length > 0)
+ embedBuilder.AddField("Normal Commands", normalCommands);
+ args.context.Channel.SendMessageAsync(embed: embedBuilder.Build());
+ }
+
+ private EmbedBuilder GenerateHelpCommand(string command)
+ {
+ var embedBuilder = new EmbedBuilder();
+ var cmd = PluginLoader.Commands.Find(p => p.Command == command ||
+ p.Aliases is not null && p.Aliases.Contains(command)
+ );
+ if (cmd == null) return null;
+
+ embedBuilder.AddField("Usage", Config.AppSettings["prefix"] + cmd.Usage);
+ embedBuilder.AddField("Description", cmd.Description);
+ if (cmd.Aliases is null)
+ return embedBuilder;
+ embedBuilder.AddField("Alias", cmd.Aliases.Count == 0 ? "-" : string.Join(", ", cmd.Aliases));
+
+ return embedBuilder;
+ }
+}
diff --git a/DiscordBotUI/Bot/DiscordBot.cs b/DiscordBotUI/Bot/DiscordBot.cs
new file mode 100644
index 0000000..5bf5cec
--- /dev/null
+++ b/DiscordBotUI/Bot/DiscordBot.cs
@@ -0,0 +1,83 @@
+using System.Threading.Tasks;
+
+using PluginManager;
+using PluginManager.Interfaces;
+using PluginManager.Loaders;
+using PluginManager.Others;
+
+namespace DiscordBotUI.Bot
+{
+ internal class DiscordBot
+ {
+ private readonly string[] _StartArguments;
+
+ public DiscordBot(string[] args)
+ {
+ this._StartArguments = args;
+ }
+
+ public async Task InitializeBot()
+ {
+ string token = Config.AppSettings["token"];
+ string prefix = Config.AppSettings["prefix"];
+ PluginManager.Bot.Boot discordBooter = new PluginManager.Bot.Boot(token, prefix);
+ await discordBooter.Awake();
+ }
+
+ public async Task LoadPlugins()
+ {
+ var loader = new PluginLoader(Config.DiscordBot.client);
+
+ loader.OnCommandLoaded += (data) =>
+ {
+ if (data.IsSuccess)
+ {
+ Config.Logger.Log("Successfully loaded command : " + data.PluginName, typeof(ICommandAction),
+ LogType.INFO
+ );
+ }
+
+ else
+ {
+ Config.Logger.Log("Failed to load command : " + data.PluginName + " because " + data.ErrorMessage,
+ typeof(ICommandAction), LogType.ERROR
+ );
+ }
+ };
+ loader.OnEventLoaded += (data) =>
+ {
+ if (data.IsSuccess)
+ {
+ Config.Logger.Log("Successfully loaded event : " + data.PluginName, typeof(ICommandAction),
+ LogType.INFO
+ );
+ }
+ else
+ {
+ Config.Logger.Log("Failed to load event : " + data.PluginName + " because " + data.ErrorMessage,
+ typeof(ICommandAction), LogType.ERROR
+ );
+ }
+ };
+
+ loader.OnSlashCommandLoaded += (data) =>
+ {
+ if (data.IsSuccess)
+ {
+ Config.Logger.Log("Successfully loaded slash command : " + data.PluginName, typeof(ICommandAction),
+ LogType.INFO
+ );
+ }
+ else
+ {
+ Config.Logger.Log("Failed to load slash command : " + data.PluginName + " because " + data.ErrorMessage,
+ typeof(ICommandAction), LogType.ERROR
+ );
+ }
+ };
+
+ await loader.LoadPlugins();
+ }
+
+ }
+}
diff --git a/DiscordBotUI/DiscordBotUI.csproj b/DiscordBotUI/DiscordBotUI.csproj
new file mode 100644
index 0000000..eb95467
--- /dev/null
+++ b/DiscordBotUI/DiscordBotUI.csproj
@@ -0,0 +1,26 @@
+
+
+ WinExe
+ net8.0
+ enable
+ true
+ app.manifest
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DiscordBotUI/Program.cs b/DiscordBotUI/Program.cs
new file mode 100644
index 0000000..9090e15
--- /dev/null
+++ b/DiscordBotUI/Program.cs
@@ -0,0 +1,23 @@
+using System;
+
+using Avalonia;
+
+namespace DiscordBotUI
+{
+ internal class Program
+ {
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ [STAThread]
+ public static void Main(string[] args) => BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .WithInterFont()
+ .LogToTrace();
+ }
+}
diff --git a/DiscordBotUI/Windows/HomePage.axaml b/DiscordBotUI/Windows/HomePage.axaml
new file mode 100644
index 0000000..c1b71e0
--- /dev/null
+++ b/DiscordBotUI/Windows/HomePage.axaml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DiscordBotUI/Windows/HomePage.axaml.cs b/DiscordBotUI/Windows/HomePage.axaml.cs
new file mode 100644
index 0000000..0b1bd3f
--- /dev/null
+++ b/DiscordBotUI/Windows/HomePage.axaml.cs
@@ -0,0 +1,70 @@
+using System;
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Threading;
+
+using DiscordBotUI.Bot;
+using DiscordBotUI.Windows;
+using PluginManager;
+using PluginManager.Others;
+using PluginManager.Others.Logger;
+
+namespace DiscordBotUI;
+
+public partial class HomePage : Window
+{
+ private readonly DiscordBot _DiscordBot;
+
+ public HomePage()
+ {
+ InitializeComponent();
+ _DiscordBot = new DiscordBot(null!);
+
+ Loaded += HomePage_Loaded;
+ }
+
+ private async void HomePage_Loaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ await Config.Initialize();
+
+ while(string.IsNullOrWhiteSpace(Config.AppSettings["token"]) || string.IsNullOrWhiteSpace(Config.AppSettings["prefix"]))
+ {
+ await new SettingsPage().ShowDialog(this);
+ }
+
+ textBoxToken.Text = Config.AppSettings["token"];
+ textBoxPrefix.Text = Config.AppSettings["prefix"];
+ textBoxServerId.Text = Config.AppSettings["ServerID"];
+ }
+
+ private void SetTextToTB(Log logMessage)
+ {
+ logTextBlock.Text += $"[{logMessage.Type}] [{logMessage.ThrowTime.ToShortTimeString()}] {logMessage.Message}\n";
+ }
+
+ private async void ButtonStartBotClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+
+ Config.Logger.OnLog += async (sender, logMessage) =>
+ {
+ await Dispatcher.UIThread.InvokeAsync(() => SetTextToTB(logMessage), DispatcherPriority.Background);
+ };
+
+ await _DiscordBot.InitializeBot();
+
+ await _DiscordBot.LoadPlugins();
+
+ }
+
+ private async void SettingsMenuClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ await new SettingsPage().ShowDialog(this);
+ }
+
+ private void PluginsMenuClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ new PluginsPage().Show();
+ }
+}
\ No newline at end of file
diff --git a/DiscordBotUI/Windows/PluginsPage.axaml b/DiscordBotUI/Windows/PluginsPage.axaml
new file mode 100644
index 0000000..b7aca62
--- /dev/null
+++ b/DiscordBotUI/Windows/PluginsPage.axaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
diff --git a/DiscordBotUI/Windows/PluginsPage.axaml.cs b/DiscordBotUI/Windows/PluginsPage.axaml.cs
new file mode 100644
index 0000000..090bf45
--- /dev/null
+++ b/DiscordBotUI/Windows/PluginsPage.axaml.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using PluginManager;
+
+namespace DiscordBotUI.Windows;
+
+
+public partial class PluginsPage: Window
+{
+ public ObservableCollection _Plugins { get; private set; }
+ public PluginsPage()
+ {
+ InitializeComponent();
+
+ Loaded += OnAppLoaded;
+ }
+
+ private async void OnAppLoaded(object? sender, RoutedEventArgs e)
+ {
+ var plugins = await Config.PluginsManager.GetInstalledPlugins();
+ _Plugins = new ObservableCollection();
+ foreach (var plugin in plugins)
+ {
+ _Plugins.Add(new PluginViewModel(plugin.PluginName, plugin.PluginVersion.ToShortString(), plugin.IsMarkedToUninstall));
+ }
+
+ dataGridPlugins.ItemsSource = _Plugins;
+ }
+
+
+}
+
+public class PluginViewModel
+{
+ public string Name { get; set; }
+ public string Version { get; set; }
+ public bool IsMarkedToUninstall { get; set; }
+
+ public PluginViewModel(string Name, string Version, bool isMarkedToUninstall)
+ {
+ this.Name = Name;
+ this.Version = Version;
+ IsMarkedToUninstall = isMarkedToUninstall;
+ }
+}
\ No newline at end of file
diff --git a/DiscordBotUI/Windows/SettingsPage.axaml b/DiscordBotUI/Windows/SettingsPage.axaml
new file mode 100644
index 0000000..94d0f97
--- /dev/null
+++ b/DiscordBotUI/Windows/SettingsPage.axaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DiscordBotUI/Windows/SettingsPage.axaml.cs b/DiscordBotUI/Windows/SettingsPage.axaml.cs
new file mode 100644
index 0000000..f81173f
--- /dev/null
+++ b/DiscordBotUI/Windows/SettingsPage.axaml.cs
@@ -0,0 +1,44 @@
+using Avalonia.Controls;
+
+using PluginManager;
+
+namespace DiscordBotUI;
+
+public partial class SettingsPage : Window
+{
+ public SettingsPage()
+ {
+ InitializeComponent();
+ }
+
+ private async void ButtonSaveSettingsClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ string token = textBoxToken.Text;
+ string botPrefix = textBoxPrefix.Text;
+ string serverId = textBoxServerId.Text;
+
+ if (string.IsNullOrWhiteSpace(serverId)) serverId = string.Empty;
+ if (string.IsNullOrWhiteSpace(token))
+ {
+ labelErrorMessage.Content = "The token is invalid";
+ return;
+ }
+
+ if(string.IsNullOrWhiteSpace(botPrefix) || botPrefix.Length > 1 || botPrefix.Length < 1)
+ {
+ labelErrorMessage.Content = "The prefix is invalid";
+ return;
+ }
+
+ Config.AppSettings.Add("token", token);
+ Config.AppSettings.Add("prefix", botPrefix);
+ Config.AppSettings.Add("ServerID", serverId);
+
+ await Config.AppSettings.SaveToFile();
+
+ Config.Logger.Log("Config Saved");
+
+ Close();
+
+ }
+}
\ No newline at end of file
diff --git a/DiscordBotUI/app.manifest b/DiscordBotUI/app.manifest
new file mode 100644
index 0000000..1a06515
--- /dev/null
+++ b/DiscordBotUI/app.manifest
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+