// 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 Microsoft.Extensions.Logging; namespace ProjectMakoto.Util.Initializers; internal static class DisCatSharpExtensionsLoader { static Bot bot = null; static List singleCommandTranslations = new(); static List groupCommandTranslations = new(); internal static void GetCommandTranslations(ApplicationCommandsTranslationContext x) { if (singleCommandTranslations.IsNotNullAndNotEmpty() && groupCommandTranslations.IsNotNullAndNotEmpty()) { x.AddSingleTranslation(JsonConvert.SerializeObject(singleCommandTranslations)); x.AddGroupTranslation(JsonConvert.SerializeObject(groupCommandTranslations)); return; } object CreateTranslationRecursively(Type typeToCreate, CommandTranslation translation) { try { var nameValues = translation.Names; if (nameValues is null) return null; var descriptionValues = translation.Descriptions; var typeValue = translation.Type; var optionsValues = translation.Options; var choicesValues = translation.Choices; var groupsValues = translation.Groups; var commandsValues = translation.Commands; Log.Verbose("Creating instance of '{type}'", typeToCreate.Name); var translator = Activator.CreateInstance(typeToCreate); var createTypeProperties = typeToCreate.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); createTypeProperties.First(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "name")).SetValue(translator, nameValues["en"]); if (typeToCreate == typeof(DisCatSharp.ApplicationCommands.Entities.GroupTranslator) || typeToCreate == typeof(DisCatSharp.ApplicationCommands.Entities.CommandTranslator)) createTypeProperties.First(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "type")).SetValue(translator, (ApplicationCommandType?)typeValue); if (createTypeProperties.Any(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "description"))) createTypeProperties.First(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "description")).SetValue(translator, descriptionValues["en"]); Dictionary NameTranslationDictionary = new(); foreach (var nameTranslation in nameValues ?? new()) { if (nameTranslation.Key == "en") { NameTranslationDictionary.Add("en-GB", nameTranslation.Value); NameTranslationDictionary.Add("en-US", nameTranslation.Value); continue; } NameTranslationDictionary.Add(nameTranslation.Key, nameTranslation.Value); } if (createTypeProperties.Any(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "name_translations"))) createTypeProperties.First(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "name_translations")).SetValue(translator, NameTranslationDictionary); Dictionary DescriptionTranslationDictionary = new(); foreach (var descriptionTranslations in descriptionValues ?? new()) { if (descriptionTranslations.Key == "en") { DescriptionTranslationDictionary.Add("en-GB", descriptionTranslations.Value); DescriptionTranslationDictionary.Add("en-US", descriptionTranslations.Value); continue; } DescriptionTranslationDictionary.Add(descriptionTranslations.Key, descriptionTranslations.Value); } if (createTypeProperties.Any(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "description_translations"))) createTypeProperties.First(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "description_translations")).SetValue(translator, DescriptionTranslationDictionary); if (commandsValues is not null && createTypeProperties.Any(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "commands"))) { Log.Verbose("Creating sub-command translations for command '{name}'", nameValues.First()); var commandProperty = createTypeProperties.First(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "commands")); commandProperty.SetValue(translator, new List()); foreach (var value in commandsValues) { var obj = (DisCatSharp.ApplicationCommands.Entities.CommandTranslator)CreateTranslationRecursively(typeof(DisCatSharp.ApplicationCommands.Entities.CommandTranslator), value); if (obj is null) continue; ((List)commandProperty.GetValue(translator)).Add(obj); } if (((List)commandProperty.GetValue(translator)).Count == 0) commandProperty.SetValue(translator, null); } if (optionsValues is not null && createTypeProperties.Any(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "options"))) { Log.Verbose("Creating option translations for command '{name}'", nameValues.First()); var optionProperty = createTypeProperties.First(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "options")); optionProperty.SetValue(translator, new List()); foreach (var value in optionsValues) { var obj = (DisCatSharp.ApplicationCommands.Entities.OptionTranslator)CreateTranslationRecursively(typeof(DisCatSharp.ApplicationCommands.Entities.OptionTranslator), value); if (obj is null) continue; ((List)optionProperty.GetValue(translator)).Add(obj); } if (((List)optionProperty.GetValue(translator)).Count == 0) optionProperty.SetValue(translator, null); } if (choicesValues is not null && createTypeProperties.Any(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "choices"))) { Log.Verbose("Creating choice translations for command '{name}'", nameValues.First()); var choiceProperty = createTypeProperties.First(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "choices")); choiceProperty.SetValue(translator, new List()); foreach (var value in choicesValues) { var obj = (DisCatSharp.ApplicationCommands.Entities.ChoiceTranslator)CreateTranslationRecursively(typeof(DisCatSharp.ApplicationCommands.Entities.ChoiceTranslator), value); if (obj is null) continue; ((List)choiceProperty.GetValue(translator)).Add(obj); } if (((List)choiceProperty.GetValue(translator)).Count == 0) choiceProperty.SetValue(translator, null); } if (groupsValues is not null && createTypeProperties.Any(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "groups"))) { Log.Verbose("Creating group translations for command '{name}'", nameValues.First()); var groupProperty = createTypeProperties.First(x => x.GetCustomAttributes().Any(attr => attr is JsonPropertyAttribute attribute && attribute.PropertyName == "groups")); groupProperty.SetValue(translator, new List()); foreach (var value in groupsValues) { var obj = (DisCatSharp.ApplicationCommands.Entities.SubGroupTranslator)CreateTranslationRecursively(typeof(DisCatSharp.ApplicationCommands.Entities.SubGroupTranslator), value); if (obj is null) continue; ((List)groupProperty.GetValue(translator)).Add(obj); } if (((List)groupProperty.GetValue(translator)).Count == 0) groupProperty.SetValue(translator, null); } return translator; } catch (Exception ex) { Log.Error(ex, "Failed to generate DCS-Compatible Translations"); throw; } } foreach (var translation in bot.LoadedTranslations.CommandList) singleCommandTranslations.Add( (DisCatSharp.ApplicationCommands.Entities.CommandTranslator)CreateTranslationRecursively(typeof(DisCatSharp.ApplicationCommands.Entities.CommandTranslator), translation)); foreach (var translation in bot.LoadedTranslations.CommandList) groupCommandTranslations.Add( (DisCatSharp.ApplicationCommands.Entities.GroupTranslator)CreateTranslationRecursively(typeof(DisCatSharp.ApplicationCommands.Entities.GroupTranslator), translation)); x.AddSingleTranslation(JsonConvert.SerializeObject(singleCommandTranslations)); x.AddGroupTranslation(JsonConvert.SerializeObject(groupCommandTranslations)); } public static async Task Load(Bot bot) { DisCatSharpExtensionsLoader.bot = bot; if (bot.status.LoadedConfig.Secrets.Discord.Token.Length <= 0) { Log.Fatal("No discord token provided"); await Task.Delay(1000); Environment.Exit((int)ExitCodes.NoToken); return; } Log.Debug("Registering DiscordClient.."); bot.DiscordClient = new DiscordShardedClient(new DiscordConfiguration { Token = bot.status.LoadedConfig.Secrets.Discord.Token, TokenType = TokenType.Bot, MinimumLogLevel = Microsoft.Extensions.Logging.LogLevel.Trace, Intents = DiscordIntents.All, AutoReconnect = true, LoggerFactory = bot.msLoggerFactory, HttpTimeout = TimeSpan.FromSeconds(60), MessageCacheSize = 4096, EnableSentry = true, ReportMissingFields = bot.status.LoadedConfig.IsDev, AttachUserInfo = true, DeveloperUserId = 411950662662881290, DisableUpdateCheck = true, }); bot.ExperienceHandler = new(bot); Log.Debug("Registering CommandsNext.."); var cNext = await bot.DiscordClient.UseCommandsNextAsync(new CommandsNextConfiguration { EnableDefaultHelp = false, EnableMentionPrefix = false, IgnoreExtraArguments = true, EnableDms = false, ServiceProvider = new ServiceCollection() .AddSingleton(bot) .BuildServiceProvider(), PrefixResolver = new PrefixResolverDelegate(bot.GetPrefix) }); Log.Debug("Registering DisCatSharp TwoFactor.."); var tfa = bot.DiscordClient.UseTwoFactorAsync(new TwoFactorConfiguration { ResponseConfiguration = new TwoFactorResponseConfiguration { ShowResponse = false, AuthenticatorAccountPrefix = "Project Makoto" }, Issuer = "Project Makoto", }); DiscordEventHandler.SetupEvents(bot); bot.DiscordClient.GuildDownloadCompleted += bot.GuildDownloadCompleted; Log.Debug("Registering Interactivity.."); _ = await bot.DiscordClient.UseInteractivityAsync(new InteractivityConfiguration { }); var appCommands = await bot.DiscordClient.UseApplicationCommandsAsync(new ApplicationCommandsConfiguration { ServiceProvider = new ServiceCollection() .AddSingleton(bot) .BuildServiceProvider(), EnableDefaultHelp = false, EnableLocalization = true, DebugStartup = true }); if (bot.status.CurrentAppHash != bot.status.LoadedConfig.DontModify.LastKnownHash && Directory.Exists("CompiledCommands")) { Log.Debug("Clearing cached Commands.."); await FileExtensions.CleanupFilesAndDirectories(new(), Directory.GetFiles("CompiledCommands").ToList()); } await BasePlugin.RaisePreLogin(bot, bot.DiscordClient); Log.Debug("Compiling Built-In Commands.."); var commandModules = Commands.Commands.GetList(); bot._CommandModules = commandModules; var assemblies = await CommandCompiler.BuildCommands(bot, bot.status.CurrentAppHash, commandModules, null); CommandCompiler.RegisterAssemblies(bot, cNext, appCommands, GetCommandTranslations, assemblies); Log.Debug("Registering Debug Commands.."); appCommands.RegisterGuildCommands(bot.status.LoadedConfig.Discord.DevelopmentGuild, GetCommandTranslations); Log.Debug("Registering Command Converters.."); cNext.RegisterConverter(new CustomArgumentConverter.BoolConverter()); cNext.RegisterConverter(new CustomArgumentConverter.AttachmentConverter()); var commandsNextTypes = new List(); var applicationCommandTypes = new List(); await Util.Initializers.PluginLoader.LoadPluginCommands(bot, cNext, appCommands); _ = Task.Run(async () => { while (!bot.status.DiscordInitialized) await Task.Delay(100); Stopwatch sw = new(); sw.Start(); _ = bot.DiscordClient.UpdateStatusAsync(userStatus: UserStatus.Online, activity: new DiscordActivity("Registering commands..", ActivityType.Custom)); var applicationCommandsExtension = bot.DiscordClient.GetFirstShard().GetApplicationCommands(); while (applicationCommandsExtension?.RegisteredCommands?.Count == 0 && sw.ElapsedMilliseconds < TimeSpan.FromMinutes(5).TotalMilliseconds) await Task.Delay(1000); if (applicationCommandsExtension?.RegisteredCommands?.Count == 0) { Log.Fatal("Commands did not register."); _ = bot.ExitApplication(true); return; } bot.status.DiscordCommandsRegistered = true; while (true) { try { if (bot.DatabaseClient.Disposed) return; await bot.DiscordClient.UpdateStatusAsync(activity: new DiscordActivity($"{bot.DiscordClient.GetGuilds().Count.ToString("N0", CultureInfo.CreateSpecificCulture("en-US"))} guilds | Up for {Math.Round((DateTime.UtcNow - bot.status.startupTime).TotalHours, 1).ToString(CultureInfo.CreateSpecificCulture("en-US"))}h", ActivityType.Custom)); await Task.Delay(30000); } catch (Exception ex) { Log.Error(ex, "Failed to update user status"); await Task.Delay(30000); } } }); } }