518 lines
30 KiB
C#
518 lines
30 KiB
C#
// Project Makoto
|
|
// Copyright (C) 2024 Fortunevale
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY
|
|
|
|
using System.Collections.Immutable;
|
|
using System.Runtime.Loader;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
|
|
namespace ProjectMakoto.Util.Initializers;
|
|
internal static class CommandCompiler
|
|
{
|
|
internal static List<PortableExecutableReference> AssemblyReferences = new();
|
|
|
|
internal static async Task<(Assembly compiledCommands, CompilationType Type)[]> BuildCommands(Bot bot, string applicationHash, IEnumerable<MakotoModule> moduleList, KeyValuePair<string, BasePlugin>? plugin = null)
|
|
{
|
|
var isPlugin = plugin != null;
|
|
plugin ??= new KeyValuePair<string, BasePlugin>("Built-In", null);
|
|
List<(Assembly compiledCommands, CompilationType Type)> assemblyList = new();
|
|
|
|
string currentHash;
|
|
|
|
if (plugin.Value.Key != "Built-In")
|
|
currentHash = HashingExtensions.ComputeSHA256Hash(plugin.Value.Value.LoadedFile);
|
|
else
|
|
currentHash = applicationHash;
|
|
|
|
if (bot.status.LoadedConfig.CommandCache.TryGetValue(plugin.Value.Key, out var supplierInfo) &&
|
|
currentHash == supplierInfo.LastKnownHash &&
|
|
applicationHash == bot.status.LoadedConfig.DontModify.LastKnownHash &&
|
|
supplierInfo.CompiledCommands.All(x => File.Exists(x.Key)) &&
|
|
supplierInfo.CompiledCommands.Count != 0)
|
|
{
|
|
if (isPlugin)
|
|
Log.Information("Loading {0} Commands from Plugin from '{1}' ({2}) from compiled assemblies..",
|
|
supplierInfo.CompiledCommands.Count,
|
|
plugin.Value.Value.Name,
|
|
plugin.Value.Value.Version.ToString());
|
|
else
|
|
Log.Information("Loading {0} Commands from compiled assemblies..",
|
|
supplierInfo.CompiledCommands.Count);
|
|
|
|
foreach (var b in supplierInfo.CompiledCommands)
|
|
{
|
|
AssemblyLoadContext loadContext = new(null);
|
|
using var file = new FileStream(b.Key, FileMode.Open, FileAccess.Read);
|
|
var assembly = loadContext.LoadFromStream(file);
|
|
|
|
assemblyList.Add((assembly, b.Value));
|
|
}
|
|
|
|
return assemblyList.ToArray();
|
|
}
|
|
|
|
_ = bot.status.LoadedConfig.CommandCache.TryAdd(plugin.Value.Key, new());
|
|
supplierInfo = bot.status.LoadedConfig.CommandCache[plugin.Value.Key];
|
|
supplierInfo.LastKnownHash = currentHash;
|
|
|
|
if (supplierInfo.CompiledCommands.Count != 0)
|
|
_ = FileExtensions.CleanupFilesAndDirectories(new(), supplierInfo.CompiledCommands.Select(x => x.Key).ToList());
|
|
|
|
supplierInfo.CompiledCommands = new();
|
|
|
|
(string Code, CompilationType Type, string ModuleName)[][] getClassCode()
|
|
{
|
|
var classHeader = GetFileHeader();
|
|
|
|
string createCodeWithDefaultClass(IEnumerable<string> code, MakotoCommandType supportedType, int? Priority)
|
|
{
|
|
var inheritType = supportedType switch
|
|
{
|
|
MakotoCommandType.SlashCommand or MakotoCommandType.ContextMenu => typeof(ApplicationCommandsModule),
|
|
MakotoCommandType.PrefixCommand => typeof(BaseCommandModule),
|
|
_ => throw new NotImplementedException()
|
|
};
|
|
|
|
return $$"""
|
|
{{classHeader}}
|
|
|
|
{{(Priority is not null ? $"[{typeof(ModulePriorityAttribute).FullName}({Priority})]" : "")}}
|
|
public sealed class {{GetUniqueCodeCompatibleName()}} : {{inheritType.FullName}}
|
|
{
|
|
public {{typeof(Bot).FullName}} _bot { private get; set; }
|
|
|
|
{{string.Join("\n\n", code)}}
|
|
}
|
|
""";
|
|
}
|
|
|
|
(string Code, CompilationType Type, string ModuleName)[] getModuleDefinition(MakotoModule module)
|
|
{
|
|
module.Registered = true;
|
|
|
|
var rawSlashCommandList = module.Commands
|
|
.Where(x => x.SupportedCommandTypes.Contains(MakotoCommandType.SlashCommand))
|
|
.Select(x => getMethodDefinition(x, module, null, MakotoCommandType.SlashCommand));
|
|
|
|
var rawPrefixCommandList = module.Commands
|
|
.Where(x => x.SupportedCommandTypes.Contains(MakotoCommandType.PrefixCommand))
|
|
.Select(x => getMethodDefinition(x, module, null, MakotoCommandType.PrefixCommand));
|
|
|
|
var rawContextCommandList = module.Commands
|
|
.Where(x => x.SupportedCommandTypes.Contains(MakotoCommandType.ContextMenu))
|
|
.Select(x => getMethodDefinition(x, module, null, MakotoCommandType.ContextMenu));
|
|
|
|
var rawCodeList = new List<(string Code, CompilationType Type, string ModuleName)>();
|
|
|
|
if (rawSlashCommandList.Any())
|
|
rawCodeList.Add((createCodeWithDefaultClass(rawSlashCommandList, MakotoCommandType.SlashCommand, module.Priority), CompilationType.App, module.Name));
|
|
|
|
if (rawContextCommandList.Any())
|
|
rawCodeList.Add((createCodeWithDefaultClass(rawContextCommandList, MakotoCommandType.ContextMenu, module.Priority), CompilationType.App, module.Name));
|
|
|
|
if (rawPrefixCommandList.Any())
|
|
rawCodeList.Add((createCodeWithDefaultClass(rawPrefixCommandList, MakotoCommandType.PrefixCommand, module.Priority), CompilationType.Prefix, module.Name));
|
|
|
|
return rawCodeList.ToArray();
|
|
}
|
|
|
|
string getMethodDefinition(MakotoCommand command, MakotoModule module, MakotoCommand? parent, MakotoCommandType supportedType)
|
|
{
|
|
command.Registered = true;
|
|
var TaskName = GetUniqueCodeCompatibleName();
|
|
|
|
if (!command.SupportedCommandTypes.Contains(supportedType))
|
|
return string.Empty;
|
|
|
|
string getAttribute()
|
|
{
|
|
switch (supportedType)
|
|
{
|
|
case MakotoCommandType.SlashCommand:
|
|
if (command.IsGroup)
|
|
return $$"""
|
|
[{{typeof(SlashCommandGroupAttribute).FullName}}("{{command.Name}}", "{{command.Description}}"{{(command.RequiredPermissions is null ? "" : $", {(long)command.RequiredPermissions}")}}, {{(command.AllowPrivateUsage ? string.Empty : "allowedContexts: [InteractionContextType.Guild]")}}, isNsfw: {{command.IsNsfw.ToString().ToLower()}})]
|
|
""";
|
|
else
|
|
return $$"""
|
|
[{{typeof(SlashCommandAttribute).FullName}}("{{command.Name}}", "{{command.Description}}"{{(command.RequiredPermissions is null ? "" : $", {(long)command.RequiredPermissions}")}}, {{(command.AllowPrivateUsage ? string.Empty : "allowedContexts: [InteractionContextType.Guild]")}}, isNsfw: {{command.IsNsfw.ToString().ToLower()}})]
|
|
""";
|
|
case MakotoCommandType.PrefixCommand:
|
|
if (command.IsGroup)
|
|
return $$"""
|
|
[{{typeof(GroupAttribute).FullName}}("{{command.Name}}"), {{typeof(DescriptionAttribute).FullName}}("{{command.Description}}")
|
|
{{(command.Aliases.IsNotNullAndNotEmpty() ? $", {typeof(AliasesAttribute).FullName}({string.Join(", ", command.Aliases.Select(x => $"\"{x}\""))})" : "")}}]
|
|
""";
|
|
else
|
|
return $$"""
|
|
[{{typeof(CommandAttribute).FullName}}("{{command.AlternativeName ?? command.Name}}"), {{typeof(DescriptionAttribute).FullName}}("{{command.Description}}")
|
|
{{(command.Aliases.IsNotNullAndNotEmpty() ? $", {typeof(AliasesAttribute).FullName}({string.Join(", ", command.Aliases.Select(x => $"\"{x}\""))})" : "")}}]
|
|
""";
|
|
case MakotoCommandType.ContextMenu:
|
|
return $$"""
|
|
[{{typeof(ContextMenuAttribute).FullName}}({{typeof(ApplicationCommandType).FullName}}.{{Enum.GetName(typeof(ApplicationCommandType), command.ContextMenuType)}}, "{{command.Name}}", {{(command.AllowPrivateUsage ? string.Empty : "allowedContexts: [InteractionContextType.Guild]")}}, isNsfw: {{command.IsNsfw.ToString().ToLower()}})]
|
|
""";
|
|
|
|
default:
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
string getPopulationMethods()
|
|
{
|
|
var contextType = supportedType switch
|
|
{
|
|
MakotoCommandType.SlashCommand => typeof(InteractionContext),
|
|
MakotoCommandType.PrefixCommand => typeof(CommandContext),
|
|
MakotoCommandType.ContextMenu => typeof(ContextMenuContext),
|
|
_ => throw new NotImplementedException(),
|
|
};
|
|
|
|
return isPlugin ? $$"""
|
|
private static {{typeof(Type).FullName}} {{TaskName}}_CommandType { get; set; }
|
|
private static {{typeof(MethodInfo).FullName}} {{TaskName}}_CommandMethod { get; set; }
|
|
public static void Populate_{{TaskName}}({{typeof(Bot).FullName}} _bot)
|
|
{
|
|
Log.Debug("Populating execution properties for '{CommandName}':'{taskname}'", "{{command.Name}}","{{TaskName}}");
|
|
{{(parent is null ? $"{TaskName}_CommandType = _bot.PluginCommandModules[\"{plugin.Value.Key}\"].First(x => x.Name == \"{module.Name}\").Commands.First(x => x.Name == \"{command.Name}\").Command;" : $"{TaskName}_CommandType = _bot.PluginCommandModules[\"{plugin.Value.Key}\"].First(x => x.Name == \"{module.Name}\").Commands.First(x => x.Name == \"{parent.Name}\").SubCommands.First(x => x.Name == \"{command.Name}\").Command;")}}
|
|
{{TaskName}}_CommandMethod = {{TaskName}}_CommandType.GetMethods().First(x => x.Name == "ExecuteCommand" && x.GetParameters().Any(param => param.ParameterType == typeof({{contextType.FullName}})));
|
|
}
|
|
""" :
|
|
$$"""
|
|
private static {{typeof(Type).FullName}} {{TaskName}}_CommandType { get; set; }
|
|
private static {{typeof(MethodInfo).FullName}} {{TaskName}}_CommandMethod { get; set; }
|
|
public static void Populate_{{TaskName}}({{typeof(Bot).FullName}} _bot)
|
|
{
|
|
Log.Debug("Populating execution properties for '{CommandName}':'{taskname}' ({module}, {command})", "{{command.Name}}","{{TaskName}}","{{module.Name}}","{{command.Name}}");
|
|
{{(parent is null ? $"{TaskName}_CommandType = _bot.CommandModules.First(x => x.Name == \"{module.Name}\").Commands.First(x => x.Name == \"{command.Name}\").Command;" : $"{TaskName}_CommandType = _bot.CommandModules.First(x => x.Name == \"{module.Name}\").Commands.First(x => x.Name == \"{parent.Name}\").SubCommands.First(x => x.Name == \"{command.Name}\").Command;")}}
|
|
{{TaskName}}_CommandMethod = {{TaskName}}_CommandType.GetMethods().First(x => x.Name == "ExecuteCommand" && x.GetParameters().Any(param => param.ParameterType == typeof({{contextType.FullName}})));
|
|
}
|
|
""";
|
|
}
|
|
|
|
switch (supportedType)
|
|
{
|
|
case MakotoCommandType.SlashCommand:
|
|
if (command.IsGroup)
|
|
return $$"""
|
|
{{getAttribute()}}
|
|
public sealed class {{GetUniqueCodeCompatibleName()}} : {{typeof(ApplicationCommandsModule).FullName}}
|
|
{
|
|
public {{typeof(Bot).FullName}} _bot { private get; set; }
|
|
|
|
{{string.Join("\n\n", command.SubCommands.Select(x => $$"""
|
|
{{getMethodDefinition(x, module, command, supportedType)}}
|
|
"""))}}
|
|
}
|
|
""";
|
|
else
|
|
return $$"""
|
|
{{getAttribute()}}
|
|
public {{typeof(Task).FullName}} {{TaskName}}_Execute({{typeof(InteractionContext).FullName}} ctx{{(command.Overloads?.Length > 0 ? ", " : "")}}
|
|
{{string.Join(", ", command.Overloads?.Select(x => $"[{typeof(OptionAttribute).FullName}(\"{x.Name}\", \"{x.Description}\", {(x.AutoCompleteType != null).ToString().ToLower()})" +
|
|
$"{(x.ChannelType is not null ? $", {typeof(ChannelTypesAttribute).FullName}(({typeof(ChannelType).FullName}){(int)x.ChannelType})" : "")}" +
|
|
$"{(x.MinimumValue is not null ? $", {typeof(MinimumValueAttribute).FullName}({x.MinimumValue})" : "")}" +
|
|
$"{(x.MaximumValue is not null ? $", {typeof(MaximumValueAttribute).FullName}({x.MaximumValue})" : "")}" +
|
|
$"{(x.AutoCompleteType is not null ? $", {typeof(AutocompleteAttribute).FullName}(typeof({x.AutoCompleteType.FullName
|
|
.Replace('+', '.')}))" : "")}" +
|
|
$"] {x.Type.Name}{(x.Required ? "" : "?")} {x.Name} {(x.Required ? "" : " = null")}"))}})
|
|
{
|
|
try
|
|
{
|
|
{{typeof(Task).FullName}} t = ({{typeof(Task).FullName}}){{TaskName}}_CommandMethod.Invoke({{typeof(Activator).FullName}}.CreateInstance({{TaskName}}_CommandType),
|
|
new {{typeof(object[]).FullName}}
|
|
{ ctx, _bot, new Dictionary<string, object>
|
|
{
|
|
{{string.Join(",\n", command.Overloads?.Select(x => $"{{ \"{x.Name}\", {x.Name} }}"))}}
|
|
}, {{command.IsEphemeral.ToString().ToLower()}}, true, false
|
|
});
|
|
|
|
t.Add(_bot, ctx);
|
|
}
|
|
catch ({{typeof(Exception).FullName}} ex)
|
|
{
|
|
Log.Error(ex, $"Failed to execute plugin's application command");
|
|
}
|
|
|
|
return {{typeof(Task).FullName}}.CompletedTask;
|
|
}
|
|
|
|
{{getPopulationMethods()}}
|
|
""";
|
|
case MakotoCommandType.PrefixCommand:
|
|
if (command.IsGroup)
|
|
return $$"""
|
|
{{getAttribute()}}
|
|
public sealed class {{GetUniqueCodeCompatibleName()}} : {{typeof(BaseCommandModule).FullName}}
|
|
{
|
|
public {{typeof(Bot).FullName}} _bot { private get; set; }
|
|
|
|
{{(command.UseDefaultHelp ? $$"""
|
|
|
|
[{{typeof(GroupCommandAttribute).FullName}}, {{typeof(CommandAttribute).FullName}}("help"), {{typeof(DescriptionAttribute).FullName}}("Sends a list of available sub-commands")]
|
|
public async {{typeof(Task).FullName}} Help({{typeof(CommandContext).FullName}} ctx)
|
|
=> {{typeof(PrefixCommandUtil).FullName}}.SendGroupHelp(_bot, ctx, "{{command.Name}}").Add(_bot, ctx);
|
|
""" : "")}}
|
|
|
|
{{string.Join("\n\n", command.SubCommands.Select(x => $$"""
|
|
{{getMethodDefinition(x, module, command, supportedType)}}
|
|
"""))}}
|
|
}
|
|
""";
|
|
else
|
|
return $$"""
|
|
{{getAttribute()}}
|
|
public {{typeof(Task).FullName}} {{TaskName}}_Execute({{typeof(CommandContext).FullName}} ctx{{(command.Overloads?.Length > 0 ? ", " : "")}}{{string.Join(", ", command.Overloads?.Select(x => $"{(x.UseRemainingString ? $"[{typeof(RemainingTextAttribute).FullName}]" : "")} [{typeof(DescriptionAttribute).FullName}(\"{x.Description}\")] {x.Type.Name}{(x.Required ? "" : "?")} {x.Name} {(x.Required ? "" : " = null")}") ?? [])}})
|
|
{
|
|
try
|
|
{
|
|
{{typeof(Task).FullName}} t = ({{typeof(Task).FullName}}){{TaskName}}_CommandMethod.Invoke({{typeof(Activator).FullName}}.CreateInstance({{TaskName}}_CommandType),
|
|
new {{typeof(object[]).FullName}}
|
|
{ ctx, _bot, new Dictionary<string, object>
|
|
{
|
|
{{string.Join(",\n", command.Overloads?.Select(x => $"{{ \"{x.Name}\", {x.Name} }}") ?? [])}}
|
|
}
|
|
});
|
|
|
|
t.Add(_bot, ctx);
|
|
}
|
|
catch ({{typeof(Exception).FullName}} ex)
|
|
{
|
|
Log.Error(ex, $"Failed to execute plugin's command");
|
|
}
|
|
|
|
return {{typeof(Task).FullName}}.CompletedTask;
|
|
}
|
|
|
|
{{getPopulationMethods()}}
|
|
""";
|
|
case MakotoCommandType.ContextMenu:
|
|
return $$"""
|
|
{{(!command.AlternativeName.IsNullOrWhiteSpace() ? $"[{typeof(PrefixCommandAlternativeAttribute).FullName}(\"{command.AlternativeName}\")]" : "")}}
|
|
{{getAttribute()}}
|
|
public {{typeof(Task).FullName}} {{TaskName}}_Execute({{typeof(ContextMenuContext).FullName}} ctx)
|
|
{
|
|
try
|
|
{
|
|
{{typeof(Task).FullName}} t = ({{typeof(Task).FullName}}){{TaskName}}_CommandMethod.Invoke({{typeof(Activator).FullName}}.CreateInstance({{TaskName}}_CommandType),
|
|
new {{typeof(object[]).FullName}}
|
|
{ ctx, _bot, new Dictionary<string, object>
|
|
{
|
|
{{(command.ContextMenuType == ApplicationCommandType.Message ? "{ \"message\", ctx.TargetMessage }" : "{ \"user\", ctx.TargetMember ?? ctx.TargetUser }")}}
|
|
}, {{command.IsEphemeral.ToString().ToLower()}}, true, false
|
|
});
|
|
|
|
t.Add(_bot, ctx);
|
|
}
|
|
catch ({{typeof(Exception).FullName}} ex)
|
|
{
|
|
Log.Error(ex, $"Failed to execute plugin's command");
|
|
}
|
|
|
|
return {{typeof(Task).FullName}}.CompletedTask;
|
|
}
|
|
|
|
{{getPopulationMethods()}}
|
|
""";
|
|
|
|
default:
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
var rawModules = moduleList.Select(x => getModuleDefinition(x)).ToArray();
|
|
|
|
return rawModules;
|
|
}
|
|
|
|
var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
|
|
#if DEBUG
|
|
.WithOptimizationLevel(OptimizationLevel.Debug)
|
|
#else
|
|
.WithOptimizationLevel(OptimizationLevel.Release)
|
|
#endif
|
|
.WithDeterministic(true);
|
|
|
|
if (isPlugin)
|
|
Log.Information("Compiling {0} Commands from Plugin from '{1}' ({2}).",
|
|
moduleList.Count(),
|
|
plugin.Value.Value.Name,
|
|
plugin.Value.Value.Version.ToString());
|
|
else
|
|
Log.Information("Compiling {0} Built-In Commands..",
|
|
moduleList.Count());
|
|
|
|
|
|
foreach (var modules in getClassCode())
|
|
{
|
|
foreach (var classCode in modules)
|
|
{
|
|
var compilation = CSharpCompilation.Create(CommandCompiler.GetUniqueCodeCompatibleName() + $"_{Regex.Replace($"{classCode.ModuleName}_{Enum.GetName(classCode.Type)}", @"[^a-zA-Z0-9_]", "")}")
|
|
.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(classCode.Code))
|
|
.AddReferences(AssemblyReferences)
|
|
.WithOptions(options);
|
|
|
|
var data = new CompilationData(classCode.Type, classCode.Code, moduleList, plugin.Value.Key);
|
|
|
|
try
|
|
{
|
|
using (var stream = new MemoryStream())
|
|
{
|
|
var result = compilation.Emit(stream);
|
|
if (!result.Success)
|
|
{
|
|
Log.Error("Failed to emit compilation\n{diagnostics}",
|
|
JsonConvert.SerializeObject(result.Diagnostics.Select(x => $"{x.Id}: {x.GetMessage()}: {x.Location}: {data.code[x.Location.SourceSpan.Start..x.Location.SourceSpan.End]}"), Formatting.Indented));
|
|
|
|
Exception exception = new();
|
|
exception.Data.Add("diagnostics", result.Diagnostics);
|
|
throw exception;
|
|
}
|
|
|
|
var assemblyBytes = stream.ToArray();
|
|
var assembly = Assembly.Load(assemblyBytes);
|
|
assemblyList.Add((assembly, data.type));
|
|
|
|
_ = Directory.CreateDirectory("CompiledCommands");
|
|
|
|
var path = $"CompiledCommands/{assembly.GetName().Name}.dll";
|
|
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.ReadWrite))
|
|
{
|
|
_ = stream.Seek(0, SeekOrigin.Begin);
|
|
await stream.CopyToAsync(fileStream);
|
|
await fileStream.FlushAsync();
|
|
|
|
supplierInfo.CompiledCommands.Add(path, data.type);
|
|
}
|
|
|
|
#if DEBUG
|
|
File.WriteAllText($"CompiledCommands/{assembly.GetName().Name}.cs", classCode.Code);
|
|
#endif
|
|
|
|
Log.Debug("Compiled class with {cmdCount} commands for '{plugin}' of type '{type}'", data.moduleList.Sum(x => x.Commands.Count()), data.Identifier, data.type);
|
|
Log.Verbose($"\n{data.code}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ImmutableArray<Diagnostic>? diagnostics = null;
|
|
|
|
try
|
|
{
|
|
diagnostics = (ImmutableArray<Diagnostic>)ex.Data["diagnostics"];
|
|
}
|
|
catch { }
|
|
|
|
Log.Error(ex, "Failed Compilation of class type '{type}'", data.type);
|
|
Log.Verbose($"\n{data.code}");
|
|
|
|
await Task.Delay(1000);
|
|
|
|
if (diagnostics.HasValue)
|
|
{
|
|
Console.WriteLine();
|
|
for (var i = 0; i < data.code.Length; i++)
|
|
{
|
|
var foundDiagnostic = diagnostics.Value.FirstOrDefault(x => i >= x.Location.SourceSpan.Start && i <= x.Location.SourceSpan.End, null);
|
|
|
|
if (foundDiagnostic is not null)
|
|
switch (foundDiagnostic.Severity)
|
|
{
|
|
case DiagnosticSeverity.Hidden:
|
|
Console.ForegroundColor = ConsoleColor.Gray;
|
|
break;
|
|
case DiagnosticSeverity.Info:
|
|
Console.ForegroundColor = ConsoleColor.Cyan;
|
|
break;
|
|
case DiagnosticSeverity.Warning:
|
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
|
break;
|
|
case DiagnosticSeverity.Error:
|
|
Console.ForegroundColor = ConsoleColor.Red;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
else
|
|
Console.ForegroundColor = ConsoleColor.White;
|
|
|
|
Console.Write(data.code[i]);
|
|
}
|
|
Console.WriteLine();
|
|
}
|
|
|
|
#if DEBUG
|
|
File.WriteAllText($"CompiledCommands/failed.cs", classCode.Code);
|
|
#endif
|
|
|
|
_ = Console.ReadLine();
|
|
}
|
|
}
|
|
}
|
|
|
|
return assemblyList.ToArray();
|
|
}
|
|
|
|
internal static void RegisterAssemblies(Bot _bot, IReadOnlyDictionary<int, CommandsNextExtension> cNext, IReadOnlyDictionary<int, ApplicationCommandsExtension> appCommands, Action<ApplicationCommandsTranslationContext> translationContext, IEnumerable<(Assembly compiledAssembly, CompilationType type)> assemblyList)
|
|
{
|
|
foreach (var (compiledAssembly, type) in assemblyList)
|
|
{
|
|
foreach (var parentType in compiledAssembly.GetTypes())
|
|
{
|
|
foreach (var method in parentType.GetMethods())
|
|
{
|
|
if (method.Name.StartsWith("Populate"))
|
|
_ = method.Invoke(null, new object[] { _bot });
|
|
}
|
|
|
|
foreach (var subType in parentType.GetNestedTypes())
|
|
{
|
|
foreach (var method in subType.GetMethods())
|
|
{
|
|
if (method.Name.StartsWith("Populate"))
|
|
_ = method.Invoke(null, new object[] { _bot });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var (compiledAssembly, type) in assemblyList)
|
|
{
|
|
switch (type)
|
|
{
|
|
case CompilationType.Prefix:
|
|
cNext.RegisterCommands(compiledAssembly.GetTypes().First(x => x.BaseType == typeof(BaseCommandModule)));
|
|
break;
|
|
|
|
case CompilationType.App:
|
|
if (_bot.status.LoadedConfig.IsDev)
|
|
appCommands.RegisterGuildCommands(compiledAssembly.GetTypes().First(x => x.BaseType == typeof(ApplicationCommandsModule)), _bot.status.LoadedConfig.Discord.DevelopmentGuild, translationContext);
|
|
else
|
|
appCommands.RegisterGlobalCommands(compiledAssembly.GetTypes().First(x => x.BaseType == typeof(ApplicationCommandsModule)), translationContext);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string GetUniqueCodeCompatibleName()
|
|
=> $"a{Guid.NewGuid().ToString().ToLower().Replace("-", "")}";
|
|
|
|
private static string? FileHeaderCache { get; set; } = null;
|
|
private static string GetFileHeader()
|
|
{
|
|
FileHeaderCache ??= """
|
|
// This file was auto generated and is part of Project Makoto.
|
|
|
|
namespace ProjectMakoto;
|
|
|
|
""" + string.Join("\n", File.ReadAllText("Global.cs").ReplaceLineEndings("\n").Split("\n").Where(x => !x.StartsWith("//"))).Replace("global ", "");
|
|
|
|
return FileHeaderCache;
|
|
}
|
|
}
|