17 Commits

Author SHA1 Message Date
3858156393 Made Entry Class static 2023-10-22 12:52:52 +03:00
6279c5c3a9 Updated Logger and Created Command to change settings variables 2023-10-01 14:11:34 +03:00
f58a57c6cd Improved logging. 2023-09-26 21:46:54 +03:00
d00ebfd7ed Fixed new name in README 2023-09-25 22:14:53 +03:00
ab279bd284 updated README.md 2023-09-25 22:12:44 +03:00
89c4932cd7 Fixed plugin refresh command and added new method for executing tasks without return type in ConsoleUtilities 2023-09-25 22:06:42 +03:00
c577f625c2 New method to execute using a progress bar feedback on process 2023-09-18 23:54:04 +03:00
58624f4037 Improved download speed and started using Spectre.Console package 2023-09-18 23:13:44 +03:00
c9249dc71b Plugin install does not display all logs when reloading plugin list 2023-09-07 14:55:09 +03:00
5e4f1ca35f The plugin install command now automatically refreshes the installed plugins. 2023-09-07 14:43:10 +03:00
0d8fdb5904 Updated log manager 2023-09-07 13:28:49 +03:00
92a18e3495 Removed URLs file from bot config 2023-09-07 12:50:36 +03:00
e929646e8e removed error when invalid plugin. It was called even when a typo was made 2023-08-15 16:42:13 +03:00
6315d13d18 Fixed Internal Actions to refresh after external actions are loaded 2023-08-15 16:17:32 +03:00
ee527bb36f a 2023-08-08 22:21:36 +03:00
86514d1770 Fixed version 2023-08-08 22:20:16 +03:00
9e8ed1e911 Fixed version 2023-08-08 22:19:29 +03:00
38 changed files with 845 additions and 776 deletions

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using PluginManager;
using PluginManager.Interfaces; using PluginManager.Interfaces;
using PluginManager.Others; using PluginManager.Others;

View File

@@ -17,9 +17,8 @@ public class Exit : ICommandAction
{ {
if (args is null || args.Length == 0) if (args is null || args.Length == 0)
{ {
Config.Logger.Log("Exiting...", "Exit", isInternal: false); Config.Logger.Log("Exiting...", source: typeof(ICommandAction), type: LogType.WARNING);
await Config.AppSettings.SaveToFile(); await Config.AppSettings.SaveToFile();
await Config.Logger.SaveToFile();
Environment.Exit(0); Environment.Exit(0);
} }
else else
@@ -34,7 +33,7 @@ public class Exit : ICommandAction
case "-f": case "-f":
case "force": case "force":
Config.Logger.Log("Exiting (FORCE)...", "Exit", LogLevel.WARNING, false); Config.Logger.Log("Exiting (FORCE)...", source: typeof(ICommandAction), type: LogType.WARNING);
Environment.Exit(0); Environment.Exit(0);
break; break;

View File

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

View File

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

View File

@@ -33,7 +33,7 @@ public class Help : ICommandAction
items.Add(new[] { "-", "-", "-" }); items.Add(new[] { "-", "-", "-" });
Utilities.Utilities.FormatAndAlignTable(items, ConsoleUtilities.FormatAndAlignTable(items,
TableFormat.CENTER_EACH_COLUMN_BASED TableFormat.CENTER_EACH_COLUMN_BASED
); );
return; return;
@@ -55,7 +55,7 @@ public class Help : ICommandAction
new[] { "-", "-", "-" } new[] { "-", "-", "-" }
}; };
Utilities.Utilities.FormatAndAlignTable(actionData, ConsoleUtilities.FormatAndAlignTable(actionData,
TableFormat.CENTER_EACH_COLUMN_BASED TableFormat.CENTER_EACH_COLUMN_BASED
); );
} }

View File

@@ -1,12 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordBot.Utilities; using System.Windows.Input;
using DiscordBot.Bot.Actions.Extra;
using PluginManager; using PluginManager;
using PluginManager.Interfaces; using PluginManager.Interfaces;
using PluginManager.Loaders; using PluginManager.Loaders;
using PluginManager.Online; using PluginManager.Online;
using PluginManager.Others; using PluginManager.Others;
using Spectre.Console;
namespace DiscordBot.Bot.Actions; namespace DiscordBot.Bot.Actions;
@@ -15,7 +17,7 @@ public class Plugin : ICommandAction
private bool pluginsLoaded; private bool pluginsLoaded;
public string ActionName => "plugin"; public string ActionName => "plugin";
public string Description => "Manages plugins. Use plugin help for more info."; public string Description => "Manages plugins. Use plugin help for more info.";
public string Usage => "plugin [help|list|load|install]"; public string Usage => "plugin [help|list|load|install|refresh]";
public InternalActionRunType RunType => InternalActionRunType.ON_CALL; public InternalActionRunType RunType => InternalActionRunType.ON_CALL;
public async Task Execute(string[] args) public async Task Execute(string[] args)
@@ -27,103 +29,36 @@ public class Plugin : ICommandAction
Console.WriteLine("list : Lists all plugins"); Console.WriteLine("list : Lists all plugins");
Console.WriteLine("load : Loads all plugins"); Console.WriteLine("load : Loads all plugins");
Console.WriteLine("install : Installs a plugin"); Console.WriteLine("install : Installs a plugin");
Console.WriteLine("refresh : Refreshes the plugin list");
return; return;
} }
switch ( args[0] ) var manager = new PluginsManager();
switch (args[0])
{ {
case "list": case "refresh":
var manager = await PluginMethods.RefreshPlugins(true);
new PluginsManager(Program.URLs["PluginList"], Program.URLs["PluginVersions"]);
var data = await manager.GetAvailablePlugins();
var items = new List<string[]>
{
new[] { "-", "-", "-", "-" },
new[] { "Name", "Description", "Type", "Version" },
new[] { "-", "-", "-", "-" }
};
foreach (var plugin in data) items.Add(new[] { plugin[0], plugin[1], plugin[2], plugin[3] });
items.Add(new[] { "-", "-", "-", "-" });
Utilities.Utilities.FormatAndAlignTable(items, TableFormat.DEFAULT);
break; break;
case "list":
await PluginMethods.List();
break;
case "load": case "load":
if (pluginsLoaded) if (pluginsLoaded)
{
Config.Logger.Log("Plugins already loaded", source: typeof(ICommandAction), type: LogType.WARNING);
break; break;
var loader = new PluginLoader(Config.DiscordBot.client);
var cc = Console.ForegroundColor;
loader.onCMDLoad += (name, typeName, success, exception) =>
{
if (name == null || name.Length < 2)
name = typeName;
if (success)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("[CMD] Successfully loaded command : " + name);
} }
else if (Config.DiscordBot is null)
{ {
Console.ForegroundColor = ConsoleColor.Red; Config.Logger.Log("DiscordBot is null", source: typeof(ICommandAction), type: LogType.WARNING);
if (exception is null) break;
Console.WriteLine("An error occured while loading: " + name);
else
Console.WriteLine("[CMD] Failed to load command : " + name + " because " +
exception!.Message
);
} }
Console.ForegroundColor = cc; pluginsLoaded = await PluginMethods.LoadPlugins(args);
};
loader.onEVELoad += (name, typeName, success, exception) =>
{
if (name == null || name.Length < 2)
name = typeName;
if (success)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("[EVENT] Successfully loaded event : " + name);
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("[EVENT] Failed to load event : " + name + " because " + exception!.Message);
}
Console.ForegroundColor = cc;
};
loader.onSLSHLoad += (name, typeName, success, exception) =>
{
if (name == null || name.Length < 2)
name = typeName;
if (success)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("[SLASH] Successfully loaded command : " + name);
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("[SLASH] Failed to load command : " + name + " because " +
exception!.Message
);
}
Console.ForegroundColor = cc;
};
loader.LoadPlugins();
Console.ForegroundColor = cc;
pluginsLoaded = true;
break; break;
case "install": case "install":
@@ -140,57 +75,7 @@ public class Plugin : ICommandAction
} }
} }
var pluginManager = await PluginMethods.DownloadPlugin(manager, pluginName);
new PluginsManager(Program.URLs["PluginList"], Program.URLs["PluginVersions"]);
var pluginData = await pluginManager.GetPluginLinkByName(pluginName);
if (pluginData == null || pluginData.Length == 0)
{
Console.WriteLine($"Plugin {pluginName} not found. Please check the spelling and try again.");
break;
}
var pluginType = pluginData[0];
var pluginLink = pluginData[1];
var pluginRequirements = pluginData[2];
Console.WriteLine("Downloading plugin...");
//download plugin progress bar for linux and windows terminals
var spinner = new Utilities.Utilities.Spinner();
spinner.Start();
IProgress<float> progress = new Progress<float>(p => { spinner.Message = $"Downloading {pluginName}... {Math.Round(p, 2)}% "; });
await ServerCom.DownloadFileAsync(pluginLink, $"./Data/{pluginType}s/{pluginName}.dll", progress);
spinner.Stop();
Console.WriteLine();
if (pluginRequirements == string.Empty)
{
Console.WriteLine("Plugin installed successfully");
break;
}
Console.WriteLine("Downloading plugin requirements...");
var requirementsURLs = await ServerCom.ReadTextFromURL(pluginRequirements);
foreach (var requirement in requirementsURLs)
{
if (requirement.Length < 2)
continue;
var reqdata = requirement.Split(',');
var url = reqdata[0];
var filename = reqdata[1];
Console.WriteLine($"Downloading {filename}... ");
spinner.Start();
await ServerCom.DownloadFileAsync(url, $"./{filename}.dll", null);
spinner.Stop();
await Task.Delay(1000);
Console.WriteLine("Downloaded " + filename + " successfully");
}
Console.WriteLine("Finished installing " + pluginName + " successfully");
break; break;
} }
} }

View File

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

View File

@@ -7,7 +7,7 @@
<StartupObject/> <StartupObject/>
<SignAssembly>False</SignAssembly> <SignAssembly>False</SignAssembly>
<IsPublishable>True</IsPublishable> <IsPublishable>True</IsPublishable>
<AssemblyVersion>1.0.2.2</AssemblyVersion> <AssemblyVersion>1.0.3.1</AssemblyVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>none</DebugType> <DebugType>none</DebugType>
@@ -34,7 +34,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Discord.Net" Version="3.11.0"/> <PackageReference Include="Discord.Net" Version="3.11.0"/>
<PackageReference Include="Sodium.Core" Version="1.3.3"/> <PackageReference Include="pythonnet" Version="3.0.1" />
<PackageReference Include="Spectre.Console" Version="0.47.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\PluginManager\PluginManager.csproj"/> <ProjectReference Include="..\PluginManager\PluginManager.csproj"/>

View File

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

View File

@@ -1,8 +1,5 @@
using System;
using System.IO;
using System.Threading.Tasks;
using PluginManager; using PluginManager;
using PluginManager.Online; using Spectre.Console;
namespace DiscordBot; namespace DiscordBot;
@@ -10,55 +7,22 @@ public static class Installer
{ {
public static void GenerateStartupConfig() public static void GenerateStartupConfig()
{ {
Console.WriteLine("Welcome to the SethBot installer !"); AnsiConsole.MarkupLine("Welcome to the [bold]SethBot[/] installer !");
Console.WriteLine("First, we need to configure the bot. Don't worry, it will be quick !"); AnsiConsole.MarkupLine("First, we need to configure the bot. Don't worry, it will be quick !");
Console.WriteLine("The following information will be stored in the config.json file in the ./Data/Resources folder. You can change it later from there.");
Console.WriteLine("The bot token is required to run the bot. You can get it from the Discord Developer Portal. (https://discord.com/developers/applications)");
if (!Config.AppSettings.ContainsKey("token")) var token = AnsiConsole.Ask<string>("Please enter the bot [yellow]token[/]:");
{ var prefix = AnsiConsole.Ask<string>("Please enter the bot [yellow]prefix[/]:");
Console.WriteLine("Please enter the bot token :"); var serverId = AnsiConsole.Ask<string>("Please enter the [yellow]Server ID[/]:");
var token = Console.ReadLine();
if (string.IsNullOrWhiteSpace(serverId)) serverId = "NULL";
Config.AppSettings.Add("token", token); Config.AppSettings.Add("token", token);
}
if (!Config.AppSettings.ContainsKey("prefix"))
{
Console.WriteLine("Please enter the bot prefix :");
var prefix = Console.ReadLine();
Config.AppSettings.Add("prefix", prefix); Config.AppSettings.Add("prefix", prefix);
}
if (!Config.AppSettings.ContainsKey("ServerID"))
{
Console.WriteLine("Please enter the Server ID :");
var serverId = Console.ReadLine();
Config.AppSettings.Add("ServerID", serverId); Config.AppSettings.Add("ServerID", serverId);
}
Config.Logger.Log("Config Saved", "Installer", isInternal: true);
Config.AppSettings.SaveToFile(); Config.AppSettings.SaveToFile();
Console.WriteLine("Config saved !"); AnsiConsole.MarkupLine("[bold]Config saved ![/]");
}
public static async Task SetupPluginDatabase() Config.Logger.Log("Config Saved", source: typeof(Installer));
{
Console.WriteLine("The plugin database is required to run the bot but there is nothing configured yet.");
Console.WriteLine("Downloading the default database...");
await DownloadPluginDatabase();
}
private static async Task DownloadPluginDatabase(
string url = "https://raw.githubusercontent.com/andreitdr/SethDiscordBot/gh-pages/defaultURLs.json")
{
var path = "./Data/Resources/URLs.json";
Directory.CreateDirectory("./Data/Resources");
var spinner = new Utilities.Utilities.Spinner();
spinner.Start();
await ServerCom.DownloadFileAsync(url, path, null);
spinner.Stop();
} }
} }

View File

@@ -1,40 +1,32 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordBot.Utilities;
using PluginManager.Bot; using PluginManager.Bot;
using PluginManager.Online;
using PluginManager.Online.Helpers;
using PluginManager.Others; using PluginManager.Others;
using PluginManager.Others.Actions; using PluginManager.Others.Actions;
using Spectre.Console;
using static PluginManager.Config; using static PluginManager.Config;
namespace DiscordBot; namespace DiscordBot;
public class Program public class Program
{ {
public static SettingsDictionary<string, string> URLs;
public static InternalActionManager internalActionManager; public static InternalActionManager internalActionManager;
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
/// </summary> /// </summary>
[STAThread]
public static void Startup(string[] args) public static void Startup(string[] args)
{ {
PreLoadComponents(args).Wait(); PreLoadComponents(args).Wait();
if (!AppSettings.ContainsKey("ServerID") || !AppSettings.ContainsKey("token") || if (!AppSettings.ContainsKey("ServerID") || !AppSettings.ContainsKey("token") || !AppSettings.ContainsKey("prefix"))
AppSettings["token"] == null ||
AppSettings["token"]?.Length != 70 && AppSettings["token"]?.Length != 59 ||
!AppSettings.ContainsKey("prefix") || AppSettings["prefix"] == null ||
AppSettings["prefix"]?.Length != 1 ||
args.Length == 1 && args[0] == "/reset")
Installer.GenerateStartupConfig(); Installer.GenerateStartupConfig();
HandleInput(args.ToList()).Wait(); HandleInput().Wait();
} }
/// <summary> /// <summary>
@@ -42,10 +34,10 @@ public class Program
/// </summary> /// </summary>
private static void NoGUI() private static void NoGUI()
{ {
#if DEBUG internalActionManager.Initialize().Wait();
Console.WriteLine("Debug mode enabled"); internalActionManager.Execute("plugin", "load").Wait();
internalActionManager.Execute("plugin", "load").Wait(); // Load plugins at startup internalActionManager.Refresh().Wait();
#endif
while (true) while (true)
{ {
@@ -64,56 +56,31 @@ public class Program
/// Start the bot without user interface /// Start the bot without user interface
/// </summary> /// </summary>
/// <returns>Returns the boot loader for the Discord Bot</returns> /// <returns>Returns the boot loader for the Discord Bot</returns>
private static async Task<Boot> StartNoGui() private static async Task StartNoGui()
{ {
Console.Clear(); Console.Clear();
Console.ForegroundColor = ConsoleColor.DarkYellow; Console.ForegroundColor = ConsoleColor.DarkYellow;
var startupMessageList = Console.WriteLine($"Running on version: {Assembly.GetExecutingAssembly().GetName().Version}");
await ServerCom.ReadTextFromURL(URLs["StartupMessage"]); Console.WriteLine("Git SethBot: https://github.com/andreitdr/SethDiscordBot");
Console.WriteLine("Git Plugins: https://github.com/andreitdr/SethPlugins");
foreach (var message in startupMessageList) ConsoleUtilities.WriteColorText("&rRemember to close the bot using the ShutDown command (&yexit&r) or some settings won't be saved");
Console.WriteLine(message);
Console.WriteLine( ConsoleUtilities.WriteColorText($"Running on &m{Functions.GetOperatingSystem()}");
$"Running on version: {Assembly.GetExecutingAssembly().GetName().Version}"
);
Console.WriteLine($"Git URL: {AppSettings["GitURL"]}");
Utilities.Utilities.WriteColorText(
"&rRemember to close the bot using the ShutDown command (&ysd&r) or some settings won't be saved\n"
);
Console.ForegroundColor = ConsoleColor.White;
if (AppSettings.ContainsKey("LaunchMessage"))
Utilities.Utilities.WriteColorText(AppSettings["LaunchMessage"]);
Utilities.Utilities.WriteColorText(
"Please note that the bot saves a backup save file every time you are using the shudown command (&ysd&c)"
);
Console.WriteLine("Running on " + Functions.GetOperatingSystem());
Console.WriteLine("============================ LOG ============================"); Console.WriteLine("============================ LOG ============================");
Console.ForegroundColor = ConsoleColor.White;
try try
{ {
var token = ""; var token = AppSettings["token"];
#if DEBUG
if (File.Exists("./Data/Resources/token.txt")) token = File.ReadAllText("./Data/Resources/token.txt");
else token = AppSettings["token"];
#else
token = AppSettings["token"];
#endif
var prefix = AppSettings["prefix"]; var prefix = AppSettings["prefix"];
var discordbooter = new Boot(token, prefix); var discordbooter = new Boot(token, prefix);
await discordbooter.Awake(); await discordbooter.Awake();
return discordbooter;
} }
catch ( Exception ex ) catch ( Exception ex )
{ {
Logger.Log(ex.ToString(), "Bot", LogLevel.ERROR); Logger.Log(ex.ToString(), source: typeof(Program), type: LogType.CRITICAL);
return null;
} }
} }
@@ -121,27 +88,12 @@ public class Program
/// Handle user input arguments from the startup of the application /// Handle user input arguments from the startup of the application
/// </summary> /// </summary>
/// <param name="args">The arguments</param> /// <param name="args">The arguments</param>
private static async Task HandleInput(List<string> args) private static async Task HandleInput()
{ {
Console.WriteLine("Loading Core ..."); await StartNoGui();
//Handle arguments here:
if (args.Contains("--gui"))
{
// GUI not implemented yet
Console.WriteLine("GUI not implemented yet");
return;
}
// Starting bot after all arguments are handled
var b = await StartNoGui();
try try
{ {
internalActionManager = new InternalActionManager("./Data/Actions", "*.dll"); internalActionManager = new InternalActionManager("./Data/Plugins", "*.dll");
await internalActionManager.Initialize();
NoGUI(); NoGUI();
} }
catch ( IOException ex ) catch ( IOException ex )
@@ -150,12 +102,12 @@ public class Program
{ {
if (AppSettings.ContainsKey("LaunchMessage")) if (AppSettings.ContainsKey("LaunchMessage"))
AppSettings.Add("LaunchMessage", AppSettings.Add("LaunchMessage",
"An error occured while closing the bot last time. Please consider closing the bot using the &rsd&c method !\nThere is a risk of losing all data or corruption of the save file, which in some cases requires to reinstall the bot !" "An error occured while closing the bot last time. Please consider closing the bot using the &rexit&c method !\n" +
); "There is a risk of losing all data or corruption of the save file, which in some cases requires to reinstall the bot !");
Logger
.Log("An error occured while closing the bot last time. Please consider closing the bot using the &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 !", Logger.Log("An error occured while closing the bot last time. Please consider closing the bot using the &rexit&c method !\n" +
"Bot", LogLevel.ERROR "There is a risk of losing all data or corruption of the save file, which in some cases requires to reinstall the bot !",
); source: typeof(Program), type: LogType.ERROR);
} }
} }
} }
@@ -164,84 +116,27 @@ public class Program
{ {
await Initialize(); await Initialize();
Logger.LogEvent += (message, type, isInternal) => Logger.OnLog += (sender, logMessage) =>
{ {
if (type == LogLevel.INFO) string messageColor = logMessage.Type switch
Console.ForegroundColor = ConsoleColor.Green; {
else if (type == LogLevel.WARNING) LogType.INFO => "[green]",
Console.ForegroundColor = ConsoleColor.DarkYellow; LogType.WARNING => "[yellow]",
else if (type == LogLevel.ERROR) LogType.ERROR => "[red]",
Console.ForegroundColor = ConsoleColor.Red; LogType.CRITICAL => "[red]",
else if (type == LogLevel.CRITICAL) _ => "[white]"
Console.ForegroundColor = ConsoleColor.DarkRed;
Console.WriteLine($"[{type.ToString()}] {message}");
Console.ResetColor();
}; };
if (!Directory.Exists("./Data/Resources") || !File.Exists("./Data/Resources/URLs.json")) if (logMessage.Message.Contains('['))
await Installer.SetupPluginDatabase(); {
// If the message contains a tag, just print it as it is. No need to format it
Console.WriteLine(logMessage.Message);
URLs = new SettingsDictionary<string, string>("./Data/Resources/URLs.json"); return;
}
Console.WriteLine("Loading resources ...");
if (AppSettings.ContainsKey("DeleteLogsAtStartup"))
if (AppSettings["DeleteLogsAtStartup"] == "true")
foreach (var file in Directory.GetFiles("./Output/Logs/"))
File.Delete(file);
var OnlineDefaultKeys = await ServerCom.ReadTextFromURL(URLs["SetupKeys"]);
AnsiConsole.MarkupLine($"{messageColor}{logMessage.ThrowTime} {logMessage.Message} [/]");
};
AppSettings["Version"] = Assembly.GetExecutingAssembly().GetName().Version.ToString(); AppSettings["Version"] = Assembly.GetExecutingAssembly().GetName().Version.ToString();
foreach (var key in OnlineDefaultKeys)
{
if (key.Length <= 3 || !key.Contains(' ')) continue;
var s = key.Split(' ');
try
{
AppSettings[s[0]] = s[1];
}
catch ( Exception ex )
{
Logger.Log(ex.ToString(), "Bot", LogLevel.ERROR);
}
}
var onlineSettingsList = await ServerCom.ReadTextFromURL(URLs["Versions"]);
foreach (var key in onlineSettingsList)
{
if (key.Length <= 3 || !key.Contains(' ')) continue;
var s = key.Split(' ');
switch ( s[0] )
{
case "CurrentVersion":
var currentVersion = AppSettings["Version"];
var newVersion = s[1];
if (new VersionString(newVersion) != new VersionString(newVersion))
{
Console.WriteLine("A new updated was found. Check the changelog for more information.");
var changeLog = await ServerCom.ReadTextFromURL(URLs["Changelog"]);
foreach (var item in changeLog)
Utilities.Utilities.WriteColorText(item);
Console.WriteLine("Current version: " + currentVersion);
Console.WriteLine("Latest version: " + newVersion);
Console.WriteLine("Download from here: https://github.com/andreitdr/SethDiscordBot/releases");
Console.WriteLine("Press any key to continue ...");
Console.ReadKey();
}
break;
}
}
Console.Clear();
} }
} }

View File

@@ -1,11 +1,86 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Spectre.Console;
namespace DiscordBot.Utilities; namespace DiscordBot.Utilities;
public static class Utilities public class TableData
{ {
public List<string> Columns;
public List<string[]> Rows;
public bool IsEmpty => Rows.Count == 0;
public bool HasRoundBorders { get; set; } = true;
public TableData(List<string> columns)
{
Columns = columns;
Rows = new List<string[]>();
}
public TableData(string[] columns)
{
Columns = columns.ToList();
Rows = new List<string[]>();
}
public void AddRow(string[] row)
{
Rows.Add(row);
}
}
public static class ConsoleUtilities
{
public static async Task<T> ExecuteWithProgressBar<T>(Task<T> function, string message)
{
T result = default;
await AnsiConsole.Progress()
.Columns(
new ProgressColumn[]
{
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn(),
}
)
.StartAsync(
async ctx =>
{
var task = ctx.AddTask(message);
task.IsIndeterminate = true;
result = await function;
task.Increment(100);
}
);
return result;
}
public static async Task ExecuteWithProgressBar(Task function, string message)
{
await AnsiConsole.Progress()
.Columns(new ProgressColumn[]
{
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn(),
})
.StartAsync(async ctx =>
{
var task = ctx.AddTask(message);
task.IsIndeterminate = true;
await function;
task.Increment(100);
});
}
private static readonly Dictionary<char, ConsoleColor> Colors = new() private static readonly Dictionary<char, ConsoleColor> Colors = new()
{ {
{ 'g', ConsoleColor.Green }, { 'g', ConsoleColor.Green },
@@ -17,19 +92,43 @@ public static class Utilities
private static readonly char ColorPrefix = '&'; private static readonly char ColorPrefix = '&';
private static bool CanAproximateTo(this float f, float y) private static bool CanAproximateTo(this float f, float y)
{ {
return MathF.Abs(f - y) < 0.000001; return MathF.Abs(f - y) < 0.000001;
} }
public static void PrintAsTable(this TableData tableData)
{
var table = new Table();
table.Border(tableData.HasRoundBorders ? TableBorder.Rounded : TableBorder.Square);
table.AddColumns(tableData.Columns.ToArray());
foreach (var row in tableData.Rows)
table.AddRow(row);
AnsiConsole.Write(table);
}
/// <summary> /// <summary>
/// A way to create a table based on input data /// A way to create a table based on input data
/// </summary> /// </summary>
/// <param name="data">The List of arrays of strings that represent the rows.</param> /// <param name="data">The List of arrays of string that represent the rows.</param>
public static void FormatAndAlignTable(List<string[]> data, TableFormat format) public static void FormatAndAlignTable(List<string[]> data, TableFormat format)
{ {
if (format == TableFormat.SPECTRE_CONSOLE)
{
var table = new Table();
table.Border(TableBorder.Rounded);
table.AddColumns(data[0]);
data.RemoveAt(0);
foreach (var row in data)
table.AddRow(row);
AnsiConsole.Write(table);
return;
}
if (format == TableFormat.CENTER_EACH_COLUMN_BASED) if (format == TableFormat.CENTER_EACH_COLUMN_BASED)
{ {
var tableLine = '-'; var tableLine = '-';

View File

@@ -2,6 +2,7 @@
public enum TableFormat public enum TableFormat
{ {
SPECTRE_CONSOLE,
CENTER_EACH_COLUMN_BASED, CENTER_EACH_COLUMN_BASED,
CENTER_OVERALL_LENGTH, CENTER_OVERALL_LENGTH,
DEFAULT DEFAULT

View File

@@ -85,9 +85,6 @@ public class Boot
await commandServiceHandler.InstallCommandsAsync(); await commandServiceHandler.InstallCommandsAsync();
await Task.Delay(2000);
Config._DiscordBotClient = this; Config._DiscordBotClient = this;
while (!isReady) ; while (!isReady) ;
@@ -108,8 +105,7 @@ public class Boot
if (arg.Message.Contains("401")) if (arg.Message.Contains("401"))
{ {
Config.AppSettings.Remove("token"); Config.AppSettings.Remove("token");
Config.Logger.Log("The token is invalid. Please restart the bot and enter a valid token.", this, Config.Logger.Log("The token is invalid. Please restart the bot and enter a valid token.", source:typeof(Boot), type: LogType.CRITICAL);
LogLevel.ERROR);
await Config.AppSettings.SaveToFile(); await Config.AppSettings.SaveToFile();
await Task.Delay(4000); await Task.Delay(4000);
Environment.Exit(0); Environment.Exit(0);
@@ -118,7 +114,7 @@ public class Boot
private async Task Client_LoggedOut() private async Task Client_LoggedOut()
{ {
Config.Logger.Log("Successfully Logged Out", this); Config.Logger.Log("Successfully Logged Out", source: typeof(Boot));
await Log(new LogMessage(LogSeverity.Info, "Boot", "Successfully logged out from discord !")); await Log(new LogMessage(LogSeverity.Info, "Boot", "Successfully logged out from discord !"));
} }
@@ -130,7 +126,7 @@ public class Boot
private Task LoggedIn() private Task LoggedIn()
{ {
Config.Logger.Log("Successfully Logged In", this); Config.Logger.Log("Successfully Logged In", source: typeof(Boot));
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -140,13 +136,12 @@ public class Boot
{ {
case LogSeverity.Error: case LogSeverity.Error:
case LogSeverity.Critical: case LogSeverity.Critical:
Config.Logger.Log(message.Message, this, LogLevel.ERROR); Config.Logger.Log(message.Message, source: typeof(Boot), type: LogType.ERROR);
break; break;
case LogSeverity.Info: case LogSeverity.Info:
case LogSeverity.Debug: case LogSeverity.Debug:
Config.Logger.Log(message.Message, this); Config.Logger.Log(message.Message, source: typeof(Boot), type: LogType.INFO);
break; break;

View File

@@ -57,7 +57,7 @@ internal class CommandHandler
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log(ex.Message, "CommandHandler", LogLevel.ERROR); Config.Logger.Log(ex.Message, type: LogType.ERROR, source: typeof(CommandHandler));
} }
return Task.CompletedTask; return Task.CompletedTask;
@@ -126,8 +126,7 @@ internal class CommandHandler
} }
if (plugin is null) if (plugin is null)
throw new Exception("Failed to run command ! " + message.CleanContent + " (user: " + return;
context.Message.Author.Username + " - " + context.Message.Author.Id + ")");
if (plugin.requireAdmin && !context.Message.Author.isAdmin()) if (plugin.requireAdmin && !context.Message.Author.isAdmin())
return; return;
@@ -140,13 +139,19 @@ internal class CommandHandler
DBCommandExecutingArguments cmd = new(context, cleanMessage, split[0], argsClean); DBCommandExecutingArguments cmd = new(context, cleanMessage, split[0], argsClean);
Config.Logger.Log(
message: $"User ({context.User.Username}) from Guild \"{context.Guild.Name}\" executed command \"{cmd.cleanContent}\"",
source: typeof(CommandHandler),
type: LogType.INFO
);
if (context.Channel is SocketDMChannel) if (context.Channel is SocketDMChannel)
plugin.ExecuteDM(cmd); plugin.ExecuteDM(cmd);
else plugin.ExecuteServer(cmd); else plugin.ExecuteServer(cmd);
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log(ex.Message, this, LogLevel.ERROR); Config.Logger.Log(ex.Message, type: LogType.ERROR, source: typeof(CommandHandler));
} }
} }
} }

View File

@@ -9,9 +9,9 @@ namespace PluginManager;
public class Config public class Config
{ {
private static bool IsLoaded; private static bool _isLoaded;
public static DBLogger Logger; public static Logger Logger;
public static SettingsDictionary<string, string>? AppSettings; public static SettingsDictionary<string, string> AppSettings;
internal static Boot? _DiscordBotClient; internal static Boot? _DiscordBotClient;
@@ -19,7 +19,7 @@ public class Config
public static async Task Initialize() public static async Task Initialize()
{ {
if (IsLoaded) return; if (_isLoaded) return;
Directory.CreateDirectory("./Data/Resources"); Directory.CreateDirectory("./Data/Resources");
Directory.CreateDirectory("./Data/Plugins"); Directory.CreateDirectory("./Data/Plugins");
@@ -30,15 +30,14 @@ public class Config
AppSettings = new SettingsDictionary<string, string>("./Data/Resources/config.json"); AppSettings = new SettingsDictionary<string, string>("./Data/Resources/config.json");
AppSettings["LogFolder"] = "./Data/Logs/Logs"; AppSettings["LogFolder"] = "./Data/Logs/Logs";
AppSettings["ErrorFolder"] = "./Data/Logs/Errors";
Logger = new DBLogger(); Logger = new Logger(false, true);
ArchiveManager.Initialize(); ArchiveManager.Initialize();
IsLoaded = true; _isLoaded = true;
Logger.Log("Config initialized", LogLevel.INFO); Logger.Log(message: "Config initialized", source: typeof(Config));
} }
} }

View File

@@ -1,17 +0,0 @@
using System.Collections.Generic;
namespace PluginManager.Interfaces.Exceptions;
public interface IException
{
public List<string> Messages { get; set; }
public bool isFatal { get; }
public string GenerateFullMessage();
public void HandleException();
public IException AppendError(string message);
public IException AppendError(List<string> messages);
public IException IsFatal(bool isFatal = true);
}

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ public class ActionsLoader
private readonly string actionExtension = "dll"; private readonly string actionExtension = "dll";
private readonly string actionFolder = @"./Data/Actions/"; private readonly string actionFolder = @"./Data/Plugins/";
public ActionsLoader(string path, string extension) public ActionsLoader(string path, string extension)
{ {

View File

@@ -53,10 +53,9 @@ internal class Loader
{ {
Assembly.LoadFrom(file); Assembly.LoadFrom(file);
} }
catch (Exception ex) catch
{ {
Config.Logger.Log("PluginName: " + new FileInfo(file).Name.Split('.')[0] + " not loaded", this, Config.Logger.Log("PluginName: " + new FileInfo(file).Name.Split('.')[0] + " not loaded", source: typeof(Loader), type: LogType.ERROR);
LogLevel.ERROR);
continue; continue;
} }
@@ -130,7 +129,7 @@ internal class Loader
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log(ex.Message, this, LogLevel.ERROR); Config.Logger.Log(ex.Message, source: typeof(Loader), type: LogType.ERROR);
return null; return null;
} }

View File

@@ -88,11 +88,10 @@ public class PluginLoader
Events = new List<DBEvent>(); Events = new List<DBEvent>();
SlashCommands = new List<DBSlashCommand>(); SlashCommands = new List<DBSlashCommand>();
Config.Logger.Log("Starting plugin loader ... Client: " + _client.CurrentUser.Username, this, Config.Logger.Log("Starting plugin loader ... Client: " + _client.CurrentUser.Username, source: typeof(PluginLoader), type: LogType.INFO);
LogLevel.INFO);
var loader = new Loader("./Data/Plugins", "dll"); var loader = new Loader("./Data/Plugins", "dll");
loader.FileLoaded += args => Config.Logger.Log($"{args.PluginName} file Loaded", this, LogLevel.INFO); loader.FileLoaded += args => Config.Logger.Log($"{args.PluginName} file Loaded", source: typeof(PluginLoader), type: LogType.INFO);
loader.PluginLoaded += Loader_PluginLoaded; loader.PluginLoaded += Loader_PluginLoaded;
var res = loader.Load(); var res = loader.Load();
Events = res.Item1; Events = res.Item1;
@@ -117,7 +116,7 @@ public class PluginLoader
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log(ex.Message, this, LogLevel.ERROR); Config.Logger.Log(ex.Message, source: typeof(PluginLoader), type: LogType.ERROR);
} }
break; break;
@@ -151,13 +150,13 @@ public class PluginLoader
var instance = (DBEvent)Activator.CreateInstance(type); var instance = (DBEvent)Activator.CreateInstance(type);
instance.Start(client); instance.Start(client);
Events.Add(instance); Events.Add(instance);
Config.Logger.Log($"[EVENT] Loaded external {type.FullName}!", LogLevel.INFO); Config.Logger.Log($"[EVENT] Loaded external {type.FullName}!", source: typeof(PluginLoader));
} }
else if (type.IsClass && typeof(DBCommand).IsAssignableFrom(type)) else if (type.IsClass && typeof(DBCommand).IsAssignableFrom(type))
{ {
var instance = (DBCommand)Activator.CreateInstance(type); var instance = (DBCommand)Activator.CreateInstance(type);
Commands.Add(instance); Commands.Add(instance);
Config.Logger.Log($"[CMD] Instance: {type.FullName} loaded !", LogLevel.INFO); Config.Logger.Log($"[CMD] Instance: {type.FullName} loaded !", source: typeof(PluginLoader));
} }
else if (type.IsClass && typeof(DBSlashCommand).IsAssignableFrom(type)) else if (type.IsClass && typeof(DBSlashCommand).IsAssignableFrom(type))
{ {
@@ -170,13 +169,13 @@ public class PluginLoader
await client.CreateGlobalApplicationCommandAsync(builder.Build()); await client.CreateGlobalApplicationCommandAsync(builder.Build());
SlashCommands.Add(instance); SlashCommands.Add(instance);
Config.Logger.Log($"[SLASH] Instance: {type.FullName} loaded !", LogLevel.INFO); Config.Logger.Log($"[SLASH] Instance: {type.FullName} loaded !", source: typeof(PluginLoader));
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
//Console.WriteLine(ex.Message); //Console.WriteLine(ex.Message);
Config.Logger.Error(ex); Config.Logger.Log(ex.Message, source: typeof(PluginLoader), type: LogType.ERROR);
} }
} }

View File

@@ -35,21 +35,25 @@ internal static class OnlineFunctions
if (progress == null || !contentLength.HasValue) if (progress == null || !contentLength.HasValue)
{ {
await download.CopyToAsync(destination, cancellation); await download.CopyToAsync(destination, cancellation);
if(!contentLength.HasValue)
progress?.Report(100f);
return; return;
} }
// Convert absolute progress (bytes downloaded) into relative progress (0% - 100%) // Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
var relativeProgress = new Progress<long>(totalBytes => // total ... 100%
// downloaded ... x%
// x = downloaded * 100 / total => x = downloaded / total * 100
var relativeProgress = new Progress<long>(totalBytesDownloaded =>
{ {
progress?.Report((float)totalBytes / contentLength.Value * progress?.Report(totalBytesDownloaded / (float)contentLength.Value * 100);
100); downloadedBytes?.Report(totalBytesDownloaded);
downloadedBytes?.Report(totalBytes);
} }
); );
// Use extension method to report progress while downloading // Use extension method to report progress while downloading
await download.CopyToOtherStreamAsync(destination, bufferSize, relativeProgress, cancellation); await download.CopyToOtherStreamAsync(destination, bufferSize, relativeProgress, cancellation);
progress.Report(100); progress.Report(100f);
} }
} }
} }

View File

@@ -20,6 +20,15 @@ public class PluginsManager
VersionsLink = vlink; VersionsLink = vlink;
} }
/// <summary>
/// The default Plugin Manager constructor. It uses the default links.
/// </summary>
public PluginsManager()
{
PluginsLink = "https://raw.githubusercontent.com/andreitdr/SethPlugins/releases/PluginsList";
VersionsLink = "https://raw.githubusercontent.com/andreitdr/SethPlugins/releases/Versions";
}
/// <summary> /// <summary>
/// The URL of the server /// The URL of the server
/// </summary> /// </summary>
@@ -33,7 +42,7 @@ public class PluginsManager
/// <returns></returns> /// <returns></returns>
public async Task<List<string[]>> GetAvailablePlugins() public async Task<List<string[]>> GetAvailablePlugins()
{ {
Config.Logger.Log("Got data from " + VersionsLink, this, LogLevel.INFO); // Config.Logger.Log("Got data from " + VersionsLink, this, LogLevel.INFO);
try try
{ {
var list = await ServerCom.ReadTextFromURL(PluginsLink); var list = await ServerCom.ReadTextFromURL(PluginsLink);
@@ -76,15 +85,11 @@ public class PluginsManager
} }
} }
} }
data.Add(new[] { "-", "-", "-", "-" });
return data; return data;
} }
catch (Exception exception) catch (Exception exception)
{ {
Config.Logger.Log("Failed to execute command: listplugs\nReason: " + exception.Message, this, Config.Logger.Log(message: "Failed to execute command: listplugs\nReason: " + exception.Message, source: typeof(PluginsManager), type: LogType.ERROR );
LogLevel.ERROR);
} }
return null; return null;
@@ -101,7 +106,7 @@ public class PluginsManager
var split = item.Split(','); var split = item.Split(',');
if (split[0] == pakName) if (split[0] == pakName)
{ {
Config.Logger.Log("Searched for " + pakName + " and found " + split[1] + " as version.", LogLevel.INFO); // Config.Logger.Log("Searched for " + pakName + " and found " + split[1] + " as version.", LogLevel.INFO);
return new VersionString(split[1]); return new VersionString(split[1]);
} }
} }
@@ -136,8 +141,7 @@ public class PluginsManager
} }
catch (Exception exception) catch (Exception exception)
{ {
Config.Logger.Log("Failed to execute command: listplugs\nReason: " + exception.Message, this, Config.Logger.Log("Failed to execute command: plugin list\nReason: " + exception.Message, source: typeof(PluginsManager), type: LogType.ERROR);
LogLevel.ERROR);
} }
return null; return null;

View File

@@ -48,4 +48,5 @@ public static class ServerCom
{ {
await DownloadFileAsync(URl, location, progress, null); await DownloadFileAsync(URl, location, progress, null);
} }
} }

View File

@@ -18,29 +18,37 @@ public class InternalActionManager
public async Task Initialize() public async Task Initialize()
{ {
loader.ActionLoadedEvent += OnActionLoaded; //loader.ActionLoadedEvent += OnActionLoaded;
var m_actions = await loader.Load(); var m_actions = await loader.Load();
if (m_actions == null) return; if (m_actions == null) return;
foreach (var action in m_actions) foreach (var action in m_actions)
Actions.Add(action.ActionName, action); {
Actions.TryAdd(action.ActionName, action);
}
} }
private void OnActionLoaded(string name, string typeName, bool success, Exception? e) public async Task Refresh()
{ {
if (!success) Actions.Clear();
{ await Initialize();
Config.Logger.Error(e);
return;
} }
Config.Logger.Log($"Action {name} loaded successfully", LogLevel.INFO, true); // private void OnActionLoaded(string name, string typeName, bool success, Exception? e)
} // {
// if (!success)
// {
// Config.Logger.Error(e);
// return;
// }
//
// Config.Logger.Log($"Action {name} loaded successfully", LogLevel.INFO, true);
// }
public async Task<string> Execute(string actionName, params string[]? args) public async Task<string> Execute(string actionName, params string[]? args)
{ {
if (!Actions.ContainsKey(actionName)) if (!Actions.ContainsKey(actionName))
{ {
Config.Logger.Log($"Action {actionName} not found", "InternalActionManager", LogLevel.WARNING, true); Config.Logger.Log($"Action {actionName} not found", type: LogType.ERROR, source: typeof(InternalActionManager));
return "Action not found"; return "Action not found";
} }
@@ -51,7 +59,7 @@ public class InternalActionManager
} }
catch (Exception e) catch (Exception e)
{ {
Config.Logger.Log(e.Message, "InternalActionManager", LogLevel.ERROR); Config.Logger.Log(e.Message , type: LogType.ERROR, source: typeof(InternalActionManager));
return e.Message; return e.Message;
} }
} }

View File

@@ -2,7 +2,6 @@
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace PluginManager.Others; namespace PluginManager.Others;
@@ -94,7 +93,7 @@ public static class ArchiveManager
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log(ex.Message, "Archive Manager", LogLevel.ERROR); // Write the error to a file Config.Logger.Log(message: ex.Message, source: typeof(ArchiveManager), type: LogType.ERROR); // Write the error to a file
await Task.Delay(100); await Task.Delay(100);
return await ReadFromPakAsync(FileName, archFile); return await ReadFromPakAsync(FileName, archFile);
} }
@@ -132,8 +131,7 @@ public static class ArchiveManager
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log($"Failed to extract {entry.Name}. Exception: {ex.Message}", Config.Logger.Log(ex.Message, source: typeof(ArchiveManager), type: LogType.ERROR);
"Archive Manager", LogLevel.ERROR);
} }
currentZIPFile++; currentZIPFile++;
@@ -165,8 +163,7 @@ public static class ArchiveManager
} }
catch (Exception ex) catch (Exception ex)
{ {
Config.Logger.Log($"Failed to extract {entry.Name}. Exception: {ex.Message}", Config.Logger.Log(ex.Message, source: typeof(ArchiveManager), type: LogType.ERROR);
"Archive Manager", LogLevel.ERROR);
} }
await Task.Delay(10); await Task.Delay(10);

View File

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

View File

@@ -14,7 +14,7 @@ public enum OperatingSystem
/// <summary> /// <summary>
/// The output log type /// The output log type
/// </summary> /// </summary>
public enum LogLevel public enum LogType
{ {
INFO, INFO,
WARNING, WARNING,
@@ -30,8 +30,8 @@ public enum UnzipProgressType
public enum SaveType public enum SaveType
{ {
NORMAL, TXT,
BACKUP JSON
} }
public enum InternalActionRunType public enum InternalActionRunType

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using PluginManager.Others.Exceptions;
namespace PluginManager.Others; namespace PluginManager.Others;
@@ -16,10 +15,8 @@ public class SettingsDictionary<TKey, TValue> : IDictionary<TKey, TValue>
_file = file; _file = file;
if (!LoadFromFile()) if (!LoadFromFile())
{ {
ConfigFailedToLoad.CreateError("Failed to load config") _dictionary = new Dictionary<TKey, TValue>();
.AppendError("The file is empty or does not exist") SaveToFile();
.IsFatal()
.HandleException();
} }
} }
@@ -50,10 +47,6 @@ public class SettingsDictionary<TKey, TValue> : IDictionary<TKey, TValue>
} }
catch catch
{ {
ConfigFailedToLoad
.CreateError("Failed to load config")
.IsFatal()
.HandleException();
return false; return false;
} }
@@ -126,11 +119,6 @@ public class SettingsDictionary<TKey, TValue> : IDictionary<TKey, TValue>
if(this._dictionary[key] is string s && !string.IsNullOrEmpty(s) && !string.IsNullOrWhiteSpace(s)) if(this._dictionary[key] is string s && !string.IsNullOrEmpty(s) && !string.IsNullOrWhiteSpace(s))
return this._dictionary[key]; return this._dictionary[key];
ConfigNoKeyWasPresent.CreateError($"Key {(key is string ? key : typeof(TKey).Name)} was not present in {_file ?? "config"}")
.AppendError("Deleting the file may fix this issue")
.IsFatal()
.HandleException();
return default!; return default!;
} }
set => this._dictionary![key] = value; set => this._dictionary![key] = value;

View File

@@ -6,13 +6,9 @@ This project is based on:
- [.NET 6 (C#)](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) - [.NET 6 (C#)](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
- [Discord.Net](https://github.com/discord-net/Discord.Net) - [Discord.Net](https://github.com/discord-net/Discord.Net)
- Some plugins can be found [here](https://github.com/andreitdr/SethPlugins).
## Plugins ## Plugins
#### Requirements: - Some plugins can be found in [this repo](https://github.com/andreitdr/SethPlugins).
- [Visual Studio](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&channel=Release&version=VS2022&source=VSLandingPage&cid=2030&passive=false)
- .NET 6 (downloaded with Visual Studio)
Plugin Types: Plugin Types:
1. Commands 1. Commands
@@ -21,6 +17,10 @@ Plugin Types:
### How to create a plugin ### How to create a plugin
#### Requirements:
- [Visual Studio](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&channel=Release&version=VS2022&source=VSLandingPage&cid=2030&passive=false)
- .NET 6 (downloaded with Visual Studio)
First of all, create a new project (class library) in Visual Studio. First of all, create a new project (class library) in Visual Studio.
Then import the PluginManager as reference to your project. Then import the PluginManager as reference to your project.
@@ -94,7 +94,7 @@ public class LevelCommand : DBCommand
From here on, start coding. When your plugin is done, build it as any DLL project then add it to the following path From here on, start coding. When your plugin is done, build it as any DLL project then add it to the following path
`{bot_executable}/Data/Plugins/<optional subfolder>/[plugin name].dll` `{bot_executable}/Data/Plugins/<optional subfolder>/[plugin name].dll`
Then, reload bot and execute command `lp` in bot's console. The plugin should be loaded into memory or an error is thrown if not. If an error is thrown, then Then, reload bot and execute command `plugin load` in the console. The plugin should be loaded into memory or an error is thrown if not. If an error is thrown, then
there is something wrong in your command's code. there is something wrong in your command's code.
## 2. Events ## 2. Events
@@ -192,4 +192,4 @@ namespace SlashCommands
You can create multiple commands, events and slash commands into one single plugin (class library). The PluginManager will detect the classes and load them individualy. If there are more commands (normal commands, events or slash commands) into a single project (class library) they can use the same resources (a class for example) that is contained within the plugin. You can create multiple commands, events and slash commands into one single plugin (class library). The PluginManager will detect the classes and load them individualy. If there are more commands (normal commands, events or slash commands) into a single project (class library) they can use the same resources (a class for example) that is contained within the plugin.
> Updated: 5.08.2023 > Updated: 25.09.2023

View File

@@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MusicPlayer", "..\SethPlugi
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LevelingSystem", "..\SethPlugins\LevelingSystem\LevelingSystem.csproj", "{BFE3491C-AC01-4252-B242-6451270FC548}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LevelingSystem", "..\SethPlugins\LevelingSystem\LevelingSystem.csproj", "{BFE3491C-AC01-4252-B242-6451270FC548}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PythonCompatibilityLayer", "..\SethPlugins\PythonCompatibilityLayer\PythonCompatibilityLayer.csproj", "{81ED4953-13E5-4950-96A8-8CEF5FD59559}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -35,6 +37,10 @@ Global
{BFE3491C-AC01-4252-B242-6451270FC548}.Debug|Any CPU.Build.0 = Debug|Any CPU {BFE3491C-AC01-4252-B242-6451270FC548}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFE3491C-AC01-4252-B242-6451270FC548}.Release|Any CPU.ActiveCfg = Release|Any CPU {BFE3491C-AC01-4252-B242-6451270FC548}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BFE3491C-AC01-4252-B242-6451270FC548}.Release|Any CPU.Build.0 = Release|Any CPU {BFE3491C-AC01-4252-B242-6451270FC548}.Release|Any CPU.Build.0 = Release|Any CPU
{81ED4953-13E5-4950-96A8-8CEF5FD59559}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81ED4953-13E5-4950-96A8-8CEF5FD59559}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81ED4953-13E5-4950-96A8-8CEF5FD59559}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81ED4953-13E5-4950-96A8-8CEF5FD59559}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -42,6 +48,7 @@ Global
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{1690CBBC-BDC0-4DD8-B701-F8817189D9D5} = {78B6D390-F61A-453F-B38D-E4C054321615} {1690CBBC-BDC0-4DD8-B701-F8817189D9D5} = {78B6D390-F61A-453F-B38D-E4C054321615}
{BFE3491C-AC01-4252-B242-6451270FC548} = {78B6D390-F61A-453F-B38D-E4C054321615} {BFE3491C-AC01-4252-B242-6451270FC548} = {78B6D390-F61A-453F-B38D-E4C054321615}
{81ED4953-13E5-4950-96A8-8CEF5FD59559} = {78B6D390-F61A-453F-B38D-E4C054321615}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3FB3C5DE-ED21-4D2E-ABDD-3A00EE4A2FFF} SolutionGuid = {3FB3C5DE-ED21-4D2E-ABDD-3A00EE4A2FFF}