Merged projects with plugins and modules
This commit is contained in:
@@ -23,7 +23,7 @@ namespace DiscordBot.Bot.Actions
|
||||
new InternalActionOption("fileName", "The file name")
|
||||
];
|
||||
|
||||
public InternalActionRunType RunType => InternalActionRunType.ON_CALL;
|
||||
public InternalActionRunType RunType => InternalActionRunType.OnCall;
|
||||
|
||||
public async Task Execute(string[] args)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ public class Clear: ICommandAction
|
||||
public string Usage => "clear";
|
||||
public IEnumerable<InternalActionOption> ListOfOptions => [];
|
||||
|
||||
public InternalActionRunType RunType => InternalActionRunType.ON_CALL;
|
||||
public InternalActionRunType RunType => InternalActionRunType.OnCall;
|
||||
|
||||
public Task Execute(string[] args)
|
||||
{
|
||||
|
||||
@@ -18,13 +18,13 @@ public class Exit: ICommandAction
|
||||
new InternalActionOption("help", "Displays this message"),
|
||||
new InternalActionOption("force | -f", "Exits the bot without saving the config")
|
||||
};
|
||||
public InternalActionRunType RunType => InternalActionRunType.ON_CALL;
|
||||
public InternalActionRunType RunType => InternalActionRunType.OnCall;
|
||||
|
||||
public async Task Execute(string[] args)
|
||||
{
|
||||
if (args is null || args.Length == 0)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Exiting...", this, LogType.WARNING);
|
||||
Application.CurrentApplication.Logger.Log("Exiting...", this, LogType.Warning);
|
||||
await Application.CurrentApplication.ApplicationEnvironmentVariables.SaveToFile();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public class Exit: ICommandAction
|
||||
|
||||
case "-f":
|
||||
case "force":
|
||||
Application.CurrentApplication.Logger.Log("Exiting (FORCE)...", this, LogType.WARNING);
|
||||
Application.CurrentApplication.Logger.Log("Exiting (FORCE)...", this, LogType.Warning);
|
||||
Environment.Exit(0);
|
||||
break;
|
||||
|
||||
|
||||
@@ -227,13 +227,13 @@ internal static class PluginMethods
|
||||
{
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Successfully loaded command : " + data.PluginName, LogType.INFO, "\t\t > {Message}");
|
||||
Application.CurrentApplication.Logger.Log("Successfully loaded command : " + data.PluginName, LogType.Info, "\t\t > {Message}");
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Failed to load command : " + data.PluginName + " because " + data.ErrorMessage,
|
||||
typeof(PluginMethods), LogType.ERROR
|
||||
typeof(PluginMethods), LogType.Error
|
||||
);
|
||||
}
|
||||
|
||||
@@ -243,12 +243,12 @@ internal static class PluginMethods
|
||||
{
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Successfully loaded event : " + data.PluginName, LogType.INFO, "\t\t > {Message}");
|
||||
Application.CurrentApplication.Logger.Log("Successfully loaded event : " + data.PluginName, LogType.Info, "\t\t > {Message}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Failed to load event : " + data.PluginName + " because " + data.ErrorMessage,
|
||||
typeof(PluginMethods), LogType.ERROR
|
||||
typeof(PluginMethods), LogType.Error
|
||||
);
|
||||
}
|
||||
|
||||
@@ -259,12 +259,12 @@ internal static class PluginMethods
|
||||
{
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Successfully loaded slash command : " + data.PluginName, LogType.INFO, "\t\t > {Message}");
|
||||
Application.CurrentApplication.Logger.Log("Successfully loaded slash command : " + data.PluginName, LogType.Info, "\t\t > {Message}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Failed to load slash command : " + data.PluginName + " because " + data.ErrorMessage,
|
||||
typeof(PluginMethods), LogType.ERROR
|
||||
typeof(PluginMethods), LogType.Error
|
||||
);
|
||||
}
|
||||
|
||||
@@ -275,12 +275,12 @@ internal static class PluginMethods
|
||||
{
|
||||
if (data.IsSuccess)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Successfully loaded action : " + data.PluginName, LogType.INFO, "\t\t > {Message}");
|
||||
Application.CurrentApplication.Logger.Log("Successfully loaded action : " + data.PluginName, LogType.Info, "\t\t > {Message}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Failed to load action : " + data.PluginName + " because " + data.ErrorMessage,
|
||||
typeof(PluginMethods), LogType.ERROR
|
||||
typeof(PluginMethods), LogType.Error
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class Help: ICommandAction
|
||||
new InternalActionOption("command", "The command to get help for")
|
||||
];
|
||||
|
||||
public InternalActionRunType RunType => InternalActionRunType.ON_CALL;
|
||||
public InternalActionRunType RunType => InternalActionRunType.OnCall;
|
||||
|
||||
public async Task Execute(string[] args)
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace DiscordBot.Bot.Actions
|
||||
|
||||
public IEnumerable<InternalActionOption> ListOfOptions => [];
|
||||
|
||||
public InternalActionRunType RunType => InternalActionRunType.ON_CALL;
|
||||
public InternalActionRunType RunType => InternalActionRunType.OnCall;
|
||||
|
||||
public Task Execute(string[] args)
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@ public class Plugin: ICommandAction
|
||||
])
|
||||
};
|
||||
|
||||
public InternalActionRunType RunType => InternalActionRunType.ON_CALL;
|
||||
public InternalActionRunType RunType => InternalActionRunType.OnCall;
|
||||
|
||||
public async Task Execute(string[] args)
|
||||
{
|
||||
@@ -126,13 +126,13 @@ public class Plugin: ICommandAction
|
||||
case "load":
|
||||
if (pluginsLoaded)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Plugins already loaded", this, LogType.WARNING);
|
||||
Application.CurrentApplication.Logger.Log("Plugins already loaded", this, LogType.Warning);
|
||||
break;
|
||||
}
|
||||
|
||||
if (Application.CurrentApplication.DiscordBotClient is null)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("DiscordBot is null", this, LogType.WARNING);
|
||||
Application.CurrentApplication.Logger.Log("DiscordBot is null", this, LogType.Warning);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public class SettingsConfig: ICommandAction
|
||||
new InternalActionOption("remove", "Remove a setting"),
|
||||
new InternalActionOption("add", "Add a setting")
|
||||
};
|
||||
public InternalActionRunType RunType => InternalActionRunType.ON_CALL;
|
||||
public InternalActionRunType RunType => InternalActionRunType.OnCall;
|
||||
public Task Execute(string[] args)
|
||||
{
|
||||
if (args is null)
|
||||
|
||||
@@ -40,13 +40,13 @@ internal class Help: DBCommand
|
||||
/// <param name="context">The command context</param>
|
||||
public void ExecuteServer(DbCommandExecutingArguments args)
|
||||
{
|
||||
if (args.arguments is not null)
|
||||
if (args.Arguments is not null)
|
||||
{
|
||||
var e = GenerateHelpCommand(args.arguments[0]);
|
||||
var e = GenerateHelpCommand(args.Arguments[0]);
|
||||
if (e is null)
|
||||
args.context.Channel.SendMessageAsync("Unknown Command " + args.arguments[0]);
|
||||
args.Context.Channel.SendMessageAsync("Unknown Command " + args.Arguments[0]);
|
||||
else
|
||||
args.context.Channel.SendMessageAsync(embed: e.Build());
|
||||
args.Context.Channel.SendMessageAsync(embed: e.Build());
|
||||
|
||||
|
||||
return;
|
||||
@@ -68,7 +68,7 @@ internal class Help: DBCommand
|
||||
embedBuilder.AddField("Admin Commands", adminCommands);
|
||||
if (normalCommands.Length > 0)
|
||||
embedBuilder.AddField("Normal Commands", normalCommands);
|
||||
args.context.Channel.SendMessageAsync(embed: embedBuilder.Build());
|
||||
args.Context.Channel.SendMessageAsync(embed: embedBuilder.Build());
|
||||
}
|
||||
|
||||
private EmbedBuilder GenerateHelpCommand(string command)
|
||||
|
||||
@@ -71,7 +71,7 @@ public class Program
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log(ex.ToString(), typeof(Program), LogType.CRITICAL);
|
||||
Application.CurrentApplication.Logger.Log(ex.ToString(), typeof(Program), LogType.Critical);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ public class App
|
||||
if (arg.Message.Contains("401"))
|
||||
{
|
||||
Application.CurrentApplication.ApplicationEnvironmentVariables.Remove("token");
|
||||
Application.CurrentApplication.Logger.Log("The token is invalid.", this, LogType.CRITICAL);
|
||||
Application.CurrentApplication.Logger.Log("The token is invalid.", this, LogType.Critical);
|
||||
await Application.CurrentApplication.ApplicationEnvironmentVariables.SaveToFile();
|
||||
await Task.Delay(3000);
|
||||
|
||||
@@ -134,12 +134,12 @@ public class App
|
||||
{
|
||||
case LogSeverity.Error:
|
||||
case LogSeverity.Critical:
|
||||
Application.CurrentApplication.Logger.Log(message.Message, this, LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log(message.Message, this, LogType.Error);
|
||||
break;
|
||||
|
||||
case LogSeverity.Info:
|
||||
case LogSeverity.Debug:
|
||||
Application.CurrentApplication.Logger.Log(message.Message, this, LogType.INFO);
|
||||
Application.CurrentApplication.Logger.Log(message.Message, this, LogType.Info);
|
||||
|
||||
|
||||
break;
|
||||
|
||||
@@ -141,9 +141,9 @@ internal class CommandHandler
|
||||
DbCommandExecutingArguments cmd = new(context, cleanMessage, split[0], argsClean);
|
||||
|
||||
Application.CurrentApplication.Logger.Log(
|
||||
$"User ({context.User.Username}) from Guild \"{context.Guild.Name}\" executed command \"{cmd.cleanContent}\"",
|
||||
$"User ({context.User.Username}) from Guild \"{context.Guild.Name}\" executed command \"{cmd.CleanContent}\"",
|
||||
this,
|
||||
LogType.INFO
|
||||
LogType.Info
|
||||
);
|
||||
|
||||
if (context.Channel is SocketDMChannel)
|
||||
|
||||
@@ -41,7 +41,7 @@ public class PluginLoader
|
||||
|
||||
if (_Client == null)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Discord client is null", this, LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log("Discord client is null", this, LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ public class PluginLoader
|
||||
|
||||
private void FileLoadedException(FileLoaderResult result)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log(result.ErrorMessage, this, LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log(result.ErrorMessage, this, LogType.Error);
|
||||
}
|
||||
|
||||
private async void OnPluginLoaded(PluginLoadResultData result)
|
||||
@@ -71,10 +71,10 @@ public class PluginLoader
|
||||
{
|
||||
case PluginType.ACTION:
|
||||
ICommandAction action = (ICommandAction)result.Plugin;
|
||||
if (action.RunType == InternalActionRunType.ON_STARTUP || action.RunType == InternalActionRunType.BOTH)
|
||||
if (action.RunType == InternalActionRunType.OnStartup || action.RunType == InternalActionRunType.OnStartupAndCall)
|
||||
action.Execute(null);
|
||||
|
||||
if(action.RunType == InternalActionRunType.ON_CALL || action.RunType == InternalActionRunType.BOTH)
|
||||
if(action.RunType == InternalActionRunType.OnCall || action.RunType == InternalActionRunType.OnStartupAndCall)
|
||||
Actions.Add(action);
|
||||
|
||||
OnActionLoaded?.Invoke(result);
|
||||
@@ -101,11 +101,11 @@ public class PluginLoader
|
||||
OnSlashCommandLoaded?.Invoke(result);
|
||||
}
|
||||
else
|
||||
Application.CurrentApplication.Logger.Log($"Failed to start slash command {result.PluginName}", this, LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log($"Failed to start slash command {result.PluginName}", this, LogType.Error);
|
||||
break;
|
||||
case PluginType.UNKNOWN:
|
||||
default:
|
||||
Application.CurrentApplication.Logger.Log("Unknown plugin type", this, LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log("Unknown plugin type", this, LogType.Error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ internal static class PluginLoaderExtensions
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log($"Error starting event {dbEvent.Name}: {e.Message}", typeof(PluginLoader), LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log($"Error starting event {dbEvent.Name}: {e.Message}", typeof(PluginLoader), LogType.Error);
|
||||
Application.CurrentApplication.Logger.LogException(e, typeof(PluginLoader));
|
||||
return false;
|
||||
}
|
||||
@@ -39,14 +39,14 @@ internal static class PluginLoaderExtensions
|
||||
if(pluginLoader._Client.Guilds.Count == 0) return;
|
||||
if (!ulong.TryParse(Application.CurrentApplication.ServerID, out _))
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Invalid ServerID in config file. Can not reset specific guild commands", typeof(PluginLoader), LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log("Invalid ServerID in config file. Can not reset specific guild commands", typeof(PluginLoader), LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
SocketGuild? guild = pluginLoader._Client.GetGuild(ulong.Parse(Application.CurrentApplication.ServerID));
|
||||
if(guild is null)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Failed to get guild with ID " + Application.CurrentApplication.ServerID, typeof(PluginLoader), LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log("Failed to get guild with ID " + Application.CurrentApplication.ServerID, typeof(PluginLoader), LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ internal static class PluginLoaderExtensions
|
||||
SocketGuild? guild = pluginLoader._Client.GetGuild(result);
|
||||
if (guild is null)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Failed to get guild with ID " + Application.CurrentApplication.ServerID, typeof(PluginLoader), LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log("Failed to get guild with ID " + Application.CurrentApplication.ServerID, typeof(PluginLoader), LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ internal static class PluginLoaderExtensions
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log($"Error starting slash command {dbSlashCommand.Name}: {e.Message}", typeof(PluginLoader), LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log($"Error starting slash command {dbSlashCommand.Name}: {e.Message}", typeof(PluginLoader), LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ public class PluginManager : IPluginManager
|
||||
{
|
||||
if (await pluginUpdater.HasUpdate(plugin.PluginName))
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Updating plugin: " + plugin.PluginName, this, LogType.INFO);
|
||||
Application.CurrentApplication.Logger.Log("Updating plugin: " + plugin.PluginName, this, LogType.Info);
|
||||
await pluginUpdater.UpdatePlugin(plugin.PluginName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ public class InternalActionManager
|
||||
|
||||
PluginLoader.Actions.ForEach(action =>
|
||||
{
|
||||
if (action.RunType == InternalActionRunType.ON_CALL || action.RunType == InternalActionRunType.BOTH)
|
||||
if (action.RunType == InternalActionRunType.OnCall || action.RunType == InternalActionRunType.OnStartupAndCall)
|
||||
{
|
||||
if (this.Actions.ContainsKey(action.ActionName))
|
||||
{
|
||||
// This should never happen. If it does, log it and return
|
||||
Application.CurrentApplication.Logger.Log($"Action {action.ActionName} already exists", this, LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log($"Action {action.ActionName} already exists", this, LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -49,15 +49,15 @@ public class InternalActionManager
|
||||
{
|
||||
if (!Actions.ContainsKey(actionName))
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log($"Action {actionName} not found", this, LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log($"Action {actionName} not found", this, LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (Actions[actionName].RunType == InternalActionRunType.ON_STARTUP)
|
||||
if (Actions[actionName].RunType == InternalActionRunType.OnStartup)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log($"Action {actionName} is not executable", this, LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log($"Action {actionName} is not executable", this, LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public class InternalActionManager
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log(e.Message, type: LogType.ERROR, Sender: this);
|
||||
Application.CurrentApplication.Logger.Log(e.Message, type: LogType.Error, Sender: this);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ public static class ArchiveManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log(ex.Message, typeof(ArchiveManager), LogType.ERROR); // Write the error to a file
|
||||
Application.CurrentApplication.Logger.Log(ex.Message, typeof(ArchiveManager), LogType.Error); // Write the error to a file
|
||||
await Task.Delay(100);
|
||||
return await ReadFromPakAsync(fileName, archFile);
|
||||
}
|
||||
@@ -123,7 +123,7 @@ public static class ArchiveManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log(ex.Message, typeof(ArchiveManager), LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log(ex.Message, typeof(ArchiveManager), LogType.Error);
|
||||
}
|
||||
|
||||
currentZipFile++;
|
||||
@@ -158,7 +158,7 @@ public static class ArchiveManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log(ex.Message, typeof(ArchiveManager), LogType.ERROR);
|
||||
Application.CurrentApplication.Logger.Log(ex.Message, typeof(ArchiveManager), LogType.Error);
|
||||
}
|
||||
|
||||
await Task.Delay(10);
|
||||
|
||||
@@ -7,42 +7,42 @@ namespace DiscordBotCore.Others;
|
||||
public class DbCommandExecutingArguments
|
||||
{
|
||||
|
||||
public SocketCommandContext context { get; init; }
|
||||
public string cleanContent { get; init; }
|
||||
public string commandUsed { get; init; }
|
||||
public string[]? arguments { get; init; }
|
||||
public ISocketMessageChannel Channel => context.Channel;
|
||||
public SocketCommandContext Context { get; init; }
|
||||
public string CleanContent { get; init; }
|
||||
public string CommandUsed { get; init; }
|
||||
public string[]? Arguments { get; init; }
|
||||
public ISocketMessageChannel Channel => Context.Channel;
|
||||
|
||||
public DbCommandExecutingArguments(
|
||||
SocketCommandContext context, string cleanContent, string commandUsed, string[]? arguments)
|
||||
{
|
||||
this.context = context;
|
||||
this.cleanContent = cleanContent;
|
||||
this.commandUsed = commandUsed;
|
||||
this.arguments = arguments;
|
||||
this.Context = context;
|
||||
this.CleanContent = cleanContent;
|
||||
this.CommandUsed = commandUsed;
|
||||
this.Arguments = arguments;
|
||||
}
|
||||
|
||||
public DbCommandExecutingArguments(SocketUserMessage? message, DiscordSocketClient client)
|
||||
{
|
||||
context = new SocketCommandContext(client, message);
|
||||
Context = new SocketCommandContext(client, message);
|
||||
var pos = 0;
|
||||
if (message.HasMentionPrefix(client.CurrentUser, ref pos))
|
||||
{
|
||||
var mentionPrefix = "<@" + client.CurrentUser.Id + ">";
|
||||
cleanContent = message.Content.Substring(mentionPrefix.Length + 1);
|
||||
CleanContent = message.Content.Substring(mentionPrefix.Length + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
cleanContent = message.Content.Substring(Application.CurrentApplication.DiscordBotClient.BotPrefix.Length);
|
||||
CleanContent = message.Content.Substring(Application.CurrentApplication.DiscordBotClient.BotPrefix.Length);
|
||||
}
|
||||
|
||||
var split = cleanContent.Split(' ');
|
||||
var split = CleanContent.Split(' ');
|
||||
|
||||
string[]? argsClean = null;
|
||||
if (split.Length > 1)
|
||||
argsClean = string.Join(' ', split, 1, split.Length - 1).Split(' ');
|
||||
|
||||
commandUsed = split[0];
|
||||
arguments = argsClean;
|
||||
CommandUsed = split[0];
|
||||
Arguments = argsClean;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ namespace DiscordBotCore.Others;
|
||||
/// </summary>
|
||||
public enum LogType
|
||||
{
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR,
|
||||
CRITICAL
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Critical
|
||||
}
|
||||
|
||||
public enum UnzipProgressType
|
||||
@@ -21,9 +21,9 @@ public enum UnzipProgressType
|
||||
|
||||
public enum InternalActionRunType
|
||||
{
|
||||
ON_STARTUP,
|
||||
ON_CALL,
|
||||
BOTH
|
||||
OnStartup,
|
||||
OnCall,
|
||||
OnStartupAndCall
|
||||
}
|
||||
|
||||
[Flags]
|
||||
|
||||
22
Modules/LoggerModule/Entry.cs
Normal file
22
Modules/LoggerModule/Entry.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces.Logger;
|
||||
using DiscordBotCore.Interfaces.Modules;
|
||||
|
||||
namespace LoggerModule
|
||||
{
|
||||
public class Entry : IModule<ILogger>
|
||||
{
|
||||
public string Name => "Logger Module";
|
||||
const string _LogFolder = "./Data/Logs/";
|
||||
const string _LogFormat = "{ThrowTime} {SenderName} {Message}";
|
||||
|
||||
public ILogger Module { get; private set; }
|
||||
|
||||
public Task Initialize()
|
||||
{
|
||||
ILogger logger = new Logger(_LogFolder, _LogFormat);
|
||||
Module = logger;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
79
Modules/LoggerModule/LogMessage.cs
Normal file
79
Modules/LoggerModule/LogMessage.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using DiscordBotCore.Interfaces.Logger;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace LoggerModule
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
122
Modules/LoggerModule/Logger.cs
Normal file
122
Modules/LoggerModule/Logger.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using DiscordBotCore.Interfaces.Logger;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace LoggerModule;
|
||||
|
||||
public sealed class Logger : ILogger
|
||||
{
|
||||
private readonly FileStream _LogFileStream;
|
||||
|
||||
public List<string> LogMessageProperties = typeof(ILogMessage).GetProperties().Select(p => p.Name).ToList();
|
||||
private Action<string>? _OutFunction;
|
||||
public string LogMessageFormat { get ; set; }
|
||||
|
||||
public Logger(string logFolder, string logMessageFormat, Action<string>? outFunction = null)
|
||||
{
|
||||
this.LogMessageFormat = logMessageFormat;
|
||||
this._OutFunction = outFunction;
|
||||
var logFile = logFolder + DateTime.Now.ToString("yyyy-MM-dd") + ".log";
|
||||
_LogFileStream = File.Open(logFile, FileMode.Append, FileAccess.Write, FileShare.Read);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a formatted string based on the default parameters of the ILogMessage and a string defined as model
|
||||
/// </summary>
|
||||
/// <param name="message">The message</param>
|
||||
/// <returns>A formatted string with the message values</returns>
|
||||
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());
|
||||
}
|
||||
|
||||
switch (message.LogMessageType)
|
||||
{
|
||||
case LogType.Info:
|
||||
messageAsString = $"[green]{messageAsString} [/]";
|
||||
break;
|
||||
case LogType.Warning:
|
||||
messageAsString = $"[yellow]{messageAsString} [/]";
|
||||
break;
|
||||
case LogType.Error:
|
||||
messageAsString = $"[red]{messageAsString} [/]";
|
||||
break;
|
||||
case LogType.Critical:
|
||||
messageAsString = $"[red] [bold]{messageAsString} [/][/]";
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return messageAsString;
|
||||
}
|
||||
|
||||
private async void LogToFile(string message)
|
||||
{
|
||||
byte[] messageAsBytes = System.Text.Encoding.ASCII.GetBytes(message);
|
||||
await _LogFileStream.WriteAsync(messageAsBytes, 0, messageAsBytes.Length);
|
||||
|
||||
byte[] newLine = System.Text.Encoding.ASCII.GetBytes(Environment.NewLine);
|
||||
await _LogFileStream.WriteAsync(newLine, 0, newLine.Length);
|
||||
|
||||
await _LogFileStream.FlushAsync();
|
||||
}
|
||||
|
||||
private string GenerateLogMessage(ILogMessage message, string customFormat)
|
||||
{
|
||||
string messageAsString = customFormat;
|
||||
foreach (var prop in LogMessageProperties)
|
||||
{
|
||||
Type messageType = typeof(ILogMessage);
|
||||
messageAsString = messageAsString.Replace("{" + prop + "}", messageType?.GetProperty(prop)?.GetValue(message)?.ToString());
|
||||
}
|
||||
|
||||
switch (message.LogMessageType)
|
||||
{
|
||||
case LogType.Info:
|
||||
messageAsString = $"[green]{messageAsString} [/]";
|
||||
break;
|
||||
case LogType.Warning:
|
||||
messageAsString = $"[yellow]{messageAsString} [/]";
|
||||
break;
|
||||
case LogType.Error:
|
||||
messageAsString = $"[red]{messageAsString} [/]";
|
||||
break;
|
||||
case LogType.Critical:
|
||||
messageAsString = $"[red][bold]{messageAsString} [/][/]";
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return messageAsString;
|
||||
}
|
||||
|
||||
public void Log(ILogMessage message, string format)
|
||||
{
|
||||
string messageAsString = GenerateLogMessage(message, format);
|
||||
_OutFunction?.Invoke(messageAsString);
|
||||
LogToFile(messageAsString);
|
||||
}
|
||||
|
||||
public void Log(ILogMessage message)
|
||||
{
|
||||
string messageAsString = GenerateLogMessage(message);
|
||||
_OutFunction?.Invoke(messageAsString);
|
||||
LogToFile(messageAsString);
|
||||
|
||||
}
|
||||
|
||||
public void Log(string message) => Log(new LogMessage(message, string.Empty, LogType.Info));
|
||||
public void Log(string message, LogType logType, string format) => Log(new LogMessage(message, logType), format);
|
||||
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));
|
||||
|
||||
public void SetOutFunction(Action<string> outFunction)
|
||||
{
|
||||
this._OutFunction = outFunction;
|
||||
}
|
||||
}
|
||||
13
Modules/LoggerModule/LoggerModule.csproj
Normal file
13
Modules/LoggerModule/LoggerModule.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DiscordBotCore\DiscordBotCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
13
Plugins/CppWrapper/CppWrapper.csproj
Normal file
13
Plugins/CppWrapper/CppWrapper.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DiscordBotCore\DiscordBotCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
112
Plugins/CppWrapper/LibraryManagement/ExternLibrary.cs
Normal file
112
Plugins/CppWrapper/LibraryManagement/ExternLibrary.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using DiscordBotCore;
|
||||
|
||||
namespace CppWrapper.LibraryManagement
|
||||
{
|
||||
public sealed class ExternLibrary
|
||||
{
|
||||
public string LibraryPath { get; init; }
|
||||
public IntPtr LibraryHandle { get; private set; }
|
||||
|
||||
public ExternLibrary(string libraryPath)
|
||||
{
|
||||
LibraryPath = libraryPath;
|
||||
LibraryHandle = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public void InitializeLibrary()
|
||||
{
|
||||
if(LibraryHandle != IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Application.CurrentApplication.Logger.Log($"Loading library {LibraryPath}");
|
||||
|
||||
|
||||
if(!NativeLibrary.TryLoad(LibraryPath, out IntPtr hModule))
|
||||
{
|
||||
throw new DllNotFoundException($"Unable to load library {LibraryPath}");
|
||||
}
|
||||
|
||||
Application.CurrentApplication.Logger.Log($"Library {LibraryPath} loaded successfully [{hModule}]");
|
||||
|
||||
LibraryHandle = hModule;
|
||||
}
|
||||
|
||||
public void FreeLibrary()
|
||||
{
|
||||
if(LibraryHandle == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeLibrary.Free(LibraryHandle);
|
||||
LibraryHandle = IntPtr.Zero;
|
||||
|
||||
Application.CurrentApplication.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);
|
||||
|
||||
Application.CurrentApplication.Logger.Log($"Function pointer for {methodName} obtained successfully [address: {functionPointer}]");
|
||||
|
||||
T result = (T)Marshal.GetDelegateForFunctionPointer(functionPointer, typeof(T));
|
||||
|
||||
Application.CurrentApplication.Logger.Log($"Delegate for {methodName} created successfully");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private IntPtr GetFunctionPointerForDelegate<T>(T functionDelegate) where T : Delegate
|
||||
{
|
||||
IntPtr functionPointer = Marshal.GetFunctionPointerForDelegate(functionDelegate);
|
||||
|
||||
Application.CurrentApplication.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);
|
||||
|
||||
Application.CurrentApplication.Logger.Log($"Function {setterExternFunctionName} bound to local action successfully");
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Plugins/CppWrapper/Objects/ApplicationStruct.cs
Normal file
34
Plugins/CppWrapper/Objects/ApplicationStruct.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CppWrapper.Objects
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
public struct ApplicationStruct
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 19)]
|
||||
public string ServerId;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)]
|
||||
public string Prefix;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 71)]
|
||||
public string Token;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
public struct ComplexObject
|
||||
{
|
||||
public int Integer;
|
||||
public double DoubleValue;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
|
||||
public string strValue;
|
||||
|
||||
public ComplexObject(int integer, double doubleValue, string strValue)
|
||||
{
|
||||
Integer = integer;
|
||||
DoubleValue = doubleValue;
|
||||
this.strValue = strValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Plugins/CppWrapper/Objects/ObjectConvertor.cs
Normal file
17
Plugins/CppWrapper/Objects/ObjectConvertor.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using DiscordBotCore;
|
||||
|
||||
namespace CppWrapper.Objects
|
||||
{
|
||||
public static class ObjectConvertor
|
||||
{
|
||||
public static ApplicationStruct ToApplicationStruct(this Application application)
|
||||
{
|
||||
return new ApplicationStruct
|
||||
{
|
||||
Token = application.ApplicationEnvironmentVariables["token"],
|
||||
Prefix = application.ApplicationEnvironmentVariables["prefix"],
|
||||
ServerId = application.ServerID
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Plugins/DiscordBotUI/Delegates.cs
Normal file
22
Plugins/DiscordBotUI/Delegates.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using CppWrapper.Objects;
|
||||
|
||||
namespace DiscordBotUI
|
||||
{
|
||||
public abstract class Delegates
|
||||
{
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void ProcessApplicationData(ref ApplicationStruct appData);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void ProcessComplexObject(ref ComplexObject complexObject);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void CsharpFunctionDelegate();
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void SetCsharpFunctionPointerDelegate(IntPtr funcPtr);
|
||||
}
|
||||
}
|
||||
13
Plugins/DiscordBotUI/DiscordBotUI.csproj
Normal file
13
Plugins/DiscordBotUI/DiscordBotUI.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DiscordBotCore\DiscordBotCore.csproj" />
|
||||
<ProjectReference Include="..\CppWrapper\CppWrapper.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
52
Plugins/DiscordBotUI/Entry.cs
Normal file
52
Plugins/DiscordBotUI/Entry.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Actions;
|
||||
|
||||
using CppWrapper.Objects;
|
||||
using CppWrapper.LibraryManagement;
|
||||
using DiscordBotCore;
|
||||
using CppWrapper;
|
||||
|
||||
namespace DiscordBotUI;
|
||||
|
||||
public class Entry : ICommandAction
|
||||
{
|
||||
public string ActionName => "cppui";
|
||||
|
||||
public string? Description => "A C++ linker to the C++ UI for the bot";
|
||||
|
||||
public string? Usage => "cppui";
|
||||
|
||||
public IEnumerable<InternalActionOption> ListOfOptions => [];
|
||||
|
||||
public InternalActionRunType RunType => InternalActionRunType.OnStartupAndCall;
|
||||
|
||||
public async Task Execute(string[]? args)
|
||||
{
|
||||
try{
|
||||
|
||||
string appUiComponent = "./Data/Test/libtestlib.dll";
|
||||
|
||||
ExternLibrary externalLibrary = new ExternLibrary(appUiComponent);
|
||||
externalLibrary.InitializeLibrary();
|
||||
|
||||
externalLibrary.SetExternFunctionSetterPointerToCustomDelegate<Delegates.SetCsharpFunctionPointerDelegate, Delegates.CsharpFunctionDelegate>("setCSharpFunctionPointer", () =>
|
||||
{
|
||||
Console.WriteLine("Hello from C#. This code is called from the C# function");
|
||||
});
|
||||
|
||||
Delegates.ProcessComplexObject processObj = externalLibrary.GetDelegateForFunctionPointer<Delegates.ProcessComplexObject>("ProcessComplexObject");
|
||||
|
||||
ComplexObject complexObject = new ComplexObject(10, 10.5, "Hello from C#");
|
||||
processObj(ref complexObject);
|
||||
|
||||
Console.WriteLine($"Integer: {complexObject.Integer}");
|
||||
Console.WriteLine($"Double: {complexObject.DoubleValue}");
|
||||
Console.WriteLine($"String: {complexObject.strValue}");
|
||||
|
||||
externalLibrary.FreeLibrary();
|
||||
} catch (Exception dllException) {
|
||||
Application.CurrentApplication.Logger.LogException(dllException, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Plugins/LevelingSystem/LevelCommand.cs
Normal file
52
Plugins/LevelingSystem/LevelCommand.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Discord;
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace LevelingSystem;
|
||||
|
||||
internal class LevelCommand: DBCommand
|
||||
{
|
||||
public string Command => "level";
|
||||
|
||||
public List<string> Aliases => ["lvl", "rank"];
|
||||
|
||||
public string Description => "Display tour current level";
|
||||
|
||||
public string Usage => "level";
|
||||
|
||||
public bool requireAdmin => false;
|
||||
|
||||
public async void ExecuteServer(DbCommandExecutingArguments args)
|
||||
{
|
||||
if(Variables.Database is null)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Database is not initialized", this, LogType.Warning);
|
||||
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 = (int)user[1];
|
||||
var exp = (int)user[2];
|
||||
|
||||
var builder = new EmbedBuilder();
|
||||
var r = new Random();
|
||||
builder.WithColor(r.Next(256), r.Next(256), r.Next(256));
|
||||
builder.AddField("Current Level", level, true)
|
||||
.AddField("Current EXP", exp, true)
|
||||
.AddField("Required Exp", (level * 8 + 24).ToString(), true);
|
||||
builder.WithTimestamp(DateTimeOffset.Now);
|
||||
builder.WithAuthor(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 Discord.WebSocket;
|
||||
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Database;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using static LevelingSystem.Variables;
|
||||
|
||||
namespace LevelingSystem;
|
||||
|
||||
internal class LevelEvent : DBEvent
|
||||
{
|
||||
public string Name => "Leveling System Event Handler";
|
||||
public string Description => "The Leveling System Event Handler";
|
||||
|
||||
public bool RequireOtherThread => false;
|
||||
|
||||
public async void Start(DiscordSocketClient client)
|
||||
{
|
||||
|
||||
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.Others.JsonManager.SaveToJsonFile(DataFolder + "Settings.txt", GlobalSettings);
|
||||
}
|
||||
else
|
||||
GlobalSettings = await DiscordBotCore.Others.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)");
|
||||
|
||||
|
||||
|
||||
client.MessageReceived += ClientOnMessageReceived;
|
||||
}
|
||||
|
||||
private async Task ClientOnMessageReceived(SocketMessage arg)
|
||||
{
|
||||
if (arg.Author.IsBot || arg.IsTTS || arg.Content.StartsWith(Application.CurrentApplication.ApplicationEnvironmentVariables["prefix"]))
|
||||
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 = (int)userData[1];
|
||||
var exp = (int)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);
|
||||
}
|
||||
|
||||
}
|
||||
13
Plugins/LevelingSystem/LevelingSystem.csproj
Normal file
13
Plugins/LevelingSystem/LevelingSystem.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DiscordBotCore\DiscordBotCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
20
Plugins/LevelingSystem/ReloadAction.cs
Normal file
20
Plugins/LevelingSystem/ReloadAction.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
using DiscordBotCore.Others.Actions;
|
||||
|
||||
namespace LevelingSystem;
|
||||
|
||||
public class ReloadAction: ICommandAction
|
||||
{
|
||||
public string ActionName => "LevelingSystemReload";
|
||||
public string? Description => "Reloads the Leveling System config file";
|
||||
public string? Usage => "LevelingSystemReload";
|
||||
public InternalActionRunType RunType => InternalActionRunType.OnCall;
|
||||
|
||||
public IEnumerable<InternalActionOption> ListOfOptions => [];
|
||||
|
||||
public async Task Execute(string[]? args)
|
||||
{
|
||||
Variables.GlobalSettings = await JsonManager.ConvertFromJson<Settings>(Variables.DataFolder + "Settings.txt");
|
||||
}
|
||||
}
|
||||
19
Plugins/LevelingSystem/Settings.cs
Normal file
19
Plugins/LevelingSystem/Settings.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Database;
|
||||
|
||||
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 = Application.GetResourceFullPath("LevelingSystem/");
|
||||
internal static SqlDatabase? Database;
|
||||
internal static readonly Dictionary<ulong, DateTime> WaitingList = new();
|
||||
internal static Settings GlobalSettings = new();
|
||||
}
|
||||
96
Plugins/MusicPlayer/Commands/AddMelody.cs
Normal file
96
Plugins/MusicPlayer/Commands/AddMelody.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using Discord;
|
||||
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Online;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace MusicPlayer.Commands;
|
||||
|
||||
public class AddMelody: DBCommand
|
||||
{
|
||||
public string Command => "add_melody";
|
||||
|
||||
public List<string>? Aliases => new()
|
||||
{
|
||||
"madd"
|
||||
};
|
||||
|
||||
public string Description => "Add a custom melody to the database";
|
||||
public string Usage => "add_melody [title],[description?],[aliases],[byteSize]";
|
||||
public bool requireAdmin => false;
|
||||
|
||||
public async void ExecuteServer(DbCommandExecutingArguments args)
|
||||
{
|
||||
var arguments = string.Join(" ", args.Arguments);
|
||||
string[] split = arguments.Split(',');
|
||||
|
||||
if (split.Length < 4)
|
||||
{
|
||||
var message = "";
|
||||
message += "Invalid arguments given. Please use the following format:\n";
|
||||
message += "add_melody [title],[description?],[aliases],[byteSize]\n";
|
||||
message += "title: The title of the melody\n";
|
||||
message += "description: The description of the melody\n";
|
||||
message += "aliases: The aliases of the melody. Use | to separate them\n";
|
||||
message += "byteSize: The byte size of the melody. Default is 1024. ( & will use default)\n";
|
||||
|
||||
await args.Context.Channel.SendMessageAsync(message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Context.Message.Attachments.Count == 0)
|
||||
{
|
||||
await args.Context.Channel.SendMessageAsync("You must upload a valid .mp3 audio or .mp4 video file !!");
|
||||
return;
|
||||
}
|
||||
|
||||
var file = args.Context.Message.Attachments.FirstOrDefault();
|
||||
if (!(file.Filename.EndsWith(".mp3") || file.Filename.EndsWith(".mp4")))
|
||||
{
|
||||
await args.Context.Channel.SendMessageAsync("Invalid file format !!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var title = split[0];
|
||||
var description = split[1];
|
||||
string[]? aliases = split[2]?.Split('|') ?? null;
|
||||
var byteSize = split[3];
|
||||
int bsize;
|
||||
if (!int.TryParse(byteSize, out bsize))
|
||||
bsize = 1024;
|
||||
|
||||
|
||||
var msg = await args.Context.Channel.SendMessageAsync("Saving melody ...");
|
||||
Console.WriteLine("Saving melody");
|
||||
|
||||
IProgress<float> downloadProgress = new Progress<float>();
|
||||
|
||||
|
||||
var location = Application.GetResourceFullPath($"Music/Melodies/{title}.mp3");
|
||||
Directory.CreateDirectory(Application.GetResourceFullPath("Music/Melodies"));
|
||||
await ServerCom.DownloadFileAsync(file.Url, location, downloadProgress);
|
||||
|
||||
Console.WriteLine($"Done. Saved at {location}");
|
||||
|
||||
await msg.ModifyAsync(a => a.Content = "Done");
|
||||
|
||||
|
||||
var info =
|
||||
MusicInfoExtensions.CreateMusicInfo(title, location, description ?? "Unknown", aliases.ToList(), bsize);
|
||||
|
||||
Variables._MusicDatabase?.Add(title, info);
|
||||
|
||||
var builder = new EmbedBuilder();
|
||||
builder.Title = "A new music was successfully added !";
|
||||
builder.AddField("Title", info.Title);
|
||||
builder.AddField("Description", info.Description);
|
||||
builder.AddField("Aliases", string.Join(" | ", aliases));
|
||||
await args.Context.Channel.SendMessageAsync(embed: builder.Build());
|
||||
|
||||
await Variables._MusicDatabase.SaveToFile();
|
||||
|
||||
}
|
||||
}
|
||||
77
Plugins/MusicPlayer/Commands/AddMelodyYoutube.cs
Normal file
77
Plugins/MusicPlayer/Commands/AddMelodyYoutube.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace MusicPlayer.Commands;
|
||||
|
||||
public class AddMelodyYoutube: DBCommand
|
||||
{
|
||||
public string Command => "add_melody_youtube";
|
||||
|
||||
public List<string>? Aliases => new()
|
||||
{
|
||||
"madd-yt"
|
||||
};
|
||||
|
||||
public string Description => "Add melody to the database from a youtube link";
|
||||
public string Usage => "add_melody_youtube [URL] <alias1|alias2|...>";
|
||||
public bool requireAdmin => true;
|
||||
|
||||
public async void ExecuteServer(DbCommandExecutingArguments args)
|
||||
{
|
||||
if (args.Arguments is null)
|
||||
{
|
||||
await args.Context.Channel.SendMessageAsync("Invalid arguments given. Please use the following format:\nadd_melody_youtube [URL]");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var URL = args.Arguments[0];
|
||||
|
||||
if (!URL.StartsWith("https://www.youtube.com/watch?v=") && !URL.StartsWith("https://youtu.be/"))
|
||||
{
|
||||
await args.Context.Channel.SendMessageAsync("Invalid URL given. Please use the following format:\nadd_melody_youtube [URL]");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Arguments.Length <= 1)
|
||||
{
|
||||
await args.Channel.SendMessageAsync("Please specify at least one alias for the melody !");
|
||||
return;
|
||||
}
|
||||
|
||||
var msg = await args.Context.Channel.SendMessageAsync("Saving melody ...");
|
||||
|
||||
var title = await YoutubeDLP.DownloadMelody(URL);
|
||||
|
||||
if (title == null)
|
||||
{
|
||||
await msg.ModifyAsync(x => x.Content = "Failed to download melody !");
|
||||
return;
|
||||
}
|
||||
|
||||
var joinedAliases = string.Join(" ", args.Arguments.Skip(1));
|
||||
List<string> aliases = joinedAliases.Split('|').ToList();
|
||||
|
||||
|
||||
if (Variables._MusicDatabase.ContainsMelodyWithNameOrAlias(title))
|
||||
Variables._MusicDatabase.Remove(title);
|
||||
|
||||
Variables._MusicDatabase.Add(title, new MusicInfo()
|
||||
{
|
||||
Aliases = aliases,
|
||||
ByteSize = 1024,
|
||||
Description = "Melody added from youtube link",
|
||||
Location = Application.GetResourceFullPath($"Music/Melodies/{title}.mp3"),
|
||||
Title = title
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
await Variables._MusicDatabase.SaveToFile();
|
||||
await msg.ModifyAsync(x => x.Content = "Melody saved !");
|
||||
}
|
||||
}
|
||||
38
Plugins/MusicPlayer/Commands/SearchMelody.cs
Normal file
38
Plugins/MusicPlayer/Commands/SearchMelody.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Discord;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace MusicPlayer.Commands;
|
||||
|
||||
public class SearchMelody: DBCommand
|
||||
{
|
||||
|
||||
public string Command => "search_melody";
|
||||
public List<string>? Aliases => null;
|
||||
public string Description => "Search for a melody in the database";
|
||||
public string Usage => "search_melody [melody name OR one of its aliases]";
|
||||
public bool requireAdmin => false;
|
||||
|
||||
public void ExecuteServer(DbCommandExecutingArguments args)
|
||||
{
|
||||
var title = string.Join(" ", args.Arguments);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
args.Context.Channel.SendMessageAsync("You need to specify a melody name");
|
||||
return;
|
||||
}
|
||||
|
||||
List<MusicInfo>? info = Variables._MusicDatabase.GetMusicInfoList(title);
|
||||
if (info == null || info.Count == 0)
|
||||
{
|
||||
args.Context.Channel.SendMessageAsync("No melody with that name or alias was found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.Count > 1)
|
||||
args.Context.Channel.SendMessageAsync(embed: info.ToEmbed(Color.DarkOrange));
|
||||
else
|
||||
args.Context.Channel.SendMessageAsync(embed: info[0].ToEmbed(Color.DarkOrange));
|
||||
}
|
||||
}
|
||||
27
Plugins/MusicPlayer/Events/OnLoad.cs
Normal file
27
Plugins/MusicPlayer/Events/OnLoad.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Discord.WebSocket;
|
||||
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
|
||||
namespace MusicPlayer.Events;
|
||||
|
||||
public class OnLoad: DBEvent
|
||||
{
|
||||
private static readonly string _DefaultMusicPath = "Music/";
|
||||
private static readonly string _DefaultSaveLocation = "Music/Melodies/";
|
||||
private static readonly string _DefaultMusicDB = "Music/music_db.json";
|
||||
public string Name => "Music Commands";
|
||||
public string Description => "The default music commands event loader";
|
||||
public bool RequireOtherThread => false;
|
||||
|
||||
public async void Start(DiscordSocketClient client)
|
||||
{
|
||||
var path1 = Application.GetResourceFullPath(_DefaultMusicPath);
|
||||
var path2 = Application.GetResourceFullPath(_DefaultSaveLocation);
|
||||
var path3 = Application.GetResourceFullPath(_DefaultMusicDB);
|
||||
Directory.CreateDirectory(path1);
|
||||
Directory.CreateDirectory(path2);
|
||||
Variables._MusicDatabase = new MusicDatabase(path3);
|
||||
await Variables._MusicDatabase.LoadFromFile();
|
||||
}
|
||||
}
|
||||
35
Plugins/MusicPlayer/Events/OnVoiceRemoved.cs
Normal file
35
Plugins/MusicPlayer/Events/OnVoiceRemoved.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace MusicPlayer.Events;
|
||||
|
||||
public class OnVoiceRemoved: DBEvent
|
||||
{
|
||||
|
||||
public string Name => "Event: OnVoiceRemoved";
|
||||
public string Description => "Called when bot leaves a voice channel";
|
||||
|
||||
public bool RequireOtherThread => false;
|
||||
|
||||
public void Start(DiscordSocketClient client)
|
||||
{
|
||||
client.UserVoiceStateUpdated += async (user, oldState, newState) =>
|
||||
{
|
||||
if (user.Id == client.CurrentUser.Id && newState.VoiceChannel == null)
|
||||
{
|
||||
Variables._MusicPlayer?.MusicQueue.Clear();
|
||||
Variables._MusicPlayer?.Skip();
|
||||
Variables._MusicPlayer?.Stop();
|
||||
await Variables.audioClient!.StopAsync();
|
||||
Variables.audioClient = null;
|
||||
Variables._MusicPlayer = null;
|
||||
|
||||
Application.CurrentApplication.Logger.Log("Bot left voice channel.", this, LogType.Info);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
64
Plugins/MusicPlayer/MusicDatabase.cs
Normal file
64
Plugins/MusicPlayer/MusicDatabase.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace MusicPlayer;
|
||||
|
||||
public class MusicDatabase: SettingsDictionary<string, MusicInfo>
|
||||
{
|
||||
public MusicDatabase(string file): base(file)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the database contains a melody with the specified name or alias
|
||||
/// </summary>
|
||||
/// <param name="melodyName">The name (alias) of the melody</param>
|
||||
/// <returns></returns>
|
||||
public bool ContainsMelodyWithNameOrAlias(string melodyName)
|
||||
{
|
||||
return ContainsKey(melodyName) || Values.Any(elem => elem.Aliases.Contains(melodyName, StringComparer.CurrentCultureIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the music info of a melody with the specified name or alias. Returns the first match or null if no match was found.
|
||||
/// </summary>
|
||||
/// <param name="searchQuery">The music name or one of its aliases.</param>
|
||||
/// <returns></returns>
|
||||
public MusicInfo? GetMusicInfo(string searchQuery)
|
||||
{
|
||||
return FirstOrDefault(kvp => kvp.Key.Contains(searchQuery, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
kvp.Value.Aliases.Any(alias => alias.Contains(searchQuery, StringComparison.InvariantCultureIgnoreCase))
|
||||
).Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of music info that match the search query. Returns null if an error occurred, or empty list if no match was found.
|
||||
/// </summary>
|
||||
/// <param name="searchQuery">The search query</param>
|
||||
/// <returns>null if an error occured, otherwise a list with songs that match the search query. If no song match the list is empty</returns>
|
||||
public List<MusicInfo>? GetMusicInfoList(string searchQuery)
|
||||
{
|
||||
try
|
||||
{
|
||||
return this.Where(kvp =>
|
||||
kvp.Key.Contains(searchQuery, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
kvp.Value.Aliases.Any(alias => alias.Contains(searchQuery, StringComparison.InvariantCultureIgnoreCase))
|
||||
)
|
||||
.Select(item => item.Value).ToList();
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new entry to the database based on the music info. It uses the title as the key.
|
||||
/// </summary>
|
||||
/// <param name="musicInfo">The music to add to</param>
|
||||
public void AddNewEntryBasedOnMusicInfo(MusicInfo musicInfo)
|
||||
{
|
||||
Add(musicInfo.Title, musicInfo);
|
||||
}
|
||||
}
|
||||
10
Plugins/MusicPlayer/MusicInfo.cs
Normal file
10
Plugins/MusicPlayer/MusicInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace MusicPlayer;
|
||||
|
||||
public class MusicInfo
|
||||
{
|
||||
public string Title { get; init; }
|
||||
public string? Description { get; init; }
|
||||
public string Location { get; init; }
|
||||
public List<string>? Aliases { get; init; }
|
||||
public int? ByteSize { get; init; } = 1024;
|
||||
}
|
||||
53
Plugins/MusicPlayer/MusicInfoExtensions.cs
Normal file
53
Plugins/MusicPlayer/MusicInfoExtensions.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Discord;
|
||||
|
||||
namespace MusicPlayer;
|
||||
|
||||
public static class MusicInfoExtensions
|
||||
{
|
||||
public static void AddAlias(this MusicInfo musicInfo, string alias)
|
||||
{
|
||||
musicInfo.Aliases.Add(alias);
|
||||
}
|
||||
|
||||
public static void RemoveAlias(this MusicInfo musicInfo, string alias)
|
||||
{
|
||||
musicInfo.Aliases.Remove(alias);
|
||||
}
|
||||
|
||||
public static MusicInfo CreateMusicInfo(string title, string fileLocation, string? Description = "Unknown", List<string>? aliases = null, int? byteSize = 1024)
|
||||
{
|
||||
return new MusicInfo()
|
||||
{
|
||||
Title = title,
|
||||
Aliases = aliases,
|
||||
Description = Description,
|
||||
Location = fileLocation,
|
||||
ByteSize = byteSize
|
||||
};
|
||||
}
|
||||
|
||||
public static Embed ToEmbed(this MusicInfo musicInfo, Color? embedColor = null)
|
||||
{
|
||||
var builder = new EmbedBuilder();
|
||||
builder.Color = embedColor ?? Color.Default;
|
||||
builder.WithTitle(musicInfo.Title);
|
||||
builder.WithDescription(musicInfo.Description);
|
||||
if (musicInfo.Aliases != null)
|
||||
builder.AddField("Aliases", string.Join(", ", musicInfo.Aliases));
|
||||
else
|
||||
builder.AddField("Aliases", "None");
|
||||
builder.AddField("Location", musicInfo.Location);
|
||||
builder.AddField("ByteSize", musicInfo.ByteSize);
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
public static Embed ToEmbed(this List<MusicInfo> musicInfo, Color? embedColor = null)
|
||||
{
|
||||
var builder = new EmbedBuilder();
|
||||
builder.Color = embedColor ?? Color.Default;
|
||||
builder.WithTitle("Search results");
|
||||
builder.WithDescription("Found " + musicInfo.Count + " results");
|
||||
builder.AddField("Results", string.Join("\n", musicInfo.Select(item => item.Title)));
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
175
Plugins/MusicPlayer/MusicPlayer.cs
Normal file
175
Plugins/MusicPlayer/MusicPlayer.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using System.Diagnostics;
|
||||
using Discord.Audio;
|
||||
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace MusicPlayer;
|
||||
|
||||
public class MusicPlayer
|
||||
{
|
||||
private static int defaultByteSize = 1024;
|
||||
|
||||
public Queue<MusicInfo> MusicQueue { get; private set; }
|
||||
|
||||
public bool isPaused { get; private set; }
|
||||
public bool isPlaying { get; private set; }
|
||||
|
||||
private bool isQueueRunning;
|
||||
public int ByteSize { get; private set; }
|
||||
|
||||
public MusicInfo? CurrentlyPlaying { get; private set; }
|
||||
|
||||
public MusicPlayer()
|
||||
{
|
||||
MusicQueue = new Queue<MusicInfo>();
|
||||
}
|
||||
|
||||
public async Task PlayQueue()
|
||||
{
|
||||
if (isQueueRunning)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Another queue is running !", typeof(MusicPlayer), LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Variables.audioClient is null)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("Audio Client is null", typeof(MusicPlayer), LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
isQueueRunning = true;
|
||||
|
||||
string? ffmpegPath = await Application.CurrentApplication.PluginManager.GetDependencyLocation("FFMPEG");
|
||||
if(ffmpegPath is null)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log("FFMPEG is missing. Please install it and try again.", typeof(MusicPlayer), LogType.Error);
|
||||
isQueueRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ffmpegPath = ffmpegPath.Replace("\\", "/");
|
||||
ffmpegPath = Path.GetFullPath(ffmpegPath);
|
||||
|
||||
Console.WriteLine("FFMPEG Path: " + ffmpegPath);
|
||||
|
||||
while (MusicQueue.TryDequeue(out var dequeuedMusic))
|
||||
{
|
||||
CurrentlyPlaying = dequeuedMusic;
|
||||
await using var dsAudioStream = Variables.audioClient.CreatePCMStream(AudioApplication.Mixed);
|
||||
using var ffmpeg = CreateStream(ffmpegPath, CurrentlyPlaying.Location);
|
||||
if (ffmpeg is null)
|
||||
{
|
||||
Application.CurrentApplication.Logger.Log($"Failed to start ffmpeg process. FFMPEG is missing or the {CurrentlyPlaying.Location} has an invalid format.", typeof(MusicPlayer), LogType.Error);
|
||||
continue;
|
||||
}
|
||||
await using var ffmpegOut = ffmpeg.StandardOutput.BaseStream;
|
||||
await PlayCurrentTrack(dsAudioStream, ffmpegOut, CurrentlyPlaying.ByteSize ?? defaultByteSize);
|
||||
}
|
||||
isQueueRunning = false;
|
||||
CurrentlyPlaying = null;
|
||||
}
|
||||
|
||||
public void Loop(int numberOfTimes)
|
||||
{
|
||||
if (CurrentlyPlaying is null) return;
|
||||
|
||||
Queue<MusicInfo> tempQueue = new();
|
||||
for (var i = 0; i < numberOfTimes; i++)
|
||||
{
|
||||
tempQueue.Enqueue(CurrentlyPlaying);
|
||||
}
|
||||
|
||||
foreach (var musicInfo in MusicQueue)
|
||||
{
|
||||
tempQueue.Enqueue(musicInfo);
|
||||
}
|
||||
|
||||
MusicQueue = tempQueue;
|
||||
}
|
||||
|
||||
private async Task PlayCurrentTrack(Stream discordVoiceChannelStream, Stream fileStreamFfmpeg, int byteSize)
|
||||
{
|
||||
if (isPlaying) return;
|
||||
ByteSize = byteSize;
|
||||
|
||||
isPlaying = true;
|
||||
isPaused = false;
|
||||
|
||||
while (isPlaying)
|
||||
{
|
||||
if (isPaused) continue;
|
||||
|
||||
var bits = new byte[byteSize];
|
||||
var read = await fileStreamFfmpeg.ReadAsync(bits, 0, ByteSize);
|
||||
if (read == 0) break;
|
||||
|
||||
try
|
||||
{
|
||||
await discordVoiceChannelStream.WriteAsync(bits, 0, read);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Application.CurrentApplication.Logger.LogException(ex, this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
await discordVoiceChannelStream.FlushAsync();
|
||||
await fileStreamFfmpeg.FlushAsync();
|
||||
|
||||
isPlaying = false;
|
||||
isPaused = false;
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
isPaused = true;
|
||||
}
|
||||
|
||||
public void Unpause()
|
||||
{
|
||||
isPaused = false;
|
||||
}
|
||||
|
||||
public bool Enqueue(string musicName)
|
||||
{
|
||||
var minfo = Variables._MusicDatabase.GetMusicInfo(musicName);
|
||||
if (minfo is null) return false;
|
||||
|
||||
MusicQueue.Enqueue(minfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Skip()
|
||||
{
|
||||
isPlaying = false;
|
||||
}
|
||||
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
// set volume
|
||||
}
|
||||
|
||||
private static Process? CreateStream(string? fileName, string path)
|
||||
{
|
||||
return Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = $"-hide_banner -loglevel panic -i \"{path}\" -ac 2 -f s16le -ar 48000 pipe:1",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
MusicQueue.Clear();
|
||||
isPlaying = false;
|
||||
}
|
||||
|
||||
}
|
||||
25
Plugins/MusicPlayer/MusicPlayer.csproj
Normal file
25
Plugins/MusicPlayer/MusicPlayer.csproj
Normal file
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\DiscordBotCore\DiscordBotCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="libs\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="libs\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="libs\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
54
Plugins/MusicPlayer/SlashCommands/Loop.cs
Normal file
54
Plugins/MusicPlayer/SlashCommands/Loop.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Interfaces;
|
||||
|
||||
namespace MusicPlayer.SlashCommands;
|
||||
|
||||
public class Loop: DBSlashCommand
|
||||
{
|
||||
|
||||
public string Name => "loop";
|
||||
public string Description => "Loop the current song for a certain amount of times. If no times are specified, it will loop once";
|
||||
public bool canUseDM => false;
|
||||
public bool HasInteraction => false;
|
||||
|
||||
public List<SlashCommandOptionBuilder> Options => new()
|
||||
{
|
||||
new()
|
||||
{
|
||||
Type = ApplicationCommandOptionType.Integer,
|
||||
Name = "times",
|
||||
Description = "How many times to loop the song",
|
||||
IsRequired = false
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public void ExecuteServer(SocketSlashCommand context)
|
||||
{
|
||||
if (Variables._MusicPlayer.CurrentlyPlaying == null)
|
||||
{
|
||||
context.RespondAsync("There is nothing playing right now");
|
||||
return;
|
||||
}
|
||||
|
||||
var times = context.Data.Options.FirstOrDefault()?.Value.ToString() ?? "1";
|
||||
|
||||
if (!int.TryParse(times, out var timesToLoop))
|
||||
{
|
||||
context.RespondAsync("Invalid number");
|
||||
return;
|
||||
}
|
||||
|
||||
if (timesToLoop < 1)
|
||||
{
|
||||
context.RespondAsync("You need to specify a number greater than 0");
|
||||
return;
|
||||
}
|
||||
|
||||
Variables._MusicPlayer.Loop(timesToLoop);
|
||||
|
||||
context.RespondAsync($"Looping {Variables._MusicPlayer.CurrentlyPlaying.Title} {timesToLoop} times. Check the queue to see the progress");
|
||||
|
||||
}
|
||||
}
|
||||
78
Plugins/MusicPlayer/SlashCommands/Play.cs
Normal file
78
Plugins/MusicPlayer/SlashCommands/Play.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
|
||||
using DiscordBotCore;
|
||||
using DiscordBotCore.Interfaces;
|
||||
using DiscordBotCore.Others;
|
||||
|
||||
namespace MusicPlayer.SlashCommands;
|
||||
|
||||
public class Play: DBSlashCommand
|
||||
{
|
||||
public string Name => "play";
|
||||
public string Description => "Play music command";
|
||||
public bool canUseDM => false;
|
||||
public bool HasInteraction => false;
|
||||
|
||||
public List<SlashCommandOptionBuilder> Options => new()
|
||||
{
|
||||
new()
|
||||
{
|
||||
IsRequired = true,
|
||||
Description = "The music name to be played",
|
||||
Name = "music-name",
|
||||
Type = ApplicationCommandOptionType.String
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
public async void ExecuteServer(SocketSlashCommand context)
|
||||
{
|
||||
var melodyName = context.Data.Options.First().Value as string;
|
||||
|
||||
if (melodyName is null)
|
||||
{
|
||||
await context.RespondAsync("Failed to retrieve melody with name " + melodyName);
|
||||
return;
|
||||
}
|
||||
|
||||
var melody = Variables._MusicDatabase.GetMusicInfo(melodyName);
|
||||
if (melody is null)
|
||||
{
|
||||
await context.RespondAsync("The searched melody does not exists in the database. Sorry :(");
|
||||
return;
|
||||
}
|
||||
|
||||
var user = context.User as IGuildUser;
|
||||
if (user is null)
|
||||
{
|
||||
await context.RespondAsync("Failed to get user data from channel ! Check error log at " + DateTime.Now.ToLongTimeString());
|
||||
Application.CurrentApplication.Logger.Log("User is null while trying to convert from context.User to IGuildUser.", typeof(Play), LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var voiceChannel = user.VoiceChannel;
|
||||
if (voiceChannel is null)
|
||||
{
|
||||
await context.RespondAsync("Unknown voice channel. Maybe I do not have permission to join it ?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Variables.audioClient is null)
|
||||
{
|
||||
Variables.audioClient = await voiceChannel.ConnectAsync(true); // self deaf
|
||||
}
|
||||
|
||||
Variables._MusicPlayer ??= new MusicPlayer();
|
||||
|
||||
if (!Variables._MusicPlayer.Enqueue(melodyName))
|
||||
{
|
||||
await context.RespondAsync("Failed to enqueue your request. Something went wrong !");
|
||||
return;
|
||||
}
|
||||
await context.RespondAsync("Enqueued your request");
|
||||
|
||||
await Variables._MusicPlayer.PlayQueue(); //start queue
|
||||
}
|
||||
}
|
||||
49
Plugins/MusicPlayer/SlashCommands/Queue.cs
Normal file
49
Plugins/MusicPlayer/SlashCommands/Queue.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Interfaces;
|
||||
|
||||
namespace MusicPlayer.SlashCommands;
|
||||
|
||||
public class Queue: DBSlashCommand
|
||||
{
|
||||
public string Name => "queue";
|
||||
public string Description => "Queue a melody to play";
|
||||
public bool canUseDM => false;
|
||||
public bool HasInteraction => false;
|
||||
public List<SlashCommandOptionBuilder> Options => null;
|
||||
|
||||
|
||||
public async void ExecuteServer(SocketSlashCommand context)
|
||||
{
|
||||
if (Variables._MusicPlayer is null)
|
||||
{
|
||||
await context.RespondAsync("No music is currently playing.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Variables._MusicPlayer.MusicQueue.Count == 0 && Variables._MusicPlayer.CurrentlyPlaying == null)
|
||||
{
|
||||
await context.RespondAsync("No music is currently playing");
|
||||
return;
|
||||
}
|
||||
|
||||
var builder = new EmbedBuilder()
|
||||
{
|
||||
Title = "Music Queue",
|
||||
Description = "Here is the current music queue",
|
||||
Color = Color.Blue
|
||||
};
|
||||
|
||||
if (Variables._MusicPlayer.CurrentlyPlaying != null)
|
||||
builder.AddField("Current music", Variables._MusicPlayer.CurrentlyPlaying.Title);
|
||||
|
||||
var i = 1;
|
||||
foreach (var melody in Variables._MusicPlayer.MusicQueue)
|
||||
{
|
||||
builder.AddField($"#{i}", melody.Title);
|
||||
i++;
|
||||
}
|
||||
|
||||
await context.RespondAsync(embed: builder.Build());
|
||||
}
|
||||
}
|
||||
35
Plugins/MusicPlayer/SlashCommands/Skip.cs
Normal file
35
Plugins/MusicPlayer/SlashCommands/Skip.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using DiscordBotCore.Interfaces;
|
||||
|
||||
namespace MusicPlayer.SlashCommands;
|
||||
|
||||
public class Skip: DBSlashCommand
|
||||
{
|
||||
public string Name => "skip";
|
||||
public string Description => "Skip the current melody";
|
||||
public bool canUseDM => false;
|
||||
public bool HasInteraction => false;
|
||||
public List<SlashCommandOptionBuilder> Options => null;
|
||||
|
||||
public async void ExecuteServer(SocketSlashCommand context)
|
||||
{
|
||||
if (Variables._MusicPlayer is null)
|
||||
{
|
||||
await context.RespondAsync("No music is currently playing.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Variables._MusicPlayer.MusicQueue.Count == 0 && Variables._MusicPlayer.CurrentlyPlaying == null)
|
||||
{
|
||||
await context.RespondAsync("No music is currently playing");
|
||||
return;
|
||||
}
|
||||
|
||||
var melodyTitle = Variables._MusicPlayer.CurrentlyPlaying.Title;
|
||||
|
||||
await context.RespondAsync($"Skipping {melodyTitle} ...");
|
||||
Variables._MusicPlayer.Skip();
|
||||
await context.ModifyOriginalResponseAsync(x => x.Content = $"Skipped {melodyTitle}");
|
||||
}
|
||||
}
|
||||
11
Plugins/MusicPlayer/Variables.cs
Normal file
11
Plugins/MusicPlayer/Variables.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Discord.Audio;
|
||||
|
||||
namespace MusicPlayer;
|
||||
|
||||
public class Variables
|
||||
{
|
||||
public static MusicDatabase? _MusicDatabase;
|
||||
public static MusicPlayer? _MusicPlayer;
|
||||
|
||||
public static IAudioClient? audioClient;
|
||||
}
|
||||
42
Plugins/MusicPlayer/YoutubeDLP.cs
Normal file
42
Plugins/MusicPlayer/YoutubeDLP.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace MusicPlayer;
|
||||
|
||||
public class YoutubeDLP
|
||||
{
|
||||
public static async Task<string?> DownloadMelody(string url)
|
||||
{
|
||||
Console.WriteLine("Downloading melody: " + url);
|
||||
var process = new Process();
|
||||
process.StartInfo.FileName = await DiscordBotCore.Application.CurrentApplication.PluginManager.GetDependencyLocation("yt-dlp");
|
||||
process.StartInfo.Arguments = $"-x --force-overwrites -o \"{DiscordBotCore.Application.GetResourceFullPath("/Music/Melodies")}/%(title)s.%(ext)s\" --audio-format mp3 {url}";
|
||||
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
var title = "";
|
||||
process.OutputDataReceived += (sender, args) =>
|
||||
{
|
||||
if (args.Data != null)
|
||||
{
|
||||
if (args.Data.StartsWith("[ExtractAudio] Destination: "))
|
||||
{
|
||||
title = args.Data.Replace("[ExtractAudio] Destination: ", "").Replace(".mp3", "");
|
||||
title = title.Replace("\\", "/");
|
||||
title = title.Split('/').Last().Replace(".mp3", "").TrimEnd();
|
||||
|
||||
Console.WriteLine("Output title: " + title);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine(args.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
Console.WriteLine("Waiting for process to exit ...");
|
||||
process.BeginOutputReadLine();
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
return title ?? null;
|
||||
}
|
||||
}
|
||||
BIN
Plugins/MusicPlayer/libs/linux/ffmpeg
Normal file
BIN
Plugins/MusicPlayer/libs/linux/ffmpeg
Normal file
Binary file not shown.
BIN
Plugins/MusicPlayer/libs/linux/libopus.so
Normal file
BIN
Plugins/MusicPlayer/libs/linux/libopus.so
Normal file
Binary file not shown.
BIN
Plugins/MusicPlayer/libs/linux/libsodium.so
Normal file
BIN
Plugins/MusicPlayer/libs/linux/libsodium.so
Normal file
Binary file not shown.
BIN
Plugins/MusicPlayer/libs/windows/ffmpeg.exe
Normal file
BIN
Plugins/MusicPlayer/libs/windows/ffmpeg.exe
Normal file
Binary file not shown.
BIN
Plugins/MusicPlayer/libs/windows/libopus.dll
Normal file
BIN
Plugins/MusicPlayer/libs/windows/libopus.dll
Normal file
Binary file not shown.
BIN
Plugins/MusicPlayer/libs/windows/libsodium.dll
Normal file
BIN
Plugins/MusicPlayer/libs/windows/libsodium.dll
Normal file
Binary file not shown.
BIN
Plugins/MusicPlayer/libs/windows/opus.dll
Normal file
BIN
Plugins/MusicPlayer/libs/windows/opus.dll
Normal file
Binary file not shown.
@@ -6,6 +6,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordBot", "DiscordBot\Di
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordBotCore", "DiscordBotCore\DiscordBotCore.csproj", "{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{5CF9AD7B-6BF0-4035-835F-722F989C01E1}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{EA4FA308-7B2C-458E-8485-8747D745DD59}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CppWrapper", "Plugins\CppWrapper\CppWrapper.csproj", "{B21FC858-C397-4B0B-BFED-218EFA28E3E1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotUI", "Plugins\DiscordBotUI\DiscordBotUI.csproj", "{E1A93D4E-A541-44B1-9DD1-FEF9B503CFD6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LevelingSystem", "Plugins\LevelingSystem\LevelingSystem.csproj", "{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MusicPlayer", "Plugins\MusicPlayer\MusicPlayer.csproj", "{F3C61A47-F758-4145-B496-E3ECCF979D89}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerModule", "Modules\LoggerModule\LoggerModule.csproj", "{367F3197-8B9E-4BDC-A6DE-226E721F9ED1}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -20,6 +34,26 @@ Global
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B21FC858-C397-4B0B-BFED-218EFA28E3E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B21FC858-C397-4B0B-BFED-218EFA28E3E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B21FC858-C397-4B0B-BFED-218EFA28E3E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B21FC858-C397-4B0B-BFED-218EFA28E3E1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E1A93D4E-A541-44B1-9DD1-FEF9B503CFD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E1A93D4E-A541-44B1-9DD1-FEF9B503CFD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E1A93D4E-A541-44B1-9DD1-FEF9B503CFD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E1A93D4E-A541-44B1-9DD1-FEF9B503CFD6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F3C61A47-F758-4145-B496-E3ECCF979D89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F3C61A47-F758-4145-B496-E3ECCF979D89}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F3C61A47-F758-4145-B496-E3ECCF979D89}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F3C61A47-F758-4145-B496-E3ECCF979D89}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{367F3197-8B9E-4BDC-A6DE-226E721F9ED1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{367F3197-8B9E-4BDC-A6DE-226E721F9ED1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{367F3197-8B9E-4BDC-A6DE-226E721F9ED1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{367F3197-8B9E-4BDC-A6DE-226E721F9ED1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -27,4 +61,11 @@ Global
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3FB3C5DE-ED21-4D2E-ABDD-3A00EE4A2FFF}
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{B21FC858-C397-4B0B-BFED-218EFA28E3E1} = {5CF9AD7B-6BF0-4035-835F-722F989C01E1}
|
||||
{E1A93D4E-A541-44B1-9DD1-FEF9B503CFD6} = {5CF9AD7B-6BF0-4035-835F-722F989C01E1}
|
||||
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689} = {5CF9AD7B-6BF0-4035-835F-722F989C01E1}
|
||||
{F3C61A47-F758-4145-B496-E3ECCF979D89} = {5CF9AD7B-6BF0-4035-835F-722F989C01E1}
|
||||
{367F3197-8B9E-4BDC-A6DE-226E721F9ED1} = {EA4FA308-7B2C-458E-8485-8747D745DD59}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
Reference in New Issue
Block a user