Compare commits
320 Commits
v1.0.0.14
...
licenta_20
| Author | SHA1 | Date | |
|---|---|---|---|
| 89385a8c89 | |||
| f68b6cb877 | |||
| e9b61cc4be | |||
| 062b0d0133 | |||
| c3dd68576d | |||
| fafa493db7 | |||
| 15b6d224de | |||
| 81905460da | |||
| 11b9515bab | |||
| fcd3a59d54 | |||
| 7de5d88aea | |||
| dc40f4ebe4 | |||
| 8ba9448beb | |||
| f70e8a565b | |||
| 14ca51b18a | |||
| a0177ce145 | |||
| e57e941c94 | |||
| 5bfeb47fc8 | |||
| 9fce6dcf9d | |||
| b605321086 | |||
| 3a714808e2 | |||
| 5061a92412 | |||
| 296dbf5309 | |||
| e6976a5a74 | |||
| 3e4709148f | |||
| 3a7bd53cfc | |||
| 2bd368dcce | |||
| 0c33422b5c | |||
| 16b6e42a97 | |||
| 7106a928d6 | |||
| 4ea7b25e4d | |||
| 2cb868d747 | |||
| f10d72d704 | |||
| 0493dfaeb4 | |||
| 2d319f3d34 | |||
| c548c6191d | |||
| f19aafcec6 | |||
| 2e6b6b9a61 | |||
| f5d48a398d | |||
| c8cf887a09 | |||
| 4ab8438a7c | |||
| 87c889266b | |||
| 8aaefac706 | |||
| a4afb28f36 | |||
| 62ba5ec63d | |||
| a6ed4078ca | |||
| 20165af15a | |||
| d26c84480a | |||
| 84b19e2069 | |||
| 8b2169dc7b | |||
| 7ebe3e7014 | |||
| 49fe637455 | |||
| a754b0e5a9 | |||
| c79c792c43 | |||
| 424bf2196f | |||
| a12aa66660 | |||
| dee4793176 | |||
| 54be74b1cb | |||
| 9102cfaa47 | |||
| f2a9982d41 | |||
| bd3f79430b | |||
| 44d8b4684e | |||
| 9e8bfbbe16 | |||
| f8df0f0254 | |||
| 5b1d511f77 | |||
| cfcfecd4bc | |||
| c2dc01cbbb | |||
| e229362d38 | |||
| 8a2212e47f | |||
| 0630d2e291 | |||
| f108a1fe08 | |||
| 81eb966752 | |||
| c61a9d5e51 | |||
| 49403e70fd | |||
| be75ef03cb | |||
| aff40dfd63 | |||
| a584423939 | |||
| d51526bf22 | |||
| 30e92b742c | |||
| f7ba5f94ff | |||
| 34a54cd78f | |||
| 046c9bf98b | |||
| a23da51c08 | |||
| 1c002edc6d | |||
| 0a64de2439 | |||
| c080074292 | |||
| 95e8d95c92 | |||
| 9c98d2e219 | |||
| 18a059af0e | |||
| 27e25a9166 | |||
| 721c28c283 | |||
| 8366de28cc | |||
| 8c338820c5 | |||
| 08c5febd66 | |||
| 4a08a25167 | |||
| 66fbaf3e26 | |||
| 894d09f6fa | |||
| 8ace51c840 | |||
| 1fd065f4c2 | |||
| 1a4b654036 | |||
| 457b2b7364 | |||
| 3f2c98cb11 | |||
| 224784031c | |||
| 13900bb3f3 | |||
| 3f8590b8f3 | |||
| 6599428043 | |||
| 349c669284 | |||
| 2052eb634a | |||
| 48a133d58c | |||
| e0eae076f1 | |||
| 79c6daa11a | |||
| d186efcdaf | |||
| 8483439555 | |||
| 9aeb406f6f | |||
| 16147b6bd7 | |||
| 9b563cc0f6 | |||
| fa7e7988d5 | |||
| 68886fa5f0 | |||
| 86b951f50f | |||
| 1881102fb7 | |||
| e5e156f371 | |||
| d9d5c05313 | |||
| 9a8ddb5388 | |||
| 1a5f0cbede | |||
| 23961a48b0 | |||
| de7c65c27b | |||
| bc20101795 | |||
| cdd426b03c | |||
| 83115d72a4 | |||
| d3dd29f4bf | |||
| 5345515512 | |||
| c87d1a89c5 | |||
| 4c8fd1a672 | |||
| a5e65a5ea5 | |||
| d6398cd1cc | |||
| 5731165d29 | |||
| 8dbbfbfaef | |||
| 152e09f4af | |||
| 17147d920d | |||
| 413d465d7f | |||
| d5f78c831e | |||
| 7847c6cc8d | |||
| 82716a4f4f | |||
| 9476f9ec31 | |||
| dc787ac130 | |||
| 9525394a6e | |||
| fc93255503 | |||
| cadf500400 | |||
| 780614e1e7 | |||
| f32920c564 | |||
| 123e8e90a1 | |||
| 0323c888b3 | |||
| 1bb6d3b731 | |||
| 4dc5819c4e | |||
| 5d4fa6fba7 | |||
| b6675af9cb | |||
| 23a914d2c9 | |||
| e8822deeac | |||
| 29ecdb6883 | |||
| 90aa5875b5 | |||
| 0fccf706a1 | |||
| fd9cd49844 | |||
| 3c3c6a1301 | |||
| a2179787b9 | |||
| 8c06df9110 | |||
| ef7a2c0896 | |||
| 14f280baef | |||
| 196fb6d3d1 | |||
| cc355d7d4f | |||
| af90ae5fba | |||
| c8480b3c83 | |||
| fe32ebc4d7 | |||
| 2280957ea9 | |||
| 944d59d9a3 | |||
| 79ecff971b | |||
| 1f0e6516fd | |||
| 692f3d8f8c | |||
| 6d41d51694 | |||
| 5f23bdadcf | |||
| d3555b6fca | |||
| b5cdc0afeb | |||
| 3858156393 | |||
| 6279c5c3a9 | |||
| f58a57c6cd | |||
| d00ebfd7ed | |||
| ab279bd284 | |||
| 89c4932cd7 | |||
| c577f625c2 | |||
| 58624f4037 | |||
| c9249dc71b | |||
| 5e4f1ca35f | |||
| 0d8fdb5904 | |||
| 92a18e3495 | |||
| e929646e8e | |||
| 6315d13d18 | |||
| ee527bb36f | |||
| 86514d1770 | |||
| 9e8ed1e911 | |||
| f3c3c7939c | |||
| 361ed37362 | |||
| ed3128b940 | |||
| 6bbd68a135 | |||
| 06d322b5b3 | |||
| 5497ee9119 | |||
| 0aa78e3560 | |||
| 41ad37b3bb | |||
| 0104d09509 | |||
| b4f5e40f12 | |||
| 7107b17f19 | |||
| 42e1fd917e | |||
| 9e6fcdbe6f | |||
| a24d8aa222 | |||
| 0e5c9ff14b | |||
| f8977d8840 | |||
| bb9768f3a1 | |||
| b3d6930142 | |||
| 5254be44be | |||
| 207e0d6abd | |||
| 968d23380f | |||
| fff9e2e897 | |||
| 3e4e777d8d | |||
| 7f906d400f | |||
| c1161a3bca | |||
| ac512e3a27 | |||
| 730b628fe3 | |||
| 701edc5c6a | |||
| e7688762b8 | |||
| a7a71bf49a | |||
| ac7212ca00 | |||
| 298e557260 | |||
| 7ba791f906 | |||
| 4a6a12baae | |||
| f1dda5da3c | |||
| 3ab96e2d0d | |||
| 970c519a32 | |||
| 188920ec7f | |||
| dcfc4ea32f | |||
| a8c02176d3 | |||
| 1665d47a25 | |||
| 0b2f1e6ab6 | |||
| bcd9245502 | |||
| 59da9b295b | |||
| 99d7d5e7e7 | |||
| e4c60f1606 | |||
| 77f1bef862 | |||
| f16c139362 | |||
| c94cdca6eb | |||
| dcdf80112d | |||
| eb836c5b74 | |||
| de680c6771 | |||
|
|
bcef58a46b | ||
|
|
0dc8cdbce5 | ||
|
|
dbdbaa9802 | ||
|
|
5edcf93371 | ||
|
|
b0be76c62b | ||
| 75a77389a8 | |||
| 0bbced3d58 | |||
| 244209093e | |||
| 54a68d635d | |||
| d7a5cb5a64 | |||
| 6124f89cb0 | |||
| 810a527cc1 | |||
| 0a2dff0c6d | |||
| 382c376c03 | |||
| 84b7d663bc | |||
| 623232b67e | |||
| d5df6cfb9d | |||
| 10b9548c29 | |||
| fa1a136ef1 | |||
| d20cb62139 | |||
| f2418d0395 | |||
| 460a85944a | |||
| 7e2fa02d07 | |||
| 873855937f | |||
| 1cdd2644df | |||
| 532540b74f | |||
| 9ba4ca43e2 | |||
| 8bcaf3f254 | |||
| 0d5c90323a | |||
| 5b01b15216 | |||
| 4f18f505f4 | |||
| 2d3566a01a | |||
| 22f2cd4e59 | |||
| 1683234376 | |||
| 69d99b4189 | |||
| 4a5e0ef2f3 | |||
| 79731a9704 | |||
| bd53d099d1 | |||
| de61f5de88 | |||
| 0527d43dd2 | |||
| e3511cd96b | |||
| d355d3c9b7 | |||
| 5bb13aa4a6 | |||
| 655f5e2ce0 | |||
| 9014d78a7d | |||
| 1c026e7f49 | |||
| d32b3902c9 | |||
| e5f3aff39a | |||
| 11ec02ef68 | |||
| cad19935d5 | |||
| 47f88f167f | |||
| 9d6c335799 | |||
| cbaf552e7a | |||
| a4975a4578 | |||
| 725d02d152 | |||
| ae7118e89a | |||
| cad3bb5b75 | |||
| 269d7d56ff | |||
| 403c023191 | |||
| 3f7a8e04d4 | |||
| 0abbd24b86 | |||
| 21f1975fbc | |||
| d6f072904e | |||
| 6fc491a0d6 | |||
| 7bc9db03f0 | |||
| 641f0f2856 | |||
| ef5439d204 | |||
| c2093c2aca | |||
| a39f7bb0c9 | |||
| 6d73ec7f24 |
25
.dockerignore
Normal file
25
.dockerignore
Normal file
@@ -0,0 +1,25 @@
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/.idea
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
104
.gitignore
vendored
104
.gitignore
vendored
@@ -29,10 +29,8 @@ x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
[Dd]ata/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
@@ -64,6 +62,9 @@ project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# Tye
|
||||
.tye/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
@@ -363,8 +364,99 @@ MigrationBackup/
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
*.txt
|
||||
##
|
||||
## Visual studio for Mac
|
||||
##
|
||||
|
||||
#folders
|
||||
/Plugins/
|
||||
/DiscordBot.rar
|
||||
|
||||
# globs
|
||||
Makefile.in
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
config.make
|
||||
config.status
|
||||
aclocal.m4
|
||||
install-sh
|
||||
autom4te.cache/
|
||||
*.tar.gz
|
||||
tarballs/
|
||||
test-results/
|
||||
|
||||
# Mac bundle stuff
|
||||
*.dmg
|
||||
*.app
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
##
|
||||
## Visual Studio Code
|
||||
##
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
/DiscordBotWebUI/Data
|
||||
/DiscordBot/Data
|
||||
/WebUI/Data
|
||||
/WebUI_Old/Data
|
||||
/WebUI/bin
|
||||
/WebUI_Old/bin
|
||||
Data/
|
||||
@@ -1,4 +0,0 @@
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="4.0.0.0" />
|
||||
</dependentAssembly>
|
||||
@@ -1,95 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
|
||||
using PluginManager;
|
||||
using PluginManager.Interfaces;
|
||||
using PluginManager.Loaders;
|
||||
using PluginManager.Others;
|
||||
|
||||
namespace DiscordBot.Discord.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// The help command
|
||||
/// </summary>
|
||||
internal class Help : DBCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Command name
|
||||
/// </summary>
|
||||
public string Command => "help";
|
||||
|
||||
public List<string> Aliases => null;
|
||||
|
||||
/// <summary>
|
||||
/// Command Description
|
||||
/// </summary>
|
||||
public string Description => "This command allows you to check all loaded commands";
|
||||
|
||||
/// <summary>
|
||||
/// Command usage
|
||||
/// </summary>
|
||||
public string Usage => "help <command>";
|
||||
|
||||
/// <summary>
|
||||
/// Check if the command require administrator to be executed
|
||||
/// </summary>
|
||||
public bool requireAdmin => false;
|
||||
|
||||
/// <summary>
|
||||
/// The main body of the command
|
||||
/// </summary>
|
||||
/// <param name="context">The command context</param>
|
||||
public void ExecuteServer(SocketCommandContext context)
|
||||
{
|
||||
var args = Functions.GetArguments(context.Message);
|
||||
if (args.Count != 0)
|
||||
{
|
||||
foreach (var item in args)
|
||||
{
|
||||
var e = GenerateHelpCommand(item);
|
||||
if (e is null)
|
||||
context.Channel.SendMessageAsync("Unknown Command " + item);
|
||||
else
|
||||
context.Channel.SendMessageAsync(embed: e.Build());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var embedBuilder = new EmbedBuilder();
|
||||
|
||||
var adminCommands = "";
|
||||
var normalCommands = "";
|
||||
|
||||
foreach (var cmd in PluginLoader.Commands!)
|
||||
{
|
||||
if (cmd.requireAdmin)
|
||||
adminCommands += cmd.Command + " ";
|
||||
else
|
||||
normalCommands += cmd.Command + " ";
|
||||
}
|
||||
|
||||
embedBuilder.AddField("Admin Commands", adminCommands);
|
||||
embedBuilder.AddField("Normal Commands", normalCommands);
|
||||
context.Channel.SendMessageAsync(embed: embedBuilder.Build());
|
||||
}
|
||||
|
||||
private EmbedBuilder GenerateHelpCommand(string command)
|
||||
{
|
||||
var embedBuilder = new EmbedBuilder();
|
||||
var cmd = PluginLoader.Commands!.Find(p => p.Command == command || (p.Aliases is not null && p.Aliases.Contains(command)));
|
||||
if (cmd == null) return null;
|
||||
|
||||
embedBuilder.AddField("Usage", Config.GetValue<string>("prefix") + cmd.Usage);
|
||||
embedBuilder.AddField("Description", cmd.Description);
|
||||
if (cmd.Aliases is null)
|
||||
return embedBuilder;
|
||||
embedBuilder.AddField("Alias", cmd.Aliases.Count == 0 ? "-" : string.Join(", ", cmd.Aliases));
|
||||
|
||||
return embedBuilder;
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Discord.WebSocket;
|
||||
using PluginManager.Interfaces;
|
||||
using PluginManager.Others;
|
||||
using PluginManager.Others.Permissions;
|
||||
using DiscordLibCommands = Discord.Commands;
|
||||
using DiscordLib = Discord;
|
||||
using OperatingSystem = PluginManager.Others.OperatingSystem;
|
||||
|
||||
namespace DiscordBot.Discord.Commands;
|
||||
|
||||
internal class Restart : DBCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Command name
|
||||
/// </summary>
|
||||
public string Command => "restart";
|
||||
|
||||
public List<string> Aliases => null;
|
||||
|
||||
/// <summary>
|
||||
/// Command Description
|
||||
/// </summary>
|
||||
public string Description => "Restart the bot";
|
||||
|
||||
/// <summary>
|
||||
/// Command usage
|
||||
/// </summary>
|
||||
public string Usage => "restart [-p | -c | -args | -cmd] <args>";
|
||||
|
||||
/// <summary>
|
||||
/// Check if the command require administrator to be executed
|
||||
/// </summary>
|
||||
public bool requireAdmin => true;
|
||||
|
||||
/// <summary>
|
||||
/// The main body of the command
|
||||
/// </summary>
|
||||
/// <param name="context">The command context</param>
|
||||
public async void ExecuteServer(DiscordLibCommands.SocketCommandContext context)
|
||||
{
|
||||
var args = Functions.GetArguments(context.Message);
|
||||
var OS = Functions.GetOperatingSystem();
|
||||
if (args.Count == 0)
|
||||
{
|
||||
switch (OS)
|
||||
{
|
||||
case OperatingSystem.WINDOWS:
|
||||
Process.Start("./DiscordBot.exe");
|
||||
break;
|
||||
case OperatingSystem.LINUX:
|
||||
case OperatingSystem.MAC_OS:
|
||||
Process.Start("./DiscordBot");
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (args[0])
|
||||
{
|
||||
case "-p":
|
||||
case "-poweroff":
|
||||
case "-c":
|
||||
case "-close":
|
||||
Environment.Exit(0);
|
||||
break;
|
||||
case "-cmd":
|
||||
case "-args":
|
||||
var cmd = "--args";
|
||||
|
||||
if (args.Count > 1)
|
||||
for (var i = 1; i < args.Count; i++)
|
||||
cmd += $" {args[i]}";
|
||||
|
||||
|
||||
switch (OS)
|
||||
{
|
||||
case OperatingSystem.WINDOWS:
|
||||
Functions.WriteLogFile("Restarting the bot with the following arguments: \"" + cmd + "\"");
|
||||
Process.Start("./DiscordBot.exe", cmd);
|
||||
break;
|
||||
case OperatingSystem.LINUX:
|
||||
//case PluginManager.Others.OperatingSystem.MAC_OS: ?? - not tested
|
||||
Process.Start("./DiscordBot", cmd);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
Environment.Exit(0);
|
||||
break;
|
||||
default:
|
||||
await context.Channel.SendMessageAsync("Invalid argument. Use `help restart` to see the usage.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
|
||||
using PluginManager;
|
||||
using PluginManager.Interfaces;
|
||||
|
||||
namespace DiscordBot.Discord.Commands;
|
||||
|
||||
internal class Settings : DBCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Command name
|
||||
/// </summary>
|
||||
public string Command => "set";
|
||||
|
||||
public List<string> Aliases => null;
|
||||
|
||||
/// <summary>
|
||||
/// Command Description
|
||||
/// </summary>
|
||||
public string Description => "This command allows you change all settings. Use \"set help\" to show details";
|
||||
|
||||
/// <summary>
|
||||
/// Command usage
|
||||
/// </summary>
|
||||
public string Usage => "set [keyword] [new Value]";
|
||||
|
||||
/// <summary>
|
||||
/// Check if the command require administrator to be executed
|
||||
/// </summary>
|
||||
public bool requireAdmin => true;
|
||||
|
||||
/// <summary>
|
||||
/// The main body of the command
|
||||
/// </summary>
|
||||
/// <param name="context">The command context</param>
|
||||
public async void Execute(SocketCommandContext context)
|
||||
{
|
||||
var channel = context.Message.Channel;
|
||||
try
|
||||
{
|
||||
var content = context.Message.Content;
|
||||
var data = content.Split(' ');
|
||||
var keyword = data[1];
|
||||
if (keyword.ToLower() == "help")
|
||||
{
|
||||
await channel.SendMessageAsync("set token [new value] -- set the value of the new token (require restart)");
|
||||
await channel.SendMessageAsync("set prefix [new value] -- set the value of the new preifx (require restart)");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (keyword.ToLower())
|
||||
{
|
||||
case "token":
|
||||
if (data.Length != 3)
|
||||
{
|
||||
await channel.SendMessageAsync("Invalid token !");
|
||||
return;
|
||||
}
|
||||
|
||||
Config.SetValue("token", data[2]);
|
||||
break;
|
||||
case "prefix":
|
||||
if (data.Length != 3)
|
||||
{
|
||||
await channel.SendMessageAsync("Invalid token !");
|
||||
return;
|
||||
}
|
||||
|
||||
Config.SetValue("token", data[2]);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
await channel.SendMessageAsync("Restart required ...");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
await channel.SendMessageAsync("Unknown usage to this command !\nUsage: " + Usage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
|
||||
using PluginManager;
|
||||
|
||||
using static PluginManager.Others.Functions;
|
||||
|
||||
namespace DiscordBot.Discord.Core;
|
||||
|
||||
internal class Boot
|
||||
{
|
||||
/// <summary>
|
||||
/// The bot prefix
|
||||
/// </summary>
|
||||
public readonly string botPrefix;
|
||||
|
||||
/// <summary>
|
||||
/// The bot token
|
||||
/// </summary>
|
||||
public readonly string botToken;
|
||||
|
||||
/// <summary>
|
||||
/// The bot client
|
||||
/// </summary>
|
||||
public DiscordSocketClient client;
|
||||
|
||||
/// <summary>
|
||||
/// The bot command handler
|
||||
/// </summary>
|
||||
private CommandHandler commandServiceHandler;
|
||||
|
||||
/// <summary>
|
||||
/// The command service
|
||||
/// </summary>
|
||||
private CommandService service;
|
||||
|
||||
/// <summary>
|
||||
/// The main Boot constructor
|
||||
/// </summary>
|
||||
/// <param name="botToken">The bot token</param>
|
||||
/// <param name="botPrefix">The bot prefix</param>
|
||||
public Boot(string botToken, string botPrefix)
|
||||
{
|
||||
this.botPrefix = botPrefix;
|
||||
this.botToken = botToken;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the bot is ready
|
||||
/// </summary>
|
||||
/// <value> true if the bot is ready, othwerwise false </value>
|
||||
public bool isReady { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The start method for the bot. This method is used to load the bot
|
||||
/// </summary>
|
||||
/// <returns>Task</returns>
|
||||
public async Task Awake()
|
||||
{
|
||||
DiscordSocketConfig config = new DiscordSocketConfig { AlwaysDownloadUsers = true };
|
||||
|
||||
client = new DiscordSocketClient(config);
|
||||
service = new CommandService();
|
||||
|
||||
CommonTasks();
|
||||
|
||||
await client.LoginAsync(TokenType.Bot, botToken);
|
||||
await client.StartAsync();
|
||||
|
||||
commandServiceHandler = new CommandHandler(client, service, botPrefix);
|
||||
await commandServiceHandler.InstallCommandsAsync();
|
||||
|
||||
await Task.Delay(2000);
|
||||
while (!isReady) ;
|
||||
}
|
||||
|
||||
private void CommonTasks()
|
||||
{
|
||||
if (client == null) return;
|
||||
client.LoggedOut += Client_LoggedOut;
|
||||
client.Log += Log;
|
||||
client.LoggedIn += LoggedIn;
|
||||
client.Ready += Ready;
|
||||
}
|
||||
|
||||
private Task Client_LoggedOut()
|
||||
{
|
||||
WriteLogFile("Successfully Logged Out");
|
||||
Log(new LogMessage(LogSeverity.Info, "Boot", "Successfully logged out from discord !"));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Ready()
|
||||
{
|
||||
Console.Title = "ONLINE";
|
||||
isReady = true;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task LoggedIn()
|
||||
{
|
||||
Console.Title = "CONNECTED";
|
||||
WriteLogFile("The bot has been logged in at " + DateTime.Now.ToShortDateString() + " (" +
|
||||
DateTime.Now.ToShortTimeString() + ")"
|
||||
);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Log(LogMessage message)
|
||||
{
|
||||
switch (message.Severity)
|
||||
{
|
||||
case LogSeverity.Error:
|
||||
case LogSeverity.Critical:
|
||||
WriteErrFile(message.Message);
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("[ERROR] " + message.Message);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
|
||||
break;
|
||||
|
||||
case LogSeverity.Info:
|
||||
case LogSeverity.Debug:
|
||||
WriteLogFile(message.Message);
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("[INFO] " + message.Message);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
|
||||
using PluginManager.Loaders;
|
||||
using PluginManager.Others;
|
||||
using PluginManager.Others.Permissions;
|
||||
|
||||
namespace DiscordBot.Discord.Core;
|
||||
|
||||
internal class CommandHandler
|
||||
{
|
||||
private readonly string botPrefix;
|
||||
private readonly DiscordSocketClient client;
|
||||
private readonly CommandService commandService;
|
||||
|
||||
/// <summary>
|
||||
/// Command handler constructor
|
||||
/// </summary>
|
||||
/// <param name="client">The discord bot client</param>
|
||||
/// <param name="commandService">The discord bot command service</param>
|
||||
/// <param name="botPrefix">The prefix to watch for</param>
|
||||
public CommandHandler(DiscordSocketClient client, CommandService commandService, string botPrefix)
|
||||
{
|
||||
this.client = client;
|
||||
this.commandService = commandService;
|
||||
this.botPrefix = botPrefix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method to initialize all commands
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task InstallCommandsAsync()
|
||||
{
|
||||
client.MessageReceived += MessageHandler;
|
||||
await commandService.AddModulesAsync(Assembly.GetEntryAssembly(), null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The message handler for the bot
|
||||
/// </summary>
|
||||
/// <param name="Message">The message got from the user in discord chat</param>
|
||||
/// <returns></returns>
|
||||
private async Task MessageHandler(SocketMessage Message)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Message as SocketUserMessage == null)
|
||||
return;
|
||||
|
||||
var message = Message as SocketUserMessage;
|
||||
|
||||
if (message == null)
|
||||
return;
|
||||
|
||||
if (!message.Content.StartsWith(botPrefix))
|
||||
return;
|
||||
|
||||
var argPos = 0;
|
||||
|
||||
if (message.HasMentionPrefix(client.CurrentUser, ref argPos))
|
||||
{
|
||||
await message.Channel.SendMessageAsync("Can not exec mentioned commands !");
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.Author.IsBot)
|
||||
return;
|
||||
|
||||
var context = new SocketCommandContext(client, message);
|
||||
|
||||
await commandService.ExecuteAsync(context, argPos, null);
|
||||
|
||||
var plugin = PluginLoader.Commands!.Where(p => p.Command == message.Content.Split(' ')[0].Substring(botPrefix.Length) || (p.Aliases is not null && p.Aliases.Contains(message.Content.Split(' ')[0].Substring(botPrefix.Length)))).FirstOrDefault();
|
||||
|
||||
if (plugin is null) throw new System.Exception("Failed to run command. !");
|
||||
|
||||
if (plugin.requireAdmin && !context.Message.Author.isAdmin())
|
||||
return;
|
||||
|
||||
if (context.Channel is SocketDMChannel)
|
||||
plugin.ExecuteDM(context);
|
||||
else plugin.ExecuteServer(context);
|
||||
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
ex.WriteErrFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>disable</Nullable>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<SignAssembly>False</SignAssembly>
|
||||
<IsPublishable>True</IsPublishable>
|
||||
<AssemblyVersion>1.0.0.13</AssemblyVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>none</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Data\**" />
|
||||
<Compile Remove="obj\**" />
|
||||
<Compile Remove="Output\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="Data\**" />
|
||||
<EmbeddedResource Remove="obj\**" />
|
||||
<EmbeddedResource Remove="Output\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Data\**" />
|
||||
<None Remove="obj\**" />
|
||||
<None Remove="Output\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net" Version="3.7.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PluginManager\PluginManager.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,428 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Discord;
|
||||
|
||||
using DiscordBot.Discord.Core;
|
||||
using PluginManager;
|
||||
using PluginManager.Items;
|
||||
using PluginManager.Online;
|
||||
using PluginManager.Others;
|
||||
|
||||
namespace DiscordBot;
|
||||
|
||||
public class Program
|
||||
{
|
||||
private static bool loadPluginsOnStartup;
|
||||
private static bool listPluginsAtStartup;
|
||||
private static ConsoleCommandsHandler consoleCommandsHandler;
|
||||
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
[Obsolete]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Loading resources ...");
|
||||
PreLoadComponents().Wait();
|
||||
do
|
||||
{
|
||||
if (!Config.ContainsKey("ServerID"))
|
||||
{
|
||||
|
||||
|
||||
Console.WriteLine("Please enter the server ID: ");
|
||||
Console_Utilities.WriteColorText("You can find it in the Server Settings at &r\"Widget\"&c section");
|
||||
Console.WriteLine("Example: 1234567890123456789");
|
||||
|
||||
Console.WriteLine("This is not required, but is recommended. If you refuse to provide the ID, just press enter.\nThe server id is required to make easier for the bot to interact with the server.\nRemember: this bot is for one server ONLY.");
|
||||
Console.Write("User Input > ");
|
||||
ConsoleKeyInfo key = Console.ReadKey();
|
||||
if (key.Key == ConsoleKey.Enter)
|
||||
Config.AddValueToVariables("ServerID", "null", false);
|
||||
else
|
||||
{
|
||||
string SID = key.KeyChar + Console.ReadLine();
|
||||
if (SID.Length != 18)
|
||||
{
|
||||
Console.Clear();
|
||||
Console_Utilities.WriteColorText("&rYour server ID is not 18 characters long. Please try again. \n");
|
||||
|
||||
continue;
|
||||
}
|
||||
Config.AddValueToVariables("ServerID", SID, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Config.ContainsKey("token") || Config.GetValue<string>("token") == null || (Config.GetValue<string>("token")?.Length != 70 && Config.GetValue<string>("token")?.Length != 59))
|
||||
{
|
||||
Console.WriteLine("Please insert your token");
|
||||
Console.Write("Token = ");
|
||||
var token = Console.ReadLine();
|
||||
if (token?.Length == 59 || token?.Length == 70)
|
||||
Config.AddValueToVariables("token", token, true);
|
||||
else
|
||||
{
|
||||
Console.Clear();
|
||||
Console_Utilities.WriteColorText("&rThe token length is invalid !");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Config.ContainsKey("prefix") || Config.GetValue<string>("prefix") == null || Config.GetValue<string>("prefix")?.Length != 1)
|
||||
{
|
||||
Console.WriteLine("Please insert your prefix (max. 1 character long):");
|
||||
Console.WriteLine("For a prefix longer then one character, the first character will be saved and the others will be ignored.\n No spaces, numbers, '/' or '\\' allowed");
|
||||
Console.Write("Prefix = ");
|
||||
var prefix = Console.ReadLine()![0];
|
||||
|
||||
if (prefix == ' ' || char.IsDigit(prefix) || prefix == '/' || prefix == '\\')
|
||||
{
|
||||
Console.Clear();
|
||||
Console_Utilities.WriteColorText("&rThe prefix is invalid");
|
||||
continue;
|
||||
}
|
||||
Config.AddValueToVariables("prefix", prefix.ToString(), false);
|
||||
}
|
||||
|
||||
break;
|
||||
} while (true);
|
||||
|
||||
HandleInput(args).Wait();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The main loop for the discord bot
|
||||
/// </summary>
|
||||
/// <param name="discordbooter">The discord booter used to start the application</param>
|
||||
private static void NoGUI(Boot discordbooter)
|
||||
{
|
||||
|
||||
#if DEBUG
|
||||
Console.WriteLine();
|
||||
ConsoleCommandsHandler.ExecuteCommad("lp").Wait();
|
||||
#else
|
||||
if (loadPluginsOnStartup) consoleCommandsHandler.HandleCommand("lp");
|
||||
if (listPluginsAtStartup) consoleCommandsHandler.HandleCommand("listplugs");
|
||||
#endif
|
||||
Config.SaveConfig(SaveType.NORMAL).Wait();
|
||||
|
||||
while (true)
|
||||
{
|
||||
|
||||
var cmd = Console.ReadLine();
|
||||
if (!consoleCommandsHandler.HandleCommand(cmd!
|
||||
#if DEBUG
|
||||
, false
|
||||
#endif
|
||||
|
||||
) && cmd.Length > 0)
|
||||
Console.WriteLine("Failed to run command " + cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the bot without user interface
|
||||
/// </summary>
|
||||
/// <returns>Returns the boot loader for the Discord Bot</returns>
|
||||
private static async Task<Boot> StartNoGUI()
|
||||
{
|
||||
Console.Clear();
|
||||
Console.ForegroundColor = ConsoleColor.DarkYellow;
|
||||
|
||||
List<string> startupMessageList = await ServerCom.ReadTextFromURL("https://raw.githubusercontent.com/Wizzy69/installer/discord-bot-files/StartupMessage");
|
||||
|
||||
foreach (var message in startupMessageList)
|
||||
Console.WriteLine(message);
|
||||
|
||||
Console.WriteLine($"Running on version: {Config.GetValue<string>("Version") ?? System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()}");
|
||||
Console.WriteLine($"Git URL: {Config.GetValue<string>("GitURL") ?? " Could not find Git URL"}");
|
||||
|
||||
Console_Utilities.WriteColorText("&rRemember to close the bot using the ShutDown command (&ysd&r) or some settings won't be saved\n");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
|
||||
if (Config.ContainsKey("LaunchMessage"))
|
||||
{
|
||||
Console_Utilities.WriteColorText(Config.GetValue<string>("LaunchMessage"));
|
||||
Config.RemoveKey("LaunchMessage");
|
||||
}
|
||||
|
||||
Console_Utilities.WriteColorText("Please note that the bot saves a backup save file every time you are using the shudown command (&ysd&c)");
|
||||
Console.WriteLine($"============================ LOG ============================");
|
||||
|
||||
try
|
||||
{
|
||||
var token = Config.GetValue<string>("token");
|
||||
#if DEBUG
|
||||
Console.WriteLine("Starting in DEBUG MODE");
|
||||
if (!Directory.Exists("./Data/BetaTest"))
|
||||
Console.WriteLine("Failed to start in debug mode because the folder ./Data/BetaTest does not exist");
|
||||
else
|
||||
{
|
||||
token = File.ReadAllText("./Data/BetaTest/token.txt");
|
||||
|
||||
//Debug mode code...
|
||||
}
|
||||
#endif
|
||||
|
||||
var prefix = Config.GetValue<string>("prefix");
|
||||
var discordbooter = new Boot(token, prefix);
|
||||
await discordbooter.Awake();
|
||||
return discordbooter;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear folder
|
||||
/// </summary>
|
||||
/// <param name="d">Directory path</param>
|
||||
private static Task ClearFolder(string d)
|
||||
{
|
||||
var files = Directory.GetFiles(d);
|
||||
var fileNumb = files.Length;
|
||||
for (var i = 0; i < fileNumb; i++)
|
||||
{
|
||||
File.Delete(files[i]);
|
||||
Console.WriteLine("Deleting : " + files[i]);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle user input arguments from the startup of the application
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments</param>
|
||||
private static async Task HandleInput(string[] args)
|
||||
{
|
||||
|
||||
var len = args.Length;
|
||||
|
||||
if (len == 3 && args[0] == "/download")
|
||||
{
|
||||
var url = args[1];
|
||||
var location = args[2];
|
||||
|
||||
await ServerCom.DownloadFileAsync(url, location);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (len > 0 && (args.Contains("--cmd") || args.Contains("--args") || args.Contains("--nomessage")))
|
||||
{
|
||||
if (args.Contains("lp") || args.Contains("loadplugins"))
|
||||
loadPluginsOnStartup = true;
|
||||
if (args.Contains("listplugs"))
|
||||
listPluginsAtStartup = true;
|
||||
|
||||
len = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var b = await StartNoGUI();
|
||||
consoleCommandsHandler = new ConsoleCommandsHandler(b.client);
|
||||
|
||||
if (len > 0 && args[0] == "/remplug")
|
||||
{
|
||||
|
||||
string plugName = Functions.MergeStrings(args, 1);
|
||||
Console.WriteLine("Starting to remove " + plugName);
|
||||
await ConsoleCommandsHandler.ExecuteCommad("remplug " + plugName);
|
||||
loadPluginsOnStartup = true;
|
||||
len = 0;
|
||||
}
|
||||
|
||||
if (len > 0 && args[0] == "/updateplug")
|
||||
{
|
||||
string plugName = args.MergeStrings(1);
|
||||
Console.WriteLine("Updating " + plugName);
|
||||
await ConsoleCommandsHandler.ExecuteCommad("dwplug" + plugName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len == 0 || (args[0] != "--exec" && args[0] != "--execute"))
|
||||
{
|
||||
|
||||
Thread mainThread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
NoGUI(b);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
if (ex.Message == "No process is on the other end of the pipe." || (uint)ex.HResult == 0x800700E9)
|
||||
{
|
||||
if (!Config.ContainsKey("LaunchMessage"))
|
||||
Config.AddValueToVariables("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 !", false);
|
||||
Functions.WriteErrFile(ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
mainThread.Start();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.DarkYellow;
|
||||
Console.WriteLine("Execute command interface noGUI\n\n");
|
||||
Console.WriteLine(
|
||||
"\tCommand name\t\t\t\tDescription\n" +
|
||||
"-- help | -help\t\t ------ \tDisplay the help message\n" +
|
||||
"--reset-full\t\t ------ \tReset all files (clear files)\n" +
|
||||
"--reset-logs\t\t ------ \tClear up the output folder\n" +
|
||||
"--start\t\t ------ \tStart the bot\n" +
|
||||
"exit\t\t\t ------ \tClose the application"
|
||||
);
|
||||
while (true)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.Write("> ");
|
||||
var message = Console.ReadLine().Split(' ');
|
||||
|
||||
switch (message[0])
|
||||
{
|
||||
case "--help":
|
||||
case "-help":
|
||||
Console.ForegroundColor = ConsoleColor.DarkYellow;
|
||||
Console.WriteLine("\tCommand name\t\t\t\tDescription\n" + "-- help | -help\t\t ------ \tDisplay the help message\n" + "--reset-full\t\t ------ \tReset all files (clear files)\n" + "--reset-settings\t ------ \tReset only bot settings\n" + "--reset-logs\t\t ------ \tClear up the output folder\n" + "--start\t\t ------ \tStart the bot\n" + "exit\t\t\t ------ \tClose the application");
|
||||
break;
|
||||
case "--reset-full":
|
||||
await ClearFolder("./Data/Resources/");
|
||||
await ClearFolder("./Output/Logs/");
|
||||
await ClearFolder("./Output/Errors");
|
||||
await ClearFolder("./Data/Languages/");
|
||||
await ClearFolder("./Data/Plugins/Commands");
|
||||
await ClearFolder("./Data/Plugins/Events");
|
||||
Console.WriteLine("Successfully cleared all folders");
|
||||
break;
|
||||
case "--reset-logs":
|
||||
await ClearFolder("./Output/Logs");
|
||||
await ClearFolder("./Output/Errors");
|
||||
Console.WriteLine("Successfully clear logs folder");
|
||||
break;
|
||||
case "--exit":
|
||||
case "exit":
|
||||
Environment.Exit(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.WriteLine("Failed to execute command " + message[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task PreLoadComponents()
|
||||
{
|
||||
Console_Utilities.ProgressBar main = new Console_Utilities.ProgressBar(ProgressBarType.NO_END);
|
||||
main.Start();
|
||||
Directory.CreateDirectory("./Data/Resources");
|
||||
Directory.CreateDirectory("./Data/Plugins/Commands");
|
||||
Directory.CreateDirectory("./Data/Plugins/Events");
|
||||
Directory.CreateDirectory("./Data/PAKS");
|
||||
await Config.LoadConfig();
|
||||
if (Config.ContainsKey("DeleteLogsAtStartup"))
|
||||
if (Config.GetValue<bool>("DeleteLogsAtStartup"))
|
||||
foreach (var file in Directory.GetFiles("./Output/Logs/"))
|
||||
File.Delete(file);
|
||||
List<string> OnlineDefaultKeys = await ServerCom.ReadTextFromURL("https://raw.githubusercontent.com/Wizzy69/installer/discord-bot-files/SetupKeys");
|
||||
|
||||
Config.PluginConfig.Load();
|
||||
|
||||
if (!Config.ContainsKey("Version"))
|
||||
Config.AddValueToVariables("Version", Assembly.GetExecutingAssembly().GetName().Version.ToString(), false);
|
||||
else
|
||||
Config.SetValue("Version", Assembly.GetExecutingAssembly().GetName().Version.ToString());
|
||||
|
||||
foreach (var key in OnlineDefaultKeys)
|
||||
{
|
||||
if (key.Length <= 3 || !key.Contains(' ')) continue;
|
||||
string[] s = key.Split(' ');
|
||||
try
|
||||
{
|
||||
if (Config.ContainsKey(s[0])) Config.SetValue(s[0], s[1]);
|
||||
else Config.GetAndAddValueToVariable(s[0], s[1], s[2].Equals("true", StringComparison.CurrentCultureIgnoreCase));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Functions.WriteErrFile(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
List<string> onlineSettingsList = await ServerCom.ReadTextFromURL("https://raw.githubusercontent.com/Wizzy69/installer/discord-bot-files/OnlineData");
|
||||
main.Stop();
|
||||
foreach (var key in onlineSettingsList)
|
||||
{
|
||||
if (key.Length <= 3 || !key.Contains(' ')) continue;
|
||||
|
||||
string[] s = key.Split(' ');
|
||||
switch (s[0])
|
||||
{
|
||||
case "CurrentVersion":
|
||||
string newVersion = s[1];
|
||||
if (!newVersion.Equals(Config.GetValue<string>("Version")))
|
||||
{
|
||||
if (Functions.GetOperatingSystem() == PluginManager.Others.OperatingSystem.WINDOWS)
|
||||
{
|
||||
|
||||
string url = $"https://github.com/Wizzy69/SethDiscordBot/releases/download/v{newVersion}/net6.0.zip";
|
||||
//string url2 = $"https://github.com/Wizzy69/SethDiscordBot/releases/download/v{newVersion}-preview/net6.0.zip";
|
||||
|
||||
Process.Start(".\\Updater\\Updater.exe", $"{newVersion} {url} {Process.GetCurrentProcess().ProcessName}");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
string url = $"https://github.com/Wizzy69/SethDiscordBot/releases/download/v{newVersion}/net6.0_linux.zip";
|
||||
Process.Start("./Updater/Updater", $"/update {url} ./DiscordBot ./");
|
||||
}
|
||||
//Environment.Exit(0);
|
||||
}
|
||||
|
||||
break;
|
||||
case "UpdaterVersion":
|
||||
string updaternewversion = s[1];
|
||||
if (Config.UpdaterVersion != updaternewversion)
|
||||
{
|
||||
Console.Clear();
|
||||
Console.WriteLine("Installing updater ...\nDo NOT close the bot during update !");
|
||||
Console_Utilities.ProgressBar bar = new Console_Utilities.ProgressBar(ProgressBarType.NO_END);
|
||||
bar.Start();
|
||||
await ServerCom.DownloadFileNoProgressAsync("https://github.com/Wizzy69/installer/releases/download/release-1-discordbot/Updater.zip", "./Updater.zip");
|
||||
await Functions.ExtractArchive("./Updater.zip", "./", null, UnzipProgressType.PercentageFromTotalSize);
|
||||
Config.UpdaterVersion = updaternewversion;
|
||||
File.Delete("Updater.zip");
|
||||
await Config.SaveConfig(SaveType.NORMAL);
|
||||
bar.Stop();
|
||||
Console.Clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Console_Utilities.Initialize();
|
||||
await Config.SaveConfig(SaveType.NORMAL);
|
||||
Console.Clear();
|
||||
}
|
||||
}
|
||||
113
DiscordBotCore.Configuration/Configuration.cs
Normal file
113
DiscordBotCore.Configuration/Configuration.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using DiscordBotCore.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace DiscordBotCore.Configuration;
|
||||
|
||||
public class Configuration : ConfigurationBase
|
||||
{
|
||||
private readonly bool _EnableAutoAddOnGetWithDefault;
|
||||
private Configuration(ILogger logger, string diskLocation, bool enableAutoAddOnGetWithDefault): base(logger, diskLocation)
|
||||
{
|
||||
_EnableAutoAddOnGetWithDefault = enableAutoAddOnGetWithDefault;
|
||||
}
|
||||
|
||||
public override async Task SaveToFile()
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(_InternalDictionary, Formatting.Indented);
|
||||
await File.WriteAllTextAsync(_DiskLocation, json);
|
||||
}
|
||||
|
||||
public override T Get<T>(string key, T defaultValue)
|
||||
{
|
||||
T value = base.Get(key, defaultValue);
|
||||
|
||||
if (_EnableAutoAddOnGetWithDefault && value.Equals(defaultValue))
|
||||
{
|
||||
Add(key, defaultValue);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public override List<T> GetList<T>(string key, List<T> defaultValue)
|
||||
{
|
||||
List<T> value = base.GetList(key, defaultValue);
|
||||
|
||||
if (_EnableAutoAddOnGetWithDefault && value.All(defaultValue.Contains))
|
||||
{
|
||||
Add(key, defaultValue);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public override void LoadFromFile()
|
||||
{
|
||||
if (!File.Exists(_DiskLocation))
|
||||
{
|
||||
SaveToFile().Wait();
|
||||
return;
|
||||
}
|
||||
|
||||
string jsonContent = File.ReadAllText(_DiskLocation);
|
||||
var jObject = JsonConvert.DeserializeObject<JObject>(jsonContent);
|
||||
|
||||
if (jObject is null)
|
||||
{
|
||||
SaveToFile().Wait();
|
||||
return;
|
||||
}
|
||||
|
||||
_InternalDictionary.Clear();
|
||||
|
||||
foreach (var kvp in jObject)
|
||||
{
|
||||
AddPairToDictionary(kvp, _InternalDictionary);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPairToDictionary(KeyValuePair<string, JToken> kvp, IDictionary<string, object> dict)
|
||||
{
|
||||
if (kvp.Value is JObject nestedJObject)
|
||||
{
|
||||
dict[kvp.Key] = nestedJObject.ToObject<Dictionary<string, object>>();
|
||||
|
||||
foreach (var nestedKvp in nestedJObject)
|
||||
{
|
||||
AddPairToDictionary(nestedKvp, dict[kvp.Key] as Dictionary<string, object>);
|
||||
}
|
||||
}
|
||||
else if (kvp.Value is JArray nestedJArray)
|
||||
{
|
||||
dict[kvp.Key] = nestedJArray.ToObject<List<object>>();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (kvp.Value.Type == JTokenType.Integer)
|
||||
dict[kvp.Key] = kvp.Value.Value<int>();
|
||||
else if (kvp.Value.Type == JTokenType.Float)
|
||||
dict[kvp.Key] = kvp.Value.Value<float>();
|
||||
else if (kvp.Value.Type == JTokenType.Boolean)
|
||||
dict[kvp.Key] = kvp.Value.Value<bool>();
|
||||
else if (kvp.Value.Type == JTokenType.String)
|
||||
dict[kvp.Key] = kvp.Value.Value<string>();
|
||||
else if (kvp.Value.Type == JTokenType.Date)
|
||||
dict[kvp.Key] = kvp.Value.Value<DateTime>();
|
||||
else
|
||||
dict[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new Settings Dictionary from a file
|
||||
/// </summary>
|
||||
/// <param name="baseFile">The file location</param>
|
||||
/// <param name="enableAutoAddOnGetWithDefault">Set this to true if you want to update the dictionary with default values on get</param>
|
||||
public static Configuration CreateFromFile(ILogger logger, string baseFile, bool enableAutoAddOnGetWithDefault)
|
||||
{
|
||||
var settings = new Configuration(logger, baseFile, enableAutoAddOnGetWithDefault);
|
||||
settings.LoadFromFile();
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
167
DiscordBotCore.Configuration/ConfigurationBase.cs
Normal file
167
DiscordBotCore.Configuration/ConfigurationBase.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using System.Collections;
|
||||
using System.Net.Mime;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace DiscordBotCore.Configuration;
|
||||
|
||||
public abstract class ConfigurationBase : IConfiguration
|
||||
{
|
||||
protected readonly IDictionary<string, object> _InternalDictionary = new Dictionary<string, object>();
|
||||
protected readonly string _DiskLocation;
|
||||
protected readonly ILogger _Logger;
|
||||
|
||||
protected ConfigurationBase(ILogger logger, string diskLocation)
|
||||
{
|
||||
this._DiskLocation = diskLocation;
|
||||
this._Logger = logger;
|
||||
}
|
||||
|
||||
public virtual void Add(string key, object? value)
|
||||
{
|
||||
if (_InternalDictionary.ContainsKey(key))
|
||||
return;
|
||||
|
||||
if (value is null)
|
||||
return;
|
||||
|
||||
_InternalDictionary.Add(key, value);
|
||||
}
|
||||
|
||||
public virtual void Set(string key, object value)
|
||||
{
|
||||
_InternalDictionary[key] = value;
|
||||
}
|
||||
|
||||
public virtual object Get(string key)
|
||||
{
|
||||
return _InternalDictionary[key];
|
||||
}
|
||||
|
||||
public virtual T Get<T>(string key, T defaulobject)
|
||||
{
|
||||
if (_InternalDictionary.TryGetValue(key, out var value))
|
||||
{
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
|
||||
return defaulobject;
|
||||
}
|
||||
|
||||
public virtual T? Get<T>(string key)
|
||||
{
|
||||
if (_InternalDictionary.TryGetValue(key, out var value))
|
||||
{
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public virtual IDictionary<TSubKey, TSubValue> GetDictionary<TSubKey, TSubValue>(string key)
|
||||
{
|
||||
if (_InternalDictionary.TryGetValue(key, out var value))
|
||||
{
|
||||
if (value is not IDictionary)
|
||||
{
|
||||
throw new Exception("The value is not a dictionary");
|
||||
}
|
||||
|
||||
var dictionary = new Dictionary<TSubKey, TSubValue>();
|
||||
foreach (DictionaryEntry item in (IDictionary)value)
|
||||
{
|
||||
dictionary.Add((TSubKey)Convert.ChangeType(item.Key, typeof(TSubKey)), (TSubValue)Convert.ChangeType(item.Value, typeof(TSubValue)));
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
return new Dictionary<TSubKey, TSubValue>();
|
||||
}
|
||||
|
||||
public virtual List<T> GetList<T>(string key, List<T> defaulobject)
|
||||
{
|
||||
if(_InternalDictionary.TryGetValue(key, out var value))
|
||||
{
|
||||
if (value is not IList)
|
||||
{
|
||||
throw new Exception("The value is not a list");
|
||||
}
|
||||
|
||||
var list = new List<T>();
|
||||
foreach (object? item in (IList)value)
|
||||
{
|
||||
list.Add((T)Convert.ChangeType(item, typeof(T)));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
_Logger.Log($"Key '{key}' not found in settings dictionary. Adding default value.", LogType.Warning);
|
||||
|
||||
return defaulobject;
|
||||
}
|
||||
|
||||
public virtual void Remove(string key)
|
||||
{
|
||||
_InternalDictionary.Remove(key);
|
||||
}
|
||||
|
||||
public virtual IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
||||
{
|
||||
return _InternalDictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
_InternalDictionary.Clear();
|
||||
}
|
||||
|
||||
public virtual bool ContainsKey(string key)
|
||||
{
|
||||
return _InternalDictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<KeyValuePair<string, object>> Where(Func<KeyValuePair<string, object>, bool> predicate)
|
||||
{
|
||||
return _InternalDictionary.Where(predicate);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<KeyValuePair<string, object>> Where(Func<KeyValuePair<string, object>, int, bool> predicate)
|
||||
{
|
||||
return _InternalDictionary.Where(predicate);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<TResult> Where<TResult>(Func<KeyValuePair<string, object>, TResult> selector)
|
||||
{
|
||||
return _InternalDictionary.Select(selector);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<TResult> Where<TResult>(Func<KeyValuePair<string, object>, int, TResult> selector)
|
||||
{
|
||||
return _InternalDictionary.Select(selector);
|
||||
}
|
||||
|
||||
public virtual KeyValuePair<string, object> FirstOrDefault(Func<KeyValuePair<string, object>, bool> predicate)
|
||||
{
|
||||
return _InternalDictionary.FirstOrDefault(predicate);
|
||||
}
|
||||
|
||||
public virtual KeyValuePair<string, object> FirstOrDefault()
|
||||
{
|
||||
return _InternalDictionary.FirstOrDefault();
|
||||
}
|
||||
|
||||
public virtual bool ContainsAllKeys(params string[] keys)
|
||||
{
|
||||
return keys.All(ContainsKey);
|
||||
}
|
||||
|
||||
public virtual bool TryGetValue(string key, out object? value)
|
||||
{
|
||||
return _InternalDictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public abstract Task SaveToFile();
|
||||
|
||||
public abstract void LoadFromFile();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>AnyCPU;x64;ARM64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiscordBotCore.Logging\DiscordBotCore.Logging.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
132
DiscordBotCore.Configuration/IConfiguration.cs
Normal file
132
DiscordBotCore.Configuration/IConfiguration.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
namespace DiscordBotCore.Configuration;
|
||||
|
||||
public interface IConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds an element to the custom settings dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="value">The value</param>
|
||||
void Add(string key, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of a key in the custom settings dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="value">The value</param>
|
||||
void Set(string key, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of a key in the custom settings dictionary. If the T type is different then the object type, it will try to convert it.
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="defaultObject">The default value to be returned if the searched value is not found</param>
|
||||
/// <typeparam name="T">The type of the returned value</typeparam>
|
||||
/// <returns></returns>
|
||||
T Get<T>(string key, T defaultObject);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of a key in the custom settings dictionary. If the T type is different then the object type, it will try to convert it.
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <typeparam name="T">The type of the returned value</typeparam>
|
||||
/// <returns></returns>
|
||||
T? Get<T>(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of values from the custom settings dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="defaultObject">The default list to be returned if nothing is found</param>
|
||||
/// <typeparam name="T">The type of the returned value</typeparam>
|
||||
/// <returns></returns>
|
||||
List<T> GetList<T>(string key, List<T> defaultObject);
|
||||
|
||||
/// <summary>
|
||||
/// Remove a key from the custom settings dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
void Remove(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Get the enumerator of the custom settings dictionary
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerator<KeyValuePair<string, object>> GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Clear the custom settings dictionary
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Check if the custom settings dictionary contains a key
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <returns></returns>
|
||||
bool ContainsKey(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Filter the custom settings dictionary based on a predicate
|
||||
/// </summary>
|
||||
/// <param name="predicate">The predicate</param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<KeyValuePair<string, object>> Where(Func<KeyValuePair<string, object>, bool> predicate);
|
||||
|
||||
/// <summary>
|
||||
/// Filter the custom settings dictionary based on a predicate
|
||||
/// </summary>
|
||||
/// <param name="predicate">The predicate</param>
|
||||
IEnumerable<KeyValuePair<string, object>> Where(Func<KeyValuePair<string, object>, int, bool> predicate);
|
||||
|
||||
/// <summary>
|
||||
/// Filter the custom settings dictionary based on a predicate
|
||||
/// </summary>
|
||||
/// <param name="selector">The predicate</param>
|
||||
IEnumerable<TResult> Where<TResult>(Func<KeyValuePair<string, object>, TResult> selector);
|
||||
|
||||
/// <summary>
|
||||
/// Filter the custom settings dictionary based on a predicate
|
||||
/// </summary>
|
||||
/// <param name="selector">The predicate</param>
|
||||
IEnumerable<TResult> Where<TResult>(Func<KeyValuePair<string, object>, int, TResult> selector);
|
||||
|
||||
/// <summary>
|
||||
/// Get the first element of the custom settings dictionary based on a predicate
|
||||
/// </summary>
|
||||
/// <param name="predicate">The predicate</param>
|
||||
KeyValuePair<string, object> FirstOrDefault(Func<KeyValuePair<string, object>, bool> predicate);
|
||||
|
||||
/// <summary>
|
||||
/// Get the first element of the custom settings dictionary
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
KeyValuePair<string, object> FirstOrDefault();
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the custom settings dictionary contains all the keys
|
||||
/// </summary>
|
||||
/// <param name="keys">A list of keys</param>
|
||||
/// <returns></returns>
|
||||
bool ContainsAllKeys(params string[] keys);
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the value of a key in the custom settings dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="value">The value</param>
|
||||
/// <returns></returns>
|
||||
bool TryGetValue(string key, out object? value);
|
||||
|
||||
/// <summary>
|
||||
/// Save the custom settings dictionary to a file
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task SaveToFile();
|
||||
|
||||
/// <summary>
|
||||
/// Load the custom settings dictionary from a file
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
void LoadFromFile();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>AnyCPU;x64;ARM64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
693
DiscordBotCore.Database.Sqlite/SqlDatabase.cs
Normal file
693
DiscordBotCore.Database.Sqlite/SqlDatabase.cs
Normal file
@@ -0,0 +1,693 @@
|
||||
using System.Data;
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
namespace DiscordBotCore.Database.Sqlite;
|
||||
|
||||
public class SqlDatabase
|
||||
{
|
||||
private readonly SqliteConnection _Connection;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a SQL connection by specifying its private path
|
||||
/// </summary>
|
||||
/// <param name="fileName">The path to the database (it is starting from ./Data/Resources/)</param>
|
||||
public SqlDatabase(string fileName)
|
||||
{
|
||||
var connectionString = $"Data Source={fileName}";
|
||||
_Connection = new SqliteConnection(connectionString);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Open the SQL Connection. To close use the Stop() method
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task Open()
|
||||
{
|
||||
await _Connection.OpenAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Insert into a specified table some values
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <param name="values">The values to be inserted (in the correct order and number)</param>
|
||||
/// <returns></returns>
|
||||
public async Task InsertAsync(string tableName, params string[] values)
|
||||
{
|
||||
var query = $"INSERT INTO {tableName} VALUES (";
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
query += $"'{values[i]}'";
|
||||
if (i != values.Length - 1)
|
||||
query += ", ";
|
||||
}
|
||||
|
||||
query += ")";
|
||||
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Insert into a specified table some values
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <param name="values">The values to be inserted (in the correct order and number)</param>
|
||||
/// <returns></returns>
|
||||
public void Insert(string tableName, params string[] values)
|
||||
{
|
||||
var query = $"INSERT INTO {tableName} VALUES (";
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
query += $"'{values[i]}'";
|
||||
if (i != values.Length - 1)
|
||||
query += ", ";
|
||||
}
|
||||
|
||||
query += ")";
|
||||
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove every row in a table that has a certain propery
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <param name="KeyName">The column name that the search is made by</param>
|
||||
/// <param name="KeyValue">The value that is searched in the specified column</param>
|
||||
/// <returns></returns>
|
||||
public async Task RemoveKeyAsync(string tableName, string KeyName, string KeyValue)
|
||||
{
|
||||
var query = $"DELETE FROM {tableName} WHERE {KeyName} = '{KeyValue}'";
|
||||
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove every row in a table that has a certain propery
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <param name="KeyName">The column name that the search is made by</param>
|
||||
/// <param name="KeyValue">The value that is searched in the specified column</param>
|
||||
/// <returns></returns>
|
||||
public void RemoveKey(string tableName, string KeyName, string KeyValue)
|
||||
{
|
||||
var query = $"DELETE FROM {tableName} WHERE {KeyName} = '{KeyValue}'";
|
||||
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the key exists in the table
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <param name="keyName">The column that the search is made by</param>
|
||||
/// <param name="KeyValue">The value that is searched in the specified column</param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> KeyExistsAsync(string tableName, string keyName, string KeyValue)
|
||||
{
|
||||
var query = $"SELECT * FROM {tableName} where {keyName} = '{KeyValue}'";
|
||||
|
||||
if (await ReadDataAsync(query) is not null)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the key exists in the table
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <param name="keyName">The column that the search is made by</param>
|
||||
/// <param name="KeyValue">The value that is searched in the specified column</param>
|
||||
/// <returns></returns>
|
||||
public bool KeyExists(string tableName, string keyName, string KeyValue)
|
||||
{
|
||||
var query = $"SELECT * FROM {tableName} where {keyName} = '{KeyValue}'";
|
||||
|
||||
if (ReadData(query) is not null)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set value of a column in a table
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <param name="keyName">The column that the search is made by</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="ResultColumnValue">The new value that will replace the old value from the column specified</param>
|
||||
public async Task SetValueAsync(
|
||||
string tableName, string keyName, string KeyValue, string ResultColumnName,
|
||||
string ResultColumnValue)
|
||||
{
|
||||
if (!await TableExistsAsync(tableName))
|
||||
throw new Exception($"Table {tableName} does not exist");
|
||||
|
||||
await ExecuteAsync(
|
||||
$"UPDATE {tableName} SET {ResultColumnName}='{ResultColumnValue}' WHERE {keyName}='{KeyValue}'"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set value of a column in a table
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <param name="keyName">The column that the search is made by</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="ResultColumnValue">The new value that will replace the old value from the column specified</param>
|
||||
public void SetValue(
|
||||
string tableName, string keyName, string KeyValue, string ResultColumnName,
|
||||
string ResultColumnValue)
|
||||
{
|
||||
if (!TableExists(tableName))
|
||||
throw new Exception($"Table {tableName} does not exist");
|
||||
|
||||
Execute($"UPDATE {tableName} SET {ResultColumnName}='{ResultColumnValue}' WHERE {keyName}='{KeyValue}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get value from a column in a table
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <param name="keyName">The column that the search is made by</param>
|
||||
/// <param name="KeyValue">The value that is searched in the specified column</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>
|
||||
public async Task<string?> GetValueAsync(
|
||||
string tableName, string keyName, string KeyValue,
|
||||
string ResultColumnName)
|
||||
{
|
||||
if (!await TableExistsAsync(tableName))
|
||||
throw new Exception($"Table {tableName} does not exist");
|
||||
|
||||
return await ReadDataAsync($"SELECT {ResultColumnName} FROM {tableName} WHERE {keyName}='{KeyValue}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get value from a column in a table
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <param name="keyName">The column that the search is made by</param>
|
||||
/// <param name="KeyValue">The value that is searched in the specified column</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>
|
||||
public string? GetValue(string tableName, string keyName, string KeyValue, string ResultColumnName)
|
||||
{
|
||||
if (!TableExists(tableName))
|
||||
throw new Exception($"Table {tableName} does not exist");
|
||||
|
||||
return ReadData($"SELECT {ResultColumnName} FROM {tableName} WHERE {keyName}='{KeyValue}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the connection to the SQL Database
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async void Stop()
|
||||
{
|
||||
await _Connection.CloseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change the structure of a table by adding new columns
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <param name="columns">The columns to be added</param>
|
||||
/// <param name="TYPE">The type of the columns (TEXT, INTEGER, FLOAT, etc)</param>
|
||||
/// <returns></returns>
|
||||
public async Task AddColumnsToTableAsync(string tableName, string[] columns, string TYPE = "TEXT")
|
||||
{
|
||||
var command = _Connection.CreateCommand();
|
||||
command.CommandText = $"SELECT * FROM {tableName}";
|
||||
var reader = await command.ExecuteReaderAsync();
|
||||
var tableColumns = new List<string>();
|
||||
for (var i = 0; i < reader.FieldCount; i++)
|
||||
tableColumns.Add(reader.GetName(i));
|
||||
|
||||
foreach (var column in columns)
|
||||
if (!tableColumns.Contains(column))
|
||||
{
|
||||
command.CommandText = $"ALTER TABLE {tableName} ADD COLUMN {column} {TYPE}";
|
||||
await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change the structure of a table by adding new columns
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <param name="columns">The columns to be added</param>
|
||||
/// <param name="TYPE">The type of the columns (TEXT, INTEGER, FLOAT, etc)</param>
|
||||
/// <returns></returns>
|
||||
public void AddColumnsToTable(string tableName, string[] columns, string TYPE = "TEXT")
|
||||
{
|
||||
var command = _Connection.CreateCommand();
|
||||
command.CommandText = $"SELECT * FROM {tableName}";
|
||||
var reader = command.ExecuteReader();
|
||||
var tableColumns = new List<string>();
|
||||
for (var i = 0; i < reader.FieldCount; i++)
|
||||
tableColumns.Add(reader.GetName(i));
|
||||
|
||||
foreach (var column in columns)
|
||||
if (!tableColumns.Contains(column))
|
||||
{
|
||||
command.CommandText = $"ALTER TABLE {tableName} ADD COLUMN {column} {TYPE}";
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a table exists
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <returns>True if the table exists, false if not</returns>
|
||||
public async Task<bool> TableExistsAsync(string tableName)
|
||||
{
|
||||
var cmd = _Connection.CreateCommand();
|
||||
cmd.CommandText = $"SELECT name FROM sqlite_master WHERE type='table' AND name='{tableName}'";
|
||||
var result = await cmd.ExecuteScalarAsync();
|
||||
|
||||
if (result == null)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a table exists
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <returns>True if the table exists, false if not</returns>
|
||||
public bool TableExists(string tableName)
|
||||
{
|
||||
var cmd = _Connection.CreateCommand();
|
||||
cmd.CommandText = $"SELECT name FROM sqlite_master WHERE type='table' AND name='{tableName}'";
|
||||
var result = cmd.ExecuteScalar();
|
||||
|
||||
if (result == null)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a table
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <param name="columns">The columns of the table</param>
|
||||
/// <returns></returns>
|
||||
public async Task CreateTableAsync(string tableName, params string[] columns)
|
||||
{
|
||||
var cmd = _Connection.CreateCommand();
|
||||
cmd.CommandText = $"CREATE TABLE IF NOT EXISTS {tableName} ({string.Join(", ", columns)})";
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a table
|
||||
/// </summary>
|
||||
/// <param name="tableName">The table name</param>
|
||||
/// <param name="columns">The columns of the table</param>
|
||||
/// <returns></returns>
|
||||
public void CreateTable(string tableName, params string[] columns)
|
||||
{
|
||||
var cmd = _Connection.CreateCommand();
|
||||
cmd.CommandText = $"CREATE TABLE IF NOT EXISTS {tableName} ({string.Join(", ", columns)})";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a custom query
|
||||
/// </summary>
|
||||
/// <param name="query">The query</param>
|
||||
/// <returns>The number of rows that the query modified</returns>
|
||||
public async Task<int> ExecuteAsync(string query)
|
||||
{
|
||||
if (!_Connection.State.HasFlag(ConnectionState.Open))
|
||||
await _Connection.OpenAsync();
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
var answer = await command.ExecuteNonQueryAsync();
|
||||
return answer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a custom query
|
||||
/// </summary>
|
||||
/// <param name="query">The query</param>
|
||||
/// <returns>The number of rows that the query modified</returns>
|
||||
public int Execute(string query)
|
||||
{
|
||||
if (!_Connection.State.HasFlag(ConnectionState.Open))
|
||||
_Connection.Open();
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
var r = command.ExecuteNonQuery();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read data from the result table and return the first row
|
||||
/// </summary>
|
||||
/// <param name="query">The query</param>
|
||||
/// <returns>The result is a string that has all values separated by space character</returns>
|
||||
public async Task<string?> ReadDataAsync(string query)
|
||||
{
|
||||
if (!_Connection.State.HasFlag(ConnectionState.Open))
|
||||
await _Connection.OpenAsync();
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
var reader = await command.ExecuteReaderAsync();
|
||||
|
||||
var values = new object[reader.FieldCount];
|
||||
if (reader.Read())
|
||||
{
|
||||
reader.GetValues(values);
|
||||
return string.Join<object>(" ", values);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read data from the result table and return the first row
|
||||
/// </summary>
|
||||
/// <param name="query">The query</param>
|
||||
/// <param name="parameters">The parameters of the query</param>
|
||||
/// <returns>The result is a string that has all values separated by space character</returns>
|
||||
public async Task<string?> ReadDataAsync(string query, params KeyValuePair<string, object>[] parameters)
|
||||
{
|
||||
if (!_Connection.State.HasFlag(ConnectionState.Open))
|
||||
await _Connection.OpenAsync();
|
||||
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
var p = CreateParameter(parameter);
|
||||
if (p is not null)
|
||||
command.Parameters.Add(p);
|
||||
}
|
||||
|
||||
var reader = await command.ExecuteReaderAsync();
|
||||
|
||||
var values = new object[reader.FieldCount];
|
||||
if (reader.Read())
|
||||
{
|
||||
reader.GetValues(values);
|
||||
return string.Join<object>(" ", values);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read data from the result table and return the first row
|
||||
/// </summary>
|
||||
/// <param name="query">The query</param>
|
||||
/// <returns>The result is a string that has all values separated by space character</returns>
|
||||
public string? ReadData(string query)
|
||||
{
|
||||
if (!_Connection.State.HasFlag(ConnectionState.Open))
|
||||
_Connection.Open();
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
var reader = command.ExecuteReader();
|
||||
|
||||
var values = new object[reader.FieldCount];
|
||||
if (reader.Read())
|
||||
{
|
||||
reader.GetValues(values);
|
||||
return string.Join<object>(" ", values);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read data from the result table and return the first row
|
||||
/// </summary>
|
||||
/// <param name="query">The query</param>
|
||||
/// <returns>The first row as separated items</returns>
|
||||
public async Task<object[]?> ReadDataArrayAsync(string query)
|
||||
{
|
||||
if (!_Connection.State.HasFlag(ConnectionState.Open))
|
||||
await _Connection.OpenAsync();
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
var reader = await command.ExecuteReaderAsync();
|
||||
|
||||
var values = new object[reader.FieldCount];
|
||||
if (reader.Read())
|
||||
{
|
||||
reader.GetValues(values);
|
||||
return values;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<object[]?> ReadDataArrayAsync(string query, params KeyValuePair<string, object>[] parameters)
|
||||
{
|
||||
if (!_Connection.State.HasFlag(ConnectionState.Open))
|
||||
await _Connection.OpenAsync();
|
||||
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
var p = CreateParameter(parameter);
|
||||
if (p is not null)
|
||||
command.Parameters.Add(p);
|
||||
}
|
||||
|
||||
var reader = await command.ExecuteReaderAsync();
|
||||
|
||||
var values = new object[reader.FieldCount];
|
||||
if (reader.Read())
|
||||
{
|
||||
reader.GetValues(values);
|
||||
return values;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read data from the result table and return the first row
|
||||
/// </summary>
|
||||
/// <param name="query">The query</param>
|
||||
/// <returns>The first row as separated items</returns>
|
||||
public object[]? ReadDataArray(string query)
|
||||
{
|
||||
if (!_Connection.State.HasFlag(ConnectionState.Open))
|
||||
_Connection.Open();
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
var reader = command.ExecuteReader();
|
||||
|
||||
var values = new object[reader.FieldCount];
|
||||
if (reader.Read())
|
||||
{
|
||||
reader.GetValues(values);
|
||||
return values;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// </summary>
|
||||
/// <param name="query">The query</param>
|
||||
/// <returns>A list of string arrays representing the values that the query returns</returns>
|
||||
public async Task<List<string[]>?> ReadAllRowsAsync(string query)
|
||||
{
|
||||
if (!_Connection.State.HasFlag(ConnectionState.Open))
|
||||
await _Connection.OpenAsync();
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
var reader = await command.ExecuteReaderAsync();
|
||||
|
||||
if (!reader.HasRows)
|
||||
return null;
|
||||
|
||||
List<string[]> rows = new();
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
var values = new string[reader.FieldCount];
|
||||
reader.GetValues(values);
|
||||
rows.Add(values);
|
||||
}
|
||||
|
||||
if (rows.Count == 0) return null;
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a parameter for a query
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the parameter</param>
|
||||
/// <param name="value">The value of the parameter</param>
|
||||
/// <returns>The SQLiteParameter that has the name, value and DBType set according to your inputs</returns>
|
||||
private static SqliteParameter? CreateParameter(string name, object value)
|
||||
{
|
||||
var parameter = new SqliteParameter();
|
||||
parameter.ParameterName = name;
|
||||
parameter.Value = value;
|
||||
|
||||
if (value is string)
|
||||
parameter.DbType = DbType.String;
|
||||
else if (value is int)
|
||||
parameter.DbType = DbType.Int32;
|
||||
else if (value is long)
|
||||
parameter.DbType = DbType.Int64;
|
||||
else if (value is float)
|
||||
parameter.DbType = DbType.Single;
|
||||
else if (value is double)
|
||||
parameter.DbType = DbType.Double;
|
||||
else if (value is bool)
|
||||
parameter.DbType = DbType.Boolean;
|
||||
else if (value is DateTime)
|
||||
parameter.DbType = DbType.DateTime;
|
||||
else if (value is byte[])
|
||||
parameter.DbType = DbType.Binary;
|
||||
else if (value is Guid)
|
||||
parameter.DbType = DbType.Guid;
|
||||
else if (value is decimal)
|
||||
parameter.DbType = DbType.Decimal;
|
||||
else if (value is TimeSpan)
|
||||
parameter.DbType = DbType.Time;
|
||||
else if (value is DateTimeOffset)
|
||||
parameter.DbType = DbType.DateTimeOffset;
|
||||
else if (value is ushort)
|
||||
parameter.DbType = DbType.UInt16;
|
||||
else if (value is uint)
|
||||
parameter.DbType = DbType.UInt32;
|
||||
else if (value is ulong)
|
||||
parameter.DbType = DbType.UInt64;
|
||||
else if (value is sbyte)
|
||||
parameter.DbType = DbType.SByte;
|
||||
else if (value is short)
|
||||
parameter.DbType = DbType.Int16;
|
||||
else if (value is byte)
|
||||
parameter.DbType = DbType.Byte;
|
||||
else if (value is char)
|
||||
parameter.DbType = DbType.StringFixedLength;
|
||||
else if (value is char[])
|
||||
parameter.DbType = DbType.StringFixedLength;
|
||||
else
|
||||
return null;
|
||||
|
||||
return parameter;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a parameter for a query. The function automatically detects the type of the value.
|
||||
/// </summary>
|
||||
/// <param name="parameterValues">The parameter raw inputs. The Key is name and the Value is the value of the parameter</param>
|
||||
/// <returns>The SQLiteParameter that has the name, value and DBType set according to your inputs</returns>
|
||||
private static SqliteParameter? CreateParameter(KeyValuePair<string, object> parameterValues)
|
||||
{
|
||||
return CreateParameter(parameterValues.Key, parameterValues.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a query with parameters
|
||||
/// </summary>
|
||||
/// <param name="query">The query to execute</param>
|
||||
/// <param name="parameters">The parameters of the query</param>
|
||||
/// <returns>The number of rows that the query modified in the database</returns>
|
||||
public async Task<int> ExecuteNonQueryAsync(string query, params KeyValuePair<string, object>[] parameters)
|
||||
{
|
||||
if (!_Connection.State.HasFlag(ConnectionState.Open))
|
||||
await _Connection.OpenAsync();
|
||||
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
var p = CreateParameter(parameter);
|
||||
if (p is not null)
|
||||
command.Parameters.Add(p);
|
||||
}
|
||||
|
||||
return await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a query with parameters that returns a specific type of object. The function will return the first row of the result transformed into the specified type.
|
||||
/// </summary>
|
||||
/// <param name="query">The query to execute</param>
|
||||
/// <param name="convertor">The convertor function that will convert each row of the response into an object of <typeparamref name="T"/></param>
|
||||
/// <param name="parameters">The parameters of the query</param>
|
||||
/// <typeparam name="T">The return object type</typeparam>
|
||||
/// <returns>An object of type T that represents the output of the convertor function based on the array of objects that the first row of the result has</returns>
|
||||
public async Task<T?> ReadObjectOfTypeAsync<T>(string query, Func<object[], T> convertor, params KeyValuePair<string, object>[] parameters)
|
||||
{
|
||||
if (!_Connection.State.HasFlag(ConnectionState.Open))
|
||||
await _Connection.OpenAsync();
|
||||
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
var p = CreateParameter(parameter);
|
||||
if (p is not null)
|
||||
command.Parameters.Add(p);
|
||||
}
|
||||
|
||||
var reader = await command.ExecuteReaderAsync();
|
||||
var values = new object[reader.FieldCount];
|
||||
if (reader.Read())
|
||||
{
|
||||
reader.GetValues(values);
|
||||
return convertor(values);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Execute a query with parameters that returns a specific type of object. The function will return a list of objects of the specified type.
|
||||
/// </summary>
|
||||
/// <param name="query">The query to execute</param>
|
||||
/// <param name="convertor">The convertor from object[] to T</param>
|
||||
/// <param name="parameters">The parameters of the query</param>
|
||||
/// <typeparam name="T">The expected object type</typeparam>
|
||||
/// <returns>A list of objects of type T that represents each line of the output of the specified query, converted to T</returns>
|
||||
public async Task<List<T>> ReadListOfTypeAsync<T>(string query, Func<object[], T> convertor,
|
||||
params KeyValuePair<string, object>[] parameters)
|
||||
{
|
||||
if (!_Connection.State.HasFlag(ConnectionState.Open))
|
||||
await _Connection.OpenAsync();
|
||||
|
||||
var command = new SqliteCommand(query, _Connection);
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
var p = CreateParameter(parameter);
|
||||
if (p is not null)
|
||||
command.Parameters.Add(p);
|
||||
}
|
||||
|
||||
var reader = await command.ExecuteReaderAsync();
|
||||
//
|
||||
if (!reader.HasRows)
|
||||
return null;
|
||||
|
||||
List<T> rows = new();
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
var values = new object[reader.FieldCount];
|
||||
reader.GetValues(values);
|
||||
rows.Add(convertor(values));
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
10
DiscordBotCore.Logging/DiscordBotCore.Logging.csproj
Normal file
10
DiscordBotCore.Logging/DiscordBotCore.Logging.csproj
Normal file
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>AnyCPU;x64;ARM64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
10
DiscordBotCore.Logging/ILogMessage.cs
Normal file
10
DiscordBotCore.Logging/ILogMessage.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace DiscordBotCore.Logging;
|
||||
|
||||
public interface ILogMessage
|
||||
{
|
||||
public string Message { get; protected set; }
|
||||
public DateTime ThrowTime { get; protected set; }
|
||||
public string SenderName { get; protected set; }
|
||||
public LogType LogMessageType { get; protected set; }
|
||||
|
||||
}
|
||||
13
DiscordBotCore.Logging/ILogger.cs
Normal file
13
DiscordBotCore.Logging/ILogger.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace DiscordBotCore.Logging;
|
||||
|
||||
public interface ILogger
|
||||
{
|
||||
List<ILogMessage> LogMessages { get; protected set; }
|
||||
event Action<ILogMessage>? OnLogReceived;
|
||||
|
||||
void Log(string message);
|
||||
void Log(string message, LogType logType);
|
||||
void Log(string message, object sender);
|
||||
void Log(string message, object sender, LogType type);
|
||||
void LogException(Exception exception, object sender, bool logFullStack = false);
|
||||
}
|
||||
75
DiscordBotCore.Logging/LogMessage.cs
Normal file
75
DiscordBotCore.Logging/LogMessage.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
namespace DiscordBotCore.Logging;
|
||||
|
||||
internal sealed class LogMessage : ILogMessage
|
||||
{
|
||||
private static readonly string _DefaultLogMessageSender = "\b";
|
||||
public string Message { get; set; }
|
||||
public DateTime ThrowTime { get; set; }
|
||||
public string SenderName { get; set; }
|
||||
public LogType LogMessageType { get; set; }
|
||||
|
||||
public LogMessage(string message, LogType logMessageType)
|
||||
{
|
||||
Message = message;
|
||||
LogMessageType = logMessageType;
|
||||
ThrowTime = DateTime.Now;
|
||||
SenderName = string.Empty;
|
||||
}
|
||||
|
||||
public LogMessage(string message, object sender)
|
||||
{
|
||||
Message = message;
|
||||
SenderName = sender is string && sender as string == string.Empty ? _DefaultLogMessageSender : sender.GetType().FullName ?? sender.GetType().Name;
|
||||
ThrowTime = DateTime.Now;
|
||||
LogMessageType = LogType.Info;
|
||||
}
|
||||
|
||||
public LogMessage(string message, object sender, DateTime throwTime)
|
||||
{
|
||||
Message = message;
|
||||
SenderName = sender is string && sender as string == string.Empty ? _DefaultLogMessageSender : sender.GetType().FullName ?? sender.GetType().Name;
|
||||
ThrowTime = throwTime;
|
||||
LogMessageType = LogType.Info;
|
||||
}
|
||||
|
||||
public LogMessage(string message, object sender, LogType logMessageType)
|
||||
{
|
||||
Message = message;
|
||||
SenderName = sender is string && sender as string == string.Empty ? _DefaultLogMessageSender : sender.GetType().FullName ?? sender.GetType().Name;
|
||||
ThrowTime = DateTime.Now;
|
||||
LogMessageType = logMessageType;
|
||||
|
||||
}
|
||||
|
||||
public LogMessage(string message, DateTime throwTime, object sender, LogType logMessageType)
|
||||
{
|
||||
Message = message;
|
||||
ThrowTime = throwTime;
|
||||
SenderName = sender is string && sender as string == string.Empty ? _DefaultLogMessageSender : sender.GetType().FullName ?? sender.GetType().Name;
|
||||
LogMessageType = logMessageType;
|
||||
}
|
||||
|
||||
public LogMessage WithMessage(string message)
|
||||
{
|
||||
this.Message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LogMessage WithCurrentThrowTime()
|
||||
{
|
||||
this.ThrowTime = DateTime.Now;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LogMessage WithMessageType(LogType logType)
|
||||
{
|
||||
this.LogMessageType = logType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static LogMessage CreateFromException(Exception exception, object Sender, bool logFullStack)
|
||||
{
|
||||
LogMessage message = new LogMessage(logFullStack? exception.ToString() : exception.Message, Sender, LogType.Error);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
9
DiscordBotCore.Logging/LogType.cs
Normal file
9
DiscordBotCore.Logging/LogType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DiscordBotCore.Logging;
|
||||
|
||||
public enum LogType
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Critical
|
||||
}
|
||||
62
DiscordBotCore.Logging/Logger.cs
Normal file
62
DiscordBotCore.Logging/Logger.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
namespace DiscordBotCore.Logging;
|
||||
|
||||
public sealed class Logger : ILogger
|
||||
{
|
||||
private readonly string _LogFile;
|
||||
private readonly string _LogMessageFormat;
|
||||
private readonly int _MaxHistorySize;
|
||||
|
||||
private readonly List<string> _logMessageProperties = typeof(ILogMessage)
|
||||
.GetProperties()
|
||||
.Select(p => p.Name)
|
||||
.ToList();
|
||||
|
||||
|
||||
public List<ILogMessage> LogMessages { get; set; }
|
||||
public event Action<ILogMessage>? OnLogReceived;
|
||||
|
||||
|
||||
public Logger(string logFolder, string logMessageFormat, int maxHistorySize)
|
||||
{
|
||||
this._LogMessageFormat = logMessageFormat;
|
||||
this._LogFile = Path.Combine(logFolder, $"{DateTime.Now:yyyy-MM-dd}.log");
|
||||
this._MaxHistorySize = maxHistorySize;
|
||||
LogMessages = new List<ILogMessage>();
|
||||
}
|
||||
|
||||
private string GenerateLogMessage(ILogMessage message)
|
||||
{
|
||||
string messageAsString = new string(_LogMessageFormat);
|
||||
foreach (var prop in _logMessageProperties)
|
||||
{
|
||||
Type messageType = typeof(ILogMessage);
|
||||
messageAsString = messageAsString.Replace("{" + prop + "}", messageType.GetProperty(prop)?.GetValue(message)?.ToString());
|
||||
}
|
||||
|
||||
return messageAsString;
|
||||
}
|
||||
|
||||
private async Task LogToFile(string message)
|
||||
{
|
||||
await using var streamWriter = new StreamWriter(_LogFile, true);
|
||||
await streamWriter.WriteLineAsync(message);
|
||||
}
|
||||
|
||||
private async void Log(ILogMessage message)
|
||||
{
|
||||
var messageAsString = GenerateLogMessage(message);
|
||||
OnLogReceived?.Invoke(message);
|
||||
LogMessages.Add(message);
|
||||
if (LogMessages.Count > _MaxHistorySize)
|
||||
{
|
||||
LogMessages.RemoveAt(0);
|
||||
}
|
||||
await LogToFile(messageAsString);
|
||||
}
|
||||
|
||||
public void Log(string message) => Log(new LogMessage(message, string.Empty, LogType.Info));
|
||||
public void Log(string message, LogType logType) => Log(new LogMessage(message, logType));
|
||||
public void Log(string message, object sender) => Log(new LogMessage(message, sender));
|
||||
public void Log(string message, object sender, LogType type) => Log(new LogMessage(message, sender, type));
|
||||
public void LogException(Exception exception, object sender, bool logFullStack = false) => Log(LogMessage.CreateFromException(exception, sender, logFullStack));
|
||||
}
|
||||
10
DiscordBotCore.Networking/DiscordBotCore.Networking.csproj
Normal file
10
DiscordBotCore.Networking/DiscordBotCore.Networking.csproj
Normal file
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>AnyCPU;x64;ARM64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
25
DiscordBotCore.Networking/FileDownloader.cs
Normal file
25
DiscordBotCore.Networking/FileDownloader.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using DiscordBotCore.Networking.Helpers;
|
||||
|
||||
namespace DiscordBotCore.Networking;
|
||||
|
||||
public class FileDownloader
|
||||
{
|
||||
private readonly string _DownloadUrl;
|
||||
private readonly string _DownloadLocation;
|
||||
|
||||
private readonly HttpClient _HttpClient;
|
||||
|
||||
public FileDownloader(string downloadUrl, string downloadLocation)
|
||||
{
|
||||
_DownloadUrl = downloadUrl;
|
||||
_DownloadLocation = downloadLocation;
|
||||
|
||||
_HttpClient = new HttpClient();
|
||||
}
|
||||
|
||||
public async Task DownloadFile(Action<float> progressCallback)
|
||||
{
|
||||
await using var fileStream = new FileStream(_DownloadLocation, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
await _HttpClient.DownloadFileAsync(_DownloadUrl, fileStream, new Progress<float>(progressCallback));
|
||||
}
|
||||
}
|
||||
91
DiscordBotCore.Networking/Helpers/OnlineFunctions.cs
Normal file
91
DiscordBotCore.Networking/Helpers/OnlineFunctions.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
namespace DiscordBotCore.Networking.Helpers;
|
||||
|
||||
internal static class OnlineFunctions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Copy one Stream to another <see langword="async" />
|
||||
/// </summary>
|
||||
/// <param name="stream">The base stream</param>
|
||||
/// <param name="destination">The destination stream</param>
|
||||
/// <param name="bufferSize">The buffer to read</param>
|
||||
/// <param name="progress">The progress</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <exception cref="ArgumentNullException">Triggered if any <see cref="Stream" /> is empty</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="ArgumentException">Triggered in <paramref name="destination" /> is not writable</exception>
|
||||
private static async Task CopyToOtherStreamAsync(
|
||||
this Stream stream, Stream destination, int bufferSize,
|
||||
IProgress<long>? progress = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (stream == null) throw new ArgumentNullException(nameof(stream));
|
||||
if (destination == null) throw new ArgumentNullException(nameof(destination));
|
||||
if (bufferSize <= 0) throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||
if (!stream.CanRead) throw new InvalidOperationException("The stream is not readable.");
|
||||
if (!destination.CanWrite)
|
||||
throw new ArgumentException("Destination stream is not writable", nameof(destination));
|
||||
|
||||
var buffer = new byte[bufferSize];
|
||||
long totalBytesRead = 0;
|
||||
int bytesRead;
|
||||
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)
|
||||
.ConfigureAwait(false)) != 0)
|
||||
{
|
||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||
totalBytesRead += bytesRead;
|
||||
progress?.Report(totalBytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a <see cref="Stream" /> and saves it to another <see cref="Stream" />.
|
||||
/// </summary>
|
||||
/// <param name="client">The <see cref="HttpClient" /> that is used to download the file</param>
|
||||
/// <param name="url">The url to the file</param>
|
||||
/// <param name="destination">The <see cref="Stream" /> to save the downloaded data</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>
|
||||
/// <returns></returns>
|
||||
internal static async Task DownloadFileAsync(
|
||||
this HttpClient client, string url, Stream destination,
|
||||
IProgress<float>? progress = null,
|
||||
IProgress<long>? downloadedBytes = null, int bufferSize = 81920,
|
||||
CancellationToken cancellation = default)
|
||||
{
|
||||
using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellation))
|
||||
{
|
||||
var contentLength = response.Content.Headers.ContentLength;
|
||||
|
||||
using (var download = await response.Content.ReadAsStreamAsync(cancellation))
|
||||
{
|
||||
// Ignore progress reporting when no progress reporter was
|
||||
// passed or when the content length is unknown
|
||||
if (progress == null || !contentLength.HasValue)
|
||||
{
|
||||
await download.CopyToAsync(destination, cancellation);
|
||||
if (!contentLength.HasValue)
|
||||
progress?.Report(100f);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
|
||||
// total ... 100%
|
||||
// downloaded ... x%
|
||||
// x = downloaded * 100 / total => x = downloaded / total * 100
|
||||
var relativeProgress = new Progress<long>(totalBytesDownloaded =>
|
||||
{
|
||||
progress?.Report(totalBytesDownloaded / (float)contentLength.Value * 100);
|
||||
downloadedBytes?.Report(totalBytesDownloaded);
|
||||
}
|
||||
);
|
||||
|
||||
// Use extension method to report progress while downloading
|
||||
await download.CopyToOtherStreamAsync(destination, bufferSize, relativeProgress, cancellation);
|
||||
progress.Report(100f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
DiscordBotCore.Networking/ParallelDownloadExecutor.cs
Normal file
89
DiscordBotCore.Networking/ParallelDownloadExecutor.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using DiscordBotCore.Networking.Helpers;
|
||||
|
||||
namespace DiscordBotCore.Networking;
|
||||
|
||||
public class ParallelDownloadExecutor
|
||||
{
|
||||
private readonly List<Task> _listOfTasks;
|
||||
private readonly HttpClient _httpClient;
|
||||
private Action? OnFinishAction { get; set; }
|
||||
|
||||
public ParallelDownloadExecutor(List<Task> listOfTasks)
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
_listOfTasks = listOfTasks;
|
||||
}
|
||||
|
||||
public ParallelDownloadExecutor()
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
_listOfTasks = new List<Task>();
|
||||
}
|
||||
|
||||
public async Task StartTasks()
|
||||
{
|
||||
await Task.WhenAll(_listOfTasks);
|
||||
OnFinishAction?.Invoke();
|
||||
}
|
||||
|
||||
public async Task ExecuteAllTasks(int maxDegreeOfParallelism = 4)
|
||||
{
|
||||
using var semaphore = new SemaphoreSlim(maxDegreeOfParallelism);
|
||||
|
||||
var tasks = _listOfTasks.Select(async task =>
|
||||
{
|
||||
await semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
await task;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
OnFinishAction?.Invoke();
|
||||
}
|
||||
|
||||
public void SetFinishAction(Action action)
|
||||
{
|
||||
OnFinishAction = action;
|
||||
}
|
||||
|
||||
public void AddTask(string downloadLink, string downloadLocation)
|
||||
{
|
||||
if (string.IsNullOrEmpty(downloadLink) || string.IsNullOrEmpty(downloadLocation))
|
||||
throw new ArgumentException("Download link or location cannot be null or empty.");
|
||||
|
||||
if (Directory.Exists(Path.GetDirectoryName(downloadLocation)) == false)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(downloadLocation));
|
||||
}
|
||||
|
||||
var task = CreateDownloadTask(downloadLink, downloadLocation, null);
|
||||
_listOfTasks.Add(task);
|
||||
}
|
||||
|
||||
public void AddTask(string downloadLink, string downloadLocation, Action<float> progressCallback)
|
||||
{
|
||||
if (string.IsNullOrEmpty(downloadLink) || string.IsNullOrEmpty(downloadLocation))
|
||||
throw new ArgumentException("Download link or location cannot be null or empty.");
|
||||
|
||||
if (Directory.Exists(Path.GetDirectoryName(downloadLocation)) == false)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(downloadLocation));
|
||||
}
|
||||
|
||||
var task = CreateDownloadTask(downloadLink, downloadLocation, new Progress<float>(progressCallback));
|
||||
_listOfTasks.Add(task);
|
||||
}
|
||||
|
||||
private Task CreateDownloadTask(string downloadLink, string downloadLocation, IProgress<float> progress)
|
||||
{
|
||||
var fileStream = new FileStream(downloadLocation, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
return _httpClient.DownloadFileAsync(downloadLink, fileStream, progress);
|
||||
}
|
||||
|
||||
}
|
||||
22
DiscordBotCore.PluginCore/DiscordBotCore.PluginCore.csproj
Normal file
22
DiscordBotCore.PluginCore/DiscordBotCore.PluginCore.csproj
Normal file
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>AnyCPU;x64;ARM64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net" Version="3.17.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiscordBotCore.Logging\DiscordBotCore.Logging.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Helpers\Execution\DbSlashCommand\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,26 @@
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace DiscordBotCore.PluginCore.Helpers.Execution.DbCommand;
|
||||
|
||||
public class DbCommandExecutingArgument : IDbCommandExecutingArgument
|
||||
{
|
||||
public SocketCommandContext Context { get; init; }
|
||||
public string CleanContent { get; init; }
|
||||
public string CommandUsed { get; init; }
|
||||
public string[]? Arguments { get; init; }
|
||||
public ILogger Logger { get; init; }
|
||||
public DirectoryInfo PluginBaseDirectory { get; init; }
|
||||
|
||||
public DbCommandExecutingArgument(ILogger logger, SocketCommandContext context, string cleanContent, string commandUsed, string[]? arguments, DirectoryInfo pluginBaseDirectory)
|
||||
{
|
||||
this.Logger = logger;
|
||||
this.Context = context;
|
||||
this.CleanContent = cleanContent;
|
||||
this.CommandUsed = commandUsed;
|
||||
this.Arguments = arguments;
|
||||
this.PluginBaseDirectory = pluginBaseDirectory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace DiscordBotCore.PluginCore.Helpers.Execution.DbCommand;
|
||||
|
||||
public interface IDbCommandExecutingArgument
|
||||
{
|
||||
ILogger Logger { get; init; }
|
||||
string CleanContent { get; init; }
|
||||
string CommandUsed { get; init; }
|
||||
string[]? Arguments { get; init; }
|
||||
|
||||
SocketCommandContext Context { get; init; }
|
||||
public DirectoryInfo PluginBaseDirectory { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace DiscordBotCore.PluginCore.Helpers.Execution.DbEvent;
|
||||
|
||||
public class DbEventExecutingArgument : IDbEventExecutingArgument
|
||||
{
|
||||
public ILogger Logger { get; }
|
||||
public DiscordSocketClient Client { get; }
|
||||
public string BotPrefix { get; }
|
||||
public DirectoryInfo PluginBaseDirectory { get; }
|
||||
|
||||
public DbEventExecutingArgument(ILogger logger, DiscordSocketClient client, string botPrefix, DirectoryInfo pluginBaseDirectory)
|
||||
{
|
||||
Logger = logger;
|
||||
Client = client;
|
||||
BotPrefix = botPrefix;
|
||||
PluginBaseDirectory = pluginBaseDirectory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace DiscordBotCore.PluginCore.Helpers.Execution.DbEvent;
|
||||
|
||||
public interface IDbEventExecutingArgument
|
||||
{
|
||||
public ILogger Logger { get; }
|
||||
public DiscordSocketClient Client { get; }
|
||||
public string BotPrefix { get; }
|
||||
public DirectoryInfo PluginBaseDirectory { get; }
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using DiscordBotCore.PluginCore.Helpers.Execution.DbCommand;
|
||||
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
namespace DiscordBotCore.PluginCore.Interfaces;
|
||||
|
||||
namespace PluginManager.Interfaces;
|
||||
|
||||
public interface DBCommand
|
||||
public interface IDbCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Command to be executed
|
||||
@@ -16,7 +13,7 @@ public interface DBCommand
|
||||
/// <summary>
|
||||
/// Command aliases. Users may use this to execute the command
|
||||
/// </summary>
|
||||
List<string>? Aliases { get; }
|
||||
List<string> Aliases { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Command description
|
||||
@@ -32,17 +29,17 @@ public interface DBCommand
|
||||
/// <summary>
|
||||
/// true if the command requre admin, otherwise false
|
||||
/// </summary>
|
||||
bool requireAdmin { get; }
|
||||
bool RequireAdmin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The main body of the command. This is what is executed when user calls the command in Server
|
||||
/// </summary>
|
||||
/// <param name="context">The disocrd Context</param>
|
||||
void ExecuteServer(SocketCommandContext context) { }
|
||||
/// <param name="args">The Discord Context</param>
|
||||
Task ExecuteServer(IDbCommandExecutingArgument args) => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// The main body of the command. This is what is executed when user calls the command in DM
|
||||
/// </summary>
|
||||
/// <param name="context">The disocrd Context</param>
|
||||
void ExecuteDM(SocketCommandContext context) { }
|
||||
/// <param name="args">The Discord Context</param>
|
||||
Task ExecuteDm(IDbCommandExecutingArgument args) => Task.CompletedTask;
|
||||
}
|
||||
22
DiscordBotCore.PluginCore/Interfaces/IDbEvent.cs
Normal file
22
DiscordBotCore.PluginCore/Interfaces/IDbEvent.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using DiscordBotCore.PluginCore.Helpers.Execution.DbEvent;
|
||||
|
||||
namespace DiscordBotCore.PluginCore.Interfaces;
|
||||
|
||||
public interface IDbEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the event
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The description of the event
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The method that is invoked when the event is loaded into memory
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments for the start method</param>
|
||||
void Start(IDbEventExecutingArgument args);
|
||||
}
|
||||
22
DiscordBotCore.PluginCore/Interfaces/IDbSlashCommand.cs
Normal file
22
DiscordBotCore.PluginCore/Interfaces/IDbSlashCommand.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace DiscordBotCore.PluginCore.Interfaces;
|
||||
|
||||
public interface IDbSlashCommand
|
||||
{
|
||||
string Name { get; }
|
||||
string Description { get; }
|
||||
bool CanUseDm { get; }
|
||||
bool HasInteraction { get; }
|
||||
|
||||
List<SlashCommandOptionBuilder> Options { get; }
|
||||
|
||||
void ExecuteServer(ILogger logger, SocketSlashCommand context)
|
||||
{ }
|
||||
|
||||
void ExecuteDm(ILogger logger, SocketSlashCommand context) { }
|
||||
|
||||
Task ExecuteInteraction(ILogger logger, SocketInteraction interaction) => Task.CompletedTask;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>AnyCPU;x64;ARM64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiscordBotCore.Configuration\DiscordBotCore.Configuration.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.Logging\DiscordBotCore.Logging.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.PluginCore\DiscordBotCore.PluginCore.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.PluginManagement\DiscordBotCore.PluginManagement.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace DiscordBotCore.PluginManagement.Loading.Exceptions;
|
||||
|
||||
public class PluginNotFoundException : Exception
|
||||
{
|
||||
public PluginNotFoundException(string pluginName) : base($"Plugin {pluginName} was not found") { }
|
||||
|
||||
public PluginNotFoundException(string pluginName, string url, string branch) :
|
||||
base ($"Plugin {pluginName} was not found on {url} (branch: {branch}") { }
|
||||
}
|
||||
27
DiscordBotCore.PluginManagement.Loading/IPluginLoader.cs
Normal file
27
DiscordBotCore.PluginManagement.Loading/IPluginLoader.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.PluginCore.Interfaces;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement.Loading;
|
||||
|
||||
public interface IPluginLoader
|
||||
{
|
||||
public IReadOnlyList<IDbCommand> Commands { get; }
|
||||
public IReadOnlyList<IDbEvent> Events { get; }
|
||||
public IReadOnlyList<IDbSlashCommand> SlashCommands { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Discord client for the plugin loader. This is used to initialize the slash commands and events.
|
||||
/// </summary>
|
||||
/// <param name="discordSocketClient">The socket client that represents the running Discord Bot</param>
|
||||
public void SetDiscordClient(DiscordSocketClient discordSocketClient);
|
||||
|
||||
/// <summary>
|
||||
/// Loads all the plugins that are installed.
|
||||
/// </summary>
|
||||
public Task LoadPlugins();
|
||||
|
||||
/// <summary>
|
||||
/// Unload all plugins from the plugin manager.
|
||||
/// </summary>
|
||||
public Task UnloadAllPlugins();
|
||||
}
|
||||
371
DiscordBotCore.PluginManagement.Loading/PluginLoader.cs
Normal file
371
DiscordBotCore.PluginManagement.Loading/PluginLoader.cs
Normal file
@@ -0,0 +1,371 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reflection;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Configuration;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.PluginCore.Helpers.Execution.DbEvent;
|
||||
using DiscordBotCore.PluginCore.Interfaces;
|
||||
using DiscordBotCore.PluginManagement.Loading.Exceptions;
|
||||
using DiscordBotCore.Utilities.Responses;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement.Loading;
|
||||
|
||||
public class PluginLoader : IPluginLoader
|
||||
{
|
||||
private static readonly string _HelpCommandNamespaceFullName = "DiscordBotCore.Commands.HelpCommand";
|
||||
|
||||
private readonly IPluginManager _PluginManager;
|
||||
private readonly ILogger _Logger;
|
||||
private readonly IConfiguration _Configuration;
|
||||
|
||||
private DiscordSocketClient? _DiscordClient;
|
||||
private PluginLoaderContext? PluginLoaderContext;
|
||||
|
||||
private readonly List<IDbCommand> _Commands = new List<IDbCommand>();
|
||||
private readonly List<IDbEvent> _Events = new List<IDbEvent>();
|
||||
private readonly List<IDbSlashCommand> _SlashCommands = new List<IDbSlashCommand>();
|
||||
private readonly List<SocketApplicationCommand> _ApplicationCommands = new List<SocketApplicationCommand>();
|
||||
|
||||
public PluginLoader(IPluginManager pluginManager, ILogger logger, IConfiguration configuration)
|
||||
{
|
||||
_PluginManager = pluginManager;
|
||||
_Logger = logger;
|
||||
_Configuration = configuration;
|
||||
}
|
||||
|
||||
public IReadOnlyList<IDbCommand> Commands => _Commands;
|
||||
public IReadOnlyList<IDbEvent> Events => _Events;
|
||||
public IReadOnlyList<IDbSlashCommand> SlashCommands => _SlashCommands;
|
||||
|
||||
public void SetDiscordClient(DiscordSocketClient discordSocketClient)
|
||||
{
|
||||
if (_DiscordClient is not null && discordSocketClient == _DiscordClient)
|
||||
{
|
||||
_Logger.Log("A client is already set. Please set the client only once.", this, LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (discordSocketClient.LoginState != LoginState.LoggedIn)
|
||||
{
|
||||
_Logger.Log("The client must be logged in before setting it.", this, LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
_DiscordClient = discordSocketClient;
|
||||
}
|
||||
|
||||
public async Task LoadPlugins()
|
||||
{
|
||||
if (PluginLoaderContext is not null)
|
||||
{
|
||||
_Logger.Log("The plugins are already loaded", this, LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
_Events.Clear();
|
||||
_Commands.Clear();
|
||||
_SlashCommands.Clear();
|
||||
_ApplicationCommands.Clear();
|
||||
|
||||
await LoadPluginFiles();
|
||||
|
||||
LoadEverythingOfType<IDbEvent>();
|
||||
var helpCommand = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.FirstOrDefault(assembly => assembly.DefinedTypes.Any(type => type.FullName == _HelpCommandNamespaceFullName)
|
||||
&& assembly.FullName != null
|
||||
&& assembly.FullName.StartsWith("DiscordBotCore"));
|
||||
|
||||
if (helpCommand is not null)
|
||||
{
|
||||
var helpCommandType = helpCommand.DefinedTypes.FirstOrDefault(type => type.FullName == _HelpCommandNamespaceFullName &&
|
||||
typeof(IDbCommand).IsAssignableFrom(type));
|
||||
if (helpCommandType is not null)
|
||||
{
|
||||
InitializeType<IDbCommand>(helpCommandType);
|
||||
}
|
||||
}
|
||||
|
||||
LoadEverythingOfType<IDbCommand>();
|
||||
LoadEverythingOfType<IDbSlashCommand>();
|
||||
|
||||
|
||||
_Logger.Log("Loaded plugins", this);
|
||||
}
|
||||
|
||||
public async Task UnloadAllPlugins()
|
||||
{
|
||||
if (PluginLoaderContext is null)
|
||||
{
|
||||
_Logger.Log("The plugins are not loaded. Please load the plugins before unloading them.", this, LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
await UnloadSlashCommands();
|
||||
|
||||
PluginLoaderContext.Unload();
|
||||
PluginLoaderContext = null;
|
||||
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private async Task UnloadSlashCommands()
|
||||
{
|
||||
if (_DiscordClient is null)
|
||||
{
|
||||
_Logger.Log("The client is not set. Please set the client before unloading slash commands.", this, LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (SocketApplicationCommand command in _ApplicationCommands)
|
||||
{
|
||||
await command.DeleteAsync();
|
||||
}
|
||||
|
||||
_ApplicationCommands.Clear();
|
||||
_Logger.Log("Unloaded all slash commands", this);
|
||||
}
|
||||
|
||||
private async Task LoadPluginFiles()
|
||||
{
|
||||
var installedPlugins = await _PluginManager.GetInstalledPlugins();
|
||||
|
||||
if (installedPlugins.Count == 0)
|
||||
{
|
||||
_Logger.Log("No plugin files found. Please check the plugin files.", this, LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var files = installedPlugins.Where(plugin => plugin.IsEnabled).Select(plugin => plugin.FilePath);
|
||||
|
||||
PluginLoaderContext = new PluginLoaderContext(_Logger, "PluginLoader");
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
string fullFilePath = Path.GetFullPath(file);
|
||||
if (string.IsNullOrEmpty(fullFilePath))
|
||||
{
|
||||
_Logger.Log("The file path is empty. Please check the plugin file path.", PluginLoaderContext, LogType.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!File.Exists(fullFilePath))
|
||||
{
|
||||
_Logger.Log("The file does not exist. Please check the plugin file path.", PluginLoaderContext, LogType.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
PluginLoaderContext.LoadFromAssemblyPath(fullFilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_Logger.LogException(ex, this);
|
||||
}
|
||||
}
|
||||
|
||||
_Logger.Log($"Loaded {PluginLoaderContext.Assemblies.Count()} assemblies", this);
|
||||
}
|
||||
|
||||
private void LoadEverythingOfType<T>()
|
||||
{
|
||||
if (PluginLoaderContext is null)
|
||||
{
|
||||
_Logger.Log("The plugins are not loaded. Please load the plugins before loading them.", this, LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var types = PluginLoaderContext.Assemblies
|
||||
.SelectMany(s => s.GetTypes())
|
||||
.Where(p => typeof(T).IsAssignableFrom(p) && !p.IsInterface);
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
InitializeType<T>(type);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeType<T>(Type type)
|
||||
{
|
||||
T? plugin = (T?)Activator.CreateInstance(type);
|
||||
if (plugin is null)
|
||||
{
|
||||
_Logger.Log($"Failed to create instance of plugin with type {type.FullName} [{type.Assembly}]", this, LogType.Error);
|
||||
}
|
||||
|
||||
switch (plugin)
|
||||
{
|
||||
case IDbEvent dbEvent:
|
||||
InitializeEvent(dbEvent);
|
||||
break;
|
||||
case IDbCommand dbCommand:
|
||||
InitializeDbCommand(dbCommand);
|
||||
break;
|
||||
case IDbSlashCommand dbSlashCommand:
|
||||
InitializeSlashCommand(dbSlashCommand);
|
||||
break;
|
||||
default:
|
||||
throw new PluginNotFoundException($"Unknown plugin type {plugin.GetType().FullName}");
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeDbCommand(IDbCommand command)
|
||||
{
|
||||
_Commands.Add(command);
|
||||
_Logger.Log("Command loaded: " + command.Command, this);
|
||||
}
|
||||
|
||||
private void InitializeEvent(IDbEvent eEvent)
|
||||
{
|
||||
if (!TryStartEvent(eEvent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_Events.Add(eEvent);
|
||||
_Logger.Log("Event loaded: " + eEvent, this);
|
||||
}
|
||||
|
||||
private async void InitializeSlashCommand(IDbSlashCommand slashCommand)
|
||||
{
|
||||
bool result = await TryStartSlashCommand(slashCommand);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_DiscordClient is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (slashCommand.HasInteraction)
|
||||
{
|
||||
_DiscordClient.InteractionCreated += interaction => slashCommand.ExecuteInteraction(_Logger, interaction);
|
||||
}
|
||||
|
||||
_SlashCommands.Add(slashCommand);
|
||||
_Logger.Log("Slash command loaded: " + slashCommand.Name, this);
|
||||
}
|
||||
|
||||
private bool TryStartEvent(IDbEvent dbEvent)
|
||||
{
|
||||
string? botPrefix = _Configuration.Get<string>("prefix");
|
||||
if (string.IsNullOrEmpty(botPrefix))
|
||||
{
|
||||
_Logger.Log("Bot prefix is not set. Please set the bot prefix in the configuration.", this, LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_DiscordClient is null)
|
||||
{
|
||||
_Logger.Log("Discord client is not set. Please set the discord client before starting events.", this, LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
string? resourcesFolder = _Configuration.Get<string>("ResourcesFolder");
|
||||
if (string.IsNullOrEmpty(resourcesFolder))
|
||||
{
|
||||
_Logger.Log("Resources folder is not set. Please set the resources folder in the configuration.", this, LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(resourcesFolder))
|
||||
{
|
||||
_Logger.Log("Resources folder does not exist. Please create the resources folder.", this, LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
string? eventConfigDirectory = Path.Combine(resourcesFolder, dbEvent.GetType().Assembly.GetName().Name);
|
||||
|
||||
Directory.CreateDirectory(eventConfigDirectory);
|
||||
|
||||
IDbEventExecutingArgument args = new DbEventExecutingArgument(
|
||||
_Logger,
|
||||
_DiscordClient,
|
||||
botPrefix,
|
||||
new DirectoryInfo(eventConfigDirectory));
|
||||
|
||||
dbEvent.Start(args);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> TryStartSlashCommand(IDbSlashCommand? dbSlashCommand)
|
||||
{
|
||||
if (dbSlashCommand is null)
|
||||
{
|
||||
_Logger.Log("The loaded slash command was null. Please check the plugin.", this, LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_DiscordClient is null)
|
||||
{
|
||||
_Logger.Log("The client is not set. Please set the client before starting slash commands.", this, LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_DiscordClient.Guilds.Count == 0)
|
||||
{
|
||||
_Logger.Log("The client is not connected to any guilds. Please check the client.", this, LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
var builder = new SlashCommandBuilder();
|
||||
builder.WithName(dbSlashCommand.Name);
|
||||
builder.WithDescription(dbSlashCommand.Description);
|
||||
builder.Options = dbSlashCommand.Options;
|
||||
|
||||
if (dbSlashCommand.CanUseDm)
|
||||
builder.WithContextTypes(InteractionContextType.BotDm, InteractionContextType.Guild);
|
||||
else
|
||||
builder.WithContextTypes(InteractionContextType.Guild);
|
||||
|
||||
List<ulong> serverIds = _Configuration.GetList("ServerIds", new List<ulong>());
|
||||
|
||||
if (serverIds.Any())
|
||||
{
|
||||
foreach(ulong guildId in serverIds)
|
||||
{
|
||||
IResponse<SocketApplicationCommand> result = await EnableSlashCommandPerGuild(guildId, builder);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
_Logger.Log($"Failed to enable slash command {dbSlashCommand.Name} for guild {guildId}", this, LogType.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.Data is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_ApplicationCommands.Add(result.Data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var command = await _DiscordClient.CreateGlobalApplicationCommandAsync(builder.Build());
|
||||
_ApplicationCommands.Add(command);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<IResponse<SocketApplicationCommand>> EnableSlashCommandPerGuild(ulong guildId, SlashCommandBuilder builder)
|
||||
{
|
||||
SocketGuild? guild = _DiscordClient?.GetGuild(guildId);
|
||||
if (guild is null)
|
||||
{
|
||||
_Logger.Log("Failed to get guild with ID " + guildId, this, LogType.Error);
|
||||
return Response<SocketApplicationCommand>.Failure("Failed to get guild with ID " + guildId);
|
||||
}
|
||||
|
||||
var command = await guild.CreateApplicationCommandAsync(builder.Build());
|
||||
return Response<SocketApplicationCommand>.Success(command);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement.Loading;
|
||||
|
||||
public class PluginLoaderContext : AssemblyLoadContext
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public PluginLoaderContext(ILogger logger, string name) : base(name: name, isCollectible: true)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override Assembly? Load(AssemblyName assemblyName)
|
||||
{
|
||||
//_logger.Log("Assembly load requested: " + assemblyName.Name, this);
|
||||
return base.Load(assemblyName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>AnyCPU;x64;ARM64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiscordBotCore.Configuration\DiscordBotCore.Configuration.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.Logging\DiscordBotCore.Logging.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.Networking\DiscordBotCore.Networking.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.Utilities\DiscordBotCore.Utilities.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
14
DiscordBotCore.PluginManagement/Helpers/IPluginRepository.cs
Normal file
14
DiscordBotCore.PluginManagement/Helpers/IPluginRepository.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using DiscordBotCore.PluginManagement.Models;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement.Helpers;
|
||||
|
||||
public interface IPluginRepository
|
||||
{
|
||||
public Task<List<OnlinePlugin>> GetAllPlugins(int operatingSystem, bool includeNotApproved);
|
||||
|
||||
public Task<OnlinePlugin?> GetPluginById(int pluginId);
|
||||
public Task<OnlinePlugin?> GetPluginByName(string pluginName, int operatingSystem, bool includeNotApproved);
|
||||
|
||||
public Task<List<OnlineDependencyInfo>> GetDependenciesForPlugin(int pluginId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace DiscordBotCore.PluginManagement.Helpers;
|
||||
|
||||
public interface IPluginRepositoryConfiguration
|
||||
{
|
||||
public string BaseUrl { get; }
|
||||
|
||||
public string PluginRepositoryLocation { get; }
|
||||
public string DependenciesRepositoryLocation { get; }
|
||||
}
|
||||
161
DiscordBotCore.PluginManagement/Helpers/PluginRepository.cs
Normal file
161
DiscordBotCore.PluginManagement/Helpers/PluginRepository.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using System.Net.Mime;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.PluginManagement.Models;
|
||||
using DiscordBotCore.Utilities;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement.Helpers;
|
||||
|
||||
public class PluginRepository : IPluginRepository
|
||||
{
|
||||
private readonly IPluginRepositoryConfiguration _pluginRepositoryConfiguration;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public PluginRepository(IPluginRepositoryConfiguration pluginRepositoryConfiguration, ILogger logger)
|
||||
{
|
||||
_pluginRepositoryConfiguration = pluginRepositoryConfiguration;
|
||||
_httpClient = new HttpClient();
|
||||
_httpClient.BaseAddress = new Uri(_pluginRepositoryConfiguration.BaseUrl);
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<List<OnlinePlugin>> GetAllPlugins(int operatingSystem, bool includeNotApproved)
|
||||
{
|
||||
string url = CreateUrlWithQueryParams(_pluginRepositoryConfiguration.PluginRepositoryLocation,
|
||||
"get-all-plugins", new Dictionary<string, string>
|
||||
{
|
||||
{ "operatingSystem", operatingSystem.ToString() },
|
||||
{ "includeNotApproved", includeNotApproved.ToString() }
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await _httpClient.GetAsync(url);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
List<OnlinePlugin> plugins = await JsonManager.ConvertFromJson<List<OnlinePlugin>>(content);
|
||||
|
||||
return plugins;
|
||||
}
|
||||
catch (HttpRequestException exception)
|
||||
{
|
||||
_logger.LogException(exception,this);
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task<OnlinePlugin?> GetPluginById(int pluginId)
|
||||
{
|
||||
string url = CreateUrlWithQueryParams(_pluginRepositoryConfiguration.PluginRepositoryLocation,
|
||||
"get-by-id", new Dictionary<string, string>
|
||||
{
|
||||
{ "pluginId", pluginId.ToString() },
|
||||
{ "includeNotApproved", "false" }
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await _httpClient.GetAsync(url);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
OnlinePlugin plugin = await JsonManager.ConvertFromJson<OnlinePlugin>(content);
|
||||
|
||||
return plugin;
|
||||
}
|
||||
catch (HttpRequestException exception)
|
||||
{
|
||||
_logger.LogException(exception, this);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task<OnlinePlugin?> GetPluginByName(string pluginName, int operatingSystem, bool includeNotApproved)
|
||||
{
|
||||
string url = CreateUrlWithQueryParams(_pluginRepositoryConfiguration.PluginRepositoryLocation,
|
||||
"get-by-name", new Dictionary<string, string>
|
||||
{
|
||||
{ "pluginName", pluginName },
|
||||
{ "operatingSystem", operatingSystem.ToString() },
|
||||
{ "includeNotApproved", includeNotApproved.ToString() }
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await _httpClient.GetAsync(url);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.Log($"Plugin {pluginName} not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
OnlinePlugin plugin = await JsonManager.ConvertFromJson<OnlinePlugin>(content);
|
||||
|
||||
return plugin;
|
||||
}
|
||||
catch (HttpRequestException exception)
|
||||
{
|
||||
_logger.LogException(exception, this);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task<List<OnlineDependencyInfo>> GetDependenciesForPlugin(int pluginId)
|
||||
{
|
||||
string url = CreateUrlWithQueryParams(_pluginRepositoryConfiguration.DependenciesRepositoryLocation,
|
||||
"get-by-plugin-id", new Dictionary<string, string>
|
||||
{
|
||||
{ "pluginId", pluginId.ToString() }
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await _httpClient.GetAsync(url);
|
||||
if(!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.Log($"Failed to get dependencies for plugin with ID {pluginId}");
|
||||
return [];
|
||||
}
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
List<OnlineDependencyInfo> dependencies = await JsonManager.ConvertFromJson<List<OnlineDependencyInfo>>(content);
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
catch(HttpRequestException exception)
|
||||
{
|
||||
_logger.LogException(exception, this);
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private string CreateUrlWithQueryParams(string baseUrl, string endpoint, Dictionary<string, string> queryParams)
|
||||
{
|
||||
QueryBuilder queryBuilder = new QueryBuilder();
|
||||
foreach (var(key,value) in queryParams)
|
||||
{
|
||||
queryBuilder.Add(key, value);
|
||||
}
|
||||
|
||||
string queryString = queryBuilder.ToQueryString().ToString();
|
||||
string url = baseUrl + endpoint + queryString;
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement.Helpers;
|
||||
|
||||
public class PluginRepositoryConfiguration : IPluginRepositoryConfiguration
|
||||
{
|
||||
public static PluginRepositoryConfiguration Default => new ("http://localhost:8080/api/v1/",
|
||||
"plugin/",
|
||||
"dependency/");
|
||||
|
||||
public string BaseUrl { get; }
|
||||
public string PluginRepositoryLocation { get; }
|
||||
public string DependenciesRepositoryLocation { get; }
|
||||
|
||||
[JsonConstructor]
|
||||
public PluginRepositoryConfiguration(string baseUrl, string pluginRepositoryLocation, string dependenciesRepositoryLocation)
|
||||
{
|
||||
BaseUrl = baseUrl;
|
||||
PluginRepositoryLocation = pluginRepositoryLocation;
|
||||
DependenciesRepositoryLocation = dependenciesRepositoryLocation;
|
||||
}
|
||||
}
|
||||
20
DiscordBotCore.PluginManagement/IPluginManager.cs
Normal file
20
DiscordBotCore.PluginManagement/IPluginManager.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using DiscordBotCore.PluginManagement.Models;
|
||||
using DiscordBotCore.Utilities.Responses;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement;
|
||||
|
||||
public interface IPluginManager
|
||||
{
|
||||
Task<List<OnlinePlugin>> GetPluginsList();
|
||||
Task<IResponse<OnlinePlugin>> GetPluginDataByName(string pluginName);
|
||||
Task<IResponse<OnlinePlugin>> GetPluginDataById(int pluginId);
|
||||
Task<IResponse<bool>> AppendPluginToDatabase(LocalPlugin pluginData);
|
||||
Task<List<LocalPlugin>> GetInstalledPlugins();
|
||||
Task<IResponse<string>> GetDependencyLocation(string dependencyName);
|
||||
Task<IResponse<string>> GetDependencyLocation(string dependencyName, string pluginName);
|
||||
string GenerateDependencyRelativePath(string pluginName, string dependencyPath);
|
||||
Task<IResponse<bool>> InstallPlugin(OnlinePlugin plugin, IProgress<float> progress);
|
||||
Task SetEnabledStatus(string pluginName, bool status);
|
||||
Task<IResponse<bool>> UninstallPluginByName(string pluginName);
|
||||
Task<IResponse<LocalPlugin>> GetLocalPluginByName(string pluginName);
|
||||
}
|
||||
46
DiscordBotCore.PluginManagement/Models/LocalPlugin.cs
Normal file
46
DiscordBotCore.PluginManagement/Models/LocalPlugin.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement.Models;
|
||||
|
||||
public class LocalPlugin
|
||||
{
|
||||
public string PluginName { get; private set; }
|
||||
public string PluginVersion { get; private set; }
|
||||
public string FilePath { get; private set; }
|
||||
public Dictionary<string, string> ListOfExecutableDependencies {get; private set;}
|
||||
public bool IsOfflineAdded { get; internal set; }
|
||||
public bool IsEnabled { get; internal set; }
|
||||
|
||||
[JsonConstructor]
|
||||
public LocalPlugin(string pluginName, string pluginVersion, string filePath, Dictionary<string, string> listOfExecutableDependencies, bool isOfflineAdded, bool isEnabled)
|
||||
{
|
||||
PluginName = pluginName;
|
||||
PluginVersion = pluginVersion;
|
||||
ListOfExecutableDependencies = listOfExecutableDependencies;
|
||||
FilePath = filePath;
|
||||
IsOfflineAdded = isOfflineAdded;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
private LocalPlugin(string pluginName, string pluginVersion, string filePath,
|
||||
Dictionary<string, string> listOfExecutableDependencies)
|
||||
{
|
||||
PluginName = pluginName;
|
||||
PluginVersion = pluginVersion;
|
||||
ListOfExecutableDependencies = listOfExecutableDependencies;
|
||||
FilePath = filePath;
|
||||
IsOfflineAdded = false;
|
||||
IsEnabled = true;
|
||||
}
|
||||
|
||||
public static LocalPlugin FromOnlineInfo(OnlinePlugin plugin, List<OnlineDependencyInfo> dependencies, string downloadLocation)
|
||||
{
|
||||
LocalPlugin localPlugin = new LocalPlugin(
|
||||
plugin.Name, plugin.Version, downloadLocation,
|
||||
dependencies.Where(dependency => dependency.IsExecutable)
|
||||
.ToDictionary(dependency => dependency.DependencyName, dependency => dependency.DownloadLocation)
|
||||
);
|
||||
|
||||
return localPlugin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement.Models;
|
||||
|
||||
public class OnlineDependencyInfo
|
||||
{
|
||||
public string DependencyName { get; private set; }
|
||||
[JsonPropertyName("dependencyLink")]
|
||||
public string DownloadLink { get; private set; }
|
||||
[JsonPropertyName("dependencyLocation")]
|
||||
public string DownloadLocation { get; private set; }
|
||||
public bool IsExecutable { get; private set; }
|
||||
|
||||
[JsonConstructor]
|
||||
public OnlineDependencyInfo(string dependencyName, string downloadLink, string downloadLocation, bool isExecutable)
|
||||
{
|
||||
DependencyName = dependencyName;
|
||||
DownloadLink = downloadLink;
|
||||
DownloadLocation = downloadLocation;
|
||||
IsExecutable = isExecutable;
|
||||
}
|
||||
}
|
||||
29
DiscordBotCore.PluginManagement/Models/OnlinePlugin.cs
Normal file
29
DiscordBotCore.PluginManagement/Models/OnlinePlugin.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement.Models;
|
||||
|
||||
public class OnlinePlugin
|
||||
{
|
||||
public int Id { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public string Description { get; private set; }
|
||||
public string Version { get; private set; }
|
||||
public string Author { get; private set; }
|
||||
public string DownloadLink { get; private set; }
|
||||
public int OperatingSystem { get; private set; }
|
||||
public bool IsApproved { get; private set; }
|
||||
|
||||
[JsonConstructor]
|
||||
public OnlinePlugin(int id, string name, string description, string version,
|
||||
string author, string downloadLink, int operatingSystem, bool isApproved)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Description = description;
|
||||
Version = version;
|
||||
Author = author;
|
||||
DownloadLink = downloadLink;
|
||||
OperatingSystem = operatingSystem;
|
||||
IsApproved = isApproved;
|
||||
}
|
||||
}
|
||||
316
DiscordBotCore.PluginManagement/PluginManager.cs
Normal file
316
DiscordBotCore.PluginManagement/PluginManager.cs
Normal file
@@ -0,0 +1,316 @@
|
||||
using System.Diagnostics;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.Networking;
|
||||
using DiscordBotCore.PluginManagement.Helpers;
|
||||
using DiscordBotCore.PluginManagement.Models;
|
||||
using DiscordBotCore.Utilities;
|
||||
using DiscordBotCore.Configuration;
|
||||
using DiscordBotCore.Utilities.Responses;
|
||||
using OperatingSystem = DiscordBotCore.Utilities.OperatingSystem;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement;
|
||||
|
||||
public sealed class PluginManager : IPluginManager
|
||||
{
|
||||
private static readonly string _LibrariesBaseFolder = "Libraries";
|
||||
private readonly IPluginRepository _PluginRepository;
|
||||
private readonly ILogger _Logger;
|
||||
private readonly IConfiguration _Configuration;
|
||||
|
||||
public PluginManager(IPluginRepository pluginRepository, ILogger logger, IConfiguration configuration)
|
||||
{
|
||||
_PluginRepository = pluginRepository;
|
||||
_Logger = logger;
|
||||
_Configuration = configuration;
|
||||
}
|
||||
|
||||
public async Task<List<OnlinePlugin>> GetPluginsList()
|
||||
{
|
||||
int os = OperatingSystem.GetOperatingSystemInt();
|
||||
var onlinePlugins = await _PluginRepository.GetAllPlugins(os, false);
|
||||
|
||||
if (!onlinePlugins.Any())
|
||||
{
|
||||
_Logger.Log($"No plugins found for operatingSystem: {OperatingSystem.GetOperatingSystemString((OperatingSystem.OperatingSystemEnum)os)}", LogType.Warning);
|
||||
return [];
|
||||
}
|
||||
|
||||
return onlinePlugins;
|
||||
}
|
||||
|
||||
public async Task<IResponse<OnlinePlugin>> GetPluginDataByName(string pluginName)
|
||||
{
|
||||
int os = OperatingSystem.GetOperatingSystemInt();
|
||||
var plugin = await _PluginRepository.GetPluginByName(pluginName, os, false);
|
||||
|
||||
if (plugin is null)
|
||||
{
|
||||
return Response<OnlinePlugin>.Failure($"Plugin {pluginName} not found in the repository for operating system {OperatingSystem.GetOperatingSystemString((OperatingSystem.OperatingSystemEnum)os)}.");
|
||||
}
|
||||
|
||||
return Response<OnlinePlugin>.Success(plugin);
|
||||
}
|
||||
|
||||
public async Task<IResponse<OnlinePlugin>> GetPluginDataById(int pluginId)
|
||||
{
|
||||
var plugin = await _PluginRepository.GetPluginById(pluginId);
|
||||
if (plugin is null)
|
||||
{
|
||||
return Response<OnlinePlugin>.Failure($"Plugin {pluginId} not found in the repository.");
|
||||
}
|
||||
|
||||
return Response<OnlinePlugin>.Success(plugin);
|
||||
}
|
||||
|
||||
private async Task<IResponse<bool>> RemovePluginFromDatabase(string pluginName)
|
||||
{
|
||||
string? pluginDatabaseFile = _Configuration.Get<string>("PluginDatabase");
|
||||
|
||||
if (pluginDatabaseFile is null)
|
||||
{
|
||||
return Response.Failure("PluginDatabase file path is not present in the config file");
|
||||
}
|
||||
|
||||
List<LocalPlugin> installedPlugins = await JsonManager.ConvertFromJson<List<LocalPlugin>>(await File.ReadAllTextAsync(pluginDatabaseFile));
|
||||
|
||||
installedPlugins.RemoveAll(p => p.PluginName == pluginName);
|
||||
await JsonManager.SaveToJsonFile(pluginDatabaseFile, installedPlugins);
|
||||
|
||||
return Response.Success();
|
||||
}
|
||||
|
||||
public async Task<IResponse<bool>> AppendPluginToDatabase(LocalPlugin pluginData)
|
||||
{
|
||||
string? pluginDatabaseFile = _Configuration.Get<string>("PluginDatabase");
|
||||
if (pluginDatabaseFile is null)
|
||||
{
|
||||
return Response.Failure("PluginDatabase file path is not present in the config file");
|
||||
}
|
||||
|
||||
List<LocalPlugin> installedPlugins = await GetInstalledPlugins();
|
||||
|
||||
foreach (var dependency in pluginData.ListOfExecutableDependencies)
|
||||
{
|
||||
pluginData.ListOfExecutableDependencies[dependency.Key] = dependency.Value;
|
||||
}
|
||||
|
||||
if (installedPlugins.Any(plugin => plugin.PluginName == pluginData.PluginName))
|
||||
{
|
||||
_Logger.Log($"Plugin {pluginData.PluginName} already exists in the database. Updating...", this, LogType.Info);
|
||||
installedPlugins.RemoveAll(p => p.PluginName == pluginData.PluginName);
|
||||
}
|
||||
|
||||
installedPlugins.Add(pluginData);
|
||||
await JsonManager.SaveToJsonFile(pluginDatabaseFile, installedPlugins);
|
||||
|
||||
return Response.Success();
|
||||
}
|
||||
|
||||
public async Task<List<LocalPlugin>> GetInstalledPlugins()
|
||||
{
|
||||
string? pluginDatabaseFile = _Configuration.Get<string>("PluginDatabase");
|
||||
if (pluginDatabaseFile is null)
|
||||
{
|
||||
_Logger.Log("Plugin database file path is not present in the config file", this, LogType.Warning);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!File.Exists(pluginDatabaseFile))
|
||||
{
|
||||
_Logger.Log("Plugin database file not found", this, LogType.Warning);
|
||||
await CreateEmptyPluginDatabase();
|
||||
return [];
|
||||
}
|
||||
|
||||
return await JsonManager.ConvertFromJson<List<LocalPlugin>>(await File.ReadAllTextAsync(pluginDatabaseFile));
|
||||
}
|
||||
|
||||
public async Task<IResponse<string>> GetDependencyLocation(string dependencyName)
|
||||
{
|
||||
List<LocalPlugin> installedPlugins = await GetInstalledPlugins();
|
||||
|
||||
foreach (var plugin in installedPlugins)
|
||||
{
|
||||
if (plugin.ListOfExecutableDependencies.TryGetValue(dependencyName, out var dependencyPath))
|
||||
{
|
||||
string relativePath = GenerateDependencyRelativePath(plugin.PluginName, dependencyPath);
|
||||
return Response<string>.Success(relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
return Response<string>.Failure($"Dependency {dependencyName} not found in the installed plugins.");
|
||||
}
|
||||
|
||||
public async Task<IResponse<string>> GetDependencyLocation(string dependencyName, string pluginName)
|
||||
{
|
||||
List<LocalPlugin> installedPlugins = await GetInstalledPlugins();
|
||||
|
||||
foreach (var plugin in installedPlugins)
|
||||
{
|
||||
if (plugin.PluginName == pluginName && plugin.ListOfExecutableDependencies.ContainsKey(dependencyName))
|
||||
{
|
||||
string dependencyPath = plugin.ListOfExecutableDependencies[dependencyName];
|
||||
string relativePath = GenerateDependencyRelativePath(pluginName, dependencyPath);
|
||||
return Response<string>.Success(relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
return Response<string>.Failure($"Dependency {dependencyName} not found in the installed plugins.");
|
||||
}
|
||||
|
||||
public string GenerateDependencyRelativePath(string pluginName, string dependencyPath)
|
||||
{
|
||||
string relative = $"./{_LibrariesBaseFolder}/{pluginName}/{dependencyPath}";
|
||||
return relative;
|
||||
}
|
||||
|
||||
public async Task<IResponse<bool>> InstallPlugin(OnlinePlugin plugin, IProgress<float> progress)
|
||||
{
|
||||
string? pluginsFolder = _Configuration.Get<string>("PluginFolder");
|
||||
if (pluginsFolder is null)
|
||||
{
|
||||
return Response.Failure("Plugin folder path is not present in the config file");
|
||||
}
|
||||
|
||||
var localPluginResponse = await GetLocalPluginByName(plugin.Name);
|
||||
if (localPluginResponse is { IsSuccess: true, Data: not null })
|
||||
{
|
||||
var response = await IsNewVersion(localPluginResponse.Data.PluginVersion, plugin.Version);
|
||||
if (!response.IsSuccess)
|
||||
{
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
List<OnlineDependencyInfo> dependencies = await _PluginRepository.GetDependenciesForPlugin(plugin.Id);
|
||||
|
||||
string downloadLocation = $"{pluginsFolder}/{plugin.Name}.dll";
|
||||
|
||||
IProgress<float> downloadProgress = new Progress<float>(progress.Report);
|
||||
|
||||
FileDownloader fileDownloader = new FileDownloader(plugin.DownloadLink, downloadLocation);
|
||||
await fileDownloader.DownloadFile(downloadProgress.Report);
|
||||
|
||||
ParallelDownloadExecutor executor = new ParallelDownloadExecutor();
|
||||
|
||||
foreach (var dependency in dependencies)
|
||||
{
|
||||
string dependencyLocation = GenerateDependencyRelativePath(plugin.Name, dependency.DownloadLocation);
|
||||
|
||||
executor.AddTask(dependency.DownloadLink, dependencyLocation, progress.Report);
|
||||
}
|
||||
|
||||
await executor.ExecuteAllTasks();
|
||||
|
||||
LocalPlugin localPlugin = LocalPlugin.FromOnlineInfo(plugin, dependencies, downloadLocation);
|
||||
var result = await AppendPluginToDatabase(localPlugin);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task SetEnabledStatus(string pluginName, bool status)
|
||||
{
|
||||
var plugins = await GetInstalledPlugins();
|
||||
var plugin = plugins.Find(p => p.PluginName == pluginName);
|
||||
|
||||
if (plugin == null)
|
||||
return;
|
||||
|
||||
plugin.IsEnabled = status;
|
||||
|
||||
await RemovePluginFromDatabase(pluginName);
|
||||
await AppendPluginToDatabase(plugin);
|
||||
|
||||
}
|
||||
|
||||
public async Task<IResponse<bool>> UninstallPluginByName(string pluginName)
|
||||
{
|
||||
var localPluginResponse = await GetLocalPluginByName(pluginName);
|
||||
if (!localPluginResponse.IsSuccess)
|
||||
{
|
||||
return Response.Failure(localPluginResponse.Message);
|
||||
}
|
||||
|
||||
var localPlugin = localPluginResponse.Data;
|
||||
|
||||
if (localPlugin is null)
|
||||
{
|
||||
return Response.Failure($"Plugin {pluginName} not found in the database");
|
||||
}
|
||||
|
||||
File.Delete(localPlugin.FilePath);
|
||||
|
||||
if (Directory.Exists($"./{_LibrariesBaseFolder}/{pluginName}"))
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles($"./{_LibrariesBaseFolder}/{pluginName}"))
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
|
||||
var response = await RemovePluginFromDatabase(pluginName);
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<IResponse<LocalPlugin>> GetLocalPluginByName(string pluginName)
|
||||
{
|
||||
List<LocalPlugin> installedPlugins = await GetInstalledPlugins();
|
||||
var plugin = installedPlugins.Find(p => p.PluginName == pluginName);
|
||||
|
||||
if (plugin is null)
|
||||
{
|
||||
return Response<LocalPlugin>.Failure($"Plugin {pluginName} not found in the database");
|
||||
}
|
||||
|
||||
return Response<LocalPlugin>.Success(plugin);
|
||||
}
|
||||
|
||||
private async Task<IResponse<bool>> IsNewVersion(string currentVersion, string newVersion)
|
||||
{
|
||||
// currentVersion = "1.0.0"
|
||||
// newVersion = "1.0.1"
|
||||
|
||||
var currentVersionParts = currentVersion.Split('.').Select(int.Parse).ToArray();
|
||||
var newVersionParts = newVersion.Split('.').Select(int.Parse).ToArray();
|
||||
|
||||
if (currentVersionParts.Length != 3 || newVersionParts.Length != 3)
|
||||
{
|
||||
return Response.Failure("Invalid version format");
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (newVersionParts[i] > currentVersionParts[i])
|
||||
{
|
||||
return Response.Success();
|
||||
}
|
||||
else if (newVersionParts[i] < currentVersionParts[i])
|
||||
{
|
||||
return Response.Failure("Current version is newer");
|
||||
}
|
||||
}
|
||||
|
||||
return Response.Failure("Versions are the same");
|
||||
}
|
||||
|
||||
private async Task<bool> CreateEmptyPluginDatabase()
|
||||
{
|
||||
string ? pluginDatabaseFile = _Configuration.Get<string>("PluginDatabase");
|
||||
if (pluginDatabaseFile is null)
|
||||
{
|
||||
_Logger.Log("Plugin database file path is not present in the config file", this, LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (File.Exists(pluginDatabaseFile))
|
||||
{
|
||||
_Logger.Log("Plugin database file already exists", this, LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
List<LocalPlugin> installedPlugins = new List<LocalPlugin>();
|
||||
await JsonManager.SaveToJsonFile(pluginDatabaseFile, installedPlugins);
|
||||
_Logger.Log("Plugin database file created", this, LogType.Info);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
189
DiscordBotCore.Utilities/ArchiveManager.cs
Normal file
189
DiscordBotCore.Utilities/ArchiveManager.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System.IO.Compression;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.Configuration;
|
||||
|
||||
namespace DiscordBotCore.Utilities;
|
||||
|
||||
public class ArchiveManager
|
||||
{
|
||||
private readonly ILogger _Logger;
|
||||
private readonly IConfiguration _Configuration;
|
||||
|
||||
public ArchiveManager(ILogger logger, IConfiguration configuration)
|
||||
{
|
||||
_Logger = logger;
|
||||
_Configuration = configuration;
|
||||
}
|
||||
|
||||
public void CreateFromFile(string file, string folder)
|
||||
{
|
||||
if (!Directory.Exists(folder))
|
||||
Directory.CreateDirectory(folder);
|
||||
|
||||
var archiveName = folder + Path.GetFileNameWithoutExtension(file) + ".zip";
|
||||
if (File.Exists(archiveName))
|
||||
File.Delete(archiveName);
|
||||
|
||||
using ZipArchive archive = ZipFile.Open(archiveName, ZipArchiveMode.Create);
|
||||
archive.CreateEntryFromFile(file, Path.GetFileName(file));
|
||||
}
|
||||
|
||||
/// <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 Task<byte[]?> ReadAllBytes(string fileName, string archName)
|
||||
{
|
||||
string? archiveFolderBasePath = _Configuration.Get<string>("ArchiveFolder");
|
||||
if(archiveFolderBasePath is null)
|
||||
throw new Exception("Archive folder not found");
|
||||
|
||||
Directory.CreateDirectory(archiveFolderBasePath);
|
||||
|
||||
archName = Path.Combine(archiveFolderBasePath, archName);
|
||||
|
||||
if (!File.Exists(archName))
|
||||
throw new Exception("Failed to load file !");
|
||||
|
||||
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");
|
||||
|
||||
await using var memoryStream = new MemoryStream();
|
||||
var stream = entry.Open();
|
||||
await stream.CopyToAsync(memoryStream);
|
||||
var data = memoryStream.ToArray();
|
||||
|
||||
stream.Close();
|
||||
memoryStream.Close();
|
||||
|
||||
Console.WriteLine("Read file from archive: " + fileName);
|
||||
Console.WriteLine("Size: " + data.Length);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read data from a file that is inside an archive (ZIP format)
|
||||
/// </summary>
|
||||
/// <param name="fileName">The file name that is inside the archive or its full path</param>
|
||||
/// <param name="archFile">The archive location from the PAKs folder</param>
|
||||
/// <returns>A string that represents the content of the file or null if the file does not exists or it has no content</returns>
|
||||
public async Task<string?> ReadFromPakAsync(string fileName, string archFile)
|
||||
{
|
||||
string? archiveFolderBasePath = _Configuration.Get<string>("ArchiveFolder");
|
||||
if(archiveFolderBasePath is null)
|
||||
throw new Exception("Archive folder not found");
|
||||
|
||||
Directory.CreateDirectory(archiveFolderBasePath);
|
||||
|
||||
archFile = Path.Combine(archiveFolderBasePath, archFile);
|
||||
|
||||
if (!File.Exists(archFile))
|
||||
throw new Exception("Failed to load file !");
|
||||
|
||||
try
|
||||
{
|
||||
string? textValue = null;
|
||||
using (var fs = new FileStream(archFile, FileMode.Open))
|
||||
using (var zip = new ZipArchive(fs, ZipArchiveMode.Read))
|
||||
{
|
||||
foreach (var entry in zip.Entries)
|
||||
if (entry.Name == fileName || entry.FullName == fileName)
|
||||
using (var s = entry.Open())
|
||||
using (var reader = new StreamReader(s))
|
||||
{
|
||||
textValue = await reader.ReadToEndAsync();
|
||||
reader.Close();
|
||||
s.Close();
|
||||
fs.Close();
|
||||
}
|
||||
}
|
||||
|
||||
return textValue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_Logger.LogException(ex, this);
|
||||
await Task.Delay(100);
|
||||
return await ReadFromPakAsync(fileName, archFile);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract zip to location
|
||||
/// </summary>
|
||||
/// <param name="zip">The zip location</param>
|
||||
/// <param name="folder">The target location</param>
|
||||
/// <param name="progress">The progress that is updated as a file is processed</param>
|
||||
/// <param name="type">The type of progress</param>
|
||||
/// <returns></returns>
|
||||
public async Task ExtractArchive(
|
||||
string zip, string folder, IProgress<float> progress,
|
||||
UnzipProgressType type)
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
using var archive = ZipFile.OpenRead(zip);
|
||||
var totalZipFiles = archive.Entries.Count();
|
||||
if (type == UnzipProgressType.PercentageFromNumberOfFiles)
|
||||
{
|
||||
var currentZipFile = 0;
|
||||
foreach (var entry in archive.Entries)
|
||||
{
|
||||
if (entry.FullName.EndsWith("/")) // it is a folder
|
||||
Directory.CreateDirectory(Path.Combine(folder, entry.FullName));
|
||||
|
||||
else
|
||||
try
|
||||
{
|
||||
entry.ExtractToFile(Path.Combine(folder, entry.FullName), true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_Logger.LogException(ex, this);
|
||||
}
|
||||
|
||||
currentZipFile++;
|
||||
await Task.Delay(10);
|
||||
if (progress != null)
|
||||
progress.Report((float)currentZipFile / totalZipFiles * 100);
|
||||
}
|
||||
}
|
||||
else if (type == UnzipProgressType.PercentageFromTotalSize)
|
||||
{
|
||||
ulong zipSize = 0;
|
||||
|
||||
foreach (var entry in archive.Entries)
|
||||
zipSize += (ulong)entry.CompressedLength;
|
||||
|
||||
ulong currentSize = 0;
|
||||
foreach (var entry in archive.Entries)
|
||||
{
|
||||
if (entry.FullName.EndsWith("/"))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(folder, entry.FullName));
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string path = Path.Combine(folder, entry.FullName);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
entry.ExtractToFile(path, true);
|
||||
currentSize += (ulong)entry.CompressedLength;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_Logger.LogException(ex, this);
|
||||
}
|
||||
|
||||
await Task.Delay(10);
|
||||
if (progress != null)
|
||||
progress.Report((float)currentSize / zipSize * 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
DiscordBotCore.Utilities/DiscordBotCore.Utilities.csproj
Normal file
15
DiscordBotCore.Utilities/DiscordBotCore.Utilities.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>AnyCPU;x64;ARM64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiscordBotCore.Configuration\DiscordBotCore.Configuration.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.Logging\DiscordBotCore.Logging.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
102
DiscordBotCore.Utilities/JsonManager.cs
Normal file
102
DiscordBotCore.Utilities/JsonManager.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace DiscordBotCore.Utilities;
|
||||
|
||||
public static class JsonManager
|
||||
{
|
||||
|
||||
public static async Task<string> ConvertToJson<T>(List<T> data, string[] propertyNamesToUse)
|
||||
{
|
||||
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||
if (propertyNamesToUse == null) throw new ArgumentNullException(nameof(propertyNamesToUse));
|
||||
|
||||
// Use reflection to filter properties dynamically
|
||||
var filteredData = data.Select(item =>
|
||||
{
|
||||
if (item == null) return null;
|
||||
|
||||
var type = typeof(T);
|
||||
var propertyInfos = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
// Create a dictionary with specified properties and their values
|
||||
var selectedProperties = propertyInfos
|
||||
.Where(p => propertyNamesToUse.Contains(p.Name))
|
||||
.ToDictionary(p => p.Name, p => p.GetValue(item));
|
||||
|
||||
return selectedProperties;
|
||||
}).ToList();
|
||||
|
||||
// Serialize the filtered data to JSON
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true, // For pretty-print JSON; remove if not needed
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
return await Task.FromResult(JsonSerializer.Serialize(filteredData, options));
|
||||
}
|
||||
|
||||
public static async Task<string> ConvertToJsonString<T>(T Data)
|
||||
{
|
||||
var str = new MemoryStream();
|
||||
await JsonSerializer.SerializeAsync(str, Data, typeof(T), new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = false,
|
||||
});
|
||||
var result = Encoding.ASCII.GetString(str.ToArray());
|
||||
await str.FlushAsync();
|
||||
str.Close();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
JsonSerializerOptions options = new JsonSerializerOptions()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
var obj = await JsonSerializer.DeserializeAsync<T>(text, options);
|
||||
await text.FlushAsync();
|
||||
text.Close();
|
||||
|
||||
return (obj ?? default)!;
|
||||
}
|
||||
}
|
||||
132
DiscordBotCore.Utilities/OneOf.cs
Normal file
132
DiscordBotCore.Utilities/OneOf.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
namespace DiscordBotCore.Utilities;
|
||||
|
||||
public class OneOf<T0, T1>
|
||||
{
|
||||
public T0 Item0 { get; }
|
||||
public T1 Item1 { get; }
|
||||
|
||||
public object? Value => Item0 != null ? Item0 : Item1;
|
||||
|
||||
public OneOf(T0 item0)
|
||||
{
|
||||
Item0 = item0;
|
||||
}
|
||||
|
||||
public OneOf(T1 item1)
|
||||
{
|
||||
Item1 = item1;
|
||||
}
|
||||
|
||||
public static implicit operator OneOf<T0, T1>(T0 item0) => new OneOf<T0, T1>(item0);
|
||||
public static implicit operator OneOf<T0, T1>(T1 item1) => new OneOf<T0, T1>(item1);
|
||||
|
||||
public void Match(Action<T0> item0, Action<T1> item1)
|
||||
{
|
||||
if (Item0 != null)
|
||||
item0(Item0);
|
||||
else
|
||||
item1(Item1);
|
||||
}
|
||||
|
||||
public TResult Match<TResult>(Func<T0, TResult> item0, Func<T1, TResult> item1)
|
||||
{
|
||||
return Item0 != null ? item0(Item0) : item1(Item1);
|
||||
}
|
||||
|
||||
public Type GetActualType()
|
||||
{
|
||||
return Item0 != null ? Item0.GetType() : Item1.GetType();
|
||||
}
|
||||
}
|
||||
|
||||
public class OneOf<T0, T1, T2>
|
||||
{
|
||||
public T0 Item0 { get; }
|
||||
public T1 Item1 { get; }
|
||||
public T2 Item2 { get; }
|
||||
|
||||
public OneOf(T0 item0)
|
||||
{
|
||||
Item0 = item0;
|
||||
}
|
||||
|
||||
public OneOf(T1 item1)
|
||||
{
|
||||
Item1 = item1;
|
||||
}
|
||||
|
||||
public OneOf(T2 item2)
|
||||
{
|
||||
Item2 = item2;
|
||||
}
|
||||
|
||||
public static implicit operator OneOf<T0, T1, T2>(T0 item0) => new OneOf<T0, T1, T2>(item0);
|
||||
public static implicit operator OneOf<T0, T1, T2>(T1 item1) => new OneOf<T0, T1, T2>(item1);
|
||||
public static implicit operator OneOf<T0, T1, T2>(T2 item2) => new OneOf<T0, T1, T2>(item2);
|
||||
|
||||
public void Match(Action<T0> item0, Action<T1> item1, Action<T2> item2)
|
||||
{
|
||||
if (Item0 != null)
|
||||
item0(Item0);
|
||||
else if (Item1 != null)
|
||||
item1(Item1);
|
||||
else
|
||||
item2(Item2);
|
||||
}
|
||||
|
||||
public TResult Match<TResult>(Func<T0, TResult> item0, Func<T1, TResult> item1, Func<T2, TResult> item2)
|
||||
{
|
||||
return Item0 != null ? item0(Item0) : Item1 != null ? item1(Item1) : item2(Item2);
|
||||
}
|
||||
}
|
||||
|
||||
public class OneOf<T0, T1, T2, T3>
|
||||
{
|
||||
public T0 Item0 { get; }
|
||||
public T1 Item1 { get; }
|
||||
public T2 Item2 { get; }
|
||||
public T3 Item3 { get; }
|
||||
|
||||
public OneOf(T0 item0)
|
||||
{
|
||||
Item0 = item0;
|
||||
}
|
||||
|
||||
public OneOf(T1 item1)
|
||||
{
|
||||
Item1 = item1;
|
||||
}
|
||||
|
||||
public OneOf(T2 item2)
|
||||
{
|
||||
Item2 = item2;
|
||||
}
|
||||
|
||||
public OneOf(T3 item3)
|
||||
{
|
||||
Item3 = item3;
|
||||
}
|
||||
|
||||
public static implicit operator OneOf<T0, T1, T2, T3>(T0 item0) => new OneOf<T0, T1, T2, T3>(item0);
|
||||
public static implicit operator OneOf<T0, T1, T2, T3>(T1 item1) => new OneOf<T0, T1, T2, T3>(item1);
|
||||
public static implicit operator OneOf<T0, T1, T2, T3>(T2 item2) => new OneOf<T0, T1, T2, T3>(item2);
|
||||
public static implicit operator OneOf<T0, T1, T2, T3>(T3 item3) => new OneOf<T0, T1, T2, T3>(item3);
|
||||
|
||||
public void Match(Action<T0> item0, Action<T1> item1, Action<T2> item2, Action<T3> item3)
|
||||
{
|
||||
if (Item0 != null)
|
||||
item0(Item0);
|
||||
else if (Item1 != null)
|
||||
item1(Item1);
|
||||
else if (Item2 != null)
|
||||
item2(Item2);
|
||||
else
|
||||
item3(Item3);
|
||||
}
|
||||
|
||||
public TResult Match<TResult>(Func<T0, TResult> item0, Func<T1, TResult> item1, Func<T2, TResult> item2, Func<T3, TResult> item3)
|
||||
{
|
||||
return Item0 != null ? item0(Item0) : Item1 != null ? item1(Item1) : Item2 != null ? item2(Item2) : item3(Item3);
|
||||
}
|
||||
|
||||
}
|
||||
46
DiscordBotCore.Utilities/OperatingSystem.cs
Normal file
46
DiscordBotCore.Utilities/OperatingSystem.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace DiscordBotCore.Utilities;
|
||||
|
||||
public class OperatingSystem
|
||||
{
|
||||
public enum OperatingSystemEnum : int
|
||||
{
|
||||
Windows = 0,
|
||||
Linux = 1,
|
||||
MacOs = 2
|
||||
}
|
||||
|
||||
public static OperatingSystemEnum GetOperatingSystem()
|
||||
{
|
||||
if(System.OperatingSystem.IsLinux()) return OperatingSystemEnum.Linux;
|
||||
if(System.OperatingSystem.IsWindows()) return OperatingSystemEnum.Windows;
|
||||
if(System.OperatingSystem.IsMacOS()) return OperatingSystemEnum.MacOs;
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
public static string GetOperatingSystemString(OperatingSystemEnum os)
|
||||
{
|
||||
return os switch
|
||||
{
|
||||
OperatingSystemEnum.Windows => "Windows",
|
||||
OperatingSystemEnum.Linux => "Linux",
|
||||
OperatingSystemEnum.MacOs => "MacOS",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
public static OperatingSystemEnum GetOperatingSystemFromString(string os)
|
||||
{
|
||||
return os.ToLower() switch
|
||||
{
|
||||
"windows" => OperatingSystemEnum.Windows,
|
||||
"linux" => OperatingSystemEnum.Linux,
|
||||
"macos" => OperatingSystemEnum.MacOs,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
public static int GetOperatingSystemInt()
|
||||
{
|
||||
return (int) GetOperatingSystem();
|
||||
}
|
||||
}
|
||||
258
DiscordBotCore.Utilities/Option.cs
Normal file
258
DiscordBotCore.Utilities/Option.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
namespace DiscordBotCore.Utilities;
|
||||
public class Option2<T0, T1, TError> where TError : Exception
|
||||
{
|
||||
private readonly int _Index;
|
||||
|
||||
private T0 Item0 { get; } = default!;
|
||||
private T1 Item1 { get; } = default!;
|
||||
|
||||
private TError Error { get; } = default!;
|
||||
|
||||
public Option2(T0 item0)
|
||||
{
|
||||
Item0 = item0;
|
||||
_Index = 0;
|
||||
}
|
||||
|
||||
public Option2(T1 item1)
|
||||
{
|
||||
Item1 = item1;
|
||||
_Index = 1;
|
||||
}
|
||||
|
||||
public Option2(TError error)
|
||||
{
|
||||
Error = error;
|
||||
_Index = 2;
|
||||
}
|
||||
|
||||
public static implicit operator Option2<T0, T1, TError>(T0 item0) => new Option2<T0, T1, TError>(item0);
|
||||
public static implicit operator Option2<T0, T1, TError>(T1 item1) => new Option2<T0, T1, TError>(item1);
|
||||
public static implicit operator Option2<T0, T1, TError>(TError error) => new Option2<T0, T1, TError>(error);
|
||||
|
||||
public void Match(Action<T0> item0, Action<T1> item1, Action<TError> error)
|
||||
{
|
||||
switch (_Index)
|
||||
{
|
||||
case 0:
|
||||
item0(Item0);
|
||||
break;
|
||||
case 1:
|
||||
item1(Item1);
|
||||
break;
|
||||
case 2:
|
||||
error(Error);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public TResult Match<TResult>(Func<T0, TResult> item0, Func<T1, TResult> item1, Func<TError, TResult> error)
|
||||
{
|
||||
return _Index switch
|
||||
{
|
||||
0 => item0(Item0),
|
||||
1 => item1(Item1),
|
||||
2 => error(Error),
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _Index switch
|
||||
{
|
||||
0 => $"Option2<{typeof(T0).Name}>: {Item0}",
|
||||
1 => $"Option2<{typeof(T1).Name}>: {Item1}",
|
||||
2 => $"Option2<{typeof(TError).Name}>: {Error}",
|
||||
_ => "Invalid Option2"
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class Option3<T0, T1, T2, TError> where TError : Exception
|
||||
{
|
||||
private readonly int _Index;
|
||||
|
||||
private T0 Item0 { get; } = default!;
|
||||
private T1 Item1 { get; } = default!;
|
||||
private T2 Item2 { get; } = default!;
|
||||
private TError Error { get; } = default!;
|
||||
|
||||
public Option3(T0 item0)
|
||||
{
|
||||
Item0 = item0;
|
||||
_Index = 0;
|
||||
}
|
||||
|
||||
public Option3(T1 item1)
|
||||
{
|
||||
Item1 = item1;
|
||||
_Index = 1;
|
||||
}
|
||||
|
||||
public Option3(T2 item2)
|
||||
{
|
||||
Item2 = item2;
|
||||
_Index = 2;
|
||||
}
|
||||
|
||||
public Option3(TError error)
|
||||
{
|
||||
Error = error;
|
||||
_Index = 3;
|
||||
}
|
||||
|
||||
public static implicit operator Option3<T0, T1, T2, TError>(T0 item0) => new Option3<T0, T1, T2, TError>(item0);
|
||||
public static implicit operator Option3<T0, T1, T2, TError>(T1 item1) => new Option3<T0, T1, T2, TError>(item1);
|
||||
public static implicit operator Option3<T0, T1, T2, TError>(T2 item2) => new Option3<T0, T1, T2, TError>(item2);
|
||||
public static implicit operator Option3<T0, T1, T2, TError>(TError error) => new Option3<T0, T1, T2, TError>(error);
|
||||
|
||||
public void Match(Action<T0> item0, Action<T1> item1, Action<T2> item2, Action<TError> error)
|
||||
{
|
||||
switch (_Index)
|
||||
{
|
||||
case 0:
|
||||
item0(Item0);
|
||||
break;
|
||||
case 1:
|
||||
item1(Item1);
|
||||
break;
|
||||
case 2:
|
||||
item2(Item2);
|
||||
break;
|
||||
case 3:
|
||||
error(Error);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public TResult Match<TResult>(Func<T0, TResult> item0, Func<T1, TResult> item1, Func<T2, TResult> item2, Func<TError, TResult> error)
|
||||
{
|
||||
return _Index switch
|
||||
{
|
||||
0 => item0(Item0),
|
||||
1 => item1(Item1),
|
||||
2 => item2(Item2),
|
||||
3 => error(Error),
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _Index switch
|
||||
{
|
||||
0 => $"Option3<{typeof(T0).Name}>: {Item0}",
|
||||
1 => $"Option3<{typeof(T1).Name}>: {Item1}",
|
||||
2 => $"Option3<{typeof(T2).Name}>: {Item2}",
|
||||
3 => $"Option3<{typeof(TError).Name}>: {Error}",
|
||||
_ => "Invalid Option3"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class Option4<T0, T1, T2, T3, TError> where TError : Exception
|
||||
{
|
||||
private readonly int _Index;
|
||||
|
||||
private T0 Item0 { get; } = default!;
|
||||
private T1 Item1 { get; } = default!;
|
||||
private T2 Item2 { get; } = default!;
|
||||
private T3 Item3 { get; } = default!;
|
||||
|
||||
private TError Error { get; } = default!;
|
||||
|
||||
public Option4(T0 item0)
|
||||
{
|
||||
Item0 = item0;
|
||||
_Index = 0;
|
||||
}
|
||||
|
||||
public Option4(T1 item1)
|
||||
{
|
||||
Item1 = item1;
|
||||
_Index = 1;
|
||||
}
|
||||
|
||||
public Option4(T2 item2)
|
||||
{
|
||||
Item2 = item2;
|
||||
_Index = 2;
|
||||
}
|
||||
|
||||
public Option4(T3 item3)
|
||||
{
|
||||
Item3 = item3;
|
||||
_Index = 3;
|
||||
}
|
||||
|
||||
public Option4(TError error)
|
||||
{
|
||||
Error = error;
|
||||
_Index = 4;
|
||||
}
|
||||
|
||||
|
||||
public static implicit operator Option4<T0, T1, T2, T3, TError>(T0 item0) => new Option4<T0, T1, T2, T3, TError>(item0);
|
||||
public static implicit operator Option4<T0, T1, T2, T3, TError>(T1 item1) => new Option4<T0, T1, T2, T3, TError>(item1);
|
||||
public static implicit operator Option4<T0, T1, T2, T3, TError>(T2 item2) => new Option4<T0, T1, T2, T3, TError>(item2);
|
||||
public static implicit operator Option4<T0, T1, T2, T3, TError>(T3 item3) => new Option4<T0, T1, T2, T3, TError>(item3);
|
||||
public static implicit operator Option4<T0, T1, T2, T3, TError>(TError error) => new Option4<T0, T1, T2, T3, TError>(error);
|
||||
|
||||
|
||||
public void Match(Action<T0> item0, Action<T1> item1, Action<T2> item2, Action<T3> item3, Action<TError> error)
|
||||
{
|
||||
switch (_Index)
|
||||
{
|
||||
case 0:
|
||||
item0(Item0);
|
||||
break;
|
||||
case 1:
|
||||
item1(Item1);
|
||||
break;
|
||||
case 2:
|
||||
item2(Item2);
|
||||
break;
|
||||
case 3:
|
||||
item3(Item3);
|
||||
break;
|
||||
case 4:
|
||||
error(Error);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public TResult Match<TResult>(Func<T0, TResult> item0, Func<T1, TResult> item1, Func<T2, TResult> item2, Func<T3, TResult> item3, Func<TError, TResult> error)
|
||||
{
|
||||
return _Index switch
|
||||
{
|
||||
0 => item0(Item0),
|
||||
1 => item1(Item1),
|
||||
2 => item2(Item2),
|
||||
3 => item3(Item3),
|
||||
4 => error(Error),
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _Index switch
|
||||
{
|
||||
0 => $"Option4<{typeof(T0).Name}>: {Item0}",
|
||||
1 => $"Option4<{typeof(T1).Name}>: {Item1}",
|
||||
2 => $"Option4<{typeof(T2).Name}>: {Item2}",
|
||||
3 => $"Option4<{typeof(T3).Name}>: {Item3}",
|
||||
4 => $"Option4<{typeof(TError).Name}>: {Error}",
|
||||
_ => "Invalid Option4"
|
||||
};
|
||||
}
|
||||
}
|
||||
8
DiscordBotCore.Utilities/Responses/IResponse.cs
Normal file
8
DiscordBotCore.Utilities/Responses/IResponse.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace DiscordBotCore.Utilities.Responses;
|
||||
|
||||
public interface IResponse<out T>
|
||||
{
|
||||
public bool IsSuccess { get; }
|
||||
public string Message { get; }
|
||||
public T? Data { get; }
|
||||
}
|
||||
45
DiscordBotCore.Utilities/Responses/Response.cs
Normal file
45
DiscordBotCore.Utilities/Responses/Response.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace DiscordBotCore.Utilities.Responses;
|
||||
|
||||
public class Response : IResponse<bool>
|
||||
{
|
||||
public bool IsSuccess => Data;
|
||||
public string Message { get; }
|
||||
public bool Data { get; }
|
||||
|
||||
private Response(bool result)
|
||||
{
|
||||
Data = result;
|
||||
Message = string.Empty;
|
||||
}
|
||||
|
||||
private Response(string message)
|
||||
{
|
||||
Data = false;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public static Response Success() => new Response(true);
|
||||
public static Response Failure(string message) => new Response(message);
|
||||
}
|
||||
|
||||
public class Response<T> : IResponse<T> where T : class
|
||||
{
|
||||
public bool IsSuccess => Data is not null;
|
||||
public string Message { get; }
|
||||
public T? Data { get; }
|
||||
|
||||
private Response(T data)
|
||||
{
|
||||
Data = data;
|
||||
Message = string.Empty;
|
||||
}
|
||||
|
||||
private Response(string message)
|
||||
{
|
||||
Data = null;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public static Response<T> Success(T data) => new Response<T>(data);
|
||||
public static Response<T> Failure(string message) => new Response<T>(message);
|
||||
}
|
||||
80
DiscordBotCore.Utilities/Result.cs
Normal file
80
DiscordBotCore.Utilities/Result.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
namespace DiscordBotCore.Utilities;
|
||||
|
||||
public class Result
|
||||
{
|
||||
private bool? _Result;
|
||||
private Exception? Exception { get; }
|
||||
|
||||
|
||||
private Result(Exception exception)
|
||||
{
|
||||
_Result = null;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
private Result(bool result)
|
||||
{
|
||||
_Result = result;
|
||||
Exception = null;
|
||||
}
|
||||
|
||||
public bool IsSuccess => _Result.HasValue && _Result.Value;
|
||||
|
||||
public void HandleException(Action<Exception> action)
|
||||
{
|
||||
if(IsSuccess)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
action(Exception!);
|
||||
}
|
||||
|
||||
public static Result Success() => new Result(true);
|
||||
public static Result Failure(Exception ex) => new Result(ex);
|
||||
public static Result Failure(string message) => new Result(new Exception(message));
|
||||
|
||||
public void Match(Action successAction, Action<Exception> exceptionAction)
|
||||
{
|
||||
if (_Result.HasValue && _Result.Value)
|
||||
{
|
||||
successAction();
|
||||
}
|
||||
else
|
||||
{
|
||||
exceptionAction(Exception!);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public TResult Match<TResult>(Func<TResult> successAction, Func<Exception,TResult> errorAction)
|
||||
{
|
||||
return IsSuccess ? successAction() : errorAction(Exception!);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class Result<T>
|
||||
{
|
||||
private readonly OneOf<T, Exception> _Result;
|
||||
|
||||
private Result(OneOf<T, Exception> result)
|
||||
{
|
||||
_Result = result;
|
||||
}
|
||||
|
||||
public static Result<T> From (T value) => new Result<T>(new OneOf<T, Exception>(value));
|
||||
public static implicit operator Result<T>(Exception exception) => new Result<T>(new OneOf<T, Exception>(exception));
|
||||
|
||||
|
||||
|
||||
public void Match(Action<T> valueAction, Action<Exception> exceptionAction)
|
||||
{
|
||||
_Result.Match(valueAction, exceptionAction);
|
||||
}
|
||||
|
||||
public TResult Match<TResult>(Func<T, TResult> valueFunc, Func<Exception, TResult> exceptionFunc)
|
||||
{
|
||||
return _Result.Match(valueFunc, exceptionFunc);
|
||||
}
|
||||
}
|
||||
7
DiscordBotCore.Utilities/UnzipProgressType.cs
Normal file
7
DiscordBotCore.Utilities/UnzipProgressType.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DiscordBotCore.Utilities;
|
||||
|
||||
public enum UnzipProgressType
|
||||
{
|
||||
PercentageFromNumberOfFiles,
|
||||
PercentageFromTotalSize
|
||||
}
|
||||
183
DiscordBotCore/Bot/CommandHandler.cs
Normal file
183
DiscordBotCore/Bot/CommandHandler.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Configuration;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.PluginCore.Helpers;
|
||||
using DiscordBotCore.PluginCore.Helpers.Execution.DbCommand;
|
||||
using DiscordBotCore.PluginCore.Interfaces;
|
||||
using DiscordBotCore.PluginManagement.Loading;
|
||||
|
||||
namespace DiscordBotCore.Bot;
|
||||
|
||||
internal class CommandHandler : ICommandHandler
|
||||
{
|
||||
private static readonly string _DefaultPrefix = ";";
|
||||
|
||||
private readonly CommandService _commandService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IPluginLoader _pluginLoader;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Command handler constructor
|
||||
/// </summary>
|
||||
/// <param name="pluginLoader">The plugin loader</param>
|
||||
/// <param name="commandService">The discord bot command service</param>
|
||||
/// <param name="botPrefix">The prefix to watch for</param>
|
||||
/// <param name="logger">The logger</param>
|
||||
public CommandHandler(ILogger logger, IPluginLoader pluginLoader, IConfiguration configuration, CommandService commandService)
|
||||
{
|
||||
_commandService = commandService;
|
||||
_logger = logger;
|
||||
_pluginLoader = pluginLoader;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method to initialize all commands
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task InstallCommandsAsync(DiscordSocketClient client)
|
||||
{
|
||||
client.MessageReceived += (message) => MessageHandler(client, message);
|
||||
client.SlashCommandExecuted += Client_SlashCommandExecuted;
|
||||
await _commandService.AddModulesAsync(Assembly.GetEntryAssembly(), null);
|
||||
}
|
||||
|
||||
private Task Client_SlashCommandExecuted(SocketSlashCommand arg)
|
||||
{
|
||||
try
|
||||
{
|
||||
var plugin = _pluginLoader.SlashCommands.FirstOrDefault(p => p.Name == arg.Data.Name);
|
||||
|
||||
if (plugin is null)
|
||||
throw new Exception("Failed to run command !");
|
||||
|
||||
if (arg.Channel is SocketDMChannel)
|
||||
plugin.ExecuteDm(_logger, arg);
|
||||
else plugin.ExecuteServer(_logger, arg);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogException(ex, this);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The message handler for the bot
|
||||
/// </summary>
|
||||
/// <param name="Message">The message got from the user in discord chat</param>
|
||||
/// <returns></returns>
|
||||
private async Task MessageHandler(DiscordSocketClient socketClient, SocketMessage socketMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (socketMessage.Author.IsBot)
|
||||
return;
|
||||
|
||||
if (socketMessage as SocketUserMessage == null)
|
||||
return;
|
||||
|
||||
var message = socketMessage as SocketUserMessage;
|
||||
|
||||
if (message is null)
|
||||
return;
|
||||
|
||||
var argPos = 0;
|
||||
|
||||
string botPrefix = this._configuration.Get<string>("prefix", _DefaultPrefix);
|
||||
|
||||
if (!message.Content.StartsWith(botPrefix) && !message.HasMentionPrefix(socketClient.CurrentUser, ref argPos))
|
||||
return;
|
||||
|
||||
var context = new SocketCommandContext(socketClient, message);
|
||||
|
||||
await _commandService.ExecuteAsync(context, argPos, null);
|
||||
|
||||
IDbCommand? plugin;
|
||||
var cleanMessage = "";
|
||||
|
||||
if (message.HasMentionPrefix(socketClient.CurrentUser, ref argPos))
|
||||
{
|
||||
var mentionPrefix = "<@" + socketClient.CurrentUser.Id + ">";
|
||||
|
||||
plugin = _pluginLoader.Commands!
|
||||
.FirstOrDefault(plug => plug.Command ==
|
||||
message.Content.Substring(mentionPrefix.Length + 1)
|
||||
.Split(' ')[0] ||
|
||||
plug.Aliases.Contains(message.CleanContent
|
||||
.Substring(mentionPrefix.Length + 1)
|
||||
.Split(' ')[0]
|
||||
)
|
||||
);
|
||||
|
||||
cleanMessage = message.Content.Substring(mentionPrefix.Length + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
plugin = _pluginLoader.Commands!
|
||||
.FirstOrDefault(p => p.Command ==
|
||||
message.Content.Split(' ')[0].Substring(botPrefix.Length) ||
|
||||
p.Aliases.Contains(
|
||||
message.Content.Split(' ')[0]
|
||||
.Substring(botPrefix.Length)
|
||||
)
|
||||
);
|
||||
cleanMessage = message.Content.Substring(botPrefix.Length);
|
||||
}
|
||||
|
||||
if (plugin is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (plugin.RequireAdmin && !context.Message.Author.IsAdmin())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var split = cleanMessage.Split(' ');
|
||||
|
||||
string[]? argsClean = null;
|
||||
if (split.Length > 1)
|
||||
{
|
||||
argsClean = string.Join(' ', split, 1, split.Length - 1).Split(' ');
|
||||
}
|
||||
|
||||
DbCommandExecutingArgument cmd = new(_logger,
|
||||
context,
|
||||
cleanMessage,
|
||||
split[0],
|
||||
argsClean,
|
||||
new DirectoryInfo(Path.Combine(_configuration.Get<string>("ResourcesFolder"), plugin.Command)));
|
||||
|
||||
_logger.Log(
|
||||
$"User ({context.User.Username}) from Guild \"{context.Guild.Name}\" executed command \"{cmd.CleanContent}\"",
|
||||
this,
|
||||
LogType.Info
|
||||
);
|
||||
|
||||
if (context.Channel is SocketDMChannel)
|
||||
{
|
||||
await plugin.ExecuteDm(cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
await plugin.ExecuteServer(cmd);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogException(ex, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
DiscordBotCore/Bot/DiscordBotApplication.cs
Normal file
144
DiscordBotCore/Bot/DiscordBotApplication.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Configuration;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.PluginManagement.Loading;
|
||||
|
||||
|
||||
namespace DiscordBotCore.Bot;
|
||||
|
||||
public class DiscordBotApplication : IDiscordBotApplication
|
||||
{
|
||||
internal static IPluginLoader _InternalPluginLoader;
|
||||
|
||||
private CommandHandler _CommandServiceHandler;
|
||||
private CommandService _Service;
|
||||
private readonly ILogger _Logger;
|
||||
private readonly IConfiguration _Configuration;
|
||||
private readonly IPluginLoader _PluginLoader;
|
||||
|
||||
public bool IsReady { get; private set; }
|
||||
|
||||
public DiscordSocketClient Client { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The main Boot constructor
|
||||
/// </summary>
|
||||
public DiscordBotApplication(ILogger logger, IConfiguration configuration, IPluginLoader pluginLoader)
|
||||
{
|
||||
this._Logger = logger;
|
||||
this._Configuration = configuration;
|
||||
this._PluginLoader = pluginLoader;
|
||||
|
||||
_InternalPluginLoader = pluginLoader;
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
{
|
||||
if (!IsReady)
|
||||
{
|
||||
_Logger.Log("Can not stop the bot. It is not yet initialized.", this, LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
await _PluginLoader.UnloadAllPlugins();
|
||||
|
||||
await Client.LogoutAsync();
|
||||
await Client.StopAsync();
|
||||
|
||||
Client.Log -= Log;
|
||||
Client.LoggedIn -= LoggedIn;
|
||||
Client.Ready -= Ready;
|
||||
Client.Disconnected -= Client_Disconnected;
|
||||
|
||||
await Client.DisposeAsync();
|
||||
|
||||
|
||||
IsReady = false;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The start method for the bot. This method is used to load the bot
|
||||
/// </summary>
|
||||
public async Task StartAsync()
|
||||
{
|
||||
var config = new DiscordSocketConfig
|
||||
{
|
||||
AlwaysDownloadUsers = true,
|
||||
|
||||
//Disable system clock checkup (for responses at slash commands)
|
||||
UseInteractionSnowflakeDate = false,
|
||||
GatewayIntents = GatewayIntents.All
|
||||
};
|
||||
|
||||
DiscordSocketClient client = new DiscordSocketClient(config);
|
||||
|
||||
|
||||
_Service = new CommandService();
|
||||
|
||||
client.Log += Log;
|
||||
client.LoggedIn += LoggedIn;
|
||||
client.Ready += Ready;
|
||||
client.Disconnected += Client_Disconnected;
|
||||
|
||||
Client = client;
|
||||
await client.LoginAsync(TokenType.Bot, _Configuration.Get<string>("token"));
|
||||
await client.StartAsync();
|
||||
|
||||
_CommandServiceHandler = new CommandHandler(_Logger, _PluginLoader, _Configuration, _Service);
|
||||
|
||||
await _CommandServiceHandler.InstallCommandsAsync(client);
|
||||
|
||||
while (!IsReady)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Client_Disconnected(Exception arg)
|
||||
{
|
||||
if (arg.Message.Contains("401"))
|
||||
{
|
||||
_Configuration.Set("token", string.Empty);
|
||||
_Logger.Log("The token is invalid.", this, LogType.Critical);
|
||||
await _Configuration.SaveToFile();
|
||||
}
|
||||
}
|
||||
|
||||
private Task Ready()
|
||||
{
|
||||
IsReady = true;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task LoggedIn()
|
||||
{
|
||||
_Logger.Log("Successfully Logged In", this);
|
||||
_PluginLoader.SetDiscordClient(Client);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Log(LogMessage message)
|
||||
{
|
||||
switch (message.Severity)
|
||||
{
|
||||
case LogSeverity.Error:
|
||||
case LogSeverity.Critical:
|
||||
_Logger.Log(message.Message, this, LogType.Error);
|
||||
break;
|
||||
|
||||
case LogSeverity.Info:
|
||||
case LogSeverity.Debug:
|
||||
_Logger.Log(message.Message, this, LogType.Info);
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
13
DiscordBotCore/Bot/ICommandHandler.cs
Normal file
13
DiscordBotCore/Bot/ICommandHandler.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace DiscordBotCore.Bot;
|
||||
|
||||
internal interface ICommandHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// The method to initialize all commands
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task InstallCommandsAsync(DiscordSocketClient client);
|
||||
}
|
||||
20
DiscordBotCore/Bot/IDiscordBotApplication.cs
Normal file
20
DiscordBotCore/Bot/IDiscordBotApplication.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace DiscordBotCore.Bot;
|
||||
|
||||
public interface IDiscordBotApplication
|
||||
{
|
||||
public bool IsReady { get; }
|
||||
public DiscordSocketClient Client { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The start method for the bot. This method is used to load the bot
|
||||
/// </summary>
|
||||
Task StartAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Stops the bot and cleans up resources.
|
||||
/// </summary>
|
||||
Task StopAsync();
|
||||
}
|
||||
77
DiscordBotCore/Commands/HelpCommand.cs
Normal file
77
DiscordBotCore/Commands/HelpCommand.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Discord;
|
||||
using DiscordBotCore.Bot;
|
||||
using DiscordBotCore.PluginCore.Helpers.Execution.DbCommand;
|
||||
using DiscordBotCore.PluginCore.Interfaces;
|
||||
|
||||
namespace DiscordBotCore.Commands;
|
||||
|
||||
public class HelpCommand : IDbCommand
|
||||
{
|
||||
public string Command => "help";
|
||||
public List<string> Aliases => [];
|
||||
public string Description => "Help command for the bot.";
|
||||
public string Usage => "help <command>";
|
||||
public bool RequireAdmin => false;
|
||||
|
||||
public async Task ExecuteServer(IDbCommandExecutingArgument args)
|
||||
{
|
||||
if (args.Arguments is not null)
|
||||
{
|
||||
string searchedCommand = args.Arguments[0];
|
||||
IDbCommand? command = DiscordBotApplication._InternalPluginLoader.Commands.FirstOrDefault(c => c.Command.Equals(searchedCommand, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (command is null)
|
||||
{
|
||||
await args.Context.Channel.SendMessageAsync($"Command `{searchedCommand}` not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
EmbedBuilder helpEmbed = GenerateHelpCommand(command);
|
||||
await args.Context.Channel.SendMessageAsync(embed: helpEmbed.Build());
|
||||
return;
|
||||
}
|
||||
|
||||
if (DiscordBotApplication._InternalPluginLoader.Commands.Count == 0)
|
||||
{
|
||||
await args.Context.Channel.SendMessageAsync("No commands found.");
|
||||
return;
|
||||
}
|
||||
|
||||
var embedBuilder = new EmbedBuilder();
|
||||
|
||||
var adminCommands = "";
|
||||
var normalCommands = "";
|
||||
|
||||
foreach (var cmd in DiscordBotApplication._InternalPluginLoader.Commands)
|
||||
if (cmd.RequireAdmin)
|
||||
adminCommands += cmd.Command + " ";
|
||||
else
|
||||
normalCommands += cmd.Command + " ";
|
||||
|
||||
|
||||
if (adminCommands.Length > 0)
|
||||
embedBuilder.AddField("Admin Commands", adminCommands);
|
||||
if (normalCommands.Length > 0)
|
||||
embedBuilder.AddField("Normal Commands", normalCommands);
|
||||
await args.Context.Channel.SendMessageAsync(embed: embedBuilder.Build());
|
||||
}
|
||||
|
||||
private EmbedBuilder GenerateHelpCommand(IDbCommand command)
|
||||
{
|
||||
EmbedBuilder builder = new();
|
||||
builder.WithTitle($"Command: {command.Command}");
|
||||
builder.WithDescription(command.Description);
|
||||
builder.WithColor(Color.Blue);
|
||||
builder.AddField("Usage", command.Usage);
|
||||
string aliases = "";
|
||||
foreach (var alias in command.Aliases)
|
||||
aliases += alias + " ";
|
||||
builder.AddField("Aliases", aliases.Length > 0 ? aliases : "None");
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
18
DiscordBotCore/DiscordBotCore.csproj
Normal file
18
DiscordBotCore/DiscordBotCore.csproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
<Platforms>AnyCPU;x64;ARM64</Platforms>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net" Version="3.17.2" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiscordBotCore.Configuration\DiscordBotCore.Configuration.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.PluginCore\DiscordBotCore.PluginCore.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.PluginManagement.Loading\DiscordBotCore.PluginManagement.Loading.csproj" />
|
||||
<ProjectReference Include="..\DiscordBotCore.PluginManagement\DiscordBotCore.PluginManagement.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,12 +1,9 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace PluginManager.Others.Permissions;
|
||||
namespace DiscordBotCore.Others;
|
||||
|
||||
/// <summary>
|
||||
/// A class whith all discord permissions
|
||||
/// </summary>
|
||||
public static class DiscordPermissions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -15,7 +12,7 @@ public static class DiscordPermissions
|
||||
/// <param name="role">The role</param>
|
||||
/// <param name="permission">The permission</param>
|
||||
/// <returns></returns>
|
||||
public static bool hasPermission(this IRole role, GuildPermission permission)
|
||||
public static bool HasPermission(this IRole role, GuildPermission permission)
|
||||
{
|
||||
return role.Permissions.Has(permission);
|
||||
}
|
||||
@@ -26,20 +23,19 @@ public static class DiscordPermissions
|
||||
/// <param name="user">The user</param>
|
||||
/// <param name="role">The role</param>
|
||||
/// <returns></returns>
|
||||
public static bool hasRole(this SocketGuildUser user, IRole role)
|
||||
public static bool HasRole(this SocketGuildUser user, IRole role)
|
||||
{
|
||||
return user.Roles.Contains(role);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if user has the specified permission
|
||||
/// </summary>
|
||||
/// <param name="user">The user</param>
|
||||
/// <param name="permission">The permission</param>
|
||||
/// <returns></returns>
|
||||
public static bool hasPermission(this SocketGuildUser user, GuildPermission permission)
|
||||
public static bool HasPermission(this SocketGuildUser user, GuildPermission permission)
|
||||
{
|
||||
return user.Roles.Where(role => role.hasPermission(permission)).Any() || user.Guild.Owner == user;
|
||||
return user.Roles.Any(role => role.HasPermission(permission)) || user.Guild.Owner == user;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -47,9 +43,9 @@ public static class DiscordPermissions
|
||||
/// </summary>
|
||||
/// <param name="user">The user</param>
|
||||
/// <returns></returns>
|
||||
public static bool isAdmin(this SocketGuildUser user)
|
||||
public static bool IsAdmin(this SocketGuildUser user)
|
||||
{
|
||||
return user.hasPermission(GuildPermission.Administrator);
|
||||
return user.HasPermission(GuildPermission.Administrator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -57,8 +53,8 @@ public static class DiscordPermissions
|
||||
/// </summary>
|
||||
/// <param name="user">The user</param>
|
||||
/// <returns></returns>
|
||||
public static bool isAdmin(this SocketUser user)
|
||||
public static bool IsAdmin(this SocketUser user)
|
||||
{
|
||||
return isAdmin((SocketGuildUser)user);
|
||||
return IsAdmin((SocketGuildUser)user);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
DiscordBotCore/Properties/launchSettings.json
Normal file
12
DiscordBotCore/Properties/launchSettings.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"profiles": {
|
||||
"DiscordBotCore": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:49707;http://localhost:49708"
|
||||
}
|
||||
}
|
||||
}
|
||||
26
LICENSE.txt
Normal file
26
LICENSE.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
================================== Bootstrap Icons ==================================
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019-2024 The Bootstrap Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
=======================================================================================
|
||||
|
||||
14
Modules/CppCompatibilityModule/CppCompatibilityModule.csproj
Normal file
14
Modules/CppCompatibilityModule/CppCompatibilityModule.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>AnyCPU;x64;ARM64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DiscordBotCore\DiscordBotCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
18
Modules/CppCompatibilityModule/Extern/Delegates.cs
vendored
Normal file
18
Modules/CppCompatibilityModule/Extern/Delegates.cs
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CppCompatibilityModule.Extern;
|
||||
|
||||
public static class Delegates
|
||||
{
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void ProcessObject(ref object obj);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void ExecuteDelegateFunction();
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void SetExternFunctionPointerDelegate(IntPtr funcPtr);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void CsharpFunctionDelegate();
|
||||
}
|
||||
138
Modules/CppCompatibilityModule/Extern/ExternLibrary.cs
vendored
Normal file
138
Modules/CppCompatibilityModule/Extern/ExternLibrary.cs
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
using System.Net.Mime;
|
||||
using System.Runtime.InteropServices;
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Utilities;
|
||||
|
||||
namespace CppCompatibilityModule.Extern
|
||||
{
|
||||
public sealed class ExternLibrary
|
||||
{
|
||||
private readonly ILogger _Logger;
|
||||
public string LibraryPath { get; init; }
|
||||
public IntPtr LibraryHandle { get; private set; }
|
||||
|
||||
public ExternLibrary(ILogger logger, string libraryPath)
|
||||
{
|
||||
LibraryPath = libraryPath;
|
||||
LibraryHandle = IntPtr.Zero;
|
||||
_Logger = logger;
|
||||
}
|
||||
|
||||
public Result InitializeLibrary()
|
||||
{
|
||||
if(LibraryHandle != IntPtr.Zero)
|
||||
{
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
_Logger.Log($"Loading library {LibraryPath}");
|
||||
|
||||
|
||||
if(!NativeLibrary.TryLoad(LibraryPath, out IntPtr hModule))
|
||||
{
|
||||
return Result.Failure(new DllNotFoundException($"Unable to load library {LibraryPath}"));
|
||||
}
|
||||
|
||||
_Logger.Log($"Library {LibraryPath} loaded successfully [{hModule}]");
|
||||
|
||||
LibraryHandle = hModule;
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
public void FreeLibrary()
|
||||
{
|
||||
if(LibraryHandle == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeLibrary.Free(LibraryHandle);
|
||||
LibraryHandle = IntPtr.Zero;
|
||||
|
||||
_Logger.Log($"Library {LibraryPath} freed successfully");
|
||||
}
|
||||
|
||||
private IntPtr GetFunctionPointer(string functionName)
|
||||
{
|
||||
if(LibraryHandle == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Library is not loaded");
|
||||
}
|
||||
|
||||
if(!NativeLibrary.TryGetExport(LibraryHandle, functionName, out IntPtr functionPointer))
|
||||
{
|
||||
throw new EntryPointNotFoundException($"Unable to find function {functionName}");
|
||||
}
|
||||
|
||||
return functionPointer;
|
||||
}
|
||||
|
||||
public T GetDelegateForFunctionPointer<T>(string methodName) where T : Delegate
|
||||
{
|
||||
IntPtr functionPointer = GetFunctionPointer(methodName);
|
||||
|
||||
_Logger.Log($"Function pointer for {methodName} obtained successfully [address: {functionPointer}]");
|
||||
|
||||
T result = (T)Marshal.GetDelegateForFunctionPointer(functionPointer, typeof(T));
|
||||
|
||||
_Logger.Log($"Delegate for {methodName} created successfully");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private IntPtr GetFunctionPointerForDelegate<T>(T functionDelegate) where T : Delegate
|
||||
{
|
||||
IntPtr functionPointer = Marshal.GetFunctionPointerForDelegate(functionDelegate);
|
||||
|
||||
_Logger.Log($"Function pointer for delegate {functionDelegate.Method.Name} obtained successfully [address: {functionPointer}]");
|
||||
|
||||
return functionPointer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tells the extern setter function to point its function to this C# function instead.
|
||||
/// This function takes the name of the extern setter function and the C# function to be executed.
|
||||
/// <para><b>How it works:</b></para>
|
||||
/// Find the external setter method by its name. It should take one parameter, which is the pointer to the function to be executed.
|
||||
/// Take the delegate function that should be executed and get its function pointer.
|
||||
/// Call the external setter with the new function memory address. This should replace the old C++ function with the new C# function.
|
||||
/// </summary>
|
||||
/// <param name="setterExternFunctionName">The setter function name</param>
|
||||
/// <param name="executableFunction">The function that the C++ setter will make its internal function to point to</param>
|
||||
/// <typeparam name="ExecuteDelegate">A delegate that reflects the executable function structure</typeparam>
|
||||
/// <typeparam name="SetDelegate">The Setter delegate </typeparam>
|
||||
/// <returns>A response if it exists as an object</returns>
|
||||
public object? SetExternFunctionSetterPointerToCustomDelegate<SetDelegate, ExecuteDelegate>(string setterExternFunctionName, ExecuteDelegate executableFunction) where ExecuteDelegate : Delegate where SetDelegate : Delegate
|
||||
{
|
||||
SetDelegate setterDelegate = GetDelegateForFunctionPointer<SetDelegate>(setterExternFunctionName);
|
||||
IntPtr executableFunctionPtr = GetFunctionPointerForDelegate(executableFunction);
|
||||
|
||||
var result = setterDelegate.DynamicInvoke(executableFunctionPtr);
|
||||
|
||||
_Logger.Log($"Function {setterExternFunctionName} bound to local action successfully");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void CallFunction(string methodName, ref object parameter)
|
||||
{
|
||||
var functionDelegate = GetDelegateForFunctionPointer<Delegates.ProcessObject>(methodName);
|
||||
|
||||
functionDelegate(ref parameter);
|
||||
|
||||
_Logger.Log($"Function {methodName} called successfully with parameter");
|
||||
}
|
||||
|
||||
public void CallFunction(string methodName)
|
||||
{
|
||||
var functionDelegate = GetDelegateForFunctionPointer<Delegates.ExecuteDelegateFunction>(methodName);
|
||||
|
||||
functionDelegate();
|
||||
|
||||
_Logger.Log($"Function {methodName} called successfully");
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Modules/CppCompatibilityModule/Extern/ExternalApplication.cs
vendored
Normal file
58
Modules/CppCompatibilityModule/Extern/ExternalApplication.cs
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace CppCompatibilityModule.Extern;
|
||||
|
||||
public class ExternalApplication
|
||||
{
|
||||
public Guid ApplicationId { get; private set; }
|
||||
private readonly ExternLibrary _ExternLibrary;
|
||||
private readonly ILogger _Logger;
|
||||
|
||||
private ExternalApplication(ILogger logger, Guid applicationGuid, ExternLibrary library)
|
||||
{
|
||||
this.ApplicationId = applicationGuid;
|
||||
this._ExternLibrary = library;
|
||||
this._Logger = logger;
|
||||
}
|
||||
|
||||
internal void CallFunction(string methodName, ref object parameter)
|
||||
{
|
||||
_ExternLibrary.CallFunction(methodName, ref parameter);
|
||||
}
|
||||
|
||||
internal void CallFunction(string methodName)
|
||||
{
|
||||
_ExternLibrary.CallFunction(methodName);
|
||||
}
|
||||
|
||||
internal T GetDelegateForFunctionPointer<T>(string methodName) where T : Delegate
|
||||
{
|
||||
return _ExternLibrary.GetDelegateForFunctionPointer<T>(methodName);
|
||||
}
|
||||
|
||||
internal void SetExternFunctionToPointToFunction(string externalFunctionName, Delegates.CsharpFunctionDelegate localFunction)
|
||||
{
|
||||
_ExternLibrary.SetExternFunctionSetterPointerToCustomDelegate<Delegates.SetExternFunctionPointerDelegate, Delegates.CsharpFunctionDelegate>(externalFunctionName, localFunction);
|
||||
}
|
||||
|
||||
internal void FreeLibrary()
|
||||
{
|
||||
_ExternLibrary.FreeLibrary();
|
||||
}
|
||||
|
||||
public static ExternalApplication? CreateFromDllFile(ILogger logger, string dllFilePath)
|
||||
{
|
||||
ExternLibrary library = new ExternLibrary(logger, dllFilePath);
|
||||
var result = library.InitializeLibrary();
|
||||
|
||||
return result.Match<ExternalApplication?>(
|
||||
() => new ExternalApplication(logger, Guid.NewGuid(), library),
|
||||
(ex) => {
|
||||
logger.Log(ex.Message, LogType.Error);
|
||||
library.FreeLibrary();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
67
Modules/CppCompatibilityModule/ExternalApplicationHandler.cs
Normal file
67
Modules/CppCompatibilityModule/ExternalApplicationHandler.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace CppCompatibilityModule;
|
||||
|
||||
public class ExternalApplicationHandler
|
||||
{
|
||||
private readonly ILogger _Logger;
|
||||
private ExternalApplicationManager? _ExternalApplicationManager;
|
||||
|
||||
public ExternalApplicationHandler(ILogger logger)
|
||||
{
|
||||
_Logger = logger;
|
||||
_ExternalApplicationManager = new ExternalApplicationManager(logger);
|
||||
}
|
||||
|
||||
public Guid CreateApplication(string dllFilePath)
|
||||
{
|
||||
|
||||
if (_ExternalApplicationManager is null)
|
||||
{
|
||||
_Logger.Log("Failed to create application because the manager is not initialized. This should have never happened in the first place !!!", this, LogType.Critical);
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
if (_ExternalApplicationManager.TryCreateApplication(dllFilePath, out Guid appId))
|
||||
{
|
||||
return appId;
|
||||
}
|
||||
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
public void StopApplication(Guid applicationId)
|
||||
{
|
||||
if (_ExternalApplicationManager is null)
|
||||
{
|
||||
_Logger.Log("Failed to stop application because the manager is not initialized. This should have never happened in the first place!!!", this, LogType.Critical);
|
||||
return;
|
||||
}
|
||||
|
||||
_ExternalApplicationManager.FreeApplication(applicationId);
|
||||
}
|
||||
|
||||
public void CallFunctionWithParameter(Guid appId, string functionName, ref object parameter)
|
||||
{
|
||||
if (_ExternalApplicationManager is null)
|
||||
{
|
||||
_Logger.Log("Failed to call function because the manager is not initialized. This should have never happened in the first place!!!", this, LogType.Critical);
|
||||
return;
|
||||
}
|
||||
|
||||
_ExternalApplicationManager.ExecuteApplicationFunctionWithParameter(appId, functionName, ref parameter);
|
||||
}
|
||||
|
||||
public void CallFunctionWithoutParameter(Guid appId, string functionName)
|
||||
{
|
||||
if (_ExternalApplicationManager is null)
|
||||
{
|
||||
_Logger.Log("Failed to call function because the manager is not initialized. This should have never happened in the first place!!!", this, LogType.Critical);
|
||||
return;
|
||||
}
|
||||
|
||||
_ExternalApplicationManager.ExecuteApplicationFunctionWithoutParameter(appId, functionName);
|
||||
}
|
||||
}
|
||||
75
Modules/CppCompatibilityModule/ExternalApplicationManager.cs
Normal file
75
Modules/CppCompatibilityModule/ExternalApplicationManager.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using CppCompatibilityModule.Extern;
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Logging;
|
||||
|
||||
namespace CppCompatibilityModule;
|
||||
|
||||
internal class ExternalApplicationManager
|
||||
{
|
||||
private readonly ILogger _Logger;
|
||||
private List<ExternalApplication> _ExternalApplications;
|
||||
|
||||
public ExternalApplicationManager(ILogger logger)
|
||||
{
|
||||
_Logger = logger;
|
||||
_ExternalApplications = new List<ExternalApplication>();
|
||||
}
|
||||
|
||||
public bool TryCreateApplication(string applicationFileName, out Guid applicationId)
|
||||
{
|
||||
ExternalApplication? externalApplication = ExternalApplication.CreateFromDllFile(_Logger, applicationFileName);
|
||||
|
||||
if(externalApplication is null)
|
||||
{
|
||||
applicationId = Guid.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
_ExternalApplications.Add(externalApplication);
|
||||
|
||||
applicationId = externalApplication.ApplicationId;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void FreeApplication(Guid applicationId)
|
||||
{
|
||||
var application = _ExternalApplications.FirstOrDefault(app => app.ApplicationId == applicationId, null);
|
||||
if(application is null)
|
||||
{
|
||||
_Logger.Log($"Couldn't find application with id {applicationId}");
|
||||
return;
|
||||
}
|
||||
|
||||
application.FreeLibrary();
|
||||
_ExternalApplications.Remove(application);
|
||||
|
||||
_Logger.Log($"Application with id {applicationId} freed successfully");
|
||||
}
|
||||
|
||||
public void ExecuteApplicationFunctionWithParameter(Guid appId, string functionName, ref object parameter)
|
||||
{
|
||||
var application = _ExternalApplications.FirstOrDefault(app => app.ApplicationId == appId);
|
||||
if(application is null)
|
||||
{
|
||||
_Logger.Log($"Couldn't find application with id {appId}");
|
||||
return;
|
||||
}
|
||||
|
||||
application.CallFunction(functionName, ref parameter);
|
||||
}
|
||||
|
||||
public void ExecuteApplicationFunctionWithoutParameter(Guid appId, string functionName)
|
||||
{
|
||||
var application = _ExternalApplications.FirstOrDefault(app => app.ApplicationId == appId);
|
||||
if(application is null)
|
||||
{
|
||||
_Logger.Log($"Couldn't find application with id {appId}");
|
||||
return;
|
||||
}
|
||||
|
||||
application.CallFunction(functionName);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
using System;
|
||||
using PluginManager.Others;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading;
|
||||
|
||||
namespace PluginManager
|
||||
{
|
||||
internal class AppConfig
|
||||
{
|
||||
public string? UpdaterVersion { get; set; }
|
||||
public Dictionary<string, object>? ApplicationVariables { get; init; }
|
||||
public List<string>? ProtectedKeyWords { get; init; }
|
||||
public Dictionary<string, string>? PluginVersions { get; init; }
|
||||
}
|
||||
|
||||
public static class Config
|
||||
{
|
||||
public static class PluginConfig
|
||||
{
|
||||
public static readonly List<Tuple<string, PluginType>> InstalledPlugins = new();
|
||||
|
||||
public static void Load()
|
||||
{
|
||||
new Thread(LoadCommands).Start();
|
||||
new Thread(LoadEvents).Start();
|
||||
}
|
||||
|
||||
private static void LoadCommands()
|
||||
{
|
||||
string cmd_path = "./Data/Plugins/Commands/";
|
||||
string[] files = Directory.GetFiles(cmd_path, $"*.{Loaders.PluginLoader.pluginCMDExtension}", SearchOption.AllDirectories);
|
||||
foreach (var file in files)
|
||||
if (!file.Contains("PluginManager", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
string PluginName = new FileInfo(file).Name;
|
||||
string name = PluginName.Substring(0, PluginName.Length - 1 - PluginManager.Loaders.PluginLoader.pluginCMDExtension.Length);
|
||||
InstalledPlugins.Add(new(name, PluginType.Command));
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadEvents()
|
||||
{
|
||||
string eve_path = "./Data/Plugins/Events/";
|
||||
string[] files = Directory.GetFiles(eve_path, $"*.{Loaders.PluginLoader.pluginEVEExtension}", SearchOption.AllDirectories);
|
||||
foreach (var file in files)
|
||||
if (!file.Contains("PluginManager", StringComparison.InvariantCultureIgnoreCase))
|
||||
if (!file.Contains("PluginManager", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
string PluginName = new FileInfo(file).Name;
|
||||
string name = PluginName.Substring(0, PluginName.Length - 1 - PluginManager.Loaders.PluginLoader.pluginEVEExtension.Length);
|
||||
InstalledPlugins.Add(new(name, PluginType.Event));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Contains(string pluginName)
|
||||
{
|
||||
foreach (var tuple in InstalledPlugins)
|
||||
if (tuple.Item1 == pluginName)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static PluginType GetPluginType(string pluginName)
|
||||
{
|
||||
foreach (var tuple in InstalledPlugins)
|
||||
if (tuple.Item1 == pluginName)
|
||||
return tuple.Item2;
|
||||
|
||||
|
||||
return PluginType.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
private static AppConfig? appConfig { get; set; }
|
||||
|
||||
public static string UpdaterVersion { get => appConfig.UpdaterVersion; set => appConfig.UpdaterVersion = value; }
|
||||
|
||||
public static string GetPluginVersion(string pluginName) => appConfig!.PluginVersions![pluginName];
|
||||
public static void SetPluginVersion(string pluginName, string newVersion)
|
||||
{
|
||||
if (appConfig!.PluginVersions!.ContainsKey(pluginName))
|
||||
appConfig.PluginVersions[pluginName] = newVersion;
|
||||
else appConfig.PluginVersions.Add(pluginName, newVersion);
|
||||
|
||||
// SaveConfig();
|
||||
}
|
||||
|
||||
public static void RemovePluginVersion(string pluginName) => appConfig!.PluginVersions!.Remove(pluginName);
|
||||
public static bool PluginVersionsContainsKey(string pluginName) => appConfig!.PluginVersions!.ContainsKey(pluginName);
|
||||
|
||||
public static void AddValueToVariables<T>(string key, T value, bool isProtected)
|
||||
{
|
||||
if (value == null)
|
||||
throw new Exception("The value cannot be null");
|
||||
if (appConfig!.ApplicationVariables!.ContainsKey(key))
|
||||
throw new Exception($"The key ({key}) already exists in the variables. Value {GetValue<T>(key)}");
|
||||
|
||||
appConfig.ApplicationVariables.Add(key, value);
|
||||
if (isProtected && key != "Version")
|
||||
appConfig.ProtectedKeyWords!.Add(key);
|
||||
|
||||
SaveConfig(SaveType.NORMAL);
|
||||
}
|
||||
|
||||
public static Type GetVariableType(string value)
|
||||
{
|
||||
if (int.TryParse(value, out var intValue))
|
||||
return typeof(int);
|
||||
if (bool.TryParse(value, out var boolValue))
|
||||
return typeof(bool);
|
||||
if (float.TryParse(value, out var floatValue))
|
||||
return typeof(float);
|
||||
if (double.TryParse(value, out var doubleValue))
|
||||
return typeof(double);
|
||||
if (uint.TryParse(value, out var uintValue))
|
||||
return typeof(uint);
|
||||
if (long.TryParse(value, out var longValue))
|
||||
return typeof(long);
|
||||
if (byte.TryParse(value, out var byteValue))
|
||||
return typeof(byte);
|
||||
return typeof(string);
|
||||
}
|
||||
|
||||
public static void GetAndAddValueToVariable(string key, string value, bool isReadOnly)
|
||||
{
|
||||
if (Config.ContainsKey(key))
|
||||
return;
|
||||
if (int.TryParse(value, out var intValue))
|
||||
Config.AddValueToVariables(key, intValue, isReadOnly);
|
||||
else if (bool.TryParse(value, out var boolValue))
|
||||
Config.AddValueToVariables(key, boolValue, isReadOnly);
|
||||
else if (float.TryParse(value, out var floatValue))
|
||||
Config.AddValueToVariables(key, floatValue, isReadOnly);
|
||||
else if (double.TryParse(value, out var doubleValue))
|
||||
Config.AddValueToVariables(key, doubleValue, isReadOnly);
|
||||
else if (uint.TryParse(value, out var uintValue))
|
||||
Config.AddValueToVariables(key, uintValue, isReadOnly);
|
||||
else if (long.TryParse(value, out var longValue))
|
||||
Config.AddValueToVariables(key, longValue, isReadOnly);
|
||||
else if (byte.TryParse(value, out var byteValue))
|
||||
Config.AddValueToVariables(key, byteValue, isReadOnly);
|
||||
else
|
||||
Config.AddValueToVariables(key, value, isReadOnly);
|
||||
}
|
||||
|
||||
public static T? GetValue<T>(string key)
|
||||
{
|
||||
if (!appConfig!.ApplicationVariables!.ContainsKey(key)) return default;
|
||||
try
|
||||
{
|
||||
JsonElement element = (JsonElement)appConfig.ApplicationVariables[key];
|
||||
return element.Deserialize<T>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return (T)appConfig.ApplicationVariables[key];
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetValue<T>(string key, T value)
|
||||
{
|
||||
if (value == null)
|
||||
throw new Exception("Value is null");
|
||||
if (!appConfig!.ApplicationVariables!.ContainsKey(key))
|
||||
throw new Exception("Key does not exist in the config file");
|
||||
if (appConfig.ProtectedKeyWords!.Contains(key))
|
||||
throw new Exception("Key is protected");
|
||||
|
||||
appConfig.ApplicationVariables[key] = JsonSerializer.SerializeToElement(value);
|
||||
SaveConfig(SaveType.NORMAL);
|
||||
}
|
||||
|
||||
public static void RemoveKey(string key)
|
||||
{
|
||||
if (key == "Version" || key == "token" || key == "prefix")
|
||||
throw new Exception("Key is protected");
|
||||
appConfig!.ApplicationVariables!.Remove(key);
|
||||
appConfig.ProtectedKeyWords!.Remove(key);
|
||||
SaveConfig(SaveType.NORMAL);
|
||||
}
|
||||
|
||||
public static bool IsReadOnly(string key)
|
||||
{
|
||||
return appConfig.ProtectedKeyWords.Contains(key);
|
||||
}
|
||||
|
||||
public static async Task SaveConfig(SaveType type)
|
||||
{
|
||||
if (type == SaveType.NORMAL)
|
||||
{
|
||||
string path = Functions.dataFolder + "config.json";
|
||||
await Functions.SaveToJsonFile<AppConfig>(path, appConfig!);
|
||||
return;
|
||||
}
|
||||
if (type == SaveType.BACKUP)
|
||||
{
|
||||
string path = Functions.dataFolder + "config.json.bak";
|
||||
await Functions.SaveToJsonFile<AppConfig>(path, appConfig!);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static async Task LoadConfig()
|
||||
{
|
||||
string path = Functions.dataFolder + "config.json";
|
||||
if (File.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
appConfig = await Functions.ConvertFromJson<AppConfig>(path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
File.Delete(path);
|
||||
Console.WriteLine("An error occured while loading the settings. Importing from backup file...");
|
||||
path = Functions.dataFolder + "config.json.bak";
|
||||
appConfig = await Functions.ConvertFromJson<AppConfig>(path);
|
||||
Functions.WriteErrFile(ex.Message);
|
||||
}
|
||||
|
||||
|
||||
Functions.WriteLogFile($"Loaded {appConfig.ApplicationVariables!.Keys.Count} application variables.\nLoaded {appConfig.ProtectedKeyWords!.Count} readonly variables.");
|
||||
return;
|
||||
}
|
||||
appConfig = new() { ApplicationVariables = new Dictionary<string, object>(), ProtectedKeyWords = new List<string>(), PluginVersions = new Dictionary<string, string>(), UpdaterVersion = "-1" };
|
||||
}
|
||||
|
||||
public static bool ContainsValue<T>(T value) => appConfig!.ApplicationVariables!.ContainsValue(value!);
|
||||
public static bool ContainsKey(string key) => appConfig!.ApplicationVariables!.ContainsKey(key);
|
||||
|
||||
public static IDictionary<string, object> GetAllVariables() => appConfig.ApplicationVariables;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace PluginManager.Interfaces;
|
||||
|
||||
public interface DBEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the event
|
||||
/// </summary>
|
||||
string name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The description of the event
|
||||
/// </summary>
|
||||
string description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The method that is invoked when the event is loaded into memory
|
||||
/// </summary>
|
||||
/// <param name="client">The discord bot client</param>
|
||||
void Start(DiscordSocketClient client);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using PluginManager.Others;
|
||||
|
||||
namespace PluginManager.Items;
|
||||
|
||||
public class Command
|
||||
{
|
||||
/// <summary>
|
||||
/// The author of the command
|
||||
/// </summary>
|
||||
public SocketUser? Author;
|
||||
|
||||
/// <summary>
|
||||
/// The Command class contructor
|
||||
/// </summary>
|
||||
/// <param name="message">The message that was sent</param>
|
||||
public Command(SocketMessage message)
|
||||
{
|
||||
Author = message.Author;
|
||||
var data = message.Content.Split(' ');
|
||||
Arguments = data.Length > 1 ? new List<string>(data.MergeStrings(1).Split(' ')) : new List<string>();
|
||||
CommandName = data[0].Substring(1);
|
||||
PrefixUsed = data[0][0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of arguments
|
||||
/// </summary>
|
||||
public List<string> Arguments { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The command that is executed
|
||||
/// </summary>
|
||||
public string CommandName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The prefix that is used for the command
|
||||
/// </summary>
|
||||
public char PrefixUsed { get; }
|
||||
}
|
||||
|
||||
public class ConsoleCommand
|
||||
{
|
||||
public string CommandName { get; init; }
|
||||
public string Description { get; init; }
|
||||
public string Usage { get; init; }
|
||||
public Action<string[]> Action { get; init; }
|
||||
}
|
||||
@@ -1,468 +0,0 @@
|
||||
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;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Discord.WebSocket;
|
||||
|
||||
using PluginManager.Interfaces;
|
||||
using PluginManager.Loaders;
|
||||
using PluginManager.Online;
|
||||
using PluginManager.Online.Helpers;
|
||||
using PluginManager.Online.Updates;
|
||||
using PluginManager.Others;
|
||||
|
||||
namespace PluginManager.Items;
|
||||
|
||||
public class ConsoleCommandsHandler
|
||||
{
|
||||
private static readonly PluginsManager manager = new("https://raw.githubusercontent.com/Wizzy69/installer/discord-bot-files/Plugins.txt");
|
||||
private static readonly List<ConsoleCommand> commandList = new();
|
||||
private readonly DiscordSocketClient? client;
|
||||
|
||||
|
||||
private static bool isDownloading = false;
|
||||
private static bool pluginsLoaded = false;
|
||||
|
||||
public ConsoleCommandsHandler(DiscordSocketClient client)
|
||||
{
|
||||
this.client = client;
|
||||
InitializeBasicCommands();
|
||||
//Console.WriteLine("Initialized console command handler !");
|
||||
}
|
||||
|
||||
private void InitializeBasicCommands()
|
||||
{
|
||||
|
||||
commandList.Clear();
|
||||
|
||||
AddCommand("help", "Show help", "help <command>", args =>
|
||||
{
|
||||
if (args.Length <= 1)
|
||||
{
|
||||
Console.WriteLine("Available commands:");
|
||||
List<string[]> items = new List<string[]>();
|
||||
items.Add(new[] { "-", "-", "-" });
|
||||
items.Add(new[] { "Command", "Description", "Usage" });
|
||||
items.Add(new[] { " ", " ", "Argument type: <optional> [required]" });
|
||||
items.Add(new[] { "-", "-", "-" });
|
||||
|
||||
foreach (var command in commandList)
|
||||
{
|
||||
var pa = from p in command.Action.Method.GetParameters() where p.Name != null select p.ParameterType.FullName;
|
||||
items.Add(new[] { command.CommandName, command.Description, command.Usage });
|
||||
}
|
||||
|
||||
items.Add(new[] { "-", "-", "-" });
|
||||
Console_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!);
|
||||
ConsoleColor 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;
|
||||
|
||||
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.LoadPlugins();
|
||||
Console.ForegroundColor = cc;
|
||||
pluginsLoaded = true;
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand("listplugs", "list available plugins", () => { manager.ListAvailablePlugins().Wait(); });
|
||||
|
||||
AddCommand("dwplug", "download plugin", "dwplug [name]", async args =>
|
||||
{
|
||||
isDownloading = true;
|
||||
if (args.Length == 1)
|
||||
{
|
||||
isDownloading = false;
|
||||
Console.WriteLine("Please specify plugin name");
|
||||
return;
|
||||
}
|
||||
|
||||
var name = args.MergeStrings(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;
|
||||
Console_Utilities.WriteColorText("Name is invalid");
|
||||
return;
|
||||
}
|
||||
isDownloading = false;
|
||||
Console_Utilities.WriteColorText($"Failed to find plugin &b{name} &c!" + " Use &glistplugs &ccommand to display all available plugins !");
|
||||
return;
|
||||
}
|
||||
|
||||
string path;
|
||||
if (info[0] == "Command" || info[0] == "Event")
|
||||
path = "./Data/Plugins/" + info[0] + "s/" + name + "." + (info[0] == "Command" ? PluginLoader.pluginCMDExtension : PluginLoader.pluginEVEExtension);
|
||||
else
|
||||
path = $"./{info[1].Split('/')[info[1].Split('/').Length - 1]}";
|
||||
//Console.WriteLine("Downloading: " + path + " [" + info[1] + "]");
|
||||
await ServerCom.DownloadFileAsync(info[1], path);
|
||||
if (info[0] == "Event")
|
||||
Config.PluginConfig.InstalledPlugins.Add(new(name, PluginType.Event));
|
||||
else if (info[0] == "Command")
|
||||
Config.PluginConfig.InstalledPlugins.Add(new(name, PluginType.Command));
|
||||
|
||||
|
||||
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]);
|
||||
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]} ...");
|
||||
var bar = new Console_Utilities.ProgressBar(ProgressBarType.NO_END);// { Max = 100f, Color = ConsoleColor.Green };
|
||||
bar.Start();
|
||||
await Functions.ExtractArchive("./" + split[1], "./", null, UnzipProgressType.PercentageFromTotalSize);
|
||||
bar.Stop();
|
||||
Console.WriteLine("\n");
|
||||
File.Delete("./" + split[1]);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
VersionString? ver = await VersionString.GetVersionOfPackageFromWeb(name);
|
||||
if (ver is null) throw new Exception("Incorrect version");
|
||||
Config.SetPluginVersion(name, $"{ver.PackageVersionID}.{ver.PackageMainVersion}.{ver.PackageCheckVersion}");
|
||||
// Console.WriteLine();
|
||||
|
||||
isDownloading = false;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
AddCommand("value", "read value from VariableStack", "value [key]", args =>
|
||||
{
|
||||
if (args.Length != 2)
|
||||
return;
|
||||
if (!Config.ContainsKey(args[1]))
|
||||
return;
|
||||
|
||||
var data = Config.GetValue<string>(args[1]);
|
||||
Console.WriteLine($"{args[1]} => {data}");
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand("add", "add variable to the system variables", "add [key] [value] [isReadOnly=true/false]", args =>
|
||||
{
|
||||
if (args.Length < 4)
|
||||
return;
|
||||
var key = args[1];
|
||||
var value = args[2];
|
||||
var isReadOnly = args[3].Equals("true", StringComparison.CurrentCultureIgnoreCase);
|
||||
|
||||
try
|
||||
{
|
||||
Config.GetAndAddValueToVariable(key, value, isReadOnly);
|
||||
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.RemoveKey(args[1]);
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand("sd", "Shuts down the discord bot", async () =>
|
||||
{
|
||||
if (client is null)
|
||||
return;
|
||||
Console_Utilities.ProgressBar bar = new Console_Utilities.ProgressBar(ProgressBarType.NO_END);
|
||||
|
||||
bar.Start();
|
||||
await Config.SaveConfig(SaveType.NORMAL);
|
||||
await Config.SaveConfig(SaveType.BACKUP);
|
||||
await Task.Delay(4000);
|
||||
bar.Stop();
|
||||
Console.WriteLine();
|
||||
await client.StopAsync();
|
||||
await client.DisposeAsync();
|
||||
Environment.Exit(0);
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand("import", "Load an external command", "import [pluginName]", async (args) =>
|
||||
{
|
||||
if (args.Length <= 1) return;
|
||||
string pName = Functions.MergeStrings(args, 1);
|
||||
HttpClient client = new HttpClient();
|
||||
string url = (await manager.GetPluginLinkByName(pName))[1];
|
||||
Stream s = await client.GetStreamAsync(url);
|
||||
MemoryStream str = new MemoryStream();
|
||||
await s.CopyToAsync(str);
|
||||
var asmb = Assembly.Load(str.ToArray());
|
||||
|
||||
var types = asmb.GetTypes();
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (type.IsClass && typeof(DBEvent).IsAssignableFrom(type))
|
||||
{
|
||||
DBEvent instance = (DBEvent)Activator.CreateInstance(type);
|
||||
instance.Start(this.client);
|
||||
Console.WriteLine($"Loaded external {type.FullName}!");
|
||||
}
|
||||
else if (type.IsClass && typeof(DBCommand).IsAssignableFrom(type))
|
||||
{
|
||||
Console.WriteLine("Only events can be loaded from external sources !");
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddCommand("remplug", "Remove a plugin", "remplug [plugName]", async args =>
|
||||
{
|
||||
|
||||
if (args.Length <= 1) return;
|
||||
|
||||
isDownloading = true;
|
||||
string plugName = Functions.MergeStrings(args, 1);
|
||||
if (pluginsLoaded)
|
||||
{
|
||||
if (Functions.GetOperatingSystem() == Others.OperatingSystem.WINDOWS)
|
||||
{
|
||||
Process.Start("DiscordBot.exe", $"/remplug {plugName}");
|
||||
await Task.Delay(100);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.Start("./DiscordBot", $"/remplug {plugName}");
|
||||
await Task.Delay(100);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
isDownloading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
string location = "./Data/Plugins/";
|
||||
|
||||
location = Config.PluginConfig.GetPluginType(plugName) switch
|
||||
{
|
||||
PluginType.Command => location + "Commands/" + plugName + "." + PluginLoader.pluginCMDExtension,
|
||||
PluginType.Event => location + "Events/" + plugName + "." + PluginLoader.pluginEVEExtension,
|
||||
PluginType.Unknown => "./",
|
||||
_ => throw new NotImplementedException("Plugin type incorrect")
|
||||
};
|
||||
|
||||
if (!File.Exists(location))
|
||||
{
|
||||
Console.WriteLine("The plugin does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
File.Delete(location);
|
||||
if (Config.PluginConfig.Contains(plugName))
|
||||
{
|
||||
var tuple = Config.PluginConfig.InstalledPlugins.Where(t => t.Item1 == plugName).FirstOrDefault();
|
||||
Console.WriteLine("Found: " + tuple.ToString());
|
||||
Config.PluginConfig.InstalledPlugins.Remove(tuple);
|
||||
Config.RemovePluginVersion(plugName);
|
||||
await Config.SaveConfig(SaveType.NORMAL);
|
||||
}
|
||||
Console.WriteLine("Removed the plugin DLL. Checking for other files ...");
|
||||
|
||||
var info = await manager.GetPluginLinkByName(plugName);
|
||||
if (info[2] != string.Empty)
|
||||
{
|
||||
var lines = await ServerCom.ReadTextFromURL(info[2]);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (!(line.Length > 0 && line.Contains(",")))
|
||||
continue;
|
||||
var split = line.Split(',');
|
||||
if (File.Exists("./" + split[1]))
|
||||
File.Delete("./" + split[1]);
|
||||
|
||||
|
||||
Console.WriteLine("Removed: " + split[1]);
|
||||
}
|
||||
|
||||
|
||||
if (Directory.Exists(plugName))
|
||||
Directory.Delete(plugName, true);
|
||||
}
|
||||
isDownloading = false;
|
||||
Console.WriteLine(plugName + " has been successfully deleted !");
|
||||
|
||||
});
|
||||
|
||||
AddCommand("reload", "Reload the bot with all plugins", () =>
|
||||
{
|
||||
if (Functions.GetOperatingSystem() == Others.OperatingSystem.WINDOWS)
|
||||
{
|
||||
Process.Start("DiscordBot.exe", $"lp");
|
||||
HandleCommand("sd");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Process.Start("./DiscordBot", $"lp");
|
||||
HandleCommand("sd");
|
||||
}
|
||||
});
|
||||
|
||||
//Sort the commands by name
|
||||
commandList.Sort((x, y) => x.CommandName.CompareTo(y.CommandName));
|
||||
}
|
||||
|
||||
public static void AddCommand(string command, string description, string usage, Action<string[]> action)
|
||||
{
|
||||
commandList.Add(new ConsoleCommand { CommandName = command, Description = description, Action = action, Usage = usage });
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console_Utilities.WriteColorText($"Command &r{command} &cadded to the list of commands");
|
||||
}
|
||||
|
||||
public static void AddCommand(string command, string description, Action action)
|
||||
{
|
||||
AddCommand(command, description, command, args => action());
|
||||
}
|
||||
|
||||
public static void RemoveCommand(string command)
|
||||
{
|
||||
commandList.RemoveAll(x => x.CommandName == command);
|
||||
}
|
||||
|
||||
public static bool CommandExists(string command)
|
||||
{
|
||||
return GetCommand(command) is not null;
|
||||
}
|
||||
|
||||
public static ConsoleCommand? GetCommand(string command)
|
||||
{
|
||||
return commandList.FirstOrDefault(t => t.CommandName == command);
|
||||
}
|
||||
|
||||
public static async Task ExecuteCommad(string command)
|
||||
{
|
||||
var args = command.Split(' ');
|
||||
// Console.WriteLine(command);
|
||||
foreach (var item in commandList.ToList())
|
||||
if (item.CommandName == args[0])
|
||||
{
|
||||
item.Action.Invoke(args);
|
||||
Console.WriteLine();
|
||||
|
||||
while (isDownloading) await Task.Delay(1000);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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 (removeCommandExecution)
|
||||
{
|
||||
Console.SetCursorPosition(0, Console.CursorTop - 1);
|
||||
for (int i = 0; i < command.Length + 30; i++)
|
||||
Console.Write(" ");
|
||||
Console.SetCursorPosition(0, Console.CursorTop);
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
item.Action(args);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
//Console.WriteLine($"Executing: {args[0]} with the following parameters: {args.MergeStrings(1)}");
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using PluginManager.Online.Updates;
|
||||
using PluginManager.Others;
|
||||
|
||||
namespace PluginManager.Loaders;
|
||||
|
||||
internal class LoaderArgs : EventArgs
|
||||
{
|
||||
internal string? PluginName { get; init; }
|
||||
internal string? TypeName { get; init; }
|
||||
internal bool IsLoaded { get; init; }
|
||||
internal Exception? Exception { get; init; }
|
||||
internal object? Plugin { get; init; }
|
||||
}
|
||||
|
||||
internal class Loader<T>
|
||||
{
|
||||
internal Loader(string path, string extension)
|
||||
{
|
||||
this.path = path;
|
||||
this.extension = extension;
|
||||
}
|
||||
|
||||
|
||||
private string path { get; }
|
||||
private string extension { get; }
|
||||
|
||||
|
||||
internal delegate void FileLoadedEventHandler(LoaderArgs args);
|
||||
|
||||
internal delegate void PluginLoadedEventHandler(LoaderArgs args);
|
||||
|
||||
internal event FileLoadedEventHandler? FileLoaded;
|
||||
|
||||
internal event PluginLoadedEventHandler? PluginLoaded;
|
||||
|
||||
internal List<T>? Load()
|
||||
{
|
||||
var list = new List<T>();
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
return null;
|
||||
}
|
||||
|
||||
var files = Directory.GetFiles(path, $"*.{extension}", SearchOption.AllDirectories);
|
||||
foreach (var file in files)
|
||||
{
|
||||
|
||||
Assembly.LoadFrom(file);
|
||||
if (FileLoaded != null)
|
||||
{
|
||||
var args = new LoaderArgs
|
||||
{
|
||||
Exception = null,
|
||||
TypeName = nameof(T),
|
||||
IsLoaded = false,
|
||||
PluginName = new FileInfo(file).Name.Split('.')[0],
|
||||
Plugin = null
|
||||
};
|
||||
FileLoaded.Invoke(args);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var interfaceType = typeof(T);
|
||||
var types = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(a => a.GetTypes())
|
||||
.Where(p => interfaceType.IsAssignableFrom(p) && p.IsClass)
|
||||
.ToArray();
|
||||
|
||||
|
||||
list.Clear();
|
||||
foreach (var type in types)
|
||||
try
|
||||
{
|
||||
var plugin = (T)Activator.CreateInstance(type)!;
|
||||
list.Add(plugin);
|
||||
|
||||
|
||||
if (PluginLoaded != null)
|
||||
PluginLoaded.Invoke(new LoaderArgs
|
||||
{
|
||||
Exception = null,
|
||||
IsLoaded = true,
|
||||
PluginName = type.FullName,
|
||||
TypeName = nameof(T),
|
||||
Plugin = plugin
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (PluginLoaded != null) PluginLoaded.Invoke(new LoaderArgs { Exception = ex, IsLoaded = false, PluginName = type.FullName, TypeName = nameof(T) });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Functions.WriteErrFile(ex.ToString());
|
||||
}
|
||||
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Discord.WebSocket;
|
||||
|
||||
using PluginManager.Interfaces;
|
||||
using PluginManager.Online.Helpers;
|
||||
using PluginManager.Online.Updates;
|
||||
using PluginManager.Others;
|
||||
|
||||
namespace PluginManager.Loaders;
|
||||
|
||||
public class PluginLoader
|
||||
{
|
||||
public delegate void CMDLoaded(string name, string typeName, bool success, Exception? e = null);
|
||||
|
||||
public delegate void EVELoaded(string name, string typeName, bool success, Exception? e = null);
|
||||
|
||||
private const string pluginCMDFolder = @"./Data/Plugins/Commands/";
|
||||
private const string pluginEVEFolder = @"./Data/Plugins/Events/";
|
||||
|
||||
internal const string pluginCMDExtension = "dll";
|
||||
internal const string pluginEVEExtension = "dll";
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
/// <summary>
|
||||
/// Event that is fired when a <see cref="DBCommand" /> is successfully loaded into commands list
|
||||
/// </summary>
|
||||
public CMDLoaded? onCMDLoad;
|
||||
|
||||
/// <summary>
|
||||
/// Event that is fired when a <see cref="DBEvent" /> is successfully loaded into events list
|
||||
/// </summary>
|
||||
public EVELoaded? onEVELoad;
|
||||
|
||||
/// <summary>
|
||||
/// The Plugin Loader constructor
|
||||
/// </summary>
|
||||
/// <param name="discordSocketClient">The discord bot client where the plugins will pe attached to</param>
|
||||
public PluginLoader(DiscordSocketClient discordSocketClient)
|
||||
{
|
||||
_client = discordSocketClient;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A list of <see cref="DBCommand" /> commands
|
||||
/// </summary>
|
||||
public static List<DBCommand>? Commands { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of <see cref="DBEvent" /> commands
|
||||
/// </summary>
|
||||
public static List<DBEvent>? Events { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The main mathod that is called to load all events
|
||||
/// </summary>
|
||||
public async void LoadPlugins()
|
||||
{
|
||||
//Check for updates in commands
|
||||
foreach (var file in Directory.GetFiles("./Data/Plugins/Commands", $"*.{pluginCMDExtension}", SearchOption.AllDirectories))
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
string name = new FileInfo(file).Name.Split('.')[0];
|
||||
if (!Config.PluginVersionsContainsKey(name))
|
||||
Config.SetPluginVersion(name, (await VersionString.GetVersionOfPackageFromWeb(name))?.PackageVersionID + ".0.0");
|
||||
|
||||
if (await PluginUpdater.CheckForUpdates(name))
|
||||
await PluginUpdater.Download(name);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
//Check for updates in events
|
||||
foreach (var file in Directory.GetFiles("./Data/Plugins/Events", $"*.{pluginEVEExtension}", SearchOption.AllDirectories))
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
string name = new FileInfo(file).Name.Split('.')[0];
|
||||
if (!Config.PluginVersionsContainsKey(name))
|
||||
Config.SetPluginVersion(name, (await VersionString.GetVersionOfPackageFromWeb(name))?.PackageVersionID + ".0.0");
|
||||
|
||||
if (await PluginUpdater.CheckForUpdates(name))
|
||||
await PluginUpdater.Download(name);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
//Save the new config file (after the updates)
|
||||
await Config.SaveConfig(SaveType.NORMAL);
|
||||
|
||||
|
||||
//Load all plugins
|
||||
|
||||
Commands = new List<DBCommand>();
|
||||
Events = new List<DBEvent>();
|
||||
|
||||
Functions.WriteLogFile("Starting plugin loader ... Client: " + _client.CurrentUser.Username);
|
||||
Console.WriteLine("Loading plugins");
|
||||
|
||||
var commandsLoader = new Loader<DBCommand>(pluginCMDFolder, pluginCMDExtension);
|
||||
var eventsLoader = new Loader<DBEvent>(pluginEVEFolder, pluginEVEExtension);
|
||||
|
||||
commandsLoader.FileLoaded += OnCommandFileLoaded;
|
||||
commandsLoader.PluginLoaded += OnCommandLoaded;
|
||||
|
||||
eventsLoader.FileLoaded += EventFileLoaded;
|
||||
eventsLoader.PluginLoaded += OnEventLoaded;
|
||||
|
||||
Commands = commandsLoader.Load();
|
||||
Events = eventsLoader.Load();
|
||||
|
||||
}
|
||||
|
||||
private void EventFileLoaded(LoaderArgs e)
|
||||
{
|
||||
if (!e.IsLoaded)
|
||||
{
|
||||
Functions.WriteLogFile($"[EVENT] Event from file [{e.PluginName}] has been successfully created !");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCommandFileLoaded(LoaderArgs e)
|
||||
{
|
||||
if (!e.IsLoaded)
|
||||
{
|
||||
Functions.WriteLogFile($"[CMD] Command from file [{e.PluginName}] has been successfully loaded !");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEventLoaded(LoaderArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (e.IsLoaded)
|
||||
((DBEvent)e.Plugin!).Start(_client);
|
||||
|
||||
onEVELoad?.Invoke(((DBEvent)e.Plugin!).name, e.TypeName!, e.IsLoaded, e.Exception);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
Console.WriteLine("Plugin: " + e.PluginName);
|
||||
Console.WriteLine("Type: " + e.TypeName);
|
||||
Console.WriteLine("IsLoaded: " + e.IsLoaded);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCommandLoaded(LoaderArgs e)
|
||||
{
|
||||
onCMDLoad?.Invoke(((DBCommand)e.Plugin!).Command, e.TypeName!, e.IsLoaded, e.Exception);
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using PluginManager.Others;
|
||||
|
||||
namespace PluginManager.Online.Helpers
|
||||
{
|
||||
internal static class OnlineFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Downloads a <see cref="Stream"/> and saves it to another <see cref="Stream"/>.
|
||||
/// </summary>
|
||||
/// <param name="client">The <see cref="HttpClient"/> that is used to download the file</param>
|
||||
/// <param name="url">The url to the file</param>
|
||||
/// <param name="destination">The <see cref="Stream"/> to save the downloaded data</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>
|
||||
/// <returns></returns>
|
||||
internal static async Task DownloadFileAsync(this HttpClient client, string url, Stream destination, IProgress<float>? progress = null, IProgress<long>? downloadedBytes = null, int bufferSize = 81920, CancellationToken cancellation = default)
|
||||
{
|
||||
using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellation))
|
||||
{
|
||||
var contentLength = response.Content.Headers.ContentLength;
|
||||
|
||||
using (var download = await response.Content.ReadAsStreamAsync(cancellation))
|
||||
{
|
||||
// Ignore progress reporting when no progress reporter was
|
||||
// passed or when the content length is unknown
|
||||
if (progress == null || !contentLength.HasValue)
|
||||
{
|
||||
await download.CopyToAsync(destination, cancellation);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
|
||||
var relativeProgress = new Progress<long>(totalBytes =>
|
||||
{
|
||||
progress.Report((float)totalBytes / contentLength.Value * 100);
|
||||
downloadedBytes?.Report(totalBytes);
|
||||
}
|
||||
);
|
||||
|
||||
// Use extension method to report progress while downloading
|
||||
await download.CopyToOtherStreamAsync(destination, bufferSize, relativeProgress, cancellation);
|
||||
progress.Report(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read contents of a file as string from specified URL
|
||||
/// </summary>
|
||||
/// <param name="url">The URL to read from</param>
|
||||
/// <param name="cancellation">The cancellation token</param>
|
||||
/// <returns></returns>
|
||||
internal static async Task<string> DownloadStringAsync(string url, CancellationToken cancellation = default)
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
return await client.GetStringAsync(url, cancellation);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
using PluginManager.Others;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
|
||||
namespace PluginManager.Online.Helpers
|
||||
{
|
||||
public class VersionString
|
||||
{
|
||||
public int PackageVersionID;
|
||||
public int PackageMainVersion;
|
||||
public int PackageCheckVersion;
|
||||
|
||||
public VersionString(string version)
|
||||
{
|
||||
string[] data = version.Split('.');
|
||||
try
|
||||
{
|
||||
PackageVersionID = int.Parse(data[0]);
|
||||
PackageMainVersion = int.Parse(data[1]);
|
||||
PackageCheckVersion = int.Parse(data[2]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Failed to write Version", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region operators
|
||||
public static bool operator >(VersionString s1, VersionString s2)
|
||||
{
|
||||
if (s1.PackageVersionID > s2.PackageVersionID) return true;
|
||||
if (s1.PackageVersionID == s2.PackageVersionID)
|
||||
{
|
||||
if (s1.PackageMainVersion > s2.PackageMainVersion) return true;
|
||||
if (s1.PackageMainVersion == s2.PackageMainVersion && s1.PackageCheckVersion > s2.PackageCheckVersion) return true;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static bool operator <(VersionString s1, VersionString s2) => !(s1 > s2) && s1 != s2;
|
||||
|
||||
public static bool operator ==(VersionString s1, VersionString s2)
|
||||
{
|
||||
if (s1.PackageVersionID == s2.PackageVersionID && s1.PackageMainVersion == s2.PackageMainVersion && s1.PackageCheckVersion == s2.PackageCheckVersion) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool operator !=(VersionString s1, VersionString s2) => !(s1 == s2);
|
||||
|
||||
public static bool operator <=(VersionString s1, VersionString s2) => (s1 < s2 || s1 == s2);
|
||||
public static bool operator >=(VersionString s1, VersionString s2) => (s1 > s2 || s1 == s2);
|
||||
|
||||
#endregion
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "{PackageID: " + PackageVersionID + ", PackageVersion: " + PackageMainVersion + ", PackageCheckVersion: " + PackageCheckVersion + "}";
|
||||
}
|
||||
|
||||
public string ToShortString()
|
||||
{
|
||||
if (PackageVersionID == 0 && PackageCheckVersion == 0 && PackageMainVersion == 0)
|
||||
return "Unknown";
|
||||
return $"{PackageVersionID}.{PackageMainVersion}.{PackageCheckVersion}";
|
||||
}
|
||||
|
||||
public static VersionString? GetVersionOfPackage(string pakName)
|
||||
{
|
||||
if (!Config.PluginVersionsContainsKey(pakName))
|
||||
return null;
|
||||
return new VersionString(Config.GetPluginVersion(pakName));
|
||||
}
|
||||
|
||||
public static async Task<VersionString?> GetVersionOfPackageFromWeb(string pakName)
|
||||
{
|
||||
string url = "https://raw.githubusercontent.com/Wizzy69/installer/discord-bot-files/Versions";
|
||||
List<string> data = await ServerCom.ReadTextFromURL(url);
|
||||
string? version = (from item in data
|
||||
where !item.StartsWith("#") && item.StartsWith(pakName)
|
||||
select item.Split(',')[1]).FirstOrDefault();
|
||||
if (version == default || version == null) return null;
|
||||
return new VersionString(version);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using PluginManager.Online.Helpers;
|
||||
using PluginManager.Others;
|
||||
|
||||
using OperatingSystem = PluginManager.Others.OperatingSystem;
|
||||
|
||||
namespace PluginManager.Online;
|
||||
|
||||
public class PluginsManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The Plugin Manager constructor
|
||||
/// </summary>
|
||||
/// <param name="link">The link to the file where all plugins are stored</param>
|
||||
public PluginsManager(string link)
|
||||
{
|
||||
PluginsLink = link;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the server
|
||||
/// </summary>
|
||||
public string PluginsLink { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The method to load all plugins
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task ListAvailablePlugins()
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await ServerCom.ReadTextFromURL(PluginsLink);
|
||||
var lines = list.ToArray();
|
||||
|
||||
var data = new List<string[]>();
|
||||
var op = Functions.GetOperatingSystem();
|
||||
|
||||
var len = lines.Length;
|
||||
string[] titles = { "Name", "Description", "Type", "Version", "Installed" };
|
||||
data.Add(new[] { "-", "-", "-", "-", "-" });
|
||||
data.Add(titles);
|
||||
data.Add(new[] { "-", "-", "-", "-", "-" });
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
if (lines[i].Length <= 2)
|
||||
continue;
|
||||
var content = lines[i].Split(',');
|
||||
var display = new string[titles.Length];
|
||||
if (op == OperatingSystem.WINDOWS)
|
||||
{
|
||||
if (content[4].Contains("Windows"))
|
||||
{
|
||||
display[0] = content[0];
|
||||
display[1] = content[1];
|
||||
display[2] = content[2];
|
||||
display[3] = (await VersionString.GetVersionOfPackageFromWeb(content[0]) ?? new VersionString("0.0.0")).ToShortString();
|
||||
if (Config.PluginConfig.Contains(content[0]) || Config.PluginConfig.Contains(content[0]))
|
||||
display[4] = "✓";
|
||||
else
|
||||
display[4] = "X";
|
||||
data.Add(display);
|
||||
}
|
||||
}
|
||||
else if (op == OperatingSystem.LINUX)
|
||||
{
|
||||
if (content[4].Contains("Linux"))
|
||||
{
|
||||
display[0] = content[0];
|
||||
display[1] = content[1];
|
||||
display[2] = content[2];
|
||||
display[3] = (await VersionString.GetVersionOfPackageFromWeb(content[0]) ?? new VersionString("0.0.0")).ToShortString();
|
||||
if (Config.PluginConfig.Contains(content[0]) || Config.PluginConfig.Contains(content[0]))
|
||||
display[4] = "✓";
|
||||
else
|
||||
display[4] = "X";
|
||||
data.Add(display);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.Add(new[] { "-", "-", "-", "-", "-" });
|
||||
|
||||
Console_Utilities.FormatAndAlignTable(data, TableFormat.CENTER_EACH_COLUMN_BASED);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine("Failed to execute command: listplugs\nReason: " + exception.Message);
|
||||
Functions.WriteErrFile(exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method to get plugin information by its name
|
||||
/// </summary>
|
||||
/// <param name="name">The plugin name</param>
|
||||
/// <returns></returns>
|
||||
public async Task<string[]> GetPluginLinkByName(string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await ServerCom.ReadTextFromURL(PluginsLink);
|
||||
var lines = list.ToArray();
|
||||
var len = lines.Length;
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var contents = lines[i].Split(',');
|
||||
if (contents[0] == name)
|
||||
{
|
||||
if (contents.Length == 6)
|
||||
return new[] { contents[2], contents[3], contents[5] };
|
||||
if (contents.Length == 5)
|
||||
return new[] { contents[2], contents[3], string.Empty };
|
||||
throw new Exception("Failed to download plugin. Invalid Argument Length");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine("Failed to execute command: listplugs\nReason: " + exception.Message);
|
||||
Functions.WriteErrFile(exception.ToString());
|
||||
}
|
||||
|
||||
return new string[] { null!, null!, null! };
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
using PluginManager.Online.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using PluginManager.Others;
|
||||
|
||||
namespace PluginManager.Online
|
||||
{
|
||||
public static class ServerCom
|
||||
{
|
||||
/// <summary>
|
||||
/// Read all lines from a file async
|
||||
/// </summary>
|
||||
/// <param name="link">The link of the file</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<List<string>> ReadTextFromURL(string link)
|
||||
{
|
||||
string response = await OnlineFunctions.DownloadStringAsync(link);
|
||||
string[] lines = response.Split('\n');
|
||||
return lines.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download file from url
|
||||
/// </summary>
|
||||
/// <param name="URL">The url to the file</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>
|
||||
/// <returns></returns>
|
||||
public static async Task DownloadFileAsync(string URL, string location, IProgress<float> progress, IProgress<long>? downloadedBytes = null)
|
||||
{
|
||||
using (var client = new System.Net.Http.HttpClient())
|
||||
{
|
||||
client.Timeout = TimeSpan.FromMinutes(5);
|
||||
|
||||
using (var file = new FileStream(location, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
await client.DownloadFileAsync(URL, file, progress, downloadedBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download file from url
|
||||
/// </summary>
|
||||
/// <param name="URL">The url to the file</param>
|
||||
/// <param name="location">The location where to store the downloaded data</param>
|
||||
/// <returns></returns>
|
||||
public static async Task DownloadFileAsync(string URL, string location)
|
||||
{
|
||||
bool isDownloading = true;
|
||||
float c_progress = 0;
|
||||
|
||||
Console_Utilities.ProgressBar pbar = new Console_Utilities.ProgressBar(ProgressBarType.NORMAL) { Max = 100f, NoColor = true };
|
||||
|
||||
IProgress<float> progress = new Progress<float>(percent => { c_progress = percent; });
|
||||
|
||||
|
||||
Task updateProgressBarTask = new Task(() =>
|
||||
{
|
||||
while (isDownloading)
|
||||
{
|
||||
pbar.Update(c_progress);
|
||||
if (c_progress == 100f)
|
||||
break;
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
new Thread(updateProgressBarTask.Start).Start();
|
||||
await DownloadFileAsync(URL, location, progress);
|
||||
|
||||
|
||||
c_progress = pbar.Max;
|
||||
pbar.Update(100f);
|
||||
isDownloading = false;
|
||||
}
|
||||
public static async Task DownloadFileNoProgressAsync(string URL, string location)
|
||||
{
|
||||
IProgress<float> progress = new Progress<float>();
|
||||
await DownloadFileAsync(URL, location, progress);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using PluginManager.Items;
|
||||
using PluginManager.Online.Helpers;
|
||||
using PluginManager.Others;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PluginManager.Online.Updates
|
||||
{
|
||||
public class PluginUpdater
|
||||
{
|
||||
public static async Task<bool> CheckForUpdates(string pakName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var webV = await VersionString.GetVersionOfPackageFromWeb(pakName);
|
||||
var local = VersionString.GetVersionOfPackage(pakName);
|
||||
|
||||
if (local is null) return true;
|
||||
if (webV is null) return false;
|
||||
|
||||
if (webV == local) return false;
|
||||
if (webV > local) return true;
|
||||
}
|
||||
catch (Exception ex) { Console.WriteLine(ex.Message); }
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static async Task<Update> DownloadUpdateInfo(string pakName)
|
||||
{
|
||||
string url = "https://raw.githubusercontent.com/Wizzy69/installer/discord-bot-files/Versions";
|
||||
List<string> info = await ServerCom.ReadTextFromURL(url);
|
||||
VersionString? version = await VersionString.GetVersionOfPackageFromWeb(pakName);
|
||||
|
||||
if (version is null) return Update.Empty;
|
||||
Update update = new Update(pakName, string.Join('\n', info), version);
|
||||
return update;
|
||||
}
|
||||
|
||||
public static async Task Download(string pakName)
|
||||
{
|
||||
Console_Utilities.WriteColorText("An update was found for &g" + pakName + "&c. Version: &r" + (await VersionString.GetVersionOfPackageFromWeb(pakName))?.ToShortString() + "&c. Current Version: &y" + VersionString.GetVersionOfPackage(pakName)?.ToShortString());
|
||||
await ConsoleCommandsHandler.ExecuteCommad("dwplug " + pakName);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using PluginManager.Online.Helpers;
|
||||
|
||||
namespace PluginManager.Online.Updates
|
||||
{
|
||||
public class Update
|
||||
{
|
||||
public static Update Empty = new Update(null, null, null);
|
||||
public string pakName;
|
||||
public string UpdateMessage;
|
||||
|
||||
public VersionString newVersion;
|
||||
|
||||
private bool isEmpty;
|
||||
|
||||
public Update(string pakName, string updateMessage, VersionString newVersion)
|
||||
{
|
||||
this.pakName = pakName;
|
||||
UpdateMessage = updateMessage;
|
||||
this.newVersion = newVersion;
|
||||
|
||||
if (pakName is null && updateMessage is null && newVersion is null)
|
||||
isEmpty = true;
|
||||
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (isEmpty)
|
||||
throw new System.Exception("The update is EMPTY. Can not print information about an empty update !");
|
||||
return $"Package Name: {this.pakName}\n" +
|
||||
$"Update Message: {UpdateMessage}\n" +
|
||||
$"Version: {newVersion.ToString()}";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
|
||||
namespace PluginManager.Others;
|
||||
|
||||
/// <summary>
|
||||
/// A class that handles the sending of messages to the user.
|
||||
/// </summary>
|
||||
public static class ChannelManagement
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the text channel by name from server
|
||||
/// </summary>
|
||||
/// <param name="server">The server</param>
|
||||
/// <param name="name">The channel name</param>
|
||||
/// <returns>
|
||||
/// <see cref="IGuildChannel" />
|
||||
/// </returns>
|
||||
public static IGuildChannel GetTextChannel(this IGuild server, string name)
|
||||
{
|
||||
return server.GetTextChannel(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the voice channel by name from server
|
||||
/// </summary>
|
||||
/// <param name="server">The server</param>
|
||||
/// <param name="name">The channel name</param>
|
||||
/// <returns>
|
||||
/// <see cref="IGuildChannel" />
|
||||
/// </returns>
|
||||
public static IGuildChannel GetVoiceChannel(this IGuild server, string name)
|
||||
{
|
||||
return server.GetVoiceChannel(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the DM channel between <see cref="Discord.WebSocket.DiscordSocketClient" /> and <see cref="IGuildUser" />
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns>
|
||||
/// <see cref="IDMChannel" />
|
||||
/// </returns>
|
||||
public static async Task<IDMChannel> GetDMChannel(IGuildUser user)
|
||||
{
|
||||
return await user.CreateDMChannelAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the channel where the message was sent
|
||||
/// </summary>
|
||||
/// <param name="message">The message</param>
|
||||
/// <returns>
|
||||
/// <see cref="IChannel" />
|
||||
/// </returns>
|
||||
public static IChannel GetChannel(IMessage message)
|
||||
{
|
||||
return message.Channel;
|
||||
}
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
using Discord;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PluginManager.Others
|
||||
{
|
||||
public static class Console_Utilities
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
if (!Config.ContainsKey("TableVariables"))
|
||||
Config.AddValueToVariables("TableVariables", new Dictionary<string, string> { { "DefaultSpace", "3" } }, false);
|
||||
if (!Config.ContainsKey("ColorDataBase"))
|
||||
Config.AddValueToVariables("ColorDataBase", new Dictionary<char, ConsoleColor>()
|
||||
{
|
||||
{ 'g', ConsoleColor.Green },
|
||||
{ 'b', ConsoleColor.Blue },
|
||||
{ 'r', ConsoleColor.Red },
|
||||
{ 'm', ConsoleColor.Magenta },
|
||||
{ 'y', ConsoleColor.Yellow },
|
||||
}, false
|
||||
);
|
||||
|
||||
if (!Config.ContainsKey("ColorPrefix"))
|
||||
Config.AddValueToVariables("ColorPrefix", '&', false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Progress bar object
|
||||
/// </summary>
|
||||
public class ProgressBar
|
||||
{
|
||||
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; }
|
||||
|
||||
private int BarLength = 32;
|
||||
private int position = 1;
|
||||
private bool positive = true;
|
||||
|
||||
private bool isRunning;
|
||||
|
||||
public async void Start()
|
||||
{
|
||||
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 System.Threading.Tasks.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 Update(float progress)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ProgressBarType.NORMAL:
|
||||
UpdateNormal(progress);
|
||||
return;
|
||||
case ProgressBarType.NO_END:
|
||||
if (progress <= 99.9f)
|
||||
UpdateNoEnd();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNoEnd()
|
||||
{
|
||||
Console.CursorLeft = 0;
|
||||
Console.Write("[");
|
||||
for (int i = 1; i <= position; i++)
|
||||
Console.Write(" ");
|
||||
Console.Write("<==()==>");
|
||||
position += positive ? 1 : -1;
|
||||
for (int 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;
|
||||
float onechunk = 30.0f / Max;
|
||||
|
||||
int position = 1;
|
||||
|
||||
for (int i = 0; i < onechunk * progress; i++)
|
||||
{
|
||||
Console.BackgroundColor = NoColor ? ConsoleColor.Black : this.Color;
|
||||
Console.CursorLeft = position++;
|
||||
Console.Write("#");
|
||||
}
|
||||
|
||||
for (int 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) + " % ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static bool CanAproximateTo(this float f, float y) => (MathF.Abs(f - y) < 0.000001);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A way to create a table based on input data
|
||||
/// </summary>
|
||||
/// <param name="data">The List of arrays of strings that represent the rows.</param>
|
||||
public static void FormatAndAlignTable(List<string[]> data, TableFormat format)
|
||||
{
|
||||
if (format == TableFormat.CENTER_EACH_COLUMN_BASED)
|
||||
{
|
||||
char tableLine = '-';
|
||||
char tableCross = '+';
|
||||
char tableWall = '|';
|
||||
|
||||
int[] len = new int[data[0].Length];
|
||||
foreach (var line in data)
|
||||
for (int i = 0; i < line.Length; i++)
|
||||
if (line[i].Length > len[i])
|
||||
len[i] = line[i].Length;
|
||||
|
||||
|
||||
foreach (string[] row in data)
|
||||
{
|
||||
if (row[0][0] == tableLine)
|
||||
Console.Write(tableCross);
|
||||
else
|
||||
Console.Write(tableWall);
|
||||
for (int l = 0; l < row.Length; l++)
|
||||
{
|
||||
if (row[l][0] == tableLine)
|
||||
{
|
||||
for (int i = 0; i < len[l] + 4; ++i)
|
||||
Console.Write(tableLine);
|
||||
}
|
||||
else if (row[l].Length == len[l])
|
||||
{
|
||||
Console.Write(" ");
|
||||
Console.Write(row[l]);
|
||||
Console.Write(" ");
|
||||
}
|
||||
else
|
||||
{
|
||||
int lenHalf = row[l].Length / 2;
|
||||
for (int i = 0; i < ((len[l] + 4) / 2 - lenHalf); ++i)
|
||||
Console.Write(" ");
|
||||
Console.Write(row[l]);
|
||||
for (int i = (len[l] + 4) / 2 + lenHalf + 1; i < len[l] + 4; ++i)
|
||||
Console.Write(" ");
|
||||
if (row[l].Length % 2 == 0)
|
||||
Console.Write(" ");
|
||||
}
|
||||
|
||||
Console.Write(row[l][0] == tableLine ? tableCross : tableWall);
|
||||
}
|
||||
|
||||
Console.WriteLine(); //end line
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (format == TableFormat.CENTER_OVERALL_LENGTH)
|
||||
{
|
||||
int maxLen = 0;
|
||||
foreach (string[] row in data)
|
||||
foreach (string s in row)
|
||||
if (s.Length > maxLen)
|
||||
maxLen = s.Length;
|
||||
|
||||
int div = (maxLen + 4) / 2;
|
||||
|
||||
foreach (string[] row in data)
|
||||
{
|
||||
Console.Write("\t");
|
||||
if (row[0] == "-")
|
||||
Console.Write("+");
|
||||
else
|
||||
Console.Write("|");
|
||||
|
||||
foreach (string s in row)
|
||||
{
|
||||
if (s == "-")
|
||||
{
|
||||
for (int i = 0; i < maxLen + 4; ++i)
|
||||
Console.Write("-");
|
||||
}
|
||||
else if (s.Length == maxLen)
|
||||
{
|
||||
Console.Write(" ");
|
||||
Console.Write(s);
|
||||
Console.Write(" ");
|
||||
}
|
||||
else
|
||||
{
|
||||
int lenHalf = s.Length / 2;
|
||||
for (int i = 0; i < div - lenHalf; ++i)
|
||||
Console.Write(" ");
|
||||
Console.Write(s);
|
||||
for (int i = div + lenHalf + 1; i < maxLen + 4; ++i)
|
||||
Console.Write(" ");
|
||||
if (s.Length % 2 == 0)
|
||||
Console.Write(" ");
|
||||
}
|
||||
|
||||
if (s == "-")
|
||||
Console.Write("+");
|
||||
else
|
||||
Console.Write("|");
|
||||
}
|
||||
|
||||
Console.WriteLine(); //end line
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (format == TableFormat.DEFAULT)
|
||||
{
|
||||
int[] widths = new int[data[0].Length];
|
||||
int space_between_columns = int.Parse(Config.GetValue<Dictionary<string, string>>("TableVariables")?["DefaultSpace"]!);
|
||||
for (int i = 0; i < data.Count; i++)
|
||||
{
|
||||
for (int j = 0; j < data[i].Length; j++)
|
||||
{
|
||||
if (data[i][j].Length > widths[j])
|
||||
widths[j] = data[i][j].Length;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < data.Count; i++)
|
||||
{
|
||||
for (int j = 0; j < data[i].Length; j++)
|
||||
{
|
||||
if (data[i][j] == "-")
|
||||
data[i][j] = " ";
|
||||
Console.Write(data[i][j]);
|
||||
for (int k = 0; k < widths[j] - data[i][j].Length + 1 + space_between_columns; k++)
|
||||
Console.Write(" ");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception("Unknown type of table");
|
||||
}
|
||||
|
||||
public static void WriteColorText(string text, bool appendNewLineAtEnd = true)
|
||||
{
|
||||
ConsoleColor initialForeGround = Console.ForegroundColor;
|
||||
char[] input = text.ToCharArray();
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (input[i] == Config.GetValue<char>("ColorPrefix"))
|
||||
{
|
||||
if (i + 1 < input.Length)
|
||||
{
|
||||
if (Config.GetValue<Dictionary<char, ConsoleColor>>("ColorDataBase")!.ContainsKey(input[i + 1]))
|
||||
{
|
||||
Console.ForegroundColor = Config.GetValue<Dictionary<char, ConsoleColor>>("ColorDataBase")![input[i + 1]];
|
||||
i++;
|
||||
}
|
||||
else if (input[i + 1] == 'c')
|
||||
{
|
||||
Console.ForegroundColor = initialForeGround;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
Console.Write(input[i]);
|
||||
}
|
||||
|
||||
Console.ForegroundColor = initialForeGround;
|
||||
if (appendNewLineAtEnd)
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using PluginManager.Interfaces;
|
||||
|
||||
namespace PluginManager.Others;
|
||||
|
||||
/// <summary>
|
||||
/// A list of operating systems
|
||||
/// </summary>
|
||||
public enum OperatingSystem
|
||||
{
|
||||
WINDOWS, LINUX, MAC_OS, UNKNOWN
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list with all errors
|
||||
/// </summary>
|
||||
public enum Error
|
||||
{
|
||||
UNKNOWN_ERROR, GUILD_NOT_FOUND, STREAM_NOT_FOUND, INVALID_USER, INVALID_CHANNEL, INVALID_PERMISSIONS
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The output log type
|
||||
/// </summary>
|
||||
public enum OutputLogLevel { NONE, INFO, WARNING, ERROR, CRITICAL }
|
||||
|
||||
/// <summary>
|
||||
/// Plugin Type
|
||||
/// </summary>
|
||||
public enum PluginType { Command, Event, Unknown }
|
||||
|
||||
public enum UnzipProgressType { PercentageFromNumberOfFiles, PercentageFromTotalSize }
|
||||
|
||||
public enum TableFormat { CENTER_EACH_COLUMN_BASED, CENTER_OVERALL_LENGTH, DEFAULT }
|
||||
|
||||
public enum SaveType { NORMAL, BACKUP }
|
||||
public enum ProgressBarType { NORMAL, NO_END }
|
||||
@@ -1,372 +0,0 @@
|
||||
using System.IO.Compression;
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using Discord.WebSocket;
|
||||
using PluginManager.Items;
|
||||
using System.Threading;
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
|
||||
namespace PluginManager.Others
|
||||
{
|
||||
/// <summary>
|
||||
/// A special class with functions
|
||||
/// </summary>
|
||||
public static class Functions
|
||||
{
|
||||
/// <summary>
|
||||
/// The location for the Resources folder
|
||||
/// </summary>
|
||||
public static readonly string dataFolder = @"./Data/Resources/";
|
||||
|
||||
/// <summary>
|
||||
/// The location for all logs
|
||||
/// </summary>
|
||||
public static readonly string logFolder = @"./Data/Output/Logs/";
|
||||
|
||||
/// <summary>
|
||||
/// The location for all errors
|
||||
/// </summary>
|
||||
public static readonly string errFolder = @"./Data/Output/Errors/";
|
||||
|
||||
/// <summary>
|
||||
/// Archives folder
|
||||
/// </summary>
|
||||
public static readonly string pakFolder = @"./Data/PAKS/";
|
||||
|
||||
/// <summary>
|
||||
/// Beta testing folder
|
||||
/// </summary>
|
||||
public static readonly string betaFolder = @"./Data/BetaTest/";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Read data from a file that is inside an archive (ZIP format)
|
||||
/// </summary>
|
||||
/// <param name="FileName">The file name that is inside the archive or its full path</param>
|
||||
/// <param name="archFile">The archive location from the PAKs folder</param>
|
||||
/// <returns>A string that represents the content of the file or null if the file does not exists or it has no content</returns>
|
||||
public static async Task<string> ReadFromPakAsync(string FileName, string archFile)
|
||||
{
|
||||
archFile = pakFolder + archFile;
|
||||
if (!File.Exists(archFile))
|
||||
throw new Exception("Failed to load file !");
|
||||
|
||||
try
|
||||
{
|
||||
string textValue = null;
|
||||
using (var fs = new FileStream(archFile, FileMode.Open))
|
||||
using (var zip = new ZipArchive(fs, ZipArchiveMode.Read))
|
||||
foreach (var entry in zip.Entries)
|
||||
{
|
||||
if (entry.Name == FileName || entry.FullName == FileName)
|
||||
{
|
||||
using (Stream s = entry.Open())
|
||||
using (StreamReader reader = new StreamReader(s))
|
||||
{
|
||||
textValue = await reader.ReadToEndAsync();
|
||||
reader.Close();
|
||||
s.Close();
|
||||
fs.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return textValue;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await Task.Delay(100);
|
||||
return await ReadFromPakAsync(FileName, archFile);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Write logs to file
|
||||
/// </summary>
|
||||
/// <param name="LogMessage">The message to be wrote</param>
|
||||
public static void WriteLogFile(string LogMessage)
|
||||
{
|
||||
string logsPath = logFolder + $"{DateTime.Today.ToShortDateString().Replace("/", "-").Replace("\\", "-")} Log.txt";
|
||||
Directory.CreateDirectory(logFolder);
|
||||
File.AppendAllText(logsPath, LogMessage + " \n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write error to file
|
||||
/// </summary>
|
||||
/// <param name="ErrMessage">The message to be wrote</param>
|
||||
public static void WriteErrFile(string ErrMessage)
|
||||
{
|
||||
string errPath = errFolder + $"{DateTime.Today.ToShortDateString().Replace("/", "-").Replace("\\", "-")} Error.txt";
|
||||
Directory.CreateDirectory(errFolder);
|
||||
File.AppendAllText(errPath, ErrMessage + " \n");
|
||||
}
|
||||
|
||||
public static void WriteErrFile(this Exception ex)
|
||||
{
|
||||
WriteErrFile(ex.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge one array of strings into one string
|
||||
/// </summary>
|
||||
/// <param name="s">The array of strings</param>
|
||||
/// <param name="indexToStart">The index from where the merge should start (included)</param>
|
||||
/// <returns>A string built based on the array</returns>
|
||||
public static string MergeStrings(this string[] s, int indexToStart)
|
||||
{
|
||||
string r = "";
|
||||
int len = s.Length;
|
||||
if (len <= indexToStart) return "";
|
||||
for (int i = indexToStart; i < len - 1; ++i)
|
||||
{
|
||||
r += s[i] + " ";
|
||||
}
|
||||
|
||||
r += s[len - 1];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Operating system you are runnin on
|
||||
/// </summary>
|
||||
/// <returns>An Operating system</returns>
|
||||
public static OperatingSystem GetOperatingSystem()
|
||||
{
|
||||
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) return OperatingSystem.WINDOWS;
|
||||
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux)) return OperatingSystem.LINUX;
|
||||
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX)) return OperatingSystem.MAC_OS;
|
||||
return OperatingSystem.UNKNOWN;
|
||||
}
|
||||
|
||||
public static List<string> GetArguments(SocketMessage message)
|
||||
{
|
||||
Command command = new Command(message);
|
||||
return command.Arguments;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy one Stream to another <see langword="async"/>
|
||||
/// </summary>
|
||||
/// <param name="stream">The base stream</param>
|
||||
/// <param name="destination">The destination stream</param>
|
||||
/// <param name="bufferSize">The buffer to read</param>
|
||||
/// <param name="progress">The progress</param>
|
||||
/// <param name="cancellationToken">The cancellation token</param>
|
||||
/// <exception cref="ArgumentNullException">Triggered if any <see cref="Stream"/> is empty</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="ArgumentException">Triggered in <paramref name="destination"/> is not writable</exception>
|
||||
public static async Task CopyToOtherStreamAsync(this Stream stream, Stream destination, int bufferSize, IProgress<long>? progress = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (stream == null) throw new ArgumentNullException(nameof(stream));
|
||||
if (destination == null) throw new ArgumentNullException(nameof(destination));
|
||||
if (bufferSize <= 0) throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||
if (!stream.CanRead) throw new InvalidOperationException("The stream is not readable.");
|
||||
if (!destination.CanWrite) throw new ArgumentException("Destination stream is not writable", nameof(destination));
|
||||
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
long totalBytesRead = 0;
|
||||
int bytesRead;
|
||||
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||
totalBytesRead += bytesRead;
|
||||
progress?.Report(totalBytesRead);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract zip to location
|
||||
/// </summary>
|
||||
/// <param name="zip">The zip location</param>
|
||||
/// <param name="folder">The target location</param>
|
||||
/// <param name="progress">The progress that is updated as a file is processed</param>
|
||||
/// <param name="type">The type of progress</param>
|
||||
/// <returns></returns>
|
||||
public static async Task ExtractArchive(string zip, string folder, IProgress<float> progress, UnzipProgressType type)
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
using (ZipArchive archive = ZipFile.OpenRead(zip))
|
||||
{
|
||||
if (type == UnzipProgressType.PercentageFromNumberOfFiles)
|
||||
{
|
||||
int totalZIPFiles = archive.Entries.Count();
|
||||
int currentZIPFile = 0;
|
||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||
{
|
||||
if (entry.FullName.EndsWith("/")) // it is a folder
|
||||
Directory.CreateDirectory(Path.Combine(folder, entry.FullName));
|
||||
|
||||
else
|
||||
try
|
||||
{
|
||||
entry.ExtractToFile(Path.Combine(folder, entry.FullName), true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to extract {entry.Name}. Exception: {ex.Message}");
|
||||
}
|
||||
|
||||
currentZIPFile++;
|
||||
await Task.Delay(10);
|
||||
if (progress != null)
|
||||
progress.Report((float)currentZIPFile / totalZIPFiles * 100);
|
||||
}
|
||||
}
|
||||
else if (type == UnzipProgressType.PercentageFromTotalSize)
|
||||
{
|
||||
ulong zipSize = 0;
|
||||
|
||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||
zipSize += (ulong)entry.CompressedLength;
|
||||
|
||||
ulong currentSize = 0;
|
||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||
{
|
||||
if (entry.FullName.EndsWith("/"))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(folder, entry.FullName));
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
entry.ExtractToFile(Path.Combine(folder, entry.FullName), true);
|
||||
currentSize += (ulong)entry.CompressedLength;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to extract {entry.Name}. Exception: {ex.Message}");
|
||||
}
|
||||
|
||||
await Task.Delay(10);
|
||||
if (progress != null)
|
||||
progress.Report((float)currentSize / zipSize * 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Convert Bytes to highest measurement unit possible
|
||||
/// </summary>
|
||||
/// <param name="bytes">The amount of bytes</param>
|
||||
/// <returns></returns>
|
||||
public static (double, string) ConvertBytes(long bytes)
|
||||
{
|
||||
List<string> units = new List<string>()
|
||||
{
|
||||
"B",
|
||||
"KB",
|
||||
"MB",
|
||||
"GB",
|
||||
"TB"
|
||||
};
|
||||
int i = 0;
|
||||
while (bytes >= 1024)
|
||||
{
|
||||
i++;
|
||||
bytes /= 1024;
|
||||
}
|
||||
|
||||
return (bytes, units[i]);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
MemoryStream str = new MemoryStream();
|
||||
await JsonSerializer.SerializeAsync(str, Data, typeof(T), new JsonSerializerOptions { WriteIndented = true });
|
||||
await File.WriteAllBytesAsync(file, str.ToArray());
|
||||
}
|
||||
|
||||
/// <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);
|
||||
text.Close();
|
||||
return (obj ?? default)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if all words from <paramref name="str"/> are in <paramref name="baseString"/><br/>
|
||||
/// This function returns true if<br/>
|
||||
/// 1. The <paramref name="str"/> is part of <paramref name="baseString"/><br/>
|
||||
/// 2. The words (split by a space) of <paramref name="str"/> are located (separately) in <paramref name="baseString"/> <br/>
|
||||
/// <example>
|
||||
/// The following example will return <see langword="TRUE"/><br/>
|
||||
/// <c>STRContains("Hello World !", "I type word Hello and then i typed word World !")</c><br/>
|
||||
/// The following example will return <see langword="TRUE"/><br/>
|
||||
/// <c>STRContains("Hello World !", "I typed Hello World !" </c><br/>
|
||||
/// The following example will return <see langword="TRUE"/><br/>
|
||||
/// <c>STRContains("Hello World", "I type World then Hello")</c><br/>
|
||||
/// The following example will return <see langword="FALSE"/><br/>
|
||||
/// <c>STRContains("Hello World !", "I typed Hello World")</c><br/>
|
||||
/// </example>
|
||||
/// </summary>
|
||||
/// <param name="str">The string you are checking</param>
|
||||
/// <param name="baseString">The main string that should contain <paramref name="str"/></param>
|
||||
/// <returns></returns>
|
||||
public static bool STRContains(this string str, string baseString)
|
||||
{
|
||||
if (baseString.Contains(str)) return true;
|
||||
string[] array = str.Split(' ');
|
||||
foreach (var s in array)
|
||||
if (!baseString.Contains(s))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryReadValueFromJson(string input, string codeName, out JsonElement element)
|
||||
{
|
||||
Stream text;
|
||||
if (File.Exists(input))
|
||||
text = File.OpenRead(input);
|
||||
|
||||
else
|
||||
text = new MemoryStream(Encoding.ASCII.GetBytes(input));
|
||||
|
||||
var jsonObject = JsonDocument.Parse(text);
|
||||
|
||||
var data = jsonObject.RootElement.TryGetProperty(codeName, out element);
|
||||
return data;
|
||||
}
|
||||
|
||||
public static string CreateMD5(string input)
|
||||
{
|
||||
using (MD5 md5 = MD5.Create())
|
||||
{
|
||||
byte[] inputBytes = Encoding.ASCII.GetBytes(input);
|
||||
byte[] hashBytes = md5.ComputeHash(inputBytes);
|
||||
return Convert.ToHexString(hashBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="BlankWindow1.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net" Version="3.7.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
50
Plugins/LevelingSystem/LevelCommand.cs
Normal file
50
Plugins/LevelingSystem/LevelCommand.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Discord;
|
||||
using DiscordBotCore.PluginCore.Helpers.Execution.DbCommand;
|
||||
using DiscordBotCore.PluginCore.Interfaces;
|
||||
|
||||
namespace LevelingSystem;
|
||||
|
||||
internal class LevelCommand: IDbCommand
|
||||
{
|
||||
public string Command => "level";
|
||||
|
||||
public List<string> Aliases => ["lvl", "rank"];
|
||||
|
||||
public string Description => "Display your current level";
|
||||
|
||||
public string Usage => "level";
|
||||
|
||||
public bool RequireAdmin => false;
|
||||
|
||||
public async Task ExecuteServer(IDbCommandExecutingArgument args)
|
||||
{
|
||||
if(Variables.Database is null)
|
||||
{
|
||||
args.Logger.Log("Database is not initialized", this);
|
||||
return;
|
||||
}
|
||||
|
||||
object[]? user = await Variables.Database.ReadDataArrayAsync($"SELECT * FROM Levels WHERE UserID=@userId",
|
||||
new KeyValuePair<string, object>("userId", args.Context.Message.Author.Id));
|
||||
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
await args.Context.Channel.SendMessageAsync("You are now unranked !");
|
||||
return;
|
||||
}
|
||||
|
||||
var level = (long)user[1];
|
||||
var exp = (long)user[2];
|
||||
|
||||
var builder = new EmbedBuilder();
|
||||
var r = new Random();
|
||||
builder.WithColor(r.Next(256), r.Next(256), r.Next(256));
|
||||
builder.AddField("Current Level", level, true)
|
||||
.AddField("Current EXP", exp, true)
|
||||
.AddField("Required Exp", (level * 8 + 24).ToString(), true);
|
||||
builder.WithTimestamp(DateTimeOffset.Now);
|
||||
builder.WithAuthor(args.Context.Message.Author);
|
||||
await args.Context.Channel.SendMessageAsync(embed: builder.Build());
|
||||
}
|
||||
}
|
||||
83
Plugins/LevelingSystem/LevelEvent.cs
Normal file
83
Plugins/LevelingSystem/LevelEvent.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System.Net.Mime;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Database.Sqlite;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.PluginCore;
|
||||
using DiscordBotCore.PluginCore.Helpers;
|
||||
using DiscordBotCore.PluginCore.Helpers.Execution.DbEvent;
|
||||
using DiscordBotCore.PluginCore.Interfaces;
|
||||
using static LevelingSystem.Variables;
|
||||
|
||||
namespace LevelingSystem;
|
||||
|
||||
internal class LevelEvent : IDbEvent
|
||||
{
|
||||
public string Name => "Leveling System Event Handler";
|
||||
public string Description => "The Leveling System Event Handler";
|
||||
|
||||
public async void Start(IDbEventExecutingArgument args)
|
||||
{
|
||||
args.Logger.Log("Starting Leveling System Event Handler", this);
|
||||
Directory.CreateDirectory(DataFolder);
|
||||
await Task.Delay(200);
|
||||
Database = new SqlDatabase(DataFolder + "Users.db");
|
||||
await Database.Open();
|
||||
|
||||
|
||||
if (!File.Exists(DataFolder + "Settings.txt"))
|
||||
{
|
||||
GlobalSettings = new Settings
|
||||
{
|
||||
SecondsToWaitBetweenMessages = 5,
|
||||
MaxExp = 7,
|
||||
MinExp = 1
|
||||
};
|
||||
|
||||
await DiscordBotCore.Utilities.JsonManager.SaveToJsonFile(DataFolder + "Settings.txt", GlobalSettings);
|
||||
}
|
||||
else
|
||||
GlobalSettings = await DiscordBotCore.Utilities.JsonManager.ConvertFromJson<Settings>(DataFolder + "Settings.txt");
|
||||
|
||||
if (!await Database.TableExistsAsync("Levels"))
|
||||
await Database.CreateTableAsync("Levels", "UserID VARCHAR(128)", "Level INT", "EXP INT");
|
||||
|
||||
if (!await Database.TableExistsAsync("Users"))
|
||||
await Database.CreateTableAsync("Users", "UserID VARCHAR(128)", "UserMention VARCHAR(128)", "Username VARCHAR(128)", "Discriminator VARCHAR(128)");
|
||||
|
||||
args.Client.MessageReceived += (message) => ClientOnMessageReceived(message, args.BotPrefix);
|
||||
}
|
||||
|
||||
private async Task ClientOnMessageReceived(SocketMessage arg, string botPrefix)
|
||||
{
|
||||
if (arg.Author.IsBot || arg.IsTTS || arg.Content.StartsWith(botPrefix))
|
||||
return;
|
||||
|
||||
if (WaitingList.ContainsKey(arg.Author.Id) && WaitingList[arg.Author.Id] > DateTime.Now.AddSeconds(-GlobalSettings.SecondsToWaitBetweenMessages))
|
||||
return;
|
||||
|
||||
var userID = arg.Author.Id.ToString();
|
||||
|
||||
object[] userData = await Database.ReadDataArrayAsync($"SELECT * FROM Levels WHERE userID='{userID}'");
|
||||
if (userData is null)
|
||||
{
|
||||
await Database.ExecuteAsync($"INSERT INTO Levels (UserID, Level, EXP) VALUES ('{userID}', 1, 0)");
|
||||
await Database.ExecuteAsync($"INSERT INTO Users (UserID, UserMention) VALUES ('{userID}', '{arg.Author.Mention}')");
|
||||
return;
|
||||
}
|
||||
|
||||
var level = (long)userData[1];
|
||||
var exp = (long)userData[2];
|
||||
|
||||
|
||||
var random = new Random().Next(GlobalSettings.MinExp, GlobalSettings.MaxExp);
|
||||
if (exp + random >= level * 8 + 24)
|
||||
{
|
||||
await Database.ExecuteAsync($"UPDATE Levels SET Level={level + 1}, EXP={random - (level * 8 + 24 - exp)} WHERE UserID='{userID}'");
|
||||
await arg.Channel.SendMessageAsync($"{arg.Author.Mention} has leveled up to level {level + 1}!");
|
||||
}
|
||||
else await Database.ExecuteAsync($"UPDATE Levels SET EXP={exp + random} WHERE UserID='{userID}'");
|
||||
|
||||
WaitingList.Add(arg.Author.Id, DateTime.Now);
|
||||
}
|
||||
|
||||
}
|
||||
17
Plugins/LevelingSystem/LevelingSystem.csproj
Normal file
17
Plugins/LevelingSystem/LevelingSystem.csproj
Normal file
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>AnyCPU;x64;ARM64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DiscordBotCore.Database.Sqlite\DiscordBotCore.Database.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\DiscordBotCore.Logging\DiscordBotCore.Logging.csproj" />
|
||||
<ProjectReference Include="..\..\DiscordBotCore.PluginCore\DiscordBotCore.PluginCore.csproj" />
|
||||
<ProjectReference Include="..\..\DiscordBotCore.Utilities\DiscordBotCore.Utilities.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
18
Plugins/LevelingSystem/Settings.cs
Normal file
18
Plugins/LevelingSystem/Settings.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using DiscordBotCore.Database.Sqlite;
|
||||
|
||||
namespace LevelingSystem;
|
||||
|
||||
public class Settings
|
||||
{
|
||||
public int SecondsToWaitBetweenMessages { get; set; }
|
||||
public int MinExp { get; set; }
|
||||
public int MaxExp { get; set; }
|
||||
}
|
||||
|
||||
internal static class Variables
|
||||
{
|
||||
internal static readonly string DataFolder = "./Data/Resources/LevelingSystem/";
|
||||
internal static SqlDatabase? Database;
|
||||
internal static readonly Dictionary<ulong, DateTime> WaitingList = new();
|
||||
internal static Settings GlobalSettings = new();
|
||||
}
|
||||
32
Plugins/PollMaker/Internal/PollState.cs
Normal file
32
Plugins/PollMaker/Internal/PollState.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace PollMaker.Internal;
|
||||
|
||||
internal sealed record PollState(string Question, string[] Options)
|
||||
{
|
||||
public List<HashSet<ulong>> Votes { get; } =
|
||||
Enumerable.Range(0, Options.Length).Select(_ => new HashSet<ulong>()).ToList();
|
||||
|
||||
public bool IsOpen { get; private set; } = true;
|
||||
|
||||
public void Close() => IsOpen = false;
|
||||
|
||||
/// <summary>
|
||||
/// Toggle the member’s vote.
|
||||
/// Clicking the **same button** again removes their vote;
|
||||
/// clicking a **different** button moves the vote.
|
||||
/// </summary>
|
||||
public void ToggleVote(int optionIdx, ulong userId)
|
||||
{
|
||||
if (!IsOpen) return;
|
||||
|
||||
if (Votes[optionIdx].Contains(userId))
|
||||
{
|
||||
Votes[optionIdx].Remove(userId);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var set in Votes)
|
||||
set.Remove(userId);
|
||||
|
||||
Votes[optionIdx].Add(userId);
|
||||
}
|
||||
}
|
||||
17
Plugins/PollMaker/PollMaker.csproj
Normal file
17
Plugins/PollMaker/PollMaker.csproj
Normal file
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DiscordBotCore.PluginCore\DiscordBotCore.PluginCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Commands\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
173
Plugins/PollMaker/SlashCommands/MakePoll.cs
Normal file
173
Plugins/PollMaker/SlashCommands/MakePoll.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.PluginCore.Interfaces;
|
||||
using PollMaker.Internal;
|
||||
|
||||
namespace PollMaker.SlashCommands;
|
||||
|
||||
public class MakePoll : IDbSlashCommand
|
||||
{
|
||||
public string Name => "make-poll";
|
||||
public string Description => "Create an interactive poll (2-25 answers, optional timer)";
|
||||
public bool CanUseDm => false;
|
||||
public bool HasInteraction => true;
|
||||
|
||||
// ─────────── slash-command schema ───────────
|
||||
public List<SlashCommandOptionBuilder> Options =>
|
||||
[
|
||||
new SlashCommandOptionBuilder
|
||||
{
|
||||
Name = "question",
|
||||
Description = "The poll question",
|
||||
Type = ApplicationCommandOptionType.String,
|
||||
IsRequired = true
|
||||
},
|
||||
new SlashCommandOptionBuilder
|
||||
{
|
||||
Name = "answers",
|
||||
Description = "Answers separated with ';' (min 2, max 25)",
|
||||
Type = ApplicationCommandOptionType.String,
|
||||
IsRequired = true
|
||||
},
|
||||
new SlashCommandOptionBuilder
|
||||
{
|
||||
Name = "timed",
|
||||
Description = "Close the poll automatically after a given duration",
|
||||
Type = ApplicationCommandOptionType.Boolean,
|
||||
IsRequired = false
|
||||
},
|
||||
new SlashCommandOptionBuilder
|
||||
{
|
||||
Name = "duration",
|
||||
Description = "Duration in **hours** (1-168) – required if timed = true",
|
||||
Type = ApplicationCommandOptionType.Integer,
|
||||
MinValue = 1,
|
||||
MaxValue = 168,
|
||||
IsRequired = false
|
||||
}
|
||||
];
|
||||
|
||||
// ─────────── in-memory cache ───────────
|
||||
private static readonly ConcurrentDictionary<ulong, PollState> Polls = new();
|
||||
|
||||
// ─────────── slash-command handler ───────────
|
||||
public async void ExecuteServer(ILogger log, SocketSlashCommand ctx)
|
||||
{
|
||||
string q = ctx.Data.Options.First(o => o.Name == "question").Value!.ToString()!.Trim();
|
||||
string raw = ctx.Data.Options.First(o => o.Name == "answers" ).Value!.ToString()!;
|
||||
|
||||
bool timed = ctx.Data.Options.FirstOrDefault(o => o.Name == "timed")?.Value is bool b && b;
|
||||
int hours = ctx.Data.Options.FirstOrDefault(o => o.Name == "duration")?.Value is long l ? (int)l : 0;
|
||||
|
||||
if (timed && hours == 0)
|
||||
{
|
||||
await ctx.RespondAsync("❗ When `timed` is **true**, you must supply a `duration` (1-168 hours).",
|
||||
ephemeral: true);
|
||||
return;
|
||||
}
|
||||
|
||||
var opts = raw.Split(';', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(a => a.Trim())
|
||||
.Where(a => a.Length > 0)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
if (opts.Length < 2 || opts.Length > 25)
|
||||
{
|
||||
await ctx.RespondAsync($"❗ You must supply **2-25** answers; you supplied {opts.Length}.",
|
||||
ephemeral: true);
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle($"📊 {q}")
|
||||
.WithDescription(string.Join('\n', opts.Select((o,i) => $"{i+1}. {o}")))
|
||||
.WithColor(Color.Purple)
|
||||
.WithFooter(timed
|
||||
? $"Click a button to vote • click again to un-vote • closes in {hours} h"
|
||||
: "Click a button to vote • click again to un-vote")
|
||||
.Build();
|
||||
|
||||
var cb = new ComponentBuilder();
|
||||
for (int i = 0; i < opts.Length; i++)
|
||||
cb.WithButton(label: $"{i+1}",
|
||||
customId: $"poll:{ctx.Id}:{i}", // poll:{slashId}:{idx}
|
||||
style: ButtonStyle.Secondary,
|
||||
row: i / 5);
|
||||
|
||||
await ctx.RespondAsync(embed: embed, components: cb.Build());
|
||||
var msg = await ctx.GetOriginalResponseAsync();
|
||||
|
||||
var state = new PollState(q, opts);
|
||||
Polls[msg.Id] = state;
|
||||
|
||||
if (timed)
|
||||
{
|
||||
_ = ClosePollLaterAsync(log, msg, state, hours);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ExecuteInteraction(ILogger log, SocketInteraction interaction)
|
||||
{
|
||||
if (interaction is not SocketMessageComponent btn || !btn.Data.CustomId.StartsWith("poll:"))
|
||||
return;
|
||||
|
||||
if (!Polls.TryGetValue(btn.Message.Id, out var poll))
|
||||
{
|
||||
await btn.RespondAsync("This poll is no longer active.", ephemeral: true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!poll.IsOpen)
|
||||
{
|
||||
await btn.RespondAsync("The poll has already closed.", ephemeral: true);
|
||||
return;
|
||||
}
|
||||
|
||||
var optionIdx = int.Parse(btn.Data.CustomId.Split(':')[2]);
|
||||
|
||||
poll.ToggleVote(optionIdx, btn.User.Id);
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle($"📊 {poll.Question}")
|
||||
.WithDescription(string.Join('\n',
|
||||
poll.Options.Select((o,i) => $"{i+1}. {o} — **{poll.Votes[i].Count}**")))
|
||||
.WithColor(Color.Purple)
|
||||
.WithFooter("Click a button to vote • click again to un-vote")
|
||||
.Build();
|
||||
|
||||
await btn.Message.ModifyAsync(m => m.Embed = embed);
|
||||
await btn.DeferAsync();
|
||||
}
|
||||
private static async Task ClosePollLaterAsync(ILogger log, IUserMessage msg, PollState poll, int hours)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromHours(hours));
|
||||
|
||||
poll.Close();
|
||||
|
||||
var closedEmbed = new EmbedBuilder()
|
||||
.WithTitle($"📊 {poll.Question} — closed")
|
||||
.WithDescription(string.Join('\n',
|
||||
poll.Options.Select((o,i) => $"{i+1}. {o} — **{poll.Votes[i].Count}**")))
|
||||
.WithColor(Color.DarkGrey)
|
||||
.WithFooter($"Poll closed after {hours} h • thanks for voting!")
|
||||
.Build();
|
||||
|
||||
await msg.ModifyAsync(m =>
|
||||
{
|
||||
m.Embed = closedEmbed;
|
||||
m.Components = new ComponentBuilder().Build();
|
||||
});
|
||||
|
||||
Polls.TryRemove(msg.Id, out _);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.LogException(ex, typeof(MakePoll));
|
||||
}
|
||||
}
|
||||
}
|
||||
224
README.md
224
README.md
@@ -1,98 +1,104 @@
|
||||
|
||||
# Seth Discord Bot
|
||||
|
||||
This is a Discord Bot made with C# that accepts plugins as extensions for more commands and events. All basic commands are built in already in the PluginManager class library.
|
||||
This project is based on:
|
||||
|
||||
- [.NET 6 (C#)](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
|
||||
- [.NET 8 (C#)](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||
- [Discord.Net](https://github.com/discord-net/Discord.Net)
|
||||
|
||||
|
||||
## Plugins
|
||||
#### Requirements:
|
||||
- [Visual Studio](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&channel=Release&version=VS2022&source=VSLandingPage&cid=2030&passive=false)
|
||||
- .NET 6 (downloaded with Visual Studio)
|
||||
- The source code for this plugins can be found in the [Plugins](./Plugins) folder.
|
||||
|
||||
Plugin Types:
|
||||
1. Commands
|
||||
2. Events
|
||||
3. Slash Commands
|
||||
|
||||
### How to create a plugin
|
||||
|
||||
First of all, Create a new project (class library) in Visual Studio.
|
||||

|
||||
#### Requirements:
|
||||
- [Visual Studio](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&channel=Release&version=VS2022&source=VSLandingPage&cid=2030&passive=false)
|
||||
- .NET 8 (downloaded with Visual Studio)
|
||||
|
||||

|
||||
First of all, create a new project (class library) in Visual Studio.
|
||||
Then import the PluginManager as reference to your project.
|
||||
|
||||

|
||||
## 1. Commands
|
||||
|
||||

|
||||
|
||||
Now, let's add the PluginManager reference. It can be found inside the bot's main folder under
|
||||
`DiscordBot/bin/Debug/net6.0/PluginManager.dll` or `PluginManager/bin/Debug/net6.0/PluginManager.dll`
|
||||
after one successfull build.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
1. Commands
|
||||
|
||||
Commands are loaded when all plugins are loaded into memory. When an user executes the command, only then the Execute function is called.
|
||||
Commands are loaded when all plugins are loaded into memory. The Execute method is called whenever any user (that respects the `requireAdmin` propery) calls the command using the bot prefix and the `Command`.
|
||||
Commands are plugins that allow users to interact with them.
|
||||
Here is an example of class that is a command class
|
||||
Here is an example:
|
||||
```cs
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
|
||||
using PluginManager.Interfaces;
|
||||
|
||||
namespace CMD_Utils
|
||||
namespace LevelingSystem;
|
||||
|
||||
public class LevelCommand : DBCommand
|
||||
{
|
||||
class FlipCoin : DBCommand
|
||||
public string Command => "level";
|
||||
|
||||
public List<string> Aliases => new() { "lvl" };
|
||||
|
||||
public string Description => "Display your current level";
|
||||
|
||||
public string Usage => "level";
|
||||
|
||||
public bool requireAdmin => false;
|
||||
|
||||
public async void ExecuteServer(DBCommandExecutingArguments context)
|
||||
{
|
||||
public string Command => "flip";
|
||||
|
||||
public string Description => "Flip a coin";
|
||||
|
||||
public string Usage => "flip";
|
||||
|
||||
public bool canUseDM => true;
|
||||
|
||||
public bool canUseServer => true;
|
||||
|
||||
public bool requireAdmin => false;
|
||||
|
||||
public async void Execute(SocketCommandContext context, SocketMessage message, DiscordSocketClient client, bool isDM)
|
||||
//Variables.database is a sql connection that is defined in an auxiliary file in the same namespace as this class
|
||||
object[] user = await Variables.database.ReadDataArrayAsync($"SELECT * FROM Levels WHERE UserID='{context.Message.Author.Id}'");
|
||||
if (user is null)
|
||||
{
|
||||
System.Random random = new System.Random();
|
||||
int r = random.Next(1, 3);
|
||||
if (r == 1)
|
||||
await message.Channel.SendMessageAsync("Heads");
|
||||
else await message.Channel.SendMessageAsync("Tails");
|
||||
await context.Channel.SendMessageAsync("You are now unranked !");
|
||||
return;
|
||||
}
|
||||
|
||||
int level = (int)user[1];
|
||||
int exp = (int)user[2];
|
||||
|
||||
var builder = new EmbedBuilder();
|
||||
var r = new Random();
|
||||
builder.WithColor(r.Next(256), r.Next(256), r.Next(256));
|
||||
builder.AddField("Current Level", level, true)
|
||||
.AddField("Current EXP", exp, true)
|
||||
.AddField("Required Exp", (level * 8 + 24).ToString(), true);
|
||||
builder.WithTimestamp(DateTimeOffset.Now);
|
||||
builder.WithAuthor(context.Message.Author.Mention);
|
||||
await context.Channel.SendMessageAsync(embed: builder.Build());
|
||||
}
|
||||
|
||||
//Optional method (tell the bot what should it do if the command is executed from a DM channel)
|
||||
//public async void ExecuteDM(DBCommandExecutingArguments context) {
|
||||
//
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
#### Code description:
|
||||
- Command - The keyword that triggers the execution for the command. This is what players must type in order to execute your command
|
||||
- Aliases - The aliases that can be used instead of the full name to execute the command
|
||||
- Description - The description of your command. Can be anything you like
|
||||
- Usage - The usage of your command. This is what `help [Command]` command will display
|
||||
- canUseDM - true if you plan to let users execute this command in DM chat with bot
|
||||
- canUseServer - true if you plan to let the users execute this command in a server chat
|
||||
- requireAdmin - true if this command requres an user with Administrator permission in the server
|
||||
- Execute () - the function of your command.
|
||||
- ExecuteServer () - the function that is executed only when the command is invoked in a server channel. (optional)
|
||||
- context - the command context
|
||||
- ExecuteDM () - the function that is executed only when the command is invoked in a private (DM) channel. (optional)
|
||||
- context - the command context
|
||||
- message - the message itself
|
||||
- client - the discord bot client
|
||||
- isDM - true if the message was sent from DM chat
|
||||
|
||||
From here on, start coding. When your plugin is done, build it as any DLL project then add it to the following path
|
||||
`{bot_executable}/Data/Plugins/Commands/<optional subfolder>/yourDLLName.dll`
|
||||
Then, reload bot and execute command `lp` in bot's console. The plugin should be loaded into memory or an error is thrown if not. If an error is thrown, then
|
||||
`{bot_executable}/Data/Plugins/<optional subfolder>/[plugin name].dll`
|
||||
Then, reload bot and execute command `plugin load` in the console. The plugin should be loaded into memory or an error is thrown if not. If an error is thrown, then
|
||||
there is something wrong in your command's code.
|
||||
|
||||
2. Events
|
||||
## 2. Events
|
||||
|
||||
Events are loaded when all plugins are loaded. At the moment when they are loaded, the Start function is called.
|
||||
Events are used if you want the bot to do something when something happens in server. The following example shows you how to catch when a user joins the server
|
||||
@@ -110,8 +116,6 @@ public class OnUserJoin : DBEvent
|
||||
|
||||
public async void Start(Discord.WebSocket.DiscordSocketClient client)
|
||||
{
|
||||
Console.WriteLine($"Hello World from {name}");
|
||||
|
||||
client.UserJoined += async (user) => {
|
||||
await (await user.CreateDMChannelAsync()).SendMessageAsync("Welcome to server !");
|
||||
};
|
||||
@@ -125,3 +129,115 @@ public class OnUserJoin : DBEvent
|
||||
- Start() - The main body of your event. This is executed when the bot loads all plugins
|
||||
- client - the discord bot client
|
||||
|
||||
|
||||
## 3. Slash Commands
|
||||
|
||||
|
||||
Slash commands are server based commands. They work the same way as normal commands, but they require the `/` prefix as they are integrated
|
||||
with the UI of Discord.
|
||||
Here is an example:
|
||||
```cs
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
|
||||
using PluginManager.Interfaces;
|
||||
|
||||
namespace SlashCommands
|
||||
{
|
||||
public class Random : DBSlashCommand
|
||||
{
|
||||
public string Name => "random";
|
||||
|
||||
public string Description => "Generates a random number between 2 values";
|
||||
|
||||
public bool canUseDM => true;
|
||||
|
||||
public List<SlashCommandOptionBuilder> Options => new List<SlashCommandOptionBuilder>()
|
||||
{
|
||||
new SlashCommandOptionBuilder() {Name = "min-value", Description = "Minimum value", IsRequired=true, Type = ApplicationCommandOptionType.Integer, MinValue = 0, MaxValue = int.MaxValue-1},
|
||||
new SlashCommandOptionBuilder() {Name = "max-value", Description = "Maximum value", IsRequired=true, Type=ApplicationCommandOptionType.Integer,MinValue = 0, MaxValue = int.MaxValue-1}
|
||||
};
|
||||
|
||||
public async void ExecuteServer(SocketSlashCommand command)
|
||||
{
|
||||
var rnd = new System.Random();
|
||||
var options = command.Data.Options.ToArray();
|
||||
if (options.Count() != 2)
|
||||
{
|
||||
await command.RespondAsync("Invalid parameters", ephemeral: true);
|
||||
return;
|
||||
}
|
||||
|
||||
Int64 numberOne = (Int64)options[0].Value;
|
||||
Int64 numberTwo = (Int64)options[1].Value;
|
||||
|
||||
await command.RespondAsync("Your generated number is " + rnd.Next((int)numberOne, (int)numberTwo), ephemeral: true);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Code description:
|
||||
- Name - the command name (execute with /{Name})
|
||||
- Description - The description of the command
|
||||
- canUseDM - true id this command can be activated in DM chat, false otherwise
|
||||
- Options - the arguments of the command
|
||||
- ExecuteServer() - this function will be called if the command is invoked in a server channel (optional)
|
||||
- context - the command context
|
||||
- ExecuteDM() - this function will be called if the command is invoked in a DM channel (optional)
|
||||
- context - the command context
|
||||
|
||||
|
||||
## Note:
|
||||
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.
|
||||
|
||||
|
||||
# Building from source
|
||||
|
||||
## Required tools
|
||||
You must have dotnet 8 installed in order to compile.
|
||||
You might run this commands with sudo in order to install dotnet successfully.
|
||||
### On Linux
|
||||
#### Arch
|
||||
```sh
|
||||
pacman -S dotnet-sdk-8.0
|
||||
```
|
||||
|
||||
#### Debian / Ubuntu
|
||||
```sh
|
||||
apt install dotnet-sdk-8.0
|
||||
```
|
||||
|
||||
#### Fedora / RHEL
|
||||
```sh
|
||||
dnf install dotnet-sdk-8.0
|
||||
```
|
||||
|
||||
### On Windows
|
||||
#### Default method
|
||||
Download and install dotnet 8 from the official Microsoft website using [this](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) link.
|
||||
|
||||
#### Using Visual Studio
|
||||
Download and install Visual Studio 2022 and select .NET Desktop Development while installing Visual Studio 2022.
|
||||
Open Visual Studio and select Clone a repo and paste the following link: `https://github.com/andreitdr/SethDiscordBot`.
|
||||
|
||||
Open the solution in Visual Studio and build it.
|
||||
|
||||
> Note: You might need to manually restore the NuGet packages, but VS2022 should take care of them automatically for you.
|
||||
> If not then you will need to click on Dependencies -> Packages for each project that has a yellow sign over the Dependancies tab and click Update.
|
||||
|
||||
## Cloning the repository
|
||||
```sh
|
||||
git clone https://github.com/andreitdr/SethDiscordBot
|
||||
cd SethDiscordBot
|
||||
dotnet build
|
||||
```
|
||||
|
||||
After the build succeeds, check the `/bin/Debug` folders for each project to see the built items.
|
||||
|
||||
Follow the on-screen prompts to make the bot run.
|
||||
|
||||
> Updated: 01.04.2024
|
||||
|
||||
|
||||
|
||||
@@ -1,30 +1,247 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32421.90
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordBot", "DiscordBot\DiscordBot.csproj", "{087E64F4-1E1C-4899-8223-295356C9894A}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordBotCore", "DiscordBotCore\DiscordBotCore.csproj", "{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PluginManager", "PluginManager\PluginManager.csproj", "{EDD4D9B3-98DD-4367-A09F-D1C5ACB61132}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{5CF9AD7B-6BF0-4035-835F-722F989C01E1}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Plugins\README.md = Plugins\README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{EA4FA308-7B2C-458E-8485-8747D745DD59}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LevelingSystem", "Plugins\LevelingSystem\LevelingSystem.csproj", "{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CppCompatibilityModule", "Modules\CppCompatibilityModule\CppCompatibilityModule.csproj", "{C67908F9-4A55-4DD8-B993-C26C648226F1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotCore.PluginCore", "DiscordBotCore.PluginCore\DiscordBotCore.PluginCore.csproj", "{B5725C86-2633-4C7A-A6A4-49AAA2767E54}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotCore.Networking", "DiscordBotCore.Networking\DiscordBotCore.Networking.csproj", "{10D064BB-399F-45DA-B64A-D68740A89E0F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotCore.PluginManagement", "DiscordBotCore.PluginManagement\DiscordBotCore.PluginManagement.csproj", "{AAD94C92-3048-4785-9D29-634C97152760}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotCore.Logging", "DiscordBotCore.Logging\DiscordBotCore.Logging.csproj", "{81E234B7-5182-4883-B70A-66D45F1D2427}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotCore.Utilities", "DiscordBotCore.Utilities\DiscordBotCore.Utilities.csproj", "{0EDC103A-C248-4146-98AC-3398B8FBC40F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotCore.Configuration", "DiscordBotCore.Configuration\DiscordBotCore.Configuration.csproj", "{BB77A7A4-0D6E-428C-9279-6301F4F85BE1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotCore.PluginManagement.Loading", "DiscordBotCore.PluginManagement.Loading\DiscordBotCore.PluginManagement.Loading.csproj", "{E8ED73E1-F7D9-44E7-9542-21BC86338724}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotCore.Database.Sqlite", "DiscordBotCore.Database.Sqlite\DiscordBotCore.Database.Sqlite.csproj", "{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebUI", "WebUI\WebUI.csproj", "{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PollMaker", "Plugins\PollMaker\PollMaker.csproj", "{9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{8F27B3EA-F292-40DF-B9B3-4B0E6BEA4E70}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.DiscordBotCore.Logging", "Tests\Tests.DiscordBotCore.Logging\Tests.DiscordBotCore.Logging.csproj", "{94238D37-60C6-4E40-80EC-4B4D242F5914}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.DiscordBotCore.Configuration", "Tests\Tests.DiscordBotCore.Configuration\Tests.DiscordBotCore.Configuration.csproj", "{52C59C73-C23C-4608-8827-383577DEB8D8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
Debug|x64 = Debug|x64
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|ARM64 = Release|ARM64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{087E64F4-1E1C-4899-8223-295356C9894A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{087E64F4-1E1C-4899-8223-295356C9894A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{087E64F4-1E1C-4899-8223-295356C9894A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{087E64F4-1E1C-4899-8223-295356C9894A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EDD4D9B3-98DD-4367-A09F-D1C5ACB61132}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Debug|x64.Build.0 = Debug|x64
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Release|x64.ActiveCfg = Release|x64
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Release|x64.Build.0 = Release|x64
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Debug|x64.Build.0 = Debug|x64
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Release|x64.ActiveCfg = Release|x64
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Release|x64.Build.0 = Release|x64
|
||||
{C67908F9-4A55-4DD8-B993-C26C648226F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C67908F9-4A55-4DD8-B993-C26C648226F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C67908F9-4A55-4DD8-B993-C26C648226F1}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{C67908F9-4A55-4DD8-B993-C26C648226F1}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{C67908F9-4A55-4DD8-B993-C26C648226F1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{C67908F9-4A55-4DD8-B993-C26C648226F1}.Debug|x64.Build.0 = Debug|x64
|
||||
{C67908F9-4A55-4DD8-B993-C26C648226F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C67908F9-4A55-4DD8-B993-C26C648226F1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C67908F9-4A55-4DD8-B993-C26C648226F1}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{C67908F9-4A55-4DD8-B993-C26C648226F1}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{C67908F9-4A55-4DD8-B993-C26C648226F1}.Release|x64.ActiveCfg = Release|x64
|
||||
{C67908F9-4A55-4DD8-B993-C26C648226F1}.Release|x64.Build.0 = Release|x64
|
||||
{B5725C86-2633-4C7A-A6A4-49AAA2767E54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B5725C86-2633-4C7A-A6A4-49AAA2767E54}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B5725C86-2633-4C7A-A6A4-49AAA2767E54}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{B5725C86-2633-4C7A-A6A4-49AAA2767E54}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{B5725C86-2633-4C7A-A6A4-49AAA2767E54}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{B5725C86-2633-4C7A-A6A4-49AAA2767E54}.Debug|x64.Build.0 = Debug|x64
|
||||
{B5725C86-2633-4C7A-A6A4-49AAA2767E54}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B5725C86-2633-4C7A-A6A4-49AAA2767E54}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B5725C86-2633-4C7A-A6A4-49AAA2767E54}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{B5725C86-2633-4C7A-A6A4-49AAA2767E54}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{B5725C86-2633-4C7A-A6A4-49AAA2767E54}.Release|x64.ActiveCfg = Release|x64
|
||||
{B5725C86-2633-4C7A-A6A4-49AAA2767E54}.Release|x64.Build.0 = Release|x64
|
||||
{10D064BB-399F-45DA-B64A-D68740A89E0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{10D064BB-399F-45DA-B64A-D68740A89E0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{10D064BB-399F-45DA-B64A-D68740A89E0F}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{10D064BB-399F-45DA-B64A-D68740A89E0F}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{10D064BB-399F-45DA-B64A-D68740A89E0F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{10D064BB-399F-45DA-B64A-D68740A89E0F}.Debug|x64.Build.0 = Debug|x64
|
||||
{10D064BB-399F-45DA-B64A-D68740A89E0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{10D064BB-399F-45DA-B64A-D68740A89E0F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{10D064BB-399F-45DA-B64A-D68740A89E0F}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{10D064BB-399F-45DA-B64A-D68740A89E0F}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{10D064BB-399F-45DA-B64A-D68740A89E0F}.Release|x64.ActiveCfg = Release|x64
|
||||
{10D064BB-399F-45DA-B64A-D68740A89E0F}.Release|x64.Build.0 = Release|x64
|
||||
{AAD94C92-3048-4785-9D29-634C97152760}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AAD94C92-3048-4785-9D29-634C97152760}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AAD94C92-3048-4785-9D29-634C97152760}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{AAD94C92-3048-4785-9D29-634C97152760}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{AAD94C92-3048-4785-9D29-634C97152760}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{AAD94C92-3048-4785-9D29-634C97152760}.Debug|x64.Build.0 = Debug|x64
|
||||
{AAD94C92-3048-4785-9D29-634C97152760}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AAD94C92-3048-4785-9D29-634C97152760}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AAD94C92-3048-4785-9D29-634C97152760}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{AAD94C92-3048-4785-9D29-634C97152760}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{AAD94C92-3048-4785-9D29-634C97152760}.Release|x64.ActiveCfg = Release|x64
|
||||
{AAD94C92-3048-4785-9D29-634C97152760}.Release|x64.Build.0 = Release|x64
|
||||
{81E234B7-5182-4883-B70A-66D45F1D2427}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{81E234B7-5182-4883-B70A-66D45F1D2427}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{81E234B7-5182-4883-B70A-66D45F1D2427}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{81E234B7-5182-4883-B70A-66D45F1D2427}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{81E234B7-5182-4883-B70A-66D45F1D2427}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{81E234B7-5182-4883-B70A-66D45F1D2427}.Debug|x64.Build.0 = Debug|x64
|
||||
{81E234B7-5182-4883-B70A-66D45F1D2427}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81E234B7-5182-4883-B70A-66D45F1D2427}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{81E234B7-5182-4883-B70A-66D45F1D2427}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{81E234B7-5182-4883-B70A-66D45F1D2427}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{81E234B7-5182-4883-B70A-66D45F1D2427}.Release|x64.ActiveCfg = Release|x64
|
||||
{81E234B7-5182-4883-B70A-66D45F1D2427}.Release|x64.Build.0 = Release|x64
|
||||
{0EDC103A-C248-4146-98AC-3398B8FBC40F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0EDC103A-C248-4146-98AC-3398B8FBC40F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0EDC103A-C248-4146-98AC-3398B8FBC40F}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{0EDC103A-C248-4146-98AC-3398B8FBC40F}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{0EDC103A-C248-4146-98AC-3398B8FBC40F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{0EDC103A-C248-4146-98AC-3398B8FBC40F}.Debug|x64.Build.0 = Debug|x64
|
||||
{0EDC103A-C248-4146-98AC-3398B8FBC40F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0EDC103A-C248-4146-98AC-3398B8FBC40F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0EDC103A-C248-4146-98AC-3398B8FBC40F}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{0EDC103A-C248-4146-98AC-3398B8FBC40F}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{0EDC103A-C248-4146-98AC-3398B8FBC40F}.Release|x64.ActiveCfg = Release|x64
|
||||
{0EDC103A-C248-4146-98AC-3398B8FBC40F}.Release|x64.Build.0 = Release|x64
|
||||
{BB77A7A4-0D6E-428C-9279-6301F4F85BE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BB77A7A4-0D6E-428C-9279-6301F4F85BE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BB77A7A4-0D6E-428C-9279-6301F4F85BE1}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{BB77A7A4-0D6E-428C-9279-6301F4F85BE1}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{BB77A7A4-0D6E-428C-9279-6301F4F85BE1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{BB77A7A4-0D6E-428C-9279-6301F4F85BE1}.Debug|x64.Build.0 = Debug|x64
|
||||
{BB77A7A4-0D6E-428C-9279-6301F4F85BE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BB77A7A4-0D6E-428C-9279-6301F4F85BE1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BB77A7A4-0D6E-428C-9279-6301F4F85BE1}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{BB77A7A4-0D6E-428C-9279-6301F4F85BE1}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{BB77A7A4-0D6E-428C-9279-6301F4F85BE1}.Release|x64.ActiveCfg = Release|x64
|
||||
{BB77A7A4-0D6E-428C-9279-6301F4F85BE1}.Release|x64.Build.0 = Release|x64
|
||||
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Debug|x64.Build.0 = Debug|x64
|
||||
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Release|x64.ActiveCfg = Release|x64
|
||||
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Release|x64.Build.0 = Release|x64
|
||||
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Debug|x64.Build.0 = Debug|x64
|
||||
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Release|x64.ActiveCfg = Release|x64
|
||||
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Release|x64.Build.0 = Release|x64
|
||||
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Debug|x64.Build.0 = Debug|x64
|
||||
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Release|x64.ActiveCfg = Release|x64
|
||||
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Release|x64.Build.0 = Release|x64
|
||||
{9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7}.Release|x64.Build.0 = Release|Any CPU
|
||||
{94238D37-60C6-4E40-80EC-4B4D242F5914}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{94238D37-60C6-4E40-80EC-4B4D242F5914}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{94238D37-60C6-4E40-80EC-4B4D242F5914}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{94238D37-60C6-4E40-80EC-4B4D242F5914}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{94238D37-60C6-4E40-80EC-4B4D242F5914}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{94238D37-60C6-4E40-80EC-4B4D242F5914}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{94238D37-60C6-4E40-80EC-4B4D242F5914}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{94238D37-60C6-4E40-80EC-4B4D242F5914}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{94238D37-60C6-4E40-80EC-4B4D242F5914}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{94238D37-60C6-4E40-80EC-4B4D242F5914}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{94238D37-60C6-4E40-80EC-4B4D242F5914}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{94238D37-60C6-4E40-80EC-4B4D242F5914}.Release|x64.Build.0 = Release|Any CPU
|
||||
{52C59C73-C23C-4608-8827-383577DEB8D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{52C59C73-C23C-4608-8827-383577DEB8D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{52C59C73-C23C-4608-8827-383577DEB8D8}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{52C59C73-C23C-4608-8827-383577DEB8D8}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{52C59C73-C23C-4608-8827-383577DEB8D8}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{52C59C73-C23C-4608-8827-383577DEB8D8}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{52C59C73-C23C-4608-8827-383577DEB8D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{52C59C73-C23C-4608-8827-383577DEB8D8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{52C59C73-C23C-4608-8827-383577DEB8D8}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{52C59C73-C23C-4608-8827-383577DEB8D8}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{52C59C73-C23C-4608-8827-383577DEB8D8}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{52C59C73-C23C-4608-8827-383577DEB8D8}.Release|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689} = {5CF9AD7B-6BF0-4035-835F-722F989C01E1}
|
||||
{C67908F9-4A55-4DD8-B993-C26C648226F1} = {EA4FA308-7B2C-458E-8485-8747D745DD59}
|
||||
{9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7} = {5CF9AD7B-6BF0-4035-835F-722F989C01E1}
|
||||
{94238D37-60C6-4E40-80EC-4B4D242F5914} = {8F27B3EA-F292-40DF-B9B3-4B0E6BEA4E70}
|
||||
{52C59C73-C23C-4608-8827-383577DEB8D8} = {8F27B3EA-F292-40DF-B9B3-4B0E6BEA4E70}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3FB3C5DE-ED21-4D2E-ABDD-3A00EE4A2FFF}
|
||||
EndGlobalSection
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user