Compare commits
297 Commits
| 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 |
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
|
||||||
106
.gitignore
vendored
106
.gitignore
vendored
@@ -29,10 +29,8 @@ x86/
|
|||||||
bld/
|
bld/
|
||||||
[Bb]in/
|
[Bb]in/
|
||||||
[Oo]bj/
|
[Oo]bj/
|
||||||
[Oo]ut/
|
|
||||||
[Ll]og/
|
[Ll]og/
|
||||||
[Ll]ogs/
|
[Ll]ogs/
|
||||||
[Dd]ata/
|
|
||||||
|
|
||||||
# Visual Studio 2015/2017 cache/options directory
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
.vs/
|
.vs/
|
||||||
@@ -64,6 +62,9 @@ project.lock.json
|
|||||||
project.fragment.lock.json
|
project.fragment.lock.json
|
||||||
artifacts/
|
artifacts/
|
||||||
|
|
||||||
|
# Tye
|
||||||
|
.tye/
|
||||||
|
|
||||||
# ASP.NET Scaffolding
|
# ASP.NET Scaffolding
|
||||||
ScaffoldingReadMe.txt
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
@@ -363,10 +364,99 @@ MigrationBackup/
|
|||||||
# Fody - auto-generated XML schema
|
# Fody - auto-generated XML schema
|
||||||
FodyWeavers.xsd
|
FodyWeavers.xsd
|
||||||
|
|
||||||
*.txt
|
##
|
||||||
|
## Visual studio for Mac
|
||||||
|
##
|
||||||
|
|
||||||
#folders
|
|
||||||
/Plugins/
|
# globs
|
||||||
/DiscordBot.rar
|
Makefile.in
|
||||||
/DiscordBot/Data/
|
*.userprefs
|
||||||
/DiscordBot/Updater/
|
*.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,92 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
using Discord;
|
|
||||||
using Discord.Commands;
|
|
||||||
|
|
||||||
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.Variables.GetValue("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,100 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using PluginManager.Interfaces;
|
|
||||||
using PluginManager.Others;
|
|
||||||
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,154 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using Discord;
|
|
||||||
using Discord.Commands;
|
|
||||||
using Discord.WebSocket;
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
var config = new DiscordSocketConfig
|
|
||||||
{
|
|
||||||
|
|
||||||
AlwaysDownloadUsers = true,
|
|
||||||
|
|
||||||
//Disable system clock checkup (for responses at slash commands)
|
|
||||||
UseInteractionSnowflakeDate = false
|
|
||||||
};
|
|
||||||
|
|
||||||
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 async Task Client_LoggedOut()
|
|
||||||
{
|
|
||||||
WriteLogFile("Successfully Logged Out");
|
|
||||||
await Log(new LogMessage(LogSeverity.Info, "Boot", "Successfully logged out from discord !"));
|
|
||||||
|
|
||||||
/* var cmds = await client.GetGlobalApplicationCommandsAsync();
|
|
||||||
foreach (var cmd in cmds)
|
|
||||||
await cmd.DeleteAsync();*/
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Ready()
|
|
||||||
{
|
|
||||||
Console.Title = "ONLINE";
|
|
||||||
|
|
||||||
|
|
||||||
isReady = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,126 +0,0 @@
|
|||||||
using System;
|
|
||||||
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;
|
|
||||||
client.SlashCommandExecuted += Client_SlashCommandExecuted;
|
|
||||||
await commandService.AddModulesAsync(Assembly.GetEntryAssembly(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Client_SlashCommandExecuted(SocketSlashCommand arg)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var plugin = PluginLoader.SlashCommands!
|
|
||||||
.Where(p => p.Name == arg.Data.Name)
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
if (plugin is null) throw new Exception("Failed to run command. !");
|
|
||||||
|
|
||||||
|
|
||||||
if (arg.Channel is SocketDMChannel)
|
|
||||||
plugin.ExecuteDM(arg);
|
|
||||||
else plugin.ExecuteServer(arg);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
|
|
||||||
Console.WriteLine(ex.ToString());
|
|
||||||
ex.WriteErrFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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 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 (Exception ex)
|
|
||||||
{
|
|
||||||
ex.WriteErrFile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +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.1.0</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>
|
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
|
||||||
<Exec Command="xcopy /B /Y "$(TargetDir)*.dll" "$(TargetDir)Libraries"" />
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -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.1.0</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,29 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace DiscordBot
|
|
||||||
{
|
|
||||||
|
|
||||||
public class Entry
|
|
||||||
{
|
|
||||||
[STAThread]
|
|
||||||
public static void Main(string[] args)
|
|
||||||
{
|
|
||||||
AppDomain currentDomain = AppDomain.CurrentDomain;
|
|
||||||
currentDomain.AssemblyResolve += new ResolveEventHandler(LoadFromSameFolder);
|
|
||||||
|
|
||||||
static Assembly LoadFromSameFolder(object sender, ResolveEventArgs args)
|
|
||||||
{
|
|
||||||
string folderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "./Libraries");
|
|
||||||
string assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll");
|
|
||||||
if (!File.Exists(assemblyPath)) return null;
|
|
||||||
Assembly assembly = Assembly.LoadFrom(assemblyPath);
|
|
||||||
return assembly;
|
|
||||||
}
|
|
||||||
|
|
||||||
Program.Startup(args);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,441 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using DiscordBot.Discord.Core;
|
|
||||||
|
|
||||||
using PluginManager;
|
|
||||||
using PluginManager.Database;
|
|
||||||
using PluginManager.Items;
|
|
||||||
using PluginManager.Online;
|
|
||||||
using PluginManager.Online.Helpers;
|
|
||||||
using PluginManager.Others;
|
|
||||||
|
|
||||||
using Terminal.Gui;
|
|
||||||
|
|
||||||
using OperatingSystem = PluginManager.Others.OperatingSystem;
|
|
||||||
|
|
||||||
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]
|
|
||||||
public static void Startup(string[] args)
|
|
||||||
{
|
|
||||||
|
|
||||||
PreLoadComponents().Wait();
|
|
||||||
|
|
||||||
if (!Config.Variables.Exists("ServerID") || !Config.Variables.Exists("token") ||
|
|
||||||
Config.Variables.GetValue("token") == null ||
|
|
||||||
(Config.Variables.GetValue("token")?.Length != 70 && Config.Variables.GetValue("token")?.Length != 59) ||
|
|
||||||
!Config.Variables.Exists("prefix") || Config.Variables.GetValue("prefix") == null ||
|
|
||||||
Config.Variables.GetValue("prefix")?.Length != 1 ||
|
|
||||||
(args.Length == 1 && args[0] == "/reset"))
|
|
||||||
{
|
|
||||||
Application.Init();
|
|
||||||
var top = Application.Top;
|
|
||||||
var win = new Window("Discord Bot Config - " + Assembly.GetExecutingAssembly().GetName().Version)
|
|
||||||
{
|
|
||||||
X = 0,
|
|
||||||
Y = 1,
|
|
||||||
Width = Dim.Fill(),
|
|
||||||
Height = Dim.Fill()
|
|
||||||
};
|
|
||||||
|
|
||||||
top.Add(win);
|
|
||||||
|
|
||||||
var labelInfo = new Label(
|
|
||||||
"Configuration file not found or invalid. " +
|
|
||||||
"Please fill the following fields to create a new configuration file."
|
|
||||||
)
|
|
||||||
{
|
|
||||||
X = Pos.Center(),
|
|
||||||
Y = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var labelToken = new Label("Please insert your token here: ")
|
|
||||||
{
|
|
||||||
X = 5,
|
|
||||||
Y = 5
|
|
||||||
};
|
|
||||||
|
|
||||||
var textFiledToken = new TextField("")
|
|
||||||
{
|
|
||||||
X = Pos.Left(labelToken) + labelToken.Text.Length + 2,
|
|
||||||
Y = labelToken.Y,
|
|
||||||
Width = 70
|
|
||||||
};
|
|
||||||
|
|
||||||
var labelPrefix = new Label("Please insert your prefix here: ")
|
|
||||||
{
|
|
||||||
X = 5,
|
|
||||||
Y = 8
|
|
||||||
};
|
|
||||||
var textFiledPrefix = new TextField("")
|
|
||||||
{
|
|
||||||
X = Pos.Left(labelPrefix) + labelPrefix.Text.Length + 2,
|
|
||||||
Y = labelPrefix.Y,
|
|
||||||
Width = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
var labelServerid = new Label("Please insert your server id here (optional): ")
|
|
||||||
{
|
|
||||||
X = 5,
|
|
||||||
Y = 11
|
|
||||||
};
|
|
||||||
var textFiledServerID = new TextField("")
|
|
||||||
{
|
|
||||||
X = Pos.Left(labelServerid) + labelServerid.Text.Length + 2,
|
|
||||||
Y = labelServerid.Y,
|
|
||||||
Width = 18
|
|
||||||
};
|
|
||||||
|
|
||||||
var button = new Button("Submit")
|
|
||||||
{
|
|
||||||
X = Pos.Center() - 10,
|
|
||||||
Y = 16
|
|
||||||
};
|
|
||||||
|
|
||||||
var button2 = new Button("License")
|
|
||||||
{
|
|
||||||
X = Pos.Center() + 10,
|
|
||||||
Y = 16
|
|
||||||
};
|
|
||||||
|
|
||||||
var button3 = new Button("ⓘ")
|
|
||||||
{
|
|
||||||
X = Pos.Left(textFiledServerID) + 20,
|
|
||||||
Y = textFiledServerID.Y
|
|
||||||
};
|
|
||||||
|
|
||||||
Console.CancelKeyPress += (sender, e) => { top.Running = false; };
|
|
||||||
|
|
||||||
button.Clicked += () =>
|
|
||||||
{
|
|
||||||
var passMessage = "";
|
|
||||||
if (textFiledToken.Text.Length != 70 && textFiledToken.Text.Length != 59)
|
|
||||||
passMessage += "Invalid token, ";
|
|
||||||
if (textFiledPrefix.Text.ContainsAny("0123456789/\\ ") || textFiledPrefix.Text.Length != 1)
|
|
||||||
passMessage += "Invalid prefix, ";
|
|
||||||
if (textFiledServerID.Text.Length != 18 && textFiledServerID.Text.Length > 0)
|
|
||||||
passMessage += "Invalid serverID";
|
|
||||||
|
|
||||||
if (passMessage != "")
|
|
||||||
{
|
|
||||||
MessageBox.ErrorQuery("Discord Bot Settings",
|
|
||||||
"Failed to pass check. Invalid information given:\n" + passMessage, "Retry");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Config.Variables.Add("ServerID", (string)textFiledServerID.Text, true);
|
|
||||||
Config.Variables.Add("token", (string)textFiledToken.Text, true);
|
|
||||||
Config.Variables.Add("prefix", (string)textFiledPrefix.Text, true);
|
|
||||||
|
|
||||||
MessageBox.Query("Discord Bot Settings", "Successfully saved config !\nJust start the bot :D",
|
|
||||||
"Start :D");
|
|
||||||
top.Running = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
button2.Clicked += async () =>
|
|
||||||
{
|
|
||||||
var license =
|
|
||||||
await ServerCom.ReadTextFromURL(
|
|
||||||
"https://raw.githubusercontent.com/Wizzy69/installer/discord-bot-files/LICENSE.txt");
|
|
||||||
var ProductLicense =
|
|
||||||
"Seth Discord Bot\n\nDeveloped by Wizzy#9181\nThis application can be used and modified by anyone. Plugin development for this application is also free and supported";
|
|
||||||
var r = MessageBox.Query("Discord Bot Settings", ProductLicense, "Close", "Read about libraries used");
|
|
||||||
if (r == 1)
|
|
||||||
{
|
|
||||||
var i = 0;
|
|
||||||
while (i < license.Count)
|
|
||||||
{
|
|
||||||
var print_message = license[i++] + "\n";
|
|
||||||
for (; i < license.Count && !license[i].StartsWith("-----------"); i++)
|
|
||||||
print_message += license[i] + "\n";
|
|
||||||
if (print_message.Contains("https://"))
|
|
||||||
print_message += "\n\nCTRL + Click on a link to open it";
|
|
||||||
if (MessageBox.Query("Licenses", print_message, "Next", "Quit") == 1) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
button3.Clicked += () =>
|
|
||||||
{
|
|
||||||
MessageBox.Query("Discord Bot Settings",
|
|
||||||
"Server ID can be found in Server settings => Widget => Server ID",
|
|
||||||
"Close");
|
|
||||||
};
|
|
||||||
|
|
||||||
win.Add(labelInfo, labelPrefix, labelServerid, labelToken);
|
|
||||||
win.Add(textFiledToken, textFiledPrefix, textFiledServerID, button3);
|
|
||||||
win.Add(button, button2);
|
|
||||||
Application.Run();
|
|
||||||
Application.Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
HandleInput(args).Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The main loop for the discord bot
|
|
||||||
/// </summary>
|
|
||||||
private static void NoGUI()
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
Settings.Variables.outputStream.WriteLine();
|
|
||||||
ConsoleCommandsHandler.ExecuteCommad("lp").Wait();
|
|
||||||
#else
|
|
||||||
if (loadPluginsOnStartup) consoleCommandsHandler.HandleCommand("lp");
|
|
||||||
if (listPluginsAtStartup) consoleCommandsHandler.HandleCommand("listplugs");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var cmd = Console.ReadLine();
|
|
||||||
if (!consoleCommandsHandler.HandleCommand(cmd!
|
|
||||||
#if DEBUG
|
|
||||||
, false
|
|
||||||
#endif
|
|
||||||
|
|
||||||
) && cmd.Length > 0)
|
|
||||||
Settings.Variables.outputStream.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;
|
|
||||||
|
|
||||||
var startupMessageList =
|
|
||||||
await ServerCom.ReadTextFromURL(
|
|
||||||
"https://raw.githubusercontent.com/Wizzy69/installer/discord-bot-files/StartupMessage");
|
|
||||||
|
|
||||||
foreach (var message in startupMessageList)
|
|
||||||
Settings.Variables.outputStream.WriteLine(message);
|
|
||||||
|
|
||||||
Settings.Variables.outputStream.WriteLine(
|
|
||||||
$"Running on version: {Assembly.GetExecutingAssembly().GetName().Version}");
|
|
||||||
Settings.Variables.outputStream.WriteLine($"Git URL: {Settings.Variables.WebsiteURL}");
|
|
||||||
|
|
||||||
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.Variables.Exists("LaunchMessage"))
|
|
||||||
Utilities.WriteColorText(Config.Variables.GetValue("LaunchMessage"));
|
|
||||||
|
|
||||||
|
|
||||||
Utilities.WriteColorText(
|
|
||||||
"Please note that the bot saves a backup save file every time you are using the shudown command (&ysd&c)");
|
|
||||||
Settings.Variables.outputStream.WriteLine("============================ LOG ============================");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string token = "";
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
if (await Settings.sqlDatabase.TableExistsAsync("BetaTest"))
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.WriteLine("Starting in DEBUG MODE");
|
|
||||||
token = await Settings.sqlDatabase.GetValueAsync("BetaTest", "VariableName", "Token", "Value");
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
token = Config.Variables.GetValue("token");
|
|
||||||
#endif
|
|
||||||
var prefix = Config.Variables.GetValue("prefix");
|
|
||||||
var discordbooter = new Boot(token, prefix);
|
|
||||||
await discordbooter.Awake();
|
|
||||||
return discordbooter;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.WriteLine(ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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;
|
|
||||||
|
|
||||||
var b = await StartNoGui();
|
|
||||||
consoleCommandsHandler = new ConsoleCommandsHandler(b.client);
|
|
||||||
|
|
||||||
if (len > 0 && args[0] == "/remplug")
|
|
||||||
{
|
|
||||||
var plugName = string.Join(' ', args, 1, args.Length - 1);
|
|
||||||
Settings.Variables.outputStream.WriteLine("Starting to remove " + plugName);
|
|
||||||
await ConsoleCommandsHandler.ExecuteCommad("remplug " + plugName);
|
|
||||||
loadPluginsOnStartup = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len > 0 && args[0] == "/lp")
|
|
||||||
loadPluginsOnStartup = true;
|
|
||||||
|
|
||||||
var mainThread = new Thread(() =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
NoGUI();
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
if (ex.Message == "No process is on the other end of the pipe." || (uint)ex.HResult == 0x800700E9)
|
|
||||||
{
|
|
||||||
if (Config.Variables.Exists("LaunchMessage"))
|
|
||||||
Config.Variables.Add("LaunchMessage",
|
|
||||||
"An error occured while closing the bot last time. Please consider closing the bot using the &rsd&c method !\nThere is a risk of losing all data or corruption of the save file, which in some cases requires to reinstall the bot !",
|
|
||||||
false);
|
|
||||||
Functions.WriteErrFile(ex.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mainThread.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task PreLoadComponents()
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream = Console.Out;
|
|
||||||
Settings.Variables.outputStream.WriteLine("Loading resources ...");
|
|
||||||
var main = new Utilities.ProgressBar(ProgressBarType.NO_END);
|
|
||||||
main.Start();
|
|
||||||
Directory.CreateDirectory("./Data/Resources");
|
|
||||||
Directory.CreateDirectory("./Data/Plugins");
|
|
||||||
Directory.CreateDirectory("./Data/PAKS");
|
|
||||||
|
|
||||||
Settings.sqlDatabase = new SqlDatabase(Functions.dataFolder + "SetDB.dat");
|
|
||||||
|
|
||||||
await Settings.sqlDatabase.Open();
|
|
||||||
await Config.Initialize();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (await Config.Variables.ExistsAsync("DeleteLogsAtStartup"))
|
|
||||||
if (await Config.Variables.GetValueAsync("DeleteLogsAtStartup") == "true")
|
|
||||||
foreach (var file in Directory.GetFiles("./Output/Logs/"))
|
|
||||||
File.Delete(file);
|
|
||||||
var OnlineDefaultKeys =
|
|
||||||
await ServerCom.ReadTextFromURL(
|
|
||||||
"https://raw.githubusercontent.com/Wizzy69/installer/discord-bot-files/SetupKeys");
|
|
||||||
|
|
||||||
|
|
||||||
if (!await Config.Variables.ExistsAsync("Version"))
|
|
||||||
await Config.Variables.AddAsync("Version", Assembly.GetExecutingAssembly().GetName().Version.ToString(), false);
|
|
||||||
else
|
|
||||||
await Config.Variables.SetValueAsync("Version", Assembly.GetExecutingAssembly().GetName().Version.ToString());
|
|
||||||
|
|
||||||
|
|
||||||
foreach (var key in OnlineDefaultKeys)
|
|
||||||
{
|
|
||||||
if (key.Length <= 3 || !key.Contains(' ')) continue;
|
|
||||||
var s = key.Split(' ');
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (await Config.Variables.ExistsAsync(s[0])) await Config.Variables.SetValueAsync(s[0], s[1]);
|
|
||||||
else
|
|
||||||
await Config.Variables.AddAsync(s[0], s[1], s[2].ToLower() == "true");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Functions.WriteErrFile(ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var onlineSettingsList =
|
|
||||||
await ServerCom.ReadTextFromURL(
|
|
||||||
"https://raw.githubusercontent.com/Wizzy69/installer/discord-bot-files/OnlineData");
|
|
||||||
main.Stop("Loaded online settings. Loading updates ...");
|
|
||||||
foreach (var key in onlineSettingsList)
|
|
||||||
{
|
|
||||||
if (key.Length <= 3 || !key.Contains(' ')) continue;
|
|
||||||
|
|
||||||
var s = key.Split(' ');
|
|
||||||
switch (s[0])
|
|
||||||
{
|
|
||||||
case "CurrentVersion":
|
|
||||||
var newVersion = s[1];
|
|
||||||
if (!newVersion.Equals(await Config.Variables.GetValueAsync("Version")))
|
|
||||||
{
|
|
||||||
var nVer = new VersionString(newVersion.Substring(2));
|
|
||||||
var cVer = new VersionString((await Config.Variables.GetValueAsync("Version")).Substring(2));
|
|
||||||
if (cVer > nVer)
|
|
||||||
{
|
|
||||||
await Config.Variables.SetValueAsync("Version", "1." + cVer.ToShortString() + " (Beta)");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Functions.GetOperatingSystem() == OperatingSystem.WINDOWS)
|
|
||||||
{
|
|
||||||
var url =
|
|
||||||
$"https://github.com/Wizzy69/SethDiscordBot/releases/download/v{newVersion}/net6.0.zip";
|
|
||||||
Process.Start(".\\Updater\\Updater.exe",
|
|
||||||
$"{newVersion} {url} {Process.GetCurrentProcess().ProcessName}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var url =
|
|
||||||
$"https://github.com/Wizzy69/SethDiscordBot/releases/download/v{newVersion}/net6.0_linux.zip";
|
|
||||||
Settings.Variables.outputStream.WriteLine("Downloading update ...");
|
|
||||||
await ServerCom.DownloadFileNoProgressAsync(url, "./update.zip");
|
|
||||||
await File.WriteAllTextAsync("Install.sh",
|
|
||||||
"#!/bin/bash\nunzip -qq update.zip -d ./\nrm update.zip\nchmod +x SethDiscordBot\n./DiscordBot");
|
|
||||||
Process.Start("Install.sh").WaitForExit();
|
|
||||||
Environment.Exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case "UpdaterVersion":
|
|
||||||
var updaternewversion = s[1];
|
|
||||||
if (Functions.GetOperatingSystem() == OperatingSystem.LINUX)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (!await Config.Variables.ExistsAsync("UpdaterVersion"))
|
|
||||||
await Config.Variables.AddAsync("UpdaterVersion", "0.0.0.0", false);
|
|
||||||
if (await Config.Variables.GetValueAsync("UpdaterVersion") != updaternewversion ||
|
|
||||||
!Directory.Exists("./Updater") ||
|
|
||||||
!File.Exists("./Updater/Updater.exe"))
|
|
||||||
{
|
|
||||||
Console.Clear();
|
|
||||||
Settings.Variables.outputStream.WriteLine("Installing updater ...\nDo NOT close the bot during update !");
|
|
||||||
var bar = new 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);
|
|
||||||
await Config.Variables.SetValueAsync("UpdaterVersion", updaternewversion);
|
|
||||||
File.Delete("Updater.zip");
|
|
||||||
bar.Stop("Updater has been updated !");
|
|
||||||
Console.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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,10 +1,8 @@
|
|||||||
using System.Collections.Generic;
|
using DiscordBotCore.PluginCore.Helpers.Execution.DbCommand;
|
||||||
|
|
||||||
using Discord.Commands;
|
namespace DiscordBotCore.PluginCore.Interfaces;
|
||||||
|
|
||||||
namespace PluginManager.Interfaces;
|
public interface IDbCommand
|
||||||
|
|
||||||
public interface DBCommand
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command to be executed
|
/// Command to be executed
|
||||||
@@ -15,7 +13,7 @@ public interface DBCommand
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command aliases. Users may use this to execute the command
|
/// Command aliases. Users may use this to execute the command
|
||||||
/// </summary>
|
/// </summary>
|
||||||
List<string>? Aliases { get; }
|
List<string> Aliases { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command description
|
/// Command description
|
||||||
@@ -31,21 +29,17 @@ public interface DBCommand
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// true if the command requre admin, otherwise false
|
/// true if the command requre admin, otherwise false
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool requireAdmin { get; }
|
bool RequireAdmin { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main body of the command. This is what is executed when user calls the command in Server
|
/// The main body of the command. This is what is executed when user calls the command in Server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">The disocrd Context</param>
|
/// <param name="args">The Discord Context</param>
|
||||||
void ExecuteServer(SocketCommandContext context)
|
Task ExecuteServer(IDbCommandExecutingArgument args) => Task.CompletedTask;
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main body of the command. This is what is executed when user calls the command in DM
|
/// The main body of the command. This is what is executed when user calls the command in DM
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">The disocrd Context</param>
|
/// <param name="args">The Discord Context</param>
|
||||||
void ExecuteDM(SocketCommandContext context)
|
Task ExecuteDm(IDbCommandExecutingArgument args) => Task.CompletedTask;
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using Discord.WebSocket;
|
using DiscordBotCore.PluginCore.Helpers.Execution.DbEvent;
|
||||||
|
|
||||||
namespace PluginManager.Interfaces;
|
namespace DiscordBotCore.PluginCore.Interfaces;
|
||||||
|
|
||||||
public interface DBEvent
|
public interface IDbEvent
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of the event
|
/// The name of the event
|
||||||
@@ -17,6 +17,6 @@ public interface DBEvent
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The method that is invoked when the event is loaded into memory
|
/// The method that is invoked when the event is loaded into memory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client">The discord bot client</param>
|
/// <param name="args">The arguments for the start method</param>
|
||||||
void Start(DiscordSocketClient client);
|
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;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
|
|
||||||
namespace PluginManager.Others.Permissions;
|
namespace DiscordBotCore.Others;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A class whith all discord permissions
|
|
||||||
/// </summary>
|
|
||||||
public static class DiscordPermissions
|
public static class DiscordPermissions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -15,7 +12,7 @@ public static class DiscordPermissions
|
|||||||
/// <param name="role">The role</param>
|
/// <param name="role">The role</param>
|
||||||
/// <param name="permission">The permission</param>
|
/// <param name="permission">The permission</param>
|
||||||
/// <returns></returns>
|
/// <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);
|
return role.Permissions.Has(permission);
|
||||||
}
|
}
|
||||||
@@ -26,20 +23,19 @@ public static class DiscordPermissions
|
|||||||
/// <param name="user">The user</param>
|
/// <param name="user">The user</param>
|
||||||
/// <param name="role">The role</param>
|
/// <param name="role">The role</param>
|
||||||
/// <returns></returns>
|
/// <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);
|
return user.Roles.Contains(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if user has the specified permission
|
/// Check if user has the specified permission
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user">The user</param>
|
/// <param name="user">The user</param>
|
||||||
/// <param name="permission">The permission</param>
|
/// <param name="permission">The permission</param>
|
||||||
/// <returns></returns>
|
/// <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>
|
/// <summary>
|
||||||
@@ -47,9 +43,9 @@ public static class DiscordPermissions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user">The user</param>
|
/// <param name="user">The user</param>
|
||||||
/// <returns></returns>
|
/// <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>
|
/// <summary>
|
||||||
@@ -57,8 +53,8 @@ public static class DiscordPermissions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user">The user</param>
|
/// <param name="user">The user</param>
|
||||||
/// <returns></returns>
|
/// <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,199 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using PluginManager.Online.Helpers;
|
|
||||||
|
|
||||||
namespace PluginManager;
|
|
||||||
|
|
||||||
public static class Config
|
|
||||||
{
|
|
||||||
private static bool IsLoaded = false;
|
|
||||||
public static async Task Initialize()
|
|
||||||
{
|
|
||||||
if (IsLoaded)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!await Settings.sqlDatabase.TableExistsAsync("Plugins"))
|
|
||||||
await Settings.sqlDatabase.CreateTableAsync("Plugins", "PluginName", "Version");
|
|
||||||
if (!await Settings.sqlDatabase.TableExistsAsync("Variables"))
|
|
||||||
await Settings.sqlDatabase.CreateTableAsync("Variables", "VarName", "Value", "ReadOnly");
|
|
||||||
|
|
||||||
IsLoaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Variables
|
|
||||||
{
|
|
||||||
public static async Task<string> GetValueAsync(string VarName)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded");
|
|
||||||
return await Settings.sqlDatabase.GetValueAsync("Variables", "VarName", VarName, "Value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetValue(string VarName)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded");
|
|
||||||
return Settings.sqlDatabase.GetValue("Variables", "VarName", VarName, "Value");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static async Task SetValueAsync(string VarName, string Value)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded");
|
|
||||||
|
|
||||||
if (await IsReadOnlyAsync(VarName))
|
|
||||||
throw new Exception($"Variable ({VarName}) is read only and can not be changed to {Value}");
|
|
||||||
|
|
||||||
await Settings.sqlDatabase.SetValueAsync("Variables", "VarName", VarName, "Value", Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetValue(string VarName, string Value)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded");
|
|
||||||
if (IsReadOnly(VarName))
|
|
||||||
throw new Exception($"Variable ({VarName}) is read only and can not be changed to {Value}");
|
|
||||||
Settings.sqlDatabase.SetValue("Variables", "VarName", VarName, "Value", Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static async Task<bool> IsReadOnlyAsync(string VarName)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded");
|
|
||||||
return (await Settings.sqlDatabase.GetValueAsync("Variables", "VarName", VarName, "ReadOnly")).Equals("true", StringComparison.CurrentCultureIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsReadOnly(string VarName)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded");
|
|
||||||
return (Settings.sqlDatabase.GetValue("Variables", "VarName", VarName, "ReadOnly")).Equals("true", StringComparison.CurrentCultureIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task SetReadOnlyAsync(string VarName, bool ReadOnly)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded");
|
|
||||||
await Settings.sqlDatabase.SetValueAsync("Variables", "VarName", VarName, "ReadOnly", ReadOnly ? "true" : "false");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetReadOnly(string VarName, bool ReadOnly)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded");
|
|
||||||
Settings.sqlDatabase.SetValue("Variables", "VarName", VarName, "ReadOnly", ReadOnly ? "true" : "false");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<bool> ExistsAsync(string VarName)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded");
|
|
||||||
return await Settings.sqlDatabase.KeyExistsAsync("Variables", "VarName", VarName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool Exists(string VarName)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded");
|
|
||||||
return Settings.sqlDatabase.KeyExists("Variables", "VarName", VarName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task AddAsync(string VarName, string Value, bool ReadOnly = false)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded");
|
|
||||||
if (await ExistsAsync(VarName))
|
|
||||||
{
|
|
||||||
await SetValueAsync(VarName, Value);
|
|
||||||
await SetReadOnlyAsync(VarName, ReadOnly);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await Settings.sqlDatabase.InsertAsync("Variables", VarName, Value, ReadOnly ? "true" : "false");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Add(string VarName, string Value, bool ReadOnly = false)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded");
|
|
||||||
if (Exists(VarName))
|
|
||||||
{
|
|
||||||
SetValue(VarName, Value);
|
|
||||||
SetReadOnly(VarName, ReadOnly);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Settings.sqlDatabase.Insert("Variables", VarName, Value, ReadOnly ? "true" : "false");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task RemoveKeyAsync(string VarName)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded");
|
|
||||||
await Settings.sqlDatabase.RemoveKeyAsync("Variables", "VarName", VarName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void RemoveKey(string VarName)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded");
|
|
||||||
Settings.sqlDatabase.RemoveKey("Variables", "VarName", VarName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Plugins
|
|
||||||
{
|
|
||||||
public static async Task<string> GetVersionAsync(string pluginName)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded yet");
|
|
||||||
|
|
||||||
string result = await Settings.sqlDatabase.GetValueAsync("Plugins", "PluginName", pluginName, "Version");
|
|
||||||
if (result is null)
|
|
||||||
return "0.0.0";
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetVersion(string pluginName)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded yet");
|
|
||||||
|
|
||||||
string result = Settings.sqlDatabase.GetValue("Plugins", "PluginName", pluginName, "Version");
|
|
||||||
if (result is null)
|
|
||||||
return "0.0.0";
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task SetVersionAsync(string pluginName, VersionString version)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded yet");
|
|
||||||
|
|
||||||
if (!await Settings.sqlDatabase.KeyExistsAsync("Plugins", "PluginName", pluginName))
|
|
||||||
{
|
|
||||||
await Settings.sqlDatabase.InsertAsync("Plugins", pluginName, version.ToShortString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await Settings.sqlDatabase.SetValueAsync("Plugins", "PluginName", pluginName, "Version", version.ToShortString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetVersion(string pluginName, VersionString version)
|
|
||||||
{
|
|
||||||
if (!IsLoaded)
|
|
||||||
throw new Exception("Config is not loaded yet");
|
|
||||||
|
|
||||||
if (!Settings.sqlDatabase.KeyExists("Plugins", "PluginName", pluginName))
|
|
||||||
{
|
|
||||||
Settings.sqlDatabase.Insert("Plugins", pluginName, version.ToShortString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings.sqlDatabase.SetValue("Plugins", "PluginName", pluginName, "Version", version.ToShortString());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Data.SQLite;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
namespace PluginManager.Database
|
|
||||||
{
|
|
||||||
public class SqlDatabase
|
|
||||||
{
|
|
||||||
private string ConnectionString;
|
|
||||||
private SQLiteConnection Connection;
|
|
||||||
|
|
||||||
public SqlDatabase(string fileName)
|
|
||||||
{
|
|
||||||
if (!File.Exists(fileName))
|
|
||||||
SQLiteConnection.CreateFile(fileName);
|
|
||||||
ConnectionString = $"URI=file:{fileName}";
|
|
||||||
Connection = new SQLiteConnection(ConnectionString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Open()
|
|
||||||
{
|
|
||||||
await Connection.OpenAsync();
|
|
||||||
|
|
||||||
//Console.WriteLine("Opened database successfully");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InsertAsync(string tableName, params string[] values)
|
|
||||||
{
|
|
||||||
|
|
||||||
string query = $"INSERT INTO {tableName} VALUES (";
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
query += $"'{values[i]}'";
|
|
||||||
if (i != values.Length - 1)
|
|
||||||
query += ", ";
|
|
||||||
}
|
|
||||||
query += ")";
|
|
||||||
|
|
||||||
SQLiteCommand command = new SQLiteCommand(query, Connection);
|
|
||||||
await command.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Insert(string tableName, params string[] values)
|
|
||||||
{
|
|
||||||
|
|
||||||
string query = $"INSERT INTO {tableName} VALUES (";
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
query += $"'{values[i]}'";
|
|
||||||
if (i != values.Length - 1)
|
|
||||||
query += ", ";
|
|
||||||
}
|
|
||||||
query += ")";
|
|
||||||
|
|
||||||
SQLiteCommand command = new SQLiteCommand(query, Connection);
|
|
||||||
command.ExecuteNonQuery();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RemoveKeyAsync(string tableName, string KeyName, string KeyValue)
|
|
||||||
{
|
|
||||||
|
|
||||||
string query = $"DELETE FROM {tableName} WHERE {KeyName} = '{KeyValue}'";
|
|
||||||
|
|
||||||
SQLiteCommand command = new SQLiteCommand(query, Connection);
|
|
||||||
await command.ExecuteNonQueryAsync();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveKey(string tableName, string KeyName, string KeyValue)
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
string query = $"DELETE FROM {tableName} WHERE {KeyName} = '{KeyValue}'";
|
|
||||||
|
|
||||||
SQLiteCommand command = new SQLiteCommand(query, Connection);
|
|
||||||
command.ExecuteNonQuery();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<bool> KeyExistsAsync(string tableName, string keyName, string KeyValue)
|
|
||||||
{
|
|
||||||
string query = $"SELECT * FROM {tableName} where {keyName} = '{KeyValue}'";
|
|
||||||
|
|
||||||
if (await ReadDataAsync(query) is not null)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool KeyExists(string tableName, string keyName, string KeyValue)
|
|
||||||
{
|
|
||||||
string query = $"SELECT * FROM {tableName} where {keyName} = '{KeyValue}'";
|
|
||||||
|
|
||||||
if (ReadData(query) is not null)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task SetValueAsync(string tableName, string keyName, string KeyValue, string ResultColumnName, string ResultColumnValue)
|
|
||||||
{
|
|
||||||
if (!await TableExistsAsync(tableName))
|
|
||||||
throw new System.Exception($"Table {tableName} does not exist");
|
|
||||||
|
|
||||||
await ExecuteAsync($"UPDATE {tableName} SET {ResultColumnName}='{ResultColumnValue}' WHERE {keyName}='{KeyValue}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetValue(string tableName, string keyName, string KeyValue, string ResultColumnName, string ResultColumnValue)
|
|
||||||
{
|
|
||||||
if (!TableExists(tableName))
|
|
||||||
throw new System.Exception($"Table {tableName} does not exist");
|
|
||||||
|
|
||||||
Execute($"UPDATE {tableName} SET {ResultColumnName}='{ResultColumnValue}' WHERE {keyName}='{KeyValue}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<string> GetValueAsync(string tableName, string keyName, string KeyValue, string ResultColumnName)
|
|
||||||
{
|
|
||||||
if (!await TableExistsAsync(tableName))
|
|
||||||
throw new System.Exception($"Table {tableName} does not exist");
|
|
||||||
|
|
||||||
return await ReadDataAsync($"SELECT {ResultColumnName} FROM {tableName} WHERE {keyName}='{KeyValue}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetValue(string tableName, string keyName, string KeyValue, string ResultColumnName)
|
|
||||||
{
|
|
||||||
if (!TableExists(tableName))
|
|
||||||
throw new System.Exception($"Table {tableName} does not exist");
|
|
||||||
|
|
||||||
return ReadData($"SELECT {ResultColumnName} FROM {tableName} WHERE {keyName}='{KeyValue}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Stop()
|
|
||||||
{
|
|
||||||
await Connection.CloseAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task AddColumnsToTableAsync(string tableName, string[] columns)
|
|
||||||
{
|
|
||||||
|
|
||||||
var command = Connection.CreateCommand();
|
|
||||||
command.CommandText = $"SELECT * FROM {tableName}";
|
|
||||||
var reader = await command.ExecuteReaderAsync();
|
|
||||||
var tableColumns = new List<string>();
|
|
||||||
for (int 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} TEXT";
|
|
||||||
await command.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddColumnsToTable(string tableName, string[] columns)
|
|
||||||
{
|
|
||||||
|
|
||||||
var command = Connection.CreateCommand();
|
|
||||||
command.CommandText = $"SELECT * FROM {tableName}";
|
|
||||||
var reader = command.ExecuteReader();
|
|
||||||
var tableColumns = new List<string>();
|
|
||||||
for (int 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} TEXT";
|
|
||||||
command.ExecuteNonQuery();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<int> ExecuteAsync(string query)
|
|
||||||
{
|
|
||||||
if (!Connection.State.HasFlag(System.Data.ConnectionState.Open))
|
|
||||||
await Connection.OpenAsync();
|
|
||||||
var command = new SQLiteCommand(query, Connection);
|
|
||||||
int answer = await command.ExecuteNonQueryAsync();
|
|
||||||
return answer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Execute(string query)
|
|
||||||
{
|
|
||||||
if (!Connection.State.HasFlag(System.Data.ConnectionState.Open))
|
|
||||||
Connection.Open();
|
|
||||||
var command = new SQLiteCommand(query, Connection);
|
|
||||||
int r = command.ExecuteNonQuery();
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> ReadDataAsync(string query)
|
|
||||||
{
|
|
||||||
if (!Connection.State.HasFlag(System.Data.ConnectionState.Open))
|
|
||||||
await Connection.OpenAsync();
|
|
||||||
var command = new SQLiteCommand(query, Connection);
|
|
||||||
var reader = await command.ExecuteReaderAsync();
|
|
||||||
|
|
||||||
object[] values = new object[reader.FieldCount];
|
|
||||||
if (reader.Read())
|
|
||||||
{
|
|
||||||
reader.GetValues(values);
|
|
||||||
return string.Join<object>(" ", values);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ReadData(string query)
|
|
||||||
{
|
|
||||||
if (!Connection.State.HasFlag(System.Data.ConnectionState.Open))
|
|
||||||
Connection.Open();
|
|
||||||
var command = new SQLiteCommand(query, Connection);
|
|
||||||
var reader = command.ExecuteReader();
|
|
||||||
|
|
||||||
object[] values = new object[reader.FieldCount];
|
|
||||||
if (reader.Read())
|
|
||||||
{
|
|
||||||
reader.GetValues(values);
|
|
||||||
return string.Join<object>(" ", values);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<object[]> ReadDataArrayAsync(string query)
|
|
||||||
{
|
|
||||||
if (!Connection.State.HasFlag(System.Data.ConnectionState.Open))
|
|
||||||
await Connection.OpenAsync();
|
|
||||||
var command = new SQLiteCommand(query, Connection);
|
|
||||||
var reader = await command.ExecuteReaderAsync();
|
|
||||||
|
|
||||||
object[] values = new object[reader.FieldCount];
|
|
||||||
if (reader.Read())
|
|
||||||
{
|
|
||||||
reader.GetValues(values);
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object[] ReadDataArray(string query)
|
|
||||||
{
|
|
||||||
if (!Connection.State.HasFlag(System.Data.ConnectionState.Open))
|
|
||||||
Connection.Open();
|
|
||||||
var command = new SQLiteCommand(query, Connection);
|
|
||||||
var reader = command.ExecuteReader();
|
|
||||||
|
|
||||||
object[] values = new object[reader.FieldCount];
|
|
||||||
if (reader.Read())
|
|
||||||
{
|
|
||||||
reader.GetValues(values);
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
using Discord;
|
|
||||||
using Discord.WebSocket;
|
|
||||||
|
|
||||||
namespace PluginManager.Interfaces
|
|
||||||
{
|
|
||||||
public interface DBSlashCommand
|
|
||||||
{
|
|
||||||
string Name { get; }
|
|
||||||
string Description { get; }
|
|
||||||
|
|
||||||
bool canUseDM { get; }
|
|
||||||
|
|
||||||
List<SlashCommandOptionBuilder> Options { get; }
|
|
||||||
|
|
||||||
void ExecuteServer(SocketSlashCommand context)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExecuteDM(SocketSlashCommand context) { }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
using Discord.WebSocket;
|
|
||||||
|
|
||||||
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>(string.Join(' ', data, 1, data.Length - 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,506 +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.Tasks;
|
|
||||||
|
|
||||||
using Discord.WebSocket;
|
|
||||||
|
|
||||||
using PluginManager.Interfaces;
|
|
||||||
using PluginManager.Loaders;
|
|
||||||
using PluginManager.Online;
|
|
||||||
using PluginManager.Others;
|
|
||||||
|
|
||||||
using OperatingSystem = PluginManager.Others.OperatingSystem;
|
|
||||||
|
|
||||||
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 static bool isDownloading;
|
|
||||||
private static bool pluginsLoaded;
|
|
||||||
private readonly DiscordSocketClient? client;
|
|
||||||
|
|
||||||
public ConsoleCommandsHandler(DiscordSocketClient client)
|
|
||||||
{
|
|
||||||
this.client = client;
|
|
||||||
InitializeBasicCommands();
|
|
||||||
|
|
||||||
|
|
||||||
//Settings.Variables.outputStream.WriteLine("Initialized console command handler !");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeBasicCommands()
|
|
||||||
{
|
|
||||||
commandList.Clear();
|
|
||||||
|
|
||||||
AddCommand("help", "Show help", "help <command>", args =>
|
|
||||||
{
|
|
||||||
if (args.Length <= 1)
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.WriteLine("Available commands:");
|
|
||||||
var 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[] { "-", "-", "-" });
|
|
||||||
Utilities.FormatAndAlignTable(items, TableFormat.DEFAULT);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var command in commandList)
|
|
||||||
if (command.CommandName == args[1])
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.WriteLine("Command description: " + command.Description);
|
|
||||||
Settings.Variables.outputStream.WriteLine("Command execution format:" + command.Usage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings.Variables.outputStream.WriteLine("Command not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
AddCommand("lp", "Load plugins", () =>
|
|
||||||
{
|
|
||||||
if (pluginsLoaded)
|
|
||||||
return;
|
|
||||||
var loader = new PluginLoader(client!);
|
|
||||||
var cc = Console.ForegroundColor;
|
|
||||||
loader.onCMDLoad += (name, typeName, success, exception) =>
|
|
||||||
{
|
|
||||||
if (name == null || name.Length < 2)
|
|
||||||
name = typeName;
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Green;
|
|
||||||
Settings.Variables.outputStream.WriteLine("[CMD] Successfully loaded command : " + name);
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
|
||||||
if (exception is null)
|
|
||||||
Settings.Variables.outputStream.WriteLine("An error occured while loading: " + name);
|
|
||||||
else
|
|
||||||
Settings.Variables.outputStream.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;
|
|
||||||
Settings.Variables.outputStream.WriteLine("[EVENT] Successfully loaded event : " + name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
|
||||||
Settings.Variables.outputStream.WriteLine("[EVENT] Failed to load event : " + name + " because " + exception!.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.ForegroundColor = cc;
|
|
||||||
};
|
|
||||||
|
|
||||||
loader.onSLSHLoad += (name, typeName, success, exception) =>
|
|
||||||
{
|
|
||||||
if (name == null || name.Length < 2)
|
|
||||||
name = typeName;
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Green;
|
|
||||||
Settings.Variables.outputStream.WriteLine("[SLASH] Successfully loaded command : " + name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
|
||||||
Settings.Variables.outputStream.WriteLine("[SLASH] Failed to load command : " + name + " because " + exception!.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.ForegroundColor = cc;
|
|
||||||
};
|
|
||||||
|
|
||||||
loader.LoadPlugins();
|
|
||||||
Console.ForegroundColor = cc;
|
|
||||||
pluginsLoaded = true;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
AddCommand("listplugs", "list available plugins", () => { manager.ListAvailablePlugins().Wait(); });
|
|
||||||
|
|
||||||
AddCommand("dwplug", "download plugin", "dwplug [name]", async args =>
|
|
||||||
{
|
|
||||||
isDownloading = true;
|
|
||||||
if (args.Length == 1)
|
|
||||||
{
|
|
||||||
isDownloading = false;
|
|
||||||
Settings.Variables.outputStream.WriteLine("Please specify plugin name");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var name = string.Join(' ', args, 1, args.Length - 1);
|
|
||||||
// info[0] = plugin type
|
|
||||||
// info[1] = plugin link
|
|
||||||
// info[2] = if others are required, or string.Empty if none
|
|
||||||
var info = await manager.GetPluginLinkByName(name);
|
|
||||||
if (info[1] == null) // link is null
|
|
||||||
{
|
|
||||||
if (name == "")
|
|
||||||
{
|
|
||||||
isDownloading = false;
|
|
||||||
Utilities.WriteColorText("Name is invalid");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isDownloading = false;
|
|
||||||
Utilities.WriteColorText($"Failed to find plugin &b{name} &c!" +
|
|
||||||
" Use &glistplugs &ccommand to display all available plugins !");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string path;
|
|
||||||
if (info[0] == "Plugin")
|
|
||||||
path = "./Data/Plugins/" + name + ".dll";
|
|
||||||
else
|
|
||||||
path = $"./{info[1].Split('/')[info[1].Split('/').Length - 1]}";
|
|
||||||
|
|
||||||
if (OperatingSystem.WINDOWS == Functions.GetOperatingSystem())
|
|
||||||
{
|
|
||||||
await ServerCom.DownloadFileAsync(info[1], path);
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.LINUX == Functions.GetOperatingSystem())
|
|
||||||
{
|
|
||||||
var bar = new Utilities.ProgressBar(ProgressBarType.NO_END);
|
|
||||||
bar.Start();
|
|
||||||
await ServerCom.DownloadFileNoProgressAsync(info[1], path);
|
|
||||||
bar.Stop("Plugin Downloaded !");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Settings.Variables.outputStream.WriteLine("\n");
|
|
||||||
|
|
||||||
// check requirements if any
|
|
||||||
|
|
||||||
if (info.Length == 3 && info[2] != string.Empty && info[2] != null)
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.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(',');
|
|
||||||
Settings.Variables.outputStream.WriteLine($"\nDownloading item: {split[1]}");
|
|
||||||
if (File.Exists("./" + split[1])) File.Delete("./" + split[1]);
|
|
||||||
if (OperatingSystem.WINDOWS == Functions.GetOperatingSystem())
|
|
||||||
{
|
|
||||||
await ServerCom.DownloadFileAsync(split[0], "./" + split[1]);
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.LINUX == Functions.GetOperatingSystem())
|
|
||||||
{
|
|
||||||
var bar = new Utilities.ProgressBar(ProgressBarType.NO_END);
|
|
||||||
bar.Start();
|
|
||||||
await ServerCom.DownloadFileNoProgressAsync(split[0], "./" + split[1]);
|
|
||||||
bar.Stop("Item downloaded !");
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings.Variables.outputStream.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"))
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.WriteLine($"Extracting {split[1]} ...");
|
|
||||||
var bar = new Utilities.ProgressBar(
|
|
||||||
ProgressBarType.NO_END);
|
|
||||||
bar.Start();
|
|
||||||
await Functions.ExtractArchive("./" + split[1], "./", null,
|
|
||||||
UnzipProgressType.PercentageFromTotalSize);
|
|
||||||
bar.Stop("Extracted");
|
|
||||||
Settings.Variables.outputStream.WriteLine("\n");
|
|
||||||
File.Delete("./" + split[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings.Variables.outputStream.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
var ver = await ServerCom.GetVersionOfPackageFromWeb(name);
|
|
||||||
if (ver is null) throw new Exception("Incorrect version");
|
|
||||||
await Config.Plugins.SetVersionAsync(name, ver);
|
|
||||||
|
|
||||||
isDownloading = false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
AddCommand("value", "read value from VariableStack", "value [key]", args =>
|
|
||||||
{
|
|
||||||
if (args.Length != 2)
|
|
||||||
return;
|
|
||||||
if (!Config.Variables.Exists(args[1]))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var data = Config.Variables.GetValue(args[1]);
|
|
||||||
Settings.Variables.outputStream.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.Variables.Add(key, value, isReadOnly);
|
|
||||||
Settings.Variables.outputStream.WriteLine($"Updated config file with the following command: {args[1]} => {value}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.WriteLine(ex.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
AddCommand("remv", "remove variable from system variables", "remv [key]", args =>
|
|
||||||
{
|
|
||||||
if (args.Length < 2)
|
|
||||||
return;
|
|
||||||
Config.Variables.RemoveKey(args[1]);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
AddCommand("sd", "Shuts down the discord bot", async () =>
|
|
||||||
{
|
|
||||||
if (client is null)
|
|
||||||
return;
|
|
||||||
var bar = new Utilities.ProgressBar(ProgressBarType.NO_END);
|
|
||||||
|
|
||||||
bar.Start();
|
|
||||||
bar.Stop("Saved config !");
|
|
||||||
Settings.Variables.outputStream.WriteLine();
|
|
||||||
Settings.sqlDatabase.Stop();
|
|
||||||
await client.StopAsync();
|
|
||||||
await client.DisposeAsync();
|
|
||||||
|
|
||||||
await Task.Delay(1000);
|
|
||||||
Environment.Exit(0);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
AddCommand("import", "Load an external command", "import [pluginName]", async args =>
|
|
||||||
{
|
|
||||||
if (args.Length <= 1) return;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var pName = string.Join(' ', args, 1, args.Length - 1);
|
|
||||||
var client = new HttpClient();
|
|
||||||
var url = (await manager.GetPluginLinkByName(pName))[1];
|
|
||||||
if (url is null) throw new Exception($"Invalid plugin name {pName}.");
|
|
||||||
var s = await client.GetStreamAsync(url);
|
|
||||||
var str = new MemoryStream();
|
|
||||||
await s.CopyToAsync(str);
|
|
||||||
var asmb = Assembly.Load(str.ToArray());
|
|
||||||
|
|
||||||
var types = asmb.GetTypes();
|
|
||||||
foreach (var type in types)
|
|
||||||
if (type.IsClass && typeof(DBEvent).IsAssignableFrom(type))
|
|
||||||
{
|
|
||||||
var instance = (DBEvent)Activator.CreateInstance(type);
|
|
||||||
instance.Start(this.client);
|
|
||||||
Settings.Variables.outputStream.WriteLine($"[EVENT] Loaded external {type.FullName}!");
|
|
||||||
}
|
|
||||||
else if (type.IsClass && typeof(DBCommand).IsAssignableFrom(type))
|
|
||||||
{
|
|
||||||
var instance = (DBCommand)Activator.CreateInstance(type);
|
|
||||||
Settings.Variables.outputStream.WriteLine($"[CMD] Instance: {type.FullName} loaded !");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.WriteLine(ex.Message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
AddCommand("remplug", "Remove a plugin", "remplug [plugName]", async args =>
|
|
||||||
{
|
|
||||||
if (args.Length <= 1) return;
|
|
||||||
|
|
||||||
isDownloading = true;
|
|
||||||
var plugName = string.Join(' ', args, 1, args.Length - 1);
|
|
||||||
if (pluginsLoaded)
|
|
||||||
{
|
|
||||||
if (Functions.GetOperatingSystem() == 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var location = $"./Data/Plugins/{plugName}.dll";
|
|
||||||
|
|
||||||
if (!File.Exists(location))
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.WriteLine("The plugin does not exist");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File.Delete(location);
|
|
||||||
|
|
||||||
Settings.Variables.outputStream.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]);
|
|
||||||
|
|
||||||
|
|
||||||
Settings.Variables.outputStream.WriteLine("Removed: " + split[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Directory.Exists($"./Data/Plugins/{plugName}"))
|
|
||||||
Directory.Delete($"./Data/Plugins/{plugName}", true);
|
|
||||||
|
|
||||||
if (Directory.Exists(plugName))
|
|
||||||
Directory.Delete(plugName, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
isDownloading = false;
|
|
||||||
Settings.Variables.outputStream.WriteLine(plugName + " has been successfully deleted !");
|
|
||||||
});
|
|
||||||
|
|
||||||
AddCommand("reload", "Reload the bot with all plugins", () =>
|
|
||||||
{
|
|
||||||
if (Functions.GetOperatingSystem() == OperatingSystem.WINDOWS)
|
|
||||||
{
|
|
||||||
Process.Start("DiscordBot.exe", "lp");
|
|
||||||
HandleCommand("sd");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Process.Start("./DiscordBot", "lp");
|
|
||||||
HandleCommand("sd");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//AddCommand("");
|
|
||||||
|
|
||||||
//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;
|
|
||||||
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(' ');
|
|
||||||
foreach (var item in commandList.ToList())
|
|
||||||
if (item.CommandName == args[0])
|
|
||||||
{
|
|
||||||
item.Action.Invoke(args);
|
|
||||||
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 (var i = 0; i < command.Length + 30; i++)
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
Console.SetCursorPosition(0, Console.CursorTop);
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings.Variables.outputStream.WriteLine();
|
|
||||||
item.Action(args);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
//Settings.Variables.outputStream.WriteLine($"Executing: {args[0]} with the following parameters: {args.MergeStrings(1)}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
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 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal delegate void FileLoadedEventHandler(LoaderArgs args);
|
|
||||||
|
|
||||||
internal delegate void PluginLoadedEventHandler(LoaderArgs args);
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using PluginManager.Interfaces;
|
|
||||||
using PluginManager.Others;
|
|
||||||
|
|
||||||
namespace PluginManager.Loaders
|
|
||||||
{
|
|
||||||
internal class LoaderV2
|
|
||||||
{
|
|
||||||
internal LoaderV2(string path, string extension)
|
|
||||||
{
|
|
||||||
this.path = path;
|
|
||||||
this.extension = extension;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private string path { get; }
|
|
||||||
private string extension { get; }
|
|
||||||
|
|
||||||
internal event FileLoadedEventHandler? FileLoaded;
|
|
||||||
|
|
||||||
internal event PluginLoadedEventHandler? PluginLoaded;
|
|
||||||
|
|
||||||
|
|
||||||
internal delegate void FileLoadedEventHandler(LoaderArgs args);
|
|
||||||
|
|
||||||
internal delegate void PluginLoadedEventHandler(LoaderArgs args);
|
|
||||||
|
|
||||||
|
|
||||||
internal (List<DBEvent>?, List<DBCommand>?, List<DBSlashCommand>?) Load()
|
|
||||||
{
|
|
||||||
|
|
||||||
List<DBEvent> events = new();
|
|
||||||
List<DBSlashCommand> slashCommands = new();
|
|
||||||
List<DBCommand> commands = new();
|
|
||||||
|
|
||||||
if (!Directory.Exists(path))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(path);
|
|
||||||
return (null, null, 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 = null,
|
|
||||||
IsLoaded = false,
|
|
||||||
PluginName = new FileInfo(file).Name.Split('.')[0],
|
|
||||||
Plugin = null
|
|
||||||
};
|
|
||||||
FileLoaded.Invoke(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (LoadItems<DBEvent>(), LoadItems<DBCommand>(), LoadItems<DBSlashCommand>());
|
|
||||||
}
|
|
||||||
|
|
||||||
internal List<T> LoadItems<T>()
|
|
||||||
{
|
|
||||||
List<T> list = new();
|
|
||||||
|
|
||||||
|
|
||||||
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 = typeof(T) == typeof(DBCommand) ? "DBCommand" : typeof(T) == typeof(DBEvent) ? "DBEvent" : "DBSlashCommand",
|
|
||||||
Plugin = plugin
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (PluginLoaded != null)
|
|
||||||
PluginLoaded.Invoke(new LoaderArgs
|
|
||||||
{
|
|
||||||
Exception = ex,
|
|
||||||
IsLoaded = false,
|
|
||||||
PluginName = type.FullName,
|
|
||||||
TypeName = nameof(T)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Functions.WriteErrFile(ex.ToString());
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using Discord;
|
|
||||||
using Discord.WebSocket;
|
|
||||||
|
|
||||||
using PluginManager.Interfaces;
|
|
||||||
using PluginManager.Online;
|
|
||||||
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);
|
|
||||||
|
|
||||||
public delegate void SLSHLoaded(string name, string tyypename, bool success, Exception? e = null);
|
|
||||||
|
|
||||||
private const string pluginFolder = @"./Data/Plugins/";
|
|
||||||
|
|
||||||
internal const string pluginExtension = "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>
|
|
||||||
/// Event that is fired when a <see cref="DBEvent" /> is successfully loaded into events list
|
|
||||||
/// </summary>
|
|
||||||
public SLSHLoaded? onSLSHLoad;
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
/// A list of <see cref="DBSlashCommand"/> commands
|
|
||||||
/// </summary>
|
|
||||||
public static List<DBSlashCommand>? SlashCommands { 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/", $"*.{pluginExtension}",
|
|
||||||
SearchOption.AllDirectories))
|
|
||||||
await Task.Run(async () =>
|
|
||||||
{
|
|
||||||
var name = new FileInfo(file).Name.Split('.')[0];
|
|
||||||
var version = await ServerCom.GetVersionOfPackageFromWeb(name);
|
|
||||||
if (version is null)
|
|
||||||
return;
|
|
||||||
if (Config.Plugins.GetVersion(name) is not null)
|
|
||||||
Config.Plugins.SetVersion(name, version);
|
|
||||||
|
|
||||||
if (await PluginUpdater.CheckForUpdates(name))
|
|
||||||
await PluginUpdater.Download(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
//Load all plugins
|
|
||||||
|
|
||||||
Commands = new List<DBCommand>();
|
|
||||||
Events = new List<DBEvent>();
|
|
||||||
SlashCommands = new List<DBSlashCommand>();
|
|
||||||
|
|
||||||
Functions.WriteLogFile("Starting plugin loader ... Client: " + _client.CurrentUser.Username);
|
|
||||||
Settings.Variables.outputStream.WriteLine("Loading plugins");
|
|
||||||
|
|
||||||
var loader = new LoaderV2("./Data/Plugins", "dll");
|
|
||||||
loader.FileLoaded += (args) => Functions.WriteLogFile($"{args.PluginName} file Loaded");
|
|
||||||
loader.PluginLoaded += Loader_PluginLoaded;
|
|
||||||
var res = loader.Load();
|
|
||||||
Events = res.Item1;
|
|
||||||
Commands = res.Item2;
|
|
||||||
SlashCommands = res.Item3;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void Loader_PluginLoaded(LoaderArgs args)
|
|
||||||
{
|
|
||||||
// Settings.Variables.outputStream.WriteLine(args.TypeName);
|
|
||||||
switch (args.TypeName)
|
|
||||||
{
|
|
||||||
case "DBCommand":
|
|
||||||
onCMDLoad?.Invoke(((DBCommand)args.Plugin!).Command, args.TypeName!, args.IsLoaded, args.Exception);
|
|
||||||
break;
|
|
||||||
case "DBEvent":
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (args.IsLoaded)
|
|
||||||
((DBEvent)args.Plugin!).Start(_client);
|
|
||||||
|
|
||||||
onEVELoad?.Invoke(((DBEvent)args.Plugin!).Name, args.TypeName!, args.IsLoaded, args.Exception);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.WriteLine(ex.ToString());
|
|
||||||
Settings.Variables.outputStream.WriteLine("Plugin: " + args.PluginName);
|
|
||||||
Settings.Variables.outputStream.WriteLine("Type: " + args.TypeName);
|
|
||||||
Settings.Variables.outputStream.WriteLine("IsLoaded: " + args.IsLoaded);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "DBSlashCommand":
|
|
||||||
if (args.IsLoaded)
|
|
||||||
{
|
|
||||||
var slash = (DBSlashCommand)args.Plugin;
|
|
||||||
SlashCommandBuilder builder = new SlashCommandBuilder();
|
|
||||||
builder.WithName(slash.Name);
|
|
||||||
builder.WithDescription(slash.Description);
|
|
||||||
builder.WithDMPermission(slash.canUseDM);
|
|
||||||
builder.Options = slash.Options;
|
|
||||||
//Settings.Variables.outputStream.WriteLine("Loaded " + slash.Name);
|
|
||||||
onSLSHLoad?.Invoke(((DBSlashCommand)args.Plugin!).Name, args.TypeName, args.IsLoaded, args.Exception);
|
|
||||||
await _client.CreateGlobalApplicationCommandAsync(builder.Build());
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
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,84 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace PluginManager.Online.Helpers;
|
|
||||||
|
|
||||||
public class VersionString
|
|
||||||
{
|
|
||||||
public int PackageCheckVersion;
|
|
||||||
public int PackageMainVersion;
|
|
||||||
public int PackageVersionID;
|
|
||||||
|
|
||||||
public VersionString(string version)
|
|
||||||
{
|
|
||||||
var data = version.Split('.');
|
|
||||||
try
|
|
||||||
{
|
|
||||||
PackageVersionID = int.Parse(data[0]);
|
|
||||||
PackageMainVersion = int.Parse(data[1]);
|
|
||||||
PackageCheckVersion = int.Parse(data[2]);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine(version);
|
|
||||||
throw new Exception("Failed to write Version", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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}";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#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)
|
|
||||||
{
|
|
||||||
return !(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)
|
|
||||||
{
|
|
||||||
return !(s1 == s2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator <=(VersionString s1, VersionString s2)
|
|
||||||
{
|
|
||||||
return s1 < s2 || s1 == s2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator >=(VersionString s1, VersionString s2)
|
|
||||||
{
|
|
||||||
return s1 > s2 || s1 == s2;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
@@ -1,125 +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" };
|
|
||||||
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 ServerCom.GetVersionOfPackageFromWeb(content[0]) ?? new VersionString("0.0.0"))
|
|
||||||
.ToShortString();
|
|
||||||
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 ServerCom.GetVersionOfPackageFromWeb(content[0]) ?? new VersionString("0.0.0"))
|
|
||||||
.ToShortString();
|
|
||||||
data.Add(display);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data.Add(new[] { "-", "-", "-", "-" });
|
|
||||||
|
|
||||||
Utilities.FormatAndAlignTable(data, TableFormat.CENTER_EACH_COLUMN_BASED);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.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)
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.WriteLine("Failed to execute command: listplugs\nReason: " + exception.Message);
|
|
||||||
Functions.WriteErrFile(exception.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new string[] { null!, null!, null! };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using PluginManager.Online.Helpers;
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
var response = await OnlineFunctions.DownloadStringAsync(link);
|
|
||||||
var 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 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)
|
|
||||||
{
|
|
||||||
var isDownloading = true;
|
|
||||||
float c_progress = 0;
|
|
||||||
|
|
||||||
var pbar = new Utilities.ProgressBar(ProgressBarType.NORMAL) { Max = 100f, NoColor = true };
|
|
||||||
|
|
||||||
IProgress<float> progress = new Progress<float>(percent => { c_progress = percent; });
|
|
||||||
|
|
||||||
|
|
||||||
var 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static VersionString? GetVersionOfPackage(string pakName)
|
|
||||||
{
|
|
||||||
if (Config.Plugins.GetVersion(pakName) is null)
|
|
||||||
return null;
|
|
||||||
return new VersionString(Config.Plugins.GetVersion(pakName));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<VersionString?> GetVersionOfPackageFromWeb(string pakName)
|
|
||||||
{
|
|
||||||
var url = "https://raw.githubusercontent.com/Wizzy69/installer/discord-bot-files/Versions";
|
|
||||||
var data = await ReadTextFromURL(url);
|
|
||||||
foreach (var item in data)
|
|
||||||
{
|
|
||||||
if (item.StartsWith("#"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
string[] split = item.Split(',');
|
|
||||||
if (split[0] == pakName)
|
|
||||||
return new VersionString(split[1]);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using PluginManager.Items;
|
|
||||||
using PluginManager.Others;
|
|
||||||
|
|
||||||
namespace PluginManager.Online.Updates;
|
|
||||||
|
|
||||||
public class PluginUpdater
|
|
||||||
{
|
|
||||||
public static async Task<bool> CheckForUpdates(string pakName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var webV = await ServerCom.GetVersionOfPackageFromWeb(pakName);
|
|
||||||
var local = ServerCom.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)
|
|
||||||
{
|
|
||||||
var url = "https://raw.githubusercontent.com/Wizzy69/installer/discord-bot-files/Versions";
|
|
||||||
var info = await ServerCom.ReadTextFromURL(url);
|
|
||||||
var version = await ServerCom.GetVersionOfPackageFromWeb(pakName);
|
|
||||||
|
|
||||||
if (version is null) return Update.Empty;
|
|
||||||
var update = new Update(pakName, string.Join('\n', info), version);
|
|
||||||
return update;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task Download(string pakName)
|
|
||||||
{
|
|
||||||
Utilities.WriteColorText("An update was found for &g" + pakName + "&c. Version: &r" +
|
|
||||||
(await ServerCom.GetVersionOfPackageFromWeb(pakName))?.ToShortString() +
|
|
||||||
"&c. Current Version: &y" +
|
|
||||||
ServerCom.GetVersionOfPackage(pakName)?.ToShortString());
|
|
||||||
await ConsoleCommandsHandler.ExecuteCommad("dwplug " + pakName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
using System;
|
|
||||||
using PluginManager.Online.Helpers;
|
|
||||||
|
|
||||||
namespace PluginManager.Online.Updates;
|
|
||||||
|
|
||||||
public class Update
|
|
||||||
{
|
|
||||||
public static Update Empty = new(null, null, null);
|
|
||||||
|
|
||||||
private readonly bool isEmpty;
|
|
||||||
|
|
||||||
public VersionString newVersion;
|
|
||||||
public string pakName;
|
|
||||||
public string UpdateMessage;
|
|
||||||
|
|
||||||
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 Exception("The update is EMPTY. Can not print information about an empty update !");
|
|
||||||
return $"Package Name: {pakName}\n" +
|
|
||||||
$"Update Message: {UpdateMessage}\n" +
|
|
||||||
$"Version: {newVersion}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,365 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace PluginManager.Others;
|
|
||||||
|
|
||||||
public static class Utilities
|
|
||||||
{
|
|
||||||
private static Dictionary<char, ConsoleColor> Colors = new()
|
|
||||||
{
|
|
||||||
{ 'g', ConsoleColor.Green },
|
|
||||||
{ 'b', ConsoleColor.Blue },
|
|
||||||
{ 'r', ConsoleColor.Red },
|
|
||||||
{ 'm', ConsoleColor.Magenta },
|
|
||||||
{ 'y', ConsoleColor.Yellow }
|
|
||||||
};
|
|
||||||
|
|
||||||
private static char ColorPrefix = '&';
|
|
||||||
|
|
||||||
|
|
||||||
private static bool CanAproximateTo(this float f, float y)
|
|
||||||
{
|
|
||||||
return 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)
|
|
||||||
{
|
|
||||||
var tableLine = '-';
|
|
||||||
var tableCross = '+';
|
|
||||||
var tableWall = '|';
|
|
||||||
|
|
||||||
var len = new int[data[0].Length];
|
|
||||||
foreach (var line in data)
|
|
||||||
for (var i = 0; i < line.Length; i++)
|
|
||||||
if (line[i].Length > len[i])
|
|
||||||
len[i] = line[i].Length;
|
|
||||||
|
|
||||||
|
|
||||||
foreach (var row in data)
|
|
||||||
{
|
|
||||||
if (row[0][0] == tableLine)
|
|
||||||
Settings.Variables.outputStream.Write(tableCross);
|
|
||||||
else
|
|
||||||
Settings.Variables.outputStream.Write(tableWall);
|
|
||||||
for (var l = 0; l < row.Length; l++)
|
|
||||||
{
|
|
||||||
if (row[l][0] == tableLine)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < len[l] + 4; ++i)
|
|
||||||
Settings.Variables.outputStream.Write(tableLine);
|
|
||||||
}
|
|
||||||
else if (row[l].Length == len[l])
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
Settings.Variables.outputStream.Write(row[l]);
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var lenHalf = row[l].Length / 2;
|
|
||||||
for (var i = 0; i < (len[l] + 4) / 2 - lenHalf; ++i)
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
Settings.Variables.outputStream.Write(row[l]);
|
|
||||||
for (var i = (len[l] + 4) / 2 + lenHalf + 1; i < len[l] + 4; ++i)
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
if (row[l].Length % 2 == 0)
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings.Variables.outputStream.Write(row[l][0] == tableLine ? tableCross : tableWall);
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings.Variables.outputStream.WriteLine(); //end line
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (format == TableFormat.CENTER_OVERALL_LENGTH)
|
|
||||||
{
|
|
||||||
var maxLen = 0;
|
|
||||||
foreach (var row in data)
|
|
||||||
foreach (var s in row)
|
|
||||||
if (s.Length > maxLen)
|
|
||||||
maxLen = s.Length;
|
|
||||||
|
|
||||||
var div = (maxLen + 4) / 2;
|
|
||||||
|
|
||||||
foreach (var row in data)
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.Write("\t");
|
|
||||||
if (row[0] == "-")
|
|
||||||
Settings.Variables.outputStream.Write("+");
|
|
||||||
else
|
|
||||||
Settings.Variables.outputStream.Write("|");
|
|
||||||
|
|
||||||
foreach (var s in row)
|
|
||||||
{
|
|
||||||
if (s == "-")
|
|
||||||
{
|
|
||||||
for (var i = 0; i < maxLen + 4; ++i)
|
|
||||||
Settings.Variables.outputStream.Write("-");
|
|
||||||
}
|
|
||||||
else if (s.Length == maxLen)
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
Settings.Variables.outputStream.Write(s);
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var lenHalf = s.Length / 2;
|
|
||||||
for (var i = 0; i < div - lenHalf; ++i)
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
Settings.Variables.outputStream.Write(s);
|
|
||||||
for (var i = div + lenHalf + 1; i < maxLen + 4; ++i)
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
if (s.Length % 2 == 0)
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s == "-")
|
|
||||||
Settings.Variables.outputStream.Write("+");
|
|
||||||
else
|
|
||||||
Settings.Variables.outputStream.Write("|");
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings.Variables.outputStream.WriteLine(); //end line
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (format == TableFormat.DEFAULT)
|
|
||||||
{
|
|
||||||
var widths = new int[data[0].Length];
|
|
||||||
var space_between_columns = 3;
|
|
||||||
for (var i = 0; i < data.Count; i++)
|
|
||||||
for (var j = 0; j < data[i].Length; j++)
|
|
||||||
if (data[i][j].Length > widths[j])
|
|
||||||
widths[j] = data[i][j].Length;
|
|
||||||
|
|
||||||
for (var i = 0; i < data.Count; i++)
|
|
||||||
{
|
|
||||||
for (var j = 0; j < data[i].Length; j++)
|
|
||||||
{
|
|
||||||
if (data[i][j] == "-")
|
|
||||||
data[i][j] = " ";
|
|
||||||
Settings.Variables.outputStream.Write(data[i][j]);
|
|
||||||
for (var k = 0; k < widths[j] - data[i][j].Length + 1 + space_between_columns; k++)
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings.Variables.outputStream.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("Unknown type of table");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void WriteColorText(string text, bool appendNewLineAtEnd = true)
|
|
||||||
{
|
|
||||||
if (Console.Out != Settings.Variables.outputStream)
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.Write(text);
|
|
||||||
if (appendNewLineAtEnd)
|
|
||||||
Settings.Variables.outputStream.WriteLine();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var initialForeGround = Console.ForegroundColor;
|
|
||||||
var input = text.ToCharArray();
|
|
||||||
for (var i = 0; i < input.Length; i++)
|
|
||||||
if (input[i] == ColorPrefix)
|
|
||||||
{
|
|
||||||
if (i + 1 < input.Length)
|
|
||||||
{
|
|
||||||
if (Colors.ContainsKey(input[i + 1]))
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = Colors[input[i + 1]];
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
else if (input[i + 1] == 'c')
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = initialForeGround;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.Write(input[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.ForegroundColor = initialForeGround;
|
|
||||||
if (appendNewLineAtEnd)
|
|
||||||
Settings.Variables.outputStream.WriteLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Progress bar object
|
|
||||||
/// </summary>
|
|
||||||
public class ProgressBar
|
|
||||||
{
|
|
||||||
private readonly int BarLength = 32;
|
|
||||||
|
|
||||||
private bool isRunning;
|
|
||||||
private int position = 1;
|
|
||||||
private bool positive = true;
|
|
||||||
|
|
||||||
public ProgressBar(ProgressBarType type)
|
|
||||||
{
|
|
||||||
if (Settings.Variables.outputStream != Console.Out)
|
|
||||||
throw new Exception("This class (or function) can be used with console only. For UI please use another approach.");
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float Max { get; init; }
|
|
||||||
public ConsoleColor Color { get; init; }
|
|
||||||
public bool NoColor { get; init; }
|
|
||||||
public ProgressBarType type { get; set; }
|
|
||||||
|
|
||||||
public int TotalLength { get; private set; }
|
|
||||||
|
|
||||||
|
|
||||||
public async void Start()
|
|
||||||
{
|
|
||||||
if (type != ProgressBarType.NO_END)
|
|
||||||
throw new Exception("Only NO_END progress bar can use this method");
|
|
||||||
if (isRunning)
|
|
||||||
throw new Exception("This progress bar is already running");
|
|
||||||
|
|
||||||
isRunning = true;
|
|
||||||
while (isRunning)
|
|
||||||
{
|
|
||||||
UpdateNoEnd();
|
|
||||||
await Task.Delay(100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Start(string message)
|
|
||||||
{
|
|
||||||
if (type != ProgressBarType.NO_END)
|
|
||||||
throw new Exception("Only NO_END progress bar can use this method");
|
|
||||||
if (isRunning)
|
|
||||||
throw new Exception("This progress bar is already running");
|
|
||||||
|
|
||||||
isRunning = true;
|
|
||||||
|
|
||||||
TotalLength = message.Length + BarLength + 5;
|
|
||||||
while (isRunning)
|
|
||||||
{
|
|
||||||
UpdateNoEnd(message);
|
|
||||||
await Task.Delay(100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
if (type != ProgressBarType.NO_END)
|
|
||||||
throw new Exception("Only NO_END progress bar can use this method");
|
|
||||||
if (!isRunning)
|
|
||||||
throw new Exception("Can not stop a progressbar that did not start");
|
|
||||||
isRunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop(string message)
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
|
|
||||||
if (message is not null)
|
|
||||||
{
|
|
||||||
Console.CursorLeft = 0;
|
|
||||||
for (var i = 0; i < BarLength + message.Length + 1; i++)
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
Console.CursorLeft = 0;
|
|
||||||
Settings.Variables.outputStream.WriteLine(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(float progress)
|
|
||||||
{
|
|
||||||
if (type == ProgressBarType.NO_END)
|
|
||||||
throw new Exception("This function is for progress bars with end");
|
|
||||||
|
|
||||||
UpdateNormal(progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateNoEnd(string message)
|
|
||||||
{
|
|
||||||
Console.CursorLeft = 0;
|
|
||||||
Settings.Variables.outputStream.Write("[");
|
|
||||||
for (var i = 1; i <= position; i++)
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
Settings.Variables.outputStream.Write("<==()==>");
|
|
||||||
position += positive ? 1 : -1;
|
|
||||||
for (var i = position; i <= BarLength - 1 - (positive ? 0 : 2); i++)
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
Settings.Variables.outputStream.Write("] " + message);
|
|
||||||
|
|
||||||
|
|
||||||
if (position == BarLength - 1 || position == 1)
|
|
||||||
positive = !positive;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateNoEnd()
|
|
||||||
{
|
|
||||||
Console.CursorLeft = 0;
|
|
||||||
Settings.Variables.outputStream.Write("[");
|
|
||||||
for (var i = 1; i <= position; i++)
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
Settings.Variables.outputStream.Write("<==()==>");
|
|
||||||
position += positive ? 1 : -1;
|
|
||||||
for (var i = position; i <= BarLength - 1 - (positive ? 0 : 2); i++)
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
Settings.Variables.outputStream.Write("]");
|
|
||||||
|
|
||||||
|
|
||||||
if (position == BarLength - 1 || position == 1)
|
|
||||||
positive = !positive;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateNormal(float progress)
|
|
||||||
{
|
|
||||||
Console.CursorLeft = 0;
|
|
||||||
Settings.Variables.outputStream.Write("[");
|
|
||||||
Console.CursorLeft = BarLength;
|
|
||||||
Settings.Variables.outputStream.Write("]");
|
|
||||||
Console.CursorLeft = 1;
|
|
||||||
var onechunk = 30.0f / Max;
|
|
||||||
|
|
||||||
var position = 1;
|
|
||||||
|
|
||||||
for (var i = 0; i < onechunk * progress; i++)
|
|
||||||
{
|
|
||||||
Console.BackgroundColor = NoColor ? ConsoleColor.Black : Color;
|
|
||||||
Console.CursorLeft = position++;
|
|
||||||
Settings.Variables.outputStream.Write("#");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = position; i < BarLength; i++)
|
|
||||||
{
|
|
||||||
Console.BackgroundColor = NoColor ? ConsoleColor.Black : ConsoleColor.DarkGray;
|
|
||||||
Console.CursorLeft = position++;
|
|
||||||
Settings.Variables.outputStream.Write(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.CursorLeft = BarLength + 4;
|
|
||||||
Console.BackgroundColor = ConsoleColor.Black;
|
|
||||||
if (progress.CanAproximateTo(Max))
|
|
||||||
Settings.Variables.outputStream.Write(progress + " % ✓");
|
|
||||||
else
|
|
||||||
Settings.Variables.outputStream.Write(MathF.Round(progress, 2) + " % ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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,300 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using Discord.WebSocket;
|
|
||||||
|
|
||||||
using PluginManager.Items;
|
|
||||||
|
|
||||||
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 (var s = entry.Open())
|
|
||||||
using (var 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)
|
|
||||||
{
|
|
||||||
var 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)
|
|
||||||
{
|
|
||||||
var 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>
|
|
||||||
/// Get the Operating system you are runnin on
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An Operating system</returns>
|
|
||||||
public static OperatingSystem GetOperatingSystem()
|
|
||||||
{
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return OperatingSystem.WINDOWS;
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return OperatingSystem.LINUX;
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return OperatingSystem.MAC_OS;
|
|
||||||
return OperatingSystem.UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<string> GetArguments(SocketMessage message)
|
|
||||||
{
|
|
||||||
var 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));
|
|
||||||
|
|
||||||
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>
|
|
||||||
/// 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 (var archive = ZipFile.OpenRead(zip))
|
|
||||||
{
|
|
||||||
if (type == UnzipProgressType.PercentageFromNumberOfFiles)
|
|
||||||
{
|
|
||||||
var totalZIPFiles = archive.Entries.Count();
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.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 (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
|
|
||||||
{
|
|
||||||
entry.ExtractToFile(Path.Combine(folder, entry.FullName), true);
|
|
||||||
currentSize += (ulong)entry.CompressedLength;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Settings.Variables.outputStream.WriteLine($"Failed to extract {entry.Name}. Exception: {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(10);
|
|
||||||
if (progress != null)
|
|
||||||
progress.Report((float)currentSize / zipSize * 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (var md5 = MD5.Create())
|
|
||||||
{
|
|
||||||
var inputBytes = Encoding.ASCII.GetBytes(input);
|
|
||||||
var hashBytes = md5.ComputeHash(inputBytes);
|
|
||||||
return Convert.ToHexString(hashBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +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" />
|
|
||||||
<PackageReference Include="System.Data.SQLite" Version="1.0.116" />
|
|
||||||
<PackageReference Include="Terminal.Gui" Version="1.8.2" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
|
|
||||||
using PluginManager.Database;
|
|
||||||
|
|
||||||
namespace PluginManager
|
|
||||||
{
|
|
||||||
public class Settings
|
|
||||||
{
|
|
||||||
|
|
||||||
public static class Variables
|
|
||||||
{
|
|
||||||
public static string WebsiteURL = "https://wizzy69.github.io/SethDiscordBot";
|
|
||||||
public static string UpdaterURL = "https://github.com/Wizzy69/installer/releases/download/release-1-discordbot/Updater.zip";
|
|
||||||
|
|
||||||
public static TextWriter outputStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SqlDatabase sqlDatabase;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
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>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user