62 Commits

Author SHA1 Message Date
3858156393 Made Entry Class static 2023-10-22 12:52:52 +03:00
6279c5c3a9 Updated Logger and Created Command to change settings variables 2023-10-01 14:11:34 +03:00
f58a57c6cd Improved logging. 2023-09-26 21:46:54 +03:00
d00ebfd7ed Fixed new name in README 2023-09-25 22:14:53 +03:00
ab279bd284 updated README.md 2023-09-25 22:12:44 +03:00
89c4932cd7 Fixed plugin refresh command and added new method for executing tasks without return type in ConsoleUtilities 2023-09-25 22:06:42 +03:00
c577f625c2 New method to execute using a progress bar feedback on process 2023-09-18 23:54:04 +03:00
58624f4037 Improved download speed and started using Spectre.Console package 2023-09-18 23:13:44 +03:00
c9249dc71b Plugin install does not display all logs when reloading plugin list 2023-09-07 14:55:09 +03:00
5e4f1ca35f The plugin install command now automatically refreshes the installed plugins. 2023-09-07 14:43:10 +03:00
0d8fdb5904 Updated log manager 2023-09-07 13:28:49 +03:00
92a18e3495 Removed URLs file from bot config 2023-09-07 12:50:36 +03:00
e929646e8e removed error when invalid plugin. It was called even when a typo was made 2023-08-15 16:42:13 +03:00
6315d13d18 Fixed Internal Actions to refresh after external actions are loaded 2023-08-15 16:17:32 +03:00
ee527bb36f a 2023-08-08 22:21:36 +03:00
86514d1770 Fixed version 2023-08-08 22:20:16 +03:00
9e8ed1e911 Fixed version 2023-08-08 22:19:29 +03:00
f3c3c7939c Fixed some outputs in plugin manager 2023-08-06 17:37:02 +03:00
361ed37362 Reimplemented error handling for SettingsFile 2023-08-06 17:14:57 +03:00
ed3128b940 Created a batch file to build on windows 2023-08-06 14:25:18 +03:00
6bbd68a135 Fixed release Config not found bug 2023-08-05 21:54:05 +03:00
06d322b5b3 Removed temp file creation 2023-08-05 21:53:08 +03:00
5497ee9119 - 2023-08-05 21:33:16 +03:00
0aa78e3560 Changed README.md 2023-08-05 21:32:49 +03:00
41ad37b3bb Fixed a bug when loading empty json file 2023-08-05 21:25:47 +03:00
0104d09509 Delete .github/workflows directory 2023-08-01 21:33:45 +03:00
b4f5e40f12 Moved items and reimplemented SettingsDictionary.cs 2023-08-01 21:28:44 +03:00
7107b17f19 Update README.md 2023-07-31 22:15:24 +03:00
42e1fd917e Removed some logs from console about json files that are opened 2023-07-31 22:10:26 +03:00
9e6fcdbe6f removed submodules 2023-07-31 21:47:43 +03:00
a24d8aa222 Removed dependencies 2023-07-31 21:11:17 +03:00
0e5c9ff14b Fixed help message on /help command 2023-07-31 20:55:38 +03:00
f8977d8840 Update submodules 2023-07-30 23:09:34 +03:00
bb9768f3a1 patch 2023-07-30 22:30:53 +03:00
b3d6930142 Updated functions in Discord Bot 2023-07-30 22:26:43 +03:00
5254be44be Added plugins folder to solution 2023-07-30 18:36:41 +03:00
207e0d6abd Added plugins submodule 2023-07-30 18:32:47 +03:00
968d23380f message 2023-07-30 18:31:06 +03:00
fff9e2e897 - 2023-07-30 18:27:05 +03:00
3e4e777d8d updated project file 2023-07-30 18:22:20 +03:00
7f906d400f - 2023-07-20 20:38:52 +03:00
c1161a3bca Bot no longer exits when a plugin fails to load 2023-07-20 11:32:15 +03:00
ac512e3a27 Updated display when installing a new plugin 2023-07-16 00:18:27 +03:00
730b628fe3 New method in archive manager and shortcut on exit command 2023-07-15 23:23:06 +03:00
701edc5c6a Update README.md 2023-07-10 21:29:36 +03:00
e7688762b8 Moved to API v11 due to the discriminator removal from discord 2023-07-05 21:01:51 +03:00
a7a71bf49a The logger now supports colors 2023-07-05 19:30:38 +03:00
ac7212ca00 Merge remote-tracking branch 'origin/preview' into preview 2023-07-03 14:40:08 +03:00
298e557260 Fixed some text and added some missing texts to commands. Added new command to clear screen and formated code. 2023-07-03 14:39:50 +03:00
7ba791f906 Create dotnet.yml 2023-07-01 17:37:59 +03:00
4a6a12baae Moved to API v3.10.0 2023-06-26 14:55:54 +03:00
f1dda5da3c Added new functions into Functions.cs and added help command for slash Commands. 2023-06-26 14:51:15 +03:00
3ab96e2d0d Fixed some warnings 2023-06-25 21:35:53 +03:00
970c519a32 Fixed loading plugins at startup 2023-06-15 22:12:15 +03:00
188920ec7f Cleaned up after removal of old commands 2023-06-15 21:59:48 +03:00
dcfc4ea32f Removed ConsoleCommandsHandler_OLD.cs 2023-06-15 21:57:22 +03:00
a8c02176d3 Fixed bug in PluginManager at getting info about plugin & new console commands 2023-06-15 21:55:42 +03:00
1665d47a25 Fixed a bug with invalid token bot startup 2023-06-15 14:46:28 +03:00
0b2f1e6ab6 Reimplemented Command Actions. 2023-06-07 20:36:11 +03:00
bcd9245502 update to display the changelogs for application when an update was found 2023-05-28 19:17:06 +03:00
59da9b295b 2023-05-28 19:14:39 +03:00
99d7d5e7e7 self update removed 2023-05-28 19:08:23 +03:00
54 changed files with 2554 additions and 2392 deletions

2
.gitignore vendored
View File

@@ -372,5 +372,5 @@ FodyWeavers.xsd
/DiscordBot/Data/ /DiscordBot/Data/
/DiscordBot/Updater/ /DiscordBot/Updater/
.idea/ .idea/
/DiscordBotWeb/
DiscordBot/Launcher.exe DiscordBot/Launcher.exe
DiscordBotUI/*

View File

@@ -0,0 +1,23 @@
using System;
using System.Threading.Tasks;
using PluginManager.Interfaces;
using PluginManager.Others;
namespace DiscordBot.Bot.Actions;
public class Clear : ICommandAction
{
public string ActionName => "clear";
public string Description => "Clears the console";
public string Usage => "clear";
public InternalActionRunType RunType => InternalActionRunType.ON_CALL;
public Task Execute(string[] args)
{
Console.Clear();
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("===== Seth Discord Bot =====");
Console.ResetColor();
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Threading.Tasks;
using PluginManager;
using PluginManager.Interfaces;
using PluginManager.Others;
namespace DiscordBot.Bot.Actions;
public class Exit : ICommandAction
{
public string ActionName => "exit";
public string Description => "Exits the bot and saves the config. Use exit help for more info.";
public string Usage => "exit [help|force (-f)]";
public InternalActionRunType RunType => InternalActionRunType.ON_CALL;
public async Task Execute(string[] args)
{
if (args is null || args.Length == 0)
{
Config.Logger.Log("Exiting...", source: typeof(ICommandAction), type: LogType.WARNING);
await Config.AppSettings.SaveToFile();
Environment.Exit(0);
}
else
{
switch ( args[0] )
{
case "help":
Console.WriteLine("Usage : exit [help|force]");
Console.WriteLine("help : Displays this message");
Console.WriteLine("force | -f : Exits the bot without saving the config");
break;
case "-f":
case "force":
Config.Logger.Log("Exiting (FORCE)...", source: typeof(ICommandAction), type: LogType.WARNING);
Environment.Exit(0);
break;
default:
Console.WriteLine("Invalid argument !");
break;
}
}
}
}

View File

@@ -0,0 +1,204 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using DiscordBot.Utilities;
using PluginManager;
using PluginManager.Interfaces;
using PluginManager.Loaders;
using PluginManager.Online;
using PluginManager.Others;
using Spectre.Console;
namespace DiscordBot.Bot.Actions.Extra;
internal static class PluginMethods
{
private static readonly PluginsManager PluginsManager = new();
internal static async Task List()
{
var data = await ConsoleUtilities.ExecuteWithProgressBar(PluginsManager.GetAvailablePlugins(), "Loading plugins...");
TableData tableData = new(new List<string> { "Name", "Description", "Type", "Version" });
foreach (var plugin in data) tableData.AddRow(plugin);
tableData.HasRoundBorders = false;
tableData.PrintAsTable();
}
internal static async Task RefreshPlugins(bool quiet)
{
await Program.internalActionManager.Execute("plugin", "load", quiet ? "-q" : string.Empty);
await Program.internalActionManager.Refresh();
}
internal static async Task DownloadPlugin(PluginsManager manager, string pluginName)
{
var pluginData = await manager.GetPluginLinkByName(pluginName);
if (pluginData.Length == 0)
{
Console.WriteLine($"Plugin {pluginName} not found. Please check the spelling and try again.");
return;
}
var pluginType = pluginData[0];
var pluginLink = pluginData[1];
var pluginRequirements = pluginData[2];
await AnsiConsole.Progress()
.Columns(new ProgressColumn[]
{
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn()
})
.StartAsync(async ctx =>
{
var downloadTask = ctx.AddTask("Downloading plugin...");
IProgress<float> progress = new Progress<float>(p => { downloadTask.Value = p; });
await ServerCom.DownloadFileAsync(pluginLink, $"./Data/{pluginType}s/{pluginName}.dll", progress);
downloadTask.Increment(100);
ctx.Refresh();
});
if (pluginRequirements == string.Empty)
{
Console.WriteLine("Finished installing " + pluginName + " successfully");
await RefreshPlugins(false);
return;
}
List<string> requirementsUrLs = new();
await AnsiConsole.Progress()
.Columns(new ProgressColumn[]
{
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn()
})
.StartAsync(async ctx =>
{
var gatherInformationTask = ctx.AddTask("Gathering info...");
gatherInformationTask.IsIndeterminate = true;
requirementsUrLs = await ServerCom.ReadTextFromURL(pluginRequirements);
await Task.Delay(2000);
gatherInformationTask.Increment(100);
});
await AnsiConsole.Progress()
.Columns(new ProgressColumn[]
{
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn()
})
.StartAsync(async ctx =>
{
List<Tuple<ProgressTask, IProgress<float>, Task>> downloadTasks = new();
foreach (var info in requirementsUrLs)
{
if (info.Length < 2) continue;
string[] data = info.Split(',');
string url = data[0];
string fileName = data[1];
var task = ctx.AddTask($"Downloading {fileName}...");
IProgress<float> progress = new Progress<float>(p =>
{
task.Value = p;
});
var downloadTask = ServerCom.DownloadFileAsync(url, $"./{fileName}", progress);
downloadTasks.Add(new Tuple<ProgressTask, IProgress<float>, Task>(task, progress, downloadTask));
}
foreach (var task in downloadTasks)
{
await task.Item3;
}
});
await RefreshPlugins(false);
}
internal static async Task<bool> LoadPlugins(string[] args)
{
var loader = new PluginLoader(Config.DiscordBot.client);
if (args.Length == 2 && args[1] == "-q")
{
loader.LoadPlugins();
return true;
}
var cc = Console.ForegroundColor;
loader.onCMDLoad += (name, typeName, success, exception) =>
{
if (name == null || name.Length < 2)
name = typeName;
if (success)
{
Config.Logger.Log("Successfully loaded command : " + name, source: typeof(ICommandAction),
type: LogType.INFO);
}
else
{
Config.Logger.Log("Failed to load command : " + name + " because " + exception?.Message,
source: typeof(ICommandAction), type: LogType.ERROR);
}
Console.ForegroundColor = cc;
};
loader.onEVELoad += (name, typeName, success, exception) =>
{
if (name == null || name.Length < 2)
name = typeName;
if (success)
{
Config.Logger.Log("Successfully loaded event : " + name, source: typeof(ICommandAction),
type: LogType.INFO);
}
else
{
Config.Logger.Log("Failed to load event : " + name + " because " + exception?.Message,
source: typeof(ICommandAction), type: LogType.ERROR);
}
Console.ForegroundColor = cc;
};
loader.onSLSHLoad += (name, typeName, success, exception) =>
{
if (name == null || name.Length < 2)
name = typeName;
if (success)
{
Config.Logger.Log("Successfully loaded slash command : " + name, source: typeof(ICommandAction),
type: LogType.INFO);
}
else
{
Config.Logger.Log("Failed to load slash command : " + name + " because " + exception?.Message,
source: typeof(ICommandAction), type: LogType.ERROR);
}
Console.ForegroundColor = cc;
};
loader.LoadPlugins();
Console.ForegroundColor = cc;
return true;
}
}

View File

@@ -0,0 +1,43 @@
using System.Linq;
using PluginManager;
namespace DiscordBot.Bot.Actions.Extra;
internal static class SettingsConfigExtra
{
internal static void SetSettings(string key, params string[] value)
{
if (key is null) return;
if (value is null) return;
if (!Config.AppSettings.ContainsKey(key))
return;
Config.AppSettings[key] = string.Join(' ', value);
// Config.AppSettings.SaveToFile().Wait();
}
internal static void RemoveSettings(string key)
{
if (key is null) return;
if(!Config.AppSettings.ContainsKey(key))
return;
Config.AppSettings.Remove(key);
}
internal static void AddSettings(string key, params string[] value)
{
if (key is null) return;
if (value is null) return;
if (Config.AppSettings.ContainsKey(key))
return;
Config.AppSettings.Add(key, string.Join(' ', value));
// Config.AppSettings.SaveToFile().Wait();
}
}

View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using DiscordBot.Utilities;
using PluginManager.Interfaces;
using PluginManager.Others;
namespace DiscordBot.Bot.Actions;
public class Help : ICommandAction
{
public string ActionName => "help";
public string Description => "Shows the list of commands and their usage";
public string Usage => "help [command]";
public InternalActionRunType RunType => InternalActionRunType.ON_CALL;
public async Task Execute(string[] args)
{
if (args == null || args.Length == 0)
{
var items = new List<string[]>
{
new[] { "-", "-", "-" },
new[] { "Command", "Usage", "Description" },
new[] { "-", "-", "-" }
};
foreach (var a in Program.internalActionManager.Actions)
items.Add(new[] { a.Key, a.Value.Usage, a.Value.Description });
items.Add(new[] { "-", "-", "-" });
ConsoleUtilities.FormatAndAlignTable(items,
TableFormat.CENTER_EACH_COLUMN_BASED
);
return;
}
if (!Program.internalActionManager.Actions.ContainsKey(args[0]))
{
Console.WriteLine("Command not found");
return;
}
var action = Program.internalActionManager.Actions[args[0]];
var actionData = new List<string[]>
{
new[] { "-", "-", "-" },
new[] { "Command", "Usage", "Description" },
new[] { "-", "-", "-" },
new[] { action.ActionName, action.Usage, action.Description },
new[] { "-", "-", "-" }
};
ConsoleUtilities.FormatAndAlignTable(actionData,
TableFormat.CENTER_EACH_COLUMN_BASED
);
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Input;
using DiscordBot.Bot.Actions.Extra;
using PluginManager;
using PluginManager.Interfaces;
using PluginManager.Loaders;
using PluginManager.Online;
using PluginManager.Others;
using Spectre.Console;
namespace DiscordBot.Bot.Actions;
public class Plugin : ICommandAction
{
private bool pluginsLoaded;
public string ActionName => "plugin";
public string Description => "Manages plugins. Use plugin help for more info.";
public string Usage => "plugin [help|list|load|install|refresh]";
public InternalActionRunType RunType => InternalActionRunType.ON_CALL;
public async Task Execute(string[] args)
{
if (args is null || args.Length == 0 || args[0] == "help")
{
Console.WriteLine("Usage : plugin [help|list|load|install]");
Console.WriteLine("help : Displays this message");
Console.WriteLine("list : Lists all plugins");
Console.WriteLine("load : Loads all plugins");
Console.WriteLine("install : Installs a plugin");
Console.WriteLine("refresh : Refreshes the plugin list");
return;
}
var manager = new PluginsManager();
switch (args[0])
{
case "refresh":
await PluginMethods.RefreshPlugins(true);
break;
case "list":
await PluginMethods.List();
break;
case "load":
if (pluginsLoaded)
{
Config.Logger.Log("Plugins already loaded", source: typeof(ICommandAction), type: LogType.WARNING);
break;
}
if (Config.DiscordBot is null)
{
Config.Logger.Log("DiscordBot is null", source: typeof(ICommandAction), type: LogType.WARNING);
break;
}
pluginsLoaded = await PluginMethods.LoadPlugins(args);
break;
case "install":
var pluginName = string.Join(' ', args, 1, args.Length - 1);
if (string.IsNullOrEmpty(pluginName) || pluginName.Length < 2)
{
Console.WriteLine("Please specify a plugin name");
Console.Write("Plugin name : ");
pluginName = Console.ReadLine();
if (string.IsNullOrEmpty(pluginName) || pluginName.Length < 2)
{
Console.WriteLine("Invalid plugin name");
break;
}
}
await PluginMethods.DownloadPlugin(manager, pluginName);
break;
}
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Threading.Tasks;
using DiscordBot.Bot.Actions.Extra;
using PluginManager;
using PluginManager.Interfaces;
using PluginManager.Others;
namespace DiscordBot.Bot.Actions;
public class SettingsConfig : ICommandAction
{
public string ActionName => "config";
public string Description => "Change the settings of the bot";
public string Usage => "config [options] <setting?> <value?>";
public InternalActionRunType RunType => InternalActionRunType.ON_CALL;
public Task Execute(string[] args)
{
if (args is null)
{
foreach (var settings in Config.AppSettings)
Console.WriteLine(settings.Key + ": " + settings.Value);
return Task.CompletedTask;
}
switch (args[0])
{
case "-s":
case "set":
if(args.Length < 3)
return Task.CompletedTask;
SettingsConfigExtra.SetSettings(args[1],args[2..]);
break;
case "-r":
case "remove":
if(args.Length < 2)
return Task.CompletedTask;
SettingsConfigExtra.RemoveSettings(args[1]);
break;
case "-a":
case "add":
if(args.Length < 3)
return Task.CompletedTask;
SettingsConfigExtra.AddSettings(args[1], args[2..]);
break;
case "-h":
case "-help":
Console.WriteLine("Options:");
Console.WriteLine("-s <settingName> <newValue>: Set a setting");
Console.WriteLine("-r <settingName>: Remove a setting");
Console.WriteLine("-a <settingName> <newValue>: Add a setting");
Console.WriteLine("-h: Show this help message");
break;
default:
Console.WriteLine("Invalid option");
return Task.CompletedTask;
}
return Task.CompletedTask;
}
}

View File

@@ -1,13 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using Discord; using Discord;
using PluginManager; using PluginManager;
using PluginManager.Interfaces; using PluginManager.Interfaces;
using PluginManager.Loaders; using PluginManager.Loaders;
using PluginManager.Others; using PluginManager.Others;
namespace DiscordBot.Discord.Commands; namespace DiscordBot.Bot.Commands;
/// <summary> /// <summary>
/// The help command /// The help command
@@ -77,10 +75,11 @@ internal class Help : DBCommand
{ {
var embedBuilder = new EmbedBuilder(); var embedBuilder = new EmbedBuilder();
var cmd = PluginLoader.Commands.Find(p => p.Command == command || var cmd = PluginLoader.Commands.Find(p => p.Command == command ||
(p.Aliases is not null && p.Aliases.Contains(command))); p.Aliases is not null && p.Aliases.Contains(command)
);
if (cmd == null) return null; if (cmd == null) return null;
embedBuilder.AddField("Usage", Config.Data["prefix"] + cmd.Usage); embedBuilder.AddField("Usage", Config.AppSettings["prefix"] + cmd.Usage);
embedBuilder.AddField("Description", cmd.Description); embedBuilder.AddField("Description", cmd.Description);
if (cmd.Aliases is null) if (cmd.Aliases is null)
return embedBuilder; return embedBuilder;

View File

@@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Linq;
using Discord;
using Discord.WebSocket;
using PluginManager.Interfaces;
using PluginManager.Loaders;
using PluginManager.Others;
namespace DiscordBot.Bot.Commands.SlashCommands;
public class Help : DBSlashCommand
{
public string Name => "help";
public string Description => "This command allows you to check all loaded commands";
public bool canUseDM => true;
public List<SlashCommandOptionBuilder> Options =>
new()
{
new SlashCommandOptionBuilder()
.WithName("command")
.WithDescription("The command you want to get help for")
.WithRequired(false)
.WithType(ApplicationCommandOptionType.String)
};
public async void ExecuteServer(SocketSlashCommand context)
{
EmbedBuilder embedBuilder = new();
embedBuilder.WithTitle("Help Command");
embedBuilder.WithColor(Functions.RandomColor);
var slashCommands = PluginLoader.SlashCommands;
var options = context.Data.Options;
//Console.WriteLine("Options: " + options.Count);
if (options is null || options.Count == 0)
foreach (var slashCommand in slashCommands)
embedBuilder.AddField(slashCommand.Name, slashCommand.Description);
if (options.Count > 0)
{
var commandName = options.First().Name;
var slashCommand = slashCommands.FirstOrDefault(x => x.Name == commandName);
if (slashCommand is null)
{
await context.RespondAsync("Unknown Command " + commandName);
return;
}
embedBuilder.AddField(slashCommand.Name, slashCommand.canUseDM)
.WithDescription(slashCommand.Description);
}
await context.RespondAsync(embed: embedBuilder.Build());
}
}

View File

@@ -7,7 +7,7 @@
<StartupObject/> <StartupObject/>
<SignAssembly>False</SignAssembly> <SignAssembly>False</SignAssembly>
<IsPublishable>True</IsPublishable> <IsPublishable>True</IsPublishable>
<AssemblyVersion>1.0.2.1</AssemblyVersion> <AssemblyVersion>1.0.3.1</AssemblyVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>none</DebugType> <DebugType>none</DebugType>
@@ -29,9 +29,13 @@
<None Remove="Data\**"/> <None Remove="Data\**"/>
<None Remove="obj\**"/> <None Remove="obj\**"/>
<None Remove="Output\**"/> <None Remove="Output\**"/>
<None Remove="builder.bat" />
<None Remove="builder.sh" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Discord.Net" Version="3.9.0" /> <PackageReference Include="Discord.Net" Version="3.11.0"/>
<PackageReference Include="pythonnet" Version="3.0.1" />
<PackageReference Include="Spectre.Console" Version="0.47.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\PluginManager\PluginManager.csproj"/> <ProjectReference Include="..\PluginManager\PluginManager.csproj"/>

View File

@@ -1,31 +1,38 @@
using PluginManager.Others; using System;
using System;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
namespace DiscordBot namespace DiscordBot;
{
public class Entry public static class Entry
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
AppDomain currentDomain = AppDomain.CurrentDomain; #if DEBUG
currentDomain.AssemblyResolve += new ResolveEventHandler(LoadFromSameFolder); if (args.Length == 1 && args[0] == "/purge_plugins")
{
foreach (var plugin in Directory.GetFiles("./Data/Plugins", "*.dll", SearchOption.AllDirectories))
{
File.Delete(plugin);
}
}
#endif
var currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += LoadFromSameFolder;
static Assembly LoadFromSameFolder(object sender, ResolveEventArgs args) static Assembly LoadFromSameFolder(object sender, ResolveEventArgs args)
{ {
string folderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "./Libraries"); var folderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "./Libraries");
string assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll"); var assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll");
if (!File.Exists(assemblyPath)) return null; if (!File.Exists(assemblyPath)) return null;
Assembly assembly = Assembly.LoadFrom(assemblyPath); var assembly = Assembly.LoadFrom(assemblyPath);
return assembly; return assembly;
} }
Program.Startup(args); Program.Startup(args);
}
} }
} }

View File

@@ -1,117 +1,28 @@
using System.Diagnostics;
using System.IO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using PluginManager; using PluginManager;
using PluginManager.Others; using Spectre.Console;
using PluginManager.Online;
namespace DiscordBot;
namespace DiscordBot
{
public static class Installer public static class Installer
{ {
public static void GenerateStartupConfig() public static void GenerateStartupConfig()
{ {
Console.WriteLine("Welcome to the SethBot installer !"); AnsiConsole.MarkupLine("Welcome to the [bold]SethBot[/] installer !");
Console.WriteLine("First, we need to configure the bot. Don't worry, it will be quick !"); AnsiConsole.MarkupLine("First, we need to configure the bot. Don't worry, it will be quick !");
Console.WriteLine("The following information will be stored in the config.json file in the ./Data/Resources folder. You can change it later from there.");
Console.WriteLine("The bot tokn is required to run the bot. You can get it from the Discord Developer Portal. (https://discord.com/developers/applications)");
Console.WriteLine("Please enter the bot token :");
var token = Console.ReadLine();
Console.WriteLine("Please enter the bot prefix :"); var token = AnsiConsole.Ask<string>("Please enter the bot [yellow]token[/]:");
var prefix = Console.ReadLine(); var prefix = AnsiConsole.Ask<string>("Please enter the bot [yellow]prefix[/]:");
var serverId = AnsiConsole.Ask<string>("Please enter the [yellow]Server ID[/]:");
Console.WriteLine("Please enter the Server ID :"); if (string.IsNullOrWhiteSpace(serverId)) serverId = "NULL";
var serverId = Console.ReadLine(); Config.AppSettings.Add("token", token);
Config.AppSettings.Add("prefix", prefix);
Config.AppSettings.Add("ServerID", serverId);
Config.Data.Add("token", token); Config.AppSettings.SaveToFile();
Config.Data.Add("prefix", prefix);
Config.Data.Add("ServerID", serverId);
Config.Logger.Log("Config Saved", "Installer", LogLevel.INFO); AnsiConsole.MarkupLine("[bold]Config saved ![/]");
Config.Data.Save(); Config.Logger.Log("Config Saved", source: typeof(Installer));
Console.WriteLine("Config saved !");
}
public static async Task SetupPluginDatabase()
{
Console.WriteLine("The plugin database is required to run the bot but there is nothing configured yet.");
Console.WriteLine("Please select one option : ");
Console.WriteLine("1. Download the official database file");
Console.WriteLine("2. Create a new (CUSTOM) database file");
int choice = 0;
Console.Write("Choice : ");
choice = int.Parse(Console.ReadLine());
if (choice != 1 && choice != 2)
{
Console.WriteLine("Invalid choice !");
Console.WriteLine("Please restart the installer !");
Console.ReadKey();
Environment.Exit(0);
}
if (choice == 1)
await DownloadPluginDatabase();
if (choice == 2)
{
Console.WriteLine("Do you have a url to a valid database file ? (y/n)");
var answer = Console.ReadLine();
if (answer == "y")
{
Console.WriteLine("Please enter the url :");
var url = Console.ReadLine();
await DownloadPluginDatabase(url);
return;
}
Console.WriteLine("Do you want to create a new database file ? (y/n)");
answer = Console.ReadLine();
if (answer == "y")
{
Console.WriteLine("A new file will be generated at ./Data/Resources/URLs.json");
System.Console.WriteLine("Please edit the file and restart the bot !");
Directory.CreateDirectory("./Data/Resources");
await File.WriteAllTextAsync("./Data/Resources/URLs.json",
@"
{
""PluginList"": """",
""PluginVersions"": """",
""StartupMessage"": """",
""SetupKeys"": """",
""Versions"": """",
""Changelog"": """",
""LinuxBot"": """",
""WindowsLauncher"": """",
}
".Replace(" ", ""));
Environment.Exit(0);
return;
}
}
}
private static async Task DownloadPluginDatabase(string url = "https://raw.githubusercontent.com/Wizzy69/SethDiscordBot/gh-pages/defaultURLs.json")
{
string path = "./Data/Resources/URLs.json";
Directory.CreateDirectory("./Data/Resources");
Utilities.Utilities.ProgressBar bar = new Utilities.Utilities.ProgressBar(Utilities.ProgressBarType.NORMAL){
Max = 100,
Color = ConsoleColor.Green,
NoColor = true
};
IProgress<float> downloadProgress = new Progress<float>(p => bar.Update(p));
await ServerCom.DownloadFileAsync(url, path, downloadProgress, null);
bar.Update(bar.Max);
}
} }
} }

View File

@@ -1,50 +1,32 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Linq; using System.Linq;
using System.Reflection;
using PluginManager; using System.Threading.Tasks;
using PluginManager.Bot;
using PluginManager.Online;
using PluginManager.Online.Helpers;
using PluginManager.Others;
using DiscordBot.Utilities; using DiscordBot.Utilities;
using Microsoft.VisualBasic.CompilerServices; using PluginManager.Bot;
using OperatingSystem = PluginManager.Others.OperatingSystem; using PluginManager.Others;
using PluginManager.Others.Actions;
using Spectre.Console;
using static PluginManager.Config; using static PluginManager.Config;
namespace DiscordBot; namespace DiscordBot;
public class Program public class Program
{ {
public static Json<string, string> URLs; public static InternalActionManager internalActionManager;
private static bool loadPluginsOnStartup = false;
private static ConsoleCommandsHandler consoleCommandsHandler;
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
/// </summary> /// </summary>
[STAThread]
public static void Startup(string[] args) public static void Startup(string[] args)
{ {
PreLoadComponents(args).Wait(); PreLoadComponents(args).Wait();
if (!Config.Data.ContainsKey("ServerID") || !Config.Data.ContainsKey("token") || if (!AppSettings.ContainsKey("ServerID") || !AppSettings.ContainsKey("token") || !AppSettings.ContainsKey("prefix"))
Config.Data["token"] == null ||
(Config.Data["token"]?.Length != 70 && Config.Data["token"]?.Length != 59) ||
!Config.Data.ContainsKey("prefix") || Config.Data["prefix"] == null ||
Config.Data["prefix"]?.Length != 1 ||
(args.Length == 1 && args[0] == "/reset"))
{
Installer.GenerateStartupConfig(); Installer.GenerateStartupConfig();
}
HandleInput(args.ToList()).Wait(); HandleInput().Wait();
} }
/// <summary> /// <summary>
@@ -52,23 +34,21 @@ public class Program
/// </summary> /// </summary>
private static void NoGUI() private static void NoGUI()
{ {
#if DEBUG internalActionManager.Initialize().Wait();
Console.WriteLine("Debug mode enabled"); internalActionManager.Execute("plugin", "load").Wait();
internalActionManager.Refresh().Wait();
#endif
if (loadPluginsOnStartup)
consoleCommandsHandler.HandleCommand("lp");
while (true) while (true)
{ {
var cmd = Console.ReadLine(); var cmd = Console.ReadLine();
if (!consoleCommandsHandler.HandleCommand(cmd! var args = cmd.Split(' ');
#if DEBUG var command = args[0];
, false args = args.Skip(1).ToArray();
#endif if (args.Length == 0)
args = null;
) && cmd.Length > 0) internalActionManager.Execute(command, args).Wait(); // Execute the command
Console.WriteLine("Failed to run command " + cmd);
} }
} }
@@ -76,53 +56,31 @@ public class Program
/// Start the bot without user interface /// Start the bot without user interface
/// </summary> /// </summary>
/// <returns>Returns the boot loader for the Discord Bot</returns> /// <returns>Returns the boot loader for the Discord Bot</returns>
private static async Task<Boot> StartNoGui() private static async Task StartNoGui()
{ {
Console.Clear(); Console.Clear();
Console.ForegroundColor = ConsoleColor.DarkYellow; Console.ForegroundColor = ConsoleColor.DarkYellow;
var startupMessageList = Console.WriteLine($"Running on version: {Assembly.GetExecutingAssembly().GetName().Version}");
await ServerCom.ReadTextFromURL(URLs["StartupMessage"]); Console.WriteLine("Git SethBot: https://github.com/andreitdr/SethDiscordBot");
Console.WriteLine("Git Plugins: https://github.com/andreitdr/SethPlugins");
foreach (var message in startupMessageList) ConsoleUtilities.WriteColorText("&rRemember to close the bot using the ShutDown command (&yexit&r) or some settings won't be saved");
Console.WriteLine(message);
Console.WriteLine( ConsoleUtilities.WriteColorText($"Running on &m{Functions.GetOperatingSystem()}");
$"Running on version: {Assembly.GetExecutingAssembly().GetName().Version}");
Console.WriteLine($"Git URL: {Config.Data["GitURL"]}");
Utilities.Utilities.WriteColorText(
"&rRemember to close the bot using the ShutDown command (&ysd&r) or some settings won't be saved\n");
Console.ForegroundColor = ConsoleColor.White;
if (Config.Data.ContainsKey("LaunchMessage"))
Utilities.Utilities.WriteColorText(Config.Data["LaunchMessage"]);
Utilities.Utilities.WriteColorText(
"Please note that the bot saves a backup save file every time you are using the shudown command (&ysd&c)");
Console.WriteLine("Running on " + Functions.GetOperatingSystem().ToString());
Console.WriteLine("============================ LOG ============================"); Console.WriteLine("============================ LOG ============================");
Console.ForegroundColor = ConsoleColor.White;
try try
{ {
string token = ""; var token = AppSettings["token"];
#if DEBUG var prefix = AppSettings["prefix"];
if (File.Exists("./Data/Resources/token.txt")) token = File.ReadAllText("./Data/Resources/token.txt");
else token = Config.Data["token"];
#else
token = Config.Data["token"];
#endif
var prefix = Config.Data["prefix"];
var discordbooter = new Boot(token, prefix); var discordbooter = new Boot(token, prefix);
await discordbooter.Awake(); await discordbooter.Awake();
return discordbooter;
} }
catch ( Exception ex ) catch ( Exception ex )
{ {
Config.Logger.Log(ex.ToString(), "Bot", LogLevel.ERROR); Logger.Log(ex.ToString(), source: typeof(Program), type: LogType.CRITICAL);
return null;
} }
} }
@@ -130,199 +88,55 @@ public class Program
/// Handle user input arguments from the startup of the application /// Handle user input arguments from the startup of the application
/// </summary> /// </summary>
/// <param name="args">The arguments</param> /// <param name="args">The arguments</param>
private static async Task HandleInput(List<string> args) private static async Task HandleInput()
{ {
await StartNoGui();
Console.WriteLine("Loading Core ...");
var b = await StartNoGui();
consoleCommandsHandler = new ConsoleCommandsHandler(b.client);
try try
{ {
if(args.Contains("--gui")) internalActionManager = new InternalActionManager("./Data/Plugins", "*.dll");
{
// GUI not implemented yet
Console.WriteLine("GUI not implemented yet");
return;
}
NoGUI(); NoGUI();
} }
catch ( IOException ex ) catch ( IOException ex )
{ {
if (ex.Message == "No process is on the other end of the pipe." || (uint)ex.HResult == 0x800700E9) if (ex.Message == "No process is on the other end of the pipe." || (uint)ex.HResult == 0x800700E9)
{ {
if (Config.Data.ContainsKey("LaunchMessage")) if (AppSettings.ContainsKey("LaunchMessage"))
Config.Data.Add("LaunchMessage", AppSettings.Add("LaunchMessage",
"An error occured while closing the bot last time. Please consider closing the bot using the &rsd&c method !\nThere is a risk of losing all data or corruption of the save file, which in some cases requires to reinstall the bot !"); "An error occured while closing the bot last time. Please consider closing the bot using the &rexit&c method !\n" +
Config.Logger.Log("An error occured while closing the bot last time. Please consider closing the bot using the &rsd&c method !\nThere is a risk of losing all data or corruption of the save file, which in some cases requires to reinstall the bot !", "Bot", LogLevel.ERROR); "There is a risk of losing all data or corruption of the save file, which in some cases requires to reinstall the bot !");
Logger.Log("An error occured while closing the bot last time. Please consider closing the bot using the &rexit&c method !\n" +
"There is a risk of losing all data or corruption of the save file, which in some cases requires to reinstall the bot !",
source: typeof(Program), type: LogType.ERROR);
} }
} }
return;
} }
private static async Task PreLoadComponents(string[] args) private static async Task PreLoadComponents(string[] args)
{ {
await Initialize();
await Config.Initialize(); Logger.OnLog += (sender, logMessage) =>
if (!Directory.Exists("./Data/Resources") || !File.Exists("./Data/Resources/URLs.json"))
{ {
await Installer.SetupPluginDatabase(); string messageColor = logMessage.Type switch
}
URLs = new Json<string, string>("./Data/Resources/URLs.json");
Config.Logger.LogEvent += (message, type) => { Console.WriteLine(message); };
Console.WriteLine("Loading resources ...");
var main = new Utilities.Utilities.ProgressBar(ProgressBarType.NO_END);
main.Start();
if (Config.Data.ContainsKey("DeleteLogsAtStartup"))
if (Config.Data["DeleteLogsAtStartup"] == "true")
foreach (var file in Directory.GetFiles("./Output/Logs/"))
File.Delete(file);
var OnlineDefaultKeys =
await ServerCom.ReadTextFromURL(URLs["SetupKeys"]);
Config.Data["Version"] = Assembly.GetExecutingAssembly().GetName().Version.ToString();
foreach (var key in OnlineDefaultKeys)
{ {
if (key.Length <= 3 || !key.Contains(' ')) continue; LogType.INFO => "[green]",
var s = key.Split(' '); LogType.WARNING => "[yellow]",
try LogType.ERROR => "[red]",
LogType.CRITICAL => "[red]",
_ => "[white]"
};
if (logMessage.Message.Contains('['))
{ {
Config.Data[s[0]] = s[1]; // If the message contains a tag, just print it as it is. No need to format it
} Console.WriteLine(logMessage.Message);
catch (Exception ex) return;
{
Config.Logger.Log(ex.ToString(), "Bot", LogLevel.ERROR);
}
}
var onlineSettingsList = await ServerCom.ReadTextFromURL(URLs["Versions"]);
main.Stop("Loaded online settings. Loading updates ...");
foreach (var key in onlineSettingsList)
{
if (key.Length <= 3 || !key.Contains(' ')) continue;
var s = key.Split(' ');
switch (s[0])
{
case "CurrentVersion":
var newVersion = s[1];
var currentVersion = Config.Data["Version"];
if (!newVersion.Equals(currentVersion))
{
var nVer = new VersionString(newVersion.Substring(2));
var cVer = new VersionString((Config.Data["Version"]).Substring(2));
if (cVer > nVer)
{
Config.Data["Version"] = "1." + cVer.ToShortString() + " (Beta)";
break;
}
if (OperatingSystem.WINDOWS == Functions.GetOperatingSystem())
{
Console.Clear();
Console.WriteLine("A new update was found !");
Console.WriteLine("Run the launcher to update");
Console.WriteLine("Current version: " + currentVersion);
Console.WriteLine("Latest version: " + s[1]);
File.WriteAllText("version.txt", currentVersion);
await Task.Delay(3000);
break;
}
Console.Clear();
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("A new version of the bot is available !");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Current version : " +
Assembly.GetExecutingAssembly().GetName().Version.ToString());
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("New version : " + newVersion);
Console.ForegroundColor = ConsoleColor.White;
File.WriteAllText("version.txt", newVersion);
Console.WriteLine("Changelog :");
List<string> changeLog = await ServerCom.ReadTextFromURL(URLs["Changelog"]);
foreach (var item in changeLog)
Utilities.Utilities.WriteColorText(item);
Console.WriteLine("Do you want to update the bot ? (y/n)");
if (Console.ReadKey().Key == ConsoleKey.Y)
{
var url = URLs["LinuxBot"].Replace("{0}", newVersion);
Config.Logger.Log($"executing: download_file {url}");
await ServerCom.DownloadFileAsync(url, "./update.zip", new Progress<float>(percent => { Console.WriteLine($"\rProgress: {percent}% "); }));
await File.WriteAllTextAsync("Install.sh",
"#!/bin/bash\nunzip -qq -o update.zip \nrm update.zip\nchmod a+x DiscordBot");
try
{
Console.WriteLine("executing: chmod a+x Install.sh");
Process.Start("chmod", "a+x Install.sh").WaitForExit();
Process.Start("Install.sh").WaitForExit();
Console.WriteLine("executing: rm Install.sh");
Process.Start("rm", "Install.sh").WaitForExit();
Config.Logger.Log("The new version of the bot has been installed.");
Console.WriteLine("Please restart the bot.");
Environment.Exit(0);
}
catch (Exception ex)
{
Config.Logger.Log(ex.Message, "Updater", LogLevel.ERROR);
if (ex.Message.Contains("Access de"))
Config.Logger.Log("Please run the bot as root.");
} }
AnsiConsole.MarkupLine($"{messageColor}{logMessage.ThrowTime} {logMessage.Message} [/]");
};
} AppSettings["Version"] = Assembly.GetExecutingAssembly().GetName().Version.ToString();
}
break;
case "LauncherVersion":
var updaternewversion = s[1];
//File.WriteAllText(updaternewversion + ".txt", updaternewversion);
if (Functions.GetOperatingSystem() == OperatingSystem.LINUX)
break;
Directory.CreateDirectory(Functions.dataFolder + "Applications");
if (!Config.Data.ContainsKey("LauncherVersion"))
Config.Data["LauncherVersion"] = "0.0.0.0";
if (Config.Data["LauncherVersion"] != updaternewversion ||
!File.Exists("./Launcher.exe"))
{
Console.Clear();
Console.WriteLine("Installing a new Launcher ...\nDo NOT close the bot during update !");
var bar = new Utilities.Utilities.ProgressBar(ProgressBarType.NO_END);
bar.Start();
await ServerCom.DownloadFileAsync(URLs["WindowsLauncher"], $"./Launcher.exe", null);
//await ArchiveManager.ExtractArchive("./Updater.zip", "./", null,
// UnzipProgressType.PercentageFromTotalSize);
Config.Data["LauncherVersion"] = updaternewversion;
// File.Delete("Updater.zip");
bar.Stop("The launcher has been updated !");
Console.Clear();
}
break;
}
}
Console.Clear();
} }
} }

View File

@@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
using Discord.Commands;
using Discord.WebSocket;
namespace DiscordBot.Utilities;
public class ConsoleCommand
{
public string? CommandName { get; init; }
public string? Description { get; init; }
public string? Usage { get; init; }
public Action<string[]>? Action { get; init; }
}

View File

@@ -1,15 +1,87 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Spectre.Console;
using PluginManager;
namespace DiscordBot.Utilities; namespace DiscordBot.Utilities;
public static class Utilities public class TableData
{ {
private static Dictionary<char, ConsoleColor> Colors = new() public List<string> Columns;
public List<string[]> Rows;
public bool IsEmpty => Rows.Count == 0;
public bool HasRoundBorders { get; set; } = true;
public TableData(List<string> columns)
{
Columns = columns;
Rows = new List<string[]>();
}
public TableData(string[] columns)
{
Columns = columns.ToList();
Rows = new List<string[]>();
}
public void AddRow(string[] row)
{
Rows.Add(row);
}
}
public static class ConsoleUtilities
{
public static async Task<T> ExecuteWithProgressBar<T>(Task<T> function, string message)
{
T result = default;
await AnsiConsole.Progress()
.Columns(
new ProgressColumn[]
{
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn(),
}
)
.StartAsync(
async ctx =>
{
var task = ctx.AddTask(message);
task.IsIndeterminate = true;
result = await function;
task.Increment(100);
}
);
return result;
}
public static async Task ExecuteWithProgressBar(Task function, string message)
{
await AnsiConsole.Progress()
.Columns(new ProgressColumn[]
{
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn(),
})
.StartAsync(async ctx =>
{
var task = ctx.AddTask(message);
task.IsIndeterminate = true;
await function;
task.Increment(100);
});
}
private static readonly Dictionary<char, ConsoleColor> Colors = new()
{ {
{ 'g', ConsoleColor.Green }, { 'g', ConsoleColor.Green },
{ 'b', ConsoleColor.Blue }, { 'b', ConsoleColor.Blue },
@@ -18,21 +90,45 @@ public static class Utilities
{ 'y', ConsoleColor.Yellow } { 'y', ConsoleColor.Yellow }
}; };
private static char ColorPrefix = '&'; private static readonly char ColorPrefix = '&';
private static bool CanAproximateTo(this float f, float y) private static bool CanAproximateTo(this float f, float y)
{ {
return MathF.Abs(f - y) < 0.000001; return MathF.Abs(f - y) < 0.000001;
} }
public static void PrintAsTable(this TableData tableData)
{
var table = new Table();
table.Border(tableData.HasRoundBorders ? TableBorder.Rounded : TableBorder.Square);
table.AddColumns(tableData.Columns.ToArray());
foreach (var row in tableData.Rows)
table.AddRow(row);
AnsiConsole.Write(table);
}
/// <summary> /// <summary>
/// A way to create a table based on input data /// A way to create a table based on input data
/// </summary> /// </summary>
/// <param name="data">The List of arrays of strings that represent the rows.</param> /// <param name="data">The List of arrays of string that represent the rows.</param>
public static void FormatAndAlignTable(List<string[]> data, TableFormat format) public static void FormatAndAlignTable(List<string[]> data, TableFormat format)
{ {
if (format == TableFormat.SPECTRE_CONSOLE)
{
var table = new Table();
table.Border(TableBorder.Rounded);
table.AddColumns(data[0]);
data.RemoveAt(0);
foreach (var row in data)
table.AddRow(row);
AnsiConsole.Write(table);
return;
}
if (format == TableFormat.CENTER_EACH_COLUMN_BASED) if (format == TableFormat.CENTER_EACH_COLUMN_BASED)
{ {
var tableLine = '-'; var tableLine = '-';
@@ -201,12 +297,14 @@ public static class Utilities
Console.WriteLine(); Console.WriteLine();
} }
public class Spinner public class Spinner
{ {
private Thread thread;
private bool isRunning;
private readonly string[] Sequence; private readonly string[] Sequence;
private bool isRunning;
public string Message;
private int position; private int position;
private Thread thread;
public Spinner() public Spinner()
{ {
@@ -218,19 +316,21 @@ public static class Utilities
{ {
Console.CursorVisible = false; Console.CursorVisible = false;
isRunning = true; isRunning = true;
thread = new Thread(() => { thread = new Thread(() =>
{
while (isRunning) while (isRunning)
{ {
Console.Write("\r" + Sequence[position]); Console.SetCursorPosition(0, Console.CursorTop);
Console.Write(" " + Sequence[position] + " " + Message + " ");
position++; position++;
if (position >= Sequence.Length) if (position >= Sequence.Length)
position = 0; position = 0;
Thread.Sleep(100); Thread.Sleep(100);
} }
}); }
);
thread.Start(); thread.Start();
} }
public void Stop() public void Stop()
@@ -239,161 +339,4 @@ public static class Utilities
Console.CursorVisible = true; Console.CursorVisible = true;
} }
} }
/// <summary>
/// Progress bar object
/// </summary>
public class ProgressBar
{
private readonly int BarLength = 32;
private bool isRunning;
private int position = 1;
private bool positive = true;
public ProgressBar(ProgressBarType type)
{
this.type = type;
}
public float Max { get; init; }
public ConsoleColor Color { get; init; }
public bool NoColor { get; init; }
public ProgressBarType type { get; set; }
public int TotalLength { get; private set; }
public async void Start()
{
Console.WriteLine();
if (type != ProgressBarType.NO_END)
throw new Exception("Only NO_END progress bar can use this method");
if (isRunning)
throw new Exception("This progress bar is already running");
isRunning = true;
while (isRunning)
{
UpdateNoEnd();
await Task.Delay(100);
}
}
public async void Start(string message)
{
if (type != ProgressBarType.NO_END)
throw new Exception("Only NO_END progress bar can use this method");
if (isRunning)
throw new Exception("This progress bar is already running");
isRunning = true;
TotalLength = message.Length + BarLength + 5;
while (isRunning)
{
UpdateNoEnd(message);
await Task.Delay(100);
}
}
public void Stop()
{
if (type != ProgressBarType.NO_END)
throw new Exception("Only NO_END progress bar can use this method");
if (!isRunning)
throw new Exception("Can not stop a progressbar that did not start");
isRunning = false;
}
public void Stop(string message)
{
Stop();
if (message is not null)
{
Console.CursorLeft = 0;
for (var i = 0; i < BarLength + message.Length + 1; i++)
Console.Write(" ");
Console.CursorLeft = 0;
Console.WriteLine(message);
}
}
public void Update(float progress)
{
if (type == ProgressBarType.NO_END)
throw new Exception("This function is for progress bars with end");
UpdateNormal(progress);
}
private void UpdateNoEnd(string message)
{
Console.CursorLeft = 0;
Console.Write("[");
for (var i = 1; i <= position; i++)
Console.Write(" ");
Console.Write("<==()==>");
position += positive ? 1 : -1;
for (var i = position; i <= BarLength - 1 - (positive ? 0 : 2); i++)
Console.Write(" ");
Console.Write("] " + message);
if (position == BarLength - 1 || position == 1)
positive = !positive;
}
private void UpdateNoEnd()
{
Console.CursorLeft = 0;
Console.Write("[");
for (var i = 1; i <= position; i++)
Console.Write(" ");
Console.Write("<==()==>");
position += positive ? 1 : -1;
for (var i = position; i <= BarLength - 1 - (positive ? 0 : 2); i++)
Console.Write(" ");
Console.Write("]");
if (position == BarLength - 1 || position == 1)
positive = !positive;
}
private void UpdateNormal(float progress)
{
Console.CursorLeft = 0;
Console.Write("[");
Console.CursorLeft = BarLength;
Console.Write("]");
Console.CursorLeft = 1;
var onechunk = 30.0f / Max;
var position = 1;
for (var i = 0; i < onechunk * progress; i++)
{
Console.BackgroundColor = NoColor ? ConsoleColor.Black : Color;
Console.CursorLeft = position++;
Console.Write("#");
}
for (var i = position; i < BarLength; i++)
{
Console.BackgroundColor = NoColor ? ConsoleColor.Black : ConsoleColor.DarkGray;
Console.CursorLeft = position++;
Console.Write(" ");
}
Console.CursorLeft = BarLength + 4;
Console.BackgroundColor = ConsoleColor.Black;
if (progress.CanAproximateTo(Max))
Console.Write(progress + " % ✓");
else
Console.Write(MathF.Round(progress, 2) + " % ");
}
}
} }

View File

@@ -1,439 +0,0 @@
using System.Threading;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using Discord.WebSocket;
using PluginManager;
using PluginManager.Loaders;
using PluginManager.Online;
using PluginManager.Others;
using OperatingSystem = PluginManager.Others.OperatingSystem;
namespace DiscordBot.Utilities;
public class ConsoleCommandsHandler
{
public static ConsoleCommandsHandler handler;
private readonly PluginsManager manager;
private readonly List<ConsoleCommand> commandList = new();
private bool isDownloading;
private bool pluginsLoaded;
private DiscordSocketClient client;
public ConsoleCommandsHandler(DiscordSocketClient client)
{
this.client = client;
manager = new PluginsManager(Program.URLs["PluginList"], Program.URLs["PluginVersions"]);
InitializeBasicCommands();
Console.WriteLine("Done");
if (handler == null)
handler = this;
else
throw new Exception("ConsoleCommandsHandler already initialized");
}
private void InitializeBasicCommands()
{
commandList.Clear();
AddCommand("help", "Show help", "help <command>", args =>
{
if (args.Length <= 1)
{
Console.WriteLine("Available commands:");
var items = new List<string[]>
{
new[] { "-", "-", "-" },
new[] { "Command", "Description", "Usage" },
new[] { " ", " ", "Argument type: <optional> [required]" },
new[] { "-", "-", "-" }
};
foreach (var command in commandList)
{
if (!command.CommandName.StartsWith("_"))
items.Add(new[] { command.CommandName, command.Description, command.Usage });
}
items.Add(new[] { "-", "-", "-" });
Utilities.FormatAndAlignTable(items, TableFormat.DEFAULT);
}
else
{
foreach (var command in commandList)
if (command.CommandName == args[1])
{
Console.WriteLine("Command description: " + command.Description);
Console.WriteLine("Command execution format:" + command.Usage);
return;
}
Console.WriteLine("Command not found");
}
}
);
AddCommand("lp", "Load plugins", () =>
{
if (pluginsLoaded)
return;
var loader = new PluginLoader(client!);
var cc = Console.ForegroundColor;
loader.onCMDLoad += (name, typeName, success, exception) =>
{
if (name == null || name.Length < 2)
name = typeName;
if (success)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("[CMD] Successfully loaded command : " + name);
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
if (exception is null)
Console.WriteLine("An error occured while loading: " + name);
else
Console.WriteLine("[CMD] Failed to load command : " + name + " because " + exception!.Message);
}
Console.ForegroundColor = cc;
};
loader.onEVELoad += (name, typeName, success, exception) =>
{
if (name == null || name.Length < 2)
name = typeName;
if (success)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("[EVENT] Successfully loaded event : " + name);
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("[EVENT] Failed to load event : " + name + " because " + exception!.Message);
}
Console.ForegroundColor = cc;
};
loader.onSLSHLoad += (name, typeName, success, exception) =>
{
if (name == null || name.Length < 2)
name = typeName;
if (success)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("[SLASH] Successfully loaded command : " + name);
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("[SLASH] Failed to load command : " + name + " because " + exception!.Message);
}
Console.ForegroundColor = cc;
};
loader.LoadPlugins();
Console.ForegroundColor = cc;
pluginsLoaded = true;
}
);
AddCommand("listplugs", "list available plugins", async () => {
if(manager == null)
{
Console.WriteLine("Plugin manager is null");
return;
}
var data = await manager.GetAvailablePlugins();
var items = new List<string[]>
{
new[] { "-", "-", "-", "-" },
new[] { "Name", "Type", "Description", "Required" },
new[] { "-", "-", "-", "-" }
};
foreach (var plugin in data)
{
items.Add(new[] { plugin[0], plugin[1], plugin[2], plugin[3] });
}
items.Add(new[] { "-", "-", "-", "-" });
Utilities.FormatAndAlignTable(items, TableFormat.DEFAULT);
});
AddCommand("dwplug", "download plugin", "dwplug [name]", async args =>
{
isDownloading = true;
Utilities.Spinner spinner = new Utilities.Spinner();
if (args.Length == 1)
{
isDownloading = false;
Console.WriteLine("Please specify plugin name");
return;
}
var name = string.Join(' ', args, 1, args.Length - 1);
// info[0] = plugin type
// info[1] = plugin link
// info[2] = if others are required, or string.Empty if none
var info = await manager.GetPluginLinkByName(name);
if (info[1] == null) // link is null
{
if (name == "")
{
isDownloading = false;
Utilities.WriteColorText("Name is invalid");
return;
}
isDownloading = false;
Utilities.WriteColorText($"Failed to find plugin &b{name} &c!" +
" Use &glistplugs &ccommand to display all available plugins !");
return;
}
spinner.Start();
string path;
if (info[0] == "Plugin")
path = "./Data/Plugins/" + name + ".dll";
else
path = $"./{info[1].Split('/')[info[1].Split('/').Length - 1]}";
Console.WriteLine($"Downloading plugin {name} from {info[1]} to {path}");
await ServerCom.DownloadFileAsync(info[1], path, null);
Console.WriteLine("\n");
// check requirements if any
if (info.Length == 3 && info[2] != string.Empty && info[2] != null)
{
Console.WriteLine($"Downloading requirements for plugin : {name}");
var lines = await ServerCom.ReadTextFromURL(info[2]);
foreach (var line in lines)
{
if (!(line.Length > 0 && line.Contains(",")))
continue;
var split = line.Split(',');
Console.WriteLine($"\nDownloading item: {split[1]}");
if (File.Exists("./" + split[1])) File.Delete("./" + split[1]);
await ServerCom.DownloadFileAsync(split[0], "./" + split[1], null);
Console.WriteLine("Item " + split[1] + " downloaded !");
Console.WriteLine();
if (split[0].EndsWith(".pak"))
{
File.Move("./" + split[1], "./Data/PAKS/" + split[1], true);
}
else if (split[0].EndsWith(".zip") || split[0].EndsWith(".pkg"))
{
Console.WriteLine($"Extracting {split[1]} ...");
await ArchiveManager.ExtractArchive("./" + split[1], "./", null,
UnzipProgressType.PERCENTAGE_FROM_TOTAL_SIZE);
Console.WriteLine("\n");
File.Delete("./" + split[1]);
}
}
Console.WriteLine();
}
spinner.Stop();
var ver = await manager.GetVersionOfPackageFromWeb(name);
if (ver is null) throw new Exception("Incorrect version");
Config.Plugins[name] = ver.ToShortString();
isDownloading = false;
Config.Logger.Log("Plugin installed !", this, LogLevel.INFO);
//await ExecuteCommad("localload " + name);
}
);
AddCommand("value", "read value from VariableStack", "value [key]", args =>
{
if (args.Length != 2)
return;
if (!Config.Data.ContainsKey(args[1]))
return;
var data = Config.Data[args[1]];
Console.WriteLine($"{args[1]} => {data}");
}
);
AddCommand("add", "add variable to the system variables", "add [key] [value]", args =>
{
if (args.Length < 4)
return;
var key = args[1];
var value = args[2];
var isReadOnly = args[3].Equals("true", StringComparison.CurrentCultureIgnoreCase);
try
{
Config.Data[key] = value;
Console.WriteLine($"Updated config file with the following command: {args[1]} => {value}");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
);
AddCommand("remv", "remove variable from system variables", "remv [key]", args =>
{
if (args.Length < 2)
return;
Config.Data.Remove(args[1]);
}
);
AddCommand("sd", "Shuts down the discord bot", async () =>
{
if (client is null)
return;
await Functions.SaveToJsonFile(Functions.dataFolder + "config.json", Config.Data);
await Functions.SaveToJsonFile(Functions.dataFolder + "Plugins.json", Config.Plugins);
await client.StopAsync();
await client.DisposeAsync();
Config.Logger.SaveToFile();
await Task.Delay(1000);
Environment.Exit(0);
}
);
AddCommand("import", "Load an external command", "import [pluginName]", async args =>
{
if (args.Length <= 1) return;
try
{
var pName = string.Join(' ', args, 1, args.Length - 1);
using (var client = new HttpClient())
{
var url = (await manager.GetPluginLinkByName(pName))[1];
if (url is null) throw new Exception($"Invalid plugin name {pName}.");
var s = await client.GetStreamAsync(url);
var str = new MemoryStream();
await s.CopyToAsync(str);
var asmb = Assembly.Load(str.ToArray());
await PluginLoader.LoadPluginFromAssembly(asmb, this.client);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
});
AddCommand("localload", "Load a local command", "local [pluginName]", async args =>
{
if (args.Length <= 1) return;
try
{
var pName = string.Join(' ', args, 1, args.Length - 1);
var asmb = Assembly.LoadFile(Path.GetFullPath("./Data/Plugins/" + pName + ".dll"));
await PluginLoader.LoadPluginFromAssembly(asmb, this.client);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Config.Logger.Log(ex.Message, this, LogLevel.ERROR);
}
});
commandList.Sort((x, y) => x.CommandName.CompareTo(y.CommandName));
}
public void AddCommand(string command, string description, string usage, Action<string[]> action)
{
Console.WriteLine($"Adding command {command} ...");
commandList.Add(new ConsoleCommand
{ CommandName = command, Description = description, Action = action, Usage = usage });
Console.ForegroundColor = ConsoleColor.White;
Utilities.WriteColorText($"Command &r{command} &cadded to the list of commands");
}
public void AddCommand(string command, string description, Action action)
{
AddCommand(command, description, command, args => action());
}
public void RemoveCommand(string command)
{
commandList.RemoveAll(x => x.CommandName == command);
}
public bool CommandExists(string command)
{
return GetCommand(command) is not null;
}
public ConsoleCommand? GetCommand(string command)
{
return commandList.FirstOrDefault(t => t.CommandName == command);
}
public bool HandleCommand(string command, bool removeCommandExecution = true)
{
Console.ForegroundColor = ConsoleColor.White;
var args = command.Split(' ');
foreach (var item in commandList.ToList())
if (item.CommandName == args[0])
{
if (args[0].StartsWith("_"))
throw new Exception("This command is reserved for internal worker and can not be executed manually !");
if (removeCommandExecution)
{
Console.SetCursorPosition(0, Console.CursorTop - 1);
for (var i = 0; i < command.Length + 30; i++)
Console.Write(" ");
Console.SetCursorPosition(0, Console.CursorTop);
}
Console.WriteLine();
item.Action(args);
return true;
}
return false;
}
}

View File

@@ -1,21 +1,9 @@
using System; namespace DiscordBot.Utilities;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DiscordBot.Utilities
{
public enum TableFormat public enum TableFormat
{ {
SPECTRE_CONSOLE,
CENTER_EACH_COLUMN_BASED, CENTER_EACH_COLUMN_BASED,
CENTER_OVERALL_LENGTH, CENTER_OVERALL_LENGTH,
DEFAULT DEFAULT
} }
public enum ProgressBarType
{
NORMAL,
NO_END
}
}

35
DiscordBot/builder.bat Normal file
View File

@@ -0,0 +1,35 @@
@echo off
echo "Building..."
echo "Building linux-x64 not self-contained"
dotnet publish -r linux-x64 -p:PublishSingleFile=false --self-contained true -c Release -o ../publish/linux-x64
echo "Building win-x64 not self-contained"
dotnet publish -r win-x64 -p:PublishSingleFile=false --self-contained true -c Release -o ../publish/win-x64
echo "Building osx-x64 not self-contained"
dotnet publish -r osx-x64 -p:PublishSingleFile=false --self-contained true -c Release -o ../publish/osx-x64
echo "Building linux-x64 self-contained"
dotnet publish -r linux-x64 -p:PublishSingleFile=true --self-contained true -c Release -o ../publish/linux-x64-selfcontained
echo "Building win-x64 self-contained"
dotnet publish -r win-x64 -p:PublishSingleFile=true --self-contained true -c Release -o ../publish/win-x64-selfcontained
echo "Building osx-x64 self-contained"
dotnet publish -r osx-x64 -p:PublishSingleFile=true --self-contained true -c Release -o ../publish/osx-x64-selfcontained
echo "Zipping..."
mkdir ../publish/zip
zip -r ../publish/zip/linux-x64.zip ../publish/linux-x64
zip -r ../publish/zip/win-x64.zip ../publish/win-x64
zip -r ../publish/zip/osx-x64.zip ../publish/osx-x64
zip -r ../publish/zip/linux-x64-selfcontained.zip ../publish/linux-x64-selfcontained
zip -r ../publish/zip/win-x64-selfcontained.zip ../publish/win-x64-selfcontained
zip -r ../publish/zip/osx-x64-selfcontained.zip ../publish/osx-x64-selfcontained
echo "Done!"

6
builder.sh → DiscordBot/builder.sh Executable file → Normal file
View File

@@ -3,13 +3,13 @@
echo "Building..." echo "Building..."
echo "Building linux-x64 not self-contained" echo "Building linux-x64 not self-contained"
dotnet publish -r linux-x64 -p:PublishSingleFile=false --self-contained false -c Release -o ./publish/linux-x64 dotnet publish -r linux-x64 -p:PublishSingleFile=false --self-contained true -c Release -o ./publish/linux-x64
echo "Building win-x64 not self-contained" echo "Building win-x64 not self-contained"
dotnet publish -r win-x64 -p:PublishSingleFile=false --self-contained false -c Release -o ./publish/win-x64 dotnet publish -r win-x64 -p:PublishSingleFile=false --self-contained true -c Release -o ./publish/win-x64
echo "Building osx-x64 not self-contained" echo "Building osx-x64 not self-contained"
dotnet publish -r osx-x64 -p:PublishSingleFile=false --self-contained false -c Release -o ./publish/osx-x64 dotnet publish -r osx-x64 -p:PublishSingleFile=false --self-contained true -c Release -o ./publish/osx-x64
#One file per platform #One file per platform
echo "Building linux-x64 self-contained" echo "Building linux-x64 self-contained"

View File

@@ -1,10 +1,9 @@
using System.Net.Mime;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket; using Discord.WebSocket;
using PluginManager.Others;
namespace PluginManager.Bot; namespace PluginManager.Bot;
@@ -50,25 +49,26 @@ public class Boot
/// <summary> /// <summary>
/// Checks if the bot is ready /// Checks if the bot is ready
/// </summary> /// </summary>
/// <value> true if the bot is ready, othwerwise false </value> /// <value> true if the bot is ready, otherwise false </value>
public bool isReady { get; private set; } public bool isReady { get; private set; }
/// <summary> /// <summary>
/// The start method for the bot. This method is used to load the bot /// The start method for the bot. This method is used to load the bot
/// </summary> /// </summary>
/// <param name="config">The discord socket config. If null then the default one will be applied (AlwaysDownloadUsers=true, UseInteractionSnowflakeDate=false, GatewayIntents=GatewayIntents.All)</param> /// <param name="config">
/// The discord socket config. If null then the default one will be applied (AlwaysDownloadUsers=true,
/// UseInteractionSnowflakeDate=false, GatewayIntents=GatewayIntents.All)
/// </param>
/// <returns>Task</returns> /// <returns>Task</returns>
public async Task Awake(DiscordSocketConfig? config = null) public async Task Awake(DiscordSocketConfig? config = null)
{ {
if (config is null) if (config is null)
config = new DiscordSocketConfig config = new DiscordSocketConfig
{ {
AlwaysDownloadUsers = true, AlwaysDownloadUsers = true,
//Disable system clock checkup (for responses at slash commands) //Disable system clock checkup (for responses at slash commands)
UseInteractionSnowflakeDate = false, UseInteractionSnowflakeDate = false,
GatewayIntents = GatewayIntents.All GatewayIntents = GatewayIntents.All
}; };
@@ -85,9 +85,8 @@ public class Boot
await commandServiceHandler.InstallCommandsAsync(); await commandServiceHandler.InstallCommandsAsync();
Config._DiscordBotClient = this;
await Task.Delay(2000);
while (!isReady) ; while (!isReady) ;
} }
@@ -105,8 +104,9 @@ public class Boot
{ {
if (arg.Message.Contains("401")) if (arg.Message.Contains("401"))
{ {
Config.Data.Remove("token"); Config.AppSettings.Remove("token");
Config.Logger.Log("The token is invalid. Please restart the bot and enter a valid token.", this, Others.LogLevel.ERROR); Config.Logger.Log("The token is invalid. Please restart the bot and enter a valid token.", source:typeof(Boot), type: LogType.CRITICAL);
await Config.AppSettings.SaveToFile();
await Task.Delay(4000); await Task.Delay(4000);
Environment.Exit(0); Environment.Exit(0);
} }
@@ -114,7 +114,7 @@ public class Boot
private async Task Client_LoggedOut() private async Task Client_LoggedOut()
{ {
Config.Logger.Log("Successfully Logged Out", this); Config.Logger.Log("Successfully Logged Out", source: typeof(Boot));
await Log(new LogMessage(LogSeverity.Info, "Boot", "Successfully logged out from discord !")); await Log(new LogMessage(LogSeverity.Info, "Boot", "Successfully logged out from discord !"));
} }
@@ -126,7 +126,7 @@ public class Boot
private Task LoggedIn() private Task LoggedIn()
{ {
Config.Logger.Log("Successfully Logged In", this); Config.Logger.Log("Successfully Logged In", source: typeof(Boot));
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -136,13 +136,12 @@ public class Boot
{ {
case LogSeverity.Error: case LogSeverity.Error:
case LogSeverity.Critical: case LogSeverity.Critical:
Config.Logger.Log(message.Message, this, Others.LogLevel.ERROR); Config.Logger.Log(message.Message, source: typeof(Boot), type: LogType.ERROR);
break; break;
case LogSeverity.Info: case LogSeverity.Info:
case LogSeverity.Debug: case LogSeverity.Debug:
Config.Logger.Log(message.Message, this); Config.Logger.Log(message.Message, source: typeof(Boot), type: LogType.INFO);
break; break;

View File

@@ -2,7 +2,6 @@ using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket; using Discord.WebSocket;
using PluginManager.Interfaces; using PluginManager.Interfaces;
@@ -29,7 +28,6 @@ internal class CommandHandler
this.client = client; this.client = client;
this.commandService = commandService; this.commandService = commandService;
this.botPrefix = botPrefix; this.botPrefix = botPrefix;
} }
/// <summary> /// <summary>
@@ -47,9 +45,7 @@ internal class CommandHandler
{ {
try try
{ {
var plugin = PluginLoader.SlashCommands! var plugin = PluginLoader.SlashCommands!.FirstOrDefault(p => p.Name == arg.Data.Name);
.Where(p => p.Name == arg.Data.Name)
.FirstOrDefault();
if (plugin is null) if (plugin is null)
throw new Exception("Failed to run command. !"); throw new Exception("Failed to run command. !");
@@ -61,11 +57,10 @@ internal class CommandHandler
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log(ex.Message, "CommandHandler", LogLevel.ERROR); Config.Logger.Log(ex.Message, type: LogType.ERROR, source: typeof(CommandHandler));
} }
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <summary> /// <summary>
@@ -75,7 +70,6 @@ internal class CommandHandler
/// <returns></returns> /// <returns></returns>
private async Task MessageHandler(SocketMessage Message) private async Task MessageHandler(SocketMessage Message)
{ {
try try
{ {
if (Message.Author.IsBot) if (Message.Author.IsBot)
@@ -98,18 +92,22 @@ internal class CommandHandler
await commandService.ExecuteAsync(context, argPos, null); await commandService.ExecuteAsync(context, argPos, null);
DBCommand plugin; DBCommand? plugin;
string cleanMessage = ""; var cleanMessage = "";
if (message.HasMentionPrefix(client.CurrentUser, ref argPos)) if (message.HasMentionPrefix(client.CurrentUser, ref argPos))
{ {
string mentionPrefix = "<@" + client.CurrentUser.Id + ">"; var mentionPrefix = "<@" + client.CurrentUser.Id + ">";
plugin = PluginLoader.Commands! plugin = PluginLoader.Commands!
.FirstOrDefault(plug => plug.Command == message.Content.Substring(mentionPrefix.Length+1).Split(' ')[0] || .FirstOrDefault(plug => plug.Command ==
message.Content.Substring(mentionPrefix.Length + 1)
.Split(' ')[0] ||
( (
plug.Aliases is not null && plug.Aliases is not null &&
plug.Aliases.Contains(message.CleanContent.Substring(mentionPrefix.Length+1).Split(' ')[0]) plug.Aliases.Contains(message.CleanContent
.Substring(mentionPrefix.Length + 1)
.Split(' ')[0])
)); ));
cleanMessage = message.Content.Substring(mentionPrefix.Length + 1); cleanMessage = message.Content.Substring(mentionPrefix.Length + 1);
@@ -118,33 +116,42 @@ internal class CommandHandler
else else
{ {
plugin = PluginLoader.Commands! plugin = PluginLoader.Commands!
.FirstOrDefault(p => p.Command == message.Content.Split(' ')[0].Substring(botPrefix.Length) || .FirstOrDefault(p => p.Command ==
message.Content.Split(' ')[0].Substring(botPrefix.Length) ||
(p.Aliases is not null && (p.Aliases is not null &&
p.Aliases.Contains( p.Aliases.Contains(
message.Content.Split(' ')[0].Substring(botPrefix.Length)))); message.Content.Split(' ')[0]
.Substring(botPrefix.Length))));
cleanMessage = message.Content.Substring(botPrefix.Length); cleanMessage = message.Content.Substring(botPrefix.Length);
} }
if (plugin is null) if (plugin is null)
throw new Exception($"Failed to run command ! " + message.CleanContent + " (user: " + context.Message.Author.Username + " - " + context.Message.Author.Id + ")"); return;
if (plugin.requireAdmin && !context.Message.Author.isAdmin()) if (plugin.requireAdmin && !context.Message.Author.isAdmin())
return; return;
string[] split = cleanMessage.Split(' '); var split = cleanMessage.Split(' ');
string[] argsClean = null; string[]? argsClean = null;
if (split.Length > 1) if (split.Length > 1)
argsClean = string.Join(' ', split, 1, split.Length - 1).Split(' '); argsClean = string.Join(' ', split, 1, split.Length - 1).Split(' ');
DBCommandExecutingArguments cmd = new(context, cleanMessage, split[0], argsClean); DBCommandExecutingArguments cmd = new(context, cleanMessage, split[0], argsClean);
Config.Logger.Log(
message: $"User ({context.User.Username}) from Guild \"{context.Guild.Name}\" executed command \"{cmd.cleanContent}\"",
source: typeof(CommandHandler),
type: LogType.INFO
);
if (context.Channel is SocketDMChannel) if (context.Channel is SocketDMChannel)
plugin.ExecuteDM(cmd); plugin.ExecuteDM(cmd);
else plugin.ExecuteServer(cmd); else plugin.ExecuteServer(cmd);
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log(ex.Message, this, LogLevel.ERROR); Config.Logger.Log(ex.Message, type: LogType.ERROR, source: typeof(CommandHandler));
} }
} }
} }

View File

@@ -1,27 +1,25 @@
using System; using System;
using System.Threading.Tasks;
using System.IO; using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic; using PluginManager.Bot;
using PluginManager.Others; using PluginManager.Others;
using System.Collections;
using PluginManager.Online.Helpers;
using PluginManager.Others.Logger; using PluginManager.Others.Logger;
namespace PluginManager; namespace PluginManager;
public static class Config public class Config
{ {
private static bool IsLoaded = false; private static bool _isLoaded;
public static Logger Logger;
public static SettingsDictionary<string, string> AppSettings;
public static DBLogger Logger; internal static Boot? _DiscordBotClient;
public static Json<string, string> Data;
public static Json<string, string> Plugins; public static Boot? DiscordBot => _DiscordBotClient;
public static async Task Initialize() public static async Task Initialize()
{ {
if (IsLoaded) if (_isLoaded) return;
return;
Directory.CreateDirectory("./Data/Resources"); Directory.CreateDirectory("./Data/Resources");
Directory.CreateDirectory("./Data/Plugins"); Directory.CreateDirectory("./Data/Plugins");
@@ -29,139 +27,17 @@ public static class Config
Directory.CreateDirectory("./Data/Logs/Logs"); Directory.CreateDirectory("./Data/Logs/Logs");
Directory.CreateDirectory("./Data/Logs/Errors"); Directory.CreateDirectory("./Data/Logs/Errors");
Data = new Json<string, string>("./Data/Resources/config.json"); AppSettings = new SettingsDictionary<string, string>("./Data/Resources/config.json");
Plugins = new Json<string, string>("./Data/Resources/Plugins.json");
Config.Data["LogFolder"] = "./Data/Logs/Logs"; AppSettings["LogFolder"] = "./Data/Logs/Logs";
Config.Data["ErrorFolder"] = "./Data/Logs/Errors";
Logger = new DBLogger(); Logger = new Logger(false, true);
ArchiveManager.Initialize(); ArchiveManager.Initialize();
IsLoaded = true; _isLoaded = true;
Logger.Log("Config initialized", LogLevel.INFO); Logger.Log(message: "Config initialized", source: typeof(Config));
}
public class Json<TKey, TValue> : IDictionary<TKey, TValue>
{
protected IDictionary<TKey, TValue> _dictionary;
private readonly string _file = "";
public Json(string file)
{
_dictionary = PrivateReadConfig(file).GetAwaiter().GetResult();
this._file = file;
}
public async void Save()
{
await Functions.SaveToJsonFile(_file, _dictionary);
}
public virtual void Add(TKey key, TValue value)
{
_dictionary.Add(key, value);
}
public void Clear() { _dictionary.Clear(); }
public bool ContainsKey(TKey key)
{
if (_dictionary == null)
throw new Exception("Dictionary is null");
return _dictionary.ContainsKey(key);
}
public virtual ICollection<TKey> Keys => _dictionary.Keys;
public virtual ICollection<TValue> Values => _dictionary.Values;
public int Count => _dictionary.Count;
public bool IsReadOnly => _dictionary.IsReadOnly;
public virtual TValue this[TKey key]
{
get
{
if (_dictionary.TryGetValue(key, out TValue value)) return value;
throw new Exception("Key not found in dictionary " + key.ToString() + " (Json )" + this.GetType().Name + ")");
}
set
{
if (_dictionary.ContainsKey(key))
_dictionary[key] = value;
else _dictionary.Add(key, value);
}
}
public virtual bool TryGetValue(TKey key, out TValue value)
{
return _dictionary.TryGetValue(key, out value);
}
private async Task<Dictionary<TKey, TValue>> PrivateReadConfig(string file)
{
if (!File.Exists(file))
{
var dictionary = new Dictionary<TKey, TValue>();
await Functions.SaveToJsonFile(file, _dictionary);
return dictionary;
}
try
{
var d = await Functions.ConvertFromJson<Dictionary<TKey, TValue>>(file);
if (d is null)
throw new Exception("Failed to read config file");
return d;
}catch (Exception ex)
{
File.Delete(file);
return new Dictionary<TKey, TValue>();
}
}
public bool Remove(TKey key)
{
return _dictionary.Remove(key);
}
public void Add(KeyValuePair<TKey, TValue> item)
{
_dictionary.Add(item);
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return _dictionary.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_dictionary.CopyTo(array, arrayIndex);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return _dictionary.Remove(item);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _dictionary.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_dictionary).GetEnumerator();
}
} }
} }

View File

@@ -1,14 +1,15 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite; using System.Data.SQLite;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace PluginManager.Database namespace PluginManager.Database;
{
public class SqlDatabase public class SqlDatabase
{ {
private string ConnectionString; private readonly SQLiteConnection Connection;
private SQLiteConnection Connection;
/// <summary> /// <summary>
/// Initialize a SQL connection by specifing its private path /// Initialize a SQL connection by specifing its private path
@@ -20,8 +21,8 @@ namespace PluginManager.Database
fileName = Path.Combine("./Data/Resources", fileName); fileName = Path.Combine("./Data/Resources", fileName);
if (!File.Exists(fileName)) if (!File.Exists(fileName))
SQLiteConnection.CreateFile(fileName); SQLiteConnection.CreateFile(fileName);
ConnectionString = $"URI=file:{fileName}"; var connectionString = $"URI=file:{fileName}";
Connection = new SQLiteConnection(ConnectionString); Connection = new SQLiteConnection(connectionString);
} }
@@ -44,8 +45,8 @@ namespace PluginManager.Database
/// <returns></returns> /// <returns></returns>
public async Task InsertAsync(string tableName, params string[] values) public async Task InsertAsync(string tableName, params string[] values)
{ {
string query = $"INSERT INTO {tableName} VALUES ("; var query = $"INSERT INTO {tableName} VALUES (";
for (int i = 0; i < values.Length; i++) for (var i = 0; i < values.Length; i++)
{ {
query += $"'{values[i]}'"; query += $"'{values[i]}'";
if (i != values.Length - 1) if (i != values.Length - 1)
@@ -54,7 +55,7 @@ namespace PluginManager.Database
query += ")"; query += ")";
SQLiteCommand command = new SQLiteCommand(query, Connection); var command = new SQLiteCommand(query, Connection);
await command.ExecuteNonQueryAsync(); await command.ExecuteNonQueryAsync();
} }
@@ -68,8 +69,8 @@ namespace PluginManager.Database
/// <returns></returns> /// <returns></returns>
public void Insert(string tableName, params string[] values) public void Insert(string tableName, params string[] values)
{ {
string query = $"INSERT INTO {tableName} VALUES ("; var query = $"INSERT INTO {tableName} VALUES (";
for (int i = 0; i < values.Length; i++) for (var i = 0; i < values.Length; i++)
{ {
query += $"'{values[i]}'"; query += $"'{values[i]}'";
if (i != values.Length - 1) if (i != values.Length - 1)
@@ -78,7 +79,7 @@ namespace PluginManager.Database
query += ")"; query += ")";
SQLiteCommand command = new SQLiteCommand(query, Connection); var command = new SQLiteCommand(query, Connection);
command.ExecuteNonQuery(); command.ExecuteNonQuery();
} }
@@ -91,9 +92,9 @@ namespace PluginManager.Database
/// <returns></returns> /// <returns></returns>
public async Task RemoveKeyAsync(string tableName, string KeyName, string KeyValue) public async Task RemoveKeyAsync(string tableName, string KeyName, string KeyValue)
{ {
string query = $"DELETE FROM {tableName} WHERE {KeyName} = '{KeyValue}'"; var query = $"DELETE FROM {tableName} WHERE {KeyName} = '{KeyValue}'";
SQLiteCommand command = new SQLiteCommand(query, Connection); var command = new SQLiteCommand(query, Connection);
await command.ExecuteNonQueryAsync(); await command.ExecuteNonQueryAsync();
} }
@@ -106,9 +107,9 @@ namespace PluginManager.Database
/// <returns></returns> /// <returns></returns>
public void RemoveKey(string tableName, string KeyName, string KeyValue) public void RemoveKey(string tableName, string KeyName, string KeyValue)
{ {
string query = $"DELETE FROM {tableName} WHERE {KeyName} = '{KeyValue}'"; var query = $"DELETE FROM {tableName} WHERE {KeyName} = '{KeyValue}'";
SQLiteCommand command = new SQLiteCommand(query, Connection); var command = new SQLiteCommand(query, Connection);
command.ExecuteNonQuery(); command.ExecuteNonQuery();
} }
@@ -121,7 +122,7 @@ namespace PluginManager.Database
/// <returns></returns> /// <returns></returns>
public async Task<bool> KeyExistsAsync(string tableName, string keyName, string KeyValue) public async Task<bool> KeyExistsAsync(string tableName, string keyName, string KeyValue)
{ {
string query = $"SELECT * FROM {tableName} where {keyName} = '{KeyValue}'"; var query = $"SELECT * FROM {tableName} where {keyName} = '{KeyValue}'";
if (await ReadDataAsync(query) is not null) if (await ReadDataAsync(query) is not null)
return true; return true;
@@ -138,7 +139,7 @@ namespace PluginManager.Database
/// <returns></returns> /// <returns></returns>
public bool KeyExists(string tableName, string keyName, string KeyValue) public bool KeyExists(string tableName, string keyName, string KeyValue)
{ {
string query = $"SELECT * FROM {tableName} where {keyName} = '{KeyValue}'"; var query = $"SELECT * FROM {tableName} where {keyName} = '{KeyValue}'";
if (ReadData(query) is not null) if (ReadData(query) is not null)
return true; return true;
@@ -154,12 +155,12 @@ namespace PluginManager.Database
/// <param name="KeyValue">The value that is searched in the column specified</param> /// <param name="KeyValue">The value that is searched in the column specified</param>
/// <param name="ResultColumnName">The column that has to be modified</param> /// <param name="ResultColumnName">The column that has to be modified</param>
/// <param name="ResultColumnValue">The new value that will replace the old value from the column specified</param> /// <param name="ResultColumnValue">The new value that will replace the old value from the column specified</param>
public async Task SetValueAsync(
public async Task SetValueAsync(string tableName, string keyName, string KeyValue, string ResultColumnName, string tableName, string keyName, string KeyValue, string ResultColumnName,
string ResultColumnValue) string ResultColumnValue)
{ {
if (!await TableExistsAsync(tableName)) if (!await TableExistsAsync(tableName))
throw new System.Exception($"Table {tableName} does not exist"); throw new Exception($"Table {tableName} does not exist");
await ExecuteAsync( await ExecuteAsync(
$"UPDATE {tableName} SET {ResultColumnName}='{ResultColumnValue}' WHERE {keyName}='{KeyValue}'"); $"UPDATE {tableName} SET {ResultColumnName}='{ResultColumnValue}' WHERE {keyName}='{KeyValue}'");
@@ -173,12 +174,12 @@ namespace PluginManager.Database
/// <param name="KeyValue">The value that is searched in the column specified</param> /// <param name="KeyValue">The value that is searched in the column specified</param>
/// <param name="ResultColumnName">The column that has to be modified</param> /// <param name="ResultColumnName">The column that has to be modified</param>
/// <param name="ResultColumnValue">The new value that will replace the old value from the column specified</param> /// <param name="ResultColumnValue">The new value that will replace the old value from the column specified</param>
public void SetValue(
public void SetValue(string tableName, string keyName, string KeyValue, string ResultColumnName, string tableName, string keyName, string KeyValue, string ResultColumnName,
string ResultColumnValue) string ResultColumnValue)
{ {
if (!TableExists(tableName)) if (!TableExists(tableName))
throw new System.Exception($"Table {tableName} does not exist"); throw new Exception($"Table {tableName} does not exist");
Execute($"UPDATE {tableName} SET {ResultColumnName}='{ResultColumnValue}' WHERE {keyName}='{KeyValue}'"); Execute($"UPDATE {tableName} SET {ResultColumnName}='{ResultColumnValue}' WHERE {keyName}='{KeyValue}'");
} }
@@ -191,11 +192,12 @@ namespace PluginManager.Database
/// <param name="KeyValue">The value that is searched in the specified column</param> /// <param name="KeyValue">The value that is searched in the specified column</param>
/// <param name="ResultColumnName">The column that has the result</param> /// <param name="ResultColumnName">The column that has the result</param>
/// <returns>A string that has the requested value (can be null if nothing found)</returns> /// <returns>A string that has the requested value (can be null if nothing found)</returns>
public async Task<string?> GetValueAsync(string tableName, string keyName, string KeyValue, public async Task<string?> GetValueAsync(
string tableName, string keyName, string KeyValue,
string ResultColumnName) string ResultColumnName)
{ {
if (!await TableExistsAsync(tableName)) if (!await TableExistsAsync(tableName))
throw new System.Exception($"Table {tableName} does not exist"); throw new Exception($"Table {tableName} does not exist");
return await ReadDataAsync($"SELECT {ResultColumnName} FROM {tableName} WHERE {keyName}='{KeyValue}'"); return await ReadDataAsync($"SELECT {ResultColumnName} FROM {tableName} WHERE {keyName}='{KeyValue}'");
} }
@@ -208,11 +210,10 @@ namespace PluginManager.Database
/// <param name="KeyValue">The value that is searched in the specified column</param> /// <param name="KeyValue">The value that is searched in the specified column</param>
/// <param name="ResultColumnName">The column that has the result</param> /// <param name="ResultColumnName">The column that has the result</param>
/// <returns>A string that has the requested value (can be null if nothing found)</returns> /// <returns>A string that has the requested value (can be null if nothing found)</returns>
public string? GetValue(string tableName, string keyName, string KeyValue, string ResultColumnName) public string? GetValue(string tableName, string keyName, string KeyValue, string ResultColumnName)
{ {
if (!TableExists(tableName)) if (!TableExists(tableName))
throw new System.Exception($"Table {tableName} does not exist"); throw new Exception($"Table {tableName} does not exist");
return ReadData($"SELECT {ResultColumnName} FROM {tableName} WHERE {keyName}='{KeyValue}'"); return ReadData($"SELECT {ResultColumnName} FROM {tableName} WHERE {keyName}='{KeyValue}'");
} }
@@ -239,18 +240,16 @@ namespace PluginManager.Database
command.CommandText = $"SELECT * FROM {tableName}"; command.CommandText = $"SELECT * FROM {tableName}";
var reader = await command.ExecuteReaderAsync(); var reader = await command.ExecuteReaderAsync();
var tableColumns = new List<string>(); var tableColumns = new List<string>();
for (int i = 0; i < reader.FieldCount; i++) for (var i = 0; i < reader.FieldCount; i++)
tableColumns.Add(reader.GetName(i)); tableColumns.Add(reader.GetName(i));
foreach (var column in columns) foreach (var column in columns)
{
if (!tableColumns.Contains(column)) if (!tableColumns.Contains(column))
{ {
command.CommandText = $"ALTER TABLE {tableName} ADD COLUMN {column} {TYPE}"; command.CommandText = $"ALTER TABLE {tableName} ADD COLUMN {column} {TYPE}";
await command.ExecuteNonQueryAsync(); await command.ExecuteNonQueryAsync();
} }
} }
}
/// <summary> /// <summary>
/// Change the structure of a table by adding new columns /// Change the structure of a table by adding new columns
@@ -259,25 +258,22 @@ namespace PluginManager.Database
/// <param name="columns">The columns to be added</param> /// <param name="columns">The columns to be added</param>
/// <param name="TYPE">The type of the columns (TEXT, INTEGER, FLOAT, etc)</param> /// <param name="TYPE">The type of the columns (TEXT, INTEGER, FLOAT, etc)</param>
/// <returns></returns> /// <returns></returns>
public void AddColumnsToTable(string tableName, string[] columns, string TYPE = "TEXT") public void AddColumnsToTable(string tableName, string[] columns, string TYPE = "TEXT")
{ {
var command = Connection.CreateCommand(); var command = Connection.CreateCommand();
command.CommandText = $"SELECT * FROM {tableName}"; command.CommandText = $"SELECT * FROM {tableName}";
var reader = command.ExecuteReader(); var reader = command.ExecuteReader();
var tableColumns = new List<string>(); var tableColumns = new List<string>();
for (int i = 0; i < reader.FieldCount; i++) for (var i = 0; i < reader.FieldCount; i++)
tableColumns.Add(reader.GetName(i)); tableColumns.Add(reader.GetName(i));
foreach (var column in columns) foreach (var column in columns)
{
if (!tableColumns.Contains(column)) if (!tableColumns.Contains(column))
{ {
command.CommandText = $"ALTER TABLE {tableName} ADD COLUMN {column} {TYPE}"; command.CommandText = $"ALTER TABLE {tableName} ADD COLUMN {column} {TYPE}";
command.ExecuteNonQuery(); command.ExecuteNonQuery();
} }
} }
}
/// <summary> /// <summary>
/// Check if a table exists /// Check if a table exists
@@ -317,7 +313,6 @@ namespace PluginManager.Database
/// <param name="tableName">The table name</param> /// <param name="tableName">The table name</param>
/// <param name="columns">The columns of the table</param> /// <param name="columns">The columns of the table</param>
/// <returns></returns> /// <returns></returns>
public async Task CreateTableAsync(string tableName, params string[] columns) public async Task CreateTableAsync(string tableName, params string[] columns)
{ {
var cmd = Connection.CreateCommand(); var cmd = Connection.CreateCommand();
@@ -331,7 +326,6 @@ namespace PluginManager.Database
/// <param name="tableName">The table name</param> /// <param name="tableName">The table name</param>
/// <param name="columns">The columns of the table</param> /// <param name="columns">The columns of the table</param>
/// <returns></returns> /// <returns></returns>
public void CreateTable(string tableName, params string[] columns) public void CreateTable(string tableName, params string[] columns)
{ {
var cmd = Connection.CreateCommand(); var cmd = Connection.CreateCommand();
@@ -346,10 +340,10 @@ namespace PluginManager.Database
/// <returns>The number of rows that the query modified</returns> /// <returns>The number of rows that the query modified</returns>
public async Task<int> ExecuteAsync(string query) public async Task<int> ExecuteAsync(string query)
{ {
if (!Connection.State.HasFlag(System.Data.ConnectionState.Open)) if (!Connection.State.HasFlag(ConnectionState.Open))
await Connection.OpenAsync(); await Connection.OpenAsync();
var command = new SQLiteCommand(query, Connection); var command = new SQLiteCommand(query, Connection);
int answer = await command.ExecuteNonQueryAsync(); var answer = await command.ExecuteNonQueryAsync();
return answer; return answer;
} }
@@ -360,10 +354,10 @@ namespace PluginManager.Database
/// <returns>The number of rows that the query modified</returns> /// <returns>The number of rows that the query modified</returns>
public int Execute(string query) public int Execute(string query)
{ {
if (!Connection.State.HasFlag(System.Data.ConnectionState.Open)) if (!Connection.State.HasFlag(ConnectionState.Open))
Connection.Open(); Connection.Open();
var command = new SQLiteCommand(query, Connection); var command = new SQLiteCommand(query, Connection);
int r = command.ExecuteNonQuery(); var r = command.ExecuteNonQuery();
return r; return r;
} }
@@ -375,12 +369,12 @@ namespace PluginManager.Database
/// <returns>The result is a string that has all values separated by space character</returns> /// <returns>The result is a string that has all values separated by space character</returns>
public async Task<string?> ReadDataAsync(string query) public async Task<string?> ReadDataAsync(string query)
{ {
if (!Connection.State.HasFlag(System.Data.ConnectionState.Open)) if (!Connection.State.HasFlag(ConnectionState.Open))
await Connection.OpenAsync(); await Connection.OpenAsync();
var command = new SQLiteCommand(query, Connection); var command = new SQLiteCommand(query, Connection);
var reader = await command.ExecuteReaderAsync(); var reader = await command.ExecuteReaderAsync();
object[] values = new object[reader.FieldCount]; var values = new object[reader.FieldCount];
if (reader.Read()) if (reader.Read())
{ {
reader.GetValues(values); reader.GetValues(values);
@@ -397,12 +391,12 @@ namespace PluginManager.Database
/// <returns>The result is a string that has all values separated by space character</returns> /// <returns>The result is a string that has all values separated by space character</returns>
public string? ReadData(string query) public string? ReadData(string query)
{ {
if (!Connection.State.HasFlag(System.Data.ConnectionState.Open)) if (!Connection.State.HasFlag(ConnectionState.Open))
Connection.Open(); Connection.Open();
var command = new SQLiteCommand(query, Connection); var command = new SQLiteCommand(query, Connection);
var reader = command.ExecuteReader(); var reader = command.ExecuteReader();
object[] values = new object[reader.FieldCount]; var values = new object[reader.FieldCount];
if (reader.Read()) if (reader.Read())
{ {
reader.GetValues(values); reader.GetValues(values);
@@ -419,12 +413,12 @@ namespace PluginManager.Database
/// <returns>The first row as separated items</returns> /// <returns>The first row as separated items</returns>
public async Task<object[]?> ReadDataArrayAsync(string query) public async Task<object[]?> ReadDataArrayAsync(string query)
{ {
if (!Connection.State.HasFlag(System.Data.ConnectionState.Open)) if (!Connection.State.HasFlag(ConnectionState.Open))
await Connection.OpenAsync(); await Connection.OpenAsync();
var command = new SQLiteCommand(query, Connection); var command = new SQLiteCommand(query, Connection);
var reader = await command.ExecuteReaderAsync(); var reader = await command.ExecuteReaderAsync();
object[] values = new object[reader.FieldCount]; var values = new object[reader.FieldCount];
if (reader.Read()) if (reader.Read())
{ {
reader.GetValues(values); reader.GetValues(values);
@@ -442,12 +436,12 @@ namespace PluginManager.Database
/// <returns>The first row as separated items</returns> /// <returns>The first row as separated items</returns>
public object[]? ReadDataArray(string query) public object[]? ReadDataArray(string query)
{ {
if (!Connection.State.HasFlag(System.Data.ConnectionState.Open)) if (!Connection.State.HasFlag(ConnectionState.Open))
Connection.Open(); Connection.Open();
var command = new SQLiteCommand(query, Connection); var command = new SQLiteCommand(query, Connection);
var reader = command.ExecuteReader(); var reader = command.ExecuteReader();
object[] values = new object[reader.FieldCount]; var values = new object[reader.FieldCount];
if (reader.Read()) if (reader.Read())
{ {
reader.GetValues(values); reader.GetValues(values);
@@ -458,14 +452,14 @@ namespace PluginManager.Database
} }
/// <summary> /// <summary>
/// Read all rows from the result table and return them as a list of string arrays. The string arrays contain the values of each row /// Read all rows from the result table and return them as a list of string arrays. The string arrays contain the
/// values of each row
/// </summary> /// </summary>
/// <param name="query">The query</param> /// <param name="query">The query</param>
/// <returns>A list of string arrays representing the values that the query returns</returns> /// <returns>A list of string arrays representing the values that the query returns</returns>
public async Task<List<string[]>?> ReadAllRowsAsync(string query) public async Task<List<string[]>?> ReadAllRowsAsync(string query)
{ {
if (!Connection.State.HasFlag(System.Data.ConnectionState.Open)) if (!Connection.State.HasFlag(ConnectionState.Open))
await Connection.OpenAsync(); await Connection.OpenAsync();
var command = new SQLiteCommand(query, Connection); var command = new SQLiteCommand(query, Connection);
var reader = await command.ExecuteReaderAsync(); var reader = await command.ExecuteReaderAsync();
@@ -476,7 +470,7 @@ namespace PluginManager.Database
List<string[]> rows = new(); List<string[]> rows = new();
while (await reader.ReadAsync()) while (await reader.ReadAsync())
{ {
string[] values = new string[reader.FieldCount]; var values = new string[reader.FieldCount];
reader.GetValues(values); reader.GetValues(values);
rows.Add(values); rows.Add(values);
} }
@@ -486,4 +480,3 @@ namespace PluginManager.Database
return rows; return rows;
} }
} }
}

View File

@@ -1,10 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using Discord; using Discord;
using Discord.WebSocket; using Discord.WebSocket;
namespace PluginManager.Interfaces namespace PluginManager.Interfaces;
{
public interface DBSlashCommand public interface DBSlashCommand
{ {
string Name { get; } string Name { get; }
@@ -16,10 +15,7 @@ namespace PluginManager.Interfaces
void ExecuteServer(SocketSlashCommand context) void ExecuteServer(SocketSlashCommand context)
{ {
} }
void ExecuteDM(SocketSlashCommand context) { } void ExecuteDM(SocketSlashCommand context) { }
}
} }

View File

@@ -0,0 +1,17 @@
using System.Threading.Tasks;
using PluginManager.Others;
namespace PluginManager.Interfaces;
public interface ICommandAction
{
public string ActionName { get; }
public string? Description { get; }
public string? Usage { get; }
public InternalActionRunType RunType { get; }
public Task Execute(string[]? args);
}

View File

@@ -0,0 +1,15 @@
using System;
using PluginManager.Others;
namespace PluginManager.Interfaces.Logger;
internal interface ILog
{
string Message { get; set; }
string OutputFile { get; set; }
Type? Source { get; set; }
LogType Type { get; set; }
DateTime ThrowTime { get; set; }
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Threading.Tasks;
using PluginManager.Others;
using PluginManager.Others.Logger;
namespace PluginManager.Interfaces.Logger;
internal interface ILogger
{
bool IsEnabled { get; init; }
bool OutputToFile { get; init; }
event EventHandler<Log> OnLog;
void Log(
string message = "", string outputFile = "", Type? source = default, LogType type = LogType.INFO,
DateTime throwTime = default);
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using PluginManager.Interfaces;
using PluginManager.Others;
namespace PluginManager.Loaders;
public class ActionsLoader
{
public delegate void ActionLoaded(string name, string typeName, bool success, Exception? e = null);
private readonly string actionExtension = "dll";
private readonly string actionFolder = @"./Data/Plugins/";
public ActionsLoader(string path, string extension)
{
actionFolder = path;
actionExtension = extension;
}
public event ActionLoaded? ActionLoadedEvent;
public async Task<List<ICommandAction>?> Load()
{
Directory.CreateDirectory(actionFolder);
var files = Directory.GetFiles(actionFolder, $"*.{actionExtension}", SearchOption.AllDirectories);
var actions = new List<ICommandAction>();
foreach (var file in files)
try
{
Assembly.LoadFrom(file);
}
catch (Exception e)
{
ActionLoadedEvent?.Invoke(file, "", false, e);
}
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => typeof(ICommandAction).IsAssignableFrom(p) && !p.IsInterface);
foreach (var type in types)
try
{
var action = (ICommandAction)Activator.CreateInstance(type);
if (action.ActionName == null)
{
ActionLoadedEvent?.Invoke(action.ActionName, type.Name, false);
continue;
}
if (action.RunType == InternalActionRunType.ON_STARTUP)
await action.Execute(null);
ActionLoadedEvent?.Invoke(action.ActionName, type.Name, true);
actions.Add(action);
}
catch (Exception e)
{
ActionLoadedEvent?.Invoke(type.Name, type.Name, false, e);
}
return actions;
}
}

View File

@@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using PluginManager.Interfaces; using PluginManager.Interfaces;
using PluginManager.Others;
namespace PluginManager.Loaders;
namespace PluginManager.Loaders
{
internal class LoaderArgs : EventArgs internal class LoaderArgs : EventArgs
{ {
internal string? PluginName { get; init; } internal string? PluginName { get; init; }
@@ -16,6 +16,7 @@ namespace PluginManager.Loaders
internal Exception? Exception { get; init; } internal Exception? Exception { get; init; }
internal object? Plugin { get; init; } internal object? Plugin { get; init; }
} }
internal class Loader internal class Loader
{ {
internal Loader(string path, string extension) internal Loader(string path, string extension)
@@ -33,14 +34,8 @@ namespace PluginManager.Loaders
internal event PluginLoadedEventHandler? PluginLoaded; internal event PluginLoadedEventHandler? PluginLoaded;
internal delegate void FileLoadedEventHandler(LoaderArgs args);
internal delegate void PluginLoadedEventHandler(LoaderArgs args);
internal (List<DBEvent>?, List<DBCommand>?, List<DBSlashCommand>?) Load() internal (List<DBEvent>?, List<DBCommand>?, List<DBSlashCommand>?) Load()
{ {
List<DBEvent> events = new(); List<DBEvent> events = new();
List<DBSlashCommand> slashCommands = new(); List<DBSlashCommand> slashCommands = new();
List<DBCommand> commands = new(); List<DBCommand> commands = new();
@@ -58,11 +53,12 @@ namespace PluginManager.Loaders
{ {
Assembly.LoadFrom(file); Assembly.LoadFrom(file);
} }
catch (Exception ex) catch
{ {
Config.Logger.Log("PluginName: " + new FileInfo(file).Name.Split('.')[0] + " not loaded", this, Others.LogLevel.ERROR); Config.Logger.Log("PluginName: " + new FileInfo(file).Name.Split('.')[0] + " not loaded", source: typeof(Loader), type: LogType.ERROR);
continue; continue;
} }
if (FileLoaded != null) if (FileLoaded != null)
{ {
var args = new LoaderArgs var args = new LoaderArgs
@@ -78,7 +74,6 @@ namespace PluginManager.Loaders
} }
return (LoadItems<DBEvent>(), LoadItems<DBCommand>(), LoadItems<DBSlashCommand>()); return (LoadItems<DBEvent>(), LoadItems<DBCommand>(), LoadItems<DBSlashCommand>());
} }
@@ -110,7 +105,10 @@ namespace PluginManager.Loaders
Exception = null, Exception = null,
IsLoaded = true, IsLoaded = true,
PluginName = type.FullName, PluginName = type.FullName,
TypeName = typeof(T) == typeof(DBCommand) ? "DBCommand" : typeof(T) == typeof(DBEvent) ? "DBEvent" : "DBSlashCommand", TypeName = typeof(T) == typeof(DBCommand) ? "DBCommand" :
typeof(T) == typeof(DBEvent) ? "DBEvent" :
typeof(T) == typeof(DBSlashCommand) ? "DBSlashCommand" :
null,
Plugin = plugin Plugin = plugin
} }
); );
@@ -131,12 +129,16 @@ namespace PluginManager.Loaders
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log(ex.Message, this, Others.LogLevel.ERROR); Config.Logger.Log(ex.Message, source: typeof(Loader), type: LogType.ERROR);
return null; return null;
} }
return null; return null;
} }
}
internal delegate void FileLoadedEventHandler(LoaderArgs args);
internal delegate void PluginLoadedEventHandler(LoaderArgs args);
} }

View File

@@ -1,14 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord; using Discord;
using Discord.WebSocket; using Discord.WebSocket;
using PluginManager.Interfaces; using PluginManager.Interfaces;
using PluginManager.Online; using PluginManager.Others;
namespace PluginManager.Loaders; namespace PluginManager.Loaders;
@@ -65,7 +62,10 @@ public class PluginLoader
/// </summary> /// </summary>
public static List<DBSlashCommand>? SlashCommands { get; set; } public static List<DBSlashCommand>? SlashCommands { get; set; }
public static int PluginsLoaded { get { public static int PluginsLoaded
{
get
{
var count = 0; var count = 0;
if (Commands is not null) if (Commands is not null)
count += Commands.Count; count += Commands.Count;
@@ -74,7 +74,8 @@ public class PluginLoader
if (SlashCommands is not null) if (SlashCommands is not null)
count += SlashCommands.Count; count += SlashCommands.Count;
return count; return count;
}} }
}
/// <summary> /// <summary>
/// The main mathod that is called to load all events /// The main mathod that is called to load all events
@@ -87,10 +88,10 @@ public class PluginLoader
Events = new List<DBEvent>(); Events = new List<DBEvent>();
SlashCommands = new List<DBSlashCommand>(); SlashCommands = new List<DBSlashCommand>();
Config.Logger.Log("Starting plugin loader ... Client: " + _client.CurrentUser.Username, this, Others.LogLevel.INFO); Config.Logger.Log("Starting plugin loader ... Client: " + _client.CurrentUser.Username, source: typeof(PluginLoader), type: LogType.INFO);
var loader = new Loader("./Data/Plugins", "dll"); var loader = new Loader("./Data/Plugins", "dll");
loader.FileLoaded += (args) => Config.Logger.Log($"{args.PluginName} file Loaded", this , Others.LogLevel.INFO); loader.FileLoaded += args => Config.Logger.Log($"{args.PluginName} file Loaded", source: typeof(PluginLoader), type: LogType.INFO);
loader.PluginLoaded += Loader_PluginLoaded; loader.PluginLoaded += Loader_PluginLoaded;
var res = loader.Load(); var res = loader.Load();
Events = res.Item1; Events = res.Item1;
@@ -100,7 +101,6 @@ public class PluginLoader
private async void Loader_PluginLoaded(LoaderArgs args) private async void Loader_PluginLoaded(LoaderArgs args)
{ {
switch (args.TypeName) switch (args.TypeName)
{ {
case "DBCommand": case "DBCommand":
@@ -116,57 +116,67 @@ public class PluginLoader
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log(ex.Message, this, Others.LogLevel.ERROR); Config.Logger.Log(ex.Message, source: typeof(PluginLoader), type: LogType.ERROR);
} }
break; break;
case "DBSlashCommand": case "DBSlashCommand":
if (args.IsLoaded) if (args.IsLoaded)
{ {
var slash = (DBSlashCommand)args.Plugin; var slash = (DBSlashCommand)args.Plugin;
SlashCommandBuilder builder = new SlashCommandBuilder(); var builder = new SlashCommandBuilder();
builder.WithName(slash.Name); builder.WithName(slash.Name);
builder.WithDescription(slash.Description); builder.WithDescription(slash.Description);
builder.WithDMPermission(slash.canUseDM); builder.WithDMPermission(slash.canUseDM);
builder.Options = slash.Options; builder.Options = slash.Options;
onSLSHLoad?.Invoke(((DBSlashCommand)args.Plugin!).Name, args.TypeName, args.IsLoaded, args.Exception); onSLSHLoad?.Invoke(((DBSlashCommand)args.Plugin!).Name, args.TypeName, args.IsLoaded,
args.Exception);
await _client.CreateGlobalApplicationCommandAsync(builder.Build()); await _client.CreateGlobalApplicationCommandAsync(builder.Build());
} }
break; break;
} }
} }
public static async Task LoadPluginFromAssembly(Assembly asmb, DiscordSocketClient client) public static async Task LoadPluginFromAssembly(Assembly asmb, DiscordSocketClient client)
{ {
var types = asmb.GetTypes(); var types = asmb.GetTypes();
foreach (var type in types) foreach (var type in types)
try
{
if (type.IsClass && typeof(DBEvent).IsAssignableFrom(type)) if (type.IsClass && typeof(DBEvent).IsAssignableFrom(type))
{ {
var instance = (DBEvent)Activator.CreateInstance(type); var instance = (DBEvent)Activator.CreateInstance(type);
instance.Start(client); instance.Start(client);
PluginLoader.Events.Add(instance); Events.Add(instance);
Config.Logger.Log($"[EVENT] Loaded external {type.FullName}!", Others.LogLevel.INFO); Config.Logger.Log($"[EVENT] Loaded external {type.FullName}!", source: typeof(PluginLoader));
} }
else if (type.IsClass && typeof(DBCommand).IsAssignableFrom(type)) else if (type.IsClass && typeof(DBCommand).IsAssignableFrom(type))
{ {
var instance = (DBCommand)Activator.CreateInstance(type); var instance = (DBCommand)Activator.CreateInstance(type);
PluginLoader.Commands.Add(instance); Commands.Add(instance);
Config.Logger.Log($"[CMD] Instance: {type.FullName} loaded !", Others.LogLevel.INFO); Config.Logger.Log($"[CMD] Instance: {type.FullName} loaded !", source: typeof(PluginLoader));
} }
else if (type.IsClass && typeof(DBSlashCommand).IsAssignableFrom(type)) else if (type.IsClass && typeof(DBSlashCommand).IsAssignableFrom(type))
{ {
var instance = (DBSlashCommand)Activator.CreateInstance(type); var instance = (DBSlashCommand)Activator.CreateInstance(type);
SlashCommandBuilder builder = new SlashCommandBuilder(); var builder = new SlashCommandBuilder();
builder.WithName(instance.Name); builder.WithName(instance.Name);
builder.WithDescription(instance.Description); builder.WithDescription(instance.Description);
builder.WithDMPermission(instance.canUseDM); builder.WithDMPermission(instance.canUseDM);
builder.Options = instance.Options; builder.Options = instance.Options;
await client.CreateGlobalApplicationCommandAsync(builder.Build()); await client.CreateGlobalApplicationCommandAsync(builder.Build());
PluginLoader.SlashCommands.Add(instance); SlashCommands.Add(instance);
Config.Logger.Log($"[SLASH] Instance: {type.FullName} loaded !", Others.LogLevel.INFO); Config.Logger.Log($"[SLASH] Instance: {type.FullName} loaded !", source: typeof(PluginLoader));
}
}
catch (Exception ex)
{
//Console.WriteLine(ex.Message);
Config.Logger.Log(ex.Message, source: typeof(PluginLoader), type: LogType.ERROR);
}
} }
} }
}

View File

@@ -3,7 +3,6 @@ using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using PluginManager.Others; using PluginManager.Others;
namespace PluginManager.Online.Helpers; namespace PluginManager.Online.Helpers;
@@ -19,7 +18,8 @@ internal static class OnlineFunctions
/// <param name="progress">The <see cref="IProgress{T}" /> that is used to track the download progress</param> /// <param name="progress">The <see cref="IProgress{T}" /> that is used to track the download progress</param>
/// <param name="cancellation">The cancellation token</param> /// <param name="cancellation">The cancellation token</param>
/// <returns></returns> /// <returns></returns>
internal static async Task DownloadFileAsync(this HttpClient client, string url, Stream destination, internal static async Task DownloadFileAsync(
this HttpClient client, string url, Stream destination,
IProgress<float>? progress = null, IProgress<float>? progress = null,
IProgress<long>? downloadedBytes = null, int bufferSize = 81920, IProgress<long>? downloadedBytes = null, int bufferSize = 81920,
CancellationToken cancellation = default) CancellationToken cancellation = default)
@@ -35,20 +35,25 @@ internal static class OnlineFunctions
if (progress == null || !contentLength.HasValue) if (progress == null || !contentLength.HasValue)
{ {
await download.CopyToAsync(destination, cancellation); await download.CopyToAsync(destination, cancellation);
if(!contentLength.HasValue)
progress?.Report(100f);
return; return;
} }
// Convert absolute progress (bytes downloaded) into relative progress (0% - 100%) // Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
var relativeProgress = new Progress<long>(totalBytes => // total ... 100%
// downloaded ... x%
// x = downloaded * 100 / total => x = downloaded / total * 100
var relativeProgress = new Progress<long>(totalBytesDownloaded =>
{ {
progress?.Report((float)totalBytes / contentLength.Value * 100); progress?.Report(totalBytesDownloaded / (float)contentLength.Value * 100);
downloadedBytes?.Report(totalBytes); downloadedBytes?.Report(totalBytesDownloaded);
} }
); );
// Use extension method to report progress while downloading // Use extension method to report progress while downloading
await download.CopyToOtherStreamAsync(destination, bufferSize, relativeProgress, cancellation); await download.CopyToOtherStreamAsync(destination, bufferSize, relativeProgress, cancellation);
progress.Report(100); progress.Report(100f);
} }
} }
} }

View File

@@ -12,11 +12,25 @@ public class VersionString
{ {
var data = version.Split('.'); var data = version.Split('.');
try try
{
if (data.Length == 3)
{ {
PackageVersionID = int.Parse(data[0]); PackageVersionID = int.Parse(data[0]);
PackageMainVersion = int.Parse(data[1]); PackageMainVersion = int.Parse(data[1]);
PackageCheckVersion = int.Parse(data[2]); PackageCheckVersion = int.Parse(data[2]);
} }
else if (data.Length == 4)
{
// ignore the first item data[0]
PackageVersionID = int.Parse(data[1]);
PackageMainVersion = int.Parse(data[2]);
PackageCheckVersion = int.Parse(data[3]);
}
else
{
throw new Exception("Invalid version string");
}
}
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(version); Console.WriteLine(version);
@@ -24,6 +38,25 @@ public class VersionString
} }
} }
private bool Equals(VersionString other)
{
return PackageCheckVersion == other.PackageCheckVersion && PackageMainVersion == other.PackageMainVersion &&
PackageVersionID == other.PackageVersionID;
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((VersionString)obj);
}
public override int GetHashCode()
{
return HashCode.Combine(PackageCheckVersion, PackageMainVersion, PackageVersionID);
}
public override string ToString() public override string ToString()
{ {
return "{PackageID: " + PackageVersionID + ", PackageVersion: " + PackageMainVersion + return "{PackageID: " + PackageVersionID + ", PackageVersion: " + PackageMainVersion +

View File

@@ -1,10 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using PluginManager.Online.Helpers; using PluginManager.Online.Helpers;
using PluginManager.Others; using PluginManager.Others;
using OperatingSystem = PluginManager.Others.OperatingSystem; using OperatingSystem = PluginManager.Others.OperatingSystem;
namespace PluginManager.Online; namespace PluginManager.Online;
@@ -22,10 +20,20 @@ public class PluginsManager
VersionsLink = vlink; VersionsLink = vlink;
} }
/// <summary>
/// The default Plugin Manager constructor. It uses the default links.
/// </summary>
public PluginsManager()
{
PluginsLink = "https://raw.githubusercontent.com/andreitdr/SethPlugins/releases/PluginsList";
VersionsLink = "https://raw.githubusercontent.com/andreitdr/SethPlugins/releases/Versions";
}
/// <summary> /// <summary>
/// The URL of the server /// The URL of the server
/// </summary> /// </summary>
public string PluginsLink { get; } public string PluginsLink { get; }
public string VersionsLink { get; } public string VersionsLink { get; }
/// <summary> /// <summary>
@@ -34,6 +42,7 @@ public class PluginsManager
/// <returns></returns> /// <returns></returns>
public async Task<List<string[]>> GetAvailablePlugins() public async Task<List<string[]>> GetAvailablePlugins()
{ {
// Config.Logger.Log("Got data from " + VersionsLink, this, LogLevel.INFO);
try try
{ {
var list = await ServerCom.ReadTextFromURL(PluginsLink); var list = await ServerCom.ReadTextFromURL(PluginsLink);
@@ -76,14 +85,11 @@ public class PluginsManager
} }
} }
} }
data.Add(new[] { "-", "-", "-", "-" });
return data; return data;
} }
catch (Exception exception) catch (Exception exception)
{ {
Config.Logger.Log("Failed to execute command: listplugs\nReason: " + exception.Message, this, LogLevel.ERROR); Config.Logger.Log(message: "Failed to execute command: listplugs\nReason: " + exception.Message, source: typeof(PluginsManager), type: LogType.ERROR );
} }
return null; return null;
@@ -97,14 +103,14 @@ public class PluginsManager
if (item.StartsWith("#")) if (item.StartsWith("#"))
continue; continue;
string[] split = item.Split(','); var split = item.Split(',');
if (split[0] == pakName) if (split[0] == pakName)
{ {
Console.WriteLine("Searched for " + pakName + " and found " + split[1] + " as version.\nUsed url: " + VersionsLink); // Config.Logger.Log("Searched for " + pakName + " and found " + split[1] + " as version.", LogLevel.INFO);
return new VersionString(split[1]); return new VersionString(split[1]);
} }
} }
return null; return null;
} }
@@ -123,7 +129,7 @@ public class PluginsManager
for (var i = 0; i < len; i++) for (var i = 0; i < len; i++)
{ {
var contents = lines[i].Split(','); var contents = lines[i].Split(',');
if (contents[0] == name) if (contents[0].ToLowerInvariant() == name.ToLowerInvariant())
{ {
if (contents.Length == 6) if (contents.Length == 6)
return new[] { contents[2], contents[3], contents[5] }; return new[] { contents[2], contents[3], contents[5] };
@@ -135,9 +141,9 @@ public class PluginsManager
} }
catch (Exception exception) catch (Exception exception)
{ {
Config.Logger.Log("Failed to execute command: listplugs\nReason: " + exception.Message, this, LogLevel.ERROR); Config.Logger.Log("Failed to execute command: plugin list\nReason: " + exception.Message, source: typeof(PluginsManager), type: LogType.ERROR);
} }
return new string[] { null!, null!, null! }; return null;
} }
} }

View File

@@ -1,13 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Collections.Generic;
using PluginManager.Online.Helpers; using PluginManager.Online.Helpers;
using PluginManager.Others;
namespace PluginManager.Online; namespace PluginManager.Online;
@@ -32,7 +29,8 @@ public static class ServerCom
/// <param name="location">The location where to store the downloaded data</param> /// <param name="location">The location where to store the downloaded data</param>
/// <param name="progress">The <see cref="IProgress{T}" /> to track the download</param> /// <param name="progress">The <see cref="IProgress{T}" /> to track the download</param>
/// <returns></returns> /// <returns></returns>
public static async Task DownloadFileAsync(string URL, string location, IProgress<float> progress, public static async Task DownloadFileAsync(
string URL, string location, IProgress<float> progress,
IProgress<long>? downloadedBytes) IProgress<long>? downloadedBytes)
{ {
using (var client = new HttpClient()) using (var client = new HttpClient())
@@ -50,4 +48,5 @@ public static class ServerCom
{ {
await DownloadFileAsync(URl, location, progress, null); await DownloadFileAsync(URl, location, progress, null);
} }
} }

View File

@@ -1,51 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace PluginManager.Others.Actions
{
public class ActionManager
{
public List<InternalAction> Actions { get; private set; }
private bool _isInitialized = false;
public ActionManager()
{
if(_isInitialized) return;
Actions = new List<InternalAction>();
_isInitialized = true;
}
public bool ActionExists(string name)
{
if(!_isInitialized) throw new Exception("ActionManager is not initialized");
return Actions.Any(x => x.Name == name);
}
public void AddAction(InternalAction action)
{
if(!_isInitialized) throw new Exception("ActionManager is not initialized");
Actions.Add(action);
}
public void ExecuteAction(string name, string[] args)
{
if(!_isInitialized) throw new Exception("ActionManager is not initialized");
var action = Actions.FirstOrDefault(x => x.Name == name);
if(action == null) throw new Exception($"Action {name} not found");
action.Invoke(args);
}
public async Task ExecuteActionAsync(string name, string[] args)
{
if(!_isInitialized) throw new Exception("ActionManager is not initialized");
var action = Actions.FirstOrDefault(x => x.Name == name);
if(action == null) throw new Exception($"Action {name} not found");
await action.InvokeAsync(args);
}
}
}

View File

@@ -1,40 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PluginManager.Others.Actions
{
public class InternalAction
{
public string? Name { get; init; }
public Action<string[]> Action { get; init; }
public InternalAction(string name, Action<string[]> action)
{
Name = name;
Action = action;
}
public InternalAction(string name, Action action)
{
Name = name;
Action = (o) =>
{
action();
return;
};
}
public void Invoke(string[] args)
{
Action(args);
}
public async Task InvokeAsync(string[] args)
{
await Task.Run(() => Action(args));
}
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using PluginManager.Interfaces;
using PluginManager.Loaders;
namespace PluginManager.Others.Actions;
public class InternalActionManager
{
public Dictionary<string, ICommandAction> Actions = new();
public ActionsLoader loader;
public InternalActionManager(string path, string extension)
{
loader = new ActionsLoader(path, extension);
}
public async Task Initialize()
{
//loader.ActionLoadedEvent += OnActionLoaded;
var m_actions = await loader.Load();
if (m_actions == null) return;
foreach (var action in m_actions)
{
Actions.TryAdd(action.ActionName, action);
}
}
public async Task Refresh()
{
Actions.Clear();
await Initialize();
}
// private void OnActionLoaded(string name, string typeName, bool success, Exception? e)
// {
// if (!success)
// {
// Config.Logger.Error(e);
// return;
// }
//
// Config.Logger.Log($"Action {name} loaded successfully", LogLevel.INFO, true);
// }
public async Task<string> Execute(string actionName, params string[]? args)
{
if (!Actions.ContainsKey(actionName))
{
Config.Logger.Log($"Action {actionName} not found", type: LogType.ERROR, source: typeof(InternalActionManager));
return "Action not found";
}
try
{
await Actions[actionName].Execute(args);
return "Action executed";
}
catch (Exception e)
{
Config.Logger.Log(e.Message , type: LogType.ERROR, source: typeof(InternalActionManager));
return e.Message;
}
}
}

View File

@@ -4,25 +4,60 @@ using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace PluginManager.Others namespace PluginManager.Others;
{
public static class ArchiveManager public static class ArchiveManager
{ {
public static bool isInitialized { get; private set; }
private static string? archiveFolder; private static string? archiveFolder;
public static bool isInitialized { get; private set; }
public static void Initialize() public static void Initialize()
{ {
if (isInitialized) throw new Exception("ArchiveManager is already initialized"); if (isInitialized) throw new Exception("ArchiveManager is already initialized");
if (!Config.Data.ContainsKey("ArchiveFolder")) if (!Config.AppSettings.ContainsKey("ArchiveFolder"))
Config.Data["ArchiveFolder"] = "./Data/PAKS/"; Config.AppSettings["ArchiveFolder"] = "./Data/PAKS/";
archiveFolder = Config.Data["ArchiveFolder"]; archiveFolder = Config.AppSettings["ArchiveFolder"];
isInitialized = true; isInitialized = true;
} }
/// <summary>
/// Read a file from a zip archive. The output is a byte array
/// </summary>
/// <param name="fileName">The file name in the archive</param>
/// <param name="archName">The archive location on the disk</param>
/// <returns>An array of bytes that represents the Stream value from the file that was read inside the archive</returns>
public async static Task<byte[]?> ReadStreamFromPakAsync(string fileName, string archName)
{
if (!isInitialized) throw new Exception("ArchiveManager is not initialized");
archName = archiveFolder + archName;
if (!File.Exists(archName))
throw new Exception("Failed to load file !");
byte[]? data = null;
using (var zip = ZipFile.OpenRead(archName))
{
var entry = zip.Entries.FirstOrDefault(entry => entry.FullName == fileName || entry.Name == fileName);
if (entry is null) throw new Exception("File not found in archive");
var MemoryStream = new MemoryStream();
var stream = entry.Open();
await stream.CopyToAsync(MemoryStream);
data = MemoryStream.ToArray();
stream.Close();
MemoryStream.Close();
}
return data;
}
/// <summary> /// <summary>
/// Read data from a file that is inside an archive (ZIP format) /// Read data from a file that is inside an archive (ZIP format)
/// </summary> /// </summary>
@@ -58,7 +93,7 @@ namespace PluginManager.Others
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log(ex.Message, "Archive Manager", LogLevel.ERROR); // Write the error to a file Config.Logger.Log(message: ex.Message, source: typeof(ArchiveManager), type: LogType.ERROR); // Write the error to a file
await Task.Delay(100); await Task.Delay(100);
return await ReadFromPakAsync(FileName, archFile); return await ReadFromPakAsync(FileName, archFile);
} }
@@ -72,7 +107,8 @@ namespace PluginManager.Others
/// <param name="progress">The progress that is updated as a file is processed</param> /// <param name="progress">The progress that is updated as a file is processed</param>
/// <param name="type">The type of progress</param> /// <param name="type">The type of progress</param>
/// <returns></returns> /// <returns></returns>
public static async Task ExtractArchive(string zip, string folder, IProgress<float> progress, public static async Task ExtractArchive(
string zip, string folder, IProgress<float> progress,
UnzipProgressType type) UnzipProgressType type)
{ {
if (!isInitialized) throw new Exception("ArchiveManager is not initialized"); if (!isInitialized) throw new Exception("ArchiveManager is not initialized");
@@ -95,7 +131,7 @@ namespace PluginManager.Others
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log($"Failed to extract {entry.Name}. Exception: {ex.Message}", "Archive Manager", LogLevel.ERROR); Config.Logger.Log(ex.Message, source: typeof(ArchiveManager), type: LogType.ERROR);
} }
currentZIPFile++; currentZIPFile++;
@@ -127,7 +163,7 @@ namespace PluginManager.Others
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log($"Failed to extract {entry.Name}. Exception: {ex.Message}", "Archive Manager", LogLevel.ERROR); Config.Logger.Log(ex.Message, source: typeof(ArchiveManager), type: LogType.ERROR);
} }
await Task.Delay(10); await Task.Delay(10);
@@ -138,4 +174,3 @@ namespace PluginManager.Others
} }
} }
} }
}

View File

@@ -1,21 +1,14 @@
using Discord.Commands; using System.Linq;
using Discord.Commands;
using Discord.WebSocket; using Discord.WebSocket;
using System; using PluginManager.Bot;
using System.Collections.Generic;
using System.Linq; namespace PluginManager.Others;
using System.Text;
using System.Threading.Tasks;
namespace PluginManager.Others
{
public class DBCommandExecutingArguments public class DBCommandExecutingArguments
{ {
public SocketCommandContext context { get; init; } public DBCommandExecutingArguments(
public string cleanContent { get; init; } SocketCommandContext context, string cleanContent, string commandUsed, string[]? arguments)
public string commandUsed { get;init; }
public string[] arguments { get;init; }
public DBCommandExecutingArguments(SocketCommandContext context, string cleanContent, string commandUsed, string[] arguments)
{ {
this.context = context; this.context = context;
this.cleanContent = cleanContent; this.cleanContent = cleanContent;
@@ -23,5 +16,32 @@ namespace PluginManager.Others
this.arguments = arguments; this.arguments = arguments;
} }
public DBCommandExecutingArguments(SocketUserMessage? message, DiscordSocketClient client)
{
this.context = new SocketCommandContext(client, message);
int pos = 0;
if (message.HasMentionPrefix(client.CurrentUser, ref pos))
{
var mentionPrefix = "<@" + client.CurrentUser.Id + ">";
this.cleanContent = message.Content.Substring(mentionPrefix.Length + 1);
} }
else
{
this.cleanContent = message.Content.Substring(Config.DiscordBot.botPrefix.Length);
}
var split = this.cleanContent.Split(' ');
string[]? argsClean = null;
if (split.Length > 1)
argsClean = string.Join(' ', split, 1, split.Length - 1).Split(' ');
this.commandUsed = split[0];
this.arguments = argsClean;
}
public SocketCommandContext context { get; init; }
public string cleanContent { get; init; }
public string commandUsed { get; init; }
public string[]? arguments { get; init; }
} }

View File

@@ -14,9 +14,8 @@ public enum OperatingSystem
/// <summary> /// <summary>
/// The output log type /// The output log type
/// </summary> /// </summary>
public enum LogLevel public enum LogType
{ {
NONE,
INFO, INFO,
WARNING, WARNING,
ERROR, ERROR,
@@ -31,7 +30,18 @@ public enum UnzipProgressType
public enum SaveType public enum SaveType
{ {
NORMAL, TXT,
BACKUP JSON
} }
public enum InternalActionRunType
{
ON_STARTUP,
ON_CALL
}
internal enum ExceptionExitCode : int
{
CONFIG_FAILED_TO_LOAD = 1,
CONFIG_KEY_NOT_FOUND = 2,
}

View File

@@ -5,6 +5,7 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord;
namespace PluginManager.Others; namespace PluginManager.Others;
@@ -15,9 +16,19 @@ public static class Functions
{ {
/// <summary> /// <summary>
/// The location for the Resources folder /// The location for the Resources folder
/// String: ./Data/Resources/
/// </summary> /// </summary>
public static readonly string dataFolder = @"./Data/Resources/"; public static readonly string dataFolder = @"./Data/Resources/";
public static Color RandomColor
{
get
{
var random = new Random();
return new Color(random.Next(0, 255), random.Next(0, 255), random.Next(0, 255));
}
}
/// <summary> /// <summary>
/// Get the Operating system you are runnin on /// Get the Operating system you are runnin on
/// </summary> /// </summary>
@@ -42,7 +53,8 @@ public static class Functions
/// <exception cref="ArgumentOutOfRangeException">Triggered if <paramref name="bufferSize" /> is less then or equal to 0</exception> /// <exception cref="ArgumentOutOfRangeException">Triggered if <paramref name="bufferSize" /> is less then or equal to 0</exception>
/// <exception cref="InvalidOperationException">Triggered if <paramref name="stream" /> is not readable</exception> /// <exception cref="InvalidOperationException">Triggered if <paramref name="stream" /> is not readable</exception>
/// <exception cref="ArgumentException">Triggered in <paramref name="destination" /> is not writable</exception> /// <exception cref="ArgumentException">Triggered in <paramref name="destination" /> is not writable</exception>
public static async Task CopyToOtherStreamAsync(this Stream stream, Stream destination, int bufferSize, public static async Task CopyToOtherStreamAsync(
this Stream stream, Stream destination, int bufferSize,
IProgress<long>? progress = null, IProgress<long>? progress = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
@@ -56,7 +68,8 @@ public static class Functions
var buffer = new byte[bufferSize]; var buffer = new byte[bufferSize];
long totalBytesRead = 0; long totalBytesRead = 0;
int bytesRead; int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)
.ConfigureAwait(false)) != 0)
{ {
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
@@ -64,40 +77,22 @@ public static class Functions
} }
} }
/// <summary>
/// Save to JSON file public static T SelectRandomValueOf<T>()
/// </summary>
/// <typeparam name="T">The class type</typeparam>
/// <param name="file">The file path</param>
/// <param name="Data">The values</param>
/// <returns></returns>
public static async Task SaveToJsonFile<T>(string file, T Data)
{ {
var str = new MemoryStream(); var enums = Enum.GetValues(typeof(T));
await JsonSerializer.SerializeAsync(str, Data, typeof(T), new JsonSerializerOptions { WriteIndented = true }); var random = new Random();
await File.WriteAllBytesAsync(file, str.ToArray()); return (T)enums.GetValue(random.Next(enums.Length));
await str.FlushAsync();
str.Close();
} }
/// <summary> public static T RandomValue<T>(this T[] values)
/// Convert json text or file to some kind of data
/// </summary>
/// <typeparam name="T">The data type</typeparam>
/// <param name="input">The file or json text</param>
/// <returns></returns>
public static async Task<T> ConvertFromJson<T>(string input)
{ {
Console.WriteLine(input); Random random = new();
Stream text; return values[random.Next(values.Length)];
if (File.Exists(input)) }
text = new MemoryStream(await File.ReadAllBytesAsync(input));
else public static string ToResourcesPath(this string path)
text = new MemoryStream(Encoding.ASCII.GetBytes(input)); {
text.Position = 0; return Path.Combine(dataFolder, path);
var obj = await JsonSerializer.DeserializeAsync<T>(text);
await text.FlushAsync();
text.Close();
return (obj ?? default)!;
} }
} }

View File

@@ -0,0 +1,47 @@
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace PluginManager;
public class JsonManager
{
/// <summary>
/// Save to JSON file
/// </summary>
/// <typeparam name="T">The class type</typeparam>
/// <param name="file">The file path</param>
/// <param name="Data">The values</param>
/// <returns></returns>
public static async Task SaveToJsonFile<T>(string file, T Data)
{
var str = new MemoryStream();
await JsonSerializer.SerializeAsync(str, Data, typeof(T), new JsonSerializerOptions { WriteIndented = true });
await File.WriteAllBytesAsync(file, str.ToArray());
await str.FlushAsync();
str.Close();
}
/// <summary>
/// Convert json text or file to some kind of data
/// </summary>
/// <typeparam name="T">The data type</typeparam>
/// <param name="input">The file or json text</param>
/// <returns></returns>
public static async Task<T> ConvertFromJson<T>(string input)
{
Stream text;
if (File.Exists(input))
text = new MemoryStream(await File.ReadAllBytesAsync(input));
else
text = new MemoryStream(Encoding.ASCII.GetBytes(input));
text.Position = 0;
var obj = await JsonSerializer.DeserializeAsync<T>(text);
await text.FlushAsync();
text.Close();
return (obj ?? default)!;
}
}

View File

@@ -1,52 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PluginManager.Others.Logger
{
public class DBLogger
{
private List<LogMessage> LogHistory = new List<LogMessage>();
private List<LogMessage> ErrorHistory = new List<LogMessage>();
public IReadOnlyList<LogMessage> Logs => LogHistory;
public IReadOnlyList<LogMessage> Errors => ErrorHistory;
public delegate void LogHandler(string message, LogLevel logType);
public event LogHandler LogEvent;
private string _logFolder;
private string _errFolder;
public DBLogger()
{
_logFolder = Config.Data["LogFolder"];
_errFolder = Config.Data["ErrorFolder"];
}
public void Log(string message, string sender = "unknown", LogLevel type = LogLevel.INFO) => Log(new LogMessage(message, type, sender));
public void Log(LogMessage message)
{
if(LogEvent is not null)
LogEvent?.Invoke(message.Message, message.Type);
if (message.Type != LogLevel.NONE)
LogHistory.Add(message);
else
ErrorHistory.Add(message);
}
public void Log(string message, object sender, LogLevel type = LogLevel.NONE) => Log(message, sender.GetType().Name, type);
public async void SaveToFile()
{
await Functions.SaveToJsonFile(_logFolder + "/" + DateTime.Now.ToString("yyyy-MM-dd") + ".json", LogHistory);
await Functions.SaveToJsonFile(_errFolder + "/" + DateTime.Now.ToString("yyyy-MM-dd") + ".json", ErrorHistory);
}
}
}

View File

@@ -0,0 +1,78 @@
using System;
using System.Linq;
using PluginManager.Interfaces.Logger;
namespace PluginManager.Others.Logger;
public class Log : ILog
{
public string Message { get; set; }
public string OutputFile { get; set; }
public Type? Source { get; set; }
public LogType Type { get; set; }
public DateTime ThrowTime { get; set; }
public Log(string message, string outputFile, Type? source, LogType type, DateTime throwTime)
{
Message = message;
OutputFile = outputFile;
Source = source;
Type = type;
ThrowTime = throwTime;
}
public Log(string message, string outputFile, Type? source, LogType type)
{
Message = message;
OutputFile = outputFile;
Source = source;
Type = type;
ThrowTime = DateTime.Now;
}
public Log(string message, string outputFile, Type? source)
{
Message = message;
OutputFile = outputFile;
Source = source;
Type = LogType.INFO;
ThrowTime = DateTime.Now;
}
public Log(string message, string outputFile)
{
Message = message;
OutputFile = outputFile;
Source = typeof(Log);
Type = LogType.INFO;
ThrowTime = DateTime.Now;
}
public Log(string message)
{
Message = message;
OutputFile = "";
Source = typeof(Log);
Type = LogType.INFO;
ThrowTime = DateTime.Now;
}
public static implicit operator Log(string message) => new (message);
public static implicit operator string(Log log) => $"[{log.ThrowTime}] {log.Message}";
public string AsLongString()
{
return $"[{ThrowTime}] [{Source}] [{Type}] {Message}";
}
public string AsShortString()
{
return this;
}
public string FormatedLongString()
{
return $"[{ThrowTime}]\t[{Source}]\t\t\t[{Type}]\t{Message}";
}
}

View File

@@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PluginManager.Others.Logger
{
public class LogMessage
{
public string Message { get; set; }
public LogLevel Type { get; set; }
public string Time { get; set; }
public string Sender { get; set; }
public LogMessage(string message, LogLevel type)
{
Message = message;
Type = type;
Time = DateTime.Now.ToString("HH:mm:ss");
}
public LogMessage(string message, LogLevel type, string sender) : this(message, type)
{
Sender = sender;
}
public override string ToString()
{
return $"[{Time}] {Message}";
}
public static explicit operator LogMessage(string message)
{
return new LogMessage(message, LogLevel.INFO);
}
public static explicit operator LogMessage((string message, LogLevel type) tuple)
{
return new LogMessage(tuple.message, tuple.type);
}
public static explicit operator LogMessage((string message, LogLevel type, string sender) tuple)
{
return new LogMessage(tuple.message, tuple.type, tuple.sender);
}
}
}

View File

@@ -0,0 +1,69 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using PluginManager.Interfaces.Logger;
namespace PluginManager.Others.Logger;
public sealed class Logger : ILogger
{
public bool IsEnabled { get; init; }
public bool OutputToFile { get; init; }
public LogType LowestLogLevel { get; set; }
private bool UseShortVersion { get; }
public Logger(bool useShortVersion, bool outputToFile, LogType lowestLogLevel = LogType.INFO)
{
UseShortVersion = useShortVersion;
OutputToFile = outputToFile;
IsEnabled = true;
LowestLogLevel = lowestLogLevel;
}
public event EventHandler<Log>? OnLog;
private async Task Log(Log logMessage)
{
if (!IsEnabled) return;
OnLog?.Invoke(this, logMessage);
if (logMessage.Type < LowestLogLevel) return;
if (OutputToFile)
await File.AppendAllTextAsync(
logMessage.OutputFile,
(UseShortVersion ? logMessage : logMessage.AsLongString()) + "\n");
}
public async void Log(string message = "", string outputFile = "", Type? source = default, LogType type = LogType.INFO, DateTime throwTime = default)
{
if (!IsEnabled) return;
if (type < LowestLogLevel) return;
if (string.IsNullOrEmpty(message)) return;
if (string.IsNullOrEmpty(outputFile)) outputFile = Config.AppSettings["LogFolder"] + "/" + DateTime.Now.ToString("yyyy-MM-dd") + ".log";
if(throwTime == default) throwTime = DateTime.Now;
if (source == default) source = typeof(Log);
await Log(new Log(message, outputFile, source, type, throwTime));
}
public async void Log(Exception exception, LogType logType = LogType.ERROR, Type? source = null)
{
if (!IsEnabled) return;
if (logType < LowestLogLevel) return;
await Log(new Log(exception.Message,
Config.AppSettings["LogFolder"] + "/" + DateTime.Now.ToString("yyyy-MM-dd") + ".log",
source, logType, DateTime.Now));
}
}

View File

@@ -1,5 +1,4 @@
using System.Linq; using System.Linq;
using Discord; using Discord;
using Discord.WebSocket; using Discord.WebSocket;

View File

@@ -0,0 +1,129 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace PluginManager.Others;
public class SettingsDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
public string? _file { get; }
private IDictionary<TKey, TValue>? _dictionary;
public SettingsDictionary(string? file)
{
_file = file;
if (!LoadFromFile())
{
_dictionary = new Dictionary<TKey, TValue>();
SaveToFile();
}
}
public async Task SaveToFile()
{
if (!string.IsNullOrEmpty(_file))
await JsonManager.SaveToJsonFile(_file, _dictionary);
}
private bool LoadFromFile()
{
if (!string.IsNullOrEmpty(_file))
try
{
if (File.Exists(_file))
{
string FileContent = File.ReadAllText(_file);
if (string.IsNullOrEmpty(FileContent))
File.WriteAllText(_file, "{}");
if(!FileContent.Contains("{") || !FileContent.Contains("}"))
File.WriteAllText(_file, "{}");
}
else
File.WriteAllText(_file, "{}");
_dictionary = JsonManager.ConvertFromJson<IDictionary<TKey, TValue>>(_file).Result;
return true;
}
catch
{
return false;
}
return false;
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _dictionary!.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable) _dictionary!).GetEnumerator();
}
public void Add(KeyValuePair<TKey, TValue> item)
{
this._dictionary!.Add(item);
}
public void Clear()
{
this._dictionary!.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return this._dictionary!.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
this._dictionary!.CopyTo(array, arrayIndex);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return this._dictionary!.Remove(item);
}
public int Count => _dictionary!.Count;
public bool IsReadOnly => _dictionary!.IsReadOnly;
public void Add(TKey key, TValue value)
{
this._dictionary!.Add(key, value);
}
public bool ContainsKey(TKey key)
{
return this._dictionary!.ContainsKey(key);
}
public bool Remove(TKey key)
{
return this._dictionary!.Remove(key);
}
public bool TryGetValue(TKey key, out TValue value)
{
return this._dictionary!.TryGetValue(key, out value);
}
public TValue this[TKey key]
{
get
{
if (this._dictionary!.ContainsKey(key))
if(this._dictionary[key] is string s && !string.IsNullOrEmpty(s) && !string.IsNullOrWhiteSpace(s))
return this._dictionary[key];
return default!;
}
set => this._dictionary![key] = value;
}
public ICollection<TKey> Keys => _dictionary!.Keys;
public ICollection<TValue> Values => _dictionary!.Values;
}

View File

@@ -12,7 +12,7 @@
<None Remove="BlankWindow1.xaml" /> <None Remove="BlankWindow1.xaml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Discord.Net" Version="3.8.1" /> <PackageReference Include="Discord.Net" Version="3.11.0" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.117" /> <PackageReference Include="System.Data.SQLite.Core" Version="1.0.118" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -8,9 +8,7 @@ This project is based on:
## Plugins ## Plugins
#### Requirements: - Some plugins can be found in [this repo](https://github.com/andreitdr/SethPlugins).
- [Visual Studio](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&channel=Release&version=VS2022&source=VSLandingPage&cid=2030&passive=false)
- .NET 6 (downloaded with Visual Studio)
Plugin Types: Plugin Types:
1. Commands 1. Commands
@@ -19,6 +17,10 @@ Plugin Types:
### How to create a plugin ### How to create a plugin
#### Requirements:
- [Visual Studio](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&channel=Release&version=VS2022&source=VSLandingPage&cid=2030&passive=false)
- .NET 6 (downloaded with Visual Studio)
First of all, create a new project (class library) in Visual Studio. First of all, create a new project (class library) in Visual Studio.
Then import the PluginManager as reference to your project. Then import the PluginManager as reference to your project.
@@ -35,7 +37,7 @@ using PluginManager.Interfaces;
namespace LevelingSystem; namespace LevelingSystem;
internal class LevelCommand : DBCommand public class LevelCommand : DBCommand
{ {
public string Command => "level"; public string Command => "level";
@@ -47,7 +49,7 @@ internal class LevelCommand : DBCommand
public bool requireAdmin => false; public bool requireAdmin => false;
public async void ExecuteServer(CmdArgs context) public async void ExecuteServer(DBCommandExecutingArguments context)
{ {
//Variables.database is a sql connection that is defined in an auxiliary file in the same napespace as this class //Variables.database is a sql connection that is defined in an auxiliary file in the same napespace as this class
object[] user = await Variables.database.ReadDataArrayAsync($"SELECT * FROM Levels WHERE UserID='{context.Message.Author.Id}'"); object[] user = await Variables.database.ReadDataArrayAsync($"SELECT * FROM Levels WHERE UserID='{context.Message.Author.Id}'");
@@ -72,7 +74,7 @@ internal class LevelCommand : DBCommand
} }
//Optional method (tell the bot what should it do if the command is executed from a DM channel) //Optional method (tell the bot what should it do if the command is executed from a DM channel)
//public async void ExecuteDM(CmdArgs context) { //public async void ExecuteDM(DBCommandExecutingArguments context) {
// //
//} //}
} }
@@ -92,7 +94,7 @@ internal class LevelCommand : DBCommand
From here on, start coding. When your plugin is done, build it as any DLL project then add it to the following path From here on, start coding. When your plugin is done, build it as any DLL project then add it to the following path
`{bot_executable}/Data/Plugins/<optional subfolder>/[plugin name].dll` `{bot_executable}/Data/Plugins/<optional subfolder>/[plugin name].dll`
Then, reload bot and execute command `lp` in bot's console. The plugin should be loaded into memory or an error is thrown if not. If an error is thrown, then Then, reload bot and execute command `plugin load` in the console. The plugin should be loaded into memory or an error is thrown if not. If an error is thrown, then
there is something wrong in your command's code. there is something wrong in your command's code.
## 2. Events ## 2. Events
@@ -190,4 +192,4 @@ namespace SlashCommands
You can create multiple commands, events and slash commands into one single plugin (class library). The PluginManager will detect the classes and load them individualy. If there are more commands (normal commands, events or slash commands) into a single project (class library) they can use the same resources (a class for example) that is contained within the plugin. You can create multiple commands, events and slash commands into one single plugin (class library). The PluginManager will detect the classes and load them individualy. If there are more commands (normal commands, events or slash commands) into a single project (class library) they can use the same resources (a class for example) that is contained within the plugin.
> Updated: 7.04.2023 > Updated: 25.09.2023

View File

@@ -7,6 +7,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordBot", "DiscordBot\Di
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PluginManager", "PluginManager\PluginManager.csproj", "{EDD4D9B3-98DD-4367-A09F-D1C5ACB61132}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PluginManager", "PluginManager\PluginManager.csproj", "{EDD4D9B3-98DD-4367-A09F-D1C5ACB61132}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SethPlugins", "SethPlugins", "{78B6D390-F61A-453F-B38D-E4C054321615}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MusicPlayer", "..\SethPlugins\MusicPlayer\MusicPlayer.csproj", "{1690CBBC-BDC0-4DD8-B701-F8817189D9D5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LevelingSystem", "..\SethPlugins\LevelingSystem\LevelingSystem.csproj", "{BFE3491C-AC01-4252-B242-6451270FC548}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PythonCompatibilityLayer", "..\SethPlugins\PythonCompatibilityLayer\PythonCompatibilityLayer.csproj", "{81ED4953-13E5-4950-96A8-8CEF5FD59559}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -21,11 +29,26 @@ Global
{EDD4D9B3-98DD-4367-A09F-D1C5ACB61132}.Debug|Any CPU.Build.0 = Debug|Any CPU {EDD4D9B3-98DD-4367-A09F-D1C5ACB61132}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EDD4D9B3-98DD-4367-A09F-D1C5ACB61132}.Release|Any CPU.ActiveCfg = Release|Any CPU {EDD4D9B3-98DD-4367-A09F-D1C5ACB61132}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EDD4D9B3-98DD-4367-A09F-D1C5ACB61132}.Release|Any CPU.Build.0 = Release|Any CPU {EDD4D9B3-98DD-4367-A09F-D1C5ACB61132}.Release|Any CPU.Build.0 = Release|Any CPU
{1690CBBC-BDC0-4DD8-B701-F8817189D9D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1690CBBC-BDC0-4DD8-B701-F8817189D9D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1690CBBC-BDC0-4DD8-B701-F8817189D9D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1690CBBC-BDC0-4DD8-B701-F8817189D9D5}.Release|Any CPU.Build.0 = Release|Any CPU
{BFE3491C-AC01-4252-B242-6451270FC548}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BFE3491C-AC01-4252-B242-6451270FC548}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFE3491C-AC01-4252-B242-6451270FC548}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BFE3491C-AC01-4252-B242-6451270FC548}.Release|Any CPU.Build.0 = Release|Any CPU
{81ED4953-13E5-4950-96A8-8CEF5FD59559}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81ED4953-13E5-4950-96A8-8CEF5FD59559}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81ED4953-13E5-4950-96A8-8CEF5FD59559}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81ED4953-13E5-4950-96A8-8CEF5FD59559}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{1690CBBC-BDC0-4DD8-B701-F8817189D9D5} = {78B6D390-F61A-453F-B38D-E4C054321615}
{BFE3491C-AC01-4252-B242-6451270FC548} = {78B6D390-F61A-453F-B38D-E4C054321615}
{81ED4953-13E5-4950-96A8-8CEF5FD59559} = {78B6D390-F61A-453F-B38D-E4C054321615}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3FB3C5DE-ED21-4D2E-ABDD-3A00EE4A2FFF} SolutionGuid = {3FB3C5DE-ED21-4D2E-ABDD-3A00EE4A2FFF}