45 Commits

Author SHA1 Message Date
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
50 changed files with 2353 additions and 2260 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,24 @@
using System;
using System.Threading.Tasks;
using PluginManager;
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,47 @@
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...", "Exit", isInternal: false);
await Config.AppSettings.SaveToFile();
await Config.Logger.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)...", "Exit", LogLevel.WARNING, false);
Environment.Exit(0);
break;
default:
Console.WriteLine("Invalid argument !");
break;
}
}
}
}

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[] { "-", "-", "-" });
Utilities.Utilities.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[] { "-", "-", "-" }
};
Utilities.Utilities.FormatAndAlignTable(actionData,
TableFormat.CENTER_EACH_COLUMN_BASED
);
}
}

View File

@@ -0,0 +1,197 @@
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;
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]";
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");
return;
}
switch ( args[0] )
{
case "list":
var manager =
new PluginsManager(Program.URLs["PluginList"], Program.URLs["PluginVersions"]);
var data = await manager.GetAvailablePlugins();
var items = new List<string[]>
{
new[] { "-", "-", "-", "-" },
new[] { "Name", "Description", "Type", "Version" },
new[] { "-", "-", "-", "-" }
};
foreach (var plugin in data) items.Add(new[] { plugin[0], plugin[1], plugin[2], plugin[3] });
items.Add(new[] { "-", "-", "-", "-" });
Utilities.Utilities.FormatAndAlignTable(items, TableFormat.DEFAULT);
break;
case "load":
if (pluginsLoaded)
break;
var loader = new PluginLoader(Config.DiscordBot.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;
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;
}
}
var pluginManager =
new PluginsManager(Program.URLs["PluginList"], Program.URLs["PluginVersions"]);
var pluginData = await pluginManager.GetPluginLinkByName(pluginName);
if (pluginData == null || pluginData.Length == 0)
{
Console.WriteLine($"Plugin {pluginName} not found. Please check the spelling and try again.");
break;
}
var pluginType = pluginData[0];
var pluginLink = pluginData[1];
var pluginRequirements = pluginData[2];
Console.WriteLine("Downloading plugin...");
//download plugin progress bar for linux and windows terminals
var spinner = new Utilities.Utilities.Spinner();
spinner.Start();
IProgress<float> progress = new Progress<float>(p => { spinner.Message = $"Downloading {pluginName}... {Math.Round(p, 2)}% "; });
await ServerCom.DownloadFileAsync(pluginLink, $"./Data/{pluginType}s/{pluginName}.dll", progress);
spinner.Stop();
Console.WriteLine();
if (pluginRequirements == string.Empty)
{
Console.WriteLine("Plugin installed successfully");
break;
}
Console.WriteLine("Downloading plugin requirements...");
var requirementsURLs = await ServerCom.ReadTextFromURL(pluginRequirements);
foreach (var requirement in requirementsURLs)
{
if (requirement.Length < 2)
continue;
var reqdata = requirement.Split(',');
var url = reqdata[0];
var filename = reqdata[1];
Console.WriteLine($"Downloading {filename}... ");
spinner.Start();
await ServerCom.DownloadFileAsync(url, $"./{filename}.dll", null);
spinner.Stop();
await Task.Delay(1000);
Console.WriteLine("Downloaded " + filename + " successfully");
}
Console.WriteLine("Finished installing " + pluginName + " successfully");
break;
}
}
}

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

@@ -3,11 +3,11 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Nullable>disable</Nullable> <Nullable>disable</Nullable>
<ApplicationIcon /> <ApplicationIcon/>
<StartupObject /> <StartupObject/>
<SignAssembly>False</SignAssembly> <SignAssembly>False</SignAssembly>
<IsPublishable>True</IsPublishable> <IsPublishable>True</IsPublishable>
<AssemblyVersion>1.0.2.1</AssemblyVersion> <AssemblyVersion>1.0.2.2</AssemblyVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>none</DebugType> <DebugType>none</DebugType>
@@ -16,24 +16,27 @@
<DebugType>none</DebugType> <DebugType>none</DebugType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="Data\**" /> <Compile Remove="Data\**"/>
<Compile Remove="obj\**" /> <Compile Remove="obj\**"/>
<Compile Remove="Output\**" /> <Compile Remove="Output\**"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Remove="Data\**" /> <EmbeddedResource Remove="Data\**"/>
<EmbeddedResource Remove="obj\**" /> <EmbeddedResource Remove="obj\**"/>
<EmbeddedResource Remove="Output\**" /> <EmbeddedResource Remove="Output\**"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<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="Sodium.Core" Version="1.3.3"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\PluginManager\PluginManager.csproj" /> <ProjectReference Include="..\PluginManager\PluginManager.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,31 +1,28 @@
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 class Entry
{
public static void Main(string[] args) public static void Main(string[] args)
{ {
AppDomain currentDomain = AppDomain.CurrentDomain; var currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(LoadFromSameFolder); 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),
string assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll"); "./Libraries"
);
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,64 @@
using System.Diagnostics;
using System.IO;
using System; using System;
using System.Collections.Generic; using System.IO;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using PluginManager; using PluginManager;
using PluginManager.Others;
using PluginManager.Online; 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 !"); Console.WriteLine("Welcome to the SethBot installer !");
Console.WriteLine("First, we need to configure the bot. Don't worry, it will be quick !"); Console.WriteLine("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 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("The bot token is required to run the bot. You can get it from the Discord Developer Portal. (https://discord.com/developers/applications)");
if (!Config.AppSettings.ContainsKey("token"))
{
Console.WriteLine("Please enter the bot token :"); Console.WriteLine("Please enter the bot token :");
var token = Console.ReadLine(); var token = Console.ReadLine();
Config.AppSettings.Add("token", token);
}
if (!Config.AppSettings.ContainsKey("prefix"))
{
Console.WriteLine("Please enter the bot prefix :"); Console.WriteLine("Please enter the bot prefix :");
var prefix = Console.ReadLine(); var prefix = Console.ReadLine();
Config.AppSettings.Add("prefix", prefix);
}
if (!Config.AppSettings.ContainsKey("ServerID"))
{
Console.WriteLine("Please enter the Server ID :"); Console.WriteLine("Please enter the Server ID :");
var serverId = Console.ReadLine(); var serverId = Console.ReadLine();
Config.AppSettings.Add("ServerID", serverId);
}
Config.Data.Add("token", token); Config.Logger.Log("Config Saved", "Installer", isInternal: true);
Config.Data.Add("prefix", prefix);
Config.Data.Add("ServerID", serverId);
Config.Logger.Log("Config Saved", "Installer", LogLevel.INFO); Config.AppSettings.SaveToFile();
Config.Data.Save();
Console.WriteLine("Config saved !"); Console.WriteLine("Config saved !");
} }
public static async Task SetupPluginDatabase() public static async Task SetupPluginDatabase()
{ {
Console.WriteLine("The plugin database is required to run the bot but there is nothing configured yet."); 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("Downloading the default database...");
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(); 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)"); private static async Task DownloadPluginDatabase(
answer = Console.ReadLine(); string url = "https://raw.githubusercontent.com/andreitdr/SethDiscordBot/gh-pages/defaultURLs.json")
if (answer == "y")
{ {
Console.WriteLine("A new file will be generated at ./Data/Resources/URLs.json"); var path = "./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"); Directory.CreateDirectory("./Data/Resources");
Utilities.Utilities.ProgressBar bar = new Utilities.Utilities.ProgressBar(Utilities.ProgressBarType.NORMAL){ var spinner = new Utilities.Utilities.Spinner();
Max = 100, spinner.Start();
Color = ConsoleColor.Green, await ServerCom.DownloadFileAsync(url, path, null);
NoColor = true spinner.Stop();
};
IProgress<float> downloadProgress = new Progress<float>(p => bar.Update(p));
await ServerCom.DownloadFileAsync(url, path, downloadProgress, null);
bar.Update(bar.Max);
}
} }
} }

View File

@@ -1,30 +1,22 @@
using System; using System;
using System.Collections.Generic; 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.Bot;
using PluginManager.Online; using PluginManager.Online;
using PluginManager.Online.Helpers; using PluginManager.Online.Helpers;
using PluginManager.Others; using PluginManager.Others;
using PluginManager.Others.Actions;
using DiscordBot.Utilities;
using Microsoft.VisualBasic.CompilerServices;
using OperatingSystem = PluginManager.Others.OperatingSystem;
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 SettingsDictionary<string, string> URLs;
private static bool loadPluginsOnStartup = false; public static InternalActionManager internalActionManager;
private static ConsoleCommandsHandler consoleCommandsHandler;
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
@@ -34,15 +26,13 @@ public class Program
{ {
PreLoadComponents(args).Wait(); PreLoadComponents(args).Wait();
if (!Config.Data.ContainsKey("ServerID") || !Config.Data.ContainsKey("token") || if (!AppSettings.ContainsKey("ServerID") || !AppSettings.ContainsKey("token") ||
Config.Data["token"] == null || AppSettings["token"] == null ||
(Config.Data["token"]?.Length != 70 && Config.Data["token"]?.Length != 59) || AppSettings["token"]?.Length != 70 && AppSettings["token"]?.Length != 59 ||
!Config.Data.ContainsKey("prefix") || Config.Data["prefix"] == null || !AppSettings.ContainsKey("prefix") || AppSettings["prefix"] == null ||
Config.Data["prefix"]?.Length != 1 || AppSettings["prefix"]?.Length != 1 ||
(args.Length == 1 && args[0] == "/reset")) args.Length == 1 && args[0] == "/reset")
{
Installer.GenerateStartupConfig(); Installer.GenerateStartupConfig();
}
HandleInput(args.ToList()).Wait(); HandleInput(args.ToList()).Wait();
} }
@@ -54,21 +44,19 @@ public class Program
{ {
#if DEBUG #if DEBUG
Console.WriteLine("Debug mode enabled"); Console.WriteLine("Debug mode enabled");
internalActionManager.Execute("plugin", "load").Wait(); // Load plugins at startup
#endif #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);
} }
} }
@@ -88,40 +76,43 @@ public class Program
Console.WriteLine(message); Console.WriteLine(message);
Console.WriteLine( Console.WriteLine(
$"Running on version: {Assembly.GetExecutingAssembly().GetName().Version}"); $"Running on version: {Assembly.GetExecutingAssembly().GetName().Version}"
Console.WriteLine($"Git URL: {Config.Data["GitURL"]}"); );
Console.WriteLine($"Git URL: {AppSettings["GitURL"]}");
Utilities.Utilities.WriteColorText( Utilities.Utilities.WriteColorText(
"&rRemember to close the bot using the ShutDown command (&ysd&r) or some settings won't be saved\n"); "&rRemember to close the bot using the ShutDown command (&ysd&r) or some settings won't be saved\n"
);
Console.ForegroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.White;
if (Config.Data.ContainsKey("LaunchMessage")) if (AppSettings.ContainsKey("LaunchMessage"))
Utilities.Utilities.WriteColorText(Config.Data["LaunchMessage"]); Utilities.Utilities.WriteColorText(AppSettings["LaunchMessage"]);
Utilities.Utilities.WriteColorText( Utilities.Utilities.WriteColorText(
"Please note that the bot saves a backup save file every time you are using the shudown command (&ysd&c)"); "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("Running on " + Functions.GetOperatingSystem());
Console.WriteLine("============================ LOG ============================"); Console.WriteLine("============================ LOG ============================");
try try
{ {
string token = ""; var token = "";
#if DEBUG #if DEBUG
if (File.Exists("./Data/Resources/token.txt")) token = File.ReadAllText("./Data/Resources/token.txt"); if (File.Exists("./Data/Resources/token.txt")) token = File.ReadAllText("./Data/Resources/token.txt");
else token = Config.Data["token"]; else token = AppSettings["token"];
#else #else
token = Config.Data["token"]; token = AppSettings["token"];
#endif #endif
var prefix = Config.Data["prefix"]; var prefix = AppSettings["prefix"];
var discordbooter = new Boot(token, prefix); var discordbooter = new Boot(token, prefix);
await discordbooter.Awake(); await discordbooter.Awake();
return discordbooter; return discordbooter;
} }
catch (Exception ex) catch ( Exception ex )
{ {
Config.Logger.Log(ex.ToString(), "Bot", LogLevel.ERROR); Logger.Log(ex.ToString(), "Bot", LogLevel.ERROR);
return null; return null;
} }
} }
@@ -132,63 +123,80 @@ public class Program
/// <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(List<string> args)
{ {
Console.WriteLine("Loading Core ..."); Console.WriteLine("Loading Core ...");
var b = await StartNoGui(); //Handle arguments here:
consoleCommandsHandler = new ConsoleCommandsHandler(b.client);
try if (args.Contains("--gui"))
{
if(args.Contains("--gui"))
{ {
// GUI not implemented yet // GUI not implemented yet
Console.WriteLine("GUI not implemented yet"); Console.WriteLine("GUI not implemented yet");
return; return;
} }
// Starting bot after all arguments are handled
var b = await StartNoGui();
try
{
internalActionManager = new InternalActionManager("./Data/Actions", "*.dll");
await internalActionManager.Initialize();
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 &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 !"
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); );
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
);
} }
} }
return;
} }
private static async Task PreLoadComponents(string[] args) private static async Task PreLoadComponents(string[] args)
{ {
await Initialize();
await Config.Initialize(); Logger.LogEvent += (message, type, isInternal) =>
{
if (type == LogLevel.INFO)
Console.ForegroundColor = ConsoleColor.Green;
else if (type == LogLevel.WARNING)
Console.ForegroundColor = ConsoleColor.DarkYellow;
else if (type == LogLevel.ERROR)
Console.ForegroundColor = ConsoleColor.Red;
else if (type == LogLevel.CRITICAL)
Console.ForegroundColor = ConsoleColor.DarkRed;
Console.WriteLine($"[{type.ToString()}] {message}");
Console.ResetColor();
};
if (!Directory.Exists("./Data/Resources") || !File.Exists("./Data/Resources/URLs.json")) if (!Directory.Exists("./Data/Resources") || !File.Exists("./Data/Resources/URLs.json"))
{
await Installer.SetupPluginDatabase(); await Installer.SetupPluginDatabase();
}
URLs = new Json<string, string>("./Data/Resources/URLs.json"); URLs = new SettingsDictionary<string, string>("./Data/Resources/URLs.json");
Config.Logger.LogEvent += (message, type) => { Console.WriteLine(message); };
Console.WriteLine("Loading resources ..."); Console.WriteLine("Loading resources ...");
var main = new Utilities.Utilities.ProgressBar(ProgressBarType.NO_END);
main.Start();
if (Config.Data.ContainsKey("DeleteLogsAtStartup")) if (AppSettings.ContainsKey("DeleteLogsAtStartup"))
if (Config.Data["DeleteLogsAtStartup"] == "true") if (AppSettings["DeleteLogsAtStartup"] == "true")
foreach (var file in Directory.GetFiles("./Output/Logs/")) foreach (var file in Directory.GetFiles("./Output/Logs/"))
File.Delete(file); File.Delete(file);
var OnlineDefaultKeys =
await ServerCom.ReadTextFromURL(URLs["SetupKeys"]); var OnlineDefaultKeys = await ServerCom.ReadTextFromURL(URLs["SetupKeys"]);
Config.Data["Version"] = Assembly.GetExecutingAssembly().GetName().Version.ToString(); AppSettings["Version"] = Assembly.GetExecutingAssembly().GetName().Version.ToString();
foreach (var key in OnlineDefaultKeys) foreach (var key in OnlineDefaultKeys)
{ {
@@ -196,127 +204,38 @@ public class Program
var s = key.Split(' '); var s = key.Split(' ');
try try
{ {
Config.Data[s[0]] = s[1]; AppSettings[s[0]] = s[1];
} }
catch (Exception ex) catch ( Exception ex )
{ {
Config.Logger.Log(ex.ToString(), "Bot", LogLevel.ERROR); Logger.Log(ex.ToString(), "Bot", LogLevel.ERROR);
} }
} }
var onlineSettingsList = await ServerCom.ReadTextFromURL(URLs["Versions"]); var onlineSettingsList = await ServerCom.ReadTextFromURL(URLs["Versions"]);
main.Stop("Loaded online settings. Loading updates ...");
foreach (var key in onlineSettingsList) foreach (var key in onlineSettingsList)
{ {
if (key.Length <= 3 || !key.Contains(' ')) continue; if (key.Length <= 3 || !key.Contains(' ')) continue;
var s = key.Split(' '); var s = key.Split(' ');
switch (s[0]) switch ( s[0] )
{ {
case "CurrentVersion": case "CurrentVersion":
var currentVersion = AppSettings["Version"];
var newVersion = s[1]; var newVersion = s[1];
var currentVersion = Config.Data["Version"]; if (new VersionString(newVersion) != new VersionString(newVersion))
if (!newVersion.Equals(currentVersion))
{ {
var nVer = new VersionString(newVersion.Substring(2)); Console.WriteLine("A new updated was found. Check the changelog for more information.");
var cVer = new VersionString((Config.Data["Version"]).Substring(2)); var changeLog = await ServerCom.ReadTextFromURL(URLs["Changelog"]);
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) foreach (var item in changeLog)
Utilities.Utilities.WriteColorText(item); Utilities.Utilities.WriteColorText(item);
Console.WriteLine("Do you want to update the bot ? (y/n)"); Console.WriteLine("Current version: " + currentVersion);
if (Console.ReadKey().Key == ConsoleKey.Y) Console.WriteLine("Latest version: " + newVersion);
{
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}% "); })); Console.WriteLine("Download from here: https://github.com/andreitdr/SethDiscordBot/releases");
await File.WriteAllTextAsync("Install.sh",
"#!/bin/bash\nunzip -qq -o update.zip \nrm update.zip\nchmod a+x DiscordBot");
try Console.WriteLine("Press any key to continue ...");
{ Console.ReadKey();
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.");
}
}
}
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; break;

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,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using PluginManager;
namespace DiscordBot.Utilities; namespace DiscordBot.Utilities;
public static class Utilities public static class Utilities
{ {
private static Dictionary<char, ConsoleColor> Colors = new() private static readonly Dictionary<char, ConsoleColor> Colors = new()
{ {
{ 'g', ConsoleColor.Green }, { 'g', ConsoleColor.Green },
{ 'b', ConsoleColor.Blue }, { 'b', ConsoleColor.Blue },
@@ -18,7 +15,7 @@ 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)
@@ -201,199 +198,46 @@ 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()
{ {
Sequence = new[] {"|", "/", "-", "\\"}; Sequence = new[] { "|", "/", "-", "\\" };
position = 0; position = 0;
} }
public void Start() public void Start()
{ {
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()
{ {
isRunning=false; isRunning = false;
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,8 @@
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
{
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
}; };
@@ -86,8 +86,10 @@ public class Boot
await commandServiceHandler.InstallCommandsAsync(); await commandServiceHandler.InstallCommandsAsync();
await Task.Delay(2000); await Task.Delay(2000);
Config._DiscordBotClient = this;
while (!isReady) ; while (!isReady) ;
} }
@@ -105,8 +107,10 @@ 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.", this,
LogLevel.ERROR);
await Config.AppSettings.SaveToFile();
await Task.Delay(4000); await Task.Delay(4000);
Environment.Exit(0); Environment.Exit(0);
} }
@@ -136,7 +140,7 @@ 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, this, LogLevel.ERROR);
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. !");
@@ -65,7 +61,6 @@ internal class 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,23 +116,27 @@ 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 + ")"); throw new Exception("Failed to run command ! " + message.CleanContent + " (user: " +
context.Message.Author.Username + " - " + context.Message.Author.Id + ")");
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);

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 DBLogger Logger; public static DBLogger Logger;
public static Json<string, string> Data; public static SettingsDictionary<string, string>? AppSettings;
public static Json<string, string> Plugins;
internal static Boot? _DiscordBotClient;
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,11 +27,10 @@ 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"; AppSettings["ErrorFolder"] = "./Data/Logs/Errors";
Logger = new DBLogger(); Logger = new DBLogger();
@@ -44,124 +41,4 @@ public static class Config
Logger.Log("Config initialized", LogLevel.INFO); Logger.Log("Config initialized", LogLevel.INFO);
} }
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 readonly SQLiteConnection Connection;
{
private string ConnectionString;
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);
} }
@@ -485,5 +479,4 @@ namespace PluginManager.Database
return rows; return rows;
} }
}
} }

View File

@@ -1,12 +1,11 @@
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; }
string Description { get; } string Description { 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.Collections.Generic;
namespace PluginManager.Interfaces.Exceptions;
public interface IException
{
public List<string> Messages { get; set; }
public bool isFatal { get; }
public string GenerateFullMessage();
public void HandleException();
public IException AppendError(string message);
public IException AppendError(List<string> messages);
public IException IsFatal(bool isFatal = true);
}

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,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/Actions/";
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,21 +3,22 @@ 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; }
internal string? TypeName { get; init; } internal string? TypeName { get; init; }
internal bool IsLoaded { get; init; } internal bool IsLoaded { get; init; }
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)
{ {
this.path = path; this.path = path;
@@ -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();
@@ -60,9 +55,11 @@ namespace PluginManager.Loaders
} }
catch (Exception ex) catch (Exception ex)
{ {
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", this,
LogLevel.ERROR);
continue; continue;
} }
if (FileLoaded != null) if (FileLoaded != null)
{ {
var args = new LoaderArgs var args = new LoaderArgs
@@ -78,7 +75,6 @@ namespace PluginManager.Loaders
} }
return (LoadItems<DBEvent>(), LoadItems<DBCommand>(), LoadItems<DBSlashCommand>()); return (LoadItems<DBEvent>(), LoadItems<DBCommand>(), LoadItems<DBSlashCommand>());
} }
@@ -110,7 +106,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 +130,16 @@ namespace PluginManager.Loaders
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log(ex.Message, this, Others.LogLevel.ERROR); Config.Logger.Log(ex.Message, this, LogLevel.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;
@@ -61,11 +58,14 @@ public class PluginLoader
public static List<DBEvent>? Events { get; set; } public static List<DBEvent>? Events { get; set; }
/// <summary> /// <summary>
/// A list of <see cref="DBSlashCommand"/> commands /// A list of <see cref="DBSlashCommand" /> commands
/// </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,11 @@ 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, this,
LogLevel.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", this, LogLevel.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 +102,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 +117,67 @@ public class PluginLoader
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log(ex.Message, this, Others.LogLevel.ERROR); Config.Logger.Log(ex.Message, this, LogLevel.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}!", LogLevel.INFO);
} }
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 !", LogLevel.INFO);
} }
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 !", LogLevel.INFO);
}
}
catch (Exception ex)
{
//Console.WriteLine(ex.Message);
Config.Logger.Error(ex);
}
} }
}
} }

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)
@@ -41,7 +41,8 @@ internal static class OnlineFunctions
// 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 => var relativeProgress = new Progress<long>(totalBytes =>
{ {
progress?.Report((float)totalBytes / contentLength.Value * 100); progress?.Report((float)totalBytes / contentLength.Value *
100);
downloadedBytes?.Report(totalBytes); downloadedBytes?.Report(totalBytes);
} }
); );

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;
@@ -26,7 +24,8 @@ public class PluginsManager
/// 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>
/// The method to load all plugins /// The method to load all plugins
@@ -34,6 +33,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);
@@ -83,7 +83,8 @@ 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: listplugs\nReason: " + exception.Message, this,
LogLevel.ERROR);
} }
return null; return null;
@@ -97,14 +98,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 +124,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 +136,10 @@ 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: listplugs\nReason: " + exception.Message, this,
LogLevel.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())

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,58 @@
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.Add(action.ActionName, action);
}
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", "InternalActionManager", LogLevel.WARNING, true);
return "Action not found";
}
try
{
await Actions[actionName].Execute(args);
return "Action executed";
}
catch (Exception e)
{
Config.Logger.Log(e.Message, "InternalActionManager", LogLevel.ERROR);
return e.Message;
}
}
}

View File

@@ -2,27 +2,63 @@
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
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>
@@ -72,7 +108,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 +132,8 @@ 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($"Failed to extract {entry.Name}. Exception: {ex.Message}",
"Archive Manager", LogLevel.ERROR);
} }
currentZIPFile++; currentZIPFile++;
@@ -127,7 +165,8 @@ 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($"Failed to extract {entry.Name}. Exception: {ex.Message}",
"Archive Manager", LogLevel.ERROR);
} }
await Task.Delay(10); await Task.Delay(10);
@@ -137,5 +176,4 @@ namespace PluginManager.Others
} }
} }
} }
}
} }

View File

@@ -1,21 +1,11 @@
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PluginManager.Others namespace PluginManager.Others;
public class DBCommandExecutingArguments
{ {
public class DBCommandExecutingArguments public DBCommandExecutingArguments(
{ SocketCommandContext context, string cleanContent, string commandUsed, string[]? arguments)
public SocketCommandContext context { get; init; }
public string cleanContent { get; init; }
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 +13,8 @@ namespace PluginManager.Others
this.arguments = arguments; this.arguments = arguments;
} }
} public SocketCommandContext context { get; init; }
public string cleanContent { get; init; }
public string commandUsed { get; init; }
public string[]? arguments { get; init; }
} }

View File

@@ -16,7 +16,6 @@ public enum OperatingSystem
/// </summary> /// </summary>
public enum LogLevel public enum LogLevel
{ {
NONE,
INFO, INFO,
WARNING, WARNING,
ERROR, ERROR,
@@ -35,3 +34,14 @@ public enum SaveType
BACKUP BACKUP
} }
public enum InternalActionRunType
{
ON_STARTUP,
ON_CALL
}
internal enum ExceptionExitCode : int
{
CONFIG_FAILED_TO_LOAD = 1,
CONFIG_KEY_NOT_FOUND = 2,
}

View File

@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using PluginManager.Interfaces.Exceptions;
namespace PluginManager.Others.Exceptions;
public class ConfigFailedToLoad : IException
{
public List<string>? Messages { get; set; }
public bool isFatal { get; private set; }
public string? File { get; }
public ConfigFailedToLoad(string message, bool isFatal, string file)
{
this.isFatal = isFatal;
Messages = new List<string>() {message};
this.File = file;
}
public ConfigFailedToLoad(string message, bool isFatal)
{
this.isFatal = isFatal;
Messages = new List<string>() {message};
this.File = null;
}
public ConfigFailedToLoad(string message)
{
this.isFatal = false;
Messages = new List<string>() {message};
this.File = null;
}
public string GenerateFullMessage()
{
string messages = "";
foreach (var message in Messages)
{
messages += message + "\n";
}
return $"\nMessage: {messages}\nIsFatal: {isFatal}\nFile: {File ?? "null"}";
}
public void HandleException()
{
if (isFatal)
{
Config.Logger.Log(GenerateFullMessage(), LogLevel.CRITICAL, true);
Environment.Exit((int)ExceptionExitCode.CONFIG_FAILED_TO_LOAD);
}
Config.Logger.Log(GenerateFullMessage(), LogLevel.WARNING);
}
public IException AppendError(string message)
{
Messages.Add(message);
return this;
}
public IException AppendError(List<string> messages)
{
Messages.AddRange(messages);
return this;
}
public IException IsFatal(bool isFatal = true)
{
this.isFatal = isFatal;
return this;
}
public static ConfigFailedToLoad CreateError(string message, bool isFatal, string? file = null)
{
if (file is not null)
return new ConfigFailedToLoad(message, isFatal, file);
return new ConfigFailedToLoad(message, isFatal);
}
public static ConfigFailedToLoad CreateError(string message)
{
return new ConfigFailedToLoad(message);
}
}

View File

@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using PluginManager.Interfaces.Exceptions;
namespace PluginManager.Others.Exceptions;
public class ConfigNoKeyWasPresent: IException
{
public List<string> Messages { get; set; }
public bool isFatal { get; private set; }
public ConfigNoKeyWasPresent(string message, bool isFatal)
{
this.Messages = new List<string>() { message };
this.isFatal = isFatal;
}
public ConfigNoKeyWasPresent(string message)
{
this.Messages = new List<string>() { message };
this.isFatal = false;
}
public string GenerateFullMessage()
{
string messages = "";
foreach (var message in Messages)
{
messages += message + "\n";
}
return $"\nMessage: {messages}\nIsFatal: {isFatal}";
}
public void HandleException()
{
if (isFatal)
{
Config.Logger.Log(GenerateFullMessage(), LogLevel.CRITICAL, true);
Environment.Exit((int)ExceptionExitCode.CONFIG_KEY_NOT_FOUND);
}
Config.Logger.Log(GenerateFullMessage(), LogLevel.WARNING);
}
public IException AppendError(string message)
{
Messages.Add(message);
return this;
}
public IException AppendError(List<string> messages)
{
Messages.AddRange(messages);
return this;
}
public IException IsFatal(bool isFatal = true)
{
this.isFatal = isFatal;
return this;
}
public static ConfigNoKeyWasPresent CreateError(string message)
{
return new ConfigNoKeyWasPresent(message);
}
public static ConfigNoKeyWasPresent CreateError(string message, bool isFatal)
{
return new ConfigNoKeyWasPresent(message, isFatal);
}
}

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 +1,76 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace PluginManager.Others.Logger namespace PluginManager.Others.Logger;
{
public class DBLogger
{
private List<LogMessage> LogHistory = new List<LogMessage>(); public class DBLogger
private List<LogMessage> ErrorHistory = new List<LogMessage>(); {
public delegate void LogHandler(string message, LogLevel logType, bool isInternal = false);
private readonly string _errFolder;
private readonly string _logFolder;
private readonly List<LogMessage> ErrorHistory = new();
private readonly List<LogMessage> LogHistory = new();
public DBLogger()
{
_logFolder = Config.AppSettings["LogFolder"];
_errFolder = Config.AppSettings["ErrorFolder"];
}
public IReadOnlyList<LogMessage> Logs => LogHistory; public IReadOnlyList<LogMessage> Logs => LogHistory;
public IReadOnlyList<LogMessage> Errors => ErrorHistory; public IReadOnlyList<LogMessage> Errors => ErrorHistory;
public delegate void LogHandler(string message, LogLevel logType); public event LogHandler? LogEvent;
public event LogHandler LogEvent;
private string _logFolder; public void Log(string message, LogLevel type = LogLevel.INFO)
private string _errFolder;
public DBLogger()
{ {
_logFolder = Config.Data["LogFolder"]; Log(new LogMessage(message, type));
_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(string message, LogLevel type= LogLevel.INFO, bool isInternal = false)
{
Log(new LogMessage(message, type,"unknown", isInternal));
}
public void Log(string message, string sender = "unknown", LogLevel type = LogLevel.INFO, bool isInternal = false)
{
Log(new LogMessage(message, type,sender,isInternal));
}
public void Log(string message, string sender = "unknown", LogLevel type = LogLevel.INFO)
{
Log(new LogMessage(message, type, sender));
}
public void Error(Exception? e)
{
Log(e.Message, e.Source, LogLevel.ERROR);
}
public void Log(LogMessage message) public void Log(LogMessage message)
{ {
if(LogEvent is not null)
LogEvent?.Invoke(message.Message, message.Type); LogEvent?.Invoke(message.Message, message.Type);
if (message.Type != LogLevel.NONE) if (message.Type != LogLevel.ERROR && message.Type != LogLevel.CRITICAL)
LogHistory.Add(message); LogHistory.Add(message);
else else
ErrorHistory.Add(message); ErrorHistory.Add(message);
} }
public void Log(string message, object sender, LogLevel type = LogLevel.NONE) => Log(message, sender.GetType().Name, type); public void Log(string message, object sender, LogLevel type = LogLevel.INFO)
public async void SaveToFile()
{ {
await Functions.SaveToJsonFile(_logFolder + "/" + DateTime.Now.ToString("yyyy-MM-dd") + ".json", LogHistory); Log(message, sender.GetType().Name, type);
await Functions.SaveToJsonFile(_errFolder + "/" + DateTime.Now.ToString("yyyy-MM-dd") + ".json", ErrorHistory);
} }
public async Task SaveToFile(bool ErrorsOnly = true)
{
if(!ErrorsOnly)
await JsonManager.SaveToJsonFile(_logFolder + "/" + DateTime.Now.ToString("yyyy-MM-dd") + ".json",
LogHistory);
await JsonManager.SaveToJsonFile(_errFolder + "/" + DateTime.Now.ToString("yyyy-MM-dd") + ".json",
ErrorHistory);
} }
} }

View File

@@ -1,29 +1,34 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PluginManager.Others.Logger namespace PluginManager.Others.Logger;
public class LogMessage
{ {
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) public LogMessage(string message, LogLevel type)
{ {
Message = message; Message = message;
Type = type; Type = type;
Time = DateTime.Now.ToString("HH:mm:ss"); Time = DateTime.Now.ToString("HH:mm:ss");
isInternal = false;
} }
public LogMessage(string message, LogLevel type, string sender) : this(message, type) public LogMessage(string message, LogLevel type, string sender, bool isInternal) : this(message, type)
{ {
Sender = sender; Sender = sender;
this.isInternal = isInternal;
} }
public LogMessage(string message, LogLevel type, string sender) : this (message, type, sender, false)
{
}
public string Message { get; set; }
public LogLevel Type { get; set; }
public string Time { get; set; }
public string Sender { get; set; }
public bool isInternal { get; set; }
public override string ToString() public override string ToString()
{ {
return $"[{Time}] {Message}"; return $"[{Time}] {Message}";
@@ -43,5 +48,4 @@ namespace PluginManager.Others.Logger
{ {
return new LogMessage(tuple.message, tuple.type, tuple.sender); return new LogMessage(tuple.message, tuple.type, tuple.sender);
} }
}
} }

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,141 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using PluginManager.Others.Exceptions;
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())
{
ConfigFailedToLoad.CreateError("Failed to load config")
.AppendError("The file is empty or does not exist")
.IsFatal()
.HandleException();
}
}
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
{
ConfigFailedToLoad
.CreateError("Failed to load config")
.IsFatal()
.HandleException();
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];
ConfigNoKeyWasPresent.CreateError($"Key {(key is string ? key : typeof(TKey).Name)} was not present in {_file ?? "config"}")
.AppendError("Deleting the file may fix this issue")
.IsFatal()
.HandleException();
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

@@ -6,6 +6,8 @@ This project is based on:
- [.NET 6 (C#)](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) - [.NET 6 (C#)](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
- [Discord.Net](https://github.com/discord-net/Discord.Net) - [Discord.Net](https://github.com/discord-net/Discord.Net)
- Some plugins can be found [here](https://github.com/andreitdr/SethPlugins).
## Plugins ## Plugins
#### Requirements: #### Requirements:
@@ -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) {
// //
//} //}
} }
@@ -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: 5.08.2023

View File

@@ -7,6 +7,12 @@ 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
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -21,11 +27,21 @@ 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
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}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3FB3C5DE-ED21-4D2E-ABDD-3A00EE4A2FFF} SolutionGuid = {3FB3C5DE-ED21-4D2E-ABDD-3A00EE4A2FFF}